@arbidocs/blocks 0.4.72 → 0.4.74

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/dist/agents/index.cjs +401 -262
  2. package/dist/agents/index.js +7 -4
  3. package/dist/agents/tasks/TaskActivityTimeline.d.ts +1 -1
  4. package/dist/agents/tasks/WorkflowThreadStrip.d.ts +58 -0
  5. package/dist/agents/tasks/index.d.ts +1 -0
  6. package/dist/agents/workflows/WorkflowRunner.d.ts +86 -0
  7. package/dist/agents/workflows/WorkflowStepBar.d.ts +23 -0
  8. package/dist/agents/workflows/index.d.ts +7 -1
  9. package/dist/agents/workflows/planSteps.d.ts +28 -0
  10. package/dist/agents/workflows/run.d.ts +98 -0
  11. package/dist/agents/workflows/skillMarkdown.d.ts +15 -0
  12. package/dist/agents/workflows/types.d.ts +111 -0
  13. package/dist/agents/workflows/useWorkflowPreview.d.ts +14 -0
  14. package/dist/agents/workflows/useWorkflowView.d.ts +54 -0
  15. package/dist/agents/workflows/workflow.d.ts +3 -1
  16. package/dist/chat/index.cjs +124 -124
  17. package/dist/chat/index.js +3 -3
  18. package/dist/{chunk-DH7C2UJO.cjs → chunk-5LP54CWR.cjs} +299 -526
  19. package/dist/chunk-5LP54CWR.cjs.map +1 -0
  20. package/dist/{chunk-3SFFTSOI.cjs → chunk-7CVF3DK2.cjs} +5 -5
  21. package/dist/{chunk-3SFFTSOI.cjs.map → chunk-7CVF3DK2.cjs.map} +1 -1
  22. package/dist/chunk-7D737FLE.cjs +43 -0
  23. package/dist/chunk-7D737FLE.cjs.map +1 -0
  24. package/dist/{chunk-V3L5DONY.cjs → chunk-B4SRYJLM.cjs} +31 -2
  25. package/dist/chunk-B4SRYJLM.cjs.map +1 -0
  26. package/dist/{chunk-LFGJAZ5H.js → chunk-DDF5GPIJ.js} +6 -36
  27. package/dist/chunk-DDF5GPIJ.js.map +1 -0
  28. package/dist/chunk-DETZZBG2.cjs +91 -0
  29. package/dist/chunk-DETZZBG2.cjs.map +1 -0
  30. package/dist/chunk-EVUEIBNU.js +89 -0
  31. package/dist/chunk-EVUEIBNU.js.map +1 -0
  32. package/dist/{chunk-G5QTGKDJ.cjs → chunk-HITAN7JF.cjs} +8 -8
  33. package/dist/{chunk-G5QTGKDJ.cjs.map → chunk-HITAN7JF.cjs.map} +1 -1
  34. package/dist/{chunk-3VQYJBOI.js → chunk-HOXKR26S.js} +31 -4
  35. package/dist/chunk-HOXKR26S.js.map +1 -0
  36. package/dist/{chunk-KJ3QEKOJ.cjs → chunk-IPGKTUF7.cjs} +7 -33
  37. package/dist/chunk-IPGKTUF7.cjs.map +1 -0
  38. package/dist/{chunk-IMC2Z32C.js → chunk-KOSGX3UK.js} +269 -485
  39. package/dist/chunk-KOSGX3UK.js.map +1 -0
  40. package/dist/{chunk-KOXUBCLL.js → chunk-MK6NJQGF.js} +4 -4
  41. package/dist/{chunk-KOXUBCLL.js.map → chunk-MK6NJQGF.js.map} +1 -1
  42. package/dist/{chunk-THO7DZNN.cjs → chunk-MUMH3Z6K.cjs} +20 -15
  43. package/dist/chunk-MUMH3Z6K.cjs.map +1 -0
  44. package/dist/{chunk-5HOAOKZL.js → chunk-OYMWV45N.js} +21 -16
  45. package/dist/chunk-OYMWV45N.js.map +1 -0
  46. package/dist/chunk-P2RBE3FD.cjs +1448 -0
  47. package/dist/chunk-P2RBE3FD.cjs.map +1 -0
  48. package/dist/{chunk-G5DP27DU.js → chunk-UIWLDSTF.js} +3 -29
  49. package/dist/chunk-UIWLDSTF.js.map +1 -0
  50. package/dist/{chunk-VRPRAU3J.js → chunk-WJEKRSPH.js} +3 -3
  51. package/dist/{chunk-VRPRAU3J.js.map → chunk-WJEKRSPH.js.map} +1 -1
  52. package/dist/chunk-XD2M6LIG.js +41 -0
  53. package/dist/chunk-XD2M6LIG.js.map +1 -0
  54. package/dist/chunk-YWYVIQ7T.js +1402 -0
  55. package/dist/chunk-YWYVIQ7T.js.map +1 -0
  56. package/dist/{chunk-WBDK4PIA.cjs → chunk-Z5VUZ6YD.cjs} +6 -37
  57. package/dist/chunk-Z5VUZ6YD.cjs.map +1 -0
  58. package/dist/connect/docx.cjs +2 -2
  59. package/dist/connect/docx.js +1 -1
  60. package/dist/connect/index.cjs +379 -27
  61. package/dist/connect/index.cjs.map +1 -1
  62. package/dist/connect/index.d.ts +1 -0
  63. package/dist/connect/index.js +362 -11
  64. package/dist/connect/index.js.map +1 -1
  65. package/dist/connection/ConnectedWorkflowBuilder.d.ts +44 -0
  66. package/dist/connection/ConnectedWorkflowRunner.d.ts +31 -0
  67. package/dist/connection/useWorkflowThread.d.ts +38 -0
  68. package/dist/facts/index.cjs +1 -1
  69. package/dist/facts/index.js +1 -1
  70. package/dist/files/index.cjs +9 -83
  71. package/dist/files/index.cjs.map +1 -1
  72. package/dist/files/index.js +6 -83
  73. package/dist/files/index.js.map +1 -1
  74. package/dist/home/index.cjs +22 -21
  75. package/dist/home/index.cjs.map +1 -1
  76. package/dist/home/index.js +4 -3
  77. package/dist/home/index.js.map +1 -1
  78. package/dist/index.cjs +579 -440
  79. package/dist/index.cjs.map +1 -1
  80. package/dist/index.js +11 -8
  81. package/dist/index.js.map +1 -1
  82. package/dist/shell/index.cjs +1 -1
  83. package/dist/shell/index.js +1 -1
  84. package/dist/studio/index.cjs +7 -7
  85. package/dist/studio/index.js +5 -5
  86. package/dist/workflow-canvas/StepInspector.d.ts +57 -0
  87. package/dist/workflow-canvas/TriggerInspector.d.ts +46 -0
  88. package/dist/workflow-canvas/WorkflowBuilder.d.ts +55 -0
  89. package/dist/workflow-canvas/WorkflowCanvas.d.ts +69 -0
  90. package/dist/workflow-canvas/edit.d.ts +54 -0
  91. package/dist/workflow-canvas/index.cjs +1719 -0
  92. package/dist/workflow-canvas/index.cjs.map +1 -0
  93. package/dist/workflow-canvas/index.d.ts +13 -0
  94. package/dist/workflow-canvas/index.js +1689 -0
  95. package/dist/workflow-canvas/index.js.map +1 -0
  96. package/dist/workflow-canvas/trigger.d.ts +51 -0
  97. package/dist/workflow-canvas/viewport.d.ts +47 -0
  98. package/package.json +25 -14
  99. package/src/workflow-canvas/canvas.css +113 -0
  100. package/dist/chunk-3VQYJBOI.js.map +0 -1
  101. package/dist/chunk-5HOAOKZL.js.map +0 -1
  102. package/dist/chunk-DH7C2UJO.cjs.map +0 -1
  103. package/dist/chunk-G5DP27DU.js.map +0 -1
  104. package/dist/chunk-IMC2Z32C.js.map +0 -1
  105. package/dist/chunk-KJ3QEKOJ.cjs.map +0 -1
  106. package/dist/chunk-LFGJAZ5H.js.map +0 -1
  107. package/dist/chunk-THO7DZNN.cjs.map +0 -1
  108. package/dist/chunk-V3L5DONY.cjs.map +0 -1
  109. package/dist/chunk-WBDK4PIA.cjs.map +0 -1
