@legatoacc3/workflow-builder 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +44 -0
  2. package/dist/components/WorkflowBuilder.vue.d.ts +27 -0
  3. package/dist/components/WorkflowBuilder.vue.d.ts.map +1 -0
  4. package/dist/contracts.d.ts +28 -0
  5. package/dist/contracts.d.ts.map +1 -0
  6. package/dist/domain/builder-node-definition.d.ts +786 -0
  7. package/dist/domain/builder-node-definition.d.ts.map +1 -0
  8. package/dist/domain/builder-node-definition.js +1275 -0
  9. package/dist/domain/builder-node-metadata-response.d.ts +61 -0
  10. package/dist/domain/builder-node-metadata-response.d.ts.map +1 -0
  11. package/dist/domain/builder-node-metadata-response.js +0 -0
  12. package/dist/domain/workflow-draft.d.ts +169 -0
  13. package/dist/domain/workflow-draft.d.ts.map +1 -0
  14. package/dist/domain/workflow-draft.js +2115 -0
  15. package/dist/form-schema/form-schema-editor.d.ts +13 -0
  16. package/dist/form-schema/form-schema-editor.d.ts.map +1 -0
  17. package/dist/form-schema/form-schema-editor.js +65 -0
  18. package/dist/index.d.ts +98 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +9298 -0
  21. package/dist/inspector/BuilderNodeSettingsPanel.vue.d.ts +136 -0
  22. package/dist/inspector/BuilderNodeSettingsPanel.vue.d.ts.map +1 -0
  23. package/dist/inspector/builder-node-settings-registry.d.ts +5 -0
  24. package/dist/inspector/builder-node-settings-registry.d.ts.map +1 -0
  25. package/dist/inspector/builder-node-settings-registry.js +37 -0
  26. package/dist/session/create-builder-session.d.ts +27 -0
  27. package/dist/session/create-builder-session.d.ts.map +1 -0
  28. package/dist/session/create-builder-session.js +670 -0
  29. package/dist/workflow-builder.css +112 -0
  30. package/package.json +71 -0
@@ -0,0 +1,1275 @@
1
+ //#region src/domain/builder-node-definition.ts
2
+ function formatCodeLanguageLabel(language) {
3
+ return language === "python" ? "PYTHON3" : "JAVASCRIPT";
4
+ }
5
+ function createInspectorSections(titles) {
6
+ return titles;
7
+ }
8
+ var difyStandardSections = createInspectorSections([
9
+ {
10
+ id: "core",
11
+ kind: "core",
12
+ title: "Core Settings",
13
+ description: "Primary node configuration required for this step."
14
+ },
15
+ {
16
+ id: "advanced",
17
+ kind: "advanced",
18
+ title: "Advanced",
19
+ description: "Optional tuning and advanced behavior."
20
+ },
21
+ {
22
+ id: "outputs",
23
+ kind: "outputs",
24
+ title: "Outputs",
25
+ description: "Variables or response structure produced by this node."
26
+ },
27
+ {
28
+ id: "last-run",
29
+ kind: "last-run",
30
+ title: "Last Run",
31
+ description: "Most recent execution details for this node."
32
+ }
33
+ ]);
34
+ var branchConditionSourceOptions = [
35
+ {
36
+ label: "Form Field",
37
+ value: "form-field"
38
+ },
39
+ {
40
+ label: "Workflow State",
41
+ value: "workflow-state"
42
+ },
43
+ {
44
+ label: "Approval Decision",
45
+ value: "approval-decision"
46
+ },
47
+ {
48
+ label: "Confidence Threshold",
49
+ value: "confidence-threshold"
50
+ },
51
+ {
52
+ label: "Policy Outcome",
53
+ value: "policy-outcome"
54
+ }
55
+ ];
56
+ function inferLegacyConditionValueType(rawValue) {
57
+ const normalizedValue = rawValue.trim().replace(/^['"]|['"]$/g, "");
58
+ if (normalizedValue === "true" || normalizedValue === "false") return "boolean";
59
+ if (normalizedValue !== "" && Number.isFinite(Number(normalizedValue))) return "number";
60
+ return "string";
61
+ }
62
+ function parseLegacyBranchConditionExpression(expression) {
63
+ const normalizedExpression = expression.trim();
64
+ if (!normalizedExpression) return [];
65
+ const comparisonMatch = /^([A-Za-z0-9_.-]+)\s*(==|!=|>=|<=|>|<)\s*(.+)$/.exec(normalizedExpression);
66
+ if (!comparisonMatch) return [{
67
+ id: crypto.randomUUID(),
68
+ variableId: normalizedExpression,
69
+ variableLabel: normalizedExpression,
70
+ valueType: "string",
71
+ operator: "exists",
72
+ value: ""
73
+ }];
74
+ const variableId = comparisonMatch[1] ?? normalizedExpression;
75
+ const operator = comparisonMatch[2] ?? "==";
76
+ const normalizedValue = (comparisonMatch[3] ?? "").trim().replace(/^['"]|['"]$/g, "");
77
+ return [{
78
+ id: crypto.randomUUID(),
79
+ variableId,
80
+ variableLabel: variableId,
81
+ valueType: inferLegacyConditionValueType(normalizedValue),
82
+ operator: {
83
+ "==": "is",
84
+ "!=": "is-not",
85
+ ">": ">",
86
+ "<": "<",
87
+ ">=": ">=",
88
+ "<=": "<="
89
+ }[operator] ?? "is",
90
+ value: normalizedValue
91
+ }];
92
+ }
93
+ function formatIfElseConditionValue(value, valueType, operator) {
94
+ if ([
95
+ "exists",
96
+ "not-exists",
97
+ "empty",
98
+ "not-empty"
99
+ ].includes(operator)) return "";
100
+ if (valueType === "number" || valueType === "boolean") return value.trim();
101
+ return value.trim() ? `"${value.trim()}"` : "\"\"";
102
+ }
103
+ function buildIfElseConditionRowExpression(row) {
104
+ switch (row.operator) {
105
+ case "exists": return row.variableId;
106
+ case "not-exists": return `NOT ${row.variableId}`;
107
+ case "empty": return `${row.variableId} is empty`;
108
+ case "not-empty": return `${row.variableId} is not empty`;
109
+ case "contains": return `${row.variableId} contains ${formatIfElseConditionValue(row.value, row.valueType, row.operator)}`;
110
+ case "not-contains": return `${row.variableId} not contains ${formatIfElseConditionValue(row.value, row.valueType, row.operator)}`;
111
+ case "is": return `${row.variableId} == ${formatIfElseConditionValue(row.value, row.valueType, row.operator)}`;
112
+ case "is-not": return `${row.variableId} != ${formatIfElseConditionValue(row.value, row.valueType, row.operator)}`;
113
+ default: return `${row.variableId} ${row.operator} ${formatIfElseConditionValue(row.value, row.valueType, row.operator)}`;
114
+ }
115
+ }
116
+ function buildIfElseConditionExpression(condition) {
117
+ const rows = condition.rows?.filter((row) => row.variableId.trim()) ?? [];
118
+ if (!rows.length) return condition.expression?.trim() ?? "";
119
+ const joiner = condition.logicalOperator === "or" ? " OR " : " AND ";
120
+ return rows.map(buildIfElseConditionRowExpression).join(joiner);
121
+ }
122
+ function normalizeIfElseCaseComparisonOperator(operator) {
123
+ const normalizedOperator = String(operator ?? "").trim().toLowerCase();
124
+ switch (normalizedOperator) {
125
+ case "=": return "is";
126
+ case "!=":
127
+ case "<>": return "is-not";
128
+ case "not contains": return "not-contains";
129
+ case "not exists": return "not-exists";
130
+ case "not empty": return "not-empty";
131
+ default: return normalizedOperator;
132
+ }
133
+ }
134
+ function normalizeIfElseCaseValueType(valueType) {
135
+ if (valueType === "number") return "number";
136
+ if (valueType === "boolean") return "boolean";
137
+ return "string";
138
+ }
139
+ function getIfElseOperatorsForValueType(valueType) {
140
+ if (valueType === "number") return [
141
+ ">",
142
+ "<",
143
+ ">=",
144
+ "<=",
145
+ "is",
146
+ "is-not",
147
+ "exists",
148
+ "not-exists",
149
+ "empty",
150
+ "not-empty"
151
+ ];
152
+ if (valueType === "boolean") return [
153
+ "is",
154
+ "is-not",
155
+ "exists",
156
+ "not-exists"
157
+ ];
158
+ return [
159
+ "contains",
160
+ "not-contains",
161
+ "is",
162
+ "is-not",
163
+ "exists",
164
+ "not-exists",
165
+ "empty",
166
+ "not-empty"
167
+ ];
168
+ }
169
+ function normalizeIfElseConditionRowOperator(operator, valueType) {
170
+ const normalizedOperator = normalizeIfElseCaseComparisonOperator(operator);
171
+ if (new Set(getIfElseOperatorsForValueType(valueType)).has(normalizedOperator)) return normalizedOperator;
172
+ if (valueType === "number") return ">";
173
+ if (valueType === "boolean") return "is";
174
+ return "contains";
175
+ }
176
+ function normalizeIfElseConditionRowValue(value, valueType, operator) {
177
+ if ([
178
+ "exists",
179
+ "not-exists",
180
+ "empty",
181
+ "not-empty"
182
+ ].includes(operator)) return "";
183
+ if (valueType === "boolean") return value === false || String(value).trim().toLowerCase() === "false" ? "false" : "true";
184
+ if (Array.isArray(value)) return value.join(".");
185
+ return String(value ?? "");
186
+ }
187
+ function buildIfElseRowFromCaseCondition(condition) {
188
+ const variableId = Array.isArray(condition.variable_selector) && condition.variable_selector.length > 0 ? condition.variable_selector.join(".") : "";
189
+ if (!variableId) return null;
190
+ const valueType = normalizeIfElseCaseValueType(condition.varType);
191
+ const operator = normalizeIfElseConditionRowOperator(condition.comparison_operator, valueType);
192
+ return {
193
+ id: condition.id,
194
+ variableId,
195
+ variableLabel: variableId,
196
+ valueType,
197
+ operator,
198
+ value: normalizeIfElseConditionRowValue(condition.value, valueType, operator)
199
+ };
200
+ }
201
+ function buildIfElseCaseConditionFromRow(row) {
202
+ return {
203
+ id: row.id,
204
+ varType: row.valueType === "number" ? "number" : row.valueType === "boolean" ? "boolean" : "string",
205
+ variable_selector: row.variableId.split(".").map((segment) => segment.trim()).filter(Boolean),
206
+ comparison_operator: row.operator,
207
+ value: row.valueType === "boolean" ? row.value === "false" ? false : true : row.value
208
+ };
209
+ }
210
+ function buildIfElseCasesFromBranchConditions(branches, branchConditions = []) {
211
+ return branches.filter((branch) => branch.type !== "else").map((branch) => {
212
+ const branchCondition = branchConditions.find((condition) => condition.branchId === branch.id);
213
+ const rows = branchCondition?.rows?.filter((row) => row.variableId.trim()) ?? [];
214
+ return {
215
+ case_id: branch.id,
216
+ logical_operator: branchCondition?.logicalOperator === "or" ? "or" : "and",
217
+ conditions: rows.map(buildIfElseCaseConditionFromRow)
218
+ };
219
+ });
220
+ }
221
+ function buildBranchConditionsFromIfElseCases(branches, cases, existingConditions = []) {
222
+ return branches.filter((branch) => branch.type !== "else").map((branch) => {
223
+ const matchingCase = cases?.find((entry) => entry.case_id === branch.id);
224
+ const existingCondition = existingConditions.find((condition) => condition.branchId === branch.id);
225
+ const rows = matchingCase?.conditions?.map(buildIfElseRowFromCaseCondition).filter((row) => Boolean(row)) ?? [];
226
+ const nextCondition = {
227
+ branchId: branch.id,
228
+ type: branch.type,
229
+ source: existingCondition?.source ?? "workflow-state",
230
+ logicalOperator: matchingCase?.logical_operator === "or" ? "or" : existingCondition?.logicalOperator ?? "and",
231
+ rows,
232
+ expression: existingCondition?.expression ?? ""
233
+ };
234
+ return {
235
+ ...nextCondition,
236
+ expression: buildIfElseConditionExpression(nextCondition)
237
+ };
238
+ });
239
+ }
240
+ function buildNormalizedBranchConditions(branches, existingConditions = []) {
241
+ return branches.filter((branch) => branch.type !== "else").map((branch) => {
242
+ const existingCondition = existingConditions.find((condition) => condition.branchId === branch.id);
243
+ const rows = existingCondition?.rows?.length ? existingCondition.rows : parseLegacyBranchConditionExpression(existingCondition?.expression ?? "");
244
+ const normalizedCondition = {
245
+ branchId: branch.id,
246
+ type: branch.type,
247
+ source: existingCondition?.source ?? "workflow-state",
248
+ logicalOperator: existingCondition?.logicalOperator ?? "and",
249
+ rows,
250
+ expression: existingCondition?.expression ?? ""
251
+ };
252
+ return {
253
+ ...normalizedCondition,
254
+ expression: buildIfElseConditionExpression(normalizedCondition)
255
+ };
256
+ });
257
+ }
258
+ function buildSingleMetricSummary(eyebrow, badge, description, metricLabel, metricValue, footer) {
259
+ return {
260
+ eyebrow,
261
+ badge,
262
+ description,
263
+ metrics: [{
264
+ label: metricLabel,
265
+ value: metricValue
266
+ }],
267
+ footer
268
+ };
269
+ }
270
+ function buildModelSummaryValue(model) {
271
+ if (!model) return "Not configured";
272
+ const provider = model.provider.trim();
273
+ const name = model.name.trim();
274
+ if (provider && name) return `${provider}/${name}`;
275
+ return name || provider || "Not configured";
276
+ }
277
+ function formatSelectorSummaryValue(value) {
278
+ if (Array.isArray(value)) return value.length > 0 ? value.join(".") : "Not configured";
279
+ return String(value ?? "").trim() || "Not configured";
280
+ }
281
+ var builderNodeDefinitions = {
282
+ start: {
283
+ kind: "start",
284
+ family: "start-input",
285
+ publicLabel: "User Input",
286
+ badge: "User Input",
287
+ description: "Collect user-provided variables at workflow start.",
288
+ supportsDifyParity: true,
289
+ inspectorSections: difyStandardSections,
290
+ createDefaultConfig: () => ({
291
+ kind: "start",
292
+ variables: []
293
+ }),
294
+ buildSummary: (config) => buildSingleMetricSummary("User input", "User Input", "Define the inputs exposed when the workflow starts.", "Variables", String(config.variables.length || 0), config.variables.length > 0 ? "Start input fields" : "No custom fields")
295
+ },
296
+ "schedule-trigger": {
297
+ kind: "schedule-trigger",
298
+ family: "trigger",
299
+ publicLabel: "Schedule Trigger",
300
+ badge: "Trigger",
301
+ description: "Start this workflow on a schedule.",
302
+ supportsDifyParity: true,
303
+ inspectorSections: difyStandardSections,
304
+ createDefaultConfig: () => ({
305
+ kind: "schedule-trigger",
306
+ schedule: "",
307
+ cron_expression: "",
308
+ mode: "visual",
309
+ frequency: "daily",
310
+ visualConfig: {
311
+ time: "09:00",
312
+ weekdays: ["1"],
313
+ onMinute: 0,
314
+ monthlyDays: [1]
315
+ },
316
+ visual_config: {
317
+ time: "09:00",
318
+ weekdays: ["1"],
319
+ on_minute: 0,
320
+ monthly_days: [1]
321
+ }
322
+ }),
323
+ buildSummary: (config, input) => ({
324
+ eyebrow: "Scheduled start",
325
+ badge: "Trigger",
326
+ description: "Start this workflow on a recurring schedule.",
327
+ metrics: [{
328
+ label: "Schedule",
329
+ value: input?.nextExecutionLabel || config.schedule || "Not configured"
330
+ }]
331
+ })
332
+ },
333
+ "webhook-trigger": {
334
+ kind: "webhook-trigger",
335
+ family: "trigger",
336
+ publicLabel: "Webhook Trigger",
337
+ badge: "Trigger",
338
+ description: "Start this workflow from an inbound webhook.",
339
+ supportsDifyParity: true,
340
+ inspectorSections: difyStandardSections,
341
+ createDefaultConfig: () => ({
342
+ kind: "webhook-trigger",
343
+ webhook_url: "",
344
+ webhook_debug_url: "",
345
+ method: "POST",
346
+ content_type: "application/json",
347
+ headers: [],
348
+ params: [],
349
+ body: [],
350
+ async_mode: true,
351
+ status_code: 200,
352
+ response_body: "",
353
+ variables: [{
354
+ id: "webhook-raw",
355
+ variable: "_webhook_raw",
356
+ label: "raw",
357
+ value_selector: [],
358
+ value_type: "object",
359
+ required: true
360
+ }]
361
+ }),
362
+ buildSummary: (config) => ({
363
+ eyebrow: "Webhook start",
364
+ badge: "Trigger",
365
+ description: "Run this workflow from an inbound webhook.",
366
+ metrics: [{
367
+ label: "URL",
368
+ value: config.webhook_url || "Not configured"
369
+ }, {
370
+ label: "Method",
371
+ value: config.method
372
+ }],
373
+ footer: config.params.length + config.body.length > 0 ? `${config.params.length + config.body.length} extracted variable${config.params.length + config.body.length === 1 ? "" : "s"}` : "Inbound request setup required"
374
+ })
375
+ },
376
+ "trigger-plugin": {
377
+ kind: "trigger-plugin",
378
+ family: "trigger",
379
+ publicLabel: "Trigger Plugin",
380
+ badge: "Trigger",
381
+ description: "Start this workflow from a plugin event trigger.",
382
+ supportsDifyParity: true,
383
+ inspectorSections: difyStandardSections,
384
+ createDefaultConfig: () => ({
385
+ kind: "trigger-plugin",
386
+ provider_id: "",
387
+ provider_type: "",
388
+ provider_name: "",
389
+ event_name: "",
390
+ event_label: "",
391
+ event_parameters: {},
392
+ event_configurations: {},
393
+ output_schema: [],
394
+ parameters_schema: [],
395
+ event_node_version: "2",
396
+ config: {},
397
+ plugin_id: ""
398
+ }),
399
+ buildSummary: (config) => ({
400
+ eyebrow: "Plugin event start",
401
+ badge: "Trigger",
402
+ description: "Start this workflow when a plugin event fires.",
403
+ metrics: [{
404
+ label: "Provider",
405
+ value: config.provider_name || config.provider_id || "Not configured"
406
+ }, {
407
+ label: "Event",
408
+ value: config.event_label || config.event_name || "Required"
409
+ }],
410
+ footer: config.subscription_id?.trim() ? `Subscription: ${config.subscription_id}` : `${Object.keys(config.event_parameters ?? {}).length} event parameter(s)`
411
+ })
412
+ },
413
+ "data-source": {
414
+ kind: "data-source",
415
+ family: "start-input",
416
+ publicLabel: "Data Source",
417
+ badge: "Data Source",
418
+ description: "Start from a configured data source such as local files or plugin data.",
419
+ supportsDifyParity: true,
420
+ inspectorSections: difyStandardSections,
421
+ createDefaultConfig: () => ({
422
+ kind: "data-source",
423
+ plugin_id: "",
424
+ provider_type: "",
425
+ provider_name: "",
426
+ datasource_name: "",
427
+ datasource_label: "",
428
+ datasource_parameters: {},
429
+ datasource_configurations: {},
430
+ output_schema: [],
431
+ fileExtensions: []
432
+ }),
433
+ buildSummary: (config) => ({
434
+ eyebrow: "Data source start",
435
+ badge: "Data Source",
436
+ description: "Start this workflow from data emitted by a configured source.",
437
+ metrics: [{
438
+ label: "Provider",
439
+ value: config.provider_name || config.plugin_id || "Not configured"
440
+ }, {
441
+ label: "Source",
442
+ value: config.datasource_label || config.datasource_name || "Required"
443
+ }],
444
+ footer: config.provider_type === "local_file" ? `${config.fileExtensions?.length ?? 0} file extension(s)` : `${Object.keys(config.datasource_parameters ?? {}).length} parameter(s)`
445
+ })
446
+ },
447
+ end: {
448
+ kind: "end",
449
+ family: "output-response",
450
+ publicLabel: "Output",
451
+ badge: "Output",
452
+ description: "Expose workflow outputs for downstream consumers.",
453
+ supportsDifyParity: true,
454
+ inspectorSections: difyStandardSections,
455
+ createDefaultConfig: () => ({
456
+ kind: "end",
457
+ outputs: [{
458
+ id: "output-1",
459
+ variable: "",
460
+ value_selector: []
461
+ }]
462
+ }),
463
+ buildSummary: (config) => buildSingleMetricSummary("Workflow exit", "Output", "Expose final workflow outputs.", "Outputs", String(config.outputs.filter((entry) => entry.variable.trim()).length), "Named outputs")
464
+ },
465
+ answer: {
466
+ kind: "answer",
467
+ family: "output-response",
468
+ publicLabel: "Answer",
469
+ badge: "Answer",
470
+ description: "Compose the chat response returned to the user.",
471
+ supportsDifyParity: true,
472
+ inspectorSections: difyStandardSections,
473
+ createDefaultConfig: () => ({
474
+ kind: "answer",
475
+ variables: [],
476
+ answer: ""
477
+ }),
478
+ buildSummary: (config) => buildSingleMetricSummary("Chat response", "Answer", "Compose the response body returned in a chat-style flow.", "Content", config.answer.trim() ? "Configured" : "Required", "Supports variable insertion")
479
+ },
480
+ llm: {
481
+ kind: "llm",
482
+ family: "reasoning-routing",
483
+ publicLabel: "LLM",
484
+ badge: "LLM",
485
+ description: "Reason, generate, or transform using a language model.",
486
+ supportsDifyParity: true,
487
+ inspectorSections: difyStandardSections,
488
+ createDefaultConfig: () => ({
489
+ kind: "llm",
490
+ model: {
491
+ provider: "",
492
+ name: ""
493
+ },
494
+ prompt_template: "",
495
+ prompt_config: { jinja2_variables: [] },
496
+ context: {
497
+ enabled: false,
498
+ variable_selector: []
499
+ },
500
+ memory: {
501
+ window: {
502
+ enabled: false,
503
+ size: 3
504
+ },
505
+ query_prompt_template: ""
506
+ },
507
+ vision: {
508
+ enabled: false,
509
+ configs: {
510
+ variable_selector: [],
511
+ detail: "low"
512
+ }
513
+ },
514
+ structured_output_enabled: false,
515
+ structured_output: { schema: "" },
516
+ reasoning_format: "tagged"
517
+ }),
518
+ buildSummary: (config) => ({
519
+ eyebrow: "Model step",
520
+ badge: "LLM",
521
+ description: "Call a language model to reason, generate text, or transform inputs.",
522
+ metrics: [{
523
+ label: "Model",
524
+ value: buildModelSummaryValue(config.model)
525
+ }, {
526
+ label: "Prompt",
527
+ value: config.prompt_template.trim() ? `${config.prompt_template.trim().length} chars` : "Required"
528
+ }],
529
+ footer: config.context.variable_selector.length > 0 ? `Context: ${config.context.variable_selector.join(".")}` : "Prompt required"
530
+ })
531
+ },
532
+ "knowledge-retrieval": {
533
+ kind: "knowledge-retrieval",
534
+ family: "reasoning-routing",
535
+ publicLabel: "Knowledge Retrieval",
536
+ badge: "Knowledge",
537
+ description: "Retrieve grounded context from one or more knowledge bases.",
538
+ supportsDifyParity: true,
539
+ inspectorSections: difyStandardSections,
540
+ createDefaultConfig: () => ({
541
+ kind: "knowledge-retrieval",
542
+ query_variable_selector: [],
543
+ query_attachment_selector: [],
544
+ dataset_ids: [],
545
+ retrieval_mode: "multiple",
546
+ multiple_retrieval_config: {
547
+ top_k: 3,
548
+ reranking_enable: false,
549
+ reranking_model: null
550
+ },
551
+ single_retrieval_config: { model: null },
552
+ metadata_filtering_mode: "disabled",
553
+ metadata_filtering_conditions: {
554
+ logical_operator: "and",
555
+ conditions: []
556
+ },
557
+ metadata_model_config: null
558
+ }),
559
+ buildSummary: (config) => ({
560
+ eyebrow: "Retrieval step",
561
+ badge: "Knowledge",
562
+ description: "Query a knowledge base and pass grounded context downstream.",
563
+ metrics: [{
564
+ label: "Datasets",
565
+ value: String(config.dataset_ids.length)
566
+ }, {
567
+ label: "Recall",
568
+ value: config.retrieval_mode === "single" ? "Single" : `Top ${config.multiple_retrieval_config.top_k}`
569
+ }],
570
+ footer: formatSelectorSummaryValue(config.query_variable_selector) !== "Not configured" ? `Query: ${formatSelectorSummaryValue(config.query_variable_selector)}` : "Query variable required"
571
+ })
572
+ },
573
+ "question-classifier": {
574
+ kind: "question-classifier",
575
+ family: "reasoning-routing",
576
+ publicLabel: "Question Classifier",
577
+ badge: "Classifier",
578
+ description: "Route by categories inferred from the input.",
579
+ supportsDifyParity: true,
580
+ inspectorSections: difyStandardSections,
581
+ createDefaultConfig: () => ({
582
+ kind: "question-classifier",
583
+ query_variable_selector: [],
584
+ model: {
585
+ provider: "",
586
+ name: ""
587
+ },
588
+ classes: [{
589
+ id: "1",
590
+ name: "",
591
+ label: "CLASS 1"
592
+ }, {
593
+ id: "2",
594
+ name: "",
595
+ label: "CLASS 2"
596
+ }],
597
+ instruction: "",
598
+ memory: { window: {
599
+ enabled: false,
600
+ size: 3
601
+ } },
602
+ vision: {
603
+ enabled: false,
604
+ configs: {
605
+ variable_selector: [],
606
+ detail: "low"
607
+ }
608
+ }
609
+ }),
610
+ buildSummary: (config) => ({
611
+ eyebrow: "Routing step",
612
+ badge: "Classifier",
613
+ description: "Classify the incoming question and route to the right branch.",
614
+ metrics: [{
615
+ label: "Model",
616
+ value: buildModelSummaryValue(config.model)
617
+ }, {
618
+ label: "Classes",
619
+ value: String(config.classes.length)
620
+ }],
621
+ footer: config.classes.length > 0 ? config.classes.map((entry, index) => (entry.label || entry.name || `CLASS ${index + 1}`).trim()).filter(Boolean).slice(0, 3).join(" / ") || "Input variable required" : "Input variable required"
622
+ })
623
+ },
624
+ "if-else": {
625
+ kind: "if-else",
626
+ family: "reasoning-routing",
627
+ publicLabel: "If-Else",
628
+ badge: "If-Else",
629
+ description: "Route through IF, ELIF, or ELSE branches.",
630
+ supportsDifyParity: true,
631
+ inspectorSections: difyStandardSections,
632
+ createDefaultConfig: () => ({
633
+ kind: "if-else",
634
+ branchConditions: [],
635
+ _targetBranches: [{
636
+ id: "true",
637
+ name: "IF"
638
+ }, {
639
+ id: "false",
640
+ name: "ELSE"
641
+ }],
642
+ cases: [{
643
+ case_id: "true",
644
+ logical_operator: "and",
645
+ conditions: []
646
+ }],
647
+ logical_operator: "and",
648
+ conditions: [],
649
+ isInIteration: false,
650
+ isInLoop: false
651
+ }),
652
+ buildSummary: (config, input) => ({
653
+ eyebrow: "Condition step",
654
+ badge: "If-Else",
655
+ description: "Evaluate explicit conditions and route through IF, ELIF, or ELSE.",
656
+ metrics: [{
657
+ label: "Conditions",
658
+ value: String(config.branchConditions.length)
659
+ }, {
660
+ label: "Branches",
661
+ value: String(input?.conditionalBranches?.length ?? config.branchConditions.length + 1)
662
+ }],
663
+ footer: config.branchConditions.length > 0 ? `${new Set(config.branchConditions.map((condition) => condition.source || "workflow-state")).size} source type(s)` : "Exclusive branch routing"
664
+ })
665
+ },
666
+ "variable-aggregator": {
667
+ kind: "variable-aggregator",
668
+ family: "reasoning-routing",
669
+ publicLabel: "Variable Aggregator",
670
+ badge: "Aggregator",
671
+ description: "Converge exclusive-branch variables into grouped outputs.",
672
+ supportsDifyParity: true,
673
+ inspectorSections: difyStandardSections,
674
+ createDefaultConfig: () => ({
675
+ kind: "variable-aggregator",
676
+ output_type: "string",
677
+ variables: [],
678
+ advanced_settings: {
679
+ group_enabled: false,
680
+ groups: [{
681
+ groupId: "group-1",
682
+ group_name: "",
683
+ output_type: "string",
684
+ variables: []
685
+ }]
686
+ }
687
+ }),
688
+ buildSummary: (config) => ({
689
+ eyebrow: "Merge step",
690
+ badge: "Aggregator",
691
+ description: "Merge variables from exclusive branches into unified outputs.",
692
+ metrics: [{
693
+ label: "Mode",
694
+ value: config.advanced_settings.group_enabled ? `${config.advanced_settings.groups.length} groups` : "Single output"
695
+ }, {
696
+ label: "Sources",
697
+ value: String(config.advanced_settings.group_enabled ? config.advanced_settings.groups.reduce((count, group) => count + group.variables.length, 0) : config.variables.length)
698
+ }],
699
+ footer: config.advanced_settings.group_enabled ? "Exclusive-branch convergence only" : config.variables.length > 0 ? `Input: ${config.variables.map((selector) => selector.join(".")).join(", ")}` : "No source variables configured"
700
+ })
701
+ },
702
+ code: {
703
+ kind: "code",
704
+ family: "transformation",
705
+ publicLabel: "Code",
706
+ badge: "Code",
707
+ description: "Execute custom script logic.",
708
+ supportsDifyParity: true,
709
+ inspectorSections: difyStandardSections,
710
+ createDefaultConfig: () => ({
711
+ kind: "code",
712
+ language: "python",
713
+ variables: [],
714
+ code_language: "python3",
715
+ code: "",
716
+ outputs: {},
717
+ script: "def main():\n return {\n \"result\": \"\"\n }\n",
718
+ inputVariables: [],
719
+ outputVariables: [{
720
+ id: "output-1",
721
+ name: "result",
722
+ dataType: "string"
723
+ }],
724
+ retryCount: 0,
725
+ retryIntervalMs: 1e3,
726
+ errorHandling: "none"
727
+ }),
728
+ buildSummary: (config) => ({
729
+ eyebrow: "Logic step",
730
+ badge: "Code",
731
+ description: "Run custom logic to transform data or enforce workflow-specific behavior.",
732
+ metrics: [{
733
+ label: "Language",
734
+ value: formatCodeLanguageLabel(config.language)
735
+ }, {
736
+ label: "Outputs",
737
+ value: String(config.outputVariables.length)
738
+ }],
739
+ footer: config.script.trim() ? `${config.script.trim().split("\n").length} line${config.script.trim().split("\n").length === 1 ? "" : "s"}` : (config.retryCount ?? 0) > 0 ? `Retry ${config.retryCount} time${config.retryCount === 1 ? "" : "s"} on failure` : config.inputVariables.length > 0 ? `${config.inputVariables.length} input variable${config.inputVariables.length === 1 ? "" : "s"}` : "Script required"
740
+ })
741
+ },
742
+ template: {
743
+ kind: "template",
744
+ family: "transformation",
745
+ publicLabel: "Template",
746
+ badge: "Template",
747
+ description: "Format variables into structured text using a template.",
748
+ supportsDifyParity: true,
749
+ inspectorSections: difyStandardSections,
750
+ createDefaultConfig: () => ({
751
+ kind: "template",
752
+ variables: [],
753
+ template: ""
754
+ }),
755
+ buildSummary: (config) => ({
756
+ eyebrow: "Formatting step",
757
+ badge: "Template",
758
+ description: "Format variables into structured text using a reusable template.",
759
+ metrics: [{
760
+ label: "Output",
761
+ value: "output"
762
+ }],
763
+ footer: config.template.trim() ? "Template configured" : "Template body required"
764
+ })
765
+ },
766
+ "parameter-extractor": {
767
+ kind: "parameter-extractor",
768
+ family: "transformation",
769
+ publicLabel: "Parameter Extractor",
770
+ badge: "Extractor",
771
+ description: "Extract structured parameters from natural language.",
772
+ supportsDifyParity: true,
773
+ inspectorSections: difyStandardSections,
774
+ createDefaultConfig: () => ({
775
+ kind: "parameter-extractor",
776
+ query: [],
777
+ model: {
778
+ provider: "",
779
+ name: ""
780
+ },
781
+ parameters: [],
782
+ instruction: "",
783
+ reasoning_mode: "prompt",
784
+ memory: { window: {
785
+ enabled: false,
786
+ size: 3
787
+ } },
788
+ vision: {
789
+ enabled: false,
790
+ configs: {
791
+ variable_selector: [],
792
+ detail: "low"
793
+ }
794
+ }
795
+ }),
796
+ buildSummary: (config) => ({
797
+ eyebrow: "Structured input",
798
+ badge: "Extractor",
799
+ description: "Extract structured parameters from natural language input.",
800
+ metrics: [{
801
+ label: "Parameters",
802
+ value: String(config.parameters.length)
803
+ }, {
804
+ label: "Model",
805
+ value: buildModelSummaryValue(config.model)
806
+ }],
807
+ footer: config.query.length > 0 ? `Input: ${config.query.join(".")}` : "Input variable required"
808
+ })
809
+ },
810
+ "doc-extractor": {
811
+ kind: "doc-extractor",
812
+ family: "transformation",
813
+ publicLabel: "Doc Extractor",
814
+ badge: "Doc Extractor",
815
+ description: "Extract text from uploaded documents.",
816
+ supportsDifyParity: true,
817
+ inspectorSections: difyStandardSections,
818
+ createDefaultConfig: () => ({
819
+ kind: "doc-extractor",
820
+ variable_selector: [],
821
+ is_array_file: false
822
+ }),
823
+ buildSummary: (config) => buildSingleMetricSummary("Document parsing", "Doc Extractor", "Extract structured content from uploaded documents and files.", "Source", formatSelectorSummaryValue(config.variable_selector), "Document text extraction")
824
+ },
825
+ "list-operator": {
826
+ kind: "list-operator",
827
+ family: "transformation",
828
+ publicLabel: "List Operator",
829
+ badge: "List Operator",
830
+ description: "Filter, sort, or reshape arrays.",
831
+ supportsDifyParity: true,
832
+ inspectorSections: difyStandardSections,
833
+ createDefaultConfig: () => ({
834
+ kind: "list-operator",
835
+ inputArrayVariable: [],
836
+ operation: "map",
837
+ operationConfig: "",
838
+ variable: [],
839
+ var_type: "",
840
+ item_var_type: "",
841
+ filterEnabled: false,
842
+ filterCondition: {
843
+ key: "",
844
+ operator: "contains",
845
+ value: ""
846
+ },
847
+ filter_by: {
848
+ enabled: false,
849
+ conditions: []
850
+ },
851
+ extractEnabled: false,
852
+ extractTemplate: "1",
853
+ extract_by: {
854
+ enabled: false,
855
+ serial: "1"
856
+ },
857
+ orderByEnabled: false,
858
+ orderByKey: [],
859
+ orderByDirection: "asc",
860
+ order_by: {
861
+ enabled: false,
862
+ key: [],
863
+ value: "asc"
864
+ },
865
+ limit: {
866
+ enabled: false,
867
+ size: 10
868
+ }
869
+ }),
870
+ buildSummary: (config) => ({
871
+ eyebrow: "Array processing",
872
+ badge: "List Operator",
873
+ description: "Filter, sort, and select items from an array variable.",
874
+ metrics: [{
875
+ label: "Operation",
876
+ value: config.operation
877
+ }, {
878
+ label: "Input",
879
+ value: formatSelectorSummaryValue(config.inputArrayVariable)
880
+ }],
881
+ footer: "Array transformation"
882
+ })
883
+ },
884
+ "variable-assigner": {
885
+ kind: "variable-assigner",
886
+ family: "transformation",
887
+ publicLabel: "Variable Assigner",
888
+ badge: "Assigner",
889
+ description: "Persist or update workflow variables.",
890
+ supportsDifyParity: true,
891
+ inspectorSections: difyStandardSections,
892
+ createDefaultConfig: () => ({
893
+ kind: "variable-assigner",
894
+ version: "2",
895
+ items: [{
896
+ id: "assignment-1",
897
+ variable_selector: [],
898
+ input_type: "variable",
899
+ operation: "over-write",
900
+ value: ""
901
+ }]
902
+ }),
903
+ buildSummary: (config) => ({
904
+ eyebrow: "Variable step",
905
+ badge: "Assigner",
906
+ description: "Write values into workflow variables for later use.",
907
+ metrics: [{
908
+ label: "Assignments",
909
+ value: String(config.items.length)
910
+ }, {
911
+ label: "Write modes",
912
+ value: String(new Set(config.items.map((assignment) => assignment.operation)).size)
913
+ }],
914
+ footer: "Workflow state update"
915
+ })
916
+ },
917
+ iteration: {
918
+ kind: "iteration",
919
+ family: "execution-container",
920
+ publicLabel: "Iteration",
921
+ badge: "Iteration",
922
+ description: "Run a subflow once per array item.",
923
+ supportsDifyParity: true,
924
+ inspectorSections: difyStandardSections,
925
+ createDefaultConfig: () => ({
926
+ kind: "iteration",
927
+ startNodeType: "iteration-start",
928
+ start_node_id: "",
929
+ iterator_selector: [],
930
+ iterator_input_type: "array",
931
+ output_selector: [],
932
+ output_type: "array",
933
+ iteration_id: "",
934
+ _isShowTips: false,
935
+ is_parallel: false,
936
+ parallel_nums: 10,
937
+ error_handle_mode: "terminated",
938
+ flatten_output: true
939
+ }),
940
+ buildSummary: (config) => ({
941
+ eyebrow: "List processing",
942
+ badge: "Iteration",
943
+ description: "Run the connected downstream path once for each item in an array and collect the outputs.",
944
+ metrics: [{
945
+ label: "Mode",
946
+ value: config.is_parallel ? "parallel" : "sequential"
947
+ }, {
948
+ label: "Failure",
949
+ value: config.error_handle_mode
950
+ }],
951
+ footer: config.iterator_selector.length > 0 ? `Input: ${config.iterator_selector.join(".")}` : "Input array required"
952
+ })
953
+ },
954
+ "http-request": {
955
+ kind: "http-request",
956
+ family: "external-action",
957
+ publicLabel: "HTTP Request",
958
+ badge: "HTTP",
959
+ description: "Call an external API or webhook.",
960
+ supportsDifyParity: true,
961
+ inspectorSections: difyStandardSections,
962
+ createDefaultConfig: () => ({
963
+ kind: "http-request",
964
+ variables: [],
965
+ method: "get",
966
+ url: "",
967
+ headers: "",
968
+ params: "",
969
+ body: {
970
+ type: "none",
971
+ data: []
972
+ },
973
+ authorization: {
974
+ type: "no-auth",
975
+ config: null
976
+ },
977
+ timeout: {
978
+ max_connect_timeout: 0,
979
+ max_read_timeout: 0,
980
+ max_write_timeout: 0
981
+ },
982
+ retry_config: {
983
+ retry_enabled: true,
984
+ max_retries: 3,
985
+ retry_interval: 100
986
+ },
987
+ ssl_verify: true
988
+ }),
989
+ buildSummary: (config) => ({
990
+ eyebrow: "External call",
991
+ badge: "HTTP",
992
+ description: "Call an external API or webhook from the workflow.",
993
+ metrics: [
994
+ {
995
+ label: "Method",
996
+ value: config.method
997
+ },
998
+ {
999
+ label: "Target",
1000
+ value: config.url.replace(/^https?:\/\/[^/]+/i, "") || config.url || "Not configured"
1001
+ },
1002
+ {
1003
+ label: "Retry",
1004
+ value: config.retry_config.retry_enabled ? `${config.retry_config.max_retries}x` : "Off"
1005
+ }
1006
+ ],
1007
+ footer: config.authorization.type === "no-auth" ? "Outbound integration call" : `Auth: ${config.authorization.config?.type ?? "api-key"}`
1008
+ })
1009
+ },
1010
+ tool: {
1011
+ kind: "tool",
1012
+ family: "external-action",
1013
+ publicLabel: "Tool",
1014
+ badge: "Tool",
1015
+ description: "Invoke a built-in or custom tool.",
1016
+ supportsDifyParity: true,
1017
+ inspectorSections: difyStandardSections,
1018
+ createDefaultConfig: () => ({
1019
+ kind: "tool",
1020
+ reference: {
1021
+ id: "",
1022
+ providerType: "",
1023
+ providerName: "",
1024
+ toolName: "",
1025
+ label: ""
1026
+ },
1027
+ parameters: [],
1028
+ configurations: [],
1029
+ outputSchema: [],
1030
+ provider_id: "",
1031
+ provider_type: "",
1032
+ provider_name: "",
1033
+ tool_name: "",
1034
+ tool_label: "",
1035
+ tool_parameters: {},
1036
+ tool_configurations: {},
1037
+ output_schema: [],
1038
+ tool_node_version: "2"
1039
+ }),
1040
+ buildSummary: (config) => ({
1041
+ eyebrow: "Capability call",
1042
+ badge: "Tool",
1043
+ description: "Invoke a built-in tool, custom capability, or sub-workflow.",
1044
+ metrics: [{
1045
+ label: "Tool",
1046
+ value: config.reference.label || config.reference.toolName || "Not configured"
1047
+ }, {
1048
+ label: "Inputs",
1049
+ value: String(Object.keys(config.tool_parameters ?? {}).length || config.parameters.length)
1050
+ }],
1051
+ footer: config.credentialBinding?.trim() ? `Credential: ${config.credentialBinding}` : config.reference.providerName.trim() ? `Provider: ${config.reference.providerName}` : "Tool binding required"
1052
+ })
1053
+ },
1054
+ agent: {
1055
+ kind: "agent",
1056
+ family: "external-action",
1057
+ publicLabel: "Agent",
1058
+ badge: "Agent",
1059
+ description: "Run an agentic step with optional tool use.",
1060
+ supportsDifyParity: true,
1061
+ inspectorSections: difyStandardSections,
1062
+ createDefaultConfig: () => ({
1063
+ kind: "agent",
1064
+ model: {
1065
+ provider: "",
1066
+ name: ""
1067
+ },
1068
+ strategy: {
1069
+ mode: "function-calling",
1070
+ providerName: "",
1071
+ name: "",
1072
+ label: ""
1073
+ },
1074
+ instructions: "",
1075
+ enabledTools: [],
1076
+ memory: {
1077
+ enabled: false,
1078
+ windowSize: 3,
1079
+ window: {
1080
+ enabled: false,
1081
+ size: 3
1082
+ }
1083
+ },
1084
+ strategyParameters: [],
1085
+ outputSchema: [],
1086
+ agent_strategy_provider_name: "",
1087
+ agent_strategy_name: "",
1088
+ agent_strategy_label: "",
1089
+ agent_parameters: {},
1090
+ output_schema: [],
1091
+ meta: null,
1092
+ tool_node_version: "2"
1093
+ }),
1094
+ buildSummary: (config) => ({
1095
+ eyebrow: "Agentic step",
1096
+ badge: "Agent",
1097
+ description: "Run an agentic step that can reason and invoke tools.",
1098
+ metrics: [{
1099
+ label: "Strategy",
1100
+ value: config.strategy.label || config.strategy.name || config.strategy.mode
1101
+ }, {
1102
+ label: "Tools",
1103
+ value: String(Object.keys(config.agent_parameters ?? {}).length || config.enabledTools.length)
1104
+ }],
1105
+ footer: buildModelSummaryValue(config.model) !== "Not configured" ? `Model: ${buildModelSummaryValue(config.model)}` : config.strategy.providerName.trim() ? `Provider: ${config.strategy.providerName}` : "Strategy required"
1106
+ })
1107
+ },
1108
+ loop: {
1109
+ kind: "loop",
1110
+ family: "execution-container",
1111
+ publicLabel: "Loop",
1112
+ badge: "Loop",
1113
+ description: "Repeat a subflow until an exit condition is met.",
1114
+ supportsDifyParity: true,
1115
+ inspectorSections: difyStandardSections,
1116
+ createDefaultConfig: () => ({
1117
+ kind: "loop",
1118
+ startNodeType: "loop-start",
1119
+ start_node_id: "",
1120
+ logical_operator: "and",
1121
+ break_conditions: [],
1122
+ loop_count: 10,
1123
+ error_handle_mode: "terminated",
1124
+ loop_variables: [],
1125
+ loop_id: ""
1126
+ }),
1127
+ buildSummary: (config) => ({
1128
+ eyebrow: "Cycle control",
1129
+ badge: "Loop",
1130
+ description: "Repeat part of the workflow until a stopping rule is met.",
1131
+ metrics: [{
1132
+ label: "Variables",
1133
+ value: String(config.loop_variables.length)
1134
+ }, {
1135
+ label: "Max",
1136
+ value: config.loop_count ? String(config.loop_count) : "None"
1137
+ }],
1138
+ footer: config.break_conditions.length > 0 ? "Exit condition configured" : "Exit condition required"
1139
+ })
1140
+ },
1141
+ "human-input": {
1142
+ kind: "human-input",
1143
+ family: "reasoning-routing",
1144
+ publicLabel: "Human Input",
1145
+ badge: "Human Input",
1146
+ description: "Pause the workflow and collect structured input from a person.",
1147
+ supportsDifyParity: true,
1148
+ inspectorSections: difyStandardSections,
1149
+ createDefaultConfig: () => ({
1150
+ kind: "human-input",
1151
+ delivery_methods: [],
1152
+ user_actions: [],
1153
+ form_content: "",
1154
+ inputs: [],
1155
+ timeout: 3,
1156
+ timeout_unit: "day"
1157
+ }),
1158
+ buildSummary: (config) => ({
1159
+ eyebrow: "Human review",
1160
+ badge: "Human Input",
1161
+ description: "Pause the workflow and collect a response from a person.",
1162
+ metrics: [{
1163
+ label: "Methods",
1164
+ value: String(config.delivery_methods.filter((method) => method.enabled).length)
1165
+ }, {
1166
+ label: "Inputs",
1167
+ value: String(config.inputs.length)
1168
+ }],
1169
+ footer: config.user_actions.length > 0 ? `${config.user_actions.length} action(s)` : "No user actions configured"
1170
+ })
1171
+ },
1172
+ "knowledge-base": {
1173
+ kind: "knowledge-base",
1174
+ family: "reasoning-routing",
1175
+ publicLabel: "Knowledge Base",
1176
+ badge: "Knowledge Base",
1177
+ description: "Index incoming chunks into a knowledge base.",
1178
+ supportsDifyParity: true,
1179
+ inspectorSections: difyStandardSections,
1180
+ createDefaultConfig: () => ({
1181
+ kind: "knowledge-base",
1182
+ index_chunk_variable_selector: [],
1183
+ keyword_number: 10,
1184
+ retrieval_model: {
1185
+ top_k: 3,
1186
+ score_threshold_enabled: false,
1187
+ score_threshold: .5
1188
+ }
1189
+ }),
1190
+ buildSummary: (config) => ({
1191
+ eyebrow: "Knowledge indexing",
1192
+ badge: "Knowledge Base",
1193
+ description: "Index selected chunks into a knowledge base for retrieval.",
1194
+ metrics: [{
1195
+ label: "Chunks",
1196
+ value: formatSelectorSummaryValue(config.index_chunk_variable_selector)
1197
+ }, {
1198
+ label: "Index",
1199
+ value: config.indexing_technique || "Not configured"
1200
+ }],
1201
+ footer: config.retrieval_model.search_method ? `Retrieval: ${config.retrieval_model.search_method} · Top ${config.retrieval_model.top_k}` : "Retrieval setting required"
1202
+ })
1203
+ },
1204
+ approval: {
1205
+ kind: "approval",
1206
+ family: "lsflow-specific",
1207
+ publicLabel: "Approval",
1208
+ badge: "Approval",
1209
+ description: "Route work to a reviewer and continue by decision outcome.",
1210
+ supportsDifyParity: false,
1211
+ inspectorSections: difyStandardSections,
1212
+ createDefaultConfig: () => ({
1213
+ kind: "approval",
1214
+ approvalOwner: ""
1215
+ }),
1216
+ buildSummary: (config) => buildSingleMetricSummary("Approval step", "Approval", "Assign reviewers, set the decision path, and continue the draft flow.", "Owner", config.approvalOwner || "Not configured", "LSFlow-specific approval routing")
1217
+ },
1218
+ email: {
1219
+ kind: "email",
1220
+ family: "lsflow-specific",
1221
+ publicLabel: "Email",
1222
+ badge: "Email",
1223
+ description: "Send an email notification or outbound workflow message.",
1224
+ supportsDifyParity: false,
1225
+ inspectorSections: difyStandardSections,
1226
+ createDefaultConfig: () => ({
1227
+ kind: "email",
1228
+ to: "",
1229
+ subject: "",
1230
+ body: ""
1231
+ }),
1232
+ buildSummary: (config) => ({
1233
+ eyebrow: "Outbound message",
1234
+ badge: "Email",
1235
+ description: "Send an email notification, update, or outbound workflow message.",
1236
+ metrics: [{
1237
+ label: "To",
1238
+ value: config.to || "Not configured"
1239
+ }],
1240
+ footer: config.subject.trim() ? config.subject : "Subject required"
1241
+ })
1242
+ },
1243
+ note: {
1244
+ kind: "note",
1245
+ family: "lsflow-note",
1246
+ publicLabel: "Note",
1247
+ badge: "Note",
1248
+ description: "Capture reminders, guidance, or annotations on the canvas.",
1249
+ supportsDifyParity: false,
1250
+ inspectorSections: difyStandardSections,
1251
+ createDefaultConfig: () => ({ kind: "note" }),
1252
+ buildSummary: () => ({
1253
+ eyebrow: "Canvas note",
1254
+ badge: "Note",
1255
+ description: "Capture reminders, guidance, or annotations directly on the canvas.",
1256
+ metrics: [],
1257
+ footer: "Non-executable note"
1258
+ })
1259
+ }
1260
+ };
1261
+ function getBuilderNodeDefinition(kind) {
1262
+ if (!kind || !(kind in builderNodeDefinitions)) return null;
1263
+ return builderNodeDefinitions[kind];
1264
+ }
1265
+ function getBuilderPublicNodeLabel(kind) {
1266
+ return getBuilderNodeDefinition(kind)?.publicLabel ?? "Node";
1267
+ }
1268
+ function isDifyMappedNodeKind(kind) {
1269
+ return Boolean(getBuilderNodeDefinition(kind)?.supportsDifyParity);
1270
+ }
1271
+ function createDefaultNodeConfigurationV2(kind) {
1272
+ return getBuilderNodeDefinition(kind)?.createDefaultConfig();
1273
+ }
1274
+ //#endregion
1275
+ export { branchConditionSourceOptions, buildBranchConditionsFromIfElseCases, buildIfElseCasesFromBranchConditions, buildIfElseConditionExpression, buildNormalizedBranchConditions, builderNodeDefinitions, createDefaultNodeConfigurationV2, getBuilderNodeDefinition, getBuilderPublicNodeLabel, isDifyMappedNodeKind };