@arbidocs/blocks 0.4.72 → 0.4.73

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