@@ -0,0 +1,1689 @@
1
+ "use client";
2
+ import { useConnection } from '../chunk-OYMWV45N.js';
3
+ import { describeRRule, RecurrenceBuilder, AgentSelect, useWorkflowPreview, workflowWhen, isoToLocalInput, localInputToIso } from '../chunk-KOSGX3UK.js';
4
+ import '../chunk-CKB64Z4R.js';
5
+ import '../chunk-P6GHJSLR.js';
6
+ import { workflowSteps, dependsOn, stepLevels, stepDependencies, runFields, validateWorkflowGraph, WorkflowRunner, fromPlanSteps, layoutFromPlanSteps, skillToWorkflow, orderSteps, toPlanSteps } from '../chunk-YWYVIQ7T.js';
7
+ import '../chunk-4WWM3GQ5.js';
8
+ import '../chunk-RFXJYIDV.js';
9
+ import '../chunk-DPLRHEJZ.js';
10
+ import '../chunk-EVUEIBNU.js';
11
+ import '../chunk-BUHKUKKB.js';
12
+ import '../chunk-YU2KUMGE.js';
13
+ import '../chunk-DDF5GPIJ.js';
14
+ import '../chunk-XD2M6LIG.js';
15
+ import '../chunk-S7PSMMRG.js';
16
+ import '../chunk-HOXKR26S.js';
17
+ import '../chunk-AA3PT6KR.js';
18
+ import '../chunk-SLSBJRWW.js';
19
+ import '../chunk-NPLYHR2E.js';
20
+ import '../chunk-76JDDMSP.js';
21
+ import '../chunk-OXF2IFXG.js';
22
+ import '../chunk-TX2ZICS7.js';
23
+ import '../chunk-7F6CBP3Z.js';
24
+ import '../chunk-VUNV25KB.js';
25
+ import { useMemo, useState, useEffect, useCallback, useRef } from 'react';
26
+ import { useWorkflows, useSkills, useAgents, useConversations, useCreateWorkflow, useUpdateWorkflow, useRunWorkflow } from '@arbidocs/react';
27
+ import { Input, Textarea, Checkbox, Button, cn, Label, Popover, PopoverTrigger, PopoverContent } from '@arbidocs/react/ui';
28
+ import { X, Trash2, Plus, Maximize, ClipboardCheck, Bot, Upload, FileInput, PencilRuler, Eye, LayoutGrid, Settings2, Play, Loader2, Save, Zap, GitBranch, Check, AlertTriangle, MinusCircle } from 'lucide-react';
29
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
30
+ import { MarkerType, applyNodeChanges, ReactFlow, Background, BackgroundVariant, Controls, ControlButton, MiniMap, Handle, Position } from '@xyflow/react';
31
+
32
+ // src/workflow-canvas/edit.ts
33
+ function newSteppedWorkflow(id, title) {
34
+ return {
35
+ id,
36
+ title,
37
+ description: "",
38
+ inputs: [],
39
+ landing: "chat",
40
+ schedulable: false,
41
+ run: { kind: "steps", steps: [] }
42
+ };
43
+ }
44
+ function withSteps(workflow, steps, layout) {
45
+ const run = workflow.run.kind === "steps" ? workflow.run : { kind: "steps", steps: [] };
46
+ return {
47
+ ...workflow,
48
+ run: { ...run, steps: orderSteps(steps), layout: layout ?? run.layout }
49
+ };
50
+ }
51
+ var layoutOf = (workflow) => workflow.run.kind === "steps" && workflow.run.layout || {};
52
+ var explicit = (steps) => steps.map((s) => ({ ...s, after: stepDependencies(steps, s.id) }));
53
+ function stepIdFor(steps, title) {
54
+ const base = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").replace(/^[^a-z]+/, "") || "step";
55
+ const taken = new Set(steps.map((s) => s.id));
56
+ if (!taken.has(base)) return base;
57
+ let n = 2;
58
+ while (taken.has(`${base}-${n}`)) n++;
59
+ return `${base}-${n}`;
60
+ }
61
+ var DEFAULT_TITLES = {
62
+ form: "Details",
63
+ upload: "Upload files",
64
+ agent: "Agent step",
65
+ review: "Review"
66
+ };
67
+ function blankStep(kind, id, title) {
68
+ switch (kind) {
69
+ case "form":
70
+ return { id, kind, title, inputs: [] };
71
+ case "upload":
72
+ return { id, kind, title };
73
+ case "agent":
74
+ return { id, kind, title };
75
+ case "review":
76
+ return { id, kind, title, instructions: "" };
77
+ }
78
+ }
79
+ function addStep(workflow, kind, options = {}) {
80
+ const steps = explicit(workflowSteps(workflow));
81
+ const title = options.title ?? DEFAULT_TITLES[kind];
82
+ const id = stepIdFor(steps, title);
83
+ const step = { ...blankStep(kind, id, title), after: options.after ? [options.after] : [] };
84
+ const layout = { ...layoutOf(workflow) };
85
+ if (options.position) layout[id] = options.position;
86
+ return { workflow: withSteps(workflow, [...steps, step], layout), id };
87
+ }
88
+ function removeStep(workflow, id) {
89
+ const steps = explicit(workflowSteps(workflow));
90
+ const removed = steps.find((s) => s.id === id);
91
+ if (!removed) return workflow;
92
+ const bridged = steps.filter((s) => s.id !== id).map(
93
+ (s) => s.after?.includes(id) ? {
94
+ ...s,
95
+ after: [.../* @__PURE__ */ new Set([...s.after.filter((d) => d !== id), ...removed.after ?? []])]
96
+ } : s
97
+ );
98
+ const { [id]: _gone, ...layout } = layoutOf(workflow);
99
+ return withSteps(workflow, bridged, layout);
100
+ }
101
+ function connectSteps(workflow, from, to) {
102
+ const steps = explicit(workflowSteps(workflow));
103
+ if (from === to || !steps.some((s) => s.id === from) || dependsOn(steps, from, to)) {
104
+ return workflow;
105
+ }
106
+ return withSteps(
107
+ workflow,
108
+ steps.map(
109
+ (s) => s.id === to && !s.after?.includes(from) ? { ...s, after: [...s.after ?? [], from] } : s
110
+ )
111
+ );
112
+ }
113
+ var wouldCycle = (workflow, from, to) => from === to || dependsOn(workflowSteps(workflow), from, to);
114
+ function disconnectSteps(workflow, from, to) {
115
+ const steps = explicit(workflowSteps(workflow));
116
+ return withSteps(
117
+ workflow,
118
+ steps.map((s) => s.id === to ? { ...s, after: (s.after ?? []).filter((d) => d !== from) } : s)
119
+ );
120
+ }
121
+ function updateStep(workflow, id, patch) {
122
+ const steps = explicit(workflowSteps(workflow));
123
+ return withSteps(
124
+ workflow,
125
+ steps.map(
126
+ (s) => s.id === id ? { ...s, ...patch, id: s.id, kind: s.kind } : s
127
+ )
128
+ );
129
+ }
130
+ var renameIn = (text, from, to) => text.replace(
131
+ /\{\{([^}]*)\}\}/g,
132
+ (_, inner) => `{{${inner.split("||").map((part) => part.trim() === from ? part.replace(from, to) : part).join("||")}}}`
133
+ );
134
+ function renameInput(workflow, from, to) {
135
+ if (from === to) return workflow;
136
+ const steps = workflowSteps(workflow).map((s) => {
137
+ if (s.kind === "agent")
138
+ return {
139
+ ...s,
140
+ with: s.with ? Object.fromEntries(Object.entries(s.with).map(([k, v]) => [k, renameIn(v, from, to)])) : s.with,
141
+ run: s.run?.kind === "prompt" ? { ...s.run, promptTemplate: renameIn(s.run.promptTemplate, from, to) } : s.run
142
+ };
143
+ if (s.kind === "review" && s.instructions)
144
+ return { ...s, instructions: renameIn(s.instructions, from, to) };
145
+ return s;
146
+ });
147
+ return withSteps(workflow, steps);
148
+ }
149
+ function moveStep(workflow, id, position) {
150
+ return withSteps(workflow, workflowSteps(workflow), { ...layoutOf(workflow), [id]: position });
151
+ }
152
+ var COLUMN = 300;
153
+ var ROW = 140;
154
+ function autoLayout(steps) {
155
+ const layout = {};
156
+ stepLevels(steps).forEach(
157
+ (level, x) => level.forEach((s, y) => {
158
+ layout[s.id] = { x: x * COLUMN, y: (y - (level.length - 1) / 2) * ROW };
159
+ })
160
+ );
161
+ return layout;
162
+ }
163
+ function layoutWithDefaults(workflow) {
164
+ const auto = autoLayout(workflowSteps(workflow));
165
+ return { ...auto, ...layoutOf(workflow) };
166
+ }
167
+ var DEFAULT_STEP_INSPECTOR_LABELS = {
168
+ workflow: "Workflow",
169
+ title: "Title",
170
+ description: "Description",
171
+ waitsFor: "Waits for",
172
+ startsWithRun: "Starts with the run",
173
+ fields: "Fields",
174
+ addField: "Add field",
175
+ fieldLabel: "Label",
176
+ fieldType: "Type",
177
+ required: "Required",
178
+ runs: "Runs",
179
+ chooseRun: "Choose a skill or write an instruction\u2026",
180
+ ownPrompt: "Its own instruction",
181
+ prompt: "Instruction",
182
+ promptHint: "Use {{field}} for an input and {{steps.<id>}} for an earlier step\u2019s answer.",
183
+ inputFor: "Input",
184
+ inputHint: "Type a value, or use a field: {fields}",
185
+ inputHintNoFields: "Type a value, or add a form field to fill it from.",
186
+ thread: "Where it runs",
187
+ threadMain: "In the run\u2019s thread (sees the conversation so far)",
188
+ threadBranch: "On a branch (delivers only its result to the thread)",
189
+ output: "Shows",
190
+ outputAnswer: "The answer",
191
+ outputArtifact: "A drafted document",
192
+ uploadRequired: "Files are required",
193
+ hint: "Hint",
194
+ instructions: "What to review",
195
+ instructionsHint: "Shown on the review task; {{field}} and {{steps.<id>}} work here.",
196
+ deleteStep: "Delete step"
197
+ };
198
+ var FIELD_TYPES = ["text", "longtext", "boolean", "number", "date", "scope"];
199
+ var selectClass = "flex h-9 w-full rounded-md border border-input bg-background px-2 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring";
200
+ function Field({ label, children, hint }) {
201
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
202
+ /* @__PURE__ */ jsx(Label, { className: "text-xs", children: label }),
203
+ children,
204
+ hint && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: hint })
205
+ ] });
206
+ }
207
+ var fieldName = (label, taken) => {
208
+ const base = label.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").replace(/^[^a-z]+/, "") || "field";
209
+ let name = base;
210
+ let n = 2;
211
+ while (taken.includes(name)) name = `${base}_${n++}`;
212
+ return name;
213
+ };
214
+ function StepInspector({
215
+ workflow,
216
+ stepId,
217
+ onChange,
218
+ onSelect,
219
+ workflows = [],
220
+ labels,
221
+ testId = "step-inspector"
222
+ }) {
223
+ const l = { ...DEFAULT_STEP_INSPECTOR_LABELS, ...labels };
224
+ const steps = workflowSteps(workflow);
225
+ const step = steps.find((s) => s.id === stepId);
226
+ const run = workflow.run.kind === "steps" ? workflow.run : void 0;
227
+ if (!step || !run) {
228
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", "data-testid": `${testId}-workflow`, children: [
229
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold", children: l.workflow }),
230
+ /* @__PURE__ */ jsx(Field, { label: l.title, children: /* @__PURE__ */ jsx(
231
+ Input,
232
+ {
233
+ value: workflow.title,
234
+ onChange: (e) => onChange({ ...workflow, title: e.target.value }),
235
+ "data-testid": `${testId}-workflow-title`
236
+ }
237
+ ) }),
238
+ /* @__PURE__ */ jsx(Field, { label: l.description, children: /* @__PURE__ */ jsx(
239
+ Textarea,
240
+ {
241
+ value: workflow.description,
242
+ rows: 2,
243
+ onChange: (e) => onChange({ ...workflow, description: e.target.value })
244
+ }
245
+ ) })
246
+ ] });
247
+ }
248
+ const patch = (p) => onChange(updateStep(workflow, step.id, p));
249
+ const deps = stepDependencies(steps, step.id);
250
+ const titleOf = (id) => steps.find((s) => s.id === id)?.title ?? id;
251
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", "data-testid": `${testId}-step`, children: [
252
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
253
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-semibold", children: step.title }),
254
+ /* @__PURE__ */ jsx("code", { className: "shrink-0 rounded bg-muted px-1.5 py-0.5 text-[11px] text-muted-foreground", children: step.id })
255
+ ] }),
256
+ /* @__PURE__ */ jsx(Field, { label: l.title, children: /* @__PURE__ */ jsx(
257
+ Input,
258
+ {
259
+ value: step.title,
260
+ onChange: (e) => patch({ title: e.target.value }),
261
+ "data-testid": `${testId}-title`
262
+ }
263
+ ) }),
264
+ /* @__PURE__ */ jsx(Field, { label: l.description, children: /* @__PURE__ */ jsx(
265
+ Textarea,
266
+ {
267
+ value: step.description ?? "",
268
+ rows: 2,
269
+ onChange: (e) => patch({ description: e.target.value || void 0 })
270
+ }
271
+ ) }),
272
+ /* @__PURE__ */ jsx(Field, { label: l.waitsFor, children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-1.5", "data-testid": `${testId}-waits-for`, children: [
273
+ deps.length === 0 && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: l.startsWithRun }),
274
+ deps.map((d) => /* @__PURE__ */ jsxs(
275
+ "span",
276
+ {
277
+ className: "inline-flex items-center gap-1 rounded-full border border-border px-2 py-0.5 text-xs",
278
+ children: [
279
+ titleOf(d),
280
+ /* @__PURE__ */ jsx(
281
+ "button",
282
+ {
283
+ type: "button",
284
+ "aria-label": `${titleOf(d)} \xD7`,
285
+ onClick: () => onChange(disconnectSteps(workflow, d, step.id)),
286
+ children: /* @__PURE__ */ jsx(X, { className: "size-3" })
287
+ }
288
+ )
289
+ ]
290
+ },
291
+ d
292
+ ))
293
+ ] }) }),
294
+ step.kind === "form" && /* @__PURE__ */ jsx(
295
+ FormFields,
296
+ {
297
+ fields: step.inputs,
298
+ taken: steps.flatMap((s) => s.kind === "form" && s.id !== step.id ? s.inputs : []).map((f) => f.name),
299
+ onChange: (inputs) => {
300
+ const renamed = step.inputs.flatMap(
301
+ (f, i) => inputs[i] && inputs[i].name !== f.name ? [[f.name, inputs[i].name]] : []
302
+ );
303
+ onChange(
304
+ renamed.reduce(
305
+ (w, [from, to]) => renameInput(w, from, to),
306
+ updateStep(workflow, step.id, { inputs })
307
+ )
308
+ );
309
+ },
310
+ l,
311
+ testId
312
+ }
313
+ ),
314
+ step.kind === "upload" && /* @__PURE__ */ jsxs(Fragment, { children: [
315
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 text-sm", children: [
316
+ /* @__PURE__ */ jsx(
317
+ Checkbox,
318
+ {
319
+ checked: !!step.required,
320
+ onCheckedChange: (v) => patch({ required: v === true || void 0 })
321
+ }
322
+ ),
323
+ l.uploadRequired
324
+ ] }),
325
+ /* @__PURE__ */ jsx(Field, { label: l.hint, children: /* @__PURE__ */ jsx(
326
+ Input,
327
+ {
328
+ value: step.hint ?? "",
329
+ onChange: (e) => patch({ hint: e.target.value || void 0 })
330
+ }
331
+ ) })
332
+ ] }),
333
+ step.kind === "agent" && /* @__PURE__ */ jsx(
334
+ AgentSettings,
335
+ {
336
+ step,
337
+ workflows,
338
+ fields: runFields(workflow).map((f) => f.name),
339
+ patch,
340
+ l,
341
+ testId
342
+ }
343
+ ),
344
+ step.kind === "review" && /* @__PURE__ */ jsx(Field, { label: l.instructions, hint: l.instructionsHint, children: /* @__PURE__ */ jsx(
345
+ Textarea,
346
+ {
347
+ value: step.instructions ?? "",
348
+ rows: 4,
349
+ onChange: (e) => patch({ instructions: e.target.value || void 0 }),
350
+ "data-testid": `${testId}-instructions`
351
+ }
352
+ ) }),
353
+ /* @__PURE__ */ jsxs(
354
+ Button,
355
+ {
356
+ variant: "ghost",
357
+ size: "sm",
358
+ className: "text-destructive",
359
+ onClick: () => {
360
+ onChange(removeStep(workflow, step.id));
361
+ onSelect?.(null);
362
+ },
363
+ "data-testid": `${testId}-delete`,
364
+ children: [
365
+ /* @__PURE__ */ jsx(Trash2, { className: "size-4" }),
366
+ " ",
367
+ l.deleteStep
368
+ ]
369
+ }
370
+ )
371
+ ] });
372
+ }
373
+ function FormFields({
374
+ fields,
375
+ taken,
376
+ onChange,
377
+ l,
378
+ testId
379
+ }) {
380
+ const set = (i, p) => onChange(fields.map((f, j) => j === i ? { ...f, ...p } : f));
381
+ return /* @__PURE__ */ jsx(Field, { label: l.fields, children: /* @__PURE__ */ jsxs("div", { className: "space-y-2", "data-testid": `${testId}-fields`, children: [
382
+ fields.map((f, i) => /* @__PURE__ */ jsxs("div", { className: "space-y-1.5 rounded-md border border-border p-2", children: [
383
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
384
+ /* @__PURE__ */ jsx(
385
+ Input,
386
+ {
387
+ value: f.label,
388
+ "aria-label": l.fieldLabel,
389
+ onChange: (e) => set(i, {
390
+ label: e.target.value,
391
+ name: fieldName(e.target.value, [
392
+ ...taken,
393
+ ...fields.filter((_, j) => j !== i).map((x) => x.name)
394
+ ])
395
+ }),
396
+ className: "h-8"
397
+ }
398
+ ),
399
+ /* @__PURE__ */ jsx(
400
+ "button",
401
+ {
402
+ type: "button",
403
+ "aria-label": `${f.label} \xD7`,
404
+ onClick: () => onChange(fields.filter((_, j) => j !== i)),
405
+ children: /* @__PURE__ */ jsx(X, { className: "size-4 text-muted-foreground" })
406
+ }
407
+ )
408
+ ] }),
409
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
410
+ /* @__PURE__ */ jsx(
411
+ "select",
412
+ {
413
+ "aria-label": l.fieldType,
414
+ className: cn(selectClass, "h-8"),
415
+ value: f.type,
416
+ onChange: (e) => set(i, { type: e.target.value }),
417
+ children: FIELD_TYPES.map((t) => /* @__PURE__ */ jsx("option", { value: t, children: t }, t))
418
+ }
419
+ ),
420
+ /* @__PURE__ */ jsxs("label", { className: "flex shrink-0 items-center gap-1.5 text-xs", children: [
421
+ /* @__PURE__ */ jsx(
422
+ Checkbox,
423
+ {
424
+ checked: !!f.required,
425
+ onCheckedChange: (v) => set(i, { required: v === true || void 0 })
426
+ }
427
+ ),
428
+ l.required
429
+ ] }),
430
+ /* @__PURE__ */ jsx("code", { className: "shrink-0 text-[11px] text-muted-foreground", children: `{{${f.name}}}` })
431
+ ] })
432
+ ] }, i)),
433
+ /* @__PURE__ */ jsxs(
434
+ Button,
435
+ {
436
+ variant: "outline",
437
+ size: "sm",
438
+ onClick: () => onChange([
439
+ ...fields,
440
+ {
441
+ name: fieldName("Field", [...taken, ...fields.map((f) => f.name)]),
442
+ type: "text",
443
+ label: "Field"
444
+ }
445
+ ]),
446
+ "data-testid": `${testId}-add-field`,
447
+ children: [
448
+ /* @__PURE__ */ jsx(Plus, { className: "size-4" }),
449
+ " ",
450
+ l.addField
451
+ ]
452
+ }
453
+ )
454
+ ] }) });
455
+ }
456
+ var OWN_PROMPT = "__prompt";
457
+ function prefillSkillInputs(target, fields, current = {}) {
458
+ const out = {};
459
+ for (const f of target?.inputs ?? []) {
460
+ const typed = current[f.name];
461
+ if (typed) out[f.name] = typed;
462
+ else if (fields.includes(f.name)) out[f.name] = `{{${f.name}}}`;
463
+ }
464
+ return out;
465
+ }
466
+ function AgentSettings({
467
+ step,
468
+ workflows,
469
+ fields,
470
+ patch,
471
+ l,
472
+ testId
473
+ }) {
474
+ const target = step.workflow ? workflows.find((w) => w.id === step.workflow) : void 0;
475
+ const value = step.workflow ?? (step.run ? OWN_PROMPT : "");
476
+ const codes = fields.map((f) => `{{${f}}}`).join(", ");
477
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
478
+ /* @__PURE__ */ jsx(Field, { label: l.runs, children: /* @__PURE__ */ jsxs(
479
+ "select",
480
+ {
481
+ className: selectClass,
482
+ value,
483
+ onChange: (e) => {
484
+ const id = e.target.value;
485
+ if (id === OWN_PROMPT)
486
+ patch({
487
+ workflow: void 0,
488
+ with: void 0,
489
+ run: { kind: "prompt", promptTemplate: "" }
490
+ });
491
+ else if (id)
492
+ patch({
493
+ workflow: id,
494
+ run: void 0,
495
+ with: prefillSkillInputs(
496
+ workflows.find((w) => w.id === id),
497
+ fields,
498
+ step.with
499
+ )
500
+ });
501
+ },
502
+ "data-testid": `${testId}-runs`,
503
+ children: [
504
+ !value && /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: l.chooseRun }),
505
+ /* @__PURE__ */ jsx("option", { value: OWN_PROMPT, children: l.ownPrompt }),
506
+ workflows.map((w) => /* @__PURE__ */ jsx("option", { value: w.id, children: w.title }, w.id)),
507
+ step.workflow && !target && /* @__PURE__ */ jsx("option", { value: step.workflow, children: step.workflow })
508
+ ]
509
+ }
510
+ ) }),
511
+ step.workflow ? (target?.inputs ?? []).map((f) => /* @__PURE__ */ jsx(
512
+ Field,
513
+ {
514
+ label: `${l.inputFor}: ${f.label}`,
515
+ hint: step.with?.[f.name] ? void 0 : codes ? l.inputHint.replace("{fields}", codes) : l.inputHintNoFields,
516
+ children: /* @__PURE__ */ jsx(
517
+ Input,
518
+ {
519
+ value: step.with?.[f.name] ?? "",
520
+ onChange: (e) => patch({ with: { ...step.with, [f.name]: e.target.value } }),
521
+ "data-testid": `${testId}-with-${f.name}`
522
+ }
523
+ )
524
+ },
525
+ f.name
526
+ )) : step.run?.kind === "prompt" ? /* @__PURE__ */ jsx(Field, { label: l.prompt, hint: l.promptHint, children: /* @__PURE__ */ jsx(
527
+ Textarea,
528
+ {
529
+ value: step.run.promptTemplate,
530
+ rows: 5,
531
+ onChange: (e) => patch({ run: { kind: "prompt", promptTemplate: e.target.value } }),
532
+ "data-testid": `${testId}-prompt`
533
+ }
534
+ ) }) : null,
535
+ /* @__PURE__ */ jsx(Field, { label: l.thread, children: /* @__PURE__ */ jsxs(
536
+ "select",
537
+ {
538
+ className: selectClass,
539
+ value: step.branch ? "branch" : "main",
540
+ onChange: (e) => patch({ branch: e.target.value === "branch" || void 0 }),
541
+ "data-testid": `${testId}-thread`,
542
+ children: [
543
+ /* @__PURE__ */ jsx("option", { value: "main", children: l.threadMain }),
544
+ /* @__PURE__ */ jsx("option", { value: "branch", children: l.threadBranch })
545
+ ]
546
+ }
547
+ ) }),
548
+ /* @__PURE__ */ jsx(Field, { label: l.output, children: /* @__PURE__ */ jsxs(
549
+ "select",
550
+ {
551
+ className: selectClass,
552
+ value: step.output ?? "answer",
553
+ onChange: (e) => patch({ output: e.target.value === "artifact" ? "artifact" : void 0 }),
554
+ children: [
555
+ /* @__PURE__ */ jsx("option", { value: "answer", children: l.outputAnswer }),
556
+ /* @__PURE__ */ jsx("option", { value: "artifact", children: l.outputArtifact })
557
+ ]
558
+ }
559
+ ) })
560
+ ] });
561
+ }
562
+ var DEFAULT_TRIGGER_INSPECTOR_LABELS = {
563
+ heading: "Start",
564
+ start: "Starts",
565
+ starts: {
566
+ manual: "When someone runs it",
567
+ once: "Once, at a time",
568
+ repeat: "On a schedule",
569
+ event: "When something happens",
570
+ task: "When a task is done"
571
+ },
572
+ runAt: "When",
573
+ event: "Event",
574
+ eventPlaceholder: "Pick an event",
575
+ afterTask: "After this task",
576
+ afterTaskPlaceholder: "Pick a task",
577
+ agent: "Runs as",
578
+ access: "Access when nobody is there",
579
+ accessAsk: "Ask me each run",
580
+ accessStanding: "Keep access"
581
+ };
582
+ function triggerSummary(trigger, options = {}) {
583
+ const l = { ...DEFAULT_TRIGGER_INSPECTOR_LABELS, ...options.labels };
584
+ switch (trigger.start) {
585
+ case "once":
586
+ return trigger.runAt ? new Date(trigger.runAt).toLocaleString() : l.starts.once;
587
+ case "repeat":
588
+ return trigger.rrule ? describeRRule(trigger.rrule) : l.starts.repeat;
589
+ case "event":
590
+ return trigger.eventKind && options.eventLabel?.(trigger.eventKind) || trigger.eventKind || l.starts.event;
591
+ case "task":
592
+ return trigger.afterTaskId && options.taskLabel?.(trigger.afterTaskId) || l.starts.task;
593
+ default:
594
+ return l.starts.manual;
595
+ }
596
+ }
597
+ var selectClass2 = "flex h-9 w-full rounded-md border border-input bg-background px-2 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring";
598
+ var STARTS = ["manual", "once", "repeat", "event", "task"];
599
+ function TriggerInspector({
600
+ trigger,
601
+ onChange,
602
+ agents = [],
603
+ eventKinds = [],
604
+ tasks = [],
605
+ labels,
606
+ testId = "trigger-inspector"
607
+ }) {
608
+ const l = { ...DEFAULT_TRIGGER_INSPECTOR_LABELS, ...labels };
609
+ const set = (patch) => onChange({ ...trigger, ...patch });
610
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", "data-testid": testId, children: [
611
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold", children: l.heading }),
612
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
613
+ /* @__PURE__ */ jsx(Label, { className: "text-xs", children: l.start }),
614
+ /* @__PURE__ */ jsx("div", { className: "grid gap-1.5", role: "radiogroup", "aria-label": l.start, children: STARTS.map((start) => /* @__PURE__ */ jsx(
615
+ "button",
616
+ {
617
+ type: "button",
618
+ role: "radio",
619
+ "aria-checked": trigger.start === start,
620
+ onClick: () => set({ start }),
621
+ className: cn(
622
+ "rounded-md border px-3 py-2 text-left text-sm transition-colors",
623
+ trigger.start === start ? "border-primary bg-primary/10 text-primary" : "border-border hover:bg-muted/50"
624
+ ),
625
+ "data-testid": `${testId}-start-${start}`,
626
+ children: l.starts[start]
627
+ },
628
+ start
629
+ )) })
630
+ ] }),
631
+ trigger.start === "once" && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
632
+ /* @__PURE__ */ jsx(Label, { className: "text-xs", children: l.runAt }),
633
+ /* @__PURE__ */ jsx(
634
+ Input,
635
+ {
636
+ type: "datetime-local",
637
+ value: trigger.runAt,
638
+ onChange: (e) => set({ runAt: e.target.value }),
639
+ "data-testid": `${testId}-run-at`
640
+ }
641
+ )
642
+ ] }),
643
+ trigger.start === "repeat" && /* @__PURE__ */ jsx(RecurrenceBuilder, { rrule: trigger.rrule, onChange: (rrule) => set({ rrule }) }),
644
+ trigger.start === "event" && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
645
+ /* @__PURE__ */ jsx(Label, { className: "text-xs", children: l.event }),
646
+ /* @__PURE__ */ jsxs(
647
+ "select",
648
+ {
649
+ className: selectClass2,
650
+ value: trigger.eventKind,
651
+ onChange: (e) => set({ eventKind: e.target.value }),
652
+ "data-testid": `${testId}-event`,
653
+ children: [
654
+ /* @__PURE__ */ jsx("option", { value: "", children: l.eventPlaceholder }),
655
+ eventKinds.map((k) => /* @__PURE__ */ jsx("option", { value: k.value, children: k.label }, k.value))
656
+ ]
657
+ }
658
+ )
659
+ ] }),
660
+ trigger.start === "task" && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
661
+ /* @__PURE__ */ jsx(Label, { className: "text-xs", children: l.afterTask }),
662
+ /* @__PURE__ */ jsxs(
663
+ "select",
664
+ {
665
+ className: selectClass2,
666
+ value: trigger.afterTaskId,
667
+ onChange: (e) => set({ afterTaskId: e.target.value }),
668
+ "data-testid": `${testId}-after-task`,
669
+ children: [
670
+ /* @__PURE__ */ jsx("option", { value: "", children: l.afterTaskPlaceholder }),
671
+ tasks.map((t) => /* @__PURE__ */ jsx("option", { value: t.value, children: t.label }, t.value))
672
+ ]
673
+ }
674
+ )
675
+ ] }),
676
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
677
+ /* @__PURE__ */ jsx(Label, { className: "text-xs", children: l.agent }),
678
+ /* @__PURE__ */ jsx(
679
+ AgentSelect,
680
+ {
681
+ agents,
682
+ value: trigger.agentId,
683
+ onValueChange: (agentId) => set({ agentId }),
684
+ hideLabel: true,
685
+ testId: `${testId}-agent`
686
+ }
687
+ )
688
+ ] }),
689
+ trigger.start !== "manual" && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
690
+ /* @__PURE__ */ jsx(Label, { className: "text-xs", children: l.access }),
691
+ /* @__PURE__ */ jsxs(
692
+ "select",
693
+ {
694
+ className: selectClass2,
695
+ value: trigger.access,
696
+ onChange: (e) => set({ access: e.target.value === "standing" ? "standing" : "ask_each_run" }),
697
+ "data-testid": `${testId}-access`,
698
+ children: [
699
+ /* @__PURE__ */ jsx("option", { value: "ask_each_run", children: l.accessAsk }),
700
+ /* @__PURE__ */ jsx("option", { value: "standing", children: l.accessStanding })
701
+ ]
702
+ }
703
+ )
704
+ ] })
705
+ ] });
706
+ }
707
+
708
+ // src/workflow-canvas/viewport.ts
709
+ var READABLE_ZOOM = 0.7;
710
+ var DEFAULT_W = 256;
711
+ var DEFAULT_H = 80;
712
+ var MARGIN = 48;
713
+ var sizeOf = (n) => ({
714
+ w: n.measured?.width ?? DEFAULT_W,
715
+ h: n.measured?.height ?? DEFAULT_H
716
+ });
717
+ function readableViewport(nodes, fittedZoom, frame, {
718
+ anchor,
719
+ centre = false,
720
+ reserveRight = 0
721
+ } = {}) {
722
+ if (fittedZoom >= READABLE_ZOOM || !frame.height || nodes.length === 0) return null;
723
+ const z = READABLE_ZOOM;
724
+ const at = nodes.find((n) => n.id === anchor) ?? nodes.reduce((a, b) => b.position.x < a.position.x ? b : a);
725
+ const { w, h } = sizeOf(at);
726
+ const y = frame.height / 2 - (at.position.y + h / 2) * z;
727
+ if (centre && at.id === anchor) {
728
+ const visible = Math.max(0, frame.width - reserveRight);
729
+ return { x: visible / 2 - (at.position.x + w / 2) * z, y, zoom: z };
730
+ }
731
+ return { x: MARGIN - at.position.x * z, y, zoom: z };
732
+ }
733
+ function revealCenter(node, view, frame, reserveRight = 0) {
734
+ const { w, h } = sizeOf(node);
735
+ const { x, y, zoom } = view;
736
+ const left = node.position.x * zoom + x;
737
+ const top = node.position.y * zoom + y;
738
+ const right = frame.width - reserveRight;
739
+ if (left >= 16 && top >= 16 && left + w * zoom <= right - 16 && top + h * zoom <= frame.height - 16)
740
+ return null;
741
+ return {
742
+ x: node.position.x + w / 2 + reserveRight / 2 / zoom,
743
+ y: node.position.y + h / 2
744
+ };
745
+ }
746
+ var DEFAULT_WORKFLOW_CANVAS_LABELS = {
747
+ form: "Form",
748
+ upload: "Upload",
749
+ agent: "AI step",
750
+ review: "Approval",
751
+ branch: "Branch",
752
+ kindHints: {
753
+ form: "Ask for details",
754
+ upload: "Collect files",
755
+ agent: "Run a skill or an instruction",
756
+ review: "A person approves"
757
+ },
758
+ addNext: "Add the next step",
759
+ runs: "Runs \u201C{name}\u201D",
760
+ chooseRun: "Choose a skill or write an instruction",
761
+ fields: "{count} fields",
762
+ noFields: "No fields yet",
763
+ approvedBy: "A person approves",
764
+ emptyTitle: "Start your workflow",
765
+ emptyHint: "Add the first step: what should happen when it starts?",
766
+ fitView: "Fit view"
767
+ };
768
+ var TRIGGER_NODE = "__trigger";
769
+ var STEP_KIND_ICONS = {
770
+ form: FileInput,
771
+ upload: Upload,
772
+ agent: Bot,
773
+ review: ClipboardCheck
774
+ };
775
+ var KINDS = ["form", "upload", "agent", "review"];
776
+ function AddNext({
777
+ from,
778
+ onAdd,
779
+ l,
780
+ testId
781
+ }) {
782
+ const [open, setOpen] = useState(false);
783
+ return /* @__PURE__ */ jsxs(Popover, { open, onOpenChange: setOpen, children: [
784
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
785
+ "button",
786
+ {
787
+ type: "button",
788
+ className: "wf-add nodrag nopan flex size-7 items-center justify-center rounded-full border border-border bg-card text-muted-foreground shadow-sm hover:text-foreground",
789
+ "aria-label": l.addNext,
790
+ "data-testid": `${testId}-add-after-${from === TRIGGER_NODE ? "trigger" : from}`,
791
+ children: /* @__PURE__ */ jsx(Plus, { className: "size-4" })
792
+ }
793
+ ) }),
794
+ /* @__PURE__ */ jsxs(PopoverContent, { side: "right", align: "start", className: "w-60 p-1.5", children: [
795
+ /* @__PURE__ */ jsx("p", { className: "px-2 pb-1 pt-0.5 text-xs font-medium text-muted-foreground", children: l.addNext }),
796
+ KINDS.map((kind) => {
797
+ const Icon = STEP_KIND_ICONS[kind];
798
+ return /* @__PURE__ */ jsxs(
799
+ "button",
800
+ {
801
+ type: "button",
802
+ onClick: (e) => {
803
+ e.stopPropagation();
804
+ setOpen(false);
805
+ onAdd(from, kind);
806
+ },
807
+ className: cn(
808
+ `wf-kind-${kind}`,
809
+ "flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-left hover:bg-muted"
810
+ ),
811
+ "data-testid": `${testId}-add-after-${from === TRIGGER_NODE ? "trigger" : from}-${kind}`,
812
+ children: [
813
+ /* @__PURE__ */ jsx("span", { className: "wf-badge flex size-7 shrink-0 items-center justify-center rounded-md", children: /* @__PURE__ */ jsx(Icon, { className: "size-4" }) }),
814
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
815
+ /* @__PURE__ */ jsx("span", { className: "block text-sm font-medium", children: l[kind] }),
816
+ /* @__PURE__ */ jsx("span", { className: "block text-xs text-muted-foreground", children: l.kindHints[kind] })
817
+ ] })
818
+ ]
819
+ },
820
+ kind
821
+ );
822
+ })
823
+ ] })
824
+ ] });
825
+ }
826
+ function PhaseMark({ phase }) {
827
+ if (phase === "running") return /* @__PURE__ */ jsx(Loader2, { className: "wf-kind-text size-4 animate-spin" });
828
+ if (phase === "done") return /* @__PURE__ */ jsx(Check, { className: "wf-kind-text size-4" });
829
+ if (phase === "failed") return /* @__PURE__ */ jsx(AlertTriangle, { className: "size-4 text-destructive" });
830
+ if (phase === "skipped") return /* @__PURE__ */ jsx(MinusCircle, { className: "size-4 text-muted-foreground" });
831
+ return null;
832
+ }
833
+ function StepNode({ data }) {
834
+ const { step, phase, summary, icon, l, onAdd, testId } = data;
835
+ const KindIcon = STEP_KIND_ICONS[step.kind];
836
+ return /* @__PURE__ */ jsxs(
837
+ "div",
838
+ {
839
+ className: cn(
840
+ "wf-node w-64 py-3 pl-4 pr-3 text-left",
841
+ `wf-kind-${step.kind}`,
842
+ phase && `wf-phase-${phase}`
843
+ ),
844
+ "data-testid": `${testId}-node-${step.id}`,
845
+ "data-phase": phase,
846
+ children: [
847
+ /* @__PURE__ */ jsx(Handle, { type: "target", position: Position.Left }),
848
+ /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2.5", children: [
849
+ /* @__PURE__ */ jsx("span", { className: "wf-badge flex size-8 shrink-0 items-center justify-center rounded-lg [&_svg]:size-4", children: icon ?? /* @__PURE__ */ jsx(KindIcon, {}) }),
850
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
851
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
852
+ /* @__PURE__ */ jsx("span", { className: "wf-kind-text text-[10px] font-semibold uppercase tracking-wider", children: l[step.kind] }),
853
+ /* @__PURE__ */ jsx(PhaseMark, { phase })
854
+ ] }),
855
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-semibold leading-snug", children: step.title }),
856
+ summary && /* @__PURE__ */ jsx("p", { className: "mt-0.5 line-clamp-2 text-xs leading-snug text-muted-foreground", children: summary }),
857
+ step.kind === "agent" && step.branch && /* @__PURE__ */ jsxs("span", { className: "mt-1.5 inline-flex items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-[10px] font-medium text-muted-foreground", children: [
858
+ /* @__PURE__ */ jsx(GitBranch, { className: "size-3" }),
859
+ " ",
860
+ l.branch
861
+ ] })
862
+ ] })
863
+ ] }),
864
+ /* @__PURE__ */ jsx(Handle, { type: "source", position: Position.Right }),
865
+ onAdd && /* @__PURE__ */ jsx(AddNext, { from: step.id, onAdd, l, testId })
866
+ ]
867
+ }
868
+ );
869
+ }
870
+ function TriggerNode({ data }) {
871
+ return /* @__PURE__ */ jsxs(
872
+ "div",
873
+ {
874
+ className: "wf-start wf-kind-start relative w-60 px-4 py-3 text-left",
875
+ "data-testid": `${data.testId}-node-trigger`,
876
+ children: [
877
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
878
+ /* @__PURE__ */ jsx("span", { className: "wf-badge flex size-8 shrink-0 items-center justify-center rounded-full", children: /* @__PURE__ */ jsx(Zap, { className: "size-4" }) }),
879
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
880
+ /* @__PURE__ */ jsx("p", { className: "wf-kind-text text-[10px] font-semibold uppercase tracking-wider", children: data.title }),
881
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-semibold", children: data.summary })
882
+ ] })
883
+ ] }),
884
+ /* @__PURE__ */ jsx(Handle, { type: "source", position: Position.Right }),
885
+ data.onAdd && /* @__PURE__ */ jsx(AddNext, { from: TRIGGER_NODE, onAdd: data.onAdd, l: data.l, testId: data.testId })
886
+ ]
887
+ }
888
+ );
889
+ }
890
+ var NODE_TYPES = { step: StepNode, trigger: TriggerNode };
891
+ var firstWords = (text, n = 14) => {
892
+ const words = text.trim().split(/\s+/);
893
+ return words.length > n ? `${words.slice(0, n).join(" ")}\u2026` : text.trim();
894
+ };
895
+ function summaryOf(step, l, workflowTitle) {
896
+ if (step.description) return step.description;
897
+ switch (step.kind) {
898
+ case "agent":
899
+ if (step.workflow)
900
+ return l.runs.replace("{name}", workflowTitle?.(step.workflow) ?? step.workflow);
901
+ if (step.run?.kind === "operation") return void 0;
902
+ return step.run?.kind === "prompt" && step.run.promptTemplate.trim() ? firstWords(step.run.promptTemplate) : l.chooseRun;
903
+ case "form":
904
+ return step.inputs.length ? step.inputs.length === 1 ? step.inputs[0].label : l.fields.replace("{count}", String(step.inputs.length)) : l.noFields;
905
+ case "upload":
906
+ return step.hint;
907
+ case "review":
908
+ return step.instructions ? firstWords(step.instructions) : l.approvedBy;
909
+ }
910
+ }
911
+ function WorkflowCanvas({
912
+ workflow,
913
+ onChange,
914
+ selected = null,
915
+ onSelect,
916
+ focus,
917
+ onAddAfter,
918
+ phases,
919
+ workflowTitle,
920
+ trigger,
921
+ renderIcon,
922
+ labels,
923
+ className,
924
+ reserveRight = 0,
925
+ testId = "workflow-canvas"
926
+ }) {
927
+ const l = useMemo(() => ({ ...DEFAULT_WORKFLOW_CANVAS_LABELS, ...labels }), [labels]);
928
+ const steps = workflowSteps(workflow);
929
+ const editable = !!onChange;
930
+ const built = useMemo(() => {
931
+ const layout = layoutWithDefaults(workflow);
932
+ const all = workflowSteps(workflow);
933
+ const roots = all.filter((s) => stepDependencies(all, s.id).length === 0);
934
+ const triggerNode = trigger ? [
935
+ {
936
+ id: TRIGGER_NODE,
937
+ type: "trigger",
938
+ deletable: false,
939
+ selected: selected === TRIGGER_NODE,
940
+ position: layout[TRIGGER_NODE] ?? {
941
+ x: Math.min(0, ...Object.values(layout).map((p) => p.x)) - 320,
942
+ y: roots.length ? roots.reduce((sum, s) => sum + (layout[s.id]?.y ?? 0), 0) / roots.length : 0
943
+ },
944
+ data: { title: trigger.title, summary: trigger.summary, l, onAdd: onAddAfter, testId }
945
+ }
946
+ ] : [];
947
+ return [
948
+ ...triggerNode,
949
+ ...all.map((step) => ({
950
+ id: step.id,
951
+ type: "step",
952
+ position: layout[step.id] ?? { x: 0, y: 0 },
953
+ selected: step.id === selected,
954
+ data: {
955
+ step,
956
+ phase: phases?.[step.id],
957
+ summary: summaryOf(step, l, workflowTitle),
958
+ icon: step.icon && renderIcon ? renderIcon(step.icon) : void 0,
959
+ l,
960
+ onAdd: onAddAfter,
961
+ testId
962
+ }
963
+ }))
964
+ ];
965
+ }, [workflow, selected, phases, workflowTitle, trigger, renderIcon, l, onAddAfter, testId]);
966
+ const [nodes, setNodes] = useState(built);
967
+ useEffect(() => setNodes(built), [built]);
968
+ const edges = useMemo(
969
+ () => steps.flatMap((s) => {
970
+ const deps = stepDependencies(steps, s.id);
971
+ const from = deps.length ? deps : trigger ? [TRIGGER_NODE] : [];
972
+ return from.map((d) => ({
973
+ id: `${d}->${s.id}`,
974
+ source: d,
975
+ target: s.id,
976
+ type: "smoothstep",
977
+ deletable: d !== TRIGGER_NODE,
978
+ markerEnd: { type: MarkerType.ArrowClosed, width: 16, height: 16 },
979
+ animated: phases?.[s.id] === "running"
980
+ }));
981
+ }),
982
+ [steps, phases, trigger]
983
+ );
984
+ const onNodesChange = useCallback(
985
+ (changes) => {
986
+ setNodes((ns) => applyNodeChanges(changes, ns));
987
+ if (!onChange) return;
988
+ let next = workflow;
989
+ for (const c of changes) {
990
+ if (c.type === "position" && c.dragging === false && c.position && c.id !== TRIGGER_NODE) {
991
+ next = moveStep(next, c.id, c.position);
992
+ }
993
+ }
994
+ if (next !== workflow) onChange(next);
995
+ },
996
+ [workflow, onChange]
997
+ );
998
+ const onConnect = useCallback(
999
+ (c) => {
1000
+ if (!onChange || !c.source || !c.target) return;
1001
+ if (c.source === TRIGGER_NODE) onChange(updateStep(workflow, c.target, { after: [] }));
1002
+ else onChange(connectSteps(workflow, c.source, c.target));
1003
+ },
1004
+ [workflow, onChange]
1005
+ );
1006
+ const frame = useRef(null);
1007
+ const flowRef = useRef(null);
1008
+ const hasTrigger = !!trigger;
1009
+ const selectedRef = useRef(selected);
1010
+ selectedRef.current = selected;
1011
+ const fit = useCallback(
1012
+ async (flow, anchorOnSelection) => {
1013
+ await flow.fitView({
1014
+ padding: { top: "48px", bottom: "48px", left: "48px", right: `${reserveRight + 48}px` },
1015
+ maxZoom: 1
1016
+ });
1017
+ const box = frame.current;
1018
+ const picked = anchorOnSelection ? selectedRef.current : null;
1019
+ const view = readableViewport(
1020
+ flow.getNodes(),
1021
+ flow.getZoom(),
1022
+ { width: box?.clientWidth ?? 0, height: box?.clientHeight ?? 0 },
1023
+ {
1024
+ anchor: picked ?? (hasTrigger ? TRIGGER_NODE : null),
1025
+ centre: !!picked,
1026
+ reserveRight
1027
+ }
1028
+ );
1029
+ if (view) await flow.setViewport(view);
1030
+ },
1031
+ [reserveRight, hasTrigger]
1032
+ );
1033
+ const onInit = useCallback(
1034
+ async (flow) => {
1035
+ flowRef.current = flow;
1036
+ await fit(flow, false);
1037
+ },
1038
+ [fit]
1039
+ );
1040
+ const target = focus ?? selected;
1041
+ const revealed = useRef(null);
1042
+ useEffect(() => {
1043
+ const flow = flowRef.current;
1044
+ const box = frame.current;
1045
+ const node = target ? flow?.getNode(target) : void 0;
1046
+ const key = `${target}:${reserveRight}`;
1047
+ if (!target) revealed.current = null;
1048
+ if (!flow || !box || !node || revealed.current === key) return;
1049
+ revealed.current = key;
1050
+ const view = flow.getViewport();
1051
+ const centre = revealCenter(
1052
+ node,
1053
+ view,
1054
+ { width: box.clientWidth, height: box.clientHeight },
1055
+ reserveRight
1056
+ );
1057
+ if (centre) void flow.setCenter(centre.x, centre.y, { zoom: view.zoom, duration: 250 });
1058
+ }, [target, nodes, reserveRight]);
1059
+ const onDelete = useCallback(
1060
+ ({ nodes: gone, edges: cut }) => {
1061
+ if (!onChange) return;
1062
+ const removed = new Set(gone.map((n) => n.id));
1063
+ let next = gone.reduce((w, n) => removeStep(w, n.id), workflow);
1064
+ for (const e of cut) {
1065
+ if (!removed.has(e.source) && !removed.has(e.target)) {
1066
+ next = disconnectSteps(next, e.source, e.target);
1067
+ }
1068
+ }
1069
+ onChange(next);
1070
+ },
1071
+ [workflow, onChange]
1072
+ );
1073
+ return /* @__PURE__ */ jsxs(
1074
+ "div",
1075
+ {
1076
+ className: cn(
1077
+ "relative h-[max(560px,calc(100dvh-200px))] w-full overflow-hidden rounded-xl border border-border bg-muted/30",
1078
+ className
1079
+ ),
1080
+ ref: frame,
1081
+ "data-testid": testId,
1082
+ children: [
1083
+ /* @__PURE__ */ jsxs(
1084
+ ReactFlow,
1085
+ {
1086
+ nodes,
1087
+ edges,
1088
+ nodeTypes: NODE_TYPES,
1089
+ onNodesChange,
1090
+ onConnect,
1091
+ onDelete,
1092
+ isValidConnection: (c) => !!c.source && !!c.target && !wouldCycle(workflow, c.source, c.target),
1093
+ onNodeClick: (_, node) => onSelect?.(node.id),
1094
+ onPaneClick: () => onSelect?.(null),
1095
+ nodesDraggable: editable,
1096
+ nodesConnectable: editable,
1097
+ elementsSelectable: true,
1098
+ deleteKeyCode: editable ? ["Backspace", "Delete"] : null,
1099
+ onInit,
1100
+ minZoom: 0.3,
1101
+ proOptions: { hideAttribution: true },
1102
+ children: [
1103
+ /* @__PURE__ */ jsx(Background, { variant: BackgroundVariant.Dots, gap: 18, size: 1.5 }),
1104
+ /* @__PURE__ */ jsx(Controls, { showInteractive: false, showFitView: false, position: "bottom-left", children: /* @__PURE__ */ jsx(
1105
+ ControlButton,
1106
+ {
1107
+ className: "react-flow__controls-fitview",
1108
+ onClick: () => flowRef.current && void fit(flowRef.current, true),
1109
+ title: l.fitView,
1110
+ "aria-label": l.fitView,
1111
+ "data-testid": `${testId}-fit`,
1112
+ children: /* @__PURE__ */ jsx(Maximize, {})
1113
+ }
1114
+ ) }),
1115
+ steps.length > 3 && /* @__PURE__ */ jsx(
1116
+ MiniMap,
1117
+ {
1118
+ pannable: true,
1119
+ zoomable: true,
1120
+ position: "bottom-right",
1121
+ className: "wf-minimap !rounded-lg !border !border-border",
1122
+ style: { width: 168, height: 104 },
1123
+ nodeBorderRadius: 8,
1124
+ nodeStrokeWidth: 0
1125
+ }
1126
+ )
1127
+ ]
1128
+ }
1129
+ ),
1130
+ steps.length === 0 && onAddAfter && /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-x-0 bottom-16 flex justify-center", children: /* @__PURE__ */ jsxs(
1131
+ "div",
1132
+ {
1133
+ className: "pointer-events-auto rounded-xl border border-dashed border-border bg-card/90 p-5 text-center shadow-sm backdrop-blur",
1134
+ "data-testid": `${testId}-empty`,
1135
+ children: [
1136
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-semibold", children: l.emptyTitle }),
1137
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: l.emptyHint }),
1138
+ /* @__PURE__ */ jsx("div", { className: "mt-3 grid grid-cols-2 gap-2", children: KINDS.map((kind) => {
1139
+ const Icon = STEP_KIND_ICONS[kind];
1140
+ return /* @__PURE__ */ jsxs(
1141
+ "button",
1142
+ {
1143
+ type: "button",
1144
+ onClick: () => onAddAfter(TRIGGER_NODE, kind),
1145
+ className: cn(
1146
+ `wf-kind-${kind}`,
1147
+ "flex items-center gap-2 rounded-lg border border-border bg-card px-3 py-2 text-left text-sm hover:bg-muted"
1148
+ ),
1149
+ "data-testid": `${testId}-empty-${kind}`,
1150
+ children: [
1151
+ /* @__PURE__ */ jsx("span", { className: "wf-badge flex size-6 items-center justify-center rounded-md", children: /* @__PURE__ */ jsx(Icon, { className: "size-3.5" }) }),
1152
+ l[kind]
1153
+ ]
1154
+ },
1155
+ kind
1156
+ );
1157
+ }) })
1158
+ ]
1159
+ }
1160
+ ) })
1161
+ ]
1162
+ }
1163
+ );
1164
+ }
1165
+ var DEFAULT_WORKFLOW_BUILDER_LABELS = {
1166
+ addStep: "Add step",
1167
+ tidy: "Tidy",
1168
+ settings: "Workflow settings",
1169
+ closePanel: "Close panel",
1170
+ design: "Design",
1171
+ preview: "Preview",
1172
+ start: "Start",
1173
+ previewAnswer: "Preview: the agent would be asked \u2014\n\n{prompt}",
1174
+ problems: {
1175
+ duplicate: "\u201C{step}\u201D appears twice.",
1176
+ unknown: "\u201C{step}\u201D waits for a step that does not exist.",
1177
+ cycle: "\u201C{step}\u201D waits for itself (a loop).",
1178
+ order: "\u201C{step}\u201D is listed before a step it waits for."
1179
+ }
1180
+ };
1181
+ var PANEL_WIDTH = 344;
1182
+ var KINDS2 = ["form", "upload", "agent", "review"];
1183
+ function WorkflowBuilder({
1184
+ value,
1185
+ onChange,
1186
+ workflows = [],
1187
+ agents = [],
1188
+ eventKinds = [],
1189
+ tasks = [],
1190
+ compose,
1191
+ renderIcon,
1192
+ actions,
1193
+ labels,
1194
+ canvasLabels,
1195
+ inspectorLabels,
1196
+ triggerLabels,
1197
+ testId = "workflow-builder",
1198
+ className
1199
+ }) {
1200
+ const l = { ...DEFAULT_WORKFLOW_BUILDER_LABELS, ...labels };
1201
+ const [selected, setSelectedState] = useState(null);
1202
+ const [panelOpen, setPanelOpen] = useState(false);
1203
+ const setSelected = (id) => {
1204
+ setSelectedState(id);
1205
+ if (id) setPanelOpen(true);
1206
+ };
1207
+ const [mode, setMode] = useState("design");
1208
+ const [adding, setAdding] = useState(false);
1209
+ const { workflow, trigger } = value;
1210
+ const steps = workflowSteps(workflow);
1211
+ const problems = validateWorkflowGraph(steps);
1212
+ const titles = useMemo(() => new Map(workflows.map((w) => [w.id, w.title])), [workflows]);
1213
+ const setWorkflow = (next) => onChange({ ...value, workflow: next });
1214
+ const addAfter = (from, kind) => {
1215
+ const layout = layoutWithDefaults(workflow);
1216
+ const origin = from && from !== TRIGGER_NODE ? layout[from] : void 0;
1217
+ const siblings = steps.filter(
1218
+ (s) => from && from !== TRIGGER_NODE ? s.after?.includes(from) : s.after?.length === 0
1219
+ ).length;
1220
+ const { workflow: next, id } = addStep(workflow, kind, {
1221
+ after: from && from !== TRIGGER_NODE ? from : void 0,
1222
+ position: origin ? { x: origin.x + 320, y: origin.y + siblings * 150 } : {
1223
+ x: Math.max(0, ...Object.values(layout).map((p) => p.x)) + (steps.length ? 320 : 0),
1224
+ y: siblings * 150
1225
+ }
1226
+ });
1227
+ setWorkflow(next);
1228
+ setSelected(id);
1229
+ };
1230
+ const add = (kind) => addAfter(
1231
+ selected && selected !== TRIGGER_NODE ? selected : steps.at(-1)?.id ?? TRIGGER_NODE,
1232
+ kind
1233
+ );
1234
+ const summary = triggerSummary(trigger, {
1235
+ labels: triggerLabels,
1236
+ eventLabel: (k) => eventKinds.find((e) => e.value === k)?.label,
1237
+ taskLabel: (id) => tasks.find((t) => t.value === id)?.label
1238
+ });
1239
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-3", className), "data-testid": testId, children: [
1240
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
1241
+ /* @__PURE__ */ jsxs("div", { className: "flex rounded-md border border-border p-0.5", children: [
1242
+ /* @__PURE__ */ jsxs(
1243
+ Button,
1244
+ {
1245
+ variant: mode === "design" ? "secondary" : "ghost",
1246
+ size: "sm",
1247
+ onClick: () => setMode("design"),
1248
+ "data-testid": `${testId}-mode-design`,
1249
+ children: [
1250
+ /* @__PURE__ */ jsx(PencilRuler, { className: "size-4" }),
1251
+ " ",
1252
+ l.design
1253
+ ]
1254
+ }
1255
+ ),
1256
+ /* @__PURE__ */ jsxs(
1257
+ Button,
1258
+ {
1259
+ variant: mode === "preview" ? "secondary" : "ghost",
1260
+ size: "sm",
1261
+ onClick: () => setMode("preview"),
1262
+ disabled: problems.length > 0 || steps.length === 0,
1263
+ "data-testid": `${testId}-mode-preview`,
1264
+ children: [
1265
+ /* @__PURE__ */ jsx(Eye, { className: "size-4" }),
1266
+ " ",
1267
+ l.preview
1268
+ ]
1269
+ }
1270
+ )
1271
+ ] }),
1272
+ mode === "design" && /* @__PURE__ */ jsxs(Fragment, { children: [
1273
+ /* @__PURE__ */ jsxs(Popover, { open: adding, onOpenChange: setAdding, children: [
1274
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { size: "sm", "data-testid": `${testId}-add`, children: [
1275
+ /* @__PURE__ */ jsx(Plus, { className: "size-4" }),
1276
+ " ",
1277
+ l.addStep
1278
+ ] }) }),
1279
+ /* @__PURE__ */ jsx(PopoverContent, { align: "start", className: "w-64 p-1.5", children: KINDS2.map((kind) => {
1280
+ const Icon = STEP_KIND_ICONS[kind];
1281
+ const cls = { ...DEFAULT_WORKFLOW_CANVAS_LABELS, ...canvasLabels };
1282
+ return /* @__PURE__ */ jsxs(
1283
+ "button",
1284
+ {
1285
+ type: "button",
1286
+ onClick: () => {
1287
+ setAdding(false);
1288
+ add(kind);
1289
+ },
1290
+ className: cn(
1291
+ `wf-kind-${kind}`,
1292
+ "flex w-full items-center gap-2.5 rounded-md px-2 py-1.5 text-left hover:bg-muted"
1293
+ ),
1294
+ "data-testid": `${testId}-add-${kind}`,
1295
+ children: [
1296
+ /* @__PURE__ */ jsx("span", { className: "wf-badge flex size-7 shrink-0 items-center justify-center rounded-md", children: /* @__PURE__ */ jsx(Icon, { className: "size-4" }) }),
1297
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
1298
+ /* @__PURE__ */ jsx("span", { className: "block text-sm font-medium", children: cls[kind] }),
1299
+ /* @__PURE__ */ jsx("span", { className: "block text-xs text-muted-foreground", children: cls.kindHints[kind] })
1300
+ ] })
1301
+ ]
1302
+ },
1303
+ kind
1304
+ );
1305
+ }) })
1306
+ ] }),
1307
+ /* @__PURE__ */ jsxs(
1308
+ Button,
1309
+ {
1310
+ variant: "ghost",
1311
+ size: "sm",
1312
+ onClick: () => workflow.run.kind === "steps" && setWorkflow({ ...workflow, run: { ...workflow.run, layout: autoLayout(steps) } }),
1313
+ "data-testid": `${testId}-tidy`,
1314
+ children: [
1315
+ /* @__PURE__ */ jsx(LayoutGrid, { className: "size-4" }),
1316
+ " ",
1317
+ l.tidy
1318
+ ]
1319
+ }
1320
+ )
1321
+ ] }),
1322
+ actions && /* @__PURE__ */ jsx("div", { className: "ml-auto flex flex-wrap items-center gap-2", children: actions })
1323
+ ] }),
1324
+ problems.length > 0 && /* @__PURE__ */ jsx("ul", { className: "space-y-1 text-sm text-destructive", "data-testid": `${testId}-problems`, children: problems.map((p, i) => /* @__PURE__ */ jsx("li", { children: l.problems[p.code].replace(
1325
+ "{step}",
1326
+ steps.find((s) => s.id === p.step)?.title ?? p.step
1327
+ ) }, i)) }),
1328
+ mode === "design" ? /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1329
+ /* @__PURE__ */ jsx(
1330
+ WorkflowCanvas,
1331
+ {
1332
+ workflow,
1333
+ onChange: setWorkflow,
1334
+ selected,
1335
+ onSelect: setSelected,
1336
+ focus: selected ?? (panelOpen ? TRIGGER_NODE : null),
1337
+ onAddAfter: addAfter,
1338
+ trigger: { title: l.start, summary },
1339
+ workflowTitle: (id) => titles.get(id),
1340
+ renderIcon,
1341
+ labels: canvasLabels,
1342
+ reserveRight: panelOpen ? PANEL_WIDTH : 0,
1343
+ testId: `${testId}-canvas`
1344
+ }
1345
+ ),
1346
+ panelOpen ? /* @__PURE__ */ jsxs(
1347
+ "div",
1348
+ {
1349
+ className: "absolute bottom-3 right-3 top-3 z-10 w-80 max-w-[calc(100%-1.5rem)] overflow-y-auto rounded-xl border border-border bg-card/95 p-4 pt-10 shadow-lg backdrop-blur",
1350
+ "data-testid": `${testId}-panel`,
1351
+ children: [
1352
+ /* @__PURE__ */ jsx(
1353
+ Button,
1354
+ {
1355
+ size: "icon",
1356
+ variant: "ghost",
1357
+ className: "absolute right-2 top-2 size-7",
1358
+ onClick: () => setPanelOpen(false),
1359
+ "aria-label": l.closePanel,
1360
+ "data-testid": `${testId}-panel-close`,
1361
+ children: /* @__PURE__ */ jsx(X, { className: "size-4" })
1362
+ }
1363
+ ),
1364
+ selected === TRIGGER_NODE ? /* @__PURE__ */ jsx(
1365
+ TriggerInspector,
1366
+ {
1367
+ trigger,
1368
+ onChange: (next) => onChange({ ...value, trigger: next }),
1369
+ agents,
1370
+ eventKinds,
1371
+ tasks,
1372
+ labels: triggerLabels,
1373
+ testId: `${testId}-trigger`
1374
+ }
1375
+ ) : /* @__PURE__ */ jsx(
1376
+ StepInspector,
1377
+ {
1378
+ workflow,
1379
+ stepId: selected,
1380
+ onChange: setWorkflow,
1381
+ onSelect: setSelected,
1382
+ workflows,
1383
+ labels: inspectorLabels,
1384
+ testId: `${testId}-inspector`
1385
+ }
1386
+ )
1387
+ ]
1388
+ }
1389
+ ) : /* @__PURE__ */ jsxs(
1390
+ Button,
1391
+ {
1392
+ size: "sm",
1393
+ variant: "outline",
1394
+ className: "absolute right-3 top-3 z-10 bg-card shadow-sm",
1395
+ onClick: () => setPanelOpen(true),
1396
+ "data-testid": `${testId}-panel-open`,
1397
+ children: [
1398
+ /* @__PURE__ */ jsx(Settings2, { className: "size-4" }),
1399
+ " ",
1400
+ l.settings
1401
+ ]
1402
+ }
1403
+ )
1404
+ ] }) : /* @__PURE__ */ jsx(
1405
+ WorkflowPreview,
1406
+ {
1407
+ workflow,
1408
+ workflows,
1409
+ compose,
1410
+ answer: l.previewAnswer,
1411
+ renderIcon,
1412
+ testId: `${testId}-preview`
1413
+ },
1414
+ JSON.stringify(workflow)
1415
+ )
1416
+ ] });
1417
+ }
1418
+ function WorkflowPreview({
1419
+ workflow,
1420
+ workflows,
1421
+ compose,
1422
+ answer,
1423
+ renderIcon,
1424
+ testId
1425
+ }) {
1426
+ const run = useWorkflowPreview(workflow, {
1427
+ resolveWorkflow: (id) => workflows.find((w) => w.id === id),
1428
+ compose,
1429
+ answer
1430
+ });
1431
+ return /* @__PURE__ */ jsx(
1432
+ WorkflowRunner,
1433
+ {
1434
+ workflow,
1435
+ run,
1436
+ renderIcon,
1437
+ testIdPrefix: testId,
1438
+ renderAgentOutput: (step, state) => state?.text ? /* @__PURE__ */ jsx(
1439
+ "pre",
1440
+ {
1441
+ className: "whitespace-pre-wrap rounded-md bg-muted/40 p-3 text-xs text-muted-foreground",
1442
+ "data-testid": `${testId}-${step.id}-answer`,
1443
+ children: state.text
1444
+ }
1445
+ ) : null
1446
+ }
1447
+ );
1448
+ }
1449
+
1450
+ // src/workflow-canvas/trigger.ts
1451
+ var DEFAULT_WORKFLOW_TRIGGER = {
1452
+ start: "manual",
1453
+ runAt: "",
1454
+ rrule: "",
1455
+ timezone: typeof Intl !== "undefined" ? Intl.DateTimeFormat().resolvedOptions().timeZone : "UTC",
1456
+ eventKind: "",
1457
+ afterTaskId: "",
1458
+ agentId: "",
1459
+ access: "ask_each_run"
1460
+ };
1461
+ function triggerProblem(trigger) {
1462
+ if (trigger.start === "once" && !trigger.runAt) return "runAt";
1463
+ if (trigger.start === "repeat" && !trigger.rrule.trim()) return "rrule";
1464
+ if (trigger.start === "event" && !trigger.eventKind.trim()) return "event";
1465
+ if (trigger.start === "task" && !trigger.afterTaskId) return "task";
1466
+ return null;
1467
+ }
1468
+ function triggerFields(trigger, clearOthers) {
1469
+ const none = clearOthers ? null : void 0;
1470
+ return {
1471
+ timezone: trigger.timezone,
1472
+ rrule: trigger.start === "repeat" ? trigger.rrule.trim() : none,
1473
+ run_at: trigger.start === "once" ? localInputToIso(trigger.runAt) : none,
1474
+ event_kind: trigger.start === "event" ? trigger.eventKind.trim() : none,
1475
+ after_conversation_ext_id: trigger.start === "task" ? trigger.afterTaskId : none,
1476
+ on_demand: trigger.start === "manual"
1477
+ };
1478
+ }
1479
+ var authMode = (trigger) => trigger.start === "manual" ? "run_now_temp" : trigger.access === "standing" ? "scheduled_permanent" : "scheduled_require_auth";
1480
+ var withoutUndefined = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
1481
+ function plan(value, options) {
1482
+ return {
1483
+ goal: value.workflow.description,
1484
+ criteria: [],
1485
+ max_passes: 3,
1486
+ documents: [],
1487
+ steps: toPlanSteps(value.workflow, options)
1488
+ };
1489
+ }
1490
+ function builderToWorkflowRequest(value, workspaceId, options = {}) {
1491
+ return withoutUndefined({
1492
+ workspace_ext_id: workspaceId,
1493
+ name: value.workflow.title,
1494
+ description: value.workflow.description,
1495
+ active: true,
1496
+ shared: true,
1497
+ ...triggerFields(value.trigger, false),
1498
+ prompt: "",
1499
+ skill_ref: null,
1500
+ skill_inputs: null,
1501
+ agent_ext_id: value.trigger.agentId || null,
1502
+ auth_mode: authMode(value.trigger),
1503
+ plan: plan(value, options)
1504
+ });
1505
+ }
1506
+ function builderToWorkflowUpdate(value, options = {}) {
1507
+ return withoutUndefined({
1508
+ name: value.workflow.title,
1509
+ description: value.workflow.description,
1510
+ ...triggerFields(value.trigger, true),
1511
+ // The steps are what runs (a classic automation's single prompt or skill becomes its one step).
1512
+ prompt: "",
1513
+ skill_ref: null,
1514
+ skill_inputs: null,
1515
+ agent_ext_id: value.trigger.agentId || null,
1516
+ auth_mode: authMode(value.trigger),
1517
+ plan: plan(value, options)
1518
+ });
1519
+ }
1520
+ function workflowRecordToBuilder(record) {
1521
+ const planned = record.plan?.steps ?? [];
1522
+ const steps = planned.length ? fromPlanSteps(planned) : record.prompt || record.skill_ref ? [
1523
+ {
1524
+ id: "work",
1525
+ kind: "agent",
1526
+ title: record.name,
1527
+ ...record.skill_ref ? { workflow: record.skill_ref } : { run: { kind: "prompt", promptTemplate: record.prompt } }
1528
+ }
1529
+ ] : [];
1530
+ const when = workflowWhen(record);
1531
+ return {
1532
+ workflow: {
1533
+ id: record.external_id,
1534
+ title: record.name,
1535
+ description: record.description ?? "",
1536
+ inputs: [],
1537
+ landing: "chat",
1538
+ schedulable: false,
1539
+ run: { kind: "steps", steps, layout: layoutFromPlanSteps(planned ?? []) }
1540
+ },
1541
+ trigger: {
1542
+ ...DEFAULT_WORKFLOW_TRIGGER,
1543
+ start: when === "now" ? "manual" : when,
1544
+ runAt: isoToLocalInput(record.run_at),
1545
+ rrule: record.rrule ?? "",
1546
+ timezone: record.timezone || DEFAULT_WORKFLOW_TRIGGER.timezone,
1547
+ eventKind: record.event_kind ?? "",
1548
+ afterTaskId: record.after_conversation_ext_id ?? "",
1549
+ agentId: record.agent_ext_id ?? "",
1550
+ access: record.auth_mode === "scheduled_permanent" ? "standing" : "ask_each_run"
1551
+ }
1552
+ };
1553
+ }
1554
+ var DEFAULT_CONNECTED_WORKFLOW_BUILDER_LABELS = {
1555
+ save: "Save",
1556
+ saving: "Saving\u2026",
1557
+ runNow: "Run now",
1558
+ saved: "Saved.",
1559
+ needTrigger: "Finish setting when it starts."
1560
+ };
1561
+ var DEFAULT_WORKFLOW_EVENT_KINDS = [
1562
+ { value: "conversation_done", label: "A task is marked done" },
1563
+ { value: "conversation_ready", label: "Work is ready for me" },
1564
+ { value: "mention", label: "Someone mentions me" },
1565
+ { value: "workflow_completed", label: "Another workflow finishes" },
1566
+ { value: "workflow_failed", label: "Another workflow fails" }
1567
+ ];
1568
+ function ConnectedWorkflowBuilder({
1569
+ workflowId,
1570
+ initial,
1571
+ workflows: hostWorkflows = [],
1572
+ eventKinds = DEFAULT_WORKFLOW_EVENT_KINDS,
1573
+ compose,
1574
+ onSaved,
1575
+ onRun,
1576
+ actions,
1577
+ connectedLabels,
1578
+ testId = "workflow-builder",
1579
+ ...builderProps
1580
+ }) {
1581
+ const l = { ...DEFAULT_CONNECTED_WORKFLOW_BUILDER_LABELS, ...connectedLabels };
1582
+ const { workspaceId } = useConnection();
1583
+ const saved = useWorkflows();
1584
+ const skills = useSkills();
1585
+ const agentsQuery = useAgents();
1586
+ const conversations = useConversations(workspaceId ?? void 0);
1587
+ const create = useCreateWorkflow();
1588
+ const update = useUpdateWorkflow();
1589
+ const run = useRunWorkflow();
1590
+ const [id, setId] = useState(workflowId);
1591
+ const [value, setValue] = useState(initial);
1592
+ const [message, setMessage] = useState(null);
1593
+ const record = saved.data?.find((w) => w.external_id === id);
1594
+ const [loaded, setLoaded] = useState(void 0);
1595
+ useEffect(() => {
1596
+ if (record && loaded !== record.external_id) {
1597
+ setValue(workflowRecordToBuilder(record));
1598
+ setLoaded(record.external_id);
1599
+ }
1600
+ }, [record, loaded]);
1601
+ const workflows = useMemo(
1602
+ () => [
1603
+ ...hostWorkflows,
1604
+ ...(skills.data ?? []).map(
1605
+ (s) => skillToWorkflow({
1606
+ name: s.name,
1607
+ slug: s.slug,
1608
+ description: s.description,
1609
+ arg_hint: s.arg_hint
1610
+ })
1611
+ )
1612
+ ],
1613
+ [hostWorkflows, skills.data]
1614
+ );
1615
+ const agents = (agentsQuery.data ?? []).map((a) => ({
1616
+ id: a.external_id,
1617
+ label: a.given_name || a.email
1618
+ }));
1619
+ const tasks = (conversations.data ?? []).filter((c) => c.status && c.status !== "done").map((c) => ({ value: c.external_id, label: c.title || c.external_id }));
1620
+ const options = { resolveWorkflow: (wid) => workflows.find((w) => w.id === wid), compose };
1621
+ const problem = triggerProblem(value.trigger) ?? (validateWorkflowGraph(workflowSteps(value.workflow)).length ? "graph" : null);
1622
+ const busy = create.isPending || update.isPending || run.isPending;
1623
+ const save = async () => {
1624
+ if (!workspaceId) return void 0;
1625
+ setMessage(null);
1626
+ const result = id ? await update.mutateAsync({ workflowId: id, patch: builderToWorkflowUpdate(value, options) }) : await create.mutateAsync(builderToWorkflowRequest(value, workspaceId, options));
1627
+ setId(result.external_id);
1628
+ setLoaded(result.external_id);
1629
+ setMessage(l.saved);
1630
+ onSaved?.(result.external_id);
1631
+ return result.external_id;
1632
+ };
1633
+ return /* @__PURE__ */ jsx(
1634
+ WorkflowBuilder,
1635
+ {
1636
+ ...builderProps,
1637
+ value,
1638
+ onChange: setValue,
1639
+ workflows,
1640
+ agents,
1641
+ eventKinds,
1642
+ tasks,
1643
+ compose,
1644
+ testId,
1645
+ actions: /* @__PURE__ */ jsxs(Fragment, { children: [
1646
+ actions,
1647
+ message && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: message }),
1648
+ problem && problem !== "graph" && /* @__PURE__ */ jsx("span", { className: "text-xs text-destructive", children: l.needTrigger }),
1649
+ /* @__PURE__ */ jsxs(
1650
+ Button,
1651
+ {
1652
+ variant: "outline",
1653
+ size: "sm",
1654
+ disabled: !!problem || busy || !workspaceId,
1655
+ onClick: async () => {
1656
+ const saved2 = await save();
1657
+ if (!saved2) return;
1658
+ const started = await run.mutateAsync(saved2);
1659
+ onRun?.(started.conversation_ext_id ?? void 0);
1660
+ },
1661
+ "data-testid": `${testId}-run`,
1662
+ children: [
1663
+ /* @__PURE__ */ jsx(Play, { className: "size-4" }),
1664
+ " ",
1665
+ l.runNow
1666
+ ]
1667
+ }
1668
+ ),
1669
+ /* @__PURE__ */ jsxs(
1670
+ Button,
1671
+ {
1672
+ size: "sm",
1673
+ disabled: !!problem || busy || !workspaceId,
1674
+ onClick: () => void save(),
1675
+ "data-testid": `${testId}-save`,
1676
+ children: [
1677
+ create.isPending || update.isPending ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx(Save, { className: "size-4" }),
1678
+ create.isPending || update.isPending ? l.saving : l.save
1679
+ ]
1680
+ }
1681
+ )
1682
+ ] })
1683
+ }
1684
+ );
1685
+ }
1686
+
1687
+ export { ConnectedWorkflowBuilder, DEFAULT_CONNECTED_WORKFLOW_BUILDER_LABELS, DEFAULT_STEP_INSPECTOR_LABELS, DEFAULT_TRIGGER_INSPECTOR_LABELS, DEFAULT_WORKFLOW_BUILDER_LABELS, DEFAULT_WORKFLOW_CANVAS_LABELS, DEFAULT_WORKFLOW_EVENT_KINDS, DEFAULT_WORKFLOW_TRIGGER, StepInspector, TRIGGER_NODE, TriggerInspector, WorkflowBuilder, WorkflowCanvas, addStep, autoLayout, builderToWorkflowRequest, builderToWorkflowUpdate, connectSteps, disconnectSteps, layoutWithDefaults, moveStep, newSteppedWorkflow, removeStep, stepIdFor, triggerProblem, triggerSummary, updateStep, workflowRecordToBuilder, wouldCycle };
1688
+ //# sourceMappingURL=index.js.map
1689
+ //# sourceMappingURL=index.js.map