@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,712 @@
|
|
|
1
|
+
import { logger } from '@elizaos/core';
|
|
2
|
+
import { getNodeDefinition, simplifyNodeForLLM } from './catalog';
|
|
3
|
+
import { fieldExistsInSchema, getAllFieldPathsTyped, loadOutputSchema, loadTriggerOutputSchema, parseExpressions, } from './outputSchema';
|
|
4
|
+
function isTriggerNode(type) {
|
|
5
|
+
const t = type.toLowerCase();
|
|
6
|
+
return t.includes('trigger') || t.includes('webhook');
|
|
7
|
+
}
|
|
8
|
+
export function validateWorkflow(workflow) {
|
|
9
|
+
const errors = [];
|
|
10
|
+
const warnings = [];
|
|
11
|
+
// 1. Check nodes array exists and is non-empty
|
|
12
|
+
if (!workflow.nodes || !Array.isArray(workflow.nodes)) {
|
|
13
|
+
errors.push('Missing or invalid nodes array');
|
|
14
|
+
return { valid: false, errors, warnings };
|
|
15
|
+
}
|
|
16
|
+
if (workflow.nodes.length === 0) {
|
|
17
|
+
errors.push('Workflow must have at least one node');
|
|
18
|
+
return { valid: false, errors, warnings };
|
|
19
|
+
}
|
|
20
|
+
// 2. Check connections structure
|
|
21
|
+
if (!workflow.connections || typeof workflow.connections !== 'object') {
|
|
22
|
+
errors.push('Missing or invalid connections object');
|
|
23
|
+
return { valid: false, errors, warnings };
|
|
24
|
+
}
|
|
25
|
+
// 3. Validate each node
|
|
26
|
+
const nodeNames = new Set();
|
|
27
|
+
const nodeMap = new Map();
|
|
28
|
+
for (const node of workflow.nodes) {
|
|
29
|
+
// Check required fields
|
|
30
|
+
if (!node.name || typeof node.name !== 'string') {
|
|
31
|
+
errors.push('Node missing name');
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (!node.type || typeof node.type !== 'string') {
|
|
35
|
+
errors.push(`Node "${node.name}" missing type`);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
// Check for duplicate names
|
|
39
|
+
if (nodeNames.has(node.name)) {
|
|
40
|
+
errors.push(`Duplicate node name: "${node.name}"`);
|
|
41
|
+
}
|
|
42
|
+
nodeNames.add(node.name);
|
|
43
|
+
nodeMap.set(node.name, node);
|
|
44
|
+
// Check position (positionNodes() will fix this after validation)
|
|
45
|
+
if (!node.position || !Array.isArray(node.position) || node.position.length !== 2) {
|
|
46
|
+
warnings.push(`Node "${node.name}" has invalid position, will be auto-positioned`);
|
|
47
|
+
}
|
|
48
|
+
// Check parameters
|
|
49
|
+
if (!node.parameters || typeof node.parameters !== 'object') {
|
|
50
|
+
warnings.push(`Node "${node.name}" missing parameters object`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// 4. Validate connections reference existing nodes
|
|
54
|
+
for (const [sourceName, outputs] of Object.entries(workflow.connections)) {
|
|
55
|
+
if (!nodeNames.has(sourceName)) {
|
|
56
|
+
errors.push(`Connection references non-existent source node: "${sourceName}"`);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
for (const [_outputType, connections] of Object.entries(outputs)) {
|
|
60
|
+
if (!Array.isArray(connections)) {
|
|
61
|
+
errors.push(`Invalid connection structure for node "${sourceName}"`);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
for (const connectionGroup of connections) {
|
|
65
|
+
if (!Array.isArray(connectionGroup)) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
for (const connection of connectionGroup) {
|
|
69
|
+
if (!connection.node || typeof connection.node !== 'string') {
|
|
70
|
+
errors.push(`Invalid connection from "${sourceName}"`);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (!nodeNames.has(connection.node)) {
|
|
74
|
+
errors.push(`Connection references non-existent target node: "${connection.node}" (from "${sourceName}")`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// 5. Check for at least one trigger node
|
|
81
|
+
const hasTrigger = workflow.nodes.some((node) => isTriggerNode(node.type) || node.name.toLowerCase().includes('start'));
|
|
82
|
+
if (!hasTrigger) {
|
|
83
|
+
warnings.push('Workflow has no trigger node - it can only be executed manually');
|
|
84
|
+
}
|
|
85
|
+
// 6. Check for orphan nodes (nodes with no incoming connections, except triggers)
|
|
86
|
+
const nodesWithIncoming = new Set();
|
|
87
|
+
for (const outputs of Object.values(workflow.connections)) {
|
|
88
|
+
for (const connectionGroup of Object.values(outputs)) {
|
|
89
|
+
for (const connections of connectionGroup) {
|
|
90
|
+
for (const conn of connections) {
|
|
91
|
+
nodesWithIncoming.add(conn.node);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
for (const node of workflow.nodes) {
|
|
97
|
+
if (!isTriggerNode(node.type) &&
|
|
98
|
+
!node.name.toLowerCase().includes('start') &&
|
|
99
|
+
!nodesWithIncoming.has(node.name)) {
|
|
100
|
+
warnings.push(`Node "${node.name}" has no incoming connections - it will never execute`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
if (errors.length > 0) {
|
|
104
|
+
return { valid: false, errors, warnings };
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
valid: true,
|
|
108
|
+
errors: [],
|
|
109
|
+
warnings,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
export function validateNodeParameters(workflow) {
|
|
113
|
+
const warnings = [];
|
|
114
|
+
for (const node of workflow.nodes) {
|
|
115
|
+
const nodeDef = getNodeDefinition(node.type);
|
|
116
|
+
if (!nodeDef) {
|
|
117
|
+
continue;
|
|
118
|
+
} // Unknown node type — skip
|
|
119
|
+
const effectiveParams = buildEffectiveParams(nodeDef, node);
|
|
120
|
+
for (const prop of nodeDef.properties) {
|
|
121
|
+
if (!prop.required) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (!isPropertyVisible(prop, effectiveParams)) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
const value = node.parameters?.[prop.name];
|
|
128
|
+
if (value === undefined || value === null || value === '') {
|
|
129
|
+
const label = prop.displayName || prop.name;
|
|
130
|
+
// Include the catalog property description in parentheses when
|
|
131
|
+
// present. The displayName alone is often opaque ("Name", "Type",
|
|
132
|
+
// "Mode") and the user has no way to know what the parameter
|
|
133
|
+
// actually governs. The description is the same hover-text the
|
|
134
|
+
// upstream node UI shows, so it carries real semantic information.
|
|
135
|
+
// Catalog descriptions sometimes contain raw HTML (e.g.
|
|
136
|
+
// <a href="...">expression</a>) sourced from the upstream
|
|
137
|
+
// node-types definitions; strip tags before interpolation so the
|
|
138
|
+
// clarification surfaces in plain-text contexts cleanly.
|
|
139
|
+
const description = prop.description?.replace(/<[^>]*>/g, '').trim();
|
|
140
|
+
const detail = description ? ` (${description})` : '';
|
|
141
|
+
warnings.push(`Node "${node.name}": missing required parameter "${label}"${detail}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return warnings;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Build effective parameters for visibility checks by applying property defaults in two passes.
|
|
149
|
+
*
|
|
150
|
+
* Pass 1: always-visible props (no displayOptions) — e.g. `resource` default "message".
|
|
151
|
+
* Pass 2: props whose displayOptions are satisfied by pass-1 defaults — e.g. `operation`
|
|
152
|
+
* default "send" becomes visible once `resource` is known.
|
|
153
|
+
*
|
|
154
|
+
* Two passes resolve the depth-2 chains present in workflows node definitions
|
|
155
|
+
* (root prop → one level of conditional). The `@version` key is injected as the
|
|
156
|
+
* node's typeVersion so displayOptions conditions that reference it work correctly.
|
|
157
|
+
*/
|
|
158
|
+
function buildEffectiveParams(nodeDef, node) {
|
|
159
|
+
const effective = { '@version': node.typeVersion };
|
|
160
|
+
// Pass 1: always-visible properties (no displayOptions)
|
|
161
|
+
for (const prop of nodeDef.properties) {
|
|
162
|
+
if (!prop.displayOptions && !(prop.name in node.parameters) && prop.default !== undefined) {
|
|
163
|
+
effective[prop.name] = prop.default;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// Merge actual params so pass 2 sees LLM-provided values (e.g. resource set
|
|
167
|
+
// explicitly while operation is omitted — operation's displayOptions depends on resource).
|
|
168
|
+
Object.assign(effective, node.parameters);
|
|
169
|
+
// Pass 2: properties with displayOptions that are now satisfied by pass-1 defaults + actual params
|
|
170
|
+
for (const prop of nodeDef.properties) {
|
|
171
|
+
if (prop.displayOptions &&
|
|
172
|
+
!(prop.name in effective) &&
|
|
173
|
+
prop.default !== undefined &&
|
|
174
|
+
isPropertyVisible(prop, effective)) {
|
|
175
|
+
effective[prop.name] = prop.default;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return effective;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* workflows displayOptions logic:
|
|
182
|
+
* - `show`: ALL conditions must match for visible
|
|
183
|
+
* - `hide`: ANY match hides the property
|
|
184
|
+
*/
|
|
185
|
+
function isPropertyVisible(prop, parameters) {
|
|
186
|
+
if (!prop.displayOptions) {
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
const show = prop.displayOptions;
|
|
190
|
+
// If "show" is defined, ALL conditions must match
|
|
191
|
+
if (show.show) {
|
|
192
|
+
for (const [key, allowedValues] of Object.entries(show.show)) {
|
|
193
|
+
if (!Array.isArray(allowedValues)) {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const paramValue = parameters?.[key];
|
|
197
|
+
if (!allowedValues.includes(paramValue)) {
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// If "hide" is defined, ANY match hides the property
|
|
203
|
+
if (show.hide) {
|
|
204
|
+
for (const [key, hiddenValues] of Object.entries(show.hide)) {
|
|
205
|
+
if (!Array.isArray(hiddenValues)) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const paramValue = parameters?.[key];
|
|
209
|
+
if (hiddenValues.includes(paramValue)) {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
export function validateNodeInputs(workflow) {
|
|
217
|
+
const warnings = [];
|
|
218
|
+
// Count incoming connections per node
|
|
219
|
+
const incomingCount = new Map();
|
|
220
|
+
for (const node of workflow.nodes) {
|
|
221
|
+
incomingCount.set(node.name, 0);
|
|
222
|
+
}
|
|
223
|
+
for (const outputs of Object.values(workflow.connections)) {
|
|
224
|
+
for (const connectionGroups of Object.values(outputs)) {
|
|
225
|
+
for (const connections of connectionGroups) {
|
|
226
|
+
for (const conn of connections) {
|
|
227
|
+
incomingCount.set(conn.node, (incomingCount.get(conn.node) || 0) + 1);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
for (const node of workflow.nodes) {
|
|
233
|
+
const nodeDef = getNodeDefinition(node.type);
|
|
234
|
+
if (!nodeDef) {
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
if (isTriggerNode(node.type) || nodeDef.group.includes('trigger')) {
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
// Dynamic inputs (workflows expression string) can't be validated statically
|
|
241
|
+
if (!Array.isArray(nodeDef.inputs)) {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const expectedInputs = nodeDef.inputs.filter((i) => i === 'main').length;
|
|
245
|
+
const actualInputs = incomingCount.get(node.name) || 0;
|
|
246
|
+
if (expectedInputs > 0 && actualInputs < expectedInputs) {
|
|
247
|
+
warnings.push(`Node "${node.name}" expects ${expectedInputs} input(s) but has ${actualInputs}`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return warnings;
|
|
251
|
+
}
|
|
252
|
+
export function positionNodes(workflow) {
|
|
253
|
+
// Clone workflow
|
|
254
|
+
const positioned = { ...workflow };
|
|
255
|
+
positioned.nodes = [...workflow.nodes];
|
|
256
|
+
// Check if all nodes already have valid positions
|
|
257
|
+
const allHavePositions = positioned.nodes.every((node) => node.position &&
|
|
258
|
+
Array.isArray(node.position) &&
|
|
259
|
+
node.position.length === 2 &&
|
|
260
|
+
typeof node.position[0] === 'number' &&
|
|
261
|
+
typeof node.position[1] === 'number');
|
|
262
|
+
if (allHavePositions) {
|
|
263
|
+
return positioned; // No changes needed
|
|
264
|
+
}
|
|
265
|
+
// Build node graph to understand flow structure
|
|
266
|
+
const nodeGraph = buildNodeGraph(positioned);
|
|
267
|
+
// Position nodes level by level (breadth-first from triggers)
|
|
268
|
+
const positionedNodes = positionByLevels(positioned.nodes, nodeGraph);
|
|
269
|
+
positioned.nodes = positionedNodes;
|
|
270
|
+
return positioned;
|
|
271
|
+
}
|
|
272
|
+
/** Ensure trigger nodes use simplified output when available. */
|
|
273
|
+
export function normalizeTriggerSimpleParam(workflow) {
|
|
274
|
+
for (const node of workflow.nodes) {
|
|
275
|
+
if (!isTriggerNode(node.type)) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
const def = getNodeDefinition(node.type);
|
|
279
|
+
const hasSimple = def?.properties?.some((p) => p.name === 'simple');
|
|
280
|
+
if (hasSimple) {
|
|
281
|
+
node.parameters = { ...node.parameters, simple: true };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Validates that $json expressions reference fields that exist in upstream node output schemas.
|
|
287
|
+
* Returns a list of invalid references that need correction.
|
|
288
|
+
*/
|
|
289
|
+
export function validateOutputReferences(workflow) {
|
|
290
|
+
const invalidRefs = [];
|
|
291
|
+
const upstreamMap = buildUpstreamMap(workflow);
|
|
292
|
+
const nodeMap = new Map(workflow.nodes.map((n) => [n.name, n]));
|
|
293
|
+
const schemaCache = new Map();
|
|
294
|
+
function getSourceSchema(sourceName) {
|
|
295
|
+
if (schemaCache.has(sourceName)) {
|
|
296
|
+
const cached = schemaCache.get(sourceName);
|
|
297
|
+
return cached === undefined ? null : cached;
|
|
298
|
+
}
|
|
299
|
+
const sourceNode = nodeMap.get(sourceName);
|
|
300
|
+
if (!sourceNode) {
|
|
301
|
+
schemaCache.set(sourceName, null);
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
const resource = sourceNode.parameters?.resource || '';
|
|
305
|
+
const operation = sourceNode.parameters?.operation || '';
|
|
306
|
+
const schemaResult = isTriggerNode(sourceNode.type)
|
|
307
|
+
? loadTriggerOutputSchema(sourceNode.type, sourceNode.parameters)
|
|
308
|
+
: loadOutputSchema(sourceNode.type, resource, operation);
|
|
309
|
+
if (!schemaResult) {
|
|
310
|
+
schemaCache.set(sourceName, null);
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
const entry = {
|
|
314
|
+
schema: schemaResult.schema,
|
|
315
|
+
fields: schemaResult.fields,
|
|
316
|
+
node: sourceNode,
|
|
317
|
+
};
|
|
318
|
+
schemaCache.set(sourceName, entry);
|
|
319
|
+
return entry;
|
|
320
|
+
}
|
|
321
|
+
for (const node of workflow.nodes) {
|
|
322
|
+
if (!node.parameters) {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
const expressions = parseExpressions(node.parameters);
|
|
326
|
+
if (expressions.length === 0) {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
const upstreamNames = upstreamMap.get(node.name) || [];
|
|
330
|
+
if (upstreamNames.length === 0) {
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
const defaultSourceName = upstreamNames[0];
|
|
334
|
+
for (const expr of expressions) {
|
|
335
|
+
const sourceName = expr.sourceNodeName || defaultSourceName;
|
|
336
|
+
const cached = getSourceSchema(sourceName);
|
|
337
|
+
if (!cached) {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
const exists = fieldExistsInSchema(expr.path, cached.schema);
|
|
341
|
+
if (!exists) {
|
|
342
|
+
const resource = cached.node.parameters?.resource || '';
|
|
343
|
+
const operation = cached.node.parameters?.operation || '';
|
|
344
|
+
invalidRefs.push({
|
|
345
|
+
nodeName: node.name,
|
|
346
|
+
expression: expr.fullExpression,
|
|
347
|
+
field: expr.field,
|
|
348
|
+
sourceNodeName: sourceName,
|
|
349
|
+
sourceNodeType: cached.node.type,
|
|
350
|
+
resource,
|
|
351
|
+
operation,
|
|
352
|
+
availableFields: getAllFieldPathsTyped(cached.schema).map((f) => `${f.path} (${f.type})`),
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return invalidRefs;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Correct invalid option parameter values and typeVersion against catalog definitions.
|
|
361
|
+
* Top-level options (resource) are fixed first so displayOptions cascading works for dependent ones (operation).
|
|
362
|
+
*/
|
|
363
|
+
export function correctOptionParameters(workflow) {
|
|
364
|
+
let corrections = 0;
|
|
365
|
+
for (const node of workflow.nodes) {
|
|
366
|
+
const nodeDef = getNodeDefinition(node.type);
|
|
367
|
+
if (!nodeDef) {
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
if (node.type !== nodeDef.name) {
|
|
371
|
+
logger.warn({ src: 'plugin:workflow:correctOptions' }, `Node "${node.name}": type "${node.type}" → "${nodeDef.name}"`);
|
|
372
|
+
node.type = nodeDef.name;
|
|
373
|
+
corrections++;
|
|
374
|
+
}
|
|
375
|
+
const validVersions = Array.isArray(nodeDef.version) ? nodeDef.version : [nodeDef.version];
|
|
376
|
+
if (node.typeVersion && !validVersions.includes(node.typeVersion)) {
|
|
377
|
+
const maxVersion = Math.max(...validVersions);
|
|
378
|
+
logger.warn({ src: 'plugin:workflow:correctOptions' }, `Node "${node.name}": typeVersion ${node.typeVersion} → ${maxVersion}`);
|
|
379
|
+
node.typeVersion = maxVersion;
|
|
380
|
+
corrections++;
|
|
381
|
+
}
|
|
382
|
+
const topLevel = [];
|
|
383
|
+
const dependent = [];
|
|
384
|
+
for (const prop of nodeDef.properties) {
|
|
385
|
+
if (prop.type !== 'options' || !prop.options?.length) {
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
if (prop.displayOptions) {
|
|
389
|
+
dependent.push(prop);
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
topLevel.push(prop);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
for (const prop of topLevel) {
|
|
396
|
+
corrections += fixOptionValue(node, prop);
|
|
397
|
+
}
|
|
398
|
+
const effectiveParamsForDeps = buildEffectiveParams(nodeDef, node);
|
|
399
|
+
for (const prop of dependent) {
|
|
400
|
+
if (!isPropertyVisible(prop, effectiveParamsForDeps)) {
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
corrections += fixOptionValue(node, prop);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
return corrections;
|
|
407
|
+
}
|
|
408
|
+
function fixOptionValue(node, prop) {
|
|
409
|
+
const currentValue = node.parameters[prop.name];
|
|
410
|
+
if (currentValue === undefined) {
|
|
411
|
+
return 0;
|
|
412
|
+
}
|
|
413
|
+
const allowedValues = prop.options?.map((o) => o.value) ?? [];
|
|
414
|
+
if (allowedValues.includes(currentValue)) {
|
|
415
|
+
return 0;
|
|
416
|
+
}
|
|
417
|
+
const corrected = prop.default !== undefined && allowedValues.includes(prop.default)
|
|
418
|
+
? prop.default
|
|
419
|
+
: allowedValues[0];
|
|
420
|
+
logger.warn({ src: 'plugin:workflow:correctOptions' }, `Node "${node.name}": ${prop.name} "${currentValue}" → "${corrected}"`);
|
|
421
|
+
node.parameters[prop.name] = corrected;
|
|
422
|
+
return 1;
|
|
423
|
+
}
|
|
424
|
+
function buildUpstreamMap(workflow) {
|
|
425
|
+
const upstream = new Map();
|
|
426
|
+
for (const node of workflow.nodes) {
|
|
427
|
+
upstream.set(node.name, []);
|
|
428
|
+
}
|
|
429
|
+
for (const [sourceName, outputs] of Object.entries(workflow.connections)) {
|
|
430
|
+
for (const connectionGroups of Object.values(outputs)) {
|
|
431
|
+
for (const connections of connectionGroups) {
|
|
432
|
+
for (const conn of connections) {
|
|
433
|
+
const existing = upstream.get(conn.node) || [];
|
|
434
|
+
if (!existing.includes(sourceName)) {
|
|
435
|
+
existing.push(sourceName);
|
|
436
|
+
upstream.set(conn.node, existing);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return upstream;
|
|
443
|
+
}
|
|
444
|
+
function buildNodeGraph(workflow) {
|
|
445
|
+
const graph = new Map();
|
|
446
|
+
// Initialize all nodes
|
|
447
|
+
for (const node of workflow.nodes) {
|
|
448
|
+
graph.set(node.name, []);
|
|
449
|
+
}
|
|
450
|
+
// Build edges from connections
|
|
451
|
+
for (const [sourceName, outputs] of Object.entries(workflow.connections)) {
|
|
452
|
+
const targets = [];
|
|
453
|
+
for (const connectionGroups of Object.values(outputs)) {
|
|
454
|
+
for (const connections of connectionGroups) {
|
|
455
|
+
for (const conn of connections) {
|
|
456
|
+
if (conn.node) {
|
|
457
|
+
targets.push(conn.node);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
graph.set(sourceName, targets);
|
|
463
|
+
}
|
|
464
|
+
return graph;
|
|
465
|
+
}
|
|
466
|
+
function positionByLevels(nodes, graph) {
|
|
467
|
+
// Find trigger/start nodes (nodes with no incoming connections)
|
|
468
|
+
const incomingCount = new Map();
|
|
469
|
+
for (const node of nodes) {
|
|
470
|
+
incomingCount.set(node.name, 0);
|
|
471
|
+
}
|
|
472
|
+
for (const targets of graph.values()) {
|
|
473
|
+
for (const target of targets) {
|
|
474
|
+
incomingCount.set(target, (incomingCount.get(target) || 0) + 1);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
const triggerNodes = nodes.filter((node) => incomingCount.get(node.name) === 0);
|
|
478
|
+
// Organize into levels
|
|
479
|
+
const levels = [];
|
|
480
|
+
const visited = new Set();
|
|
481
|
+
const queue = [];
|
|
482
|
+
// Start with triggers at level 0
|
|
483
|
+
for (const trigger of triggerNodes) {
|
|
484
|
+
queue.push({ name: trigger.name, level: 0 });
|
|
485
|
+
}
|
|
486
|
+
while (queue.length > 0) {
|
|
487
|
+
const next = queue.shift();
|
|
488
|
+
if (!next) {
|
|
489
|
+
continue;
|
|
490
|
+
}
|
|
491
|
+
const { name, level } = next;
|
|
492
|
+
if (visited.has(name)) {
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
495
|
+
visited.add(name);
|
|
496
|
+
// Add to level
|
|
497
|
+
if (!levels[level]) {
|
|
498
|
+
levels[level] = [];
|
|
499
|
+
}
|
|
500
|
+
levels[level].push(name);
|
|
501
|
+
// Add children to next level
|
|
502
|
+
const children = graph.get(name) || [];
|
|
503
|
+
for (const child of children) {
|
|
504
|
+
if (!visited.has(child)) {
|
|
505
|
+
queue.push({ name: child, level: level + 1 });
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
// Position nodes based on levels
|
|
510
|
+
const positioned = [...nodes];
|
|
511
|
+
const nodeMap = new Map(nodes.map((node) => [node.name, node]));
|
|
512
|
+
const startX = 250;
|
|
513
|
+
const startY = 300;
|
|
514
|
+
const xSpacing = 250;
|
|
515
|
+
const ySpacing = 100;
|
|
516
|
+
for (let levelIndex = 0; levelIndex < levels.length; levelIndex++) {
|
|
517
|
+
const levelNodes = levels[levelIndex];
|
|
518
|
+
const x = startX + levelIndex * xSpacing;
|
|
519
|
+
// Center nodes vertically if multiple in same level
|
|
520
|
+
const totalHeight = levelNodes.length * ySpacing;
|
|
521
|
+
const startYForLevel = startY - totalHeight / 2;
|
|
522
|
+
for (let i = 0; i < levelNodes.length; i++) {
|
|
523
|
+
const nodeName = levelNodes[i];
|
|
524
|
+
const node = nodeMap.get(nodeName);
|
|
525
|
+
if (node) {
|
|
526
|
+
const y = startYForLevel + i * ySpacing;
|
|
527
|
+
const nodeIndex = positioned.findIndex((n) => n.name === nodeName);
|
|
528
|
+
if (nodeIndex !== -1) {
|
|
529
|
+
positioned[nodeIndex] = {
|
|
530
|
+
...positioned[nodeIndex],
|
|
531
|
+
position: [x, y],
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return positioned;
|
|
538
|
+
}
|
|
539
|
+
export function detectUnknownParameters(workflow) {
|
|
540
|
+
const detections = [];
|
|
541
|
+
for (const node of workflow.nodes) {
|
|
542
|
+
const nodeDef = getNodeDefinition(node.type);
|
|
543
|
+
if (!nodeDef || !node.parameters) {
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
// Compute visible property names using effective parameters (actual + defaults).
|
|
547
|
+
// Defaults are applied for always-visible props first, then for newly-visible props,
|
|
548
|
+
// so that chained displayOptions (resource → operation → field) resolve correctly.
|
|
549
|
+
const effectiveParams = buildEffectiveParams(nodeDef, node);
|
|
550
|
+
const visibleNames = new Set();
|
|
551
|
+
for (const prop of nodeDef.properties) {
|
|
552
|
+
if (isPropertyVisible(prop, effectiveParams)) {
|
|
553
|
+
visibleNames.add(prop.name);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
const unknownKeys = [];
|
|
557
|
+
for (const key of Object.keys(node.parameters)) {
|
|
558
|
+
if (!visibleNames.has(key)) {
|
|
559
|
+
unknownKeys.push(key);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
if (unknownKeys.length === 0) {
|
|
563
|
+
continue;
|
|
564
|
+
}
|
|
565
|
+
// Provide simplified visible properties for the LLM correction prompt
|
|
566
|
+
const simplified = simplifyNodeForLLM(nodeDef);
|
|
567
|
+
const visibleSimplified = simplified.properties.filter((p) => visibleNames.has(p.name));
|
|
568
|
+
detections.push({
|
|
569
|
+
nodeName: node.name,
|
|
570
|
+
nodeType: node.type,
|
|
571
|
+
currentParams: node.parameters,
|
|
572
|
+
unknownKeys,
|
|
573
|
+
propertyDefs: visibleSimplified,
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
return detections;
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Prefix all string parameter values containing {{ }} with = so workflows evaluates them as expressions.
|
|
580
|
+
* Without =, workflows treats {{ }} as literal text.
|
|
581
|
+
* Returns the number of values prefixed.
|
|
582
|
+
*/
|
|
583
|
+
/**
|
|
584
|
+
* Deterministically attach a `credentials` block to every node that requires
|
|
585
|
+
* one. Runs after LLM generation as a safety net: even with a hardened
|
|
586
|
+
* `MANDATORY INVARIANT` rule in the system prompt, the LLM occasionally omits
|
|
587
|
+
* the block — and resolveCredentials only fires when a block is present, so
|
|
588
|
+
* an omission means the credential never gets minted server-side and the user
|
|
589
|
+
* has to wire it in workflows's UI.
|
|
590
|
+
*
|
|
591
|
+
* Selection rule:
|
|
592
|
+
* 1. Skip nodes that already have at least one credentials entry.
|
|
593
|
+
* 2. Look up the node's catalog definition. If `def.credentials` is empty,
|
|
594
|
+
* the node doesn't need credentials — skip.
|
|
595
|
+
* 3. Pick the first credential type from `def.credentials` that:
|
|
596
|
+
* - is listed in `runtimeContext.supportedCredentials.nodeTypes` for
|
|
597
|
+
* this node's type, AND
|
|
598
|
+
* - matches the node's `parameters.authentication` (when the credential's
|
|
599
|
+
* displayOptions.show.authentication is set; otherwise unconditional).
|
|
600
|
+
* 4. Inject `node.credentials = { [credType]: { id: "{{CREDENTIAL_ID}}", name } }`.
|
|
601
|
+
* The plugin's `resolveCredentials` later replaces `{{CREDENTIAL_ID}}` with
|
|
602
|
+
* the real workflows credential id.
|
|
603
|
+
*
|
|
604
|
+
* Returns the number of nodes that received an injected block (for logging).
|
|
605
|
+
*/
|
|
606
|
+
export function injectMissingCredentialBlocks(workflow, relevantNodes, runtimeContext) {
|
|
607
|
+
if (!runtimeContext?.supportedCredentials?.length) {
|
|
608
|
+
return 0;
|
|
609
|
+
}
|
|
610
|
+
// Build supportedCredType-by-nodeType lookup. Each supportedCredential entry
|
|
611
|
+
// applies to one or more node types; flip that map so we can ask
|
|
612
|
+
// "for this node type, which cred types does the host support?".
|
|
613
|
+
const supportedByNodeType = new Map();
|
|
614
|
+
for (const sc of runtimeContext.supportedCredentials) {
|
|
615
|
+
for (const nodeType of sc.nodeTypes) {
|
|
616
|
+
if (!supportedByNodeType.has(nodeType)) {
|
|
617
|
+
supportedByNodeType.set(nodeType, new Map());
|
|
618
|
+
}
|
|
619
|
+
supportedByNodeType.get(nodeType)?.set(sc.credType, sc.friendlyName);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
if (supportedByNodeType.size === 0) {
|
|
623
|
+
return 0;
|
|
624
|
+
}
|
|
625
|
+
const defByType = new Map(relevantNodes.map((n) => [n.name, n]));
|
|
626
|
+
let injected = 0;
|
|
627
|
+
for (const node of workflow.nodes) {
|
|
628
|
+
if (node.credentials && Object.keys(node.credentials).length > 0) {
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
const def = defByType.get(node.type);
|
|
632
|
+
if (!def?.credentials?.length) {
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
const supportedForType = supportedByNodeType.get(node.type);
|
|
636
|
+
if (!supportedForType?.size) {
|
|
637
|
+
continue;
|
|
638
|
+
}
|
|
639
|
+
// Resolve which credential type matches this node's authentication choice.
|
|
640
|
+
// workflows nodes typically gate credentials by `displayOptions.show.authentication`
|
|
641
|
+
// (e.g. discord's discordBotApi shows when authentication=botToken).
|
|
642
|
+
const auth = typeof node.parameters?.authentication === 'string'
|
|
643
|
+
? node.parameters.authentication
|
|
644
|
+
: null;
|
|
645
|
+
const candidate = def.credentials.find((c) => {
|
|
646
|
+
if (!supportedForType.has(c.name)) {
|
|
647
|
+
return false;
|
|
648
|
+
}
|
|
649
|
+
const showOpts = c.displayOptions
|
|
650
|
+
?.show;
|
|
651
|
+
if (showOpts?.authentication && showOpts.authentication.length > 0) {
|
|
652
|
+
return auth ? showOpts.authentication.includes(auth) : false;
|
|
653
|
+
}
|
|
654
|
+
// Unconditional credential or no show-rule: take it.
|
|
655
|
+
return true;
|
|
656
|
+
});
|
|
657
|
+
if (!candidate) {
|
|
658
|
+
continue;
|
|
659
|
+
}
|
|
660
|
+
const friendlyName = supportedForType.get(candidate.name) ?? candidate.name;
|
|
661
|
+
node.credentials = {
|
|
662
|
+
[candidate.name]: {
|
|
663
|
+
id: '{{CREDENTIAL_ID}}',
|
|
664
|
+
name: friendlyName,
|
|
665
|
+
},
|
|
666
|
+
};
|
|
667
|
+
logger.debug({
|
|
668
|
+
src: 'plugin:workflow:utils:workflow',
|
|
669
|
+
node: node.name,
|
|
670
|
+
nodeType: node.type,
|
|
671
|
+
credType: candidate.name,
|
|
672
|
+
}, 'Injected missing credentials block on node (LLM omitted it)');
|
|
673
|
+
injected++;
|
|
674
|
+
}
|
|
675
|
+
return injected;
|
|
676
|
+
}
|
|
677
|
+
export function ensureExpressionPrefix(workflow) {
|
|
678
|
+
let count = 0;
|
|
679
|
+
for (const node of workflow.nodes) {
|
|
680
|
+
if (!node.parameters) {
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
count += prefixExpressions(node.parameters);
|
|
684
|
+
}
|
|
685
|
+
return count;
|
|
686
|
+
}
|
|
687
|
+
function prefixExpressions(obj) {
|
|
688
|
+
let count = 0;
|
|
689
|
+
for (const key of Object.keys(obj)) {
|
|
690
|
+
const value = obj[key];
|
|
691
|
+
if (typeof value === 'string' && value.includes('{{') && !value.startsWith('=')) {
|
|
692
|
+
obj[key] = `=${value}`;
|
|
693
|
+
count++;
|
|
694
|
+
}
|
|
695
|
+
else if (Array.isArray(value)) {
|
|
696
|
+
for (let i = 0; i < value.length; i++) {
|
|
697
|
+
if (typeof value[i] === 'string' && value[i].includes('{{') && !value[i].startsWith('=')) {
|
|
698
|
+
value[i] = `=${value[i]}`;
|
|
699
|
+
count++;
|
|
700
|
+
}
|
|
701
|
+
else if (typeof value[i] === 'object' && value[i] !== null) {
|
|
702
|
+
count += prefixExpressions(value[i]);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
else if (typeof value === 'object' && value !== null) {
|
|
707
|
+
count += prefixExpressions(value);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return count;
|
|
711
|
+
}
|
|
712
|
+
//# sourceMappingURL=workflow.js.map
|