@elizaos/plugin-workflow 2.0.0-beta.1
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 +71 -0
- package/auto-enable.ts +18 -0
- package/dist/actions/index.d.ts +2 -0
- package/dist/actions/index.d.ts.map +1 -0
- package/dist/actions/index.js +2 -0
- package/dist/actions/index.js.map +1 -0
- package/dist/actions/workflow.d.ts +23 -0
- package/dist/actions/workflow.d.ts.map +1 -0
- package/dist/actions/workflow.js +425 -0
- package/dist/actions/workflow.js.map +1 -0
- package/dist/data/defaultNodes.json +9887 -0
- package/dist/data/schemaIndex.json +1 -0
- package/dist/data/triggerSchemaIndex.json +1 -0
- package/dist/db/index.d.ts +2 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +2 -0
- package/dist/db/index.js.map +1 -0
- package/dist/db/schema.d.ts +588 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +59 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +126 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/automations-builder.d.ts +21 -0
- package/dist/lib/automations-builder.d.ts.map +1 -0
- package/dist/lib/automations-builder.js +557 -0
- package/dist/lib/automations-builder.js.map +1 -0
- package/dist/lib/automations-types.d.ts +153 -0
- package/dist/lib/automations-types.d.ts.map +1 -0
- package/dist/lib/automations-types.js +191 -0
- package/dist/lib/automations-types.js.map +1 -0
- package/dist/lib/index.d.ts +3 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +3 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/legacy-task-migration.d.ts +20 -0
- package/dist/lib/legacy-task-migration.d.ts.map +1 -0
- package/dist/lib/legacy-task-migration.js +110 -0
- package/dist/lib/legacy-task-migration.js.map +1 -0
- package/dist/lib/legacy-text-trigger-migration.d.ts +18 -0
- package/dist/lib/legacy-text-trigger-migration.d.ts.map +1 -0
- package/dist/lib/legacy-text-trigger-migration.js +131 -0
- package/dist/lib/legacy-text-trigger-migration.js.map +1 -0
- package/dist/lib/workflow-clarification.d.ts +113 -0
- package/dist/lib/workflow-clarification.d.ts.map +1 -0
- package/dist/lib/workflow-clarification.js +425 -0
- package/dist/lib/workflow-clarification.js.map +1 -0
- package/dist/plugin-routes.d.ts +9 -0
- package/dist/plugin-routes.d.ts.map +1 -0
- package/dist/plugin-routes.js +147 -0
- package/dist/plugin-routes.js.map +1 -0
- package/dist/providers/activeWorkflows.d.ts +11 -0
- package/dist/providers/activeWorkflows.d.ts.map +1 -0
- package/dist/providers/activeWorkflows.js +72 -0
- package/dist/providers/activeWorkflows.js.map +1 -0
- package/dist/providers/index.d.ts +4 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +4 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/pendingDraft.d.ts +9 -0
- package/dist/providers/pendingDraft.d.ts.map +1 -0
- package/dist/providers/pendingDraft.js +48 -0
- package/dist/providers/pendingDraft.js.map +1 -0
- package/dist/providers/workflowStatus.d.ts +3 -0
- package/dist/providers/workflowStatus.d.ts.map +1 -0
- package/dist/providers/workflowStatus.js +69 -0
- package/dist/providers/workflowStatus.js.map +1 -0
- package/dist/register-routes.d.ts +2 -0
- package/dist/register-routes.d.ts.map +1 -0
- package/dist/register-routes.js +6 -0
- package/dist/register-routes.js.map +1 -0
- package/dist/routes/_helpers.d.ts +11 -0
- package/dist/routes/_helpers.d.ts.map +1 -0
- package/dist/routes/_helpers.js +22 -0
- package/dist/routes/_helpers.js.map +1 -0
- package/dist/routes/automations.d.ts +19 -0
- package/dist/routes/automations.d.ts.map +1 -0
- package/dist/routes/automations.js +32 -0
- package/dist/routes/automations.js.map +1 -0
- package/dist/routes/embedded-webhooks.d.ts +3 -0
- package/dist/routes/embedded-webhooks.d.ts.map +1 -0
- package/dist/routes/embedded-webhooks.js +47 -0
- package/dist/routes/embedded-webhooks.js.map +1 -0
- package/dist/routes/executions.d.ts +3 -0
- package/dist/routes/executions.d.ts.map +1 -0
- package/dist/routes/executions.js +58 -0
- package/dist/routes/executions.js.map +1 -0
- package/dist/routes/index.d.ts +4 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +14 -0
- package/dist/routes/index.js.map +1 -0
- package/dist/routes/nodes.d.ts +3 -0
- package/dist/routes/nodes.d.ts.map +1 -0
- package/dist/routes/nodes.js +168 -0
- package/dist/routes/nodes.js.map +1 -0
- package/dist/routes/validation.d.ts +3 -0
- package/dist/routes/validation.d.ts.map +1 -0
- package/dist/routes/validation.js +41 -0
- package/dist/routes/validation.js.map +1 -0
- package/dist/routes/workflow-routes.d.ts +27 -0
- package/dist/routes/workflow-routes.d.ts.map +1 -0
- package/dist/routes/workflow-routes.js +326 -0
- package/dist/routes/workflow-routes.js.map +1 -0
- package/dist/routes/workflows.d.ts +3 -0
- package/dist/routes/workflows.d.ts.map +1 -0
- package/dist/routes/workflows.js +252 -0
- package/dist/routes/workflows.js.map +1 -0
- package/dist/schemas/draftIntent.d.ts +22 -0
- package/dist/schemas/draftIntent.d.ts.map +1 -0
- package/dist/schemas/draftIntent.js +22 -0
- package/dist/schemas/draftIntent.js.map +1 -0
- package/dist/schemas/feasibility.d.ts +13 -0
- package/dist/schemas/feasibility.d.ts.map +1 -0
- package/dist/schemas/feasibility.js +9 -0
- package/dist/schemas/feasibility.js.map +1 -0
- package/dist/schemas/index.d.ts +5 -0
- package/dist/schemas/index.d.ts.map +1 -0
- package/dist/schemas/index.js +5 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/keywordExtraction.d.ts +14 -0
- package/dist/schemas/keywordExtraction.d.ts.map +1 -0
- package/dist/schemas/keywordExtraction.js +12 -0
- package/dist/schemas/keywordExtraction.js.map +1 -0
- package/dist/schemas/workflowMatching.d.ts +36 -0
- package/dist/schemas/workflowMatching.d.ts.map +1 -0
- package/dist/schemas/workflowMatching.js +30 -0
- package/dist/schemas/workflowMatching.js.map +1 -0
- package/dist/services/embedded-workflow-service.d.ts +106 -0
- package/dist/services/embedded-workflow-service.d.ts.map +1 -0
- package/dist/services/embedded-workflow-service.js +1900 -0
- package/dist/services/embedded-workflow-service.js.map +1 -0
- package/dist/services/index.d.ts +5 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +5 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/workflow-credential-store.d.ts +27 -0
- package/dist/services/workflow-credential-store.d.ts.map +1 -0
- package/dist/services/workflow-credential-store.js +92 -0
- package/dist/services/workflow-credential-store.js.map +1 -0
- package/dist/services/workflow-dispatch.d.ts +41 -0
- package/dist/services/workflow-dispatch.d.ts.map +1 -0
- package/dist/services/workflow-dispatch.js +86 -0
- package/dist/services/workflow-dispatch.js.map +1 -0
- package/dist/services/workflow-service.d.ts +63 -0
- package/dist/services/workflow-service.d.ts.map +1 -0
- package/dist/services/workflow-service.js +492 -0
- package/dist/services/workflow-service.js.map +1 -0
- package/dist/trigger-routes.d.ts +153 -0
- package/dist/trigger-routes.d.ts.map +1 -0
- package/dist/trigger-routes.js +424 -0
- package/dist/trigger-routes.js.map +1 -0
- package/dist/types/index.d.ts +457 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +59 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/catalog.d.ts +16 -0
- package/dist/utils/catalog.d.ts.map +1 -0
- package/dist/utils/catalog.js +211 -0
- package/dist/utils/catalog.js.map +1 -0
- package/dist/utils/clarification.d.ts +17 -0
- package/dist/utils/clarification.d.ts.map +1 -0
- package/dist/utils/clarification.js +46 -0
- package/dist/utils/clarification.js.map +1 -0
- package/dist/utils/context.d.ts +4 -0
- package/dist/utils/context.d.ts.map +1 -0
- package/dist/utils/context.js +18 -0
- package/dist/utils/context.js.map +1 -0
- package/dist/utils/credentialResolver.d.ts +22 -0
- package/dist/utils/credentialResolver.d.ts.map +1 -0
- package/dist/utils/credentialResolver.js +146 -0
- package/dist/utils/credentialResolver.js.map +1 -0
- package/dist/utils/generation.d.ts +36 -0
- package/dist/utils/generation.d.ts.map +1 -0
- package/dist/utils/generation.js +701 -0
- package/dist/utils/generation.js.map +1 -0
- package/dist/utils/host-capabilities.d.ts +27 -0
- package/dist/utils/host-capabilities.d.ts.map +1 -0
- package/dist/utils/host-capabilities.js +59 -0
- package/dist/utils/host-capabilities.js.map +1 -0
- package/dist/utils/inferSyntheticOutputSchema.d.ts +20 -0
- package/dist/utils/inferSyntheticOutputSchema.d.ts.map +1 -0
- package/dist/utils/inferSyntheticOutputSchema.js +151 -0
- package/dist/utils/inferSyntheticOutputSchema.js.map +1 -0
- package/dist/utils/outputSchema.d.ts +26 -0
- package/dist/utils/outputSchema.d.ts.map +1 -0
- package/dist/utils/outputSchema.js +297 -0
- package/dist/utils/outputSchema.js.map +1 -0
- package/dist/utils/validateAndRepair.d.ts +41 -0
- package/dist/utils/validateAndRepair.d.ts.map +1 -0
- package/dist/utils/validateAndRepair.js +483 -0
- package/dist/utils/validateAndRepair.js.map +1 -0
- package/dist/utils/workflow-prompts/actionResponse.d.ts +2 -0
- package/dist/utils/workflow-prompts/actionResponse.d.ts.map +1 -0
- package/dist/utils/workflow-prompts/actionResponse.js +17 -0
- package/dist/utils/workflow-prompts/actionResponse.js.map +1 -0
- package/dist/utils/workflow-prompts/draftIntent.d.ts +2 -0
- package/dist/utils/workflow-prompts/draftIntent.d.ts.map +1 -0
- package/dist/utils/workflow-prompts/draftIntent.js +23 -0
- package/dist/utils/workflow-prompts/draftIntent.js.map +1 -0
- package/dist/utils/workflow-prompts/feasibilityCheck.d.ts +2 -0
- package/dist/utils/workflow-prompts/feasibilityCheck.d.ts.map +1 -0
- package/dist/utils/workflow-prompts/feasibilityCheck.js +21 -0
- package/dist/utils/workflow-prompts/feasibilityCheck.js.map +1 -0
- package/dist/utils/workflow-prompts/fieldCorrection.d.ts +3 -0
- package/dist/utils/workflow-prompts/fieldCorrection.d.ts.map +1 -0
- package/dist/utils/workflow-prompts/fieldCorrection.js +20 -0
- package/dist/utils/workflow-prompts/fieldCorrection.js.map +1 -0
- package/dist/utils/workflow-prompts/index.d.ts +8 -0
- package/dist/utils/workflow-prompts/index.d.ts.map +1 -0
- package/dist/utils/workflow-prompts/index.js +8 -0
- package/dist/utils/workflow-prompts/index.js.map +1 -0
- package/dist/utils/workflow-prompts/keywordExtraction.d.ts +2 -0
- package/dist/utils/workflow-prompts/keywordExtraction.d.ts.map +1 -0
- package/dist/utils/workflow-prompts/keywordExtraction.js +21 -0
- package/dist/utils/workflow-prompts/keywordExtraction.js.map +1 -0
- package/dist/utils/workflow-prompts/parameterCorrection.d.ts +3 -0
- package/dist/utils/workflow-prompts/parameterCorrection.d.ts.map +1 -0
- package/dist/utils/workflow-prompts/parameterCorrection.js +29 -0
- package/dist/utils/workflow-prompts/parameterCorrection.js.map +1 -0
- package/dist/utils/workflow-prompts/workflowGeneration.d.ts +2 -0
- package/dist/utils/workflow-prompts/workflowGeneration.d.ts.map +1 -0
- package/dist/utils/workflow-prompts/workflowGeneration.js +529 -0
- package/dist/utils/workflow-prompts/workflowGeneration.js.map +1 -0
- package/dist/utils/workflow-prompts/workflowMatching.d.ts +2 -0
- package/dist/utils/workflow-prompts/workflowMatching.d.ts.map +1 -0
- package/dist/utils/workflow-prompts/workflowMatching.js +23 -0
- package/dist/utils/workflow-prompts/workflowMatching.js.map +1 -0
- package/dist/utils/workflow.d.ts +62 -0
- package/dist/utils/workflow.d.ts.map +1 -0
- package/dist/utils/workflow.js +712 -0
- package/dist/utils/workflow.js.map +1 -0
- package/package.json +87 -0
- package/src/actions/index.ts +1 -0
- package/src/actions/workflow.ts +494 -0
- package/src/data/defaultNodes.json +9887 -0
- package/src/data/schemaIndex.json +1 -0
- package/src/data/triggerSchemaIndex.json +1 -0
- package/src/db/index.ts +8 -0
- package/src/db/schema.ts +94 -0
- package/src/index.ts +179 -0
- package/src/lib/automations-builder.ts +679 -0
- package/src/lib/automations-types.ts +391 -0
- package/src/lib/index.ts +8 -0
- package/src/lib/legacy-task-migration.ts +143 -0
- package/src/lib/legacy-text-trigger-migration.ts +178 -0
- package/src/lib/workflow-clarification.ts +497 -0
- package/src/plugin-routes.ts +164 -0
- package/src/providers/activeWorkflows.ts +81 -0
- package/src/providers/index.ts +3 -0
- package/src/providers/pendingDraft.ts +55 -0
- package/src/providers/workflowStatus.ts +88 -0
- package/src/register-routes.ts +6 -0
- package/src/routes/_helpers.ts +27 -0
- package/src/routes/automations.ts +46 -0
- package/src/routes/embedded-webhooks.ts +64 -0
- package/src/routes/executions.ts +75 -0
- package/src/routes/index.ts +16 -0
- package/src/routes/nodes.ts +211 -0
- package/src/routes/validation.ts +51 -0
- package/src/routes/workflow-routes.ts +469 -0
- package/src/routes/workflows.ts +310 -0
- package/src/schemas/draftIntent.ts +21 -0
- package/src/schemas/feasibility.ts +8 -0
- package/src/schemas/index.ts +4 -0
- package/src/schemas/keywordExtraction.ts +11 -0
- package/src/schemas/workflowMatching.ts +29 -0
- package/src/services/embedded-workflow-service.ts +2224 -0
- package/src/services/index.ts +17 -0
- package/src/services/workflow-credential-store.ts +132 -0
- package/src/services/workflow-dispatch.ts +121 -0
- package/src/services/workflow-service.ts +839 -0
- package/src/trigger-routes.ts +714 -0
- package/src/types/index.ts +562 -0
- package/src/utils/catalog.ts +260 -0
- package/src/utils/clarification.ts +52 -0
- package/src/utils/context.ts +22 -0
- package/src/utils/credentialResolver.ts +234 -0
- package/src/utils/generation.ts +987 -0
- package/src/utils/host-capabilities.ts +81 -0
- package/src/utils/inferSyntheticOutputSchema.ts +163 -0
- package/src/utils/outputSchema.ts +372 -0
- package/src/utils/validateAndRepair.ts +610 -0
- package/src/utils/workflow-prompts/actionResponse.ts +16 -0
- package/src/utils/workflow-prompts/draftIntent.ts +22 -0
- package/src/utils/workflow-prompts/feasibilityCheck.ts +20 -0
- package/src/utils/workflow-prompts/fieldCorrection.ts +20 -0
- package/src/utils/workflow-prompts/index.ts +10 -0
- package/src/utils/workflow-prompts/keywordExtraction.ts +20 -0
- package/src/utils/workflow-prompts/parameterCorrection.ts +29 -0
- package/src/utils/workflow-prompts/workflowGeneration.ts +528 -0
- package/src/utils/workflow-prompts/workflowMatching.ts +22 -0
- package/src/utils/workflow.ts +895 -0
|
@@ -0,0 +1,1900 @@
|
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { logger, Service } from '@elizaos/core';
|
|
3
|
+
import { and, desc, eq, sql } from 'drizzle-orm';
|
|
4
|
+
import { embeddedCredentials, embeddedExecutions, embeddedTags, embeddedWorkflows, } from '../db/schema';
|
|
5
|
+
import { WorkflowApiError } from '../types/index';
|
|
6
|
+
import { detectHostCapabilities } from '../utils/host-capabilities';
|
|
7
|
+
export const EMBEDDED_WORKFLOW_SERVICE_TYPE = 'embedded_workflow_service';
|
|
8
|
+
/** TaskWorker name for scheduled workflow runs. Tasks created with this name
|
|
9
|
+
* carry metadata.workflowId + metadata.kind = 'workflow' and get fired by
|
|
10
|
+
* the core TaskService on the configured updateInterval. */
|
|
11
|
+
export const WORKFLOW_RUN_TASK_WORKER_NAME = 'workflow.run';
|
|
12
|
+
/** TaskWorker name for one-shot webhook-triggered workflow runs. A future
|
|
13
|
+
* webhook trigger provider creates a one-shot Task pointing at this worker;
|
|
14
|
+
* payload travels in metadata.payload. */
|
|
15
|
+
export const WORKFLOW_WEBHOOK_TASK_WORKER_NAME = 'workflow.webhook';
|
|
16
|
+
/** Discriminator on TaskMetadata so the UI can route workflow tasks. */
|
|
17
|
+
export const WORKFLOW_TASK_KIND = 'workflow';
|
|
18
|
+
/** Stable tag used on every workflow-backed Task so we can list+delete them. */
|
|
19
|
+
const WORKFLOW_TASK_TAG = 'workflow';
|
|
20
|
+
const EMBEDDED_HOST = 'embedded://local';
|
|
21
|
+
const DEFAULT_SCHEDULE_INTERVAL_MS = 60_000;
|
|
22
|
+
let loadedQuickJs = null;
|
|
23
|
+
async function loadQuickJs() {
|
|
24
|
+
loadedQuickJs ??= import('quickjs-emscripten');
|
|
25
|
+
return loadedQuickJs;
|
|
26
|
+
}
|
|
27
|
+
function cloneJson(value) {
|
|
28
|
+
return JSON.parse(JSON.stringify(value));
|
|
29
|
+
}
|
|
30
|
+
function nowIso() {
|
|
31
|
+
return new Date().toISOString();
|
|
32
|
+
}
|
|
33
|
+
function isRecord(value) {
|
|
34
|
+
return Boolean(value && typeof value === 'object' && !Array.isArray(value));
|
|
35
|
+
}
|
|
36
|
+
function normalizeWorkflowPayload(workflow, id, active) {
|
|
37
|
+
return {
|
|
38
|
+
...cloneJson(workflow),
|
|
39
|
+
id,
|
|
40
|
+
active,
|
|
41
|
+
settings: {
|
|
42
|
+
executionOrder: 'v1',
|
|
43
|
+
...(workflow.settings ?? {}),
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function responseFromWorkflow(workflow, createdAt, updatedAt, versionId) {
|
|
48
|
+
return {
|
|
49
|
+
...cloneJson(workflow),
|
|
50
|
+
id: workflow.id ?? randomUUID(),
|
|
51
|
+
createdAt,
|
|
52
|
+
updatedAt,
|
|
53
|
+
versionId,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function readString(value, fallback) {
|
|
57
|
+
return typeof value === 'string' && value.length > 0 ? value : fallback;
|
|
58
|
+
}
|
|
59
|
+
function readNumber(value, fallback) {
|
|
60
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
61
|
+
}
|
|
62
|
+
function resolveScheduleIntervalMs(parameters) {
|
|
63
|
+
const explicitMs = readNumber(parameters.intervalMs, NaN);
|
|
64
|
+
if (Number.isFinite(explicitMs) && explicitMs > 0)
|
|
65
|
+
return explicitMs;
|
|
66
|
+
const explicitSeconds = readNumber(parameters.intervalSeconds, NaN);
|
|
67
|
+
if (Number.isFinite(explicitSeconds) && explicitSeconds > 0)
|
|
68
|
+
return explicitSeconds * 1000;
|
|
69
|
+
const rule = isRecord(parameters.rule) ? parameters.rule : null;
|
|
70
|
+
const intervals = Array.isArray(rule?.interval) ? rule.interval : [];
|
|
71
|
+
const first = intervals.find(isRecord);
|
|
72
|
+
if (!first)
|
|
73
|
+
return DEFAULT_SCHEDULE_INTERVAL_MS;
|
|
74
|
+
const unit = readString(first.field, 'minutes');
|
|
75
|
+
if (unit === 'seconds')
|
|
76
|
+
return readNumber(first.secondsInterval, 60) * 1000;
|
|
77
|
+
if (unit === 'minutes')
|
|
78
|
+
return readNumber(first.minutesInterval, 1) * 60_000;
|
|
79
|
+
if (unit === 'hours')
|
|
80
|
+
return readNumber(first.hoursInterval, 1) * 3_600_000;
|
|
81
|
+
if (unit === 'days')
|
|
82
|
+
return readNumber(first.daysInterval, 1) * 86_400_000;
|
|
83
|
+
return DEFAULT_SCHEDULE_INTERVAL_MS;
|
|
84
|
+
}
|
|
85
|
+
function normalizeWebhookPath(path) {
|
|
86
|
+
return readString(path, '')
|
|
87
|
+
.trim()
|
|
88
|
+
.replace(/^\/+|\/+$/g, '');
|
|
89
|
+
}
|
|
90
|
+
function normalizeHeaderEntries(value) {
|
|
91
|
+
const headers = {};
|
|
92
|
+
if (isRecord(value)) {
|
|
93
|
+
for (const [key, headerValue] of Object.entries(value)) {
|
|
94
|
+
if (typeof headerValue !== 'undefined')
|
|
95
|
+
headers[key] = String(headerValue);
|
|
96
|
+
}
|
|
97
|
+
return headers;
|
|
98
|
+
}
|
|
99
|
+
if (Array.isArray(value)) {
|
|
100
|
+
for (const entry of value) {
|
|
101
|
+
if (!isRecord(entry))
|
|
102
|
+
continue;
|
|
103
|
+
const name = readString(entry.name, '');
|
|
104
|
+
if (name)
|
|
105
|
+
headers[name] = String(entry.value ?? '');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return headers;
|
|
109
|
+
}
|
|
110
|
+
function collectParametersList(value) {
|
|
111
|
+
const out = {};
|
|
112
|
+
if (Array.isArray(value)) {
|
|
113
|
+
for (const entry of value) {
|
|
114
|
+
if (!isRecord(entry))
|
|
115
|
+
continue;
|
|
116
|
+
const name = readString(entry.name, '');
|
|
117
|
+
if (name)
|
|
118
|
+
out[name] = entry.value ?? '';
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return out;
|
|
122
|
+
}
|
|
123
|
+
function normalizeExecutionItem(item, pairedItem) {
|
|
124
|
+
if (isRecord(item) && 'json' in item) {
|
|
125
|
+
return {
|
|
126
|
+
json: item.json,
|
|
127
|
+
...(item.pairedItem
|
|
128
|
+
? { pairedItem: item.pairedItem }
|
|
129
|
+
: {}),
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
json: (isRecord(item) ? item : { value: item }),
|
|
134
|
+
...(pairedItem ? { pairedItem } : {}),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function normalizeExecutionItems(value, fallback) {
|
|
138
|
+
if (typeof value === 'undefined')
|
|
139
|
+
return fallback.map((item) => normalizeExecutionItem(item));
|
|
140
|
+
if (Array.isArray(value)) {
|
|
141
|
+
return value.map((item, index) => normalizeExecutionItem(item, { item: index }));
|
|
142
|
+
}
|
|
143
|
+
if (isRecord(value) && Array.isArray(value.items)) {
|
|
144
|
+
return value.items.map((item, index) => normalizeExecutionItem(item, { item: index }));
|
|
145
|
+
}
|
|
146
|
+
return [normalizeExecutionItem(value)];
|
|
147
|
+
}
|
|
148
|
+
function readPath(source, path) {
|
|
149
|
+
const parts = path
|
|
150
|
+
.replace(/\[(?:'([^']+)'|"([^"]+)"|(\d+))\]/g, '.$1$2$3')
|
|
151
|
+
.split('.')
|
|
152
|
+
.map((part) => part.trim())
|
|
153
|
+
.filter(Boolean);
|
|
154
|
+
let current = source;
|
|
155
|
+
for (const part of parts) {
|
|
156
|
+
if (!isRecord(current) && !Array.isArray(current))
|
|
157
|
+
return undefined;
|
|
158
|
+
current = current[part];
|
|
159
|
+
}
|
|
160
|
+
return current;
|
|
161
|
+
}
|
|
162
|
+
function resolveParameterValue(value, item) {
|
|
163
|
+
if (typeof value !== 'string')
|
|
164
|
+
return value;
|
|
165
|
+
const trimmed = value.trim();
|
|
166
|
+
const expression = trimmed.startsWith('={{') && trimmed.endsWith('}}')
|
|
167
|
+
? trimmed.slice(3, -2).trim()
|
|
168
|
+
: trimmed.startsWith('{{') && trimmed.endsWith('}}')
|
|
169
|
+
? trimmed.slice(2, -2).trim()
|
|
170
|
+
: trimmed.startsWith('=')
|
|
171
|
+
? trimmed.slice(1).trim()
|
|
172
|
+
: trimmed;
|
|
173
|
+
const jsonPath = expression.match(/^\$json(?:\.|\[['"]?)(.+?)(?:['"]?\])?$/);
|
|
174
|
+
if (jsonPath?.[1]) {
|
|
175
|
+
return readPath(item.json, jsonPath[1]);
|
|
176
|
+
}
|
|
177
|
+
const itemJsonPath = expression.match(/^\$input\.item\.json(?:\.|\[['"]?)(.+?)(?:['"]?\])?$/);
|
|
178
|
+
if (itemJsonPath?.[1]) {
|
|
179
|
+
return readPath(item.json, itemJsonPath[1]);
|
|
180
|
+
}
|
|
181
|
+
return value;
|
|
182
|
+
}
|
|
183
|
+
function isEmptyValue(value) {
|
|
184
|
+
return (value === null ||
|
|
185
|
+
typeof value === 'undefined' ||
|
|
186
|
+
value === '' ||
|
|
187
|
+
(Array.isArray(value) && value.length === 0) ||
|
|
188
|
+
(isRecord(value) && Object.keys(value).length === 0));
|
|
189
|
+
}
|
|
190
|
+
function compareCondition(left, operation, right, item) {
|
|
191
|
+
const resolvedLeft = resolveParameterValue(left, item);
|
|
192
|
+
const resolvedRight = resolveParameterValue(right, item);
|
|
193
|
+
const op = operation.toLowerCase();
|
|
194
|
+
if (op === 'exists')
|
|
195
|
+
return typeof resolvedLeft !== 'undefined' && resolvedLeft !== null;
|
|
196
|
+
if (op === 'notexists')
|
|
197
|
+
return typeof resolvedLeft === 'undefined' || resolvedLeft === null;
|
|
198
|
+
if (op === 'empty')
|
|
199
|
+
return isEmptyValue(resolvedLeft);
|
|
200
|
+
if (op === 'notempty')
|
|
201
|
+
return !isEmptyValue(resolvedLeft);
|
|
202
|
+
if (op === 'true')
|
|
203
|
+
return resolvedLeft === true || resolvedLeft === 'true';
|
|
204
|
+
if (op === 'false')
|
|
205
|
+
return resolvedLeft === false || resolvedLeft === 'false';
|
|
206
|
+
if (op === 'contains')
|
|
207
|
+
return String(resolvedLeft ?? '').includes(String(resolvedRight ?? ''));
|
|
208
|
+
if (op === 'notcontains')
|
|
209
|
+
return !String(resolvedLeft ?? '').includes(String(resolvedRight ?? ''));
|
|
210
|
+
if (op === 'startswith')
|
|
211
|
+
return String(resolvedLeft ?? '').startsWith(String(resolvedRight ?? ''));
|
|
212
|
+
if (op === 'endswith')
|
|
213
|
+
return String(resolvedLeft ?? '').endsWith(String(resolvedRight ?? ''));
|
|
214
|
+
if (op === 'larger' || op === 'largerorequal' || op === 'gt' || op === 'gte') {
|
|
215
|
+
return op.includes('equal') || op === 'gte'
|
|
216
|
+
? Number(resolvedLeft) >= Number(resolvedRight)
|
|
217
|
+
: Number(resolvedLeft) > Number(resolvedRight);
|
|
218
|
+
}
|
|
219
|
+
if (op === 'smaller' || op === 'smallerorequal' || op === 'lt' || op === 'lte') {
|
|
220
|
+
return op.includes('equal') || op === 'lte'
|
|
221
|
+
? Number(resolvedLeft) <= Number(resolvedRight)
|
|
222
|
+
: Number(resolvedLeft) < Number(resolvedRight);
|
|
223
|
+
}
|
|
224
|
+
if (op === 'notequal' || op === 'notequals')
|
|
225
|
+
return resolvedLeft !== resolvedRight;
|
|
226
|
+
return (resolvedLeft === resolvedRight || String(resolvedLeft ?? '') === String(resolvedRight ?? ''));
|
|
227
|
+
}
|
|
228
|
+
function collectConditionEntries(parameters) {
|
|
229
|
+
const conditions = isRecord(parameters.conditions) ? parameters.conditions : {};
|
|
230
|
+
const modern = Array.isArray(conditions.conditions) ? conditions.conditions : [];
|
|
231
|
+
const out = [];
|
|
232
|
+
for (const condition of modern) {
|
|
233
|
+
if (!isRecord(condition))
|
|
234
|
+
continue;
|
|
235
|
+
const operator = isRecord(condition.operator) ? condition.operator : {};
|
|
236
|
+
out.push({
|
|
237
|
+
left: condition.leftValue ?? condition.value1,
|
|
238
|
+
operation: readString(operator.operation ?? condition.operation, 'equals'),
|
|
239
|
+
right: condition.rightValue ?? condition.value2,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
for (const group of Object.values(conditions)) {
|
|
243
|
+
if (!Array.isArray(group))
|
|
244
|
+
continue;
|
|
245
|
+
for (const condition of group) {
|
|
246
|
+
if (!isRecord(condition))
|
|
247
|
+
continue;
|
|
248
|
+
out.push({
|
|
249
|
+
left: condition.value1 ?? condition.leftValue,
|
|
250
|
+
operation: readString(condition.operation, 'equals'),
|
|
251
|
+
right: condition.value2 ?? condition.rightValue,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return out;
|
|
256
|
+
}
|
|
257
|
+
function evaluateConditions(parameters, item) {
|
|
258
|
+
const conditions = collectConditionEntries(parameters);
|
|
259
|
+
if (conditions.length === 0)
|
|
260
|
+
return true;
|
|
261
|
+
const combinator = readString(isRecord(parameters.conditions) ? parameters.conditions.combinator : undefined, 'and').toLowerCase();
|
|
262
|
+
const results = conditions.map((condition) => compareCondition(condition.left, condition.operation, condition.right, item));
|
|
263
|
+
return combinator === 'or' ? results.some(Boolean) : results.every(Boolean);
|
|
264
|
+
}
|
|
265
|
+
async function parseResponseBody(response) {
|
|
266
|
+
const text = await response.text();
|
|
267
|
+
if (!text)
|
|
268
|
+
return null;
|
|
269
|
+
const contentType = response.headers.get('content-type') ?? '';
|
|
270
|
+
if (contentType.includes('application/json')) {
|
|
271
|
+
try {
|
|
272
|
+
return JSON.parse(text);
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return text;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return text;
|
|
279
|
+
}
|
|
280
|
+
function createScheduleTriggerNode() {
|
|
281
|
+
return {
|
|
282
|
+
description: {
|
|
283
|
+
displayName: 'Schedule Trigger',
|
|
284
|
+
name: 'workflows-nodes-base.scheduleTrigger',
|
|
285
|
+
group: ['trigger'],
|
|
286
|
+
version: [1, 1.1, 1.2],
|
|
287
|
+
description: 'Starts the workflow on a schedule.',
|
|
288
|
+
defaults: { name: 'Schedule Trigger' },
|
|
289
|
+
inputs: [],
|
|
290
|
+
outputs: ['main'],
|
|
291
|
+
properties: [],
|
|
292
|
+
capabilities: { requiresLongRunning: true },
|
|
293
|
+
},
|
|
294
|
+
async execute() {
|
|
295
|
+
return [
|
|
296
|
+
[
|
|
297
|
+
{
|
|
298
|
+
json: {
|
|
299
|
+
firedAt: new Date().toISOString(),
|
|
300
|
+
trigger: 'schedule',
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
];
|
|
305
|
+
},
|
|
306
|
+
async trigger() {
|
|
307
|
+
return {};
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function createSetNode() {
|
|
312
|
+
return {
|
|
313
|
+
description: {
|
|
314
|
+
displayName: 'Edit Fields (Set)',
|
|
315
|
+
name: 'workflows-nodes-base.set',
|
|
316
|
+
group: ['transform'],
|
|
317
|
+
version: [1, 2, 3, 3.1, 3.2, 3.3, 3.4],
|
|
318
|
+
description: 'Sets values on the current item.',
|
|
319
|
+
defaults: { name: 'Edit Fields' },
|
|
320
|
+
inputs: ['main'],
|
|
321
|
+
outputs: ['main'],
|
|
322
|
+
properties: [
|
|
323
|
+
{
|
|
324
|
+
displayName: 'Include Other Fields',
|
|
325
|
+
name: 'includeOtherFields',
|
|
326
|
+
type: 'boolean',
|
|
327
|
+
default: true,
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
displayName: 'Assignments',
|
|
331
|
+
name: 'assignments',
|
|
332
|
+
type: 'fixedCollection',
|
|
333
|
+
typeOptions: { multipleValues: true },
|
|
334
|
+
default: {},
|
|
335
|
+
options: [
|
|
336
|
+
{
|
|
337
|
+
displayName: 'Assignment',
|
|
338
|
+
name: 'assignments',
|
|
339
|
+
values: [
|
|
340
|
+
{ displayName: 'Name', name: 'name', type: 'string', default: '' },
|
|
341
|
+
{ displayName: 'Value', name: 'value', type: 'string', default: '' },
|
|
342
|
+
],
|
|
343
|
+
},
|
|
344
|
+
],
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
displayName: 'Values',
|
|
348
|
+
name: 'values',
|
|
349
|
+
type: 'json',
|
|
350
|
+
default: {},
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
displayName: 'Fields',
|
|
354
|
+
name: 'fields',
|
|
355
|
+
type: 'json',
|
|
356
|
+
default: {},
|
|
357
|
+
},
|
|
358
|
+
],
|
|
359
|
+
},
|
|
360
|
+
async execute() {
|
|
361
|
+
const inputItems = this.getInputData();
|
|
362
|
+
const sourceItems = inputItems.length > 0 ? inputItems : [{ json: {} }];
|
|
363
|
+
const output = [];
|
|
364
|
+
const nodeParameters = this.getNode().parameters;
|
|
365
|
+
for (let itemIndex = 0; itemIndex < sourceItems.length; itemIndex++) {
|
|
366
|
+
const includeOtherFields = nodeParameters.includeOtherFields !== false;
|
|
367
|
+
const base = includeOtherFields
|
|
368
|
+
? { ...(sourceItems[itemIndex]?.json ?? {}) }
|
|
369
|
+
: {};
|
|
370
|
+
const assignmentContainer = isRecord(nodeParameters.assignments)
|
|
371
|
+
? nodeParameters.assignments
|
|
372
|
+
: {};
|
|
373
|
+
const assignments = Array.isArray(assignmentContainer.assignments)
|
|
374
|
+
? assignmentContainer.assignments
|
|
375
|
+
: [];
|
|
376
|
+
for (const assignment of assignments) {
|
|
377
|
+
const name = readString(assignment.name, '');
|
|
378
|
+
if (name)
|
|
379
|
+
base[name] = assignment.value;
|
|
380
|
+
}
|
|
381
|
+
const values = isRecord(nodeParameters.values) ? nodeParameters.values : {};
|
|
382
|
+
for (const group of Object.values(values)) {
|
|
383
|
+
if (!Array.isArray(group))
|
|
384
|
+
continue;
|
|
385
|
+
for (const entry of group) {
|
|
386
|
+
if (!isRecord(entry))
|
|
387
|
+
continue;
|
|
388
|
+
const name = readString(entry.name, '');
|
|
389
|
+
if (name)
|
|
390
|
+
base[name] = entry.value;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const fields = isRecord(nodeParameters.fields) ? nodeParameters.fields : {};
|
|
394
|
+
if (isRecord(fields)) {
|
|
395
|
+
Object.assign(base, fields);
|
|
396
|
+
}
|
|
397
|
+
output.push({
|
|
398
|
+
json: base,
|
|
399
|
+
pairedItem: { item: itemIndex },
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
return [output];
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
function createHttpRequestNode() {
|
|
407
|
+
return {
|
|
408
|
+
description: {
|
|
409
|
+
displayName: 'HTTP Request',
|
|
410
|
+
name: 'workflows-nodes-base.httpRequest',
|
|
411
|
+
group: ['output'],
|
|
412
|
+
version: [1, 2, 3, 4, 4.1, 4.2],
|
|
413
|
+
description: 'Makes an HTTP request.',
|
|
414
|
+
defaults: { name: 'HTTP Request' },
|
|
415
|
+
inputs: ['main'],
|
|
416
|
+
outputs: ['main'],
|
|
417
|
+
properties: [
|
|
418
|
+
{
|
|
419
|
+
displayName: 'Method',
|
|
420
|
+
name: 'method',
|
|
421
|
+
type: 'options',
|
|
422
|
+
default: 'GET',
|
|
423
|
+
options: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD'].map((method) => ({
|
|
424
|
+
name: method,
|
|
425
|
+
value: method,
|
|
426
|
+
})),
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
displayName: 'URL',
|
|
430
|
+
name: 'url',
|
|
431
|
+
type: 'string',
|
|
432
|
+
default: '',
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
displayName: 'Headers',
|
|
436
|
+
name: 'headers',
|
|
437
|
+
type: 'json',
|
|
438
|
+
default: {},
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
displayName: 'Header Parameters',
|
|
442
|
+
name: 'headerParameters',
|
|
443
|
+
type: 'fixedCollection',
|
|
444
|
+
typeOptions: { multipleValues: true },
|
|
445
|
+
default: {},
|
|
446
|
+
options: [
|
|
447
|
+
{
|
|
448
|
+
displayName: 'Parameter',
|
|
449
|
+
name: 'parameters',
|
|
450
|
+
values: [
|
|
451
|
+
{ displayName: 'Name', name: 'name', type: 'string', default: '' },
|
|
452
|
+
{ displayName: 'Value', name: 'value', type: 'string', default: '' },
|
|
453
|
+
],
|
|
454
|
+
},
|
|
455
|
+
],
|
|
456
|
+
},
|
|
457
|
+
{
|
|
458
|
+
displayName: 'Body',
|
|
459
|
+
name: 'body',
|
|
460
|
+
type: 'string',
|
|
461
|
+
default: '',
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
displayName: 'JSON Body',
|
|
465
|
+
name: 'jsonBody',
|
|
466
|
+
type: 'json',
|
|
467
|
+
default: {},
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
displayName: 'Body Parameters',
|
|
471
|
+
name: 'bodyParameters',
|
|
472
|
+
type: 'fixedCollection',
|
|
473
|
+
typeOptions: { multipleValues: true },
|
|
474
|
+
default: {},
|
|
475
|
+
options: [
|
|
476
|
+
{
|
|
477
|
+
displayName: 'Parameter',
|
|
478
|
+
name: 'parameters',
|
|
479
|
+
values: [
|
|
480
|
+
{ displayName: 'Name', name: 'name', type: 'string', default: '' },
|
|
481
|
+
{ displayName: 'Value', name: 'value', type: 'string', default: '' },
|
|
482
|
+
],
|
|
483
|
+
},
|
|
484
|
+
],
|
|
485
|
+
},
|
|
486
|
+
],
|
|
487
|
+
},
|
|
488
|
+
async execute() {
|
|
489
|
+
const inputItems = this.getInputData();
|
|
490
|
+
const sourceItems = inputItems.length > 0 ? inputItems : [{ json: {} }];
|
|
491
|
+
const output = [];
|
|
492
|
+
const nodeParameters = this.getNode().parameters;
|
|
493
|
+
for (let itemIndex = 0; itemIndex < sourceItems.length; itemIndex++) {
|
|
494
|
+
const url = readString(nodeParameters.url, '');
|
|
495
|
+
if (!url) {
|
|
496
|
+
throw new Error(`HTTP Request node requires a url parameter; got ${JSON.stringify(nodeParameters)}`);
|
|
497
|
+
}
|
|
498
|
+
const method = readString(nodeParameters.method, 'GET').toUpperCase().trim();
|
|
499
|
+
const headerContainer = isRecord(nodeParameters.headerParameters)
|
|
500
|
+
? nodeParameters.headerParameters
|
|
501
|
+
: {};
|
|
502
|
+
const headerParameters = headerContainer.parameters ?? [];
|
|
503
|
+
const headers = {
|
|
504
|
+
...normalizeHeaderEntries(nodeParameters.headers),
|
|
505
|
+
...normalizeHeaderEntries(headerParameters),
|
|
506
|
+
};
|
|
507
|
+
const requestOptions = { method, headers };
|
|
508
|
+
const bodyContainer = isRecord(nodeParameters.bodyParameters)
|
|
509
|
+
? nodeParameters.bodyParameters
|
|
510
|
+
: {};
|
|
511
|
+
const bodyParameters = bodyContainer.parameters ?? [];
|
|
512
|
+
const bodyObject = collectParametersList(bodyParameters);
|
|
513
|
+
const jsonBody = nodeParameters.jsonBody;
|
|
514
|
+
const rawBody = nodeParameters.body;
|
|
515
|
+
if (!['GET', 'HEAD'].includes(method)) {
|
|
516
|
+
if (typeof rawBody === 'string' && rawBody.length > 0) {
|
|
517
|
+
requestOptions.body = rawBody;
|
|
518
|
+
}
|
|
519
|
+
else if (isRecord(jsonBody) || Object.keys(bodyObject).length > 0) {
|
|
520
|
+
requestOptions.body = JSON.stringify(isRecord(jsonBody) ? jsonBody : bodyObject);
|
|
521
|
+
headers['content-type'] = headers['content-type'] ?? 'application/json';
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
const response = await fetch(url, requestOptions);
|
|
525
|
+
const body = await parseResponseBody(response);
|
|
526
|
+
output.push({
|
|
527
|
+
json: {
|
|
528
|
+
statusCode: response.status,
|
|
529
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
530
|
+
body,
|
|
531
|
+
},
|
|
532
|
+
pairedItem: { item: itemIndex },
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
return [output];
|
|
536
|
+
},
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
function createManualTriggerNode() {
|
|
540
|
+
return {
|
|
541
|
+
description: {
|
|
542
|
+
displayName: 'Manual Trigger',
|
|
543
|
+
name: 'workflows-nodes-base.manualTrigger',
|
|
544
|
+
group: ['trigger'],
|
|
545
|
+
version: [1],
|
|
546
|
+
description: 'Starts the workflow manually.',
|
|
547
|
+
defaults: { name: 'Manual Trigger' },
|
|
548
|
+
inputs: [],
|
|
549
|
+
outputs: ['main'],
|
|
550
|
+
properties: [],
|
|
551
|
+
},
|
|
552
|
+
async execute() {
|
|
553
|
+
return [[{ json: { firedAt: new Date().toISOString(), trigger: 'manual' } }]];
|
|
554
|
+
},
|
|
555
|
+
async trigger() {
|
|
556
|
+
return {};
|
|
557
|
+
},
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
function createWebhookNode() {
|
|
561
|
+
return {
|
|
562
|
+
description: {
|
|
563
|
+
displayName: 'Webhook',
|
|
564
|
+
name: 'workflows-nodes-base.webhook',
|
|
565
|
+
group: ['trigger'],
|
|
566
|
+
version: [1, 2],
|
|
567
|
+
description: 'Starts the workflow from an HTTP webhook.',
|
|
568
|
+
defaults: { name: 'Webhook' },
|
|
569
|
+
inputs: [],
|
|
570
|
+
outputs: ['main'],
|
|
571
|
+
properties: [
|
|
572
|
+
{ displayName: 'Path', name: 'path', type: 'string', default: '' },
|
|
573
|
+
{ displayName: 'HTTP Method', name: 'httpMethod', type: 'string', default: 'POST' },
|
|
574
|
+
{ displayName: 'Embedded Payload', name: '__embeddedPayload', type: 'json', default: {} },
|
|
575
|
+
],
|
|
576
|
+
capabilities: { requiresInbound: true },
|
|
577
|
+
},
|
|
578
|
+
async execute() {
|
|
579
|
+
const parameters = this.getNode().parameters;
|
|
580
|
+
const payload = isRecord(parameters.__embeddedPayload)
|
|
581
|
+
? parameters.__embeddedPayload
|
|
582
|
+
: { firedAt: new Date().toISOString(), trigger: 'webhook' };
|
|
583
|
+
return [[{ json: cloneJson(payload) }]];
|
|
584
|
+
},
|
|
585
|
+
async trigger() {
|
|
586
|
+
return {};
|
|
587
|
+
},
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
function createRespondToWebhookNode() {
|
|
591
|
+
return {
|
|
592
|
+
description: {
|
|
593
|
+
displayName: 'Respond to Webhook',
|
|
594
|
+
name: 'workflows-nodes-base.respondToWebhook',
|
|
595
|
+
group: ['output'],
|
|
596
|
+
version: [1],
|
|
597
|
+
description: 'Returns the current item as a webhook response.',
|
|
598
|
+
defaults: { name: 'Respond to Webhook' },
|
|
599
|
+
inputs: ['main'],
|
|
600
|
+
outputs: ['main'],
|
|
601
|
+
properties: [
|
|
602
|
+
{ displayName: 'Response Body', name: 'responseBody', type: 'json', default: {} },
|
|
603
|
+
],
|
|
604
|
+
capabilities: { requiresInbound: true },
|
|
605
|
+
},
|
|
606
|
+
async execute() {
|
|
607
|
+
const inputItems = this.getInputData();
|
|
608
|
+
const parameters = this.getNode().parameters;
|
|
609
|
+
if (isRecord(parameters.responseBody) && Object.keys(parameters.responseBody).length > 0) {
|
|
610
|
+
return [[{ json: cloneJson(parameters.responseBody) }]];
|
|
611
|
+
}
|
|
612
|
+
return [inputItems.length > 0 ? inputItems : [{ json: {} }]];
|
|
613
|
+
},
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
function createNoOpNode() {
|
|
617
|
+
return {
|
|
618
|
+
description: {
|
|
619
|
+
displayName: 'No Operation, do nothing',
|
|
620
|
+
name: 'workflows-nodes-base.noOp',
|
|
621
|
+
group: ['transform'],
|
|
622
|
+
version: [1],
|
|
623
|
+
description: 'Passes input data through unchanged.',
|
|
624
|
+
defaults: { name: 'NoOp' },
|
|
625
|
+
inputs: ['main'],
|
|
626
|
+
outputs: ['main'],
|
|
627
|
+
properties: [],
|
|
628
|
+
},
|
|
629
|
+
async execute() {
|
|
630
|
+
const inputItems = this.getInputData();
|
|
631
|
+
return [inputItems.length > 0 ? inputItems : [{ json: {} }]];
|
|
632
|
+
},
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
function createIfNode() {
|
|
636
|
+
return {
|
|
637
|
+
description: {
|
|
638
|
+
displayName: 'If',
|
|
639
|
+
name: 'workflows-nodes-base.if',
|
|
640
|
+
group: ['transform'],
|
|
641
|
+
version: [1, 2],
|
|
642
|
+
description: 'Routes items based on conditions.',
|
|
643
|
+
defaults: { name: 'If' },
|
|
644
|
+
inputs: ['main'],
|
|
645
|
+
outputs: ['main', 'main'],
|
|
646
|
+
properties: [
|
|
647
|
+
{ displayName: 'Conditions', name: 'conditions', type: 'fixedCollection', default: {} },
|
|
648
|
+
],
|
|
649
|
+
},
|
|
650
|
+
async execute() {
|
|
651
|
+
const parameters = this.getNode().parameters;
|
|
652
|
+
const inputItems = this.getInputData();
|
|
653
|
+
const trueItems = [];
|
|
654
|
+
const falseItems = [];
|
|
655
|
+
inputItems.forEach((item, index) => {
|
|
656
|
+
const out = evaluateConditions(parameters, item) ? trueItems : falseItems;
|
|
657
|
+
out.push({ ...item, pairedItem: item.pairedItem ?? { item: index } });
|
|
658
|
+
});
|
|
659
|
+
return [trueItems, falseItems];
|
|
660
|
+
},
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
function createFilterNode() {
|
|
664
|
+
return {
|
|
665
|
+
description: {
|
|
666
|
+
displayName: 'Filter',
|
|
667
|
+
name: 'workflows-nodes-base.filter',
|
|
668
|
+
group: ['transform'],
|
|
669
|
+
version: [1, 2],
|
|
670
|
+
description: 'Keeps items that match conditions.',
|
|
671
|
+
defaults: { name: 'Filter' },
|
|
672
|
+
inputs: ['main'],
|
|
673
|
+
outputs: ['main'],
|
|
674
|
+
properties: [
|
|
675
|
+
{ displayName: 'Conditions', name: 'conditions', type: 'fixedCollection', default: {} },
|
|
676
|
+
],
|
|
677
|
+
},
|
|
678
|
+
async execute() {
|
|
679
|
+
const parameters = this.getNode().parameters;
|
|
680
|
+
return [this.getInputData().filter((item) => evaluateConditions(parameters, item))];
|
|
681
|
+
},
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
function createSwitchNode() {
|
|
685
|
+
return {
|
|
686
|
+
description: {
|
|
687
|
+
displayName: 'Switch',
|
|
688
|
+
name: 'workflows-nodes-base.switch',
|
|
689
|
+
group: ['transform'],
|
|
690
|
+
version: [1, 2, 3],
|
|
691
|
+
description: 'Routes items to multiple outputs.',
|
|
692
|
+
defaults: { name: 'Switch' },
|
|
693
|
+
inputs: ['main'],
|
|
694
|
+
outputs: ['main', 'main', 'main', 'main', 'main'],
|
|
695
|
+
properties: [
|
|
696
|
+
{ displayName: 'Rules', name: 'rules', type: 'fixedCollection', default: {} },
|
|
697
|
+
],
|
|
698
|
+
},
|
|
699
|
+
async execute() {
|
|
700
|
+
const parameters = this.getNode().parameters;
|
|
701
|
+
const rulesContainer = isRecord(parameters.rules) ? parameters.rules : {};
|
|
702
|
+
const rules = Array.isArray(rulesContainer.rules) ? rulesContainer.rules : [];
|
|
703
|
+
const outputs = [[], [], [], [], []];
|
|
704
|
+
this.getInputData().forEach((item, itemIndex) => {
|
|
705
|
+
const matchedIndex = rules.findIndex((rule) => isRecord(rule) ? evaluateConditions({ conditions: rule.conditions ?? rule }, item) : false);
|
|
706
|
+
const outputIndex = matchedIndex >= 0 ? Math.min(matchedIndex, 3) : 4;
|
|
707
|
+
outputs[outputIndex].push({ ...item, pairedItem: item.pairedItem ?? { item: itemIndex } });
|
|
708
|
+
});
|
|
709
|
+
return outputs;
|
|
710
|
+
},
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
function createMergeNode() {
|
|
714
|
+
return {
|
|
715
|
+
description: {
|
|
716
|
+
displayName: 'Merge',
|
|
717
|
+
name: 'workflows-nodes-base.merge',
|
|
718
|
+
group: ['transform'],
|
|
719
|
+
version: [1, 2, 3],
|
|
720
|
+
description: 'Combines items from multiple inputs.',
|
|
721
|
+
defaults: { name: 'Merge' },
|
|
722
|
+
inputs: ['main', 'main'],
|
|
723
|
+
outputs: ['main'],
|
|
724
|
+
properties: [
|
|
725
|
+
{ displayName: 'Mode', name: 'mode', type: 'string', default: 'append' },
|
|
726
|
+
],
|
|
727
|
+
},
|
|
728
|
+
async execute() {
|
|
729
|
+
const first = this.getInputData(0) ?? [];
|
|
730
|
+
const second = this.getInputData(1) ?? [];
|
|
731
|
+
return [[...first, ...second]];
|
|
732
|
+
},
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
function createSplitInBatchesNode() {
|
|
736
|
+
return {
|
|
737
|
+
description: {
|
|
738
|
+
displayName: 'Split In Batches',
|
|
739
|
+
name: 'workflows-nodes-base.splitInBatches',
|
|
740
|
+
group: ['transform'],
|
|
741
|
+
version: [1, 2, 3],
|
|
742
|
+
description: 'Emits the next batch of items.',
|
|
743
|
+
defaults: { name: 'Split In Batches' },
|
|
744
|
+
inputs: ['main'],
|
|
745
|
+
outputs: ['main', 'main'],
|
|
746
|
+
properties: [
|
|
747
|
+
{ displayName: 'Batch Size', name: 'batchSize', type: 'number', default: 1 },
|
|
748
|
+
],
|
|
749
|
+
},
|
|
750
|
+
async execute() {
|
|
751
|
+
const inputItems = this.getInputData();
|
|
752
|
+
const batchSize = Math.max(1, readNumber(this.getNode().parameters.batchSize, inputItems.length));
|
|
753
|
+
return [inputItems.slice(0, batchSize), inputItems.slice(batchSize)];
|
|
754
|
+
},
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
function createWaitNode() {
|
|
758
|
+
return {
|
|
759
|
+
description: {
|
|
760
|
+
displayName: 'Wait',
|
|
761
|
+
name: 'workflows-nodes-base.wait',
|
|
762
|
+
group: ['transform'],
|
|
763
|
+
version: [1, 1.1],
|
|
764
|
+
description: 'Pauses execution for a duration.',
|
|
765
|
+
defaults: { name: 'Wait' },
|
|
766
|
+
inputs: ['main'],
|
|
767
|
+
outputs: ['main'],
|
|
768
|
+
properties: [
|
|
769
|
+
{ displayName: 'Amount', name: 'amount', type: 'number', default: 1 },
|
|
770
|
+
{ displayName: 'Unit', name: 'unit', type: 'string', default: 'seconds' },
|
|
771
|
+
],
|
|
772
|
+
},
|
|
773
|
+
async execute() {
|
|
774
|
+
const parameters = this.getNode().parameters;
|
|
775
|
+
const amount = Math.max(0, readNumber(parameters.amount, 1));
|
|
776
|
+
const unit = readString(parameters.unit, 'seconds');
|
|
777
|
+
const multiplier = unit === 'milliseconds'
|
|
778
|
+
? 1
|
|
779
|
+
: unit === 'minutes'
|
|
780
|
+
? 60_000
|
|
781
|
+
: unit === 'hours'
|
|
782
|
+
? 3_600_000
|
|
783
|
+
: 1000;
|
|
784
|
+
await new Promise((resolve) => setTimeout(resolve, amount * multiplier));
|
|
785
|
+
return [this.getInputData()];
|
|
786
|
+
},
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
function createDateTimeNode() {
|
|
790
|
+
return {
|
|
791
|
+
description: {
|
|
792
|
+
displayName: 'Date & Time',
|
|
793
|
+
name: 'workflows-nodes-base.dateTime',
|
|
794
|
+
group: ['transform'],
|
|
795
|
+
version: [1, 2],
|
|
796
|
+
description: 'Adds date/time values to items.',
|
|
797
|
+
defaults: { name: 'Date & Time' },
|
|
798
|
+
inputs: ['main'],
|
|
799
|
+
outputs: ['main'],
|
|
800
|
+
properties: [
|
|
801
|
+
{ displayName: 'Field Name', name: 'fieldName', type: 'string', default: 'dateTime' },
|
|
802
|
+
],
|
|
803
|
+
},
|
|
804
|
+
async execute() {
|
|
805
|
+
const inputItems = this.getInputData();
|
|
806
|
+
const fieldName = readString(this.getNode().parameters.fieldName, 'dateTime');
|
|
807
|
+
const now = new Date().toISOString();
|
|
808
|
+
return [
|
|
809
|
+
inputItems.map((item, index) => ({
|
|
810
|
+
json: { ...(item.json ?? {}), [fieldName]: now },
|
|
811
|
+
pairedItem: item.pairedItem ?? { item: index },
|
|
812
|
+
})),
|
|
813
|
+
];
|
|
814
|
+
},
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
function createCryptoNode() {
|
|
818
|
+
return {
|
|
819
|
+
description: {
|
|
820
|
+
displayName: 'Crypto',
|
|
821
|
+
name: 'workflows-nodes-base.crypto',
|
|
822
|
+
group: ['transform'],
|
|
823
|
+
version: [1],
|
|
824
|
+
description: 'Hashes data.',
|
|
825
|
+
defaults: { name: 'Crypto' },
|
|
826
|
+
inputs: ['main'],
|
|
827
|
+
outputs: ['main'],
|
|
828
|
+
properties: [
|
|
829
|
+
{ displayName: 'Value', name: 'value', type: 'string', default: '' },
|
|
830
|
+
{ displayName: 'Algorithm', name: 'algorithm', type: 'string', default: 'sha256' },
|
|
831
|
+
{ displayName: 'Field Name', name: 'fieldName', type: 'string', default: 'hash' },
|
|
832
|
+
],
|
|
833
|
+
},
|
|
834
|
+
async execute() {
|
|
835
|
+
const parameters = this.getNode().parameters;
|
|
836
|
+
const algorithm = readString(parameters.algorithm, 'sha256');
|
|
837
|
+
const fieldName = readString(parameters.fieldName, 'hash');
|
|
838
|
+
return [
|
|
839
|
+
this.getInputData().map((item, index) => {
|
|
840
|
+
const raw = resolveParameterValue(parameters.value, item);
|
|
841
|
+
const source = raw === '' || typeof raw === 'undefined' ? JSON.stringify(item.json) : String(raw);
|
|
842
|
+
return {
|
|
843
|
+
json: {
|
|
844
|
+
...(item.json ?? {}),
|
|
845
|
+
[fieldName]: createHash(algorithm).update(source).digest('hex'),
|
|
846
|
+
},
|
|
847
|
+
pairedItem: item.pairedItem ?? { item: index },
|
|
848
|
+
};
|
|
849
|
+
}),
|
|
850
|
+
];
|
|
851
|
+
},
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
function createItemListsNode() {
|
|
855
|
+
return {
|
|
856
|
+
description: {
|
|
857
|
+
displayName: 'Item Lists',
|
|
858
|
+
name: 'workflows-nodes-base.itemLists',
|
|
859
|
+
group: ['transform'],
|
|
860
|
+
version: [1, 2, 3],
|
|
861
|
+
description: 'Transforms item lists.',
|
|
862
|
+
defaults: { name: 'Item Lists' },
|
|
863
|
+
inputs: ['main'],
|
|
864
|
+
outputs: ['main'],
|
|
865
|
+
properties: [
|
|
866
|
+
{ displayName: 'Operation', name: 'operation', type: 'string', default: 'passthrough' },
|
|
867
|
+
{ displayName: 'Limit', name: 'limit', type: 'number', default: 0 },
|
|
868
|
+
],
|
|
869
|
+
},
|
|
870
|
+
async execute() {
|
|
871
|
+
const parameters = this.getNode().parameters;
|
|
872
|
+
const inputItems = this.getInputData();
|
|
873
|
+
const operation = readString(parameters.operation, 'passthrough');
|
|
874
|
+
if (operation === 'limit') {
|
|
875
|
+
const limit = Math.max(0, readNumber(parameters.limit, inputItems.length));
|
|
876
|
+
return [inputItems.slice(0, limit)];
|
|
877
|
+
}
|
|
878
|
+
return [inputItems];
|
|
879
|
+
},
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
async function runQuickJsCode(jsCode, inputItems) {
|
|
883
|
+
const { getQuickJS, shouldInterruptAfterDeadline } = await loadQuickJs();
|
|
884
|
+
const QuickJS = await getQuickJS();
|
|
885
|
+
const embeddedInput = JSON.stringify(inputItems.map((item) => normalizeExecutionItem(item)));
|
|
886
|
+
const source = `
|
|
887
|
+
"use strict";
|
|
888
|
+
const $input = ${embeddedInput};
|
|
889
|
+
const items = $input;
|
|
890
|
+
const item = $input[0] ?? { json: {} };
|
|
891
|
+
const $json = item.json ?? {};
|
|
892
|
+
const $now = new Date("${new Date().toISOString()}");
|
|
893
|
+
const $workflow = {};
|
|
894
|
+
const $env = {};
|
|
895
|
+
const console = { log() {}, warn() {}, error() {}, info() {} };
|
|
896
|
+
(function embeddedWorkflowCodeNode() {
|
|
897
|
+
${jsCode}
|
|
898
|
+
})()
|
|
899
|
+
`;
|
|
900
|
+
return QuickJS.evalCode(source, {
|
|
901
|
+
shouldInterrupt: shouldInterruptAfterDeadline(Date.now() + 5_000),
|
|
902
|
+
memoryLimitBytes: 32 * 1024 * 1024,
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
function resolveAutonomyService(runtime) {
|
|
906
|
+
const svc = runtime.getService('AUTONOMY') ??
|
|
907
|
+
runtime.getService('autonomy');
|
|
908
|
+
return svc ?? null;
|
|
909
|
+
}
|
|
910
|
+
function resolveAutonomyRoomId(svc) {
|
|
911
|
+
const fromAutonomous = typeof svc.getAutonomousRoomId === 'function' ? svc.getAutonomousRoomId() : undefined;
|
|
912
|
+
if (fromAutonomous)
|
|
913
|
+
return fromAutonomous;
|
|
914
|
+
const fromTarget = typeof svc.getTargetRoomId === 'function' ? svc.getTargetRoomId() : undefined;
|
|
915
|
+
return fromTarget ?? null;
|
|
916
|
+
}
|
|
917
|
+
function extractEventFromInputItems(inputItems) {
|
|
918
|
+
for (const item of inputItems) {
|
|
919
|
+
const json = item.json;
|
|
920
|
+
if (!isRecord(json))
|
|
921
|
+
continue;
|
|
922
|
+
const kind = typeof json.eventKind === 'string' ? json.eventKind : undefined;
|
|
923
|
+
const payload = isRecord(json.eventPayload) ? json.eventPayload : undefined;
|
|
924
|
+
if (kind || payload)
|
|
925
|
+
return { kind, payload };
|
|
926
|
+
}
|
|
927
|
+
return null;
|
|
928
|
+
}
|
|
929
|
+
function createRespondToEventNode() {
|
|
930
|
+
return {
|
|
931
|
+
description: {
|
|
932
|
+
displayName: 'Respond to Event',
|
|
933
|
+
name: 'workflows-nodes-base.respondToEvent',
|
|
934
|
+
group: ['transform'],
|
|
935
|
+
version: [1],
|
|
936
|
+
description: "Inject an instruction into the agent's autonomy room.",
|
|
937
|
+
defaults: { name: 'Respond to Event' },
|
|
938
|
+
inputs: ['main'],
|
|
939
|
+
outputs: ['main'],
|
|
940
|
+
properties: [
|
|
941
|
+
{ displayName: 'Instructions', name: 'instructions', type: 'string', default: '' },
|
|
942
|
+
{ displayName: 'Display Name', name: 'displayName', type: 'string', default: '' },
|
|
943
|
+
{ displayName: 'Wake Mode', name: 'wakeMode', type: 'string', default: 'inject_now' },
|
|
944
|
+
],
|
|
945
|
+
},
|
|
946
|
+
async execute() {
|
|
947
|
+
const node = this.getNode();
|
|
948
|
+
const inputItems = this.getInputData();
|
|
949
|
+
const parameters = node.parameters;
|
|
950
|
+
const instructions = readString(parameters.instructions, '');
|
|
951
|
+
const displayName = readString(parameters.displayName, node.name);
|
|
952
|
+
const wakeMode = readString(parameters.wakeMode, 'inject_now');
|
|
953
|
+
const runtime = this.getRuntime?.() ?? null;
|
|
954
|
+
const executionId = this.getExecutionId?.() ?? null;
|
|
955
|
+
const failure = (reason) => [
|
|
956
|
+
[
|
|
957
|
+
{
|
|
958
|
+
json: {
|
|
959
|
+
instructionInjected: false,
|
|
960
|
+
reason,
|
|
961
|
+
nodeName: node.name,
|
|
962
|
+
},
|
|
963
|
+
},
|
|
964
|
+
],
|
|
965
|
+
];
|
|
966
|
+
if (!runtime) {
|
|
967
|
+
logger.warn({ src: 'plugin:workflow:respondToEvent', nodeName: node.name }, '[respondToEvent] No agent runtime available in execution context — skipping injection');
|
|
968
|
+
return failure('runtime_unavailable');
|
|
969
|
+
}
|
|
970
|
+
const autonomyService = resolveAutonomyService(runtime);
|
|
971
|
+
if (!autonomyService) {
|
|
972
|
+
runtime.logger?.warn?.({ src: 'plugin:workflow:respondToEvent', nodeName: node.name, executionId }, '[respondToEvent] Autonomy service not registered — skipping injection');
|
|
973
|
+
return failure('autonomy_service_unavailable');
|
|
974
|
+
}
|
|
975
|
+
const roomId = resolveAutonomyRoomId(autonomyService);
|
|
976
|
+
if (!roomId) {
|
|
977
|
+
runtime.logger?.warn?.({ src: 'plugin:workflow:respondToEvent', nodeName: node.name, executionId }, '[respondToEvent] No autonomy room resolvable — skipping injection');
|
|
978
|
+
return failure('no_autonomy_room');
|
|
979
|
+
}
|
|
980
|
+
const event = extractEventFromInputItems(inputItems);
|
|
981
|
+
const eventText = event
|
|
982
|
+
? `\n\nEvent: ${event.kind ?? 'unknown'}\nPayload: ${JSON.stringify(event.payload ?? {})}`
|
|
983
|
+
: '';
|
|
984
|
+
const instructionText = `[${displayName}]\n${instructions}${eventText}`;
|
|
985
|
+
await runtime.createMemory({
|
|
986
|
+
entityId: runtime.agentId,
|
|
987
|
+
roomId,
|
|
988
|
+
content: {
|
|
989
|
+
text: instructionText,
|
|
990
|
+
source: 'workflow:respondToEvent',
|
|
991
|
+
metadata: {
|
|
992
|
+
workflowExecutionId: executionId,
|
|
993
|
+
nodeName: node.name,
|
|
994
|
+
wakeMode,
|
|
995
|
+
isAutonomousInstruction: true,
|
|
996
|
+
},
|
|
997
|
+
},
|
|
998
|
+
}, 'messages');
|
|
999
|
+
return [
|
|
1000
|
+
[
|
|
1001
|
+
{
|
|
1002
|
+
json: {
|
|
1003
|
+
instructionInjected: true,
|
|
1004
|
+
roomId,
|
|
1005
|
+
nodeName: node.name,
|
|
1006
|
+
wakeMode,
|
|
1007
|
+
},
|
|
1008
|
+
},
|
|
1009
|
+
],
|
|
1010
|
+
];
|
|
1011
|
+
},
|
|
1012
|
+
};
|
|
1013
|
+
}
|
|
1014
|
+
function createCodeNode() {
|
|
1015
|
+
return {
|
|
1016
|
+
description: {
|
|
1017
|
+
displayName: 'Code',
|
|
1018
|
+
name: 'workflows-nodes-base.code',
|
|
1019
|
+
group: ['transform'],
|
|
1020
|
+
version: [1, 2],
|
|
1021
|
+
description: 'Runs JavaScript in a QuickJS sandbox.',
|
|
1022
|
+
defaults: { name: 'Code' },
|
|
1023
|
+
inputs: ['main'],
|
|
1024
|
+
outputs: ['main'],
|
|
1025
|
+
properties: [
|
|
1026
|
+
{
|
|
1027
|
+
displayName: 'JavaScript Code',
|
|
1028
|
+
name: 'jsCode',
|
|
1029
|
+
type: 'string',
|
|
1030
|
+
default: 'return items;',
|
|
1031
|
+
},
|
|
1032
|
+
{ displayName: 'Mode', name: 'mode', type: 'string', default: 'runOnceForAllItems' },
|
|
1033
|
+
],
|
|
1034
|
+
},
|
|
1035
|
+
async execute() {
|
|
1036
|
+
const inputItems = this.getInputData();
|
|
1037
|
+
const sourceItems = inputItems.length > 0 ? inputItems : [{ json: {} }];
|
|
1038
|
+
const parameters = this.getNode().parameters;
|
|
1039
|
+
const jsCode = readString(parameters.jsCode, 'return items;');
|
|
1040
|
+
const mode = readString(parameters.mode, 'runOnceForAllItems');
|
|
1041
|
+
if (mode === 'runOnceForEachItem') {
|
|
1042
|
+
const out = [];
|
|
1043
|
+
for (const item of sourceItems) {
|
|
1044
|
+
const result = await runQuickJsCode(jsCode, [item]);
|
|
1045
|
+
out.push(...normalizeExecutionItems(result, [item]));
|
|
1046
|
+
}
|
|
1047
|
+
return [out];
|
|
1048
|
+
}
|
|
1049
|
+
const result = await runQuickJsCode(jsCode, sourceItems);
|
|
1050
|
+
return [normalizeExecutionItems(result, sourceItems)];
|
|
1051
|
+
},
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
class EmbeddedNodeTypes {
|
|
1055
|
+
nodes = new Map();
|
|
1056
|
+
constructor() {
|
|
1057
|
+
for (const node of [
|
|
1058
|
+
createScheduleTriggerNode(),
|
|
1059
|
+
createManualTriggerNode(),
|
|
1060
|
+
createWebhookNode(),
|
|
1061
|
+
createRespondToWebhookNode(),
|
|
1062
|
+
createRespondToEventNode(),
|
|
1063
|
+
createSetNode(),
|
|
1064
|
+
createHttpRequestNode(),
|
|
1065
|
+
createNoOpNode(),
|
|
1066
|
+
createIfNode(),
|
|
1067
|
+
createFilterNode(),
|
|
1068
|
+
createSwitchNode(),
|
|
1069
|
+
createMergeNode(),
|
|
1070
|
+
createSplitInBatchesNode(),
|
|
1071
|
+
createWaitNode(),
|
|
1072
|
+
createDateTimeNode(),
|
|
1073
|
+
createCryptoNode(),
|
|
1074
|
+
createItemListsNode(),
|
|
1075
|
+
createCodeNode(),
|
|
1076
|
+
]) {
|
|
1077
|
+
const canonical = node.description.name;
|
|
1078
|
+
this.nodes.set(canonical, node);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
getByName(nodeType) {
|
|
1082
|
+
return this.getByNameAndVersion(nodeType);
|
|
1083
|
+
}
|
|
1084
|
+
getByNameAndVersion(nodeType) {
|
|
1085
|
+
const node = this.nodes.get(nodeType);
|
|
1086
|
+
if (!node) {
|
|
1087
|
+
throw new Error(`Node type not available in embedded workflow runtime: ${nodeType}`);
|
|
1088
|
+
}
|
|
1089
|
+
return node;
|
|
1090
|
+
}
|
|
1091
|
+
getKnownTypes() {
|
|
1092
|
+
return Object.fromEntries([...this.nodes.keys()].map((name) => [
|
|
1093
|
+
name,
|
|
1094
|
+
{ sourcePath: 'embedded', className: name.split('.').at(-1) ?? name },
|
|
1095
|
+
]));
|
|
1096
|
+
}
|
|
1097
|
+
has(nodeType) {
|
|
1098
|
+
return this.nodes.has(nodeType);
|
|
1099
|
+
}
|
|
1100
|
+
names() {
|
|
1101
|
+
return [...this.nodes.keys()];
|
|
1102
|
+
}
|
|
1103
|
+
versions() {
|
|
1104
|
+
const out = new Map();
|
|
1105
|
+
for (const [name, node] of this.nodes) {
|
|
1106
|
+
const version = node.description.version;
|
|
1107
|
+
out.set(name, Array.isArray(version) ? version : [version]);
|
|
1108
|
+
}
|
|
1109
|
+
return out;
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
export class EmbeddedWorkflowService extends Service {
|
|
1113
|
+
static serviceType = EMBEDDED_WORKFLOW_SERVICE_TYPE;
|
|
1114
|
+
capabilityDescription = 'Feature-flagged embedded workflow runtime for local plugin-owned workflow execution.';
|
|
1115
|
+
nodeTypes = new EmbeddedNodeTypes();
|
|
1116
|
+
hostCapabilities = detectHostCapabilities();
|
|
1117
|
+
schemaReady = null;
|
|
1118
|
+
static async start(runtime) {
|
|
1119
|
+
const service = new EmbeddedWorkflowService(runtime);
|
|
1120
|
+
logger.info({ src: 'plugin:workflow:embedded' }, 'Embedded workflow service registered (lazy runtime load)');
|
|
1121
|
+
service.registerTaskWorkers();
|
|
1122
|
+
if (runtime.db) {
|
|
1123
|
+
await service.ensureSchema();
|
|
1124
|
+
await service.rehydrateSchedules();
|
|
1125
|
+
}
|
|
1126
|
+
return service;
|
|
1127
|
+
}
|
|
1128
|
+
async stop() {
|
|
1129
|
+
// Scheduling lives in core's TaskService. Tasks persist across restart;
|
|
1130
|
+
// there is nothing in-process to tear down here.
|
|
1131
|
+
}
|
|
1132
|
+
/** Register the workflow.run + workflow.webhook task workers with the
|
|
1133
|
+
* runtime's TaskService. Idempotent — safe to call once per service start. */
|
|
1134
|
+
registerTaskWorkers() {
|
|
1135
|
+
if (typeof this.runtime.registerTaskWorker !== 'function')
|
|
1136
|
+
return;
|
|
1137
|
+
if (!this.runtime.getTaskWorker?.(WORKFLOW_RUN_TASK_WORKER_NAME)) {
|
|
1138
|
+
this.runtime.registerTaskWorker({
|
|
1139
|
+
name: WORKFLOW_RUN_TASK_WORKER_NAME,
|
|
1140
|
+
execute: async (_rt, _opts, task) => {
|
|
1141
|
+
const workflowId = typeof task.metadata?.workflowId === 'string' ? task.metadata.workflowId : null;
|
|
1142
|
+
if (!workflowId) {
|
|
1143
|
+
throw new Error(`${WORKFLOW_RUN_TASK_WORKER_NAME} task ${task.id ?? '?'} missing metadata.workflowId`);
|
|
1144
|
+
}
|
|
1145
|
+
await this.executeWorkflow(workflowId, { mode: 'trigger' });
|
|
1146
|
+
return undefined;
|
|
1147
|
+
},
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
if (!this.runtime.getTaskWorker?.(WORKFLOW_WEBHOOK_TASK_WORKER_NAME)) {
|
|
1151
|
+
this.runtime.registerTaskWorker({
|
|
1152
|
+
name: WORKFLOW_WEBHOOK_TASK_WORKER_NAME,
|
|
1153
|
+
execute: async (_rt, _opts, task) => {
|
|
1154
|
+
const meta = task.metadata;
|
|
1155
|
+
const path = typeof meta?.path === 'string' ? meta.path : null;
|
|
1156
|
+
const method = typeof meta?.method === 'string' ? meta.method : 'POST';
|
|
1157
|
+
const payload = isRecord(meta?.payload) ? meta.payload : {};
|
|
1158
|
+
if (!path) {
|
|
1159
|
+
throw new Error(`${WORKFLOW_WEBHOOK_TASK_WORKER_NAME} task ${task.id ?? '?'} missing metadata.path`);
|
|
1160
|
+
}
|
|
1161
|
+
await this.executeWebhook(path, payload, method);
|
|
1162
|
+
return undefined;
|
|
1163
|
+
},
|
|
1164
|
+
});
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
get host() {
|
|
1168
|
+
return EMBEDDED_HOST;
|
|
1169
|
+
}
|
|
1170
|
+
getRuntimeNodeTypeVersions() {
|
|
1171
|
+
return this.nodeTypes.versions();
|
|
1172
|
+
}
|
|
1173
|
+
getRegisteredNodeTypes() {
|
|
1174
|
+
return this.nodeTypes.names();
|
|
1175
|
+
}
|
|
1176
|
+
supportsWorkflow(workflow) {
|
|
1177
|
+
const missing = workflow.nodes
|
|
1178
|
+
.filter((node) => !node.disabled && !this.nodeTypes.has(node.type))
|
|
1179
|
+
.map((node) => node.type);
|
|
1180
|
+
return { supported: missing.length === 0, missing: [...new Set(missing)] };
|
|
1181
|
+
}
|
|
1182
|
+
getDb() {
|
|
1183
|
+
const db = this.runtime.db;
|
|
1184
|
+
if (!db) {
|
|
1185
|
+
throw new Error('Database not available for EmbeddedWorkflowService. Embedded workflow requires plugin-sql/PGlite/Postgres persistence.');
|
|
1186
|
+
}
|
|
1187
|
+
return db;
|
|
1188
|
+
}
|
|
1189
|
+
async ensureSchema() {
|
|
1190
|
+
if (!this.schemaReady) {
|
|
1191
|
+
this.schemaReady = (async () => {
|
|
1192
|
+
const db = this.getDb();
|
|
1193
|
+
await db.execute(sql `CREATE SCHEMA IF NOT EXISTS "workflow"`);
|
|
1194
|
+
await db.execute(sql `
|
|
1195
|
+
CREATE TABLE IF NOT EXISTS "workflow"."credential_mappings" (
|
|
1196
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
1197
|
+
"user_id" text NOT NULL,
|
|
1198
|
+
"cred_type" text NOT NULL,
|
|
1199
|
+
"workflow_credential_id" text NOT NULL,
|
|
1200
|
+
"created_at" timestamp DEFAULT now() NOT NULL,
|
|
1201
|
+
"updated_at" timestamp DEFAULT now() NOT NULL
|
|
1202
|
+
)
|
|
1203
|
+
`);
|
|
1204
|
+
await db.execute(sql `
|
|
1205
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "idx_user_cred"
|
|
1206
|
+
ON "workflow"."credential_mappings" ("user_id", "cred_type")
|
|
1207
|
+
`);
|
|
1208
|
+
await db.execute(sql `
|
|
1209
|
+
CREATE TABLE IF NOT EXISTS "workflow"."embedded_workflows" (
|
|
1210
|
+
"id" text PRIMARY KEY,
|
|
1211
|
+
"name" text NOT NULL,
|
|
1212
|
+
"active" boolean DEFAULT false NOT NULL,
|
|
1213
|
+
"workflow" jsonb NOT NULL,
|
|
1214
|
+
"created_at" text NOT NULL,
|
|
1215
|
+
"updated_at" text NOT NULL,
|
|
1216
|
+
"version_id" text NOT NULL
|
|
1217
|
+
)
|
|
1218
|
+
`);
|
|
1219
|
+
await db.execute(sql `
|
|
1220
|
+
CREATE INDEX IF NOT EXISTS "idx_embedded_workflows_active"
|
|
1221
|
+
ON "workflow"."embedded_workflows" ("active")
|
|
1222
|
+
`);
|
|
1223
|
+
await db.execute(sql `
|
|
1224
|
+
CREATE INDEX IF NOT EXISTS "idx_embedded_workflows_updated_at"
|
|
1225
|
+
ON "workflow"."embedded_workflows" ("updated_at")
|
|
1226
|
+
`);
|
|
1227
|
+
await db.execute(sql `
|
|
1228
|
+
CREATE TABLE IF NOT EXISTS "workflow"."embedded_executions" (
|
|
1229
|
+
"id" text PRIMARY KEY,
|
|
1230
|
+
"workflow_id" text NOT NULL,
|
|
1231
|
+
"status" text NOT NULL,
|
|
1232
|
+
"mode" text NOT NULL,
|
|
1233
|
+
"finished" boolean DEFAULT false NOT NULL,
|
|
1234
|
+
"started_at" text NOT NULL,
|
|
1235
|
+
"stopped_at" text,
|
|
1236
|
+
"execution" jsonb NOT NULL
|
|
1237
|
+
)
|
|
1238
|
+
`);
|
|
1239
|
+
await db.execute(sql `
|
|
1240
|
+
CREATE INDEX IF NOT EXISTS "idx_embedded_executions_workflow_id"
|
|
1241
|
+
ON "workflow"."embedded_executions" ("workflow_id")
|
|
1242
|
+
`);
|
|
1243
|
+
await db.execute(sql `
|
|
1244
|
+
CREATE INDEX IF NOT EXISTS "idx_embedded_executions_status"
|
|
1245
|
+
ON "workflow"."embedded_executions" ("status")
|
|
1246
|
+
`);
|
|
1247
|
+
await db.execute(sql `
|
|
1248
|
+
CREATE INDEX IF NOT EXISTS "idx_embedded_executions_started_at"
|
|
1249
|
+
ON "workflow"."embedded_executions" ("started_at")
|
|
1250
|
+
`);
|
|
1251
|
+
await db.execute(sql `
|
|
1252
|
+
CREATE TABLE IF NOT EXISTS "workflow"."embedded_credentials" (
|
|
1253
|
+
"id" text PRIMARY KEY,
|
|
1254
|
+
"name" text NOT NULL,
|
|
1255
|
+
"type" text NOT NULL,
|
|
1256
|
+
"data" jsonb NOT NULL,
|
|
1257
|
+
"is_resolvable" boolean DEFAULT true NOT NULL,
|
|
1258
|
+
"created_at" text NOT NULL,
|
|
1259
|
+
"updated_at" text NOT NULL
|
|
1260
|
+
)
|
|
1261
|
+
`);
|
|
1262
|
+
await db.execute(sql `
|
|
1263
|
+
CREATE INDEX IF NOT EXISTS "idx_embedded_credentials_type"
|
|
1264
|
+
ON "workflow"."embedded_credentials" ("type")
|
|
1265
|
+
`);
|
|
1266
|
+
await db.execute(sql `
|
|
1267
|
+
CREATE TABLE IF NOT EXISTS "workflow"."embedded_tags" (
|
|
1268
|
+
"id" text PRIMARY KEY,
|
|
1269
|
+
"name" text NOT NULL,
|
|
1270
|
+
"created_at" text NOT NULL,
|
|
1271
|
+
"updated_at" text NOT NULL
|
|
1272
|
+
)
|
|
1273
|
+
`);
|
|
1274
|
+
await db.execute(sql `
|
|
1275
|
+
CREATE UNIQUE INDEX IF NOT EXISTS "idx_embedded_tags_name"
|
|
1276
|
+
ON "workflow"."embedded_tags" ("name")
|
|
1277
|
+
`);
|
|
1278
|
+
})();
|
|
1279
|
+
}
|
|
1280
|
+
await this.schemaReady;
|
|
1281
|
+
}
|
|
1282
|
+
async createWorkflow(workflow) {
|
|
1283
|
+
this.assertRegisteredNodes(workflow);
|
|
1284
|
+
this.assertHostSupports(workflow);
|
|
1285
|
+
await this.ensureSchema();
|
|
1286
|
+
const db = this.getDb();
|
|
1287
|
+
const id = workflow.id || randomUUID();
|
|
1288
|
+
const createdAt = nowIso();
|
|
1289
|
+
const versionId = randomUUID();
|
|
1290
|
+
const stored = normalizeWorkflowPayload(workflow, id, false);
|
|
1291
|
+
await db.insert(embeddedWorkflows).values({
|
|
1292
|
+
id,
|
|
1293
|
+
name: stored.name,
|
|
1294
|
+
active: false,
|
|
1295
|
+
createdAt,
|
|
1296
|
+
updatedAt: createdAt,
|
|
1297
|
+
versionId,
|
|
1298
|
+
workflow: stored,
|
|
1299
|
+
});
|
|
1300
|
+
return responseFromWorkflow(stored, createdAt, createdAt, versionId);
|
|
1301
|
+
}
|
|
1302
|
+
async updateWorkflow(id, workflow) {
|
|
1303
|
+
this.assertRegisteredNodes(workflow);
|
|
1304
|
+
const existing = await this.getStoredWorkflow(id);
|
|
1305
|
+
const db = this.getDb();
|
|
1306
|
+
const updatedAt = nowIso();
|
|
1307
|
+
const versionId = randomUUID();
|
|
1308
|
+
const stored = normalizeWorkflowPayload(workflow, id, existing.workflow.active ?? false);
|
|
1309
|
+
await db
|
|
1310
|
+
.update(embeddedWorkflows)
|
|
1311
|
+
.set({
|
|
1312
|
+
name: stored.name,
|
|
1313
|
+
active: stored.active ?? false,
|
|
1314
|
+
workflow: stored,
|
|
1315
|
+
updatedAt,
|
|
1316
|
+
versionId,
|
|
1317
|
+
})
|
|
1318
|
+
.where(eq(embeddedWorkflows.id, id));
|
|
1319
|
+
if (stored.active)
|
|
1320
|
+
await this.armSchedules(id);
|
|
1321
|
+
return responseFromWorkflow(stored, existing.createdAt, updatedAt, versionId);
|
|
1322
|
+
}
|
|
1323
|
+
async listWorkflows(params) {
|
|
1324
|
+
await this.ensureSchema();
|
|
1325
|
+
const db = this.getDb();
|
|
1326
|
+
const rows = await db
|
|
1327
|
+
.select()
|
|
1328
|
+
.from(embeddedWorkflows)
|
|
1329
|
+
.orderBy(desc(embeddedWorkflows.updatedAt));
|
|
1330
|
+
const data = rows
|
|
1331
|
+
.map((row) => ({
|
|
1332
|
+
workflow: cloneJson(row.workflow),
|
|
1333
|
+
createdAt: row.createdAt,
|
|
1334
|
+
updatedAt: row.updatedAt,
|
|
1335
|
+
versionId: row.versionId,
|
|
1336
|
+
}))
|
|
1337
|
+
.filter((entry) => params?.active === undefined || entry.workflow.active === params.active)
|
|
1338
|
+
.filter((entry) => {
|
|
1339
|
+
if (!params?.tags?.length)
|
|
1340
|
+
return true;
|
|
1341
|
+
const tagIds = new Set(entry.workflow.tags?.map((tag) => tag.id) ?? []);
|
|
1342
|
+
return params.tags.every((tag) => tagIds.has(tag));
|
|
1343
|
+
})
|
|
1344
|
+
.map((entry) => responseFromWorkflow(entry.workflow, entry.createdAt, entry.updatedAt, entry.versionId));
|
|
1345
|
+
return { data: typeof params?.limit === 'number' ? data.slice(0, params.limit) : data };
|
|
1346
|
+
}
|
|
1347
|
+
async getWorkflow(id) {
|
|
1348
|
+
const entry = await this.getStoredWorkflow(id);
|
|
1349
|
+
return responseFromWorkflow(entry.workflow, entry.createdAt, entry.updatedAt, entry.versionId);
|
|
1350
|
+
}
|
|
1351
|
+
async deleteWorkflow(id) {
|
|
1352
|
+
await this.ensureSchema();
|
|
1353
|
+
this.clearSchedules(id);
|
|
1354
|
+
const existing = await this.getStoredWorkflow(id);
|
|
1355
|
+
const db = this.getDb();
|
|
1356
|
+
await db.delete(embeddedWorkflows).where(eq(embeddedWorkflows.id, id));
|
|
1357
|
+
if (!existing) {
|
|
1358
|
+
throw new WorkflowApiError(`Workflow not found: ${id}`, 404);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
async activateWorkflow(id) {
|
|
1362
|
+
const entry = await this.getStoredWorkflow(id);
|
|
1363
|
+
this.assertHostSupports(entry.workflow);
|
|
1364
|
+
const db = this.getDb();
|
|
1365
|
+
entry.workflow.active = true;
|
|
1366
|
+
entry.updatedAt = nowIso();
|
|
1367
|
+
entry.versionId = randomUUID();
|
|
1368
|
+
await db
|
|
1369
|
+
.update(embeddedWorkflows)
|
|
1370
|
+
.set({
|
|
1371
|
+
active: true,
|
|
1372
|
+
workflow: entry.workflow,
|
|
1373
|
+
updatedAt: entry.updatedAt,
|
|
1374
|
+
versionId: entry.versionId,
|
|
1375
|
+
})
|
|
1376
|
+
.where(eq(embeddedWorkflows.id, id));
|
|
1377
|
+
await this.armSchedules(id);
|
|
1378
|
+
return responseFromWorkflow(entry.workflow, entry.createdAt, entry.updatedAt, entry.versionId);
|
|
1379
|
+
}
|
|
1380
|
+
async deactivateWorkflow(id) {
|
|
1381
|
+
const entry = await this.getStoredWorkflow(id);
|
|
1382
|
+
const db = this.getDb();
|
|
1383
|
+
entry.workflow.active = false;
|
|
1384
|
+
entry.updatedAt = nowIso();
|
|
1385
|
+
entry.versionId = randomUUID();
|
|
1386
|
+
this.clearSchedules(id);
|
|
1387
|
+
await db
|
|
1388
|
+
.update(embeddedWorkflows)
|
|
1389
|
+
.set({
|
|
1390
|
+
active: false,
|
|
1391
|
+
workflow: entry.workflow,
|
|
1392
|
+
updatedAt: entry.updatedAt,
|
|
1393
|
+
versionId: entry.versionId,
|
|
1394
|
+
})
|
|
1395
|
+
.where(eq(embeddedWorkflows.id, id));
|
|
1396
|
+
return responseFromWorkflow(entry.workflow, entry.createdAt, entry.updatedAt, entry.versionId);
|
|
1397
|
+
}
|
|
1398
|
+
async updateWorkflowTags(id, tagIds) {
|
|
1399
|
+
const entry = await this.getStoredWorkflow(id);
|
|
1400
|
+
const db = this.getDb();
|
|
1401
|
+
const tags = [];
|
|
1402
|
+
for (const tagId of tagIds) {
|
|
1403
|
+
const rows = await db.select().from(embeddedTags).where(eq(embeddedTags.id, tagId)).limit(1);
|
|
1404
|
+
const tag = rows[0];
|
|
1405
|
+
if (!tag)
|
|
1406
|
+
throw new WorkflowApiError(`Tag not found: ${tagId}`, 404);
|
|
1407
|
+
tags.push({ id: tag.id, name: tag.name, createdAt: tag.createdAt, updatedAt: tag.updatedAt });
|
|
1408
|
+
}
|
|
1409
|
+
entry.workflow.tags = cloneJson(tags);
|
|
1410
|
+
entry.updatedAt = nowIso();
|
|
1411
|
+
entry.versionId = randomUUID();
|
|
1412
|
+
await db
|
|
1413
|
+
.update(embeddedWorkflows)
|
|
1414
|
+
.set({
|
|
1415
|
+
workflow: entry.workflow,
|
|
1416
|
+
updatedAt: entry.updatedAt,
|
|
1417
|
+
versionId: entry.versionId,
|
|
1418
|
+
})
|
|
1419
|
+
.where(eq(embeddedWorkflows.id, id));
|
|
1420
|
+
return cloneJson(tags);
|
|
1421
|
+
}
|
|
1422
|
+
async createCredential(credential) {
|
|
1423
|
+
await this.ensureSchema();
|
|
1424
|
+
const db = this.getDb();
|
|
1425
|
+
const id = randomUUID();
|
|
1426
|
+
const timestamp = nowIso();
|
|
1427
|
+
const stored = {
|
|
1428
|
+
id,
|
|
1429
|
+
name: credential.name,
|
|
1430
|
+
type: credential.type,
|
|
1431
|
+
data: cloneJson(credential.data),
|
|
1432
|
+
isResolvable: true,
|
|
1433
|
+
createdAt: timestamp,
|
|
1434
|
+
updatedAt: timestamp,
|
|
1435
|
+
};
|
|
1436
|
+
await db.insert(embeddedCredentials).values({
|
|
1437
|
+
id,
|
|
1438
|
+
name: stored.name,
|
|
1439
|
+
type: stored.type,
|
|
1440
|
+
data: cloneJson(credential.data),
|
|
1441
|
+
isResolvable: true,
|
|
1442
|
+
createdAt: timestamp,
|
|
1443
|
+
updatedAt: timestamp,
|
|
1444
|
+
});
|
|
1445
|
+
const { data: _data, ...response } = stored;
|
|
1446
|
+
return cloneJson(response);
|
|
1447
|
+
}
|
|
1448
|
+
async deleteCredential(id) {
|
|
1449
|
+
await this.ensureSchema();
|
|
1450
|
+
await this.getDb().delete(embeddedCredentials).where(eq(embeddedCredentials.id, id));
|
|
1451
|
+
}
|
|
1452
|
+
async listExecutions(params) {
|
|
1453
|
+
await this.ensureSchema();
|
|
1454
|
+
const rows = await this.getDb()
|
|
1455
|
+
.select()
|
|
1456
|
+
.from(embeddedExecutions)
|
|
1457
|
+
.where(params?.workflowId && params?.status
|
|
1458
|
+
? and(eq(embeddedExecutions.workflowId, params.workflowId), eq(embeddedExecutions.status, params.status))
|
|
1459
|
+
: params?.workflowId
|
|
1460
|
+
? eq(embeddedExecutions.workflowId, params.workflowId)
|
|
1461
|
+
: params?.status
|
|
1462
|
+
? eq(embeddedExecutions.status, params.status)
|
|
1463
|
+
: undefined)
|
|
1464
|
+
.orderBy(desc(embeddedExecutions.startedAt));
|
|
1465
|
+
const data = rows.map((row) => cloneJson(row.execution));
|
|
1466
|
+
return { data: typeof params?.limit === 'number' ? data.slice(0, params.limit) : data };
|
|
1467
|
+
}
|
|
1468
|
+
async getExecution(id) {
|
|
1469
|
+
await this.ensureSchema();
|
|
1470
|
+
const rows = await this.getDb()
|
|
1471
|
+
.select()
|
|
1472
|
+
.from(embeddedExecutions)
|
|
1473
|
+
.where(eq(embeddedExecutions.id, id))
|
|
1474
|
+
.limit(1);
|
|
1475
|
+
const execution = rows[0]?.execution;
|
|
1476
|
+
if (!execution)
|
|
1477
|
+
throw new WorkflowApiError(`Execution not found: ${id}`, 404);
|
|
1478
|
+
return cloneJson(execution);
|
|
1479
|
+
}
|
|
1480
|
+
async deleteExecution(id) {
|
|
1481
|
+
await this.ensureSchema();
|
|
1482
|
+
await this.getDb().delete(embeddedExecutions).where(eq(embeddedExecutions.id, id));
|
|
1483
|
+
}
|
|
1484
|
+
async listTags() {
|
|
1485
|
+
await this.ensureSchema();
|
|
1486
|
+
const rows = await this.getDb().select().from(embeddedTags).orderBy(embeddedTags.name);
|
|
1487
|
+
return { data: rows.map((row) => cloneJson(row)) };
|
|
1488
|
+
}
|
|
1489
|
+
async createTag(name) {
|
|
1490
|
+
await this.ensureSchema();
|
|
1491
|
+
const db = this.getDb();
|
|
1492
|
+
const existingRows = await db
|
|
1493
|
+
.select()
|
|
1494
|
+
.from(embeddedTags)
|
|
1495
|
+
.where(eq(embeddedTags.name, name))
|
|
1496
|
+
.limit(1);
|
|
1497
|
+
const existing = existingRows[0];
|
|
1498
|
+
if (existing)
|
|
1499
|
+
return cloneJson(existing);
|
|
1500
|
+
const timestamp = nowIso();
|
|
1501
|
+
const tag = { id: randomUUID(), name, createdAt: timestamp, updatedAt: timestamp };
|
|
1502
|
+
await db.insert(embeddedTags).values(tag);
|
|
1503
|
+
return cloneJson(tag);
|
|
1504
|
+
}
|
|
1505
|
+
async getOrCreateTag(name) {
|
|
1506
|
+
await this.ensureSchema();
|
|
1507
|
+
const rows = await this.getDb().select().from(embeddedTags);
|
|
1508
|
+
const existing = rows.find((tag) => tag.name.toLowerCase() === name.toLowerCase());
|
|
1509
|
+
return existing ? cloneJson(existing) : this.createTag(name);
|
|
1510
|
+
}
|
|
1511
|
+
async executeWorkflow(id, options = {}) {
|
|
1512
|
+
const entry = await this.getStoredWorkflow(id);
|
|
1513
|
+
return this.runWorkflow(entry.workflow, options.mode ?? 'manual');
|
|
1514
|
+
}
|
|
1515
|
+
async executeWebhook(path, payload, method = 'POST') {
|
|
1516
|
+
await this.ensureSchema();
|
|
1517
|
+
const normalizedPath = normalizeWebhookPath(path);
|
|
1518
|
+
const normalizedMethod = method.toUpperCase();
|
|
1519
|
+
const rows = await this.getDb()
|
|
1520
|
+
.select()
|
|
1521
|
+
.from(embeddedWorkflows)
|
|
1522
|
+
.where(eq(embeddedWorkflows.active, true));
|
|
1523
|
+
for (const row of rows) {
|
|
1524
|
+
const workflow = cloneJson(row.workflow);
|
|
1525
|
+
const webhookNode = workflow.nodes.find((node) => {
|
|
1526
|
+
if (node.disabled || node.type !== 'workflows-nodes-base.webhook')
|
|
1527
|
+
return false;
|
|
1528
|
+
const nodePath = normalizeWebhookPath(node.parameters.path);
|
|
1529
|
+
const nodeMethod = readString(node.parameters.httpMethod, 'POST').toUpperCase();
|
|
1530
|
+
return nodePath === normalizedPath && nodeMethod === normalizedMethod;
|
|
1531
|
+
});
|
|
1532
|
+
if (!webhookNode)
|
|
1533
|
+
continue;
|
|
1534
|
+
webhookNode.parameters = {
|
|
1535
|
+
...webhookNode.parameters,
|
|
1536
|
+
__embeddedPayload: {
|
|
1537
|
+
...payload,
|
|
1538
|
+
headers: isRecord(payload.headers) ? payload.headers : {},
|
|
1539
|
+
method: normalizedMethod,
|
|
1540
|
+
path: normalizedPath,
|
|
1541
|
+
},
|
|
1542
|
+
};
|
|
1543
|
+
return this.runWorkflow(workflow, 'webhook');
|
|
1544
|
+
}
|
|
1545
|
+
throw new WorkflowApiError(`Webhook not found: ${normalizedMethod} /${normalizedPath}`, 404);
|
|
1546
|
+
}
|
|
1547
|
+
async triggerSchedulesOnce(workflowId) {
|
|
1548
|
+
// Fire scheduled workflows once on demand (used by tests / debug). Reads
|
|
1549
|
+
// active workflows directly from the DB rather than from in-process state
|
|
1550
|
+
// because scheduling state now lives in core's task table.
|
|
1551
|
+
const executions = [];
|
|
1552
|
+
if (workflowId) {
|
|
1553
|
+
const entry = await this.getStoredWorkflow(workflowId);
|
|
1554
|
+
if (!entry.workflow.active)
|
|
1555
|
+
return executions;
|
|
1556
|
+
executions.push(await this.runWorkflow(entry.workflow, 'trigger'));
|
|
1557
|
+
return executions;
|
|
1558
|
+
}
|
|
1559
|
+
await this.ensureSchema();
|
|
1560
|
+
const rows = await this.getDb()
|
|
1561
|
+
.select()
|
|
1562
|
+
.from(embeddedWorkflows)
|
|
1563
|
+
.where(eq(embeddedWorkflows.active, true));
|
|
1564
|
+
for (const row of rows) {
|
|
1565
|
+
const wf = cloneJson(row.workflow);
|
|
1566
|
+
executions.push(await this.runWorkflow(wf, 'trigger'));
|
|
1567
|
+
}
|
|
1568
|
+
return executions;
|
|
1569
|
+
}
|
|
1570
|
+
async getStoredWorkflow(id) {
|
|
1571
|
+
await this.ensureSchema();
|
|
1572
|
+
const rows = await this.getDb()
|
|
1573
|
+
.select()
|
|
1574
|
+
.from(embeddedWorkflows)
|
|
1575
|
+
.where(eq(embeddedWorkflows.id, id))
|
|
1576
|
+
.limit(1);
|
|
1577
|
+
const row = rows[0];
|
|
1578
|
+
if (!row)
|
|
1579
|
+
throw new WorkflowApiError(`Workflow not found: ${id}`, 404);
|
|
1580
|
+
return {
|
|
1581
|
+
workflow: cloneJson(row.workflow),
|
|
1582
|
+
createdAt: row.createdAt,
|
|
1583
|
+
updatedAt: row.updatedAt,
|
|
1584
|
+
versionId: row.versionId,
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
assertRegisteredNodes(workflow) {
|
|
1588
|
+
const missing = workflow.nodes
|
|
1589
|
+
.filter((node) => !node.disabled && !this.nodeTypes.has(node.type))
|
|
1590
|
+
.map((node) => `${node.name} (${node.type})`);
|
|
1591
|
+
if (missing.length > 0) {
|
|
1592
|
+
throw new WorkflowApiError(`Embedded workflow runtime does not support node(s): ${missing.join(', ')}`, 400);
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
/**
|
|
1596
|
+
* Verify the host can host every active node's capability requirements
|
|
1597
|
+
* (fs, inbound, longRunning, childProcess, net). On failure, throw a
|
|
1598
|
+
* 400 with one actionable line per offending node.
|
|
1599
|
+
*/
|
|
1600
|
+
assertHostSupports(workflow) {
|
|
1601
|
+
const host = this.hostCapabilities;
|
|
1602
|
+
const issues = [];
|
|
1603
|
+
for (const node of workflow.nodes) {
|
|
1604
|
+
if (node.disabled)
|
|
1605
|
+
continue;
|
|
1606
|
+
if (!this.nodeTypes.has(node.type))
|
|
1607
|
+
continue;
|
|
1608
|
+
const nodeType = this.nodeTypes.getByNameAndVersion(node.type);
|
|
1609
|
+
const caps = nodeType.description.capabilities;
|
|
1610
|
+
if (!caps)
|
|
1611
|
+
continue;
|
|
1612
|
+
if (caps.requiresFs && !host.fs) {
|
|
1613
|
+
issues.push(`${node.name} (${node.type}) needs filesystem access; host '${host.label}' has no fs — run on a server agent`);
|
|
1614
|
+
}
|
|
1615
|
+
if (caps.requiresInbound && !host.inbound) {
|
|
1616
|
+
issues.push(`${node.name} needs an inbound public webhook; host '${host.label}' can't receive — pair Eliza Cloud or enable plugin-tunnel`);
|
|
1617
|
+
}
|
|
1618
|
+
if (caps.requiresLongRunning && !host.longRunning) {
|
|
1619
|
+
issues.push(`${node.name} needs a long-running process; host '${host.label}' is short-lived — schedule via the cloud cron handler`);
|
|
1620
|
+
}
|
|
1621
|
+
if (caps.requiresChildProcess && !host.childProcess) {
|
|
1622
|
+
issues.push(`${node.name} spawns a child process; not allowed on '${host.label}' — run on a server agent`);
|
|
1623
|
+
}
|
|
1624
|
+
if (caps.requiresNet && !host.net) {
|
|
1625
|
+
issues.push(`${node.name} needs raw sockets; not available on '${host.label}' — use the HTTP Request node or run on a server agent`);
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
if (issues.length > 0) {
|
|
1629
|
+
throw new WorkflowApiError(`Workflow incompatible with host '${host.label}':\n - ${issues.join('\n - ')}`, 400);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
/** Re-create core Tasks for every active workflow on service start.
|
|
1633
|
+
* Tasks themselves persist across restart; this is a reconcile step that
|
|
1634
|
+
* ensures workflows whose schedule changed (or whose tasks were never
|
|
1635
|
+
* created in the first place) end up correctly scheduled. */
|
|
1636
|
+
async rehydrateSchedules() {
|
|
1637
|
+
await this.ensureSchema();
|
|
1638
|
+
const rows = await this.getDb()
|
|
1639
|
+
.select()
|
|
1640
|
+
.from(embeddedWorkflows)
|
|
1641
|
+
.where(eq(embeddedWorkflows.active, true));
|
|
1642
|
+
for (const row of rows) {
|
|
1643
|
+
await this.armSchedules(row.id);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
/** Create one recurring core Task per scheduleTrigger node on the workflow.
|
|
1647
|
+
* Idempotent: existing tasks for this workflow are removed first so the
|
|
1648
|
+
* task set always reflects the current workflow definition. */
|
|
1649
|
+
async armSchedules(workflowId) {
|
|
1650
|
+
await this.clearSchedules(workflowId);
|
|
1651
|
+
if (typeof this.runtime.createTask !== 'function')
|
|
1652
|
+
return;
|
|
1653
|
+
const entry = await this.getStoredWorkflow(workflowId);
|
|
1654
|
+
const scheduleNodes = entry.workflow.nodes.filter((node) => !node.disabled && node.type === 'workflows-nodes-base.scheduleTrigger');
|
|
1655
|
+
if (scheduleNodes.length === 0)
|
|
1656
|
+
return;
|
|
1657
|
+
for (const node of scheduleNodes) {
|
|
1658
|
+
const intervalMs = resolveScheduleIntervalMs(node.parameters);
|
|
1659
|
+
await this.runtime.createTask({
|
|
1660
|
+
name: WORKFLOW_RUN_TASK_WORKER_NAME,
|
|
1661
|
+
description: `Scheduled workflow run: ${entry.workflow.name}`,
|
|
1662
|
+
tags: ['queue', 'repeat', WORKFLOW_TASK_TAG],
|
|
1663
|
+
metadata: {
|
|
1664
|
+
kind: WORKFLOW_TASK_KIND,
|
|
1665
|
+
workflowId,
|
|
1666
|
+
scheduleNodeId: node.id,
|
|
1667
|
+
updateInterval: intervalMs,
|
|
1668
|
+
baseInterval: intervalMs,
|
|
1669
|
+
updatedAt: Date.now(),
|
|
1670
|
+
},
|
|
1671
|
+
});
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
/** Remove every core Task tagged for this workflow. */
|
|
1675
|
+
async clearSchedules(workflowId) {
|
|
1676
|
+
if (typeof this.runtime.getTasks !== 'function')
|
|
1677
|
+
return;
|
|
1678
|
+
const tasks = await this.runtime.getTasks({
|
|
1679
|
+
tags: [WORKFLOW_TASK_TAG],
|
|
1680
|
+
agentIds: [this.runtime.agentId],
|
|
1681
|
+
});
|
|
1682
|
+
if (!tasks?.length)
|
|
1683
|
+
return;
|
|
1684
|
+
for (const task of tasks) {
|
|
1685
|
+
if (task.id &&
|
|
1686
|
+
task.metadata?.workflowId === workflowId) {
|
|
1687
|
+
await this.runtime.deleteTask(task.id);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
async saveExecution(execution) {
|
|
1692
|
+
await this.ensureSchema();
|
|
1693
|
+
await this.getDb()
|
|
1694
|
+
.insert(embeddedExecutions)
|
|
1695
|
+
.values({
|
|
1696
|
+
id: execution.id,
|
|
1697
|
+
workflowId: execution.workflowId,
|
|
1698
|
+
status: execution.status,
|
|
1699
|
+
mode: execution.mode,
|
|
1700
|
+
finished: execution.finished,
|
|
1701
|
+
startedAt: execution.startedAt,
|
|
1702
|
+
stoppedAt: execution.stoppedAt ?? null,
|
|
1703
|
+
execution: cloneJson(execution),
|
|
1704
|
+
})
|
|
1705
|
+
.onConflictDoUpdate({
|
|
1706
|
+
target: embeddedExecutions.id,
|
|
1707
|
+
set: {
|
|
1708
|
+
workflowId: execution.workflowId,
|
|
1709
|
+
status: execution.status,
|
|
1710
|
+
mode: execution.mode,
|
|
1711
|
+
finished: execution.finished,
|
|
1712
|
+
startedAt: execution.startedAt,
|
|
1713
|
+
stoppedAt: execution.stoppedAt ?? null,
|
|
1714
|
+
execution: cloneJson(execution),
|
|
1715
|
+
},
|
|
1716
|
+
});
|
|
1717
|
+
}
|
|
1718
|
+
buildIncomingConnections(workflowData) {
|
|
1719
|
+
const incoming = new Map();
|
|
1720
|
+
for (const [source, outputsByType] of Object.entries(workflowData.connections ?? {})) {
|
|
1721
|
+
const mainOutputs = outputsByType.main ?? [];
|
|
1722
|
+
mainOutputs.forEach((connections, sourceOutputIndex) => {
|
|
1723
|
+
for (const connection of connections ?? []) {
|
|
1724
|
+
if (connection.type !== 'main')
|
|
1725
|
+
continue;
|
|
1726
|
+
const destination = incoming.get(connection.node) ?? [];
|
|
1727
|
+
destination.push({
|
|
1728
|
+
source,
|
|
1729
|
+
sourceOutputIndex,
|
|
1730
|
+
destinationInputIndex: connection.index ?? 0,
|
|
1731
|
+
});
|
|
1732
|
+
incoming.set(connection.node, destination);
|
|
1733
|
+
}
|
|
1734
|
+
});
|
|
1735
|
+
}
|
|
1736
|
+
return incoming;
|
|
1737
|
+
}
|
|
1738
|
+
resolveStartNodes(workflowData, mode, incoming) {
|
|
1739
|
+
const enabledNodes = workflowData.nodes.filter((node) => !node.disabled);
|
|
1740
|
+
const start = new Set();
|
|
1741
|
+
if (mode === 'webhook') {
|
|
1742
|
+
for (const node of enabledNodes) {
|
|
1743
|
+
if (node.type === 'workflows-nodes-base.webhook' &&
|
|
1744
|
+
isRecord(node.parameters.__embeddedPayload)) {
|
|
1745
|
+
start.add(node.name);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
if (start.size === 0) {
|
|
1749
|
+
for (const node of enabledNodes) {
|
|
1750
|
+
if (node.type === 'workflows-nodes-base.webhook')
|
|
1751
|
+
start.add(node.name);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
else if (mode === 'trigger') {
|
|
1756
|
+
for (const node of enabledNodes) {
|
|
1757
|
+
if (node.type === 'workflows-nodes-base.scheduleTrigger')
|
|
1758
|
+
start.add(node.name);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
else {
|
|
1762
|
+
for (const node of enabledNodes) {
|
|
1763
|
+
if (node.type === 'workflows-nodes-base.manualTrigger')
|
|
1764
|
+
start.add(node.name);
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
if (start.size === 0) {
|
|
1768
|
+
for (const node of enabledNodes) {
|
|
1769
|
+
if ((incoming.get(node.name) ?? []).length === 0)
|
|
1770
|
+
start.add(node.name);
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
return start;
|
|
1774
|
+
}
|
|
1775
|
+
collectInputData(nodeName, incoming, nodeOutputs) {
|
|
1776
|
+
const inputData = [];
|
|
1777
|
+
for (const connection of incoming.get(nodeName) ?? []) {
|
|
1778
|
+
const sourceOutputs = nodeOutputs.get(connection.source) ?? [];
|
|
1779
|
+
const sourceItems = sourceOutputs[connection.sourceOutputIndex] ?? [];
|
|
1780
|
+
inputData[connection.destinationInputIndex] = [
|
|
1781
|
+
...(inputData[connection.destinationInputIndex] ?? []),
|
|
1782
|
+
...sourceItems,
|
|
1783
|
+
];
|
|
1784
|
+
}
|
|
1785
|
+
return inputData.length > 0 ? inputData : [[]];
|
|
1786
|
+
}
|
|
1787
|
+
async executeNode(node, inputData, executionId) {
|
|
1788
|
+
const nodeType = this.nodeTypes.getByNameAndVersion(node.type);
|
|
1789
|
+
const context = {
|
|
1790
|
+
getNode: () => node,
|
|
1791
|
+
getInputData: (inputIndex = 0) => inputData[inputIndex] ?? [],
|
|
1792
|
+
getRuntime: () => this.runtime,
|
|
1793
|
+
getExecutionId: () => executionId,
|
|
1794
|
+
};
|
|
1795
|
+
const output = await nodeType.execute.call(context);
|
|
1796
|
+
return output.length > 0 ? output : [[]];
|
|
1797
|
+
}
|
|
1798
|
+
async runWorkflow(workflowData, mode) {
|
|
1799
|
+
const executionId = randomUUID();
|
|
1800
|
+
const startedAt = new Date();
|
|
1801
|
+
const pending = {
|
|
1802
|
+
id: executionId,
|
|
1803
|
+
finished: false,
|
|
1804
|
+
mode,
|
|
1805
|
+
startedAt: startedAt.toISOString(),
|
|
1806
|
+
workflowId: workflowData.id ?? '',
|
|
1807
|
+
status: 'running',
|
|
1808
|
+
};
|
|
1809
|
+
await this.saveExecution(pending);
|
|
1810
|
+
try {
|
|
1811
|
+
const enabledNodes = workflowData.nodes.filter((node) => !node.disabled);
|
|
1812
|
+
const nodeByName = new Map(enabledNodes.map((node) => [node.name, node]));
|
|
1813
|
+
const incoming = this.buildIncomingConnections(workflowData);
|
|
1814
|
+
const startNodes = this.resolveStartNodes(workflowData, mode, incoming);
|
|
1815
|
+
const nodeOutputs = new Map();
|
|
1816
|
+
const executed = new Set();
|
|
1817
|
+
const runData = {};
|
|
1818
|
+
let lastNodeExecuted;
|
|
1819
|
+
while (executed.size < enabledNodes.length) {
|
|
1820
|
+
let progressed = false;
|
|
1821
|
+
for (const node of enabledNodes) {
|
|
1822
|
+
if (executed.has(node.name))
|
|
1823
|
+
continue;
|
|
1824
|
+
const incomingConnections = incoming.get(node.name)?.filter((connection) => nodeByName.has(connection.source)) ??
|
|
1825
|
+
[];
|
|
1826
|
+
const isStartNode = startNodes.has(node.name);
|
|
1827
|
+
const dependenciesComplete = incomingConnections.every((connection) => executed.has(connection.source));
|
|
1828
|
+
if (!isStartNode && !dependenciesComplete)
|
|
1829
|
+
continue;
|
|
1830
|
+
const inputData = isStartNode && incomingConnections.length === 0
|
|
1831
|
+
? [[]]
|
|
1832
|
+
: this.collectInputData(node.name, incoming, nodeOutputs);
|
|
1833
|
+
const hasInputItems = inputData.some((items) => items.length > 0);
|
|
1834
|
+
const started = Date.now();
|
|
1835
|
+
const outputData = !isStartNode && incomingConnections.length > 0 && !hasInputItems
|
|
1836
|
+
? [[]]
|
|
1837
|
+
: await this.executeNode(node, inputData, executionId);
|
|
1838
|
+
nodeOutputs.set(node.name, outputData);
|
|
1839
|
+
runData[node.name] = [
|
|
1840
|
+
{
|
|
1841
|
+
startTime: started,
|
|
1842
|
+
executionTime: Date.now() - started,
|
|
1843
|
+
data: { main: cloneJson(outputData) },
|
|
1844
|
+
source: incomingConnections.map((connection) => ({
|
|
1845
|
+
previousNode: connection.source,
|
|
1846
|
+
previousNodeOutput: connection.sourceOutputIndex,
|
|
1847
|
+
previousNodeRun: 0,
|
|
1848
|
+
})),
|
|
1849
|
+
},
|
|
1850
|
+
];
|
|
1851
|
+
executed.add(node.name);
|
|
1852
|
+
lastNodeExecuted = node.name;
|
|
1853
|
+
progressed = true;
|
|
1854
|
+
}
|
|
1855
|
+
if (!progressed) {
|
|
1856
|
+
const unresolved = enabledNodes
|
|
1857
|
+
.filter((node) => !executed.has(node.name))
|
|
1858
|
+
.map((node) => node.name)
|
|
1859
|
+
.join(', ');
|
|
1860
|
+
throw new Error(`Unable to resolve workflow execution order for node(s): ${unresolved}`);
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
const stoppedAt = new Date();
|
|
1864
|
+
const execution = {
|
|
1865
|
+
...pending,
|
|
1866
|
+
finished: true,
|
|
1867
|
+
status: 'success',
|
|
1868
|
+
stoppedAt: stoppedAt.toISOString(),
|
|
1869
|
+
data: {
|
|
1870
|
+
resultData: {
|
|
1871
|
+
runData,
|
|
1872
|
+
lastNodeExecuted,
|
|
1873
|
+
},
|
|
1874
|
+
},
|
|
1875
|
+
};
|
|
1876
|
+
await this.saveExecution(execution);
|
|
1877
|
+
return cloneJson(execution);
|
|
1878
|
+
}
|
|
1879
|
+
catch (error) {
|
|
1880
|
+
const stoppedAt = new Date();
|
|
1881
|
+
const execution = {
|
|
1882
|
+
...pending,
|
|
1883
|
+
finished: true,
|
|
1884
|
+
status: 'error',
|
|
1885
|
+
stoppedAt: stoppedAt.toISOString(),
|
|
1886
|
+
data: {
|
|
1887
|
+
resultData: {
|
|
1888
|
+
error: {
|
|
1889
|
+
message: error instanceof Error ? error.message : String(error),
|
|
1890
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
1891
|
+
},
|
|
1892
|
+
},
|
|
1893
|
+
},
|
|
1894
|
+
};
|
|
1895
|
+
await this.saveExecution(execution);
|
|
1896
|
+
throw error;
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
//# sourceMappingURL=embedded-workflow-service.js.map
|