@agentforge4j/workflow-builder-react 0.2.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/dist/index.cjs ADDED
@@ -0,0 +1,3557 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ WorkflowBuilder: () => WorkflowBuilder,
24
+ WorkflowParseError: () => WorkflowParseError,
25
+ builderReducer: () => builderReducer,
26
+ default: () => WorkflowBuilder,
27
+ parseWorkflowJson: () => parseWorkflowJson,
28
+ serializeWorkflowJson: () => serializeWorkflowJson,
29
+ useBuilderState: () => useBuilderState,
30
+ validateWorkflow: () => validateWorkflow
31
+ });
32
+ module.exports = __toCommonJS(index_exports);
33
+
34
+ // src/api/WorkflowBuilder.tsx
35
+ var import_react14 = require("react");
36
+
37
+ // src/canvas/WorkflowCanvas.tsx
38
+ var import_style = require("@xyflow/react/dist/style.css");
39
+
40
+ // src/copy/workflow-terminology.ts
41
+ var NODE_LABELS = {
42
+ ASK_USER: "Ask a Question",
43
+ AI_STEP: "AI Step",
44
+ AI_DEBATE: "AI with Tools",
45
+ DECISION: "Decision",
46
+ REPEAT: "Loop / Repeat",
47
+ REUSE_WORKFLOW: "Reuse Existing Workflow",
48
+ LOAD_RESOURCE: "Load External Resource",
49
+ STOP: "Stop with Error",
50
+ RETRY: "Retry Previous Step",
51
+ SAVE_RESULT: "Save Result"
52
+ };
53
+ var NODE_DESCRIPTIONS = {
54
+ ASK_USER: "Collect input from the person running the workflow.",
55
+ AI_STEP: "Run an AI prompt and capture its output.",
56
+ AI_DEBATE: "Run an AI step that can call tools and external resources.",
57
+ DECISION: "Choose a path based on a condition.",
58
+ REPEAT: "Repeat a section of the workflow.",
59
+ REUSE_WORKFLOW: "Run another workflow as a step.",
60
+ LOAD_RESOURCE: "Reference an external resource (data, API, document).",
61
+ STOP: "Stop the workflow with an error message.",
62
+ RETRY: "Retry an earlier step.",
63
+ SAVE_RESULT: "Save the workflow's result."
64
+ };
65
+ var ACTION_LABELS = {
66
+ validate: "Check Workflow",
67
+ validating: "Checking...",
68
+ clientValidation: "Workflow Problems",
69
+ serverValidation: "Server Problems",
70
+ reuseWorkflow: "Reuse Existing Workflow",
71
+ saveDraft: "Save draft",
72
+ save: "Save",
73
+ saving: "Saving\u2026",
74
+ run: "Run",
75
+ running: "Running\u2026",
76
+ publish: "Publish",
77
+ publishing: "Publishing\u2026",
78
+ import: "Import",
79
+ importing: "Importing\u2026",
80
+ export: "Export",
81
+ exporting: "Exporting\u2026",
82
+ install: "Install",
83
+ downloadBundle: "Download bundle",
84
+ looksGood: "Looks good",
85
+ thingsToFix: (count) => count === 1 ? "1 thing to fix" : `${count} things to fix`,
86
+ unsavedChanges: "Unsaved changes",
87
+ upToDate: "Up to date",
88
+ validationIssues: "Validation issues",
89
+ builderTitle: "Workflow Builder",
90
+ aiAssist: "Build with AI",
91
+ guidedMode: "Guided",
92
+ advancedMode: "Advanced",
93
+ deleteNode: "Delete node",
94
+ selectStartStep: "Select start step",
95
+ fixIssue: "Fix",
96
+ workflowLooksGood: "Workflow looks good",
97
+ configureStepClose: "Configure this step and close when done.",
98
+ nameField: "Name",
99
+ typeField: "Type",
100
+ descriptionField: "Description",
101
+ questionField: "Question",
102
+ agentField: "Agent",
103
+ agentPlaceholder: "Agent id",
104
+ instructionsField: "Instructions",
105
+ approvalField: "Approval",
106
+ approvalAutomatic: "Automatic",
107
+ approvalReview: "Requires human review",
108
+ approvalRequired: "Requires human approval",
109
+ approvalHelp: "When set to 'Requires human approval', the workflow pauses here for a person to review before continuing.",
110
+ basicsSection: "Basics",
111
+ inputsOutputsSection: "Inputs / Outputs",
112
+ behaviorSection: "Behavior",
113
+ advancedSection: "Advanced",
114
+ addField: "Add field",
115
+ resultNameField: "Result name",
116
+ resourcePathField: "Resource path",
117
+ resultContextKeyField: "Result name (context key)",
118
+ reasonField: "Reason",
119
+ targetStepField: "Target step",
120
+ fallbackTargetField: "Fallback target",
121
+ maxRetriesField: "Max retries",
122
+ maxAttemptsField: "Max attempts",
123
+ primaryAgentField: "Primary agent",
124
+ challengerAgentField: "Challenger agent",
125
+ maxRoundsField: "Max rounds",
126
+ resolutionInstructionsField: "Resolution instructions",
127
+ contextKeyField: "Context key",
128
+ contextKeyHint: "Use the key of a value the previous step stored in context.",
129
+ caseLabelPlaceholder: "Case label",
130
+ caseValuePlaceholder: "Case value",
131
+ connectsToField: "Connects to",
132
+ addCase: "Add case",
133
+ defaultBranchTargetField: "Default branch target",
134
+ loopModeField: "Mode",
135
+ maxIterationsField: "Max iterations",
136
+ listContextKeyField: "List context key",
137
+ evaluatorAgentField: "Evaluator agent",
138
+ maxIterationsActionField: "When max iterations reached",
139
+ loopBodyReadOnly: "This step is inside a loop body. Nested editing is not ready yet, so its fields are read-only.",
140
+ advancedStepsGuided: "Advanced steps (for power users)",
141
+ advancedSteps: "Advanced steps",
142
+ addStep: "Add step",
143
+ chooseStepDescription: "Choose a step to add to your workflow.",
144
+ selectPlaceholder: "Select\u2026",
145
+ unsupportedBannerTitle: "Some advanced workflow parts are shown read-only because editing support is not ready yet.",
146
+ builderTipsTitle: "Builder tips",
147
+ builderTipSelect: "Select a step to configure it in the side panel.",
148
+ builderTipConnect: "Drag from a connection port to link steps \u2014 arrows show execution order.",
149
+ builderTipLibrary: "Open the step library on the left to add more steps.",
150
+ dismissTips: "Dismiss builder tips",
151
+ otherwiseBranch: "Otherwise",
152
+ loopBodyTitle: "Loop body",
153
+ loopBodyEmpty: "Connect steps inside this loop.",
154
+ workflowIdLabel: "Workflow id",
155
+ workflowNameLabel: "Workflow name",
156
+ workflowNamePlaceholder: "Workflow name",
157
+ workflowIdPlaceholder: "workflow-id",
158
+ modePickerTitle: "How would you like to build?",
159
+ modePickerGuidedHint: "A simple checklist places each step on the canvas for you.",
160
+ modePickerAdvancedHint: "Full step library and every setting from the start.",
161
+ validationDetailsDescription: "Review problems and jump to the step that needs a fix.",
162
+ formFieldsHint: "Form fields are listed below; each needs a label and key.",
163
+ fixedCountMode: "Fixed count",
164
+ forEachMode: "For each item",
165
+ agentSignalMode: "Until AI says done",
166
+ evaluatorMode: "Evaluator says done",
167
+ awaitUserAction: "Ask user",
168
+ failAction: "Stop with error",
169
+ okShort: "OK",
170
+ checkmarkOk: "\u2713 OK"
171
+ };
172
+ var BUILDER_COPY = {
173
+ startHere: "Start here \u2014 this step collects input from whoever runs the workflow.",
174
+ useTemplate: "Use a template",
175
+ templateHint: "Browse starter templates on the home page.",
176
+ modePickerTitle: ACTION_LABELS.modePickerTitle,
177
+ modePickerGuided: ACTION_LABELS.guidedMode,
178
+ modePickerGuidedHint: ACTION_LABELS.modePickerGuidedHint,
179
+ modePickerAdvanced: ACTION_LABELS.advancedMode,
180
+ modePickerAdvancedHint: ACTION_LABELS.modePickerAdvancedHint,
181
+ paletteTitle: "Steps",
182
+ expandPalette: "Expand step library",
183
+ collapsePalette: "Collapse step library",
184
+ validationDetails: "Validation details",
185
+ stepLibrary: "Step library"
186
+ };
187
+ var EDGE_LABELS = {
188
+ approvalGate: "Approval",
189
+ reviewGate: "Review"
190
+ };
191
+ var NODE_STATUS_LABELS = {
192
+ valid: "Ready",
193
+ hasIssues: "Needs attention",
194
+ needsApproval: "Approval required"
195
+ };
196
+ var ISSUE_REWRITES = {
197
+ "workflow id is required": "Add a workflow ID before validating.",
198
+ "workflow name is required": "Give your workflow a name.",
199
+ "at least one step is required": "Add at least one step to the workflow.",
200
+ "step name is required": "Name this step so it is easier to understand.",
201
+ "must be >= 1": "Use a value of 1 or more."
202
+ };
203
+ var GUIDED_STAGE_LABELS = {
204
+ addInput: "Add input",
205
+ configureInput: "Configure input",
206
+ addAiStep: "Add AI step",
207
+ addApproval: "Add approval",
208
+ requireApproval: "Require approval",
209
+ generateResult: "Generate result",
210
+ addSaveStep: "Add save step"
211
+ };
212
+
213
+ // src/canvas/edges/FlowEdge.tsx
214
+ var import_react = require("@xyflow/react");
215
+ var import_jsx_runtime = require("react/jsx-runtime");
216
+ function edgeStroke(transition) {
217
+ if (transition === "HUMAN_APPROVAL") {
218
+ return "var(--builder-color-edge-approval)";
219
+ }
220
+ if (transition === "HUMAN_REVIEW") {
221
+ return "var(--builder-color-edge-review)";
222
+ }
223
+ return "var(--builder-color-edge)";
224
+ }
225
+ function edgeDash(transition) {
226
+ if (transition === "HUMAN_REVIEW") {
227
+ return "6 4";
228
+ }
229
+ return void 0;
230
+ }
231
+ function FlowEdge({
232
+ id,
233
+ sourceX,
234
+ sourceY,
235
+ targetX,
236
+ targetY,
237
+ sourcePosition,
238
+ targetPosition,
239
+ selected,
240
+ markerEnd,
241
+ data
242
+ }) {
243
+ const transition = data?.transition ?? null;
244
+ const [path, labelX, labelY] = (0, import_react.getBezierPath)({
245
+ sourceX,
246
+ sourceY,
247
+ sourcePosition,
248
+ targetX,
249
+ targetY,
250
+ targetPosition
251
+ });
252
+ const stroke = edgeStroke(transition);
253
+ const label = transition === "HUMAN_APPROVAL" ? EDGE_LABELS.approvalGate : transition === "HUMAN_REVIEW" ? EDGE_LABELS.reviewGate : null;
254
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
255
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
256
+ import_react.BaseEdge,
257
+ {
258
+ id,
259
+ path,
260
+ style: {
261
+ strokeWidth: selected ? 2.75 : 2,
262
+ stroke,
263
+ strokeDasharray: edgeDash(transition)
264
+ },
265
+ markerEnd
266
+ }
267
+ ),
268
+ label ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.EdgeLabelRenderer, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
269
+ "div",
270
+ {
271
+ className: "wf-edge-label nodrag nopan",
272
+ style: {
273
+ transform: `translate(-50%, -50%) translate(${labelX}px,${labelY}px)`
274
+ },
275
+ children: label
276
+ }
277
+ ) }) : null
278
+ ] });
279
+ }
280
+
281
+ // src/canvas/empty/EmptyState.tsx
282
+ var import_jsx_runtime2 = require("react/jsx-runtime");
283
+ function EmptyState({
284
+ icon,
285
+ title,
286
+ description,
287
+ action,
288
+ primaryAction,
289
+ secondaryAction,
290
+ className
291
+ }) {
292
+ const resolvedAction = action ?? (primaryAction || secondaryAction ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "wf-empty-state__actions", children: [
293
+ primaryAction ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "wf-button wf-button--primary", onClick: primaryAction.onClick, children: primaryAction.label }) : null,
294
+ secondaryAction ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("button", { type: "button", className: "wf-button wf-button--secondary", onClick: secondaryAction.onClick, children: secondaryAction.label }) : null
295
+ ] }) : null);
296
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: ["wf-empty-state", className].filter(Boolean).join(" "), children: [
297
+ icon ?? null,
298
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "wf-empty-state__title", children: title }),
299
+ description ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { className: "wf-empty-state__description", children: description }) : null,
300
+ resolvedAction
301
+ ] });
302
+ }
303
+
304
+ // src/model/nodeKinds.ts
305
+ var NODE_KIND_META = {
306
+ ASK_USER: {
307
+ label: NODE_LABELS.ASK_USER,
308
+ description: NODE_DESCRIPTIONS.ASK_USER,
309
+ iconGlyph: "?"
310
+ },
311
+ AI_STEP: {
312
+ label: NODE_LABELS.AI_STEP,
313
+ description: NODE_DESCRIPTIONS.AI_STEP,
314
+ iconGlyph: "AI"
315
+ },
316
+ AI_DEBATE: {
317
+ label: NODE_LABELS.AI_DEBATE,
318
+ description: NODE_DESCRIPTIONS.AI_DEBATE,
319
+ iconGlyph: "DB",
320
+ advanced: true
321
+ },
322
+ DECISION: {
323
+ label: NODE_LABELS.DECISION,
324
+ description: NODE_DESCRIPTIONS.DECISION,
325
+ iconGlyph: "IF"
326
+ },
327
+ REPEAT: {
328
+ label: NODE_LABELS.REPEAT,
329
+ description: NODE_DESCRIPTIONS.REPEAT,
330
+ iconGlyph: "\u21BB"
331
+ },
332
+ REUSE_WORKFLOW: {
333
+ label: NODE_LABELS.REUSE_WORKFLOW,
334
+ description: NODE_DESCRIPTIONS.REUSE_WORKFLOW,
335
+ iconGlyph: "WF"
336
+ },
337
+ LOAD_RESOURCE: {
338
+ label: NODE_LABELS.LOAD_RESOURCE,
339
+ description: NODE_DESCRIPTIONS.LOAD_RESOURCE,
340
+ iconGlyph: "LD",
341
+ advanced: true
342
+ },
343
+ STOP: {
344
+ label: NODE_LABELS.STOP,
345
+ description: NODE_DESCRIPTIONS.STOP,
346
+ iconGlyph: "!",
347
+ advanced: true
348
+ },
349
+ RETRY: {
350
+ label: NODE_LABELS.RETRY,
351
+ description: NODE_DESCRIPTIONS.RETRY,
352
+ iconGlyph: "RT",
353
+ advanced: true
354
+ },
355
+ SAVE_RESULT: {
356
+ label: NODE_LABELS.SAVE_RESULT,
357
+ description: NODE_DESCRIPTIONS.SAVE_RESULT,
358
+ iconGlyph: "SV"
359
+ }
360
+ };
361
+ var LIBRARY_COMMON_KINDS = [
362
+ "ASK_USER",
363
+ "AI_STEP",
364
+ "DECISION",
365
+ "REPEAT",
366
+ "SAVE_RESULT",
367
+ "REUSE_WORKFLOW"
368
+ ];
369
+ var LIBRARY_ADVANCED_KINDS = ["AI_DEBATE", "LOAD_RESOURCE", "STOP", "RETRY"];
370
+
371
+ // src/canvas/nodes/NodeChrome.tsx
372
+ var import_jsx_runtime3 = require("react/jsx-runtime");
373
+ function StatusBadge({ issueCount, needsApproval }) {
374
+ if (issueCount > 0) {
375
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "wf-node-badge wf-node-badge--error", children: [
376
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "wf-node-badge__glyph", "aria-hidden": true, children: "!" }),
377
+ NODE_STATUS_LABELS.hasIssues
378
+ ] });
379
+ }
380
+ if (needsApproval) {
381
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "wf-node-badge wf-node-badge--warning", children: [
382
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "wf-node-badge__glyph", "aria-hidden": true, children: "A" }),
383
+ NODE_STATUS_LABELS.needsApproval
384
+ ] });
385
+ }
386
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "wf-node-badge wf-node-badge--success", children: [
387
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "wf-node-badge__glyph", "aria-hidden": true, children: "\u2713" }),
388
+ NODE_STATUS_LABELS.valid
389
+ ] });
390
+ }
391
+ function NodeChrome({
392
+ kind,
393
+ variant = "step",
394
+ title,
395
+ subtitle: subtitle2,
396
+ selected,
397
+ issueCount = 0,
398
+ needsApproval = false,
399
+ children,
400
+ className
401
+ }) {
402
+ const meta = NODE_KIND_META[kind];
403
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
404
+ "div",
405
+ {
406
+ className: [
407
+ "wf-node",
408
+ `wf-node--${variant}`,
409
+ selected ? "wf-node--selected" : "",
410
+ className ?? ""
411
+ ].filter(Boolean).join(" "),
412
+ children: [
413
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "wf-node__header", children: [
414
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "wf-node__icon", "aria-hidden": true, children: meta.iconGlyph }),
415
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "wf-node__titles", children: [
416
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "wf-node__kind", children: meta.label }),
417
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "wf-node__title", children: title })
418
+ ] })
419
+ ] }),
420
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "wf-node__body", children: [
421
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "wf-node__subtitle", children: subtitle2 }),
422
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(StatusBadge, { issueCount, needsApproval: needsApproval && issueCount === 0 })
423
+ ] }),
424
+ children
425
+ ]
426
+ }
427
+ );
428
+ }
429
+
430
+ // src/canvas/nodes/DecisionNode.tsx
431
+ var import_react2 = require("@xyflow/react");
432
+ var import_jsx_runtime4 = require("react/jsx-runtime");
433
+ function DecisionNode({ data }) {
434
+ const { canvasNode: node, selected, issueCount = 0 } = data;
435
+ if (node.kind !== "DECISION") {
436
+ return null;
437
+ }
438
+ const d = node.data;
439
+ const meta = NODE_KIND_META.DECISION;
440
+ const title = d.name?.trim() || meta.label;
441
+ const handleClass = ["wf-handle", "wf-handle--decision", selected ? "wf-handle--selected" : ""].filter(Boolean).join(" ");
442
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "wf-flow-node", children: [
443
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react2.Handle, { type: "target", position: import_react2.Position.Top, className: handleClass }),
444
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
445
+ NodeChrome,
446
+ {
447
+ kind: "DECISION",
448
+ variant: "decision",
449
+ title,
450
+ subtitle: d.contextKey?.trim() || meta.description,
451
+ selected,
452
+ issueCount,
453
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "wf-node__branches", children: [
454
+ d.cases.map((c) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "wf-node__branch", children: [
455
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "wf-node__branch-label", children: c.label || c.value }),
456
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react2.Handle, { type: "source", position: import_react2.Position.Right, id: c.value, className: handleClass })
457
+ ] }, c.value)),
458
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "wf-node__branch", children: [
459
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "wf-node__branch-label", children: ACTION_LABELS.otherwiseBranch }),
460
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react2.Handle, { type: "source", position: import_react2.Position.Right, id: "default", className: handleClass })
461
+ ] })
462
+ ] })
463
+ }
464
+ )
465
+ ] });
466
+ }
467
+
468
+ // src/canvas/nodes/LoopNode.tsx
469
+ var import_react3 = require("@xyflow/react");
470
+ var import_jsx_runtime5 = require("react/jsx-runtime");
471
+ function LoopNode({ data }) {
472
+ const { canvasNode: node, selected, issueCount = 0, loopBodyLabels } = data;
473
+ if (node.kind !== "REPEAT") {
474
+ return null;
475
+ }
476
+ const d = node.data;
477
+ const meta = NODE_KIND_META.REPEAT;
478
+ const title = d.name?.trim() || meta.label;
479
+ const handleClass = ["wf-handle", "wf-handle--loop", selected ? "wf-handle--selected" : ""].filter(Boolean).join(" ");
480
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "wf-flow-node", children: [
481
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react3.Handle, { type: "target", position: import_react3.Position.Top, className: handleClass }),
482
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
483
+ NodeChrome,
484
+ {
485
+ kind: "REPEAT",
486
+ variant: "loop",
487
+ title,
488
+ subtitle: `${d.strategy} \xB7 up to ${d.maxIterations} iteration${d.maxIterations === 1 ? "" : "s"}`,
489
+ selected,
490
+ issueCount,
491
+ className: "wf-node--loop-wide",
492
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "wf-node__loop-body", children: [
493
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "wf-node__loop-body-title", children: ACTION_LABELS.loopBodyTitle }),
494
+ loopBodyLabels && loopBodyLabels.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("ul", { className: "wf-node__loop-body-list", children: loopBodyLabels.map((label, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("li", { className: "wf-node__loop-body-item", children: [
495
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "wf-node__loop-body-bullet", "aria-hidden": true }),
496
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { children: label })
497
+ ] }, `${label}-${i}`)) }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "wf-node__loop-body-empty", children: ACTION_LABELS.loopBodyEmpty })
498
+ ] })
499
+ }
500
+ ),
501
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react3.Handle, { type: "source", position: import_react3.Position.Bottom, className: handleClass })
502
+ ] });
503
+ }
504
+
505
+ // src/canvas/nodes/StepNode.tsx
506
+ var import_react4 = require("@xyflow/react");
507
+ var import_jsx_runtime6 = require("react/jsx-runtime");
508
+ function subtitle(node) {
509
+ switch (node.kind) {
510
+ case "ASK_USER": {
511
+ const q = node.data.question?.trim() ?? "";
512
+ return q.length > 48 ? `${q.slice(0, 48)}\u2026` : q || NODE_KIND_META.ASK_USER.description;
513
+ }
514
+ case "AI_STEP":
515
+ return node.data.agentRef?.trim() || NODE_KIND_META.AI_STEP.description;
516
+ case "AI_DEBATE":
517
+ return node.data.primaryAgentRef?.trim() || NODE_KIND_META.AI_DEBATE.description;
518
+ case "DECISION":
519
+ return node.data.contextKey?.trim() || NODE_KIND_META.DECISION.description;
520
+ case "REPEAT":
521
+ return `${node.data.strategy} \xB7 max ${node.data.maxIterations}`;
522
+ case "REUSE_WORKFLOW":
523
+ return node.data.workflowRef?.trim() || NODE_KIND_META.REUSE_WORKFLOW.description;
524
+ case "LOAD_RESOURCE":
525
+ return node.data.resourcePath?.trim() || NODE_KIND_META.LOAD_RESOURCE.description;
526
+ case "STOP":
527
+ return node.data.reason?.trim() || NODE_KIND_META.STOP.description;
528
+ case "RETRY":
529
+ return NODE_KIND_META.RETRY.description;
530
+ case "SAVE_RESULT":
531
+ return node.data.resultName?.trim() || NODE_KIND_META.SAVE_RESULT.description;
532
+ default:
533
+ return "";
534
+ }
535
+ }
536
+ function StepNode({ data }) {
537
+ const { canvasNode: node, selected, issueCount = 0, needsApproval = false } = data;
538
+ const meta = NODE_KIND_META[node.kind];
539
+ const title = node.data.name?.trim() || meta.label;
540
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "wf-flow-node", children: [
541
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react4.Handle, { type: "target", position: import_react4.Position.Top, className: ["wf-handle", selected ? "wf-handle--selected" : ""].filter(Boolean).join(" ") }),
542
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
543
+ NodeChrome,
544
+ {
545
+ kind: node.kind,
546
+ variant: "step",
547
+ title,
548
+ subtitle: subtitle(node),
549
+ selected,
550
+ issueCount,
551
+ needsApproval
552
+ }
553
+ ),
554
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react4.Handle, { type: "source", position: import_react4.Position.Bottom, className: ["wf-handle", selected ? "wf-handle--selected" : ""].filter(Boolean).join(" ") })
555
+ ] });
556
+ }
557
+
558
+ // src/canvas/WorkflowCanvas.tsx
559
+ var import_react5 = require("@xyflow/react");
560
+ var import_react6 = require("react");
561
+ var import_jsx_runtime7 = require("react/jsx-runtime");
562
+ var SNAP = 16;
563
+ function nodeTransition(node) {
564
+ const data = node.data;
565
+ return data.transition ?? null;
566
+ }
567
+ function nodeNeedsApproval(node) {
568
+ return nodeTransition(node) === "HUMAN_APPROVAL";
569
+ }
570
+ function isStarterCanvas(model) {
571
+ if (model.nodes.length !== 1 || model.edges.length > 0) {
572
+ return false;
573
+ }
574
+ const only = model.nodes[0];
575
+ return only?.kind === "ASK_USER";
576
+ }
577
+ function toFlowNodes(model, selectedId, issueCountByBackendStepId) {
578
+ return model.nodes.map((n) => {
579
+ const loopBodyLabels = n.kind === "REPEAT" ? model.nodes.filter((c) => c.parentNode === n.id).map((c) => c.data.name?.trim() ? c.data.name.trim() : NODE_KIND_META[c.kind].label) : void 0;
580
+ const issueCount = n.backendStepId ? issueCountByBackendStepId[n.backendStepId] ?? 0 : 0;
581
+ return {
582
+ id: n.id,
583
+ type: n.kind === "DECISION" ? "decision" : n.kind === "REPEAT" ? "loop" : "step",
584
+ position: n.position,
585
+ parentId: n.parentNode,
586
+ extent: n.parentNode ? "parent" : void 0,
587
+ data: {
588
+ canvasNode: n,
589
+ selected: selectedId === n.id,
590
+ loopBodyLabels,
591
+ issueCount,
592
+ needsApproval: nodeNeedsApproval(n)
593
+ },
594
+ draggable: true
595
+ };
596
+ });
597
+ }
598
+ function toFlowEdges(model) {
599
+ const byId = new Map(model.nodes.map((n) => [n.id, n]));
600
+ return model.edges.map((e) => {
601
+ const source = byId.get(e.source);
602
+ return {
603
+ id: e.id,
604
+ source: e.source,
605
+ target: e.target,
606
+ sourceHandle: e.sourceHandle ?? void 0,
607
+ type: "flow",
608
+ data: { transition: source ? nodeTransition(source) : null },
609
+ markerEnd: {
610
+ type: import_react5.MarkerType.ArrowClosed,
611
+ width: 16,
612
+ height: 16,
613
+ color: "var(--builder-color-edge)"
614
+ }
615
+ };
616
+ });
617
+ }
618
+ var nodeTypes = { step: StepNode, decision: DecisionNode, loop: LoopNode };
619
+ var edgeTypes = { flow: FlowEdge };
620
+ function WorkflowCanvasInner({
621
+ model,
622
+ onModelChange,
623
+ onSelectNode,
624
+ selectedId,
625
+ onAppend,
626
+ issueCountByBackendStepId
627
+ }) {
628
+ const { screenToFlowPosition, setCenter } = (0, import_react5.useReactFlow)();
629
+ const nodesInitialized = (0, import_react5.useNodesInitialized)();
630
+ const showStarterHint = isStarterCanvas(model);
631
+ const flowNodes = (0, import_react6.useMemo)(
632
+ () => toFlowNodes(model, selectedId, issueCountByBackendStepId),
633
+ [issueCountByBackendStepId, model, selectedId]
634
+ );
635
+ const flowEdges = (0, import_react6.useMemo)(() => toFlowEdges(model), [model]);
636
+ (0, import_react6.useEffect)(() => {
637
+ if (!selectedId || !nodesInitialized) {
638
+ return;
639
+ }
640
+ const node = flowNodes.find((entry) => entry.id === selectedId);
641
+ if (!node) {
642
+ return;
643
+ }
644
+ const x = node.position.x + 120;
645
+ const y = node.position.y + 60;
646
+ void setCenter(x, y, { zoom: 1.05, duration: 250 });
647
+ }, [flowNodes, nodesInitialized, selectedId, setCenter]);
648
+ const onNodesChange = (0, import_react6.useCallback)(
649
+ (changes) => {
650
+ const nextFlow = (0, import_react5.applyNodeChanges)(changes, flowNodes);
651
+ const ids = new Set(nextFlow.map((flowNode) => flowNode.id));
652
+ const nextNodes = model.nodes.filter((n) => ids.has(n.id)).map((n) => {
653
+ const fn = nextFlow.find((flowNode) => flowNode.id === n.id);
654
+ return {
655
+ ...n,
656
+ position: fn.position,
657
+ parentNode: fn.parentId
658
+ };
659
+ });
660
+ onModelChange({ ...model, nodes: nextNodes });
661
+ },
662
+ [flowNodes, model, onModelChange]
663
+ );
664
+ const onEdgesChange = (0, import_react6.useCallback)(
665
+ (changes) => {
666
+ const nextFlow = (0, import_react5.applyEdgeChanges)(changes, flowEdges);
667
+ onModelChange({
668
+ ...model,
669
+ edges: nextFlow.map((e) => ({
670
+ id: e.id,
671
+ source: e.source,
672
+ target: e.target,
673
+ sourceHandle: e.sourceHandle ?? null,
674
+ label: null
675
+ }))
676
+ });
677
+ },
678
+ [flowEdges, model, onModelChange]
679
+ );
680
+ const onNodesDelete = (0, import_react6.useCallback)(
681
+ (deletedNodes) => {
682
+ if (deletedNodes.length === 0) {
683
+ return;
684
+ }
685
+ const deletedIds = new Set(deletedNodes.map((node) => node.id));
686
+ onModelChange({
687
+ ...model,
688
+ nodes: model.nodes.filter((node) => !deletedIds.has(node.id)),
689
+ edges: model.edges.filter((edge) => !deletedIds.has(edge.source) && !deletedIds.has(edge.target))
690
+ });
691
+ if (selectedId && deletedIds.has(selectedId)) {
692
+ onSelectNode(null);
693
+ }
694
+ },
695
+ [model, onModelChange, onSelectNode, selectedId]
696
+ );
697
+ const onEdgesDelete = (0, import_react6.useCallback)(
698
+ (deletedEdges) => {
699
+ if (deletedEdges.length === 0) {
700
+ return;
701
+ }
702
+ const deletedIds = new Set(deletedEdges.map((edge) => edge.id));
703
+ onModelChange({
704
+ ...model,
705
+ edges: model.edges.filter((edge) => !deletedIds.has(edge.id))
706
+ });
707
+ },
708
+ [model, onModelChange]
709
+ );
710
+ const deleteSelectedNode = (0, import_react6.useCallback)(() => {
711
+ if (!selectedId) {
712
+ return;
713
+ }
714
+ onModelChange({
715
+ ...model,
716
+ nodes: model.nodes.filter((node) => node.id !== selectedId),
717
+ edges: model.edges.filter((edge) => edge.source !== selectedId && edge.target !== selectedId)
718
+ });
719
+ onSelectNode(null);
720
+ }, [model, onModelChange, onSelectNode, selectedId]);
721
+ const onConnect = (0, import_react6.useCallback)(
722
+ (conn) => {
723
+ if (!conn.source || !conn.target) {
724
+ return;
725
+ }
726
+ const id = `e-${conn.source}-${conn.target}-${conn.sourceHandle ?? "src"}`;
727
+ if (model.edges.some(
728
+ (e) => e.source === conn.source && e.target === conn.target && (e.sourceHandle ?? "") === (conn.sourceHandle ?? "")
729
+ )) {
730
+ return;
731
+ }
732
+ onModelChange({
733
+ ...model,
734
+ edges: [
735
+ ...model.edges,
736
+ {
737
+ id,
738
+ source: conn.source,
739
+ target: conn.target,
740
+ sourceHandle: conn.sourceHandle ?? null,
741
+ label: null
742
+ }
743
+ ]
744
+ });
745
+ },
746
+ [model, onModelChange]
747
+ );
748
+ const onDragOver = (0, import_react6.useCallback)((e) => {
749
+ e.preventDefault();
750
+ e.dataTransfer.dropEffect = "copy";
751
+ }, []);
752
+ const onDrop = (0, import_react6.useCallback)(
753
+ (e) => {
754
+ e.preventDefault();
755
+ const kind = e.dataTransfer.getData("application/agentforge-node-kind");
756
+ if (!kind) {
757
+ return;
758
+ }
759
+ const pos = screenToFlowPosition({ x: e.clientX, y: e.clientY });
760
+ onAppend(kind, pos);
761
+ },
762
+ [onAppend, screenToFlowPosition]
763
+ );
764
+ const startNode = model.nodes.find((n) => n.id === model.startNodeId) ?? model.nodes[0];
765
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "wf-canvas", onDragOver, onDrop, children: [
766
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
767
+ import_react5.ReactFlow,
768
+ {
769
+ className: "wf-canvas__flow",
770
+ nodes: flowNodes,
771
+ edges: flowEdges,
772
+ nodeTypes,
773
+ edgeTypes,
774
+ onNodesChange,
775
+ onEdgesChange,
776
+ onNodesDelete,
777
+ onEdgesDelete,
778
+ onConnect,
779
+ onNodeClick: (_event, flowNode) => onSelectNode(flowNode.id),
780
+ onPaneClick: () => onSelectNode(null),
781
+ snapToGrid: true,
782
+ snapGrid: [SNAP, SNAP],
783
+ fitView: true,
784
+ panOnDrag: true,
785
+ zoomOnPinch: true,
786
+ panOnScroll: false,
787
+ zoomOnDoubleClick: true,
788
+ proOptions: { hideAttribution: true },
789
+ children: [
790
+ selectedId ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react5.NodeToolbar, { nodeId: selectedId, isVisible: true, position: import_react5.Position.Top, align: "end", offset: 10, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
791
+ "button",
792
+ {
793
+ type: "button",
794
+ className: "wf-button wf-button--icon wf-button--secondary",
795
+ "aria-label": ACTION_LABELS.deleteNode,
796
+ title: ACTION_LABELS.deleteNode,
797
+ onClick: deleteSelectedNode,
798
+ children: "\xD7"
799
+ }
800
+ ) }) : null,
801
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react5.Background, { variant: import_react5.BackgroundVariant.Dots, gap: SNAP, size: 1, color: "var(--builder-color-canvas-dot)" }),
802
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react5.Controls, { className: "wf-canvas__controls" }),
803
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react5.MiniMap, { pannable: true, zoomable: true, className: "wf-canvas__minimap" })
804
+ ]
805
+ }
806
+ ),
807
+ showStarterHint && startNode ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "wf-canvas__starter-overlay", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { className: "wf-canvas__starter-card", children: [
808
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
809
+ EmptyState,
810
+ {
811
+ icon: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { className: "wf-canvas__starter-icon", "aria-hidden": true, children: "\u25CE" }),
812
+ title: BUILDER_COPY.startHere,
813
+ description: NODE_KIND_META.ASK_USER.description,
814
+ primaryAction: {
815
+ label: ACTION_LABELS.selectStartStep,
816
+ onClick: () => onSelectNode(startNode.id)
817
+ },
818
+ secondaryAction: {
819
+ label: BUILDER_COPY.useTemplate,
820
+ onClick: () => {
821
+ }
822
+ }
823
+ }
824
+ ),
825
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "wf-canvas__starter-hint", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("button", { type: "button", className: "wf-link-button", onClick: () => {
826
+ }, children: BUILDER_COPY.templateHint }) })
827
+ ] }) }) : null
828
+ ] });
829
+ }
830
+ function WorkflowCanvas({ issueCountByBackendStepId = {}, ...props }) {
831
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react5.ReactFlowProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(WorkflowCanvasInner, { ...props, issueCountByBackendStepId }) });
832
+ }
833
+
834
+ // src/guided/GuidedStepper.tsx
835
+ var import_jsx_runtime8 = require("react/jsx-runtime");
836
+ function GuidedStepper({ stages, activeIndex, onStageAction }) {
837
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("section", { className: "wf-guided-stepper wf-panel", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("ol", { className: "wf-guided-stepper__list", children: stages.map((stage, index) => {
838
+ const isActive = index === activeIndex && !stage.complete;
839
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("li", { className: "wf-guided-stepper__item", children: [
840
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
841
+ "span",
842
+ {
843
+ className: [
844
+ "wf-guided-stepper__marker",
845
+ stage.complete ? "wf-guided-stepper__marker--done" : "",
846
+ isActive ? "wf-guided-stepper__marker--active" : ""
847
+ ].filter(Boolean).join(" "),
848
+ "aria-hidden": true,
849
+ children: stage.complete ? "\u2713" : "\u25CB"
850
+ }
851
+ ),
852
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "wf-guided-stepper__label", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
853
+ "span",
854
+ {
855
+ className: [
856
+ "wf-guided-stepper__text",
857
+ stage.complete ? "wf-guided-stepper__text--done" : ""
858
+ ].filter(Boolean).join(" "),
859
+ children: [
860
+ index + 1,
861
+ ". ",
862
+ stage.label
863
+ ]
864
+ }
865
+ ) }),
866
+ isActive && stage.actionLabel && onStageAction ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
867
+ "button",
868
+ {
869
+ type: "button",
870
+ className: "wf-button wf-button--secondary wf-guided-stepper__action",
871
+ onClick: () => onStageAction(index),
872
+ children: stage.actionLabel
873
+ }
874
+ ) : null
875
+ ] }, stage.label);
876
+ }) }) });
877
+ }
878
+
879
+ // src/model/mapper.ts
880
+ var import_nanoid = require("nanoid");
881
+ var nanoid = (0, import_nanoid.customAlphabet)("abcdefghijklmnopqrstuvwxyz0123456789", 10);
882
+ function findStep(workflow, stepId) {
883
+ const t = stepId.trim();
884
+ return workflow.steps.find((s) => s.stepId.trim() === t);
885
+ }
886
+ function retryPolicyNone() {
887
+ return {
888
+ allowRetry: false,
889
+ allowRetryFromPrevious: false,
890
+ allowAgentSwap: false,
891
+ allowPromptOverride: false,
892
+ maxAttempts: 0
893
+ };
894
+ }
895
+ function retryPolicySimple(maxAttempts) {
896
+ return {
897
+ allowRetry: true,
898
+ allowRetryFromPrevious: false,
899
+ allowAgentSwap: false,
900
+ allowPromptOverride: false,
901
+ maxAttempts
902
+ };
903
+ }
904
+ function exportStepJson(workflow, step) {
905
+ return serializeStepExecutable(workflow, step);
906
+ }
907
+ function serializeStepExecutable(workflow, step) {
908
+ const cm = step.contextMapping ?? { inputKeys: [], outputKeys: [] };
909
+ const body = {
910
+ kind: "STEP",
911
+ stepId: step.stepId,
912
+ name: step.name,
913
+ behaviour: serializeBehaviour(workflow, step),
914
+ contextMapping: {
915
+ inputKeys: cm.inputKeys,
916
+ outputKeys: cm.outputKeys
917
+ }
918
+ };
919
+ if (step.stepPrompt?.trim()) {
920
+ body.stepPrompt = step.stepId;
921
+ }
922
+ return body;
923
+ }
924
+ function serializeBehaviour(workflow, step) {
925
+ switch (step.behaviourType) {
926
+ case "INPUT": {
927
+ const config = step.config;
928
+ return {
929
+ type: "INPUT",
930
+ artifactId: config.artifactId,
931
+ transition: config.transition
932
+ };
933
+ }
934
+ case "AGENT": {
935
+ const config = step.config;
936
+ const max = config.maxRetries ?? 0;
937
+ return {
938
+ type: "AGENT",
939
+ agentRef: config.agentRef,
940
+ transition: config.transition,
941
+ retryPolicy: max > 0 ? retryPolicySimple(max) : retryPolicyNone()
942
+ };
943
+ }
944
+ case "SPAR": {
945
+ const config = step.config;
946
+ return {
947
+ type: "SPAR",
948
+ agentRef: config.agentRef,
949
+ transition: config.transition,
950
+ retryPolicy: retryPolicyNone(),
951
+ sparConfig: {
952
+ challengerAgentId: config.challengerAgentId,
953
+ maxRounds: config.maxRounds,
954
+ resolutionPrompt: config.resolutionPrompt
955
+ }
956
+ };
957
+ }
958
+ case "BRANCH": {
959
+ const config = step.config;
960
+ const branches = {};
961
+ for (const [value, targetId] of Object.entries(config.branches)) {
962
+ const target = findStep(workflow, targetId);
963
+ if (!target) {
964
+ throw new Error(`Branch target missing: ${targetId}`);
965
+ }
966
+ branches[value] = serializeStepExecutable(workflow, target);
967
+ }
968
+ const def = findStep(workflow, config.defaultBranch);
969
+ if (!def) {
970
+ throw new Error(`Default branch target missing: ${config.defaultBranch}`);
971
+ }
972
+ return {
973
+ type: "BRANCH",
974
+ contextKey: config.contextKey,
975
+ branches,
976
+ defaultBranch: serializeStepExecutable(workflow, def)
977
+ };
978
+ }
979
+ case "WORKFLOW_BEHAVIOUR": {
980
+ const config = step.config;
981
+ return {
982
+ type: "WORKFLOW",
983
+ workflowRef: config.workflowRef,
984
+ transition: config.transition
985
+ };
986
+ }
987
+ case "RESOURCE": {
988
+ const config = step.config;
989
+ return {
990
+ type: "RESOURCE",
991
+ resourcePath: config.resourcePath,
992
+ contextKey: config.contextKey,
993
+ transition: config.transition
994
+ };
995
+ }
996
+ case "FAIL": {
997
+ const config = step.config;
998
+ return { type: "FAIL", reason: config.reason };
999
+ }
1000
+ case "RETRY_PREVIOUS": {
1001
+ const config = step.config;
1002
+ const fallbackStep = config.fallback.trim() ? findStep(workflow, config.fallback) : void 0;
1003
+ const fallback = fallbackStep != null ? serializeStepExecutable(workflow, fallbackStep) : serializeStepExecutable(workflow, {
1004
+ stepId: `${step.stepId}-fallback-missing`,
1005
+ name: "Missing fallback",
1006
+ behaviourType: "FAIL",
1007
+ config: { reason: "Retry fallback step was not configured." }
1008
+ });
1009
+ return {
1010
+ type: "RETRY_PREVIOUS",
1011
+ retryStepId: config.retryStepId,
1012
+ retryMode: config.retryMode,
1013
+ maxAttempts: config.maxAttempts,
1014
+ fallback
1015
+ };
1016
+ }
1017
+ }
1018
+ }
1019
+ function newStepId(prefix) {
1020
+ return `${prefix}-${nanoid()}`;
1021
+ }
1022
+ function defaultNodeData(kind) {
1023
+ switch (kind) {
1024
+ case "ASK_USER":
1025
+ return {
1026
+ name: "",
1027
+ question: "",
1028
+ artifactItems: [
1029
+ { id: newStepId("item"), type: "TEXT", label: "Response", key: "value", required: true }
1030
+ ]
1031
+ };
1032
+ case "AI_STEP":
1033
+ return {
1034
+ name: "",
1035
+ agentRef: "",
1036
+ instructions: "",
1037
+ transition: "AUTO",
1038
+ maxRetries: 0
1039
+ };
1040
+ case "AI_DEBATE":
1041
+ return {
1042
+ name: "",
1043
+ primaryAgentRef: "",
1044
+ challengerAgentRef: "",
1045
+ maxRounds: 2,
1046
+ resolutionPrompt: "",
1047
+ transition: "AUTO"
1048
+ };
1049
+ case "DECISION":
1050
+ return {
1051
+ name: "",
1052
+ contextKey: "",
1053
+ cases: [{ label: "Yes", value: "yes", targetNodeId: "" }],
1054
+ defaultTargetNodeId: ""
1055
+ };
1056
+ case "REPEAT":
1057
+ return {
1058
+ name: "",
1059
+ strategy: "FIXED_COUNT",
1060
+ maxIterations: 3,
1061
+ maxIterationsAction: "AWAIT_USER",
1062
+ bodyNodeIds: []
1063
+ };
1064
+ case "SAVE_RESULT":
1065
+ return { name: "", resultName: "" };
1066
+ case "REUSE_WORKFLOW":
1067
+ return { name: "", workflowRef: "", transition: "AUTO" };
1068
+ case "LOAD_RESOURCE":
1069
+ return { name: "", resourcePath: "/schema/", resultName: "", transition: "AUTO" };
1070
+ case "STOP":
1071
+ return { name: "", reason: "" };
1072
+ case "RETRY":
1073
+ return {
1074
+ name: "",
1075
+ targetNodeId: "",
1076
+ maxAttempts: 3,
1077
+ fallbackTargetNodeId: "",
1078
+ retryMode: "SINGLE_STEP"
1079
+ };
1080
+ }
1081
+ }
1082
+ function nodeById(model) {
1083
+ return new Map(model.nodes.map((n) => [n.id, n]));
1084
+ }
1085
+ function outgoing(edges, source, sourceHandle) {
1086
+ return edges.filter((e) => e.source === source && (sourceHandle == null ? true : e.sourceHandle === sourceHandle));
1087
+ }
1088
+ function loopBodyNodeIds(model) {
1089
+ const ids = /* @__PURE__ */ new Set();
1090
+ for (const n of model.nodes) {
1091
+ if (n.parentNode) {
1092
+ const parent = model.nodes.find((p) => p.id === n.parentNode);
1093
+ if (parent?.kind === "REPEAT") {
1094
+ ids.add(n.id);
1095
+ }
1096
+ }
1097
+ }
1098
+ return ids;
1099
+ }
1100
+ function collectBranchNestedIds(model) {
1101
+ const nested = /* @__PURE__ */ new Set();
1102
+ for (const n of model.nodes) {
1103
+ if (n.kind !== "DECISION") {
1104
+ continue;
1105
+ }
1106
+ const d = n.data;
1107
+ const mergeId = d.defaultTargetNodeId?.trim();
1108
+ if (!mergeId) {
1109
+ continue;
1110
+ }
1111
+ const visit = (start) => {
1112
+ const q = [start];
1113
+ const seen = /* @__PURE__ */ new Set();
1114
+ while (q.length) {
1115
+ const cur = q.pop();
1116
+ if (cur === mergeId || seen.has(cur)) {
1117
+ continue;
1118
+ }
1119
+ seen.add(cur);
1120
+ nested.add(cur);
1121
+ for (const e of outgoing(model.edges, cur)) {
1122
+ q.push(e.target);
1123
+ }
1124
+ }
1125
+ };
1126
+ for (const c of d.cases) {
1127
+ if (c.targetNodeId.trim()) {
1128
+ visit(c.targetNodeId.trim());
1129
+ }
1130
+ }
1131
+ }
1132
+ return nested;
1133
+ }
1134
+ function topoMainNodeOrder(model, skip) {
1135
+ const start = model.startNodeId;
1136
+ if (!start || skip.has(start)) {
1137
+ return [];
1138
+ }
1139
+ const nodes = model.nodes.filter((n) => !skip.has(n.id) && !n.parentNode);
1140
+ const ids = new Set(nodes.map((n) => n.id));
1141
+ const indeg = /* @__PURE__ */ new Map();
1142
+ for (const id of ids) {
1143
+ indeg.set(id, 0);
1144
+ }
1145
+ const adj = /* @__PURE__ */ new Map();
1146
+ for (const id of ids) {
1147
+ adj.set(id, []);
1148
+ }
1149
+ for (const e of model.edges) {
1150
+ if (!ids.has(e.source) || !ids.has(e.target)) {
1151
+ continue;
1152
+ }
1153
+ indeg.set(e.target, (indeg.get(e.target) ?? 0) + 1);
1154
+ adj.get(e.source).push(e.target);
1155
+ }
1156
+ const q = [];
1157
+ for (const [id, d] of indeg) {
1158
+ if (d === 0) {
1159
+ q.push(id);
1160
+ }
1161
+ }
1162
+ if (!q.includes(start)) {
1163
+ q.unshift(start);
1164
+ }
1165
+ const out = [];
1166
+ const seen = /* @__PURE__ */ new Set();
1167
+ while (q.length) {
1168
+ const cur = q.shift();
1169
+ if (seen.has(cur)) {
1170
+ continue;
1171
+ }
1172
+ seen.add(cur);
1173
+ out.push(cur);
1174
+ for (const nxt of adj.get(cur) ?? []) {
1175
+ const next = (indeg.get(nxt) ?? 0) - 1;
1176
+ indeg.set(nxt, next);
1177
+ if (next === 0) {
1178
+ q.push(nxt);
1179
+ }
1180
+ }
1181
+ }
1182
+ for (const id of ids) {
1183
+ if (!seen.has(id)) {
1184
+ out.push(id);
1185
+ }
1186
+ }
1187
+ return out;
1188
+ }
1189
+ function artifactFromAskUser(node, artifactId) {
1190
+ const d = node.data;
1191
+ const items = (d.artifactItems ?? []).map((it) => ({
1192
+ id: it.key?.trim() || it.id,
1193
+ type: it.type,
1194
+ label: it.label,
1195
+ required: it.required,
1196
+ options: it.options
1197
+ }));
1198
+ return { id: artifactId, items };
1199
+ }
1200
+ function canvasNodeToStep(node, ctx) {
1201
+ const base = (name) => ({
1202
+ stepId: ctx.stepId,
1203
+ name: name || "Step",
1204
+ behaviourType: "INPUT",
1205
+ config: { artifactId: "", transition: "AUTO" },
1206
+ stepPrompt: "",
1207
+ contextMapping: { inputKeys: [], outputKeys: [] }
1208
+ });
1209
+ switch (node.kind) {
1210
+ case "ASK_USER": {
1211
+ const d = node.data;
1212
+ const artifactId = ctx.artifactIdForAsk ?? node.id;
1213
+ return {
1214
+ stepId: ctx.stepId,
1215
+ name: d.name || NODE_LABELS.ASK_USER,
1216
+ behaviourType: "INPUT",
1217
+ config: { artifactId, transition: "AUTO" },
1218
+ stepPrompt: "",
1219
+ contextMapping: { inputKeys: [], outputKeys: [] }
1220
+ };
1221
+ }
1222
+ case "AI_STEP": {
1223
+ const d = node.data;
1224
+ return {
1225
+ stepId: ctx.stepId,
1226
+ name: d.name || NODE_LABELS.AI_STEP,
1227
+ behaviourType: "AGENT",
1228
+ config: {
1229
+ agentRef: d.agentRef,
1230
+ transition: d.transition,
1231
+ maxRetries: d.maxRetries
1232
+ },
1233
+ stepPrompt: d.instructions,
1234
+ contextMapping: { inputKeys: [], outputKeys: [] }
1235
+ };
1236
+ }
1237
+ case "AI_DEBATE": {
1238
+ const d = node.data;
1239
+ return {
1240
+ stepId: ctx.stepId,
1241
+ name: d.name || NODE_LABELS.AI_DEBATE,
1242
+ behaviourType: "SPAR",
1243
+ config: {
1244
+ agentRef: d.primaryAgentRef,
1245
+ challengerAgentId: d.challengerAgentRef,
1246
+ maxRounds: d.maxRounds,
1247
+ resolutionPrompt: d.resolutionPrompt,
1248
+ transition: d.transition
1249
+ },
1250
+ stepPrompt: "",
1251
+ contextMapping: { inputKeys: [], outputKeys: [] }
1252
+ };
1253
+ }
1254
+ case "DECISION": {
1255
+ const d = node.data;
1256
+ const branches = {};
1257
+ for (const c of d.cases) {
1258
+ if (c.value.trim() && c.targetNodeId.trim()) {
1259
+ branches[c.value.trim()] = c.targetNodeId.trim();
1260
+ }
1261
+ }
1262
+ return {
1263
+ stepId: ctx.stepId,
1264
+ name: d.name || "Decision",
1265
+ behaviourType: "BRANCH",
1266
+ config: {
1267
+ contextKey: d.contextKey,
1268
+ branches,
1269
+ defaultBranch: d.defaultTargetNodeId.trim()
1270
+ },
1271
+ stepPrompt: "",
1272
+ contextMapping: { inputKeys: [], outputKeys: [] }
1273
+ };
1274
+ }
1275
+ case "REUSE_WORKFLOW": {
1276
+ const d = node.data;
1277
+ return {
1278
+ stepId: ctx.stepId,
1279
+ name: d.name || NODE_LABELS.REUSE_WORKFLOW,
1280
+ behaviourType: "WORKFLOW_BEHAVIOUR",
1281
+ config: { workflowRef: d.workflowRef, transition: d.transition },
1282
+ stepPrompt: "",
1283
+ contextMapping: { inputKeys: [], outputKeys: [] }
1284
+ };
1285
+ }
1286
+ case "LOAD_RESOURCE": {
1287
+ const d = node.data;
1288
+ return {
1289
+ stepId: ctx.stepId,
1290
+ name: d.name || NODE_LABELS.LOAD_RESOURCE,
1291
+ behaviourType: "RESOURCE",
1292
+ config: {
1293
+ resourcePath: d.resourcePath,
1294
+ contextKey: d.resultName,
1295
+ transition: d.transition
1296
+ },
1297
+ stepPrompt: "",
1298
+ contextMapping: { inputKeys: [], outputKeys: [] }
1299
+ };
1300
+ }
1301
+ case "STOP": {
1302
+ const d = node.data;
1303
+ return {
1304
+ stepId: ctx.stepId,
1305
+ name: d.name || NODE_LABELS.STOP,
1306
+ behaviourType: "FAIL",
1307
+ config: { reason: d.reason },
1308
+ stepPrompt: "",
1309
+ contextMapping: { inputKeys: [], outputKeys: [] }
1310
+ };
1311
+ }
1312
+ case "RETRY": {
1313
+ const d = node.data;
1314
+ return {
1315
+ stepId: ctx.stepId,
1316
+ name: d.name || NODE_LABELS.RETRY,
1317
+ behaviourType: "RETRY_PREVIOUS",
1318
+ config: {
1319
+ retryStepId: d.targetNodeId,
1320
+ retryMode: d.retryMode,
1321
+ maxAttempts: d.maxAttempts,
1322
+ fallback: d.fallbackTargetNodeId
1323
+ },
1324
+ stepPrompt: "",
1325
+ contextMapping: { inputKeys: [], outputKeys: [] }
1326
+ };
1327
+ }
1328
+ case "SAVE_RESULT":
1329
+ case "REPEAT":
1330
+ return base(node.data.name);
1331
+ default:
1332
+ return base("Step");
1333
+ }
1334
+ }
1335
+ function buildCanvasToStepIdMap(model) {
1336
+ const map = /* @__PURE__ */ new Map();
1337
+ const prefixFor = {
1338
+ ASK_USER: "ask-user",
1339
+ AI_STEP: "ai-step",
1340
+ AI_DEBATE: "ai-debate",
1341
+ DECISION: "decision",
1342
+ REPEAT: "repeat",
1343
+ SAVE_RESULT: "save-result",
1344
+ REUSE_WORKFLOW: "reuse-wf",
1345
+ LOAD_RESOURCE: "load-res",
1346
+ STOP: "stop",
1347
+ RETRY: "retry"
1348
+ };
1349
+ for (const n of model.nodes) {
1350
+ map.set(n.id, n.backendStepId ?? newStepId(prefixFor[n.kind] ?? "step"));
1351
+ }
1352
+ return map;
1353
+ }
1354
+ function buildBlueprintJsonForRepeat(model, repeatNode, canvasToStepId, artifacts) {
1355
+ const d = repeatNode.data;
1356
+ const byId = nodeById(model);
1357
+ const ordered = (d.bodyNodeIds?.length ? d.bodyNodeIds : model.nodes.filter((n) => n.parentNode === repeatNode.id).map((n) => n.id)).map((id) => byId.get(id)).filter((n) => Boolean(n));
1358
+ const innerEditable = [];
1359
+ for (const inner of ordered) {
1360
+ const sid = canvasToStepId.get(inner.id) ?? newStepId("inner");
1361
+ let artifactIdForAsk;
1362
+ if (inner.kind === "ASK_USER") {
1363
+ artifactIdForAsk = `artifact-${sid}`;
1364
+ artifacts[artifactIdForAsk] = artifactFromAskUser(inner, artifactIdForAsk);
1365
+ }
1366
+ innerEditable.push(canvasNodeToStep(inner, { stepId: sid, artifactIdForAsk }));
1367
+ }
1368
+ const wfInner = {
1369
+ id: "inline",
1370
+ name: "",
1371
+ description: "",
1372
+ steps: innerEditable,
1373
+ artifacts
1374
+ };
1375
+ const innerSteps = innerEditable.map((s) => exportStepJson(wfInner, s));
1376
+ const loopConfig = {
1377
+ terminationStrategy: d.strategy,
1378
+ maxIterations: d.maxIterations,
1379
+ maxIterationsAction: d.maxIterationsAction
1380
+ };
1381
+ if (d.strategy === "FOR_EACH" && d.forEachKey?.trim()) {
1382
+ loopConfig.forEachContextKey = d.forEachKey.trim();
1383
+ }
1384
+ if (d.strategy === "EVALUATOR" && d.evaluatorAgentId?.trim()) {
1385
+ loopConfig.evaluatorAgentId = d.evaluatorAgentId.trim();
1386
+ }
1387
+ const blueprintId = canvasToStepId.get(repeatNode.id) ?? newStepId("repeat");
1388
+ return {
1389
+ kind: "BLUEPRINT",
1390
+ blueprintId,
1391
+ name: d.name || "Repeat",
1392
+ behaviour: {
1393
+ loopConfig,
1394
+ transition: "AUTO"
1395
+ },
1396
+ steps: innerSteps
1397
+ };
1398
+ }
1399
+ function canvasToWorkflow(model) {
1400
+ const canvasToStepId = buildCanvasToStepIdMap(model);
1401
+ const loopBodies = loopBodyNodeIds(model);
1402
+ const branchNested = collectBranchNestedIds(model);
1403
+ const skipForMainTopo = /* @__PURE__ */ new Set([...loopBodies, ...branchNested]);
1404
+ const mainOrder = topoMainNodeOrder(model, skipForMainTopo);
1405
+ const artifacts = { ...model.artifacts };
1406
+ const steps = [];
1407
+ const blueprintBodies = { ...model.blueprints };
1408
+ const topLevelSchedule = [];
1409
+ const pushStepForNode = (node) => {
1410
+ const sid = canvasToStepId.get(node.id);
1411
+ let artifactIdForAsk;
1412
+ if (node.kind === "ASK_USER") {
1413
+ artifactIdForAsk = `artifact-${sid}`;
1414
+ artifacts[artifactIdForAsk] = artifactFromAskUser(node, artifactIdForAsk);
1415
+ }
1416
+ const st = canvasNodeToStep(node, { stepId: sid, artifactIdForAsk });
1417
+ if (node.kind === "SAVE_RESULT") {
1418
+ const prev = steps[steps.length - 1];
1419
+ const d = node.data;
1420
+ if (prev && d.resultName.trim()) {
1421
+ prev.contextMapping = {
1422
+ inputKeys: prev.contextMapping?.inputKeys ?? [],
1423
+ outputKeys: [d.resultName.trim()]
1424
+ };
1425
+ }
1426
+ return;
1427
+ }
1428
+ steps.push(st);
1429
+ };
1430
+ for (const nid of mainOrder) {
1431
+ const node = model.nodes.find((n) => n.id === nid);
1432
+ if (!node) {
1433
+ continue;
1434
+ }
1435
+ if (node.kind === "REPEAT") {
1436
+ const bpId = canvasToStepId.get(node.id);
1437
+ blueprintBodies[bpId] = buildBlueprintJsonForRepeat(model, node, canvasToStepId, artifacts);
1438
+ topLevelSchedule.push({ kind: "BLUEPRINT_REF", blueprintId: bpId });
1439
+ continue;
1440
+ }
1441
+ if (node.kind === "SAVE_RESULT") {
1442
+ pushStepForNode(node);
1443
+ continue;
1444
+ }
1445
+ const idx = steps.length;
1446
+ pushStepForNode(node);
1447
+ topLevelSchedule.push({ kind: "STEP", stepIndex: idx });
1448
+ }
1449
+ for (const nid of branchNested) {
1450
+ const node = model.nodes.find((n) => n.id === nid);
1451
+ if (!node || node.kind === "SAVE_RESULT") {
1452
+ continue;
1453
+ }
1454
+ pushStepForNode(node);
1455
+ }
1456
+ for (const st of steps) {
1457
+ if (st.behaviourType === "BRANCH") {
1458
+ const c = st.config;
1459
+ const mapBranches = {};
1460
+ for (const [k, targetCanvas] of Object.entries(c.branches)) {
1461
+ const tid = canvasToStepId.get(targetCanvas);
1462
+ mapBranches[k] = tid ?? targetCanvas;
1463
+ }
1464
+ const defTid = canvasToStepId.get(c.defaultBranch.trim()) ?? c.defaultBranch.trim();
1465
+ st.config = { ...c, branches: mapBranches, defaultBranch: defTid };
1466
+ }
1467
+ if (st.behaviourType === "RETRY_PREVIOUS") {
1468
+ const c = st.config;
1469
+ const fb = canvasToStepId.get(c.fallback.trim()) ?? c.fallback.trim();
1470
+ const rt = canvasToStepId.get(c.retryStepId.trim()) ?? c.retryStepId.trim();
1471
+ st.config = { ...c, retryStepId: rt, fallback: fb };
1472
+ }
1473
+ }
1474
+ return {
1475
+ id: model.workflowId.trim(),
1476
+ name: model.workflowName.trim(),
1477
+ description: model.description.trim(),
1478
+ steps,
1479
+ artifacts,
1480
+ blueprintBodies,
1481
+ topLevelSchedule
1482
+ };
1483
+ }
1484
+ function workflowToCanvas(workflow) {
1485
+ const nodes = [];
1486
+ const edges = [];
1487
+ let y = 0;
1488
+ const gap = 120;
1489
+ const stepToNode = /* @__PURE__ */ new Map();
1490
+ workflow.steps.forEach((s, i) => {
1491
+ stepToNode.set(s.stepId, `n-${i}`);
1492
+ });
1493
+ for (let i = 0; i < workflow.steps.length; i++) {
1494
+ const st = workflow.steps[i];
1495
+ const cid = `n-${i}`;
1496
+ let kind = "AI_STEP";
1497
+ let data = defaultNodeData("AI_STEP");
1498
+ if (st.behaviourType === "INPUT") {
1499
+ kind = "ASK_USER";
1500
+ const cfg = st.config;
1501
+ const art = workflow.artifacts[cfg.artifactId];
1502
+ data = {
1503
+ name: st.name,
1504
+ question: "",
1505
+ artifactItems: art?.items.map((it) => ({
1506
+ id: it.id,
1507
+ type: it.type,
1508
+ label: it.label,
1509
+ key: it.id,
1510
+ required: it.required,
1511
+ options: it.options
1512
+ })) ?? []
1513
+ };
1514
+ } else if (st.behaviourType === "AGENT") {
1515
+ const cfg = st.config;
1516
+ data = {
1517
+ name: st.name,
1518
+ agentRef: cfg.agentRef,
1519
+ instructions: st.stepPrompt ?? "",
1520
+ transition: cfg.transition,
1521
+ maxRetries: cfg.maxRetries ?? 0
1522
+ };
1523
+ } else if (st.behaviourType === "BRANCH") {
1524
+ kind = "DECISION";
1525
+ const cfg = st.config;
1526
+ data = {
1527
+ name: st.name,
1528
+ contextKey: cfg.contextKey,
1529
+ cases: Object.entries(cfg.branches).map(([value, targetNodeId]) => ({
1530
+ label: value,
1531
+ value,
1532
+ targetNodeId: stepToNode.get(targetNodeId) ?? ""
1533
+ })),
1534
+ defaultTargetNodeId: stepToNode.get(cfg.defaultBranch) ?? ""
1535
+ };
1536
+ } else if (st.behaviourType === "WORKFLOW_BEHAVIOUR") {
1537
+ kind = "REUSE_WORKFLOW";
1538
+ const cfg = st.config;
1539
+ data = { name: st.name, workflowRef: cfg.workflowRef, transition: cfg.transition };
1540
+ } else if (st.behaviourType === "RESOURCE") {
1541
+ kind = "LOAD_RESOURCE";
1542
+ const cfg = st.config;
1543
+ data = {
1544
+ name: st.name,
1545
+ resourcePath: cfg.resourcePath,
1546
+ resultName: cfg.contextKey,
1547
+ transition: cfg.transition
1548
+ };
1549
+ } else if (st.behaviourType === "FAIL") {
1550
+ kind = "STOP";
1551
+ const cfg = st.config;
1552
+ data = { name: st.name, reason: cfg.reason };
1553
+ } else if (st.behaviourType === "RETRY_PREVIOUS") {
1554
+ kind = "RETRY";
1555
+ const cfg = st.config;
1556
+ data = {
1557
+ name: st.name,
1558
+ targetNodeId: stepToNode.get(cfg.retryStepId) ?? cfg.retryStepId,
1559
+ maxAttempts: cfg.maxAttempts,
1560
+ fallbackTargetNodeId: stepToNode.get(cfg.fallback) ?? cfg.fallback,
1561
+ retryMode: cfg.retryMode
1562
+ };
1563
+ } else if (st.behaviourType === "SPAR") {
1564
+ kind = "AI_DEBATE";
1565
+ const cfg = st.config;
1566
+ data = {
1567
+ name: st.name,
1568
+ primaryAgentRef: cfg.agentRef,
1569
+ challengerAgentRef: cfg.challengerAgentId,
1570
+ maxRounds: cfg.maxRounds,
1571
+ resolutionPrompt: cfg.resolutionPrompt,
1572
+ transition: cfg.transition
1573
+ };
1574
+ }
1575
+ nodes.push({ id: cid, backendStepId: st.stepId, kind, position: { x: 200, y }, data });
1576
+ y += gap;
1577
+ if (i > 0) {
1578
+ edges.push({
1579
+ id: `e-${i - 1}-${i}`,
1580
+ source: `n-${i - 1}`,
1581
+ target: cid
1582
+ });
1583
+ }
1584
+ }
1585
+ const blueprintKeys = workflow.blueprintBodies ? Object.keys(workflow.blueprintBodies) : [];
1586
+ const unsupported = blueprintKeys.length > 0;
1587
+ const unsupportedReasons = unsupported ? [
1588
+ `${blueprintKeys.length} blueprint definition(s) are kept for export. The loop body is read-only here until nested editing is supported.`
1589
+ ] : void 0;
1590
+ return {
1591
+ workflowId: workflow.id,
1592
+ workflowName: workflow.name,
1593
+ description: workflow.description,
1594
+ startNodeId: nodes[0]?.id ?? null,
1595
+ nodes,
1596
+ edges,
1597
+ artifacts: workflow.artifacts,
1598
+ blueprints: {},
1599
+ unsupported,
1600
+ unsupportedReasons
1601
+ };
1602
+ }
1603
+
1604
+ // src/hooks/useCanvasState.ts
1605
+ var import_react7 = require("react");
1606
+ function createInitialCanvasModel() {
1607
+ const backendStepId = newStepId("ask-user");
1608
+ const id = `c-${backendStepId}`;
1609
+ return {
1610
+ workflowId: "",
1611
+ workflowName: "",
1612
+ description: "",
1613
+ startNodeId: id,
1614
+ nodes: [
1615
+ {
1616
+ id,
1617
+ backendStepId,
1618
+ kind: "ASK_USER",
1619
+ position: { x: 80, y: 120 },
1620
+ data: defaultNodeData("ASK_USER")
1621
+ }
1622
+ ],
1623
+ edges: [],
1624
+ artifacts: {},
1625
+ blueprints: {}
1626
+ };
1627
+ }
1628
+ function useCanvasState(initialModel) {
1629
+ const [model, setModel] = (0, import_react7.useState)(initialModel);
1630
+ const [selectedId, setSelectedId] = (0, import_react7.useState)(null);
1631
+ const [isDirty2, setIsDirty] = (0, import_react7.useState)(false);
1632
+ const markDirty = (0, import_react7.useCallback)(() => {
1633
+ setIsDirty(true);
1634
+ }, []);
1635
+ const markClean = (0, import_react7.useCallback)(() => {
1636
+ setIsDirty(false);
1637
+ }, []);
1638
+ const setModelFromLoad = (0, import_react7.useCallback)((next) => {
1639
+ setModel((current) => typeof next === "function" ? next(current) : next);
1640
+ setIsDirty(false);
1641
+ }, []);
1642
+ const setModelDirty = (0, import_react7.useCallback)((next) => {
1643
+ setModel((current) => typeof next === "function" ? next(current) : next);
1644
+ setIsDirty(true);
1645
+ }, []);
1646
+ const updateNodeData = (0, import_react7.useCallback)(
1647
+ (id, partial) => {
1648
+ setModelDirty((m) => ({
1649
+ ...m,
1650
+ nodes: m.nodes.map((n) => {
1651
+ if (n.id !== id) {
1652
+ return n;
1653
+ }
1654
+ return { ...n, data: { ...n.data, ...partial } };
1655
+ })
1656
+ }));
1657
+ },
1658
+ [setModelDirty]
1659
+ );
1660
+ const appendNode = (0, import_react7.useCallback)(
1661
+ (kind, position) => {
1662
+ const prefix = {
1663
+ ASK_USER: "ask-user",
1664
+ AI_STEP: "ai-step",
1665
+ AI_DEBATE: "ai-debate",
1666
+ DECISION: "decision",
1667
+ REPEAT: "repeat",
1668
+ SAVE_RESULT: "save-result",
1669
+ REUSE_WORKFLOW: "reuse-wf",
1670
+ LOAD_RESOURCE: "load-res",
1671
+ STOP: "stop",
1672
+ RETRY: "retry"
1673
+ };
1674
+ const backendStepId = newStepId(prefix[kind]);
1675
+ const id = `c-${backendStepId}`;
1676
+ const node = {
1677
+ id,
1678
+ backendStepId,
1679
+ kind,
1680
+ position,
1681
+ data: defaultNodeData(kind)
1682
+ };
1683
+ setModelDirty((m) => ({
1684
+ ...m,
1685
+ nodes: [...m.nodes, node],
1686
+ startNodeId: m.startNodeId ?? id
1687
+ }));
1688
+ setSelectedId(id);
1689
+ },
1690
+ [setModelDirty]
1691
+ );
1692
+ return {
1693
+ model,
1694
+ setModel: setModelDirty,
1695
+ setModelFromLoad,
1696
+ selectedId,
1697
+ setSelectedId,
1698
+ isDirty: isDirty2,
1699
+ markDirty,
1700
+ markClean,
1701
+ updateNodeData,
1702
+ appendNode
1703
+ };
1704
+ }
1705
+
1706
+ // src/hooks/useBuilderMode.ts
1707
+ var import_react8 = require("react");
1708
+ var BUILDER_MODE_KEY = "agentforge_builder_mode";
1709
+ function isUntouchedStarter(model) {
1710
+ if (model.nodes.length !== 1) {
1711
+ return false;
1712
+ }
1713
+ const starter = model.nodes[0];
1714
+ if (starter.kind !== "ASK_USER") {
1715
+ return false;
1716
+ }
1717
+ return !model.workflowId.trim() && !model.workflowName.trim() && !model.description.trim() && !starter.data.name.trim() && !starter.data.question.trim();
1718
+ }
1719
+ function readStoredBuilderMode() {
1720
+ if (typeof window === "undefined") {
1721
+ return null;
1722
+ }
1723
+ const value = window.localStorage.getItem(BUILDER_MODE_KEY);
1724
+ if (value === "guided" || value === "advanced") {
1725
+ return value;
1726
+ }
1727
+ return null;
1728
+ }
1729
+ function useBuilderMode(model, isNewRoute) {
1730
+ const [mode, setMode] = (0, import_react8.useState)(() => {
1731
+ const stored = readStoredBuilderMode();
1732
+ if (stored) {
1733
+ return stored;
1734
+ }
1735
+ if (isNewRoute && isUntouchedStarter(model)) {
1736
+ return "guided";
1737
+ }
1738
+ return "advanced";
1739
+ });
1740
+ const setAndPersistMode = (0, import_react8.useCallback)((nextMode) => {
1741
+ setMode(nextMode);
1742
+ if (typeof window !== "undefined") {
1743
+ window.localStorage.setItem(BUILDER_MODE_KEY, nextMode);
1744
+ }
1745
+ }, []);
1746
+ return { mode, setMode: setAndPersistMode };
1747
+ }
1748
+
1749
+ // src/validation/validateWorkflow.ts
1750
+ var ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
1751
+ function hasOptions(item) {
1752
+ if (item.type !== "SINGLE_CHOICE" && item.type !== "MULTI_CHOICE") {
1753
+ return true;
1754
+ }
1755
+ return Boolean(item.options?.some((o) => o.trim().length > 0));
1756
+ }
1757
+ function collectNestedOnlyStepIds(workflow) {
1758
+ const nested = /* @__PURE__ */ new Set();
1759
+ for (const step of workflow.steps) {
1760
+ if (step.behaviourType === "BRANCH") {
1761
+ const c = step.config;
1762
+ Object.values(c.branches).forEach((id) => {
1763
+ if (id.trim()) {
1764
+ nested.add(id.trim());
1765
+ }
1766
+ });
1767
+ if (c.defaultBranch.trim()) {
1768
+ nested.add(c.defaultBranch.trim());
1769
+ }
1770
+ }
1771
+ if (step.behaviourType === "RETRY_PREVIOUS") {
1772
+ const c = step.config;
1773
+ if (c.fallback.trim()) {
1774
+ nested.add(c.fallback.trim());
1775
+ }
1776
+ }
1777
+ }
1778
+ return nested;
1779
+ }
1780
+ function validateEditableWorkflow(workflow) {
1781
+ const errors = {
1782
+ workflow: {},
1783
+ steps: {},
1784
+ artifacts: {},
1785
+ global: []
1786
+ };
1787
+ if (!workflow.id.trim()) {
1788
+ errors.workflow.id = "Workflow id is required.";
1789
+ } else if (!ID_PATTERN.test(workflow.id.trim())) {
1790
+ errors.workflow.id = "Workflow id must use lowercase letters, numbers, and dashes.";
1791
+ }
1792
+ if (!workflow.name.trim()) {
1793
+ errors.workflow.name = "Workflow name is required.";
1794
+ }
1795
+ if (workflow.steps.length === 0) {
1796
+ errors.global.push("At least one step is required.");
1797
+ }
1798
+ if (workflow.topLevelSchedule && workflow.topLevelSchedule.length > 0) {
1799
+ const nestedOnly = collectNestedOnlyStepIds(workflow);
1800
+ workflow.topLevelSchedule.forEach((entry, scheduleIndex) => {
1801
+ if (entry.kind === "STEP") {
1802
+ if (!Number.isInteger(entry.stepIndex) || entry.stepIndex < 0 || entry.stepIndex >= workflow.steps.length) {
1803
+ errors.global.push(`Top-level schedule entry ${scheduleIndex}: invalid step index ${entry.stepIndex}.`);
1804
+ } else {
1805
+ const st = workflow.steps[entry.stepIndex];
1806
+ if (st && nestedOnly.has(st.stepId.trim())) {
1807
+ errors.global.push(
1808
+ `Top-level schedule entry ${scheduleIndex}: step "${st.stepId}" is nested-only and cannot appear in the main schedule.`
1809
+ );
1810
+ }
1811
+ }
1812
+ } else if (entry.kind === "BLUEPRINT_REF") {
1813
+ if (!entry.blueprintId?.trim()) {
1814
+ errors.global.push(`Top-level schedule entry ${scheduleIndex}: blueprint id is required.`);
1815
+ }
1816
+ }
1817
+ });
1818
+ }
1819
+ const seenStepIds = /* @__PURE__ */ new Set();
1820
+ workflow.steps.forEach((step, index) => {
1821
+ const stepErrors = {};
1822
+ if (!step.stepId.trim()) {
1823
+ stepErrors.stepId = "Step id is required.";
1824
+ } else if (!ID_PATTERN.test(step.stepId.trim())) {
1825
+ stepErrors.stepId = "Step id must use lowercase letters, numbers, and dashes.";
1826
+ } else if (seenStepIds.has(step.stepId.trim())) {
1827
+ stepErrors.stepId = "Step id must be unique.";
1828
+ } else {
1829
+ seenStepIds.add(step.stepId.trim());
1830
+ }
1831
+ if (!step.name.trim()) {
1832
+ stepErrors.name = "Step name is required.";
1833
+ }
1834
+ if (step.behaviourType === "INPUT") {
1835
+ const config = step.config;
1836
+ if (!config.artifactId.trim()) {
1837
+ stepErrors.artifactId = "Artifact id is required for INPUT steps.";
1838
+ } else if (!workflow.artifacts[config.artifactId]) {
1839
+ stepErrors.artifactId = `Artifact "${config.artifactId}" does not exist.`;
1840
+ }
1841
+ }
1842
+ if (step.behaviourType === "AGENT") {
1843
+ const config = step.config;
1844
+ if (!config.agentRef.trim()) {
1845
+ stepErrors.agentRef = "agentRef is required.";
1846
+ }
1847
+ }
1848
+ if (step.behaviourType === "SPAR") {
1849
+ const config = step.config;
1850
+ if (!config.agentRef.trim()) {
1851
+ stepErrors.agentRef = "agentRef is required.";
1852
+ }
1853
+ if (!config.challengerAgentId.trim()) {
1854
+ stepErrors.challengerAgentId = "challengerAgentId is required.";
1855
+ }
1856
+ if (!config.resolutionPrompt.trim()) {
1857
+ stepErrors.resolutionPrompt = "resolutionPrompt is required.";
1858
+ }
1859
+ if (!Number.isFinite(config.maxRounds) || config.maxRounds < 1) {
1860
+ stepErrors.maxRounds = "maxRounds must be >= 1.";
1861
+ }
1862
+ }
1863
+ if (step.behaviourType === "RESOURCE") {
1864
+ const config = step.config;
1865
+ if (!config.resourcePath.startsWith("/schema/")) {
1866
+ stepErrors.resourcePath = "resourcePath must start with /schema/.";
1867
+ }
1868
+ if (!config.contextKey.trim()) {
1869
+ stepErrors.contextKey = "contextKey is required.";
1870
+ }
1871
+ }
1872
+ if (step.behaviourType === "WORKFLOW_BEHAVIOUR") {
1873
+ const config = step.config;
1874
+ if (!config.workflowRef.trim()) {
1875
+ stepErrors.workflowRef = "workflowRef is required.";
1876
+ }
1877
+ }
1878
+ if (step.behaviourType === "FAIL") {
1879
+ const config = step.config;
1880
+ if (!config.reason.trim()) {
1881
+ stepErrors.reason = "reason is required.";
1882
+ }
1883
+ }
1884
+ if (step.behaviourType === "BRANCH") {
1885
+ const config = step.config;
1886
+ if (!config.contextKey.trim()) {
1887
+ stepErrors.contextKey = "Branch context key is required.";
1888
+ }
1889
+ const resolveId = (id) => workflow.steps.some((s) => s.stepId.trim() === id.trim());
1890
+ for (const [key, targetId] of Object.entries(config.branches)) {
1891
+ if (!targetId.trim()) {
1892
+ stepErrors[`branch-${key}`] = "Branch target step id is required.";
1893
+ } else if (!resolveId(targetId)) {
1894
+ stepErrors[`branch-${key}`] = `No step with id "${targetId}".`;
1895
+ }
1896
+ }
1897
+ if (!config.defaultBranch.trim()) {
1898
+ stepErrors.defaultBranch = "Default branch target step id is required.";
1899
+ } else if (!resolveId(config.defaultBranch)) {
1900
+ stepErrors.defaultBranch = `No step with id "${config.defaultBranch}".`;
1901
+ }
1902
+ }
1903
+ if (step.behaviourType === "RETRY_PREVIOUS") {
1904
+ const config = step.config;
1905
+ if (!config.retryStepId.trim()) {
1906
+ stepErrors.retryStepId = "retryStepId is required.";
1907
+ }
1908
+ if (!Number.isFinite(config.maxAttempts) || config.maxAttempts < 1) {
1909
+ stepErrors.maxAttempts = "maxAttempts must be >= 1.";
1910
+ }
1911
+ if (config.fallback.trim() && !workflow.steps.some((s) => s.stepId.trim() === config.fallback.trim())) {
1912
+ stepErrors.fallback = `No step with id "${config.fallback}".`;
1913
+ }
1914
+ }
1915
+ if (Object.keys(stepErrors).length > 0) {
1916
+ errors.steps[index] = stepErrors;
1917
+ }
1918
+ });
1919
+ for (const [artifactId, artifact] of Object.entries(workflow.artifacts)) {
1920
+ const artifactErrors = {};
1921
+ if (!artifactId.trim()) {
1922
+ artifactErrors.id = "Artifact id is required.";
1923
+ }
1924
+ if (artifact.items.length === 0) {
1925
+ artifactErrors.items = "Artifact must contain at least one item.";
1926
+ }
1927
+ artifact.items.forEach((item, index) => {
1928
+ if (!item.id.trim()) {
1929
+ artifactErrors[`item-${index}-id`] = "Item id is required.";
1930
+ }
1931
+ if (!item.label.trim()) {
1932
+ artifactErrors[`item-${index}-label`] = "Item label is required.";
1933
+ }
1934
+ if (!hasOptions(item)) {
1935
+ artifactErrors[`item-${index}-options`] = "Choice items must include at least one option.";
1936
+ }
1937
+ });
1938
+ if (Object.keys(artifactErrors).length > 0) {
1939
+ errors.artifacts[artifactId] = artifactErrors;
1940
+ }
1941
+ }
1942
+ return errors;
1943
+ }
1944
+ function toValidationResult(errors) {
1945
+ const issues = [];
1946
+ for (const [field, message] of Object.entries(errors.workflow)) {
1947
+ if (message) {
1948
+ issues.push({ path: `workflow.${field}`, message, severity: "error" });
1949
+ }
1950
+ }
1951
+ for (const [index, stepErrors] of Object.entries(errors.steps)) {
1952
+ for (const [field, message] of Object.entries(stepErrors)) {
1953
+ if (message) {
1954
+ issues.push({ path: `steps[${index}].${field}`, message, severity: "error" });
1955
+ }
1956
+ }
1957
+ }
1958
+ for (const [artifactId, artifactErrors] of Object.entries(errors.artifacts)) {
1959
+ for (const [field, message] of Object.entries(artifactErrors)) {
1960
+ if (message) {
1961
+ issues.push({ path: `artifacts.${artifactId}.${field}`, message, severity: "error" });
1962
+ }
1963
+ }
1964
+ }
1965
+ for (const message of errors.global) {
1966
+ issues.push({ path: "global", message, severity: "error" });
1967
+ }
1968
+ return { valid: issues.length === 0, issues };
1969
+ }
1970
+ function validateWorkflow(draft) {
1971
+ return toValidationResult(validateEditableWorkflow(draft));
1972
+ }
1973
+ function validateWorkflowEditor(draft) {
1974
+ return validateEditableWorkflow(draft);
1975
+ }
1976
+
1977
+ // src/hooks/useWorkflowDraft.ts
1978
+ var import_react9 = require("react");
1979
+ function parseStepIdFromMessage(message) {
1980
+ const m = message.match(/stepId[=:'\s]+([a-z0-9-]+)/i) ?? message.match(/step\s+['"]?([a-z0-9-]+)['"]?/i);
1981
+ return m?.[1];
1982
+ }
1983
+ function validationToIssues(model, validation) {
1984
+ const issues = [];
1985
+ for (const message of Object.values(validation.workflow)) {
1986
+ if (message) {
1987
+ issues.push({ code: "workflow", message, stepId: parseStepIdFromMessage(message) });
1988
+ }
1989
+ }
1990
+ for (const [idx, stepErrors] of Object.entries(validation.steps)) {
1991
+ for (const message of Object.values(stepErrors)) {
1992
+ if (message) {
1993
+ issues.push({
1994
+ code: "field",
1995
+ message: `Step ${idx}: ${message}`,
1996
+ stepId: model.nodes[Number(idx)]?.backendStepId
1997
+ });
1998
+ }
1999
+ }
2000
+ }
2001
+ for (const message of validation.global) {
2002
+ issues.push({ code: "global", message, stepId: void 0 });
2003
+ }
2004
+ return issues;
2005
+ }
2006
+ function useWorkflowDraft() {
2007
+ const [issues, setIssues] = (0, import_react9.useState)([]);
2008
+ const buildFromCanvas = (0, import_react9.useCallback)((model) => {
2009
+ const workflow = canvasToWorkflow(model);
2010
+ const validation = validateWorkflowEditor(workflow);
2011
+ return { workflow, validation };
2012
+ }, []);
2013
+ const validateOnly = (0, import_react9.useCallback)(
2014
+ (model) => {
2015
+ const { workflow, validation } = buildFromCanvas(model);
2016
+ const nextIssues = validationToIssues(model, validation);
2017
+ setIssues(nextIssues);
2018
+ return { workflow, validation, valid: nextIssues.length === 0 };
2019
+ },
2020
+ [buildFromCanvas]
2021
+ );
2022
+ return {
2023
+ issues,
2024
+ setIssues,
2025
+ buildFromCanvas,
2026
+ validateOnly
2027
+ };
2028
+ }
2029
+
2030
+ // src/inspector/StepConfigPanel.tsx
2031
+ var import_react10 = require("react");
2032
+ var import_jsx_runtime9 = require("react/jsx-runtime");
2033
+ function TransitionField({
2034
+ value,
2035
+ onChange
2036
+ }) {
2037
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2038
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", title: ACTION_LABELS.approvalHelp, children: ACTION_LABELS.approvalField }),
2039
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("select", { className: "wf-input wf-select", value, onChange: (e) => onChange(e.target.value), children: [
2040
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "AUTO", children: ACTION_LABELS.approvalAutomatic }),
2041
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "HUMAN_REVIEW", children: ACTION_LABELS.approvalReview }),
2042
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "HUMAN_APPROVAL", children: ACTION_LABELS.approvalRequired })
2043
+ ] })
2044
+ ] });
2045
+ }
2046
+ function Section({ title, children }) {
2047
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("section", { className: "wf-inspector__section", children: [
2048
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h3", { className: "wf-inspector__section-title", children: title }),
2049
+ children
2050
+ ] });
2051
+ }
2052
+ function StepConfigPanel({
2053
+ model,
2054
+ selectedId,
2055
+ mode,
2056
+ onClose,
2057
+ onUpdateNodeData,
2058
+ agentCatalog
2059
+ }) {
2060
+ const node = (0, import_react10.useMemo)(() => model.nodes.find((n) => n.id === selectedId) ?? null, [model.nodes, selectedId]);
2061
+ const hasAgentCatalog = Boolean(agentCatalog && agentCatalog.length > 0);
2062
+ const otherNodes = (0, import_react10.useMemo)(
2063
+ () => model.nodes.filter((n) => n.id !== node?.id).map((n) => ({ id: n.id, label: n.data.name || NODE_KIND_META[n.kind].label })),
2064
+ [model.nodes, node]
2065
+ );
2066
+ if (!node || !selectedId) {
2067
+ return null;
2068
+ }
2069
+ const title = NODE_KIND_META[node.kind].label;
2070
+ const insideLoopBody = Boolean(node.parentNode) && model.nodes.some((p) => p.id === node.parentNode && p.kind === "REPEAT");
2071
+ const behaviorOpen = mode === "advanced";
2072
+ const advancedOpen = mode === "advanced";
2073
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("aside", { className: "wf-panel wf-inspector", role: "dialog", "aria-label": title, children: [
2074
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("header", { className: "wf-panel__header wf-inspector__header", children: [
2075
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h2", { className: "wf-panel__title", children: title }),
2076
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2077
+ "button",
2078
+ {
2079
+ type: "button",
2080
+ className: "wf-button wf-button--ghost wf-button--icon",
2081
+ "aria-label": ACTION_LABELS.configureStepClose,
2082
+ onClick: onClose,
2083
+ children: "\xD7"
2084
+ }
2085
+ )
2086
+ ] }),
2087
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "wf-panel__body wf-inspector__body", children: [
2088
+ insideLoopBody ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "wf-inspector__banner", children: ACTION_LABELS.loopBodyReadOnly }) : null,
2089
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("fieldset", { disabled: insideLoopBody, className: "wf-inspector__fieldset", children: [
2090
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Section, { title: ACTION_LABELS.basicsSection, children: [
2091
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2092
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.nameField }),
2093
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2094
+ "input",
2095
+ {
2096
+ className: "wf-input",
2097
+ value: node.data.name,
2098
+ onChange: (e) => onUpdateNodeData(node.id, { name: e.target.value })
2099
+ }
2100
+ )
2101
+ ] }),
2102
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "wf-inspector__grid", children: [
2103
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2104
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.typeField }),
2105
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("input", { className: "wf-input", value: title, disabled: true })
2106
+ ] }),
2107
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2108
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.descriptionField }),
2109
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("input", { className: "wf-input", value: NODE_KIND_META[node.kind].description, disabled: true })
2110
+ ] })
2111
+ ] })
2112
+ ] }),
2113
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Section, { title: ACTION_LABELS.inputsOutputsSection, children: [
2114
+ node.kind === "ASK_USER" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2115
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2116
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.questionField }),
2117
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2118
+ "textarea",
2119
+ {
2120
+ className: "wf-input wf-textarea",
2121
+ value: node.data.question,
2122
+ onChange: (e) => onUpdateNodeData(node.id, { question: e.target.value })
2123
+ }
2124
+ )
2125
+ ] }),
2126
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { className: "wf-field__hint", children: ACTION_LABELS.formFieldsHint }),
2127
+ node.data.artifactItems.map((item, idx) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "wf-inspector__card", children: [
2128
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2129
+ "select",
2130
+ {
2131
+ className: "wf-input wf-select",
2132
+ value: item.type,
2133
+ onChange: (e) => {
2134
+ const next = [...node.data.artifactItems];
2135
+ next[idx] = { ...item, type: e.target.value };
2136
+ onUpdateNodeData(node.id, { artifactItems: next });
2137
+ },
2138
+ children: ["TEXT", "TEXT_AREA", "SINGLE_CHOICE", "MULTI_CHOICE", "BOOLEAN", "NUMBER", "DATE"].map(
2139
+ (t) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: t, children: t }, t)
2140
+ )
2141
+ }
2142
+ ),
2143
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2144
+ "input",
2145
+ {
2146
+ className: "wf-input",
2147
+ placeholder: "Label",
2148
+ value: item.label,
2149
+ onChange: (e) => {
2150
+ const next = [...node.data.artifactItems];
2151
+ next[idx] = { ...item, label: e.target.value };
2152
+ onUpdateNodeData(node.id, { artifactItems: next });
2153
+ }
2154
+ }
2155
+ ),
2156
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2157
+ "input",
2158
+ {
2159
+ className: "wf-input",
2160
+ placeholder: "Key",
2161
+ value: item.key,
2162
+ onChange: (e) => {
2163
+ const next = [...node.data.artifactItems];
2164
+ next[idx] = { ...item, key: e.target.value };
2165
+ onUpdateNodeData(node.id, { artifactItems: next });
2166
+ }
2167
+ }
2168
+ )
2169
+ ] }, item.id)),
2170
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2171
+ "button",
2172
+ {
2173
+ type: "button",
2174
+ className: "wf-button wf-button--secondary",
2175
+ onClick: () => onUpdateNodeData(node.id, {
2176
+ artifactItems: [
2177
+ ...node.data.artifactItems,
2178
+ {
2179
+ id: `item-${node.data.artifactItems.length}`,
2180
+ type: "TEXT",
2181
+ label: "",
2182
+ key: "",
2183
+ required: false
2184
+ }
2185
+ ]
2186
+ }),
2187
+ children: ACTION_LABELS.addField
2188
+ }
2189
+ )
2190
+ ] }) : null,
2191
+ node.kind === "SAVE_RESULT" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2192
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.resultNameField }),
2193
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2194
+ "input",
2195
+ {
2196
+ className: "wf-input",
2197
+ value: node.data.resultName,
2198
+ onChange: (e) => onUpdateNodeData(node.id, { resultName: e.target.value })
2199
+ }
2200
+ )
2201
+ ] }) : null,
2202
+ node.kind === "LOAD_RESOURCE" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2203
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2204
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.resourcePathField }),
2205
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2206
+ "input",
2207
+ {
2208
+ className: "wf-input",
2209
+ value: node.data.resourcePath,
2210
+ onChange: (e) => onUpdateNodeData(node.id, { resourcePath: e.target.value })
2211
+ }
2212
+ )
2213
+ ] }),
2214
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2215
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.resultContextKeyField }),
2216
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2217
+ "input",
2218
+ {
2219
+ className: "wf-input",
2220
+ value: node.data.resultName,
2221
+ onChange: (e) => onUpdateNodeData(node.id, { resultName: e.target.value })
2222
+ }
2223
+ )
2224
+ ] })
2225
+ ] }) : null
2226
+ ] }),
2227
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("details", { className: "wf-inspector__details", open: behaviorOpen, children: [
2228
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("summary", { className: "wf-inspector__details-summary", children: ACTION_LABELS.behaviorSection }),
2229
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "wf-inspector__details-body", children: [
2230
+ node.kind === "AI_STEP" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2231
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2232
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.agentField }),
2233
+ hasAgentCatalog ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2234
+ "select",
2235
+ {
2236
+ className: "wf-input wf-select",
2237
+ value: node.data.agentRef,
2238
+ onChange: (e) => onUpdateNodeData(node.id, { agentRef: e.target.value }),
2239
+ children: [
2240
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "", children: ACTION_LABELS.selectPlaceholder }),
2241
+ agentCatalog.map((a) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: a.id, children: a.name }, a.id))
2242
+ ]
2243
+ }
2244
+ ) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2245
+ "input",
2246
+ {
2247
+ className: "wf-input",
2248
+ value: node.data.agentRef,
2249
+ placeholder: ACTION_LABELS.agentPlaceholder,
2250
+ onChange: (e) => onUpdateNodeData(node.id, { agentRef: e.target.value })
2251
+ }
2252
+ )
2253
+ ] }),
2254
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2255
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.instructionsField }),
2256
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2257
+ "textarea",
2258
+ {
2259
+ className: "wf-input wf-textarea wf-textarea--tall",
2260
+ value: node.data.instructions,
2261
+ onChange: (e) => onUpdateNodeData(node.id, { instructions: e.target.value })
2262
+ }
2263
+ )
2264
+ ] }),
2265
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2266
+ TransitionField,
2267
+ {
2268
+ value: node.data.transition,
2269
+ onChange: (value) => onUpdateNodeData(node.id, { transition: value })
2270
+ }
2271
+ )
2272
+ ] }) : null,
2273
+ node.kind === "AI_DEBATE" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(DebateForm, { node, agentCatalog, onUpdate: onUpdateNodeData }) : null,
2274
+ node.kind === "DECISION" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(DecisionForm, { node, otherNodes, onUpdate: onUpdateNodeData }) : null,
2275
+ node.kind === "REPEAT" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(RepeatForm, { node, onUpdate: onUpdateNodeData }) : null,
2276
+ node.kind === "REUSE_WORKFLOW" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2277
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2278
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.reuseWorkflow }),
2279
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2280
+ "input",
2281
+ {
2282
+ className: "wf-input",
2283
+ value: node.data.workflowRef,
2284
+ placeholder: ACTION_LABELS.workflowIdPlaceholder,
2285
+ onChange: (e) => onUpdateNodeData(node.id, { workflowRef: e.target.value })
2286
+ }
2287
+ )
2288
+ ] }),
2289
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2290
+ TransitionField,
2291
+ {
2292
+ value: node.data.transition,
2293
+ onChange: (value) => onUpdateNodeData(node.id, { transition: value })
2294
+ }
2295
+ )
2296
+ ] }) : null,
2297
+ node.kind === "STOP" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2298
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.reasonField }),
2299
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2300
+ "textarea",
2301
+ {
2302
+ className: "wf-input wf-textarea",
2303
+ value: node.data.reason,
2304
+ onChange: (e) => onUpdateNodeData(node.id, { reason: e.target.value })
2305
+ }
2306
+ )
2307
+ ] }) : null,
2308
+ node.kind === "RETRY" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2309
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2310
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.targetStepField }),
2311
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2312
+ "select",
2313
+ {
2314
+ className: "wf-input wf-select",
2315
+ value: node.data.targetNodeId,
2316
+ onChange: (e) => onUpdateNodeData(node.id, { targetNodeId: e.target.value }),
2317
+ children: [
2318
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "", children: ACTION_LABELS.selectPlaceholder }),
2319
+ otherNodes.map((o) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: o.id, children: o.label }, o.id))
2320
+ ]
2321
+ }
2322
+ )
2323
+ ] }),
2324
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2325
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.fallbackTargetField }),
2326
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2327
+ "select",
2328
+ {
2329
+ className: "wf-input wf-select",
2330
+ value: node.data.fallbackTargetNodeId,
2331
+ onChange: (e) => onUpdateNodeData(node.id, { fallbackTargetNodeId: e.target.value }),
2332
+ children: [
2333
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "", children: ACTION_LABELS.selectPlaceholder }),
2334
+ otherNodes.map((o) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: o.id, children: o.label }, o.id))
2335
+ ]
2336
+ }
2337
+ )
2338
+ ] })
2339
+ ] }) : null
2340
+ ] })
2341
+ ] }),
2342
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("details", { className: "wf-inspector__details", open: advancedOpen, children: [
2343
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("summary", { className: "wf-inspector__details-summary", children: ACTION_LABELS.advancedSection }),
2344
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "wf-inspector__details-body", children: [
2345
+ node.kind === "AI_STEP" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2346
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.maxRetriesField }),
2347
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2348
+ "input",
2349
+ {
2350
+ className: "wf-input",
2351
+ type: "number",
2352
+ min: 0,
2353
+ value: node.data.maxRetries,
2354
+ onChange: (e) => onUpdateNodeData(node.id, { maxRetries: Number(e.target.value || 0) })
2355
+ }
2356
+ )
2357
+ ] }) : null,
2358
+ node.kind === "RETRY" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2359
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.maxAttemptsField }),
2360
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2361
+ "input",
2362
+ {
2363
+ className: "wf-input",
2364
+ type: "number",
2365
+ min: 1,
2366
+ value: node.data.maxAttempts,
2367
+ onChange: (e) => onUpdateNodeData(node.id, { maxAttempts: Number(e.target.value || 1) })
2368
+ }
2369
+ )
2370
+ ] }) : null
2371
+ ] })
2372
+ ] })
2373
+ ] })
2374
+ ] })
2375
+ ] });
2376
+ }
2377
+ function DebateForm({
2378
+ node,
2379
+ agentCatalog,
2380
+ onUpdate
2381
+ }) {
2382
+ if (node.kind !== "AI_DEBATE") {
2383
+ return null;
2384
+ }
2385
+ const d = node.data;
2386
+ const hasCatalog = Boolean(agentCatalog && agentCatalog.length > 0);
2387
+ const agentControl = (field) => hasCatalog ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2388
+ "select",
2389
+ {
2390
+ className: "wf-input wf-select",
2391
+ value: d[field],
2392
+ onChange: (e) => onUpdate(node.id, { [field]: e.target.value }),
2393
+ children: [
2394
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "", children: ACTION_LABELS.selectPlaceholder }),
2395
+ agentCatalog.map((a) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: a.id, children: a.name }, a.id))
2396
+ ]
2397
+ }
2398
+ ) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2399
+ "input",
2400
+ {
2401
+ className: "wf-input",
2402
+ value: d[field],
2403
+ placeholder: ACTION_LABELS.agentPlaceholder,
2404
+ onChange: (e) => onUpdate(node.id, { [field]: e.target.value })
2405
+ }
2406
+ );
2407
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2408
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2409
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.primaryAgentField }),
2410
+ agentControl("primaryAgentRef")
2411
+ ] }),
2412
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2413
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.challengerAgentField }),
2414
+ agentControl("challengerAgentRef")
2415
+ ] }),
2416
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2417
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.maxRoundsField }),
2418
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2419
+ "input",
2420
+ {
2421
+ className: "wf-input",
2422
+ type: "number",
2423
+ min: 1,
2424
+ value: d.maxRounds,
2425
+ onChange: (e) => onUpdate(node.id, { maxRounds: Number(e.target.value || 1) })
2426
+ }
2427
+ )
2428
+ ] }),
2429
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2430
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.resolutionInstructionsField }),
2431
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2432
+ "textarea",
2433
+ {
2434
+ className: "wf-input wf-textarea",
2435
+ value: d.resolutionPrompt,
2436
+ onChange: (e) => onUpdate(node.id, { resolutionPrompt: e.target.value })
2437
+ }
2438
+ )
2439
+ ] })
2440
+ ] });
2441
+ }
2442
+ function DecisionForm({
2443
+ node,
2444
+ otherNodes,
2445
+ onUpdate
2446
+ }) {
2447
+ if (node.kind !== "DECISION") {
2448
+ return null;
2449
+ }
2450
+ const d = node.data;
2451
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2452
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2453
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.contextKeyField }),
2454
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2455
+ "input",
2456
+ {
2457
+ className: "wf-input",
2458
+ value: d.contextKey,
2459
+ onChange: (e) => onUpdate(node.id, { contextKey: e.target.value })
2460
+ }
2461
+ ),
2462
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__hint", children: ACTION_LABELS.contextKeyHint })
2463
+ ] }),
2464
+ d.cases.map((c, idx) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "wf-inspector__card", children: [
2465
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2466
+ "input",
2467
+ {
2468
+ className: "wf-input",
2469
+ placeholder: ACTION_LABELS.caseLabelPlaceholder,
2470
+ value: c.label,
2471
+ onChange: (e) => {
2472
+ const next = [...d.cases];
2473
+ next[idx] = { ...c, label: e.target.value };
2474
+ onUpdate(node.id, { cases: next });
2475
+ }
2476
+ }
2477
+ ),
2478
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2479
+ "input",
2480
+ {
2481
+ className: "wf-input",
2482
+ placeholder: ACTION_LABELS.caseValuePlaceholder,
2483
+ value: c.value,
2484
+ onChange: (e) => {
2485
+ const next = [...d.cases];
2486
+ next[idx] = { ...c, value: e.target.value };
2487
+ onUpdate(node.id, { cases: next });
2488
+ }
2489
+ }
2490
+ ),
2491
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2492
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.connectsToField }),
2493
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2494
+ "select",
2495
+ {
2496
+ className: "wf-input wf-select",
2497
+ value: c.targetNodeId,
2498
+ onChange: (e) => {
2499
+ const next = [...d.cases];
2500
+ next[idx] = { ...c, targetNodeId: e.target.value };
2501
+ onUpdate(node.id, { cases: next });
2502
+ },
2503
+ children: [
2504
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "", children: ACTION_LABELS.selectPlaceholder }),
2505
+ otherNodes.map((o) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: o.id, children: o.label }, o.id))
2506
+ ]
2507
+ }
2508
+ )
2509
+ ] })
2510
+ ] }, `${c.value}-${idx}`)),
2511
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2512
+ "button",
2513
+ {
2514
+ type: "button",
2515
+ className: "wf-button wf-button--secondary",
2516
+ onClick: () => onUpdate(node.id, {
2517
+ cases: [...d.cases, { label: "New case", value: `case-${d.cases.length + 1}`, targetNodeId: "" }]
2518
+ }),
2519
+ children: ACTION_LABELS.addCase
2520
+ }
2521
+ ),
2522
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2523
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.defaultBranchTargetField }),
2524
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2525
+ "select",
2526
+ {
2527
+ className: "wf-input wf-select",
2528
+ value: d.defaultTargetNodeId,
2529
+ onChange: (e) => onUpdate(node.id, { defaultTargetNodeId: e.target.value }),
2530
+ children: [
2531
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "", children: ACTION_LABELS.selectPlaceholder }),
2532
+ otherNodes.map((o) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: o.id, children: o.label }, o.id))
2533
+ ]
2534
+ }
2535
+ )
2536
+ ] })
2537
+ ] });
2538
+ }
2539
+ function RepeatForm({ node, onUpdate }) {
2540
+ if (node.kind !== "REPEAT") {
2541
+ return null;
2542
+ }
2543
+ const d = node.data;
2544
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2545
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2546
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.loopModeField }),
2547
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2548
+ "select",
2549
+ {
2550
+ className: "wf-input wf-select",
2551
+ value: d.strategy,
2552
+ onChange: (e) => onUpdate(node.id, { strategy: e.target.value }),
2553
+ children: [
2554
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "FIXED_COUNT", children: ACTION_LABELS.fixedCountMode }),
2555
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "FOR_EACH", children: ACTION_LABELS.forEachMode }),
2556
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "AGENT_SIGNAL", children: ACTION_LABELS.agentSignalMode }),
2557
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "EVALUATOR", children: ACTION_LABELS.evaluatorMode })
2558
+ ]
2559
+ }
2560
+ )
2561
+ ] }),
2562
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2563
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.maxIterationsField }),
2564
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2565
+ "input",
2566
+ {
2567
+ className: "wf-input",
2568
+ type: "number",
2569
+ min: 1,
2570
+ value: d.maxIterations,
2571
+ onChange: (e) => onUpdate(node.id, { maxIterations: Number(e.target.value || 1) })
2572
+ }
2573
+ )
2574
+ ] }),
2575
+ d.strategy === "FOR_EACH" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2576
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.listContextKeyField }),
2577
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2578
+ "input",
2579
+ {
2580
+ className: "wf-input",
2581
+ value: d.forEachKey ?? "",
2582
+ onChange: (e) => onUpdate(node.id, { forEachKey: e.target.value })
2583
+ }
2584
+ )
2585
+ ] }) : null,
2586
+ d.strategy === "EVALUATOR" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2587
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.evaluatorAgentField }),
2588
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2589
+ "input",
2590
+ {
2591
+ className: "wf-input",
2592
+ value: d.evaluatorAgentId ?? "",
2593
+ onChange: (e) => onUpdate(node.id, { evaluatorAgentId: e.target.value })
2594
+ }
2595
+ )
2596
+ ] }) : null,
2597
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("label", { className: "wf-field", children: [
2598
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "wf-field__label", children: ACTION_LABELS.maxIterationsActionField }),
2599
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
2600
+ "select",
2601
+ {
2602
+ className: "wf-input wf-select",
2603
+ value: d.maxIterationsAction,
2604
+ onChange: (e) => onUpdate(node.id, { maxIterationsAction: e.target.value }),
2605
+ children: [
2606
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "AWAIT_USER", children: ACTION_LABELS.awaitUserAction }),
2607
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("option", { value: "FAIL", children: ACTION_LABELS.failAction })
2608
+ ]
2609
+ }
2610
+ )
2611
+ ] })
2612
+ ] });
2613
+ }
2614
+
2615
+ // src/palette/StepPalette.tsx
2616
+ var import_react11 = require("react");
2617
+ var import_jsx_runtime10 = require("react/jsx-runtime");
2618
+ function StepPalette({ mode, onAddStep, defaultCollapsed = true }) {
2619
+ const [expanded, setExpanded] = (0, import_react11.useState)(!defaultCollapsed);
2620
+ const [advancedOpen, setAdvancedOpen] = (0, import_react11.useState)(mode === "advanced");
2621
+ const [isMobile, setIsMobile] = (0, import_react11.useState)(false);
2622
+ const [mobileOpen, setMobileOpen] = (0, import_react11.useState)(false);
2623
+ (0, import_react11.useEffect)(() => {
2624
+ setAdvancedOpen(mode === "advanced");
2625
+ }, [mode]);
2626
+ (0, import_react11.useEffect)(() => {
2627
+ if (typeof window === "undefined") {
2628
+ return;
2629
+ }
2630
+ const media = window.matchMedia("(max-width: 767px)");
2631
+ const update = () => setIsMobile(media.matches);
2632
+ update();
2633
+ media.addEventListener("change", update);
2634
+ return () => media.removeEventListener("change", update);
2635
+ }, []);
2636
+ const renderKind = (kind, compact) => {
2637
+ const meta = NODE_KIND_META[kind];
2638
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2639
+ "button",
2640
+ {
2641
+ type: "button",
2642
+ draggable: true,
2643
+ title: meta.description,
2644
+ onDragStart: (e) => {
2645
+ e.dataTransfer.setData("application/agentforge-node-kind", kind);
2646
+ e.dataTransfer.effectAllowed = "copy";
2647
+ },
2648
+ onClick: () => {
2649
+ onAddStep(kind);
2650
+ if (isMobile) {
2651
+ setMobileOpen(false);
2652
+ }
2653
+ },
2654
+ className: ["wf-palette__item", compact ? "wf-palette__item--compact" : ""].filter(Boolean).join(" "),
2655
+ "aria-label": meta.label,
2656
+ children: [
2657
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "wf-palette__item-icon", "aria-hidden": true, children: meta.iconGlyph }),
2658
+ !compact ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "wf-palette__item-text", children: [
2659
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "wf-palette__item-label", children: meta.label }),
2660
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "wf-palette__item-description", children: meta.description })
2661
+ ] }) : null
2662
+ ]
2663
+ },
2664
+ kind
2665
+ );
2666
+ };
2667
+ const panelContent = /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
2668
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "wf-palette__header", children: [
2669
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "wf-palette__title", children: BUILDER_COPY.stepLibrary }),
2670
+ !isMobile ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2671
+ "button",
2672
+ {
2673
+ type: "button",
2674
+ className: "wf-button wf-button--icon wf-button--ghost",
2675
+ "aria-label": expanded ? BUILDER_COPY.collapsePalette : BUILDER_COPY.expandPalette,
2676
+ onClick: () => setExpanded((v) => !v),
2677
+ children: expanded ? "\u2039" : "\u203A"
2678
+ }
2679
+ ) : null
2680
+ ] }),
2681
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "wf-palette__body", children: [
2682
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "wf-palette__section-label", children: "Common" }),
2683
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "wf-palette__list", children: LIBRARY_COMMON_KINDS.map((kind) => renderKind(kind, !expanded && !isMobile)) }),
2684
+ expanded || isMobile ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
2685
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2686
+ "button",
2687
+ {
2688
+ type: "button",
2689
+ className: "wf-palette__advanced-toggle",
2690
+ onClick: () => setAdvancedOpen((o) => !o),
2691
+ children: [
2692
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { "aria-hidden": true, children: advancedOpen ? "\u25BE" : "\u25B8" }),
2693
+ mode === "guided" ? ACTION_LABELS.advancedStepsGuided : ACTION_LABELS.advancedSteps
2694
+ ]
2695
+ }
2696
+ ),
2697
+ advancedOpen ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "wf-palette__list", children: LIBRARY_ADVANCED_KINDS.map((kind) => renderKind(kind, false)) }) : null
2698
+ ] }) : null
2699
+ ] })
2700
+ ] });
2701
+ if (isMobile) {
2702
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "wf-palette wf-palette--mobile", children: [
2703
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2704
+ "button",
2705
+ {
2706
+ type: "button",
2707
+ className: "wf-button wf-button--primary wf-palette__mobile-trigger",
2708
+ "aria-label": ACTION_LABELS.addStep,
2709
+ "aria-expanded": mobileOpen,
2710
+ onClick: () => setMobileOpen((o) => !o),
2711
+ children: [
2712
+ "+ ",
2713
+ ACTION_LABELS.addStep
2714
+ ]
2715
+ }
2716
+ ),
2717
+ mobileOpen ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "wf-palette__mobile-sheet", role: "dialog", "aria-label": BUILDER_COPY.stepLibrary, children: [
2718
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "wf-palette__mobile-description", children: ACTION_LABELS.chooseStepDescription }),
2719
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "wf-palette__mobile-content", children: panelContent })
2720
+ ] }) : null
2721
+ ] });
2722
+ }
2723
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
2724
+ "aside",
2725
+ {
2726
+ className: ["wf-palette", expanded ? "wf-palette--expanded" : "wf-palette--collapsed"].join(" "),
2727
+ onMouseEnter: () => {
2728
+ if (!expanded) {
2729
+ setExpanded(true);
2730
+ }
2731
+ },
2732
+ onMouseLeave: () => {
2733
+ if (defaultCollapsed) {
2734
+ setExpanded(false);
2735
+ }
2736
+ },
2737
+ children: [
2738
+ !expanded ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "wf-palette__collapsed-header", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2739
+ "button",
2740
+ {
2741
+ type: "button",
2742
+ className: "wf-button wf-button--icon wf-button--ghost",
2743
+ "aria-label": BUILDER_COPY.expandPalette,
2744
+ onClick: () => setExpanded(true),
2745
+ children: "\u2630"
2746
+ }
2747
+ ) }) : null,
2748
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: ["wf-palette__panel", !expanded ? "wf-palette__panel--hidden" : ""].filter(Boolean).join(" "), children: panelContent }),
2749
+ !expanded ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "wf-palette__collapsed-list", children: LIBRARY_COMMON_KINDS.map((kind) => renderKind(kind, true)) }) : null
2750
+ ]
2751
+ }
2752
+ );
2753
+ }
2754
+
2755
+ // src/state/useBuilderState.ts
2756
+ var import_react12 = require("react");
2757
+
2758
+ // src/api/types.ts
2759
+ function emptyWorkflow() {
2760
+ return {
2761
+ id: "",
2762
+ name: "",
2763
+ description: "",
2764
+ steps: [],
2765
+ artifacts: {}
2766
+ };
2767
+ }
2768
+
2769
+ // src/state/reducer.ts
2770
+ var initialValidation = { valid: true, issues: [] };
2771
+ var initialImportMeta = { source: null, importedAt: null };
2772
+ function createInitialState(workflow = emptyWorkflow()) {
2773
+ return {
2774
+ draft: workflow,
2775
+ baseline: workflow,
2776
+ selection: { nodeId: null, edgeId: null },
2777
+ validation: initialValidation,
2778
+ importMeta: initialImportMeta
2779
+ };
2780
+ }
2781
+ function isDirty(state) {
2782
+ return JSON.stringify(state.draft) !== JSON.stringify(state.baseline);
2783
+ }
2784
+ function builderReducer(state, action) {
2785
+ switch (action.type) {
2786
+ case "SET_DRAFT":
2787
+ return { ...state, draft: action.draft };
2788
+ case "SET_BASELINE":
2789
+ return { ...state, baseline: action.baseline };
2790
+ case "SELECT_NODE":
2791
+ return {
2792
+ ...state,
2793
+ selection: { ...state.selection, nodeId: action.nodeId, edgeId: null }
2794
+ };
2795
+ case "SELECT_EDGE":
2796
+ return {
2797
+ ...state,
2798
+ selection: { ...state.selection, edgeId: action.edgeId, nodeId: null }
2799
+ };
2800
+ case "SET_VALIDATION":
2801
+ return { ...state, validation: action.validation };
2802
+ case "SET_IMPORT_META":
2803
+ return { ...state, importMeta: action.importMeta };
2804
+ default:
2805
+ return state;
2806
+ }
2807
+ }
2808
+
2809
+ // src/state/useBuilderState.ts
2810
+ function useBuilderState(initialWorkflow) {
2811
+ const [state, dispatch] = (0, import_react12.useReducer)(
2812
+ builderReducer,
2813
+ initialWorkflow,
2814
+ (workflow) => createInitialState(workflow)
2815
+ );
2816
+ const dirty = (0, import_react12.useMemo)(() => isDirty(state), [state]);
2817
+ return { state, dispatch, dirty };
2818
+ }
2819
+
2820
+ // src/validation-ui/ValidationPanel.tsx
2821
+ var import_jsx_runtime11 = require("react/jsx-runtime");
2822
+ function rewriteIssue(message) {
2823
+ const normalized = message.trim().toLowerCase();
2824
+ const matched = Object.entries(ISSUE_REWRITES).find(([key]) => normalized.includes(key));
2825
+ return matched?.[1] ?? message;
2826
+ }
2827
+ function severityGlyph(issue) {
2828
+ if (issue.code.toLowerCase().includes("warning")) {
2829
+ return "\u26A0";
2830
+ }
2831
+ return "!";
2832
+ }
2833
+ function groupIssues(model, issues) {
2834
+ const grouped = /* @__PURE__ */ new Map();
2835
+ for (const issue of issues) {
2836
+ const stepId = issue.stepId?.trim();
2837
+ if (!stepId) {
2838
+ const fallback = grouped.get("workflow") ?? { key: "workflow", title: "Workflow", issues: [] };
2839
+ fallback.issues.push(issue);
2840
+ grouped.set("workflow", fallback);
2841
+ continue;
2842
+ }
2843
+ const node = model.nodes.find((entry2) => entry2.backendStepId === stepId);
2844
+ const defaultTitle = node ? `${NODE_KIND_META[node.kind].label} \xB7 ${node.data.name?.trim() || "Untitled step"}` : stepId;
2845
+ const entry = grouped.get(stepId) ?? { key: stepId, title: defaultTitle, issues: [] };
2846
+ entry.issues.push(issue);
2847
+ grouped.set(stepId, entry);
2848
+ }
2849
+ return [...grouped.values()];
2850
+ }
2851
+ function Section2({
2852
+ title,
2853
+ groups,
2854
+ onFix
2855
+ }) {
2856
+ if (groups.length === 0) {
2857
+ return null;
2858
+ }
2859
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "wf-validation-panel__section", children: [
2860
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h4", { className: "wf-validation-panel__section-title", children: title }),
2861
+ groups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("details", { className: "wf-validation-panel__group", open: true, children: [
2862
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("summary", { className: "wf-validation-panel__group-summary", children: group.title }),
2863
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("ul", { className: "wf-validation-panel__issue-list", children: group.issues.map((issue, index) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("li", { className: "wf-validation-panel__issue", children: [
2864
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "wf-validation-panel__issue-content", children: [
2865
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "wf-validation-panel__issue-glyph", "aria-hidden": true, children: severityGlyph(issue) }),
2866
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "wf-validation-panel__issue-message", children: rewriteIssue(issue.message) })
2867
+ ] }),
2868
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2869
+ "button",
2870
+ {
2871
+ type: "button",
2872
+ className: "wf-button wf-button--secondary wf-validation-panel__fix-button",
2873
+ disabled: !issue.stepId,
2874
+ onClick: () => {
2875
+ if (!issue.stepId) {
2876
+ console.info("Validation issue has no stepId; cannot focus specific step.", issue);
2877
+ }
2878
+ onFix(issue.stepId);
2879
+ },
2880
+ children: ACTION_LABELS.fixIssue
2881
+ }
2882
+ )
2883
+ ] }, `${group.key}-${issue.code}-${index}`)) })
2884
+ ] }, group.key))
2885
+ ] });
2886
+ }
2887
+ function ValidationPanel({ model, clientIssues, serverIssues = [], onFix }) {
2888
+ const clientGroups = groupIssues(model, clientIssues);
2889
+ const serverGroups = groupIssues(model, serverIssues);
2890
+ const totalIssues = clientIssues.length + serverIssues.length;
2891
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("details", { className: "wf-validation-panel", open: totalIssues > 0, children: [
2892
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("summary", { className: "wf-validation-panel__summary", children: [
2893
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "wf-validation-panel__summary-mobile", children: totalIssues === 0 ? ACTION_LABELS.workflowLooksGood : ACTION_LABELS.thingsToFix(totalIssues) }),
2894
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "wf-validation-panel__summary-desktop", children: ACTION_LABELS.clientValidation }),
2895
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "wf-validation-panel__chevron", "aria-hidden": true, children: "\u25BE" })
2896
+ ] }),
2897
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "wf-validation-panel__body", children: totalIssues === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "wf-validation-panel__ok", children: ACTION_LABELS.checkmarkOk }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2898
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "wf-validation-panel__count", children: ACTION_LABELS.thingsToFix(totalIssues) }),
2899
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Section2, { title: ACTION_LABELS.clientValidation, groups: clientGroups, onFix }),
2900
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Section2, { title: ACTION_LABELS.serverValidation, groups: serverGroups, onFix })
2901
+ ] }) })
2902
+ ] });
2903
+ }
2904
+
2905
+ // src/validation-ui/ValidationPill.tsx
2906
+ var import_react13 = require("react");
2907
+ var import_jsx_runtime12 = require("react/jsx-runtime");
2908
+ function ValidationPill({
2909
+ model,
2910
+ clientIssues,
2911
+ serverIssues = [],
2912
+ onFix,
2913
+ className
2914
+ }) {
2915
+ const [open, setOpen] = (0, import_react13.useState)(false);
2916
+ const totalIssues = clientIssues.length + serverIssues.length;
2917
+ const looksGood = totalIssues === 0;
2918
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
2919
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2920
+ "button",
2921
+ {
2922
+ type: "button",
2923
+ className: ["wf-button wf-button--secondary wf-validation-pill", className].filter(Boolean).join(" "),
2924
+ onClick: () => setOpen(true),
2925
+ "aria-label": looksGood ? ACTION_LABELS.looksGood : ACTION_LABELS.thingsToFix(totalIssues),
2926
+ children: [
2927
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "wf-validation-pill__glyph", "aria-hidden": true, children: looksGood ? "\u2713" : "!" }),
2928
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "wf-validation-pill__label-desktop", children: looksGood ? `\u2713 ${ACTION_LABELS.looksGood}` : ACTION_LABELS.thingsToFix(totalIssues) }),
2929
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "wf-validation-pill__label-mobile", children: looksGood ? ACTION_LABELS.okShort : String(totalIssues) })
2930
+ ]
2931
+ }
2932
+ ),
2933
+ open ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "wf-validation-pill__overlay", role: "presentation", onClick: () => setOpen(false), children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
2934
+ "div",
2935
+ {
2936
+ className: "wf-panel wf-validation-pill__drawer",
2937
+ role: "dialog",
2938
+ "aria-label": ACTION_LABELS.clientValidation,
2939
+ onClick: (e) => e.stopPropagation(),
2940
+ children: [
2941
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("header", { className: "wf-panel__header", children: [
2942
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { className: "wf-panel__title", children: ACTION_LABELS.clientValidation }),
2943
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { className: "wf-panel__description", children: ACTION_LABELS.validationDetailsDescription }),
2944
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2945
+ "button",
2946
+ {
2947
+ type: "button",
2948
+ className: "wf-button wf-button--icon wf-button--ghost wf-panel__close",
2949
+ "aria-label": "Close",
2950
+ onClick: () => setOpen(false),
2951
+ children: "\xD7"
2952
+ }
2953
+ )
2954
+ ] }),
2955
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "wf-panel__body", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2956
+ ValidationPanel,
2957
+ {
2958
+ model,
2959
+ clientIssues,
2960
+ serverIssues,
2961
+ onFix: (stepId) => {
2962
+ onFix(stepId);
2963
+ setOpen(false);
2964
+ }
2965
+ }
2966
+ ) })
2967
+ ]
2968
+ }
2969
+ ) }) : null
2970
+ ] });
2971
+ }
2972
+
2973
+ // src/io/core.ts
2974
+ var STRIP_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
2975
+ var WorkflowParseError = class extends Error {
2976
+ constructor(message) {
2977
+ super(message);
2978
+ this.name = "WorkflowParseError";
2979
+ }
2980
+ };
2981
+ function assertNoProtoKey(value, path = "root") {
2982
+ if (value === null || typeof value !== "object") {
2983
+ return;
2984
+ }
2985
+ if (Array.isArray(value)) {
2986
+ value.forEach((item, index) => assertNoProtoKey(item, `${path}[${index}]`));
2987
+ return;
2988
+ }
2989
+ for (const key of Object.keys(value)) {
2990
+ if (key === "__proto__") {
2991
+ throw new WorkflowParseError(`Forbidden key "__proto__" at ${path}`);
2992
+ }
2993
+ assertNoProtoKey(value[key], `${path}.${key}`);
2994
+ }
2995
+ }
2996
+ function stripDangerousKeys(value) {
2997
+ if (value === null || typeof value !== "object") {
2998
+ return value;
2999
+ }
3000
+ if (Array.isArray(value)) {
3001
+ return value.map(stripDangerousKeys);
3002
+ }
3003
+ const result = {};
3004
+ for (const [key, child] of Object.entries(value)) {
3005
+ if (STRIP_KEYS.has(key)) {
3006
+ continue;
3007
+ }
3008
+ result[key] = stripDangerousKeys(child);
3009
+ }
3010
+ return result;
3011
+ }
3012
+ function serializeWorkflowJson(workflow) {
3013
+ return JSON.stringify(workflow, null, 2);
3014
+ }
3015
+ function parseWorkflowJson(text) {
3016
+ let parsed;
3017
+ try {
3018
+ parsed = JSON.parse(text);
3019
+ } catch {
3020
+ throw new WorkflowParseError("Invalid JSON");
3021
+ }
3022
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
3023
+ throw new WorkflowParseError("Workflow JSON must be an object");
3024
+ }
3025
+ assertNoProtoKey(parsed);
3026
+ return stripDangerousKeys(parsed);
3027
+ }
3028
+
3029
+ // src/io/browser/download.ts
3030
+ function downloadWorkflowJson(draft, filename = "workflow.json") {
3031
+ const blob = new Blob([serializeWorkflowJson(draft)], {
3032
+ type: "application/json"
3033
+ });
3034
+ const url = URL.createObjectURL(blob);
3035
+ const anchor = document.createElement("a");
3036
+ anchor.href = url;
3037
+ anchor.download = filename;
3038
+ anchor.click();
3039
+ URL.revokeObjectURL(url);
3040
+ }
3041
+ async function exportWorkflowBundle(draft, format) {
3042
+ if (format !== "json") {
3043
+ throw new Error(`Unsupported export format: ${format}`);
3044
+ }
3045
+ const name = typeof draft.name === "string" && draft.name.length > 0 ? `${draft.name}.json` : "workflow.json";
3046
+ downloadWorkflowJson(draft, name);
3047
+ }
3048
+
3049
+ // src/io/browser/upload.ts
3050
+ function readWorkflowJsonFile(file) {
3051
+ return file.text().then(parseWorkflowJson);
3052
+ }
3053
+ async function importWorkflowFromFilePicker() {
3054
+ return new Promise((resolve, reject) => {
3055
+ const input = document.createElement("input");
3056
+ input.type = "file";
3057
+ input.accept = "application/json,.json";
3058
+ input.onchange = () => {
3059
+ const file = input.files?.[0];
3060
+ if (!file) {
3061
+ reject(new Error("No file selected"));
3062
+ return;
3063
+ }
3064
+ readWorkflowJsonFile(file).then(resolve).catch(reject);
3065
+ };
3066
+ input.click();
3067
+ });
3068
+ }
3069
+
3070
+ // src/api/WorkflowBuilder.tsx
3071
+ var import_jsx_runtime13 = require("react/jsx-runtime");
3072
+ function flattenClientIssues(model, validation) {
3073
+ const safe = (value) => value ?? "";
3074
+ return [
3075
+ ...Object.entries(validation.workflow).map(([field, message]) => ({
3076
+ code: `workflow.${field}`,
3077
+ message: safe(message),
3078
+ stepId: void 0
3079
+ })),
3080
+ ...Object.entries(validation.steps).flatMap(
3081
+ ([idx, errs]) => Object.entries(errs).map(([field, message]) => ({
3082
+ code: `step.${field}`,
3083
+ message: safe(message),
3084
+ stepId: model.nodes[Number(idx)]?.backendStepId
3085
+ }))
3086
+ ),
3087
+ ...Object.entries(validation.artifacts).flatMap(
3088
+ ([artifactId, errs]) => Object.entries(errs).map(([field, message]) => ({
3089
+ code: `artifact.${artifactId}.${field}`,
3090
+ message: safe(message),
3091
+ stepId: void 0
3092
+ }))
3093
+ ),
3094
+ ...validation.global.map((message) => ({ code: "global", message: safe(message), stepId: void 0 }))
3095
+ ];
3096
+ }
3097
+ function hasApprovalTransition(node) {
3098
+ const data = node.data;
3099
+ return data.transition === "HUMAN_APPROVAL";
3100
+ }
3101
+ function hasConfiguredInput(node) {
3102
+ if (node.kind !== "ASK_USER") {
3103
+ return false;
3104
+ }
3105
+ return Boolean(node.data.name?.trim() || node.data.question?.trim());
3106
+ }
3107
+ function nextLibraryStepPosition(model, selectedId) {
3108
+ const GAP_X = 280;
3109
+ const GAP_Y = 140;
3110
+ const byId = new Map(model.nodes.map((node) => [node.id, node]));
3111
+ const outgoing2 = /* @__PURE__ */ new Map();
3112
+ const incomingCount = /* @__PURE__ */ new Map();
3113
+ for (const node of model.nodes) {
3114
+ outgoing2.set(node.id, []);
3115
+ incomingCount.set(node.id, 0);
3116
+ }
3117
+ for (const edge of model.edges) {
3118
+ outgoing2.set(edge.source, [...outgoing2.get(edge.source) ?? [], edge.target]);
3119
+ incomingCount.set(edge.target, (incomingCount.get(edge.target) ?? 0) + 1);
3120
+ }
3121
+ const selectedNode = selectedId ? byId.get(selectedId) ?? null : null;
3122
+ if (selectedNode) {
3123
+ return {
3124
+ position: { x: selectedNode.position.x + GAP_X, y: selectedNode.position.y },
3125
+ afterNodeId: selectedNode.id
3126
+ };
3127
+ }
3128
+ const start = model.startNodeId && byId.has(model.startNodeId) ? model.startNodeId : model.nodes[0]?.id ?? null;
3129
+ if (!start) {
3130
+ return { position: { x: 80, y: 120 }, afterNodeId: null };
3131
+ }
3132
+ let cursor = start;
3133
+ const visited = /* @__PURE__ */ new Set([cursor]);
3134
+ while (true) {
3135
+ const next = (outgoing2.get(cursor) ?? []).find((target) => (incomingCount.get(target) ?? 0) <= 1);
3136
+ if (!next || visited.has(next)) {
3137
+ break;
3138
+ }
3139
+ visited.add(next);
3140
+ cursor = next;
3141
+ }
3142
+ const anchor = byId.get(cursor);
3143
+ if (!anchor) {
3144
+ return { position: { x: 80, y: 120 }, afterNodeId: null };
3145
+ }
3146
+ return {
3147
+ position: {
3148
+ x: anchor.position.x + GAP_X,
3149
+ y: anchor.position.y + (anchor.kind === "DECISION" ? GAP_Y : 0)
3150
+ },
3151
+ afterNodeId: anchor.id
3152
+ };
3153
+ }
3154
+ function WorkflowBuilder({
3155
+ capabilities,
3156
+ adapters,
3157
+ actions,
3158
+ theme,
3159
+ initialWorkflow,
3160
+ agentCatalog = []
3161
+ }) {
3162
+ const seed = initialWorkflow ?? emptyWorkflow();
3163
+ const { state, dispatch, dirty } = useBuilderState(seed);
3164
+ const [pending, setPending] = (0, import_react14.useState)({});
3165
+ const [errors, setErrors] = (0, import_react14.useState)({});
3166
+ const skipDraftSync = (0, import_react14.useRef)(false);
3167
+ const initialCanvas = (0, import_react14.useMemo)(
3168
+ () => seed.steps.length > 0 ? workflowToCanvas(seed) : createInitialCanvasModel(),
3169
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- canvas seed is mount-only
3170
+ []
3171
+ );
3172
+ const {
3173
+ model,
3174
+ setModel,
3175
+ setModelFromLoad,
3176
+ selectedId,
3177
+ setSelectedId,
3178
+ markClean,
3179
+ updateNodeData,
3180
+ appendNode
3181
+ } = useCanvasState(initialCanvas);
3182
+ const { mode, setMode } = useBuilderMode(model, !initialWorkflow?.id);
3183
+ const { buildFromCanvas } = useWorkflowDraft();
3184
+ const resolvedAdapters = (0, import_react14.useMemo)(
3185
+ () => ({
3186
+ validateWorkflow: adapters?.validateWorkflow ?? validateWorkflow,
3187
+ importBundle: adapters?.importBundle ?? importWorkflowFromFilePicker,
3188
+ exportBundle: adapters?.exportBundle ?? exportWorkflowBundle
3189
+ }),
3190
+ [adapters]
3191
+ );
3192
+ (0, import_react14.useEffect)(() => {
3193
+ if (skipDraftSync.current) {
3194
+ skipDraftSync.current = false;
3195
+ return;
3196
+ }
3197
+ const draft = canvasToWorkflow(model);
3198
+ dispatch({ type: "SET_DRAFT", draft });
3199
+ }, [model, dispatch]);
3200
+ (0, import_react14.useEffect)(() => {
3201
+ let cancelled = false;
3202
+ void Promise.resolve(resolvedAdapters.validateWorkflow(state.draft)).then((result) => {
3203
+ if (!cancelled) {
3204
+ dispatch({ type: "SET_VALIDATION", validation: result });
3205
+ }
3206
+ });
3207
+ return () => {
3208
+ cancelled = true;
3209
+ };
3210
+ }, [state.draft, resolvedAdapters, dispatch]);
3211
+ const clientValidation = (0, import_react14.useMemo)(() => buildFromCanvas(model).validation, [buildFromCanvas, model]);
3212
+ const clientIssues = (0, import_react14.useMemo)(() => flattenClientIssues(model, clientValidation), [clientValidation, model]);
3213
+ const issueCountByBackendStepId = (0, import_react14.useMemo)(() => {
3214
+ const counts = {};
3215
+ for (const issue of clientIssues) {
3216
+ if (!issue.stepId) {
3217
+ continue;
3218
+ }
3219
+ counts[issue.stepId] = (counts[issue.stepId] ?? 0) + 1;
3220
+ }
3221
+ return counts;
3222
+ }, [clientIssues]);
3223
+ const runAction = (0, import_react14.useCallback)(async (key, fn) => {
3224
+ setPending((prev) => ({ ...prev, [key]: true }));
3225
+ setErrors((prev) => ({ ...prev, [key]: null }));
3226
+ try {
3227
+ await fn();
3228
+ } catch (err) {
3229
+ const message = err instanceof Error ? err.message : "Action failed";
3230
+ setErrors((prev) => ({ ...prev, [key]: message }));
3231
+ } finally {
3232
+ setPending((prev) => ({ ...prev, [key]: false }));
3233
+ }
3234
+ }, []);
3235
+ const handleImport = () => runAction("import", async () => {
3236
+ const imported = await resolvedAdapters.importBundle();
3237
+ skipDraftSync.current = true;
3238
+ setModelFromLoad(workflowToCanvas(imported));
3239
+ dispatch({ type: "SET_DRAFT", draft: imported });
3240
+ dispatch({ type: "SET_BASELINE", baseline: imported });
3241
+ dispatch({
3242
+ type: "SET_IMPORT_META",
3243
+ importMeta: { source: "file", importedAt: (/* @__PURE__ */ new Date()).toISOString() }
3244
+ });
3245
+ });
3246
+ const handleExport = () => runAction("export", async () => {
3247
+ await resolvedAdapters.exportBundle(state.draft, "json");
3248
+ });
3249
+ const handleSave = () => runAction("save", async () => {
3250
+ if (!actions?.save) {
3251
+ throw new Error("Save action is not configured");
3252
+ }
3253
+ await actions.save(state.draft);
3254
+ dispatch({ type: "SET_BASELINE", baseline: state.draft });
3255
+ markClean();
3256
+ });
3257
+ const handleRun = () => runAction("run", async () => {
3258
+ if (!actions?.run) {
3259
+ throw new Error("Run action is not configured");
3260
+ }
3261
+ await actions.run(state.draft);
3262
+ });
3263
+ const handlePublish = () => runAction("publish", async () => {
3264
+ if (!actions?.publish) {
3265
+ throw new Error("Publish action is not configured");
3266
+ }
3267
+ await actions.publish(state.draft);
3268
+ });
3269
+ const onAddStepFromLibrary = (0, import_react14.useCallback)(
3270
+ (kind, options) => {
3271
+ const { position, afterNodeId } = nextLibraryStepPosition(model, selectedId);
3272
+ const prefix = {
3273
+ ASK_USER: "ask-user",
3274
+ AI_STEP: "ai-step",
3275
+ AI_DEBATE: "ai-debate",
3276
+ DECISION: "decision",
3277
+ REPEAT: "repeat",
3278
+ SAVE_RESULT: "save-result",
3279
+ REUSE_WORKFLOW: "reuse-wf",
3280
+ LOAD_RESOURCE: "load-res",
3281
+ STOP: "stop",
3282
+ RETRY: "retry"
3283
+ };
3284
+ const backendStepId = newStepId(prefix[kind]);
3285
+ const id = `c-${backendStepId}`;
3286
+ const node = {
3287
+ id,
3288
+ backendStepId,
3289
+ kind,
3290
+ position,
3291
+ data: { ...defaultNodeData(kind), ...options?.patch }
3292
+ };
3293
+ setModel((m) => {
3294
+ const edge = afterNodeId && !m.edges.some((e) => e.source === afterNodeId && e.target === id) ? [
3295
+ {
3296
+ id: `e-${afterNodeId}-${id}-auto`,
3297
+ source: afterNodeId,
3298
+ target: id,
3299
+ sourceHandle: null,
3300
+ label: null
3301
+ }
3302
+ ] : [];
3303
+ return {
3304
+ ...m,
3305
+ nodes: [...m.nodes, node],
3306
+ edges: [...m.edges, ...edge],
3307
+ startNodeId: m.startNodeId ?? id
3308
+ };
3309
+ });
3310
+ setSelectedId(id);
3311
+ },
3312
+ [model, selectedId, setModel, setSelectedId]
3313
+ );
3314
+ const focusIssue = (0, import_react14.useCallback)(
3315
+ (stepId) => {
3316
+ if (!stepId) {
3317
+ return;
3318
+ }
3319
+ const node = model.nodes.find((n) => n.backendStepId === stepId);
3320
+ if (node) {
3321
+ setSelectedId(node.id);
3322
+ }
3323
+ },
3324
+ [model.nodes, setSelectedId]
3325
+ );
3326
+ const askUserNode = model.nodes.find((n) => n.kind === "ASK_USER");
3327
+ const guidedStages = (0, import_react14.useMemo)(
3328
+ () => [
3329
+ {
3330
+ label: GUIDED_STAGE_LABELS.addInput,
3331
+ complete: model.nodes.some((node) => node.kind === "ASK_USER" && hasConfiguredInput(node)),
3332
+ actionLabel: GUIDED_STAGE_LABELS.configureInput
3333
+ },
3334
+ {
3335
+ label: GUIDED_STAGE_LABELS.addAiStep,
3336
+ complete: model.nodes.some((node) => node.kind === "AI_STEP" || node.kind === "AI_DEBATE"),
3337
+ actionLabel: GUIDED_STAGE_LABELS.addAiStep
3338
+ },
3339
+ {
3340
+ label: GUIDED_STAGE_LABELS.addApproval,
3341
+ complete: model.nodes.some((node) => hasApprovalTransition(node)),
3342
+ actionLabel: GUIDED_STAGE_LABELS.requireApproval
3343
+ },
3344
+ {
3345
+ label: GUIDED_STAGE_LABELS.generateResult,
3346
+ complete: model.nodes.some((node) => node.kind === "SAVE_RESULT" || node.kind === "STOP"),
3347
+ actionLabel: GUIDED_STAGE_LABELS.addSaveStep
3348
+ }
3349
+ ],
3350
+ [model.nodes]
3351
+ );
3352
+ const activeGuidedIndex = guidedStages.findIndex((stage) => !stage.complete);
3353
+ const onGuidedStageAction = (0, import_react14.useCallback)(
3354
+ (index) => {
3355
+ switch (index) {
3356
+ case 0:
3357
+ if (askUserNode) {
3358
+ setSelectedId(askUserNode.id);
3359
+ }
3360
+ break;
3361
+ case 1:
3362
+ onAddStepFromLibrary("AI_STEP");
3363
+ break;
3364
+ case 2: {
3365
+ const aiNode = model.nodes.find((n) => n.kind === "AI_STEP");
3366
+ if (aiNode) {
3367
+ updateNodeData(aiNode.id, { transition: "HUMAN_APPROVAL" });
3368
+ setSelectedId(aiNode.id);
3369
+ } else {
3370
+ onAddStepFromLibrary("AI_STEP", { patch: { transition: "HUMAN_APPROVAL" } });
3371
+ }
3372
+ break;
3373
+ }
3374
+ case 3:
3375
+ onAddStepFromLibrary("SAVE_RESULT");
3376
+ break;
3377
+ default:
3378
+ break;
3379
+ }
3380
+ },
3381
+ [askUserNode, model.nodes, onAddStepFromLibrary, setSelectedId, updateNodeData]
3382
+ );
3383
+ const rootStyle = theme?.variables ? { style: theme.variables } : void 0;
3384
+ const rootClass = ["workflow-builder", theme?.className].filter(Boolean).join(" ");
3385
+ const activeError = Object.values(errors).find((value) => value) ?? null;
3386
+ const subtitle2 = [
3387
+ dirty ? ACTION_LABELS.unsavedChanges : ACTION_LABELS.upToDate,
3388
+ state.validation.valid ? null : ACTION_LABELS.validationIssues
3389
+ ].filter(Boolean).join(" \xB7 ");
3390
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: rootClass, "data-testid": "workflow-builder", ...rootStyle, children: [
3391
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("header", { className: "workflow-builder__header", children: [
3392
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3393
+ "input",
3394
+ {
3395
+ className: "workflow-builder__name-input",
3396
+ value: model.workflowName,
3397
+ placeholder: ACTION_LABELS.workflowNamePlaceholder,
3398
+ "aria-label": ACTION_LABELS.workflowNameLabel,
3399
+ onChange: (e) => setModel((m) => ({ ...m, workflowName: e.target.value }))
3400
+ }
3401
+ ),
3402
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3403
+ "input",
3404
+ {
3405
+ className: "workflow-builder__id-input",
3406
+ value: model.workflowId,
3407
+ placeholder: ACTION_LABELS.workflowIdPlaceholder,
3408
+ "aria-label": ACTION_LABELS.workflowIdLabel,
3409
+ onChange: (e) => setModel((m) => ({ ...m, workflowId: e.target.value }))
3410
+ }
3411
+ ),
3412
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "workflow-builder__mode-toggle", role: "group", "aria-label": "Builder mode", children: [
3413
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3414
+ "button",
3415
+ {
3416
+ type: "button",
3417
+ className: ["wf-button", mode === "guided" ? "wf-button--primary" : "wf-button--ghost"].join(" "),
3418
+ onClick: () => setMode("guided"),
3419
+ children: ACTION_LABELS.guidedMode
3420
+ }
3421
+ ),
3422
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3423
+ "button",
3424
+ {
3425
+ type: "button",
3426
+ className: ["wf-button", mode === "advanced" ? "wf-button--primary" : "wf-button--ghost"].join(" "),
3427
+ onClick: () => setMode("advanced"),
3428
+ children: ACTION_LABELS.advancedMode
3429
+ }
3430
+ )
3431
+ ] }),
3432
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(ValidationPill, { model, clientIssues, onFix: focusIssue }),
3433
+ capabilities.export ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3434
+ "button",
3435
+ {
3436
+ type: "button",
3437
+ className: "wf-button wf-button--ghost",
3438
+ disabled: pending.export,
3439
+ "data-testid": "workflow-builder-export",
3440
+ onClick: () => void handleExport(),
3441
+ children: pending.export ? ACTION_LABELS.exporting : ACTION_LABELS.export
3442
+ }
3443
+ ) : null,
3444
+ capabilities.save ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3445
+ "button",
3446
+ {
3447
+ type: "button",
3448
+ className: "wf-button wf-button--primary",
3449
+ disabled: pending.save,
3450
+ "data-testid": "workflow-builder-save",
3451
+ onClick: () => void handleSave(),
3452
+ children: pending.save ? ACTION_LABELS.saving : ACTION_LABELS.save
3453
+ }
3454
+ ) : null,
3455
+ capabilities.run ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3456
+ "button",
3457
+ {
3458
+ type: "button",
3459
+ className: "wf-button wf-button--secondary",
3460
+ disabled: pending.run,
3461
+ "data-testid": "workflow-builder-run",
3462
+ onClick: () => void handleRun(),
3463
+ children: pending.run ? ACTION_LABELS.running : ACTION_LABELS.run
3464
+ }
3465
+ ) : null,
3466
+ capabilities.publish ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3467
+ "button",
3468
+ {
3469
+ type: "button",
3470
+ className: "wf-button wf-button--primary",
3471
+ disabled: pending.publish,
3472
+ "data-testid": "workflow-builder-publish",
3473
+ onClick: () => void handlePublish(),
3474
+ children: pending.publish ? ACTION_LABELS.publishing : ACTION_LABELS.publish
3475
+ }
3476
+ ) : null,
3477
+ capabilities.aiAssist ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3478
+ "button",
3479
+ {
3480
+ type: "button",
3481
+ className: "wf-button wf-button--secondary",
3482
+ "data-testid": "workflow-builder-ai",
3483
+ "aria-label": ACTION_LABELS.aiAssist,
3484
+ children: ACTION_LABELS.aiAssist
3485
+ }
3486
+ ) : null,
3487
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "workflow-builder__subtitle", children: subtitle2 })
3488
+ ] }),
3489
+ model.unsupported ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "workflow-builder__banner workflow-builder__banner--warning", role: "status", children: [
3490
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "workflow-builder__banner-title", children: ACTION_LABELS.unsupportedBannerTitle }),
3491
+ model.unsupportedReasons?.length ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("ul", { className: "workflow-builder__banner-list", children: model.unsupportedReasons.map((reason, index) => /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("li", { children: reason }, index)) }) : null
3492
+ ] }) : null,
3493
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "workflow-builder__workspace", children: [
3494
+ mode === "guided" ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "workflow-builder__guided", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3495
+ GuidedStepper,
3496
+ {
3497
+ stages: guidedStages,
3498
+ activeIndex: activeGuidedIndex === -1 ? guidedStages.length - 1 : activeGuidedIndex,
3499
+ onStageAction: onGuidedStageAction
3500
+ }
3501
+ ) }) : null,
3502
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3503
+ StepPalette,
3504
+ {
3505
+ mode,
3506
+ onAddStep: (kind) => onAddStepFromLibrary(kind),
3507
+ defaultCollapsed: mode !== "advanced"
3508
+ }
3509
+ ),
3510
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "workflow-builder__canvas", "data-testid": "workflow-builder-canvas", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3511
+ WorkflowCanvas,
3512
+ {
3513
+ model,
3514
+ onModelChange: setModel,
3515
+ onSelectNode: setSelectedId,
3516
+ selectedId,
3517
+ onAppend: appendNode,
3518
+ issueCountByBackendStepId
3519
+ }
3520
+ ) }),
3521
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3522
+ StepConfigPanel,
3523
+ {
3524
+ model,
3525
+ selectedId,
3526
+ mode,
3527
+ onClose: () => setSelectedId(null),
3528
+ onUpdateNodeData: updateNodeData,
3529
+ agentCatalog
3530
+ }
3531
+ )
3532
+ ] }),
3533
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "workflow-builder__actions", children: capabilities.import ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
3534
+ "button",
3535
+ {
3536
+ type: "button",
3537
+ className: "workflow-builder__button",
3538
+ "data-testid": "workflow-builder-import",
3539
+ disabled: pending.import,
3540
+ onClick: () => void handleImport(),
3541
+ children: pending.import ? ACTION_LABELS.importing : ACTION_LABELS.import
3542
+ }
3543
+ ) : null }),
3544
+ activeError ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { className: "workflow-builder__status workflow-builder__status--error", role: "alert", children: activeError }) : null
3545
+ ] });
3546
+ }
3547
+ // Annotate the CommonJS export names for ESM import in node:
3548
+ 0 && (module.exports = {
3549
+ WorkflowBuilder,
3550
+ WorkflowParseError,
3551
+ builderReducer,
3552
+ parseWorkflowJson,
3553
+ serializeWorkflowJson,
3554
+ useBuilderState,
3555
+ validateWorkflow
3556
+ });
3557
+ //# sourceMappingURL=index.cjs.map