@arbidocs/blocks 0.4.76 → 0.4.78

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 (123) hide show
  1. package/dist/agents/index.cjs +346 -258
  2. package/dist/agents/index.js +5 -5
  3. package/dist/agents/tasks/TaskActivityTimeline.d.ts +1 -1
  4. package/dist/agents/tasks/WorkflowThreadStrip.d.ts +15 -4
  5. package/dist/agents/workflows/SchemaForm.d.ts +6 -1
  6. package/dist/agents/workflows/StepBarFrame.d.ts +75 -0
  7. package/dist/agents/workflows/StepParts.d.ts +109 -0
  8. package/dist/agents/workflows/WorkflowRunner.d.ts +15 -2
  9. package/dist/agents/workflows/WorkflowStepBar.d.ts +9 -11
  10. package/dist/agents/workflows/index.d.ts +5 -2
  11. package/dist/agents/workflows/planSteps.d.ts +8 -1
  12. package/dist/agents/workflows/run.d.ts +61 -8
  13. package/dist/agents/workflows/stepWindow.d.ts +59 -0
  14. package/dist/agents/workflows/types.d.ts +133 -4
  15. package/dist/agents/workflows/useWorkflowPreview.d.ts +2 -1
  16. package/dist/agents/workflows/useWorkflowView.d.ts +19 -0
  17. package/dist/chat/ChatPage.d.ts +1 -1
  18. package/dist/chat/WorkflowStepChip.d.ts +24 -0
  19. package/dist/chat/chat-page-types.d.ts +9 -0
  20. package/dist/chat/index.cjs +130 -122
  21. package/dist/chat/index.d.ts +1 -0
  22. package/dist/chat/index.js +2 -2
  23. package/dist/{chunk-TKB7TVMW.js → chunk-5OC5CGYX.js} +168 -100
  24. package/dist/chunk-5OC5CGYX.js.map +1 -0
  25. package/dist/{chunk-ZWA7UUZL.cjs → chunk-7YUFMNFW.cjs} +2 -54
  26. package/dist/chunk-7YUFMNFW.cjs.map +1 -0
  27. package/dist/chunk-A24C6BNA.js +92 -0
  28. package/dist/chunk-A24C6BNA.js.map +1 -0
  29. package/dist/chunk-AINNADMT.cjs +62 -0
  30. package/dist/chunk-AINNADMT.cjs.map +1 -0
  31. package/dist/chunk-CWAZNXC5.js +441 -0
  32. package/dist/chunk-CWAZNXC5.js.map +1 -0
  33. package/dist/{chunk-UIWLDSTF.js → chunk-EWSEQEPL.js} +71 -7
  34. package/dist/chunk-EWSEQEPL.js.map +1 -0
  35. package/dist/chunk-FE4ULGOR.js +151 -0
  36. package/dist/chunk-FE4ULGOR.js.map +1 -0
  37. package/dist/chunk-FT5FFXSJ.js +60 -0
  38. package/dist/chunk-FT5FFXSJ.js.map +1 -0
  39. package/dist/{chunk-WJEKRSPH.js → chunk-ICBAFX5B.js} +3 -3
  40. package/dist/{chunk-WJEKRSPH.js.map → chunk-ICBAFX5B.js.map} +1 -1
  41. package/dist/{chunk-7CVF3DK2.cjs → chunk-IF6VAYIA.cjs} +5 -5
  42. package/dist/{chunk-7CVF3DK2.cjs.map → chunk-IF6VAYIA.cjs.map} +1 -1
  43. package/dist/chunk-MLAHDAN5.cjs +450 -0
  44. package/dist/chunk-MLAHDAN5.cjs.map +1 -0
  45. package/dist/{chunk-IPGKTUF7.cjs → chunk-MP6FK43E.cjs} +71 -5
  46. package/dist/chunk-MP6FK43E.cjs.map +1 -0
  47. package/dist/{chunk-5EBGRG3X.js → chunk-OPA2Y6YZ.js} +3 -54
  48. package/dist/chunk-OPA2Y6YZ.js.map +1 -0
  49. package/dist/{chunk-7FKARMA3.cjs → chunk-SSDISIDW.cjs} +190 -122
  50. package/dist/chunk-SSDISIDW.cjs.map +1 -0
  51. package/dist/chunk-TZR36PPP.cjs +95 -0
  52. package/dist/chunk-TZR36PPP.cjs.map +1 -0
  53. package/dist/chunk-VFIZYSZB.cjs +2783 -0
  54. package/dist/chunk-VFIZYSZB.cjs.map +1 -0
  55. package/dist/chunk-WXRCMIAE.js +2707 -0
  56. package/dist/chunk-WXRCMIAE.js.map +1 -0
  57. package/dist/{chunk-HITAN7JF.cjs → chunk-XMBYDLZQ.cjs} +4 -4
  58. package/dist/{chunk-HITAN7JF.cjs.map → chunk-XMBYDLZQ.cjs.map} +1 -1
  59. package/dist/{chunk-MK6NJQGF.js → chunk-XQDM6T6C.js} +3 -3
  60. package/dist/{chunk-MK6NJQGF.js.map → chunk-XQDM6T6C.js.map} +1 -1
  61. package/dist/chunk-ZVBPRMHJ.cjs +153 -0
  62. package/dist/chunk-ZVBPRMHJ.cjs.map +1 -0
  63. package/dist/connect/docx.cjs +10 -88
  64. package/dist/connect/docx.cjs.map +1 -1
  65. package/dist/connect/docx.js +6 -86
  66. package/dist/connect/docx.js.map +1 -1
  67. package/dist/connect/index.cjs +468 -49
  68. package/dist/connect/index.cjs.map +1 -1
  69. package/dist/connect/index.d.ts +2 -0
  70. package/dist/connect/index.js +454 -37
  71. package/dist/connect/index.js.map +1 -1
  72. package/dist/connection/ConnectedTagMatrix.d.ts +20 -0
  73. package/dist/connection/ConnectedWorkflowRunner.d.ts +28 -2
  74. package/dist/connection/WorkflowRunThread.d.ts +21 -0
  75. package/dist/connection/WorkflowRunnerDialogs.d.ts +37 -0
  76. package/dist/connection/useWorkflowThread.d.ts +12 -0
  77. package/dist/document-tools/index.cjs +7 -143
  78. package/dist/document-tools/index.cjs.map +1 -1
  79. package/dist/document-tools/index.js +5 -144
  80. package/dist/document-tools/index.js.map +1 -1
  81. package/dist/docx/index.cjs +18 -17
  82. package/dist/docx/index.cjs.map +1 -1
  83. package/dist/docx/index.js +3 -2
  84. package/dist/docx/index.js.map +1 -1
  85. package/dist/facts/index.cjs +1 -1
  86. package/dist/facts/index.js +1 -1
  87. package/dist/files/index.cjs +1 -1
  88. package/dist/files/index.js +1 -1
  89. package/dist/home/index.cjs +1 -1
  90. package/dist/home/index.js +1 -1
  91. package/dist/index.cjs +560 -431
  92. package/dist/index.cjs.map +1 -1
  93. package/dist/index.d.ts +1 -0
  94. package/dist/index.js +10 -9
  95. package/dist/index.js.map +1 -1
  96. package/dist/shell/index.cjs +1 -1
  97. package/dist/shell/index.js +1 -1
  98. package/dist/studio/index.cjs +4 -4
  99. package/dist/studio/index.js +3 -3
  100. package/dist/tags/TagMatrix.d.ts +64 -0
  101. package/dist/tags/index.d.ts +3 -0
  102. package/dist/tags/tagMatrixModel.d.ts +39 -0
  103. package/dist/workflow-canvas/StepActionSettings.d.ts +132 -0
  104. package/dist/workflow-canvas/StepInspector.d.ts +25 -2
  105. package/dist/workflow-canvas/WorkflowBuilder.d.ts +11 -3
  106. package/dist/workflow-canvas/WorkflowCanvas.d.ts +27 -2
  107. package/dist/workflow-canvas/edit.d.ts +9 -1
  108. package/dist/workflow-canvas/index.cjs +882 -102
  109. package/dist/workflow-canvas/index.cjs.map +1 -1
  110. package/dist/workflow-canvas/index.js +849 -69
  111. package/dist/workflow-canvas/index.js.map +1 -1
  112. package/package.json +4 -4
  113. package/src/workflow-canvas/canvas.css +9 -0
  114. package/dist/chunk-22P5KNIM.js +0 -1422
  115. package/dist/chunk-22P5KNIM.js.map +0 -1
  116. package/dist/chunk-5EBGRG3X.js.map +0 -1
  117. package/dist/chunk-7FKARMA3.cjs.map +0 -1
  118. package/dist/chunk-IPGKTUF7.cjs.map +0 -1
  119. package/dist/chunk-TKB7TVMW.js.map +0 -1
  120. package/dist/chunk-UIWLDSTF.js.map +0 -1
  121. package/dist/chunk-VYRKO3XU.cjs +0 -1469
  122. package/dist/chunk-VYRKO3XU.cjs.map +0 -1
  123. package/dist/chunk-ZWA7UUZL.cjs.map +0 -1
@@ -0,0 +1,2707 @@
1
+ "use client";
2
+ import { FoldSection } from './chunk-XD2M6LIG.js';
3
+ import { UploadDropzone } from './chunk-EVUEIBNU.js';
4
+ import { useElementSize } from './chunk-HOXKR26S.js';
5
+ import { testIdOr, prefixedTestId } from './chunk-7F6CBP3Z.js';
6
+ import { useState, useRef, useEffect, useCallback, useMemo } from 'react';
7
+ import { cn, Popover, PopoverTrigger, PopoverContent, Button, Textarea, Card, Label, Input, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Switch } from '@arbidocs/react/ui';
8
+ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
9
+ import { Check, Loader2, Hourglass, AlertTriangle, MinusCircle, UserRound, CornerUpLeft, ShieldCheck, MessageSquare, Clock, PenLine, X, ExternalLink, Mail, Table2, CheckCircle2, ArrowLeft, ArrowRight, RotateCw, GitBranch, ChevronLeft, ChevronRight, FileText } from 'lucide-react';
10
+
11
+ // src/agents/workflows/types.ts
12
+ var EMPTY_WORKFLOW_SCOPE = { folders: [], labels: [] };
13
+
14
+ // src/agents/workflows/workflow.ts
15
+ var isWorkflowScope = (value) => typeof value === "object" && value !== null && Array.isArray(value.folders) && Array.isArray(value.labels);
16
+ var isEmptyWorkflowScope = (scope) => scope.folders.length === 0 && scope.labels.length === 0 && !scope.documents?.length;
17
+ var isFileField = (field) => field.type === "scope" || field.type === "documents";
18
+ function emptyValue(field) {
19
+ switch (field.type) {
20
+ case "boolean":
21
+ return false;
22
+ case "scope":
23
+ return { folders: [], labels: [] };
24
+ case "documents":
25
+ return { folders: [], labels: [], documents: [] };
26
+ default:
27
+ return "";
28
+ }
29
+ }
30
+ function defaultInputs(workflow) {
31
+ const inputs = {};
32
+ for (const field of workflow.inputs) {
33
+ const d = field.default;
34
+ inputs[field.name] = d === void 0 ? emptyValue(field) : isWorkflowScope(d) ? { ...d } : d;
35
+ }
36
+ return inputs;
37
+ }
38
+ function validateInputs(workflow, inputs) {
39
+ const errors = {};
40
+ for (const field of workflow.inputs) {
41
+ const value = inputs[field.name];
42
+ if (field.type === "number") {
43
+ const blank = value === "" || value === void 0;
44
+ if (blank) {
45
+ if (field.required) errors[field.name] = "required";
46
+ continue;
47
+ }
48
+ const n = Number(value);
49
+ if (Number.isNaN(n)) errors[field.name] = "number";
50
+ else if (field.min !== void 0 && n < field.min) errors[field.name] = "min";
51
+ else if (field.max !== void 0 && n > field.max) errors[field.name] = "max";
52
+ continue;
53
+ }
54
+ if (!field.required) continue;
55
+ if (field.type === "documents") {
56
+ if (!isWorkflowScope(value) || !value.documents?.length) errors[field.name] = "required";
57
+ continue;
58
+ }
59
+ if (field.type === "boolean" || field.type === "scope") continue;
60
+ if (typeof value !== "string" || value.trim() === "") errors[field.name] = "required";
61
+ }
62
+ return errors;
63
+ }
64
+ function promptFieldName(workflow) {
65
+ return workflow.inputs.find((f) => !f.advanced && (f.type === "text" || f.type === "longtext"))?.name;
66
+ }
67
+ var quoteList = (names) => {
68
+ const quoted = names.map((n) => `"${n}"`);
69
+ return quoted.length <= 1 ? quoted[0] ?? "" : `${quoted.slice(0, -1).join(", ")} and ${quoted[quoted.length - 1]}`;
70
+ };
71
+ function fragment(field, value, ctx) {
72
+ switch (field.type) {
73
+ case "documents": {
74
+ const ids = isWorkflowScope(value) ? value.documents ?? [] : [];
75
+ const names = ids.flatMap((id) => {
76
+ const title = ctx?.documentTitles?.[id];
77
+ return title ? [title] : [];
78
+ });
79
+ return quoteList(names);
80
+ }
81
+ case "boolean":
82
+ return value ? field.trueText ?? "yes" : field.falseText ?? "no";
83
+ case "select": {
84
+ const option = field.options?.find((o) => o.value === value);
85
+ return option ? option.text ?? option.label : String(value ?? "");
86
+ }
87
+ case "scope":
88
+ return "";
89
+ default:
90
+ return typeof value === "string" ? value.trim() : String(value ?? "");
91
+ }
92
+ }
93
+ function composeWorkflowPrompt(workflow, inputs, ctx) {
94
+ if (workflow.run.kind !== "prompt") return "";
95
+ const byName = new Map(workflow.inputs.map((f) => [f.name, f]));
96
+ const template = workflow.run.promptTemplate;
97
+ let out = "";
98
+ let last = 0;
99
+ const append = (text, isValue) => {
100
+ let t = text;
101
+ if (!isValue) {
102
+ t = t.replace(/[ \t]{2,}/g, " ");
103
+ if (/[ \t]$/.test(out)) t = t.replace(/^[ \t]+/, "");
104
+ }
105
+ out += t;
106
+ };
107
+ for (const m of template.matchAll(/\{\{\s*([a-z][a-z0-9_]*)\s*\}\}/gi)) {
108
+ append(template.slice(last, m.index), false);
109
+ const field = byName.get(m[1]);
110
+ append(field ? fragment(field, inputs[m[1]], ctx) : "", true);
111
+ last = m.index + m[0].length;
112
+ }
113
+ append(template.slice(last), false);
114
+ return out.trim();
115
+ }
116
+ function buildLaunch(workflow, inputs, agentId) {
117
+ const scopeField = workflow.inputs.find(isFileField);
118
+ const raw = scopeField ? inputs[scopeField.name] : void 0;
119
+ const scope = isWorkflowScope(raw) ? raw : { ...EMPTY_WORKFLOW_SCOPE };
120
+ return { workflow, inputs, scope, agentId: agentId || workflow.defaultAgentId || void 0 };
121
+ }
122
+ function workflowAccepts(workflow) {
123
+ const kinds = /* @__PURE__ */ new Set();
124
+ for (const field of workflow.inputs) if (field.context) kinds.add(field.context);
125
+ return [...kinds];
126
+ }
127
+ function workflowsAccepting(workflows, supplied) {
128
+ return workflows.filter((w) => workflowAccepts(w).some((k) => supplied.includes(k)));
129
+ }
130
+ function workflowsForHost(workflows, host) {
131
+ return workflows.filter((w) => !w.host || w.host === host);
132
+ }
133
+ function inputsFromContext(workflow, context) {
134
+ const inputs = {};
135
+ for (const field of workflow.inputs) {
136
+ if (!field.context) continue;
137
+ if (field.context === "selection") {
138
+ if (context.selection) inputs[field.name] = context.selection;
139
+ continue;
140
+ }
141
+ const ids = field.context === "image" ? context.image ? [context.image] : [] : context.documents;
142
+ if (ids?.length && isFileField(field)) {
143
+ inputs[field.name] = { folders: [], labels: [], documents: ids.slice(0, field.max ?? 1) };
144
+ }
145
+ }
146
+ return inputs;
147
+ }
148
+ var SKILL_PROMPT_LABEL = "What should it work on?";
149
+ var SKILL_PROMPT_FALLBACK_PLACEHOLDER = "Describe what you want";
150
+ var plainHint = (hint) => hint.replace(/^[<[]\s*|\s*[>\]]$/g, "").trim();
151
+ function skillToWorkflow(skill) {
152
+ const hint = plainHint((skill.arg_hint ?? "").trim());
153
+ const inputs = [
154
+ {
155
+ name: "args",
156
+ type: "longtext",
157
+ label: SKILL_PROMPT_LABEL,
158
+ placeholder: hint || SKILL_PROMPT_FALLBACK_PLACEHOLDER,
159
+ required: true
160
+ }
161
+ ];
162
+ return {
163
+ id: skill.slug,
164
+ skill: skill.slug,
165
+ title: skill.name || skill.slug,
166
+ description: skill.description ?? "",
167
+ inputs,
168
+ run: { kind: "prompt", promptTemplate: `/${skill.slug} {{args}}` },
169
+ landing: "chat",
170
+ schedulable: true
171
+ };
172
+ }
173
+ var FIELD_TYPES = [
174
+ "text",
175
+ "longtext",
176
+ "boolean",
177
+ "select",
178
+ "number",
179
+ "date",
180
+ "scope",
181
+ "documents"
182
+ ];
183
+ var str = (v) => typeof v === "string" ? v : void 0;
184
+ var num = (v) => typeof v === "number" ? v : void 0;
185
+ var bool = (v) => typeof v === "boolean" ? v : void 0;
186
+ function fieldFromFrontmatter(raw) {
187
+ if (typeof raw !== "object" || raw === null) return null;
188
+ const r = raw;
189
+ const name = str(r.name);
190
+ const type = FIELD_TYPES.find((t) => t === r.type);
191
+ if (!name || !/^[a-z][a-z0-9_]*$/.test(name) || !type) return null;
192
+ const options = Array.isArray(r.options) ? r.options.flatMap((o) => {
193
+ const ro = o;
194
+ const value = str(ro?.value);
195
+ const label = str(ro?.label);
196
+ return value && label ? [
197
+ {
198
+ value,
199
+ label,
200
+ description: str(ro.description),
201
+ text: str(ro.text),
202
+ icon: str(ro.icon)
203
+ }
204
+ ] : [];
205
+ }) : void 0;
206
+ return {
207
+ name,
208
+ type,
209
+ label: str(r.label) ?? name,
210
+ description: str(r.description),
211
+ placeholder: str(r.placeholder),
212
+ required: bool(r.required),
213
+ default: r.default,
214
+ options,
215
+ min: num(r.min),
216
+ max: num(r.max),
217
+ trueText: str(r.true_text),
218
+ falseText: str(r.false_text),
219
+ advanced: bool(r.advanced),
220
+ accept: r.accept === "image" ? "image" : void 0,
221
+ context: r.context === "documents" || r.context === "selection" || r.context === "image" ? r.context : void 0,
222
+ by: Array.isArray(r.by) ? r.by.filter((b) => b === "folders" || b === "labels") : void 0
223
+ };
224
+ }
225
+ function workflowFromFrontmatter(id, frontmatter, body) {
226
+ const inputs = Array.isArray(frontmatter.inputs) ? frontmatter.inputs.flatMap((f) => {
227
+ const field = fieldFromFrontmatter(f);
228
+ return field ? [field] : [];
229
+ }) : [];
230
+ return {
231
+ id,
232
+ title: str(frontmatter.name) ?? id,
233
+ description: str(frontmatter.description) ?? "",
234
+ icon: str(frontmatter.icon),
235
+ inputs,
236
+ run: { kind: "prompt", promptTemplate: body.trim() },
237
+ landing: frontmatter.landing === "files" ? "files" : "chat",
238
+ requiresFiles: bool(frontmatter.requires_files),
239
+ schedulable: bool(frontmatter.schedulable) ?? true
240
+ };
241
+ }
242
+ function fieldToFrontmatter(field) {
243
+ return compact({
244
+ name: field.name,
245
+ type: field.type,
246
+ label: field.label,
247
+ description: field.description,
248
+ placeholder: field.placeholder,
249
+ required: field.required,
250
+ default: field.default,
251
+ options: field.options?.map(
252
+ (o) => compact({
253
+ value: o.value,
254
+ label: o.label,
255
+ description: o.description,
256
+ text: o.text,
257
+ icon: o.icon
258
+ })
259
+ ),
260
+ min: field.min,
261
+ max: field.max,
262
+ true_text: field.trueText,
263
+ false_text: field.falseText,
264
+ advanced: field.advanced,
265
+ by: field.by,
266
+ accept: field.accept,
267
+ context: field.context
268
+ });
269
+ }
270
+ var compact = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0));
271
+ var DEFAULT_SCHEMA_FORM_LABELS = {
272
+ optional: "(optional)",
273
+ advanced: "More options",
274
+ errors: {
275
+ required: "This is needed to continue",
276
+ min: "Too small",
277
+ max: "Too large",
278
+ number: "Enter a number"
279
+ }
280
+ };
281
+ var TILE_MAX = 4;
282
+ function FieldControl({
283
+ field,
284
+ value,
285
+ error,
286
+ onChange,
287
+ l,
288
+ idFor,
289
+ renderScope,
290
+ renderDocuments,
291
+ renderIcon,
292
+ onEnterSubmit,
293
+ people
294
+ }) {
295
+ const id = `${idFor()}-control`;
296
+ const testId = idFor();
297
+ switch (field.type) {
298
+ case "text":
299
+ return /* @__PURE__ */ jsx(
300
+ Input,
301
+ {
302
+ id,
303
+ "data-testid": testId,
304
+ value: String(value ?? ""),
305
+ placeholder: field.placeholder,
306
+ "aria-required": field.required || void 0,
307
+ "aria-invalid": error ? true : void 0,
308
+ "aria-describedby": error ? idFor("error") : void 0,
309
+ onChange: (e) => onChange(e.target.value)
310
+ }
311
+ );
312
+ case "longtext":
313
+ return /* @__PURE__ */ jsx(
314
+ Textarea,
315
+ {
316
+ id,
317
+ "data-testid": testId,
318
+ value: String(value ?? ""),
319
+ placeholder: field.placeholder,
320
+ "aria-required": field.required || void 0,
321
+ "aria-invalid": error ? true : void 0,
322
+ "aria-describedby": error ? idFor("error") : void 0,
323
+ className: "min-h-[96px]",
324
+ onChange: (e) => onChange(e.target.value),
325
+ onKeyDown: (e) => {
326
+ if (onEnterSubmit && e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) {
327
+ e.preventDefault();
328
+ onEnterSubmit();
329
+ }
330
+ }
331
+ }
332
+ );
333
+ case "number":
334
+ return /* @__PURE__ */ jsx(
335
+ Input,
336
+ {
337
+ id,
338
+ "data-testid": testId,
339
+ type: "number",
340
+ inputMode: "decimal",
341
+ min: field.min,
342
+ max: field.max,
343
+ value: value === void 0 ? "" : String(value),
344
+ placeholder: field.placeholder,
345
+ "aria-invalid": error ? true : void 0,
346
+ "aria-describedby": error ? idFor("error") : void 0,
347
+ onChange: (e) => onChange(e.target.value === "" ? "" : Number(e.target.value))
348
+ }
349
+ );
350
+ case "date":
351
+ return /* @__PURE__ */ jsx(
352
+ Input,
353
+ {
354
+ id,
355
+ "data-testid": testId,
356
+ type: "date",
357
+ value: String(value ?? ""),
358
+ "aria-invalid": error ? true : void 0,
359
+ "aria-describedby": error ? idFor("error") : void 0,
360
+ onChange: (e) => onChange(e.target.value)
361
+ }
362
+ );
363
+ case "boolean":
364
+ return /* @__PURE__ */ jsx(
365
+ Switch,
366
+ {
367
+ id,
368
+ "data-testid": testId,
369
+ checked: Boolean(value),
370
+ onCheckedChange: (checked) => onChange(checked)
371
+ }
372
+ );
373
+ case "select": {
374
+ const options = field.options ?? [];
375
+ if (options.length > 0 && options.length <= TILE_MAX) {
376
+ return /* @__PURE__ */ jsx(
377
+ "div",
378
+ {
379
+ role: "radiogroup",
380
+ "aria-label": field.label,
381
+ className: "grid grid-cols-2 gap-2 sm:grid-cols-4",
382
+ "data-testid": testId,
383
+ children: options.map((option) => {
384
+ const selected = value === option.value;
385
+ return /* @__PURE__ */ jsxs(
386
+ "button",
387
+ {
388
+ type: "button",
389
+ role: "radio",
390
+ "aria-checked": selected,
391
+ "data-testid": prefixedTestId(testId, option.value, {
392
+ testId: `workflow-field-${field.name}-${option.value}`
393
+ }),
394
+ onClick: () => {
395
+ onChange(option.value);
396
+ },
397
+ className: cn(
398
+ "flex min-h-24 flex-col items-center justify-center gap-1.5 rounded-lg border p-3 text-center transition-colors pointer-coarse:min-h-28 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
399
+ selected ? "border-primary bg-primary/10" : "border-border bg-card [&:hover]:border-primary [&:hover]:bg-muted"
400
+ ),
401
+ children: [
402
+ option.icon && renderIcon && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: renderIcon(option.icon) }),
403
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: option.label }),
404
+ option.description && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: option.description })
405
+ ]
406
+ },
407
+ option.value
408
+ );
409
+ })
410
+ }
411
+ );
412
+ }
413
+ return /* @__PURE__ */ jsxs(Select, { value: String(value ?? ""), onValueChange: (v) => onChange(v), children: [
414
+ /* @__PURE__ */ jsx(SelectTrigger, { id, "data-testid": testId, className: "pointer-coarse:min-h-11", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: field.placeholder }) }),
415
+ /* @__PURE__ */ jsx(SelectContent, { children: options.map((o) => /* @__PURE__ */ jsx(SelectItem, { value: o.value, children: o.label }, o.value)) })
416
+ ] });
417
+ }
418
+ case "scope": {
419
+ const scope = isWorkflowScope(value) ? value : { folders: [], labels: [] };
420
+ return /* @__PURE__ */ jsx("div", { "data-testid": testId, children: renderScope?.(field, scope, onChange) });
421
+ }
422
+ case "documents": {
423
+ const picked = isWorkflowScope(value) ? value : { folders: [], labels: [], documents: [] };
424
+ return /* @__PURE__ */ jsx("div", { "data-testid": testId, children: renderDocuments?.(field, picked, onChange) });
425
+ }
426
+ case "person":
427
+ return people?.length ? /* @__PURE__ */ jsxs(
428
+ "select",
429
+ {
430
+ id,
431
+ "data-testid": testId,
432
+ value: String(value ?? ""),
433
+ "aria-required": field.required || void 0,
434
+ "aria-invalid": error ? true : void 0,
435
+ onChange: (e) => onChange(e.target.value),
436
+ className: "flex h-9 w-full rounded-md border border-input bg-background px-2 text-sm shadow-sm",
437
+ children: [
438
+ /* @__PURE__ */ jsx("option", { value: "", children: field.placeholder ?? "" }),
439
+ people.map((p) => /* @__PURE__ */ jsx("option", { value: p.value, children: p.label }, p.value))
440
+ ]
441
+ }
442
+ ) : /* @__PURE__ */ jsx(
443
+ Input,
444
+ {
445
+ id,
446
+ "data-testid": testId,
447
+ value: String(value ?? ""),
448
+ placeholder: field.placeholder ?? "usr-\u2026",
449
+ "aria-required": field.required || void 0,
450
+ "aria-invalid": error ? true : void 0,
451
+ onChange: (e) => onChange(e.target.value)
452
+ }
453
+ );
454
+ }
455
+ }
456
+ function FormField(props) {
457
+ const { field, error, l, idFor } = props;
458
+ const isBoolean = field.type === "boolean";
459
+ const showOptional = !field.required && field.type !== "boolean" && field.type !== "scope";
460
+ const label = /* @__PURE__ */ jsxs(
461
+ Label,
462
+ {
463
+ htmlFor: `${idFor()}-control`,
464
+ className: isBoolean ? "cursor-pointer font-normal" : void 0,
465
+ children: [
466
+ field.label,
467
+ showOptional && /* @__PURE__ */ jsxs(Fragment, { children: [
468
+ " ",
469
+ /* @__PURE__ */ jsx("span", { className: "font-normal text-muted-foreground", children: l.optional })
470
+ ] })
471
+ ]
472
+ }
473
+ );
474
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1.5", "data-slot": "schema-field", "data-field": field.name, children: [
475
+ isBoolean ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 pointer-coarse:min-h-11", children: [
476
+ /* @__PURE__ */ jsx(FieldControl, { ...props }),
477
+ label
478
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
479
+ field.type !== "scope" && label,
480
+ /* @__PURE__ */ jsx(FieldControl, { ...props })
481
+ ] }),
482
+ field.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: field.description }),
483
+ error && /* @__PURE__ */ jsx(
484
+ "p",
485
+ {
486
+ id: idFor("error"),
487
+ role: "alert",
488
+ className: "text-xs text-destructive",
489
+ "data-testid": idFor("error"),
490
+ children: l.errors[error]
491
+ }
492
+ )
493
+ ] });
494
+ }
495
+ function SchemaForm({
496
+ fields,
497
+ values,
498
+ onChange,
499
+ errors,
500
+ renderScope,
501
+ renderDocuments,
502
+ renderIcon,
503
+ onEnterSubmit,
504
+ people,
505
+ testIdPrefix,
506
+ labels,
507
+ className
508
+ }) {
509
+ const l = {
510
+ ...DEFAULT_SCHEMA_FORM_LABELS,
511
+ ...labels,
512
+ errors: { ...DEFAULT_SCHEMA_FORM_LABELS.errors, ...labels?.errors }
513
+ };
514
+ const [advancedOpen, setAdvancedOpen] = useState(false);
515
+ const common = fields.filter((f) => !f.advanced);
516
+ const advanced = fields.filter((f) => f.advanced);
517
+ const render = (field) => /* @__PURE__ */ jsx(
518
+ FormField,
519
+ {
520
+ field,
521
+ value: values[field.name],
522
+ error: errors?.[field.name],
523
+ onChange: (v) => onChange(field.name, v),
524
+ l,
525
+ idFor: (suffix) => suffix ? `${testIdOr(testIdPrefix, { testId: "workflow-field" })}-${field.name}-${suffix}` : `${testIdOr(testIdPrefix, { testId: "workflow-field" })}-${field.name}`,
526
+ renderScope,
527
+ renderDocuments,
528
+ renderIcon,
529
+ onEnterSubmit,
530
+ people
531
+ },
532
+ field.name
533
+ );
534
+ return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-4", className), "data-slot": "schema-form", children: [
535
+ common.map(render),
536
+ advanced.length > 0 && /* @__PURE__ */ jsx(
537
+ FoldSection,
538
+ {
539
+ label: l.advanced,
540
+ open: advancedOpen,
541
+ onOpenChange: setAdvancedOpen,
542
+ testId: `${testIdOr(testIdPrefix, { testId: "workflow-field" })}-advanced-toggle`,
543
+ children: /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-4", children: advanced.map(render) })
544
+ }
545
+ )
546
+ ] });
547
+ }
548
+
549
+ // src/agents/workflows/run.ts
550
+ function workflowSteps(workflow) {
551
+ return workflow.run.kind === "steps" ? workflow.run.steps : [];
552
+ }
553
+ var isSteppedWorkflow = (workflow) => workflow.run.kind === "steps";
554
+ function stepDependencies(steps, id) {
555
+ const i = steps.findIndex((s) => s.id === id);
556
+ if (i < 0) return [];
557
+ return steps[i].after ?? (i > 0 ? [steps[i - 1].id] : []);
558
+ }
559
+ function dependsOn(steps, a, b) {
560
+ const seen = /* @__PURE__ */ new Set();
561
+ const stack = [...stepDependencies(steps, a)];
562
+ while (stack.length) {
563
+ const id = stack.pop();
564
+ if (id === b) return true;
565
+ if (seen.has(id)) continue;
566
+ seen.add(id);
567
+ stack.push(...stepDependencies(steps, id));
568
+ }
569
+ return false;
570
+ }
571
+ function orderSteps(steps) {
572
+ const explicit = steps.map((s) => ({ ...s, after: stepDependencies(steps, s.id) }));
573
+ const known = new Set(explicit.map((s) => s.id));
574
+ const placed = /* @__PURE__ */ new Set();
575
+ const out = [];
576
+ let progress = true;
577
+ while (out.length < explicit.length && progress) {
578
+ progress = false;
579
+ for (const s of explicit) {
580
+ if (placed.has(s.id)) continue;
581
+ if (s.after.every((d) => placed.has(d) || !known.has(d))) {
582
+ out.push(s);
583
+ placed.add(s.id);
584
+ progress = true;
585
+ break;
586
+ }
587
+ }
588
+ }
589
+ return [...out, ...explicit.filter((s) => !placed.has(s.id))];
590
+ }
591
+ function validateWorkflowGraph(steps) {
592
+ const problems = [];
593
+ const ids = /* @__PURE__ */ new Set();
594
+ for (const s of steps) {
595
+ if (ids.has(s.id)) problems.push({ code: "duplicate", step: s.id });
596
+ ids.add(s.id);
597
+ }
598
+ steps.forEach((s, i) => {
599
+ for (const d of stepDependencies(steps, s.id)) {
600
+ if (!ids.has(d)) problems.push({ code: "unknown", step: s.id, dependency: d });
601
+ else if (d === s.id || dependsOn(steps, d, s.id)) problems.push({ code: "cycle", step: s.id });
602
+ else if (steps.findIndex((x) => x.id === d) > i)
603
+ problems.push({ code: "order", step: s.id, dependency: d });
604
+ }
605
+ });
606
+ return problems;
607
+ }
608
+ function stepLevels(steps) {
609
+ const depth = /* @__PURE__ */ new Map();
610
+ for (const s of steps) {
611
+ const deps = stepDependencies(steps, s.id).filter((d) => depth.has(d));
612
+ depth.set(s.id, deps.length ? Math.max(...deps.map((d) => depth.get(d))) + 1 : 0);
613
+ }
614
+ const levels = [];
615
+ for (const s of steps) (levels[depth.get(s.id)] ??= []).push(s);
616
+ return levels.filter(Boolean);
617
+ }
618
+ var STEP_EVENTS = {
619
+ step_started: "running",
620
+ step_waiting: "waiting",
621
+ step_done: "done",
622
+ step_skipped: "skipped",
623
+ step_failed: "failed"
624
+ };
625
+ var str2 = (v) => typeof v === "string" && v ? v : void 0;
626
+ var ACTION_TYPES = ["sign", "email_draft", "tabular_review"];
627
+ var asAction = (v) => ACTION_TYPES.includes(v) ? v : void 0;
628
+ var asRecord = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : void 0;
629
+ var asStrings = (v) => Array.isArray(v) ? v.filter((x) => typeof x === "string") : void 0;
630
+ function reopenedBy(detail) {
631
+ const to = str2(detail.to);
632
+ return asStrings(detail.reopened) ?? (to ? [to] : []);
633
+ }
634
+ function stepStatesFromActivity(events) {
635
+ const states = {};
636
+ for (const e of events) {
637
+ const detail = e.detail ?? {};
638
+ const step = str2(detail.step);
639
+ if (!step) continue;
640
+ if (e.kind === "step_sent_back") {
641
+ const to = str2(detail.to);
642
+ for (const id of reopenedBy(detail))
643
+ states[id] = id === to ? {
644
+ status: "pending",
645
+ sentBack: true,
646
+ hasNote: typeof detail.note_encrypted === "string"
647
+ } : { status: "pending" };
648
+ continue;
649
+ }
650
+ if (e.kind === "step_ready") {
651
+ states[step] = { ...states[step] ?? { status: "pending" }, announcedTo: str2(detail.to) };
652
+ continue;
653
+ }
654
+ if (e.kind === "step_progress") {
655
+ const before2 = states[step];
656
+ const waiting2 = asRecord(detail.waiting);
657
+ if (before2?.status === "waiting" && waiting2) states[step] = { ...before2, waiting: waiting2 };
658
+ continue;
659
+ }
660
+ const status = STEP_EVENTS[e.kind];
661
+ if (!status) continue;
662
+ const before = states[step];
663
+ const responseId = str2(detail.response_id) ?? before?.responseId ?? null;
664
+ const startedAt = status === "running" ? e.created_at : before?.startedAt;
665
+ const action = asAction(detail.action) ?? before?.action;
666
+ const documents = asStrings(detail.documents) ?? before?.documents;
667
+ const waiting = status === "waiting" ? asRecord(detail.waiting) : void 0;
668
+ const output = status === "done" ? asRecord(detail.output) : void 0;
669
+ const approved = Array.isArray(detail.approved) ? detail.approved.filter(asRecord) : void 0;
670
+ const finished2 = status === "done" || status === "skipped" || status === "failed";
671
+ const declinedBy = str2(detail.declined_by);
672
+ states[step] = {
673
+ status,
674
+ responseId,
675
+ error: str2(detail.error) ?? null,
676
+ ...startedAt ? { startedAt } : {},
677
+ ...action ? { action } : {},
678
+ ...documents?.length ? { documents } : {},
679
+ ...waiting ? { waiting } : {},
680
+ ...output ? { output } : {},
681
+ ...approved ? { approved } : {},
682
+ ...finished2 ? { by: e.actor_ext_id ?? null } : {},
683
+ ...finished2 && e.created_at ? { at: e.created_at } : {},
684
+ ...declinedBy ? { declinedBy } : {}
685
+ };
686
+ }
687
+ return states;
688
+ }
689
+ function stepFields(step) {
690
+ return step.kind === "form" ? step.inputs : step.kind === "review" ? step.fields ?? [] : [];
691
+ }
692
+ var runFieldsOf = (steps) => steps.flatMap(stepFields);
693
+ function runFields(workflow) {
694
+ return [...workflow.inputs, ...workflowSteps(workflow).flatMap(stepFields)];
695
+ }
696
+ function initialRunInputs(workflow, inputs = {}) {
697
+ return { ...defaultInputs({ ...workflow, inputs: runFields(workflow) }), ...inputs };
698
+ }
699
+ var finished = (s) => s?.status === "done" || s?.status === "skipped";
700
+ function stepPhase(steps, states, id) {
701
+ const s = states[id];
702
+ if (s && s.status !== "pending") return s.status;
703
+ return stepDependencies(steps, id).every((d) => finished(states[d])) ? "ready" : "blocked";
704
+ }
705
+ var isRunFinished = (steps, states) => steps.length > 0 && steps.every((s) => finished(states[s.id]));
706
+ var isPersonStep = (step) => step.kind !== "agent" && step.kind !== "action";
707
+ var isPersonAction = (step) => step.kind === "action" && (step.action.type === "sign" || step.action.type === "email_draft");
708
+ var canAssign = (step) => isPersonStep(step) || isPersonAction(step);
709
+ function nextAction(steps, states, current) {
710
+ const i = steps.findIndex((s) => s.id === current);
711
+ const step = steps[i];
712
+ if (!step) return "wait";
713
+ const phase = stepPhase(steps, states, step.id);
714
+ if (phase === "blocked") return "wait";
715
+ const following = steps[i + 1];
716
+ const independentNext = !!following && !dependsOn(steps, following.id, step.id);
717
+ if (phase === "done" || phase === "skipped") {
718
+ if (following) return "move";
719
+ return isRunFinished(steps, states) ? "finish" : "wait";
720
+ }
721
+ if (isPersonStep(step)) return "complete";
722
+ if (phase === "waiting" && step.kind === "action" && step.action.type === "email_draft")
723
+ return "complete";
724
+ if (phase !== "failed") return independentNext ? "move" : "wait";
725
+ const waitedFor = steps.slice(i + 1).some((s) => dependsOn(steps, s.id, step.id));
726
+ return waitedFor || !following ? "skip" : "move";
727
+ }
728
+ function validateStep(step, inputs) {
729
+ const fields = stepFields(step);
730
+ if (!fields.length) return {};
731
+ return validateInputs(
732
+ {
733
+ id: step.id,
734
+ title: step.title,
735
+ inputs: fields},
736
+ inputs
737
+ );
738
+ }
739
+ function upstreamSteps(steps, id) {
740
+ return steps.filter((s) => s.id !== id && dependsOn(steps, id, s.id));
741
+ }
742
+ function sendBackTargets(steps, states, id) {
743
+ return upstreamSteps(steps, id).filter((s) => ["done", "skipped", "failed"].includes(states[s.id]?.status ?? "pending")).reverse();
744
+ }
745
+ var isBlankRef = (ref) => "step" in ref ? !ref.step : "input" in ref ? !ref.input : !ref.id.trim();
746
+ function stepConfigProblems(steps) {
747
+ const out = [];
748
+ for (const s of steps) {
749
+ if (s.kind === "action") {
750
+ const a = s.action;
751
+ if (a.type === "sign") {
752
+ if (!a.document || isBlankRef(a.document)) out.push({ step: s.id, missing: "document" });
753
+ if (!a.signers.length) out.push({ step: s.id, missing: "signers" });
754
+ } else if (a.type === "email_draft") {
755
+ if (!a.to.some((t) => t.trim())) out.push({ step: s.id, missing: "recipients" });
756
+ } else {
757
+ if (!a.tags.length) out.push({ step: s.id, missing: "tags" });
758
+ if (Array.isArray(a.documents) && !a.documents.length)
759
+ out.push({ step: s.id, missing: "documents" });
760
+ }
761
+ }
762
+ if (s.kind === "agent" && s.editDocument && isBlankRef(s.editDocument))
763
+ out.push({ step: s.id, missing: "document" });
764
+ }
765
+ return out;
766
+ }
767
+ function splitDocRef(ref) {
768
+ const m = /^(doc-[A-Za-z0-9]+)(?:@v(\d+))?$/.exec(ref);
769
+ return m ? { id: m[1], ...m[2] ? { version: Number(m[2]) } : {} } : { id: ref };
770
+ }
771
+ var DEFAULT_ASSIGNEE_WORDING = {
772
+ runOwner: "whoever started the run",
773
+ projectLead: "the project lead",
774
+ input: "the person in \u201C{field}\u201D"
775
+ };
776
+ function assigneeName(assignee, options = {}) {
777
+ const w = { ...DEFAULT_ASSIGNEE_WORDING, ...options.wording };
778
+ const name = (id) => options.nameOf?.(id) ?? id;
779
+ const told = options.announcedTo ? options.nameOf?.(options.announcedTo) : void 0;
780
+ if (told) return told;
781
+ if (!assignee) return void 0;
782
+ if ("user" in assignee) return name(assignee.user);
783
+ if ("input" in assignee) {
784
+ const value = options.inputs?.[assignee.input];
785
+ if (typeof value === "string" && value) return name(value);
786
+ const label = options.fields?.find((f) => f.name === assignee.input)?.label ?? assignee.input;
787
+ return w.input.replace("{field}", label);
788
+ }
789
+ if ("runOwner" in assignee) return w.runOwner;
790
+ return w.projectLead;
791
+ }
792
+ var asText = (value) => typeof value === "string" ? value.trim() : typeof value === "number" ? String(value) : "";
793
+ function fillStepInputs(templates, inputs) {
794
+ const out = {};
795
+ for (const [name, template] of Object.entries(templates ?? {})) {
796
+ const bare = /^\{\{\s*([a-z][a-z0-9_]*)\s*\}\}$/i.exec(template);
797
+ if (bare) {
798
+ out[name] = inputs[bare[1]] ?? "";
799
+ continue;
800
+ }
801
+ out[name] = fillRunText(template, inputs);
802
+ }
803
+ return out;
804
+ }
805
+ function fillRunText(template, inputs) {
806
+ return template.replace(/\{\{([^}]*)\}\}/g, (_, expr) => {
807
+ for (const ref of expr.split("||")) {
808
+ const text = asText(inputs[ref.trim()]);
809
+ if (text) return text;
810
+ }
811
+ return "";
812
+ }).trim();
813
+ }
814
+ function runThreadTitle(workflow, inputs) {
815
+ const template = workflow.run.kind === "steps" ? workflow.run.threadTitle : void 0;
816
+ return template && fillRunText(template, inputs) || workflow.title;
817
+ }
818
+ function reviewInstructions(step, inputs) {
819
+ return step.instructions && fillRunText(step.instructions, inputs) || step.title;
820
+ }
821
+ function composeStepPrompt(workflow, step, runInputs, { resolveWorkflow, compose } = {}) {
822
+ if (step.workflow) {
823
+ const target = resolveWorkflow?.(step.workflow);
824
+ if (!target) return "";
825
+ const inputs = { ...defaultInputs(target), ...fillStepInputs(step.with, runInputs) };
826
+ return target.run.kind === "prompt" ? composeWorkflowPrompt(target, inputs) : compose?.(target.id, inputs, runInputs) ?? "";
827
+ }
828
+ if (step.run?.kind === "prompt") {
829
+ return composeWorkflowPrompt(
830
+ { ...workflow, inputs: runFields(workflow), run: step.run },
831
+ runInputs
832
+ );
833
+ }
834
+ return compose?.(step.id, runInputs, runInputs) ?? "";
835
+ }
836
+
837
+ // src/agents/workflows/planSteps.ts
838
+ function toPlanAssignee(a) {
839
+ if ("user" in a) return { user: a.user };
840
+ if ("input" in a) return { input: a.input };
841
+ if ("runOwner" in a) return { run_owner: true };
842
+ return { project_lead: true };
843
+ }
844
+ function fromPlanAssignee(a) {
845
+ if (!a) return void 0;
846
+ if (a.user) return { user: a.user };
847
+ if (a.input) return { input: a.input };
848
+ if (a.run_owner) return { runOwner: true };
849
+ if (a.project_lead) return { projectLead: true };
850
+ return void 0;
851
+ }
852
+ function toPlanDocumentRef(ref) {
853
+ if ("step" in ref) return { step: ref.step };
854
+ if ("input" in ref) return { input: ref.input };
855
+ return ref.id.trim();
856
+ }
857
+ function fromPlanDocumentRef(ref) {
858
+ if (!ref) return void 0;
859
+ if (typeof ref === "string") return { id: ref };
860
+ if (ref.step) return { step: ref.step };
861
+ if (ref.input) return { input: ref.input };
862
+ return void 0;
863
+ }
864
+ var docRefs = (refs) => (refs ?? []).map(toPlanDocumentRef);
865
+ var fromDocRefs = (refs) => (refs ?? []).flatMap((r) => {
866
+ const ref = fromPlanDocumentRef(r);
867
+ return ref ? [ref] : [];
868
+ });
869
+ function toPlanAction(a) {
870
+ switch (a.type) {
871
+ case "sign":
872
+ return {
873
+ type: "sign",
874
+ // The builder may hold a signature step whose document is not chosen yet: the server
875
+ // refuses it, and the builder says so before saving (`stepConfigProblems`).
876
+ document: a.document ? toPlanDocumentRef(a.document) : "",
877
+ signers: a.signers.map(toPlanAssignee),
878
+ ...a.message?.trim() ? { message: a.message.trim() } : {},
879
+ ...a.expiresAfterHours ? { expires_after_hours: a.expiresAfterHours } : {}
880
+ };
881
+ case "email_draft":
882
+ return {
883
+ type: "email_draft",
884
+ to: a.to.map((t) => t.trim()).filter(Boolean),
885
+ cc: (a.cc ?? []).map((t) => t.trim()).filter(Boolean),
886
+ subject: a.subject,
887
+ body_template: a.body,
888
+ attachments: docRefs(a.attachments)
889
+ };
890
+ case "tabular_review":
891
+ return {
892
+ type: "tabular_review",
893
+ tags: a.tags,
894
+ documents: Array.isArray(a.documents) ? docRefs(a.documents) : a.documents,
895
+ overwrite: !!a.overwrite
896
+ };
897
+ }
898
+ }
899
+ function fromPlanAction(a) {
900
+ switch (a?.type) {
901
+ case "sign":
902
+ return {
903
+ type: "sign",
904
+ document: fromPlanDocumentRef(a.document),
905
+ signers: (a.signers ?? []).flatMap((s) => {
906
+ const one = fromPlanAssignee(s);
907
+ return one ? [one] : [];
908
+ }),
909
+ ...a.message ? { message: a.message } : {},
910
+ ...a.expires_after_hours ? { expiresAfterHours: a.expires_after_hours } : {}
911
+ };
912
+ case "email_draft":
913
+ return {
914
+ type: "email_draft",
915
+ to: a.to ?? [],
916
+ ...a.cc?.length ? { cc: a.cc } : {},
917
+ subject: a.subject ?? "",
918
+ body: a.body_template ?? "",
919
+ ...a.attachments?.length ? { attachments: fromDocRefs(a.attachments) } : {}
920
+ };
921
+ case "tabular_review":
922
+ return {
923
+ type: "tabular_review",
924
+ tags: a.tags ?? [],
925
+ documents: Array.isArray(a.documents) ? fromDocRefs(a.documents) : a.documents ?? "run_uploads",
926
+ ...a.overwrite ? { overwrite: true } : {}
927
+ };
928
+ default:
929
+ return { type: "tabular_review", tags: [], documents: "run_uploads" };
930
+ }
931
+ }
932
+ function toStepField(f) {
933
+ return {
934
+ name: f.name,
935
+ type: f.type,
936
+ label: f.label,
937
+ required: !!f.required,
938
+ ...f.options ? { options: f.options.map((o) => ({ value: o.value, label: o.label })) } : {},
939
+ ...f.min !== void 0 ? { min: f.min } : {},
940
+ ...f.max !== void 0 ? { max: f.max } : {}
941
+ };
942
+ }
943
+ var compact2 = (o) => Object.fromEntries(Object.entries(o).filter(([, v]) => v !== void 0 && v !== ""));
944
+ function placeholders(workflow) {
945
+ return Object.fromEntries(runFields(workflow).map((f) => [f.name, `{{${f.name}}}`]));
946
+ }
947
+ function agentStep(workflow, step, options) {
948
+ const target = step.workflow ? options.resolveWorkflow?.(step.workflow) : void 0;
949
+ if (target?.skill) return { skill: target.skill, inputs: step.with ?? {}, prompt: null };
950
+ const given = Object.entries(options.inputs ?? {}).filter(
951
+ ([, v]) => v !== void 0 && v !== null && v !== ""
952
+ );
953
+ const inputs = { ...placeholders(workflow), ...Object.fromEntries(given) };
954
+ return { skill: null, inputs: null, prompt: composeStepPrompt(workflow, step, inputs, options) };
955
+ }
956
+ function toPlanSteps(workflow, options = {}) {
957
+ const layout = workflow.run.kind === "steps" ? workflow.run.layout : void 0;
958
+ return workflowSteps(workflow).map((step) => {
959
+ const ui = compact2({
960
+ description: step.description,
961
+ icon: step.icon,
962
+ commit: step.commit,
963
+ next_label: step.nextLabel,
964
+ position: layout?.[step.id],
965
+ ...step.kind === "form" ? { fields: step.inputs } : {},
966
+ ...step.kind === "upload" ? { required: step.required, accept: step.accept, hint: step.hint } : {},
967
+ ...step.kind === "review" ? { instructions: step.instructions, fields: step.fields?.length ? step.fields : void 0 } : {},
968
+ ...step.kind === "agent" ? {
969
+ output: step.output,
970
+ workflow: step.workflow,
971
+ with: step.with,
972
+ prompt_template: step.run?.kind === "prompt" ? step.run.promptTemplate : void 0
973
+ } : {}
974
+ });
975
+ return {
976
+ id: step.id,
977
+ title: step.title,
978
+ kind: step.kind,
979
+ after: step.after ?? null,
980
+ branch: step.kind === "agent" && !!step.branch,
981
+ requires_files: false,
982
+ ui,
983
+ // An unfinished signal (still being typed) is not saved: the server would refuse it.
984
+ ...step.signals?.some((s) => !isIncompleteSignal(s)) ? { signals: step.signals.filter((s) => !isIncompleteSignal(s)).map(toPlanSignal) } : {},
985
+ ...step.assignee && canAssign(step) ? { assignee: toPlanAssignee(step.assignee) } : {},
986
+ ...step.remindAfterHours && canAssign(step) ? { remind_after_hours: step.remindAfterHours } : {},
987
+ // A review's fields are what approving it is checked against (the server's `fields`).
988
+ ...step.kind === "review" && step.fields?.length ? { fields: step.fields.map(toStepField) } : {},
989
+ ...step.kind === "action" ? { action: toPlanAction(step.action) } : {},
990
+ ...step.kind === "agent" && step.editDocument && !step.branch ? { edit_document: toPlanDocumentRef(step.editDocument) } : {},
991
+ ...step.kind === "agent" ? agentStep(workflow, step, options) : { skill: null, inputs: null, prompt: null }
992
+ };
993
+ });
994
+ }
995
+ function toPlanSignal(signal) {
996
+ return signal.when ? { name: signal.name, when: signal.when } : { name: signal.name };
997
+ }
998
+ function isIncompleteSignal(signal) {
999
+ const when = signal.when;
1000
+ if (!signal.name.trim()) return true;
1001
+ if (!when) return false;
1002
+ if ("field" in when) return !when.field || String(when.equals).trim() === "";
1003
+ if ("contains" in when) return !when.contains.trim();
1004
+ if ("matches" in when) return !when.matches.trim();
1005
+ return false;
1006
+ }
1007
+ function fromPlanSignal(signal) {
1008
+ const w = signal.when;
1009
+ const when = w?.field != null && w.equals != null ? { field: w.field, equals: w.equals } : w?.contains != null ? { contains: w.contains } : w?.matches != null ? { matches: w.matches } : w?.approved ? { approved: true } : void 0;
1010
+ return when ? { name: signal.name, when } : { name: signal.name };
1011
+ }
1012
+ function fromPlanSteps(steps) {
1013
+ return (steps ?? []).map((p) => {
1014
+ const ui = p.ui ?? {};
1015
+ const base = {
1016
+ id: p.id,
1017
+ title: p.title,
1018
+ description: ui.description,
1019
+ icon: ui.icon,
1020
+ commit: ui.commit,
1021
+ nextLabel: ui.next_label,
1022
+ ...p.after != null ? { after: p.after } : {},
1023
+ ...p.signals?.length ? { signals: p.signals.map(fromPlanSignal) } : {},
1024
+ ...fromPlanAssignee(p.assignee) ? { assignee: fromPlanAssignee(p.assignee) } : {},
1025
+ ...p.remind_after_hours ? { remindAfterHours: p.remind_after_hours } : {}
1026
+ };
1027
+ switch (p.kind) {
1028
+ case "form":
1029
+ return { ...base, kind: "form", inputs: ui.fields ?? [] };
1030
+ case "upload":
1031
+ return { ...base, kind: "upload", required: ui.required, accept: ui.accept, hint: ui.hint };
1032
+ case "review":
1033
+ return {
1034
+ ...base,
1035
+ kind: "review",
1036
+ instructions: ui.instructions,
1037
+ // The client's copy of the fields (labels, options) when it kept one, else the plan's.
1038
+ ...ui.fields?.length ? { fields: ui.fields } : p.fields?.length ? { fields: p.fields.map(fromStepField) } : {}
1039
+ };
1040
+ case "action":
1041
+ return { ...base, kind: "action", action: fromPlanAction(p.action) };
1042
+ default: {
1043
+ const promptTemplate = ui.prompt_template ?? p.prompt ?? void 0;
1044
+ return {
1045
+ ...base,
1046
+ kind: "agent",
1047
+ workflow: ui.workflow ?? p.skill ?? void 0,
1048
+ with: ui.with ?? p.inputs ?? void 0,
1049
+ run: !ui.workflow && !p.skill && promptTemplate ? { kind: "prompt", promptTemplate } : void 0,
1050
+ output: ui.output,
1051
+ branch: p.branch || void 0,
1052
+ ...fromPlanDocumentRef(p.edit_document) ? { editDocument: fromPlanDocumentRef(p.edit_document) } : {}
1053
+ };
1054
+ }
1055
+ }
1056
+ });
1057
+ }
1058
+ function layoutFromPlanSteps(steps) {
1059
+ const entries = (steps ?? []).flatMap((p) => {
1060
+ const position = p.ui?.position;
1061
+ return position ? [[p.id, position]] : [];
1062
+ });
1063
+ return entries.length ? Object.fromEntries(entries) : void 0;
1064
+ }
1065
+ var FIELD_TYPES2 = /* @__PURE__ */ new Set([
1066
+ "text",
1067
+ "longtext",
1068
+ "boolean",
1069
+ "select",
1070
+ "number",
1071
+ "date",
1072
+ "scope",
1073
+ "documents",
1074
+ "person"
1075
+ ]);
1076
+ function fromStepField(f) {
1077
+ return {
1078
+ name: f.name,
1079
+ type: FIELD_TYPES2.has(f.type) ? f.type : "text",
1080
+ label: f.label ?? f.name,
1081
+ ...f.required ? { required: true } : {},
1082
+ ...f.options?.length ? { options: f.options.map((o) => ({ value: o.value, label: o.label ?? o.value })) } : {},
1083
+ ...f.min != null ? { min: f.min } : {},
1084
+ ...f.max != null ? { max: f.max } : {}
1085
+ };
1086
+ }
1087
+ var errorMessage = (e) => e instanceof Error ? e.message : typeof e === "string" ? e : "Something went wrong.";
1088
+ function firstOpen(steps, states) {
1089
+ const open = steps.find((s) => !["done", "skipped"].includes(states[s.id]?.status ?? "pending"));
1090
+ return open?.id ?? (steps.length && isRunFinished(steps, states) ? null : steps[0]?.id ?? null);
1091
+ }
1092
+ function useWorkflowView(options) {
1093
+ const { steps, states, inputs, setInput, loaded = true } = options;
1094
+ const [current, setCurrent] = useState(() => firstOpen(steps, states));
1095
+ const [showErrors, setShowErrors] = useState(false);
1096
+ const [busy, setBusy] = useState(false);
1097
+ const [actionError, setActionError] = useState(null);
1098
+ const optionsRef = useRef(options);
1099
+ optionsRef.current = options;
1100
+ const placed = useRef(steps.length > 0 && loaded);
1101
+ useEffect(() => {
1102
+ if (placed.current || steps.length === 0 || !loaded) return;
1103
+ placed.current = true;
1104
+ setCurrent(firstOpen(steps, states));
1105
+ }, [steps, states, loaded]);
1106
+ const step = steps.find((s) => s.id === current);
1107
+ const following = useCallback(
1108
+ (id) => {
1109
+ const i = steps.findIndex((s) => s.id === id);
1110
+ return steps[i + 1]?.id ?? null;
1111
+ },
1112
+ [steps]
1113
+ );
1114
+ const onward = useCallback(
1115
+ (id) => {
1116
+ const { steps: s, states: st } = optionsRef.current;
1117
+ const i = s.findIndex((x) => x.id === id);
1118
+ const open = s.slice(i + 1).find((x) => !["done", "skipped"].includes(st[x.id]?.status ?? "pending"));
1119
+ return open?.id ?? following(id);
1120
+ },
1121
+ [following]
1122
+ );
1123
+ const action = busy ? "wait" : nextAction(steps, states, current);
1124
+ const next = useCallback(async () => {
1125
+ const { steps: s, states: st, inputs: values } = optionsRef.current;
1126
+ const at = s.find((x) => x.id === current);
1127
+ const act2 = nextAction(s, st, current);
1128
+ if (!at || act2 === "wait" || busy) return;
1129
+ setActionError(null);
1130
+ if (act2 === "finish") return setCurrent(null);
1131
+ if (act2 === "move") return setCurrent(following(current));
1132
+ setBusy(true);
1133
+ try {
1134
+ if (act2 === "skip") {
1135
+ await optionsRef.current.skip(at.id);
1136
+ } else {
1137
+ if (Object.keys(validateStep(at, values)).length > 0) {
1138
+ setShowErrors(true);
1139
+ return;
1140
+ }
1141
+ await optionsRef.current.complete(at);
1142
+ }
1143
+ setShowErrors(false);
1144
+ setCurrent(onward(at.id) ?? current);
1145
+ } catch (e) {
1146
+ setActionError(errorMessage(e));
1147
+ } finally {
1148
+ setBusy(false);
1149
+ }
1150
+ }, [current, busy, following, onward]);
1151
+ const goTo = useCallback((stepId) => {
1152
+ setShowErrors(false);
1153
+ setActionError(null);
1154
+ setCurrent(stepId);
1155
+ }, []);
1156
+ const index = steps.findIndex((s) => s.id === current);
1157
+ const back = useCallback(() => {
1158
+ const i = steps.findIndex((s) => s.id === current);
1159
+ const to = i < 0 ? steps[steps.length - 1] : steps[i - 1];
1160
+ if (to) goTo(to.id);
1161
+ }, [steps, current, goTo]);
1162
+ const act = useCallback(
1163
+ (fn) => fn && (async (stepId) => {
1164
+ setBusy(true);
1165
+ setActionError(null);
1166
+ try {
1167
+ await fn(stepId);
1168
+ } catch (e) {
1169
+ setActionError(errorMessage(e));
1170
+ } finally {
1171
+ setBusy(false);
1172
+ }
1173
+ }),
1174
+ []
1175
+ );
1176
+ const currentRef = useRef(current);
1177
+ currentRef.current = current;
1178
+ const skip = useMemo(
1179
+ () => act(async (id) => {
1180
+ await optionsRef.current.skip(id);
1181
+ if (currentRef.current !== id) return;
1182
+ setShowErrors(false);
1183
+ setCurrent(onward(id) ?? id);
1184
+ }),
1185
+ [act, onward]
1186
+ );
1187
+ const retry = useMemo(
1188
+ () => options.retry ? act((id) => optionsRef.current.retry?.(id) ?? Promise.resolve()) : void 0,
1189
+ // Only whether the host offers retry matters here.
1190
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1191
+ [act, !!options.retry]
1192
+ );
1193
+ const sendBack = useMemo(
1194
+ () => options.sendBack ? async (stepId, to, note) => {
1195
+ setBusy(true);
1196
+ setActionError(null);
1197
+ try {
1198
+ await optionsRef.current.sendBack?.(stepId, to, note);
1199
+ setShowErrors(false);
1200
+ setCurrent(to);
1201
+ } catch (e) {
1202
+ setActionError(errorMessage(e));
1203
+ } finally {
1204
+ setBusy(false);
1205
+ }
1206
+ } : void 0,
1207
+ // Only whether the host offers it matters here.
1208
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1209
+ [!!options.sendBack]
1210
+ );
1211
+ const declineWithNote = useMemo(
1212
+ () => options.decline ? async (stepId, note) => {
1213
+ setBusy(true);
1214
+ setActionError(null);
1215
+ try {
1216
+ await optionsRef.current.decline?.(stepId, note);
1217
+ } catch (e) {
1218
+ setActionError(errorMessage(e));
1219
+ } finally {
1220
+ setBusy(false);
1221
+ }
1222
+ } : void 0,
1223
+ // Only whether the host offers it matters here.
1224
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1225
+ [!!options.decline]
1226
+ );
1227
+ const isLocked = useCallback(
1228
+ (stepId) => optionsRef.current.isLocked?.(stepId) ?? false,
1229
+ []
1230
+ );
1231
+ return {
1232
+ steps,
1233
+ step,
1234
+ current,
1235
+ isComplete: current === null,
1236
+ inputs,
1237
+ setInput,
1238
+ stateOf: (id) => states[id],
1239
+ phaseOf: (id) => stepPhase(steps, states, id),
1240
+ isLocked,
1241
+ errors: showErrors && step && !isLocked(step.id) ? validateStep(step, inputs) : {},
1242
+ busy,
1243
+ actionError,
1244
+ isRunning: Object.values(states).some((s) => s.status === "running"),
1245
+ action,
1246
+ canBack: (current === null || index > 0) && !busy,
1247
+ next,
1248
+ back,
1249
+ goTo,
1250
+ skip,
1251
+ retry,
1252
+ sendBack,
1253
+ decline: declineWithNote,
1254
+ nameOf: options.nameOf,
1255
+ assigneeOf: (id) => {
1256
+ const s = steps.find((x) => x.id === id);
1257
+ if (!s || !canAssign(s)) return void 0;
1258
+ return assigneeName(s.assignee, {
1259
+ nameOf: options.nameOf,
1260
+ inputs,
1261
+ fields: runFieldsOf(steps),
1262
+ announcedTo: states[id]?.announcedTo
1263
+ });
1264
+ },
1265
+ restart: () => {
1266
+ setShowErrors(false);
1267
+ setActionError(null);
1268
+ setCurrent(steps[0]?.id ?? null);
1269
+ }
1270
+ };
1271
+ }
1272
+
1273
+ // src/agents/workflows/stepWindow.ts
1274
+ var ATTENTION = ["waiting", "failed", "ready"];
1275
+ function activeStepId(levels, phaseOf) {
1276
+ const all = levels.flat();
1277
+ for (const phase of ["running", ...ATTENTION]) {
1278
+ const hit = all.find((s) => phaseOf(s.id) === phase);
1279
+ if (hit) return hit.id;
1280
+ }
1281
+ return all[all.length - 1]?.id;
1282
+ }
1283
+ function primaryOf(steps, phaseOf, current) {
1284
+ return steps.find((s) => phaseOf(s.id) === "running") ?? steps.find((s) => s.id === current) ?? ATTENTION.map((p) => steps.find((s) => phaseOf(s.id) === p)).find(Boolean) ?? steps[0];
1285
+ }
1286
+ function stepWindow({
1287
+ levels: raw,
1288
+ phaseOf,
1289
+ current,
1290
+ start: requested,
1291
+ span = 3
1292
+ }) {
1293
+ const levelsIn = raw.filter((l) => l.length > 0);
1294
+ const known = current && levelsIn.some((l) => l.some((s) => s.id === current));
1295
+ const at = known ? current : activeStepId(levelsIn, phaseOf);
1296
+ const currentLevel = Math.max(
1297
+ 0,
1298
+ levelsIn.findIndex((l) => l.some((s) => s.id === at))
1299
+ );
1300
+ const n = levelsIn.length;
1301
+ const size = Math.max(1, Math.min(span, n));
1302
+ const maxStart = Math.max(0, n - size);
1303
+ const auto = currentLevel - (size >= 3 ? 1 : 0);
1304
+ const start = Math.min(maxStart, Math.max(0, requested ?? auto));
1305
+ const end = start + size;
1306
+ const levels = levelsIn.map((steps, index) => {
1307
+ const primary = primaryOf(steps, phaseOf, at);
1308
+ return {
1309
+ index,
1310
+ steps,
1311
+ primary,
1312
+ others: steps.filter((s) => s !== primary),
1313
+ role: index === currentLevel ? "current" : index === currentLevel - 1 ? "previous" : index === currentLevel + 1 ? "next" : "other"
1314
+ };
1315
+ });
1316
+ const visible = levels.slice(start, end);
1317
+ const count = (ls) => ls.reduce((sum, l) => sum + l.steps.length, 0);
1318
+ const shown = new Set(visible.map((l) => l.primary.id));
1319
+ const all = levelsIn.flat();
1320
+ return {
1321
+ levels,
1322
+ visible,
1323
+ start,
1324
+ end,
1325
+ currentLevel,
1326
+ hiddenBefore: count(levels.slice(0, start)),
1327
+ hiddenAfter: count(levels.slice(end)),
1328
+ canBack: start > 0,
1329
+ canForward: end < n,
1330
+ runningHidden: all.filter((s) => phaseOf(s.id) === "running" && !shown.has(s.id)),
1331
+ position: Math.max(1, all.findIndex((s) => s.id === at) + 1),
1332
+ total: all.length
1333
+ };
1334
+ }
1335
+ function PhaseMark({ phase, icon }) {
1336
+ if (phase === "done") return /* @__PURE__ */ jsx(Check, { className: "size-3.5 shrink-0" });
1337
+ if (phase === "running") return /* @__PURE__ */ jsx(Loader2, { className: "size-3.5 shrink-0 animate-spin" });
1338
+ if (phase === "waiting") return /* @__PURE__ */ jsx(Hourglass, { className: "size-3.5 shrink-0" });
1339
+ if (phase === "failed") return /* @__PURE__ */ jsx(AlertTriangle, { className: "size-3.5 shrink-0" });
1340
+ if (phase === "skipped") return /* @__PURE__ */ jsx(MinusCircle, { className: "size-3.5 shrink-0" });
1341
+ return icon ? /* @__PURE__ */ jsx("span", { className: "flex size-3.5 shrink-0 items-center justify-center [&_svg]:size-3.5", children: icon }) : /* @__PURE__ */ jsx("span", { className: "mx-1 size-1.5 shrink-0 rounded-full bg-current opacity-60" });
1342
+ }
1343
+ var DEFAULT_STEP_BAR_LABELS = {
1344
+ nav: "Workflow steps",
1345
+ back: "Earlier steps",
1346
+ forward: "Later steps",
1347
+ hiddenBefore: "{count} earlier steps: show all steps",
1348
+ hiddenAfter: "{count} later steps: show all steps",
1349
+ position: "Step {position} of {total}",
1350
+ parallel: "{count} more in parallel",
1351
+ inParallel: "In parallel",
1352
+ working: "Working: {title}",
1353
+ moreWorking: "+{count} working",
1354
+ waitingFor: "Waiting for {name}",
1355
+ phases: {
1356
+ blocked: "Not started",
1357
+ ready: "To do",
1358
+ running: "Working",
1359
+ waiting: "Waiting",
1360
+ done: "Done",
1361
+ skipped: "Skipped",
1362
+ failed: "Failed"
1363
+ }
1364
+ };
1365
+ function stepPillTone(phase, current) {
1366
+ if (current) return "border-primary bg-primary/10 text-primary font-semibold shadow-sm";
1367
+ switch (phase) {
1368
+ case "done":
1369
+ return "border-emerald-600/30 text-emerald-700 dark:border-emerald-400/30 dark:text-emerald-400";
1370
+ case "running":
1371
+ return "border-primary/40 bg-primary/5 text-primary";
1372
+ case "waiting":
1373
+ return "border-amber-500/40 text-amber-700 dark:text-amber-300";
1374
+ case "failed":
1375
+ return "border-destructive/40 text-destructive";
1376
+ case "skipped":
1377
+ return "border-border text-muted-foreground line-through decoration-1";
1378
+ case "ready":
1379
+ return "border-border text-foreground";
1380
+ default:
1381
+ return "border-dashed border-border text-muted-foreground";
1382
+ }
1383
+ }
1384
+ var PHASE_TEXT = {
1385
+ done: "text-emerald-700 dark:text-emerald-400",
1386
+ running: "text-primary",
1387
+ waiting: "text-amber-700 dark:text-amber-300",
1388
+ failed: "text-destructive",
1389
+ skipped: "text-muted-foreground",
1390
+ ready: "text-foreground",
1391
+ blocked: "text-muted-foreground"
1392
+ };
1393
+ var STEP_PILL = "flex min-w-0 items-center gap-1.5 whitespace-nowrap rounded-full border px-2.5 py-1 text-xs font-medium transition-colors";
1394
+ var STEP_LABEL = "min-w-[2.5rem] truncate";
1395
+ var STEP_EXTRA = "min-w-0 shrink-[6] truncate font-normal opacity-80";
1396
+ var fill = (template, values) => Object.entries(values).reduce((s, [k, v]) => s.replace(`{${k}}`, String(v)), template);
1397
+ var NARROW_PX = 560;
1398
+ var FULL_LEVEL_PX = 190;
1399
+ function StepBarFrame({
1400
+ levels,
1401
+ phaseOf,
1402
+ current,
1403
+ onSelect,
1404
+ renderStep,
1405
+ noteOf,
1406
+ renderAfter,
1407
+ leading,
1408
+ trailing,
1409
+ isSelectable = () => true,
1410
+ icon,
1411
+ disabled,
1412
+ labels,
1413
+ tid,
1414
+ className
1415
+ }) {
1416
+ const l = { ...DEFAULT_STEP_BAR_LABELS, ...labels };
1417
+ const box = useRef(null);
1418
+ const { width } = useElementSize(box);
1419
+ const narrow = width > 0 && width < NARROW_PX;
1420
+ const [start, setStart] = useState(null);
1421
+ useEffect(() => setStart(null), [current]);
1422
+ const span = narrow ? 2 : width >= levels.length * FULL_LEVEL_PX ? levels.length : 3;
1423
+ const win = stepWindow({ levels, phaseOf, current, start, span });
1424
+ const [listOpen, setListOpen] = useState(null);
1425
+ const move = (to) => setStart(Math.max(0, Math.min(win.levels.length - 1, to)));
1426
+ const select = (id) => {
1427
+ setListOpen(null);
1428
+ setStart(null);
1429
+ onSelect(id);
1430
+ };
1431
+ const onKeyDown = (e) => {
1432
+ if (!box.current?.contains(e.target)) return;
1433
+ const to = e.key === "ArrowLeft" ? win.start - 1 : e.key === "ArrowRight" ? win.start + 1 : e.key === "Home" ? 0 : e.key === "End" ? win.levels.length : null;
1434
+ if (to === null) return;
1435
+ e.preventDefault();
1436
+ move(to);
1437
+ };
1438
+ const row = (s, isCurrent) => {
1439
+ const phase = phaseOf(s.id);
1440
+ const selectable = isSelectable(s.id);
1441
+ return /* @__PURE__ */ jsxs(
1442
+ "button",
1443
+ {
1444
+ type: "button",
1445
+ disabled: !selectable || disabled,
1446
+ onClick: () => select(s.id),
1447
+ "aria-current": isCurrent ? "step" : void 0,
1448
+ className: cn(
1449
+ "flex w-full min-w-0 items-center gap-2 rounded-md px-2 py-1 text-left text-xs hover:bg-accent disabled:cursor-default disabled:hover:bg-transparent",
1450
+ isCurrent && "bg-primary/10 font-semibold text-primary"
1451
+ ),
1452
+ "data-phase": phase,
1453
+ "data-testid": tid(`list-step-${s.id}`),
1454
+ children: [
1455
+ /* @__PURE__ */ jsx("span", { className: cn("flex shrink-0", !isCurrent && PHASE_TEXT[phase]), children: /* @__PURE__ */ jsx(PhaseMark, { phase, icon: icon?.(s) }) }),
1456
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", title: s.title, children: s.title }),
1457
+ /* @__PURE__ */ jsx("span", { className: "shrink-0 text-[10px] font-normal text-muted-foreground", children: l.phases[phase] })
1458
+ ]
1459
+ },
1460
+ s.id
1461
+ );
1462
+ };
1463
+ const at = win.levels[win.currentLevel];
1464
+ const atId = at && (at.steps.find((s) => s.id === current) ?? at.primary).id;
1465
+ const focusCurrent = (e) => {
1466
+ const el = e.currentTarget?.querySelector(
1467
+ '[aria-current="step"]'
1468
+ );
1469
+ if (!el) return;
1470
+ e.preventDefault();
1471
+ el.focus();
1472
+ el.scrollIntoView?.({ block: "nearest" });
1473
+ };
1474
+ const allSteps = /* @__PURE__ */ jsxs(Fragment, { children: [
1475
+ /* @__PURE__ */ jsx("p", { className: "px-2 py-1 text-[11px] font-medium text-muted-foreground", children: fill(l.position, { position: win.position, total: win.total }) }),
1476
+ /* @__PURE__ */ jsx("ol", { children: win.levels.map((level) => /* @__PURE__ */ jsxs(
1477
+ "li",
1478
+ {
1479
+ className: cn(level.steps.length > 1 && "my-0.5 border-l-2 border-primary/30 pl-1"),
1480
+ children: [
1481
+ level.steps.length > 1 && /* @__PURE__ */ jsx("p", { className: "px-2 text-[10px] uppercase tracking-wide text-muted-foreground", children: l.inParallel }),
1482
+ level.steps.map((s) => row(s, s.id === atId))
1483
+ ]
1484
+ },
1485
+ level.index
1486
+ )) })
1487
+ ] });
1488
+ const overflow = (side, count) => narrow && side === "before" ? null : narrow && side === "after" ? win.hiddenBefore + win.hiddenAfter > 0 ? /* @__PURE__ */ jsxs(Popover, { open: listOpen === side, onOpenChange: (o) => setListOpen(o ? side : null), children: [
1489
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
1490
+ "button",
1491
+ {
1492
+ type: "button",
1493
+ className: "flex shrink-0 items-center rounded-full px-1.5 py-1 text-[11px] tabular-nums text-muted-foreground hover:bg-accent hover:text-foreground",
1494
+ "aria-label": fill(l.position, { position: win.position, total: win.total }),
1495
+ title: fill(l.position, { position: win.position, total: win.total }),
1496
+ "data-testid": tid("steps-count"),
1497
+ children: [
1498
+ win.position,
1499
+ "/",
1500
+ win.total
1501
+ ]
1502
+ }
1503
+ ) }),
1504
+ /* @__PURE__ */ jsx(
1505
+ PopoverContent,
1506
+ {
1507
+ align: "end",
1508
+ className: "max-h-[min(24rem,70vh)] w-72 overflow-y-auto p-1",
1509
+ onOpenAutoFocus: focusCurrent,
1510
+ "data-testid": tid("all-steps"),
1511
+ children: allSteps
1512
+ }
1513
+ )
1514
+ ] }) : null : count > 0 ? /* @__PURE__ */ jsxs(Popover, { open: listOpen === side, onOpenChange: (o) => setListOpen(o ? side : null), children: [
1515
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
1516
+ "button",
1517
+ {
1518
+ type: "button",
1519
+ className: "flex shrink-0 items-center gap-0.5 rounded-full px-1.5 py-1 text-xs text-muted-foreground hover:bg-accent hover:text-foreground",
1520
+ "aria-label": fill(side === "before" ? l.hiddenBefore : l.hiddenAfter, { count }),
1521
+ title: fill(side === "before" ? l.hiddenBefore : l.hiddenAfter, { count }),
1522
+ "data-testid": tid(`hidden-${side}`),
1523
+ children: [
1524
+ /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: "\u2026" }),
1525
+ /* @__PURE__ */ jsx("span", { className: "tabular-nums", children: count })
1526
+ ]
1527
+ }
1528
+ ) }),
1529
+ /* @__PURE__ */ jsx(
1530
+ PopoverContent,
1531
+ {
1532
+ align: side === "before" ? "start" : "end",
1533
+ className: "max-h-[min(24rem,70vh)] w-80 overflow-y-auto p-1",
1534
+ onOpenAutoFocus: focusCurrent,
1535
+ "data-testid": tid("all-steps"),
1536
+ children: allSteps
1537
+ }
1538
+ )
1539
+ ] }) : null;
1540
+ const arrow = (dir) => {
1541
+ const can = dir === "back" ? win.canBack : win.canForward;
1542
+ const Icon = dir === "back" ? ChevronLeft : ChevronRight;
1543
+ return /* @__PURE__ */ jsx(
1544
+ "button",
1545
+ {
1546
+ type: "button",
1547
+ onClick: () => move(win.start + (dir === "back" ? -1 : 1)),
1548
+ disabled: !can,
1549
+ "aria-label": dir === "back" ? l.back : l.forward,
1550
+ title: dir === "back" ? l.back : l.forward,
1551
+ className: "flex size-6 shrink-0 items-center justify-center rounded-full text-muted-foreground hover:bg-accent hover:text-foreground disabled:pointer-events-none disabled:opacity-30",
1552
+ "data-testid": tid(`steps-${dir}`),
1553
+ children: /* @__PURE__ */ jsx(Icon, { className: "size-4" })
1554
+ }
1555
+ );
1556
+ };
1557
+ const running = win.runningHidden;
1558
+ const workingPill = (s) => /* @__PURE__ */ jsxs(
1559
+ "button",
1560
+ {
1561
+ type: "button",
1562
+ onClick: () => select(s.id),
1563
+ className: "flex min-w-0 max-w-[14rem] shrink items-center gap-1.5 rounded-full border border-primary/40 bg-primary/5 px-2 py-1 text-xs text-primary hover:bg-primary/10",
1564
+ title: fill(l.working, { title: s.title }),
1565
+ "data-testid": tid(`working-${s.id}`),
1566
+ children: [
1567
+ /* @__PURE__ */ jsx(Loader2, { className: "size-3.5 shrink-0 animate-spin" }),
1568
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: fill(l.working, { title: s.title }) })
1569
+ ]
1570
+ },
1571
+ s.id
1572
+ );
1573
+ return /* @__PURE__ */ jsxs(
1574
+ "nav",
1575
+ {
1576
+ ref: box,
1577
+ "aria-label": l.nav,
1578
+ onKeyDown,
1579
+ className: cn("flex min-w-0 items-center gap-1", narrow && "flex-wrap", className),
1580
+ "data-testid": tid("steps"),
1581
+ "data-narrow": narrow || void 0,
1582
+ children: [
1583
+ arrow("back"),
1584
+ win.start === 0 && leading,
1585
+ overflow("before", win.hiddenBefore),
1586
+ /* @__PURE__ */ jsx("ol", { className: "flex min-w-0 flex-1 items-center gap-1", children: win.visible.map((level, i) => /* @__PURE__ */ jsxs(
1587
+ "li",
1588
+ {
1589
+ className: cn(
1590
+ "flex min-w-[5.5rem] items-center gap-1",
1591
+ i === win.visible.length - 1 ? "flex-[0_1_auto]" : "flex-[1_1_auto]"
1592
+ ),
1593
+ "data-role": level.role,
1594
+ children: [
1595
+ renderStep(level.primary, level, win, narrow),
1596
+ level.others.length > 0 && /* @__PURE__ */ jsx(
1597
+ GroupChip,
1598
+ {
1599
+ level,
1600
+ label: fill(l.parallel, { count: level.others.length }),
1601
+ heading: l.inParallel,
1602
+ row: (s) => row(s, s.id === atId),
1603
+ tid
1604
+ }
1605
+ ),
1606
+ renderAfter?.(level),
1607
+ i < win.visible.length - 1 && !narrow && /* @__PURE__ */ jsx(
1608
+ "span",
1609
+ {
1610
+ "aria-hidden": true,
1611
+ className: cn(
1612
+ "h-px min-w-3 flex-1",
1613
+ level.steps.every((s) => ["done", "skipped"].includes(phaseOf(s.id))) ? "bg-emerald-600/40" : "bg-border"
1614
+ )
1615
+ }
1616
+ )
1617
+ ]
1618
+ },
1619
+ level.steps.map((s) => s.id).join("+")
1620
+ )) }),
1621
+ overflow("after", win.hiddenAfter),
1622
+ !win.canForward && trailing,
1623
+ arrow("forward"),
1624
+ narrow && atId && noteOf?.(atId) && /* @__PURE__ */ jsx(
1625
+ "p",
1626
+ {
1627
+ className: "min-w-0 basis-full truncate px-1 text-[11px] text-muted-foreground",
1628
+ "data-testid": tid("note"),
1629
+ children: noteOf(atId)
1630
+ }
1631
+ ),
1632
+ running.length > 0 && /* @__PURE__ */ jsxs(
1633
+ "div",
1634
+ {
1635
+ className: cn("flex min-w-0 shrink items-center gap-1", narrow && "basis-full"),
1636
+ "data-testid": tid("working"),
1637
+ children: [
1638
+ (running.length > 2 ? running.slice(0, 1) : running).map(workingPill),
1639
+ running.length > 2 && /* @__PURE__ */ jsxs(Popover, { children: [
1640
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
1641
+ "button",
1642
+ {
1643
+ type: "button",
1644
+ className: "flex shrink-0 items-center gap-1 rounded-full border border-primary/40 bg-primary/5 px-2 py-1 text-xs text-primary hover:bg-primary/10",
1645
+ "data-testid": tid("working-more"),
1646
+ children: [
1647
+ /* @__PURE__ */ jsx(Loader2, { className: "size-3.5 animate-spin" }),
1648
+ fill(l.moreWorking, { count: running.length - 1 })
1649
+ ]
1650
+ }
1651
+ ) }),
1652
+ /* @__PURE__ */ jsx(PopoverContent, { align: "end", className: "w-72 p-1", children: running.slice(1).map((s) => row(s, false)) })
1653
+ ] })
1654
+ ]
1655
+ }
1656
+ )
1657
+ ]
1658
+ }
1659
+ );
1660
+ }
1661
+ function GroupChip({
1662
+ level,
1663
+ label,
1664
+ heading,
1665
+ row,
1666
+ tid
1667
+ }) {
1668
+ return /* @__PURE__ */ jsxs(Popover, { children: [
1669
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
1670
+ "button",
1671
+ {
1672
+ type: "button",
1673
+ className: "shrink-0 rounded-full border border-border bg-muted px-1.5 py-0.5 text-[11px] font-medium tabular-nums text-muted-foreground hover:border-primary/40 hover:text-primary",
1674
+ "aria-label": label,
1675
+ title: label,
1676
+ "data-testid": tid(`group-${level.steps[0].id}`),
1677
+ children: [
1678
+ "+",
1679
+ level.others.length
1680
+ ]
1681
+ }
1682
+ ) }),
1683
+ /* @__PURE__ */ jsxs(
1684
+ PopoverContent,
1685
+ {
1686
+ align: "start",
1687
+ className: "w-72 p-1",
1688
+ "data-testid": tid(`group-${level.steps[0].id}-list`),
1689
+ children: [
1690
+ /* @__PURE__ */ jsx("p", { className: "px-2 py-1 text-[11px] font-medium text-muted-foreground", children: heading }),
1691
+ level.steps.map(row)
1692
+ ]
1693
+ }
1694
+ )
1695
+ ] });
1696
+ }
1697
+ var DEFAULT_WORKFLOW_STEP_PARTS_LABELS = {
1698
+ waitingFor: "Waiting for {name}",
1699
+ sendBack: "Send back",
1700
+ sendBackTo: "Send back to",
1701
+ sendBackNote: "What needs to change? (optional)",
1702
+ cancel: "Cancel",
1703
+ sentBackHere: "This step was sent back to be done again.",
1704
+ approvedBy: "Approved by {name}",
1705
+ approvedAt: "{date}",
1706
+ approvedWhat: "What was approved",
1707
+ viewAnswer: "View the answer",
1708
+ fieldsHeading: "Recorded with the approval",
1709
+ signers: "Signers",
1710
+ signed: "Signed",
1711
+ notSigned: "Not signed yet",
1712
+ openAndSign: "Open and sign",
1713
+ decline: "Decline",
1714
+ declineNote: "Why are you declining? (optional)",
1715
+ declineConfirm: "Decline to sign",
1716
+ declined: "Declined by {name}.",
1717
+ signedVersion: "Open the signed version",
1718
+ preparingSign: "Preparing the signature request\u2026",
1719
+ expires: "Expires {date}",
1720
+ preparingDraft: "Preparing the draft\u2026",
1721
+ draftReady: "The draft is ready in native mail. Check it, send it, then mark it as sent.",
1722
+ openDraft: "Open draft",
1723
+ emailSent: "Sent.",
1724
+ reviewRunning: "Reviewing the documents\u2026",
1725
+ reviewDone: "Reviewed {docs} documents in {cols} columns.",
1726
+ openReview: "Open the review",
1727
+ openInWord: "Open in Word",
1728
+ redlineHint: "The edits are tracked changes: accept or reject each one in Word.",
1729
+ someone: "someone"
1730
+ };
1731
+ var formatDate = (iso) => {
1732
+ const t = iso ? Date.parse(iso) : NaN;
1733
+ return Number.isNaN(t) ? "" : new Date(t).toLocaleString(void 0, { dateStyle: "medium", timeStyle: "short" });
1734
+ };
1735
+ var docName = (ref, nameOfDoc) => {
1736
+ const { id, version } = splitDocRef(ref);
1737
+ const name = nameOfDoc?.(id) ?? id;
1738
+ return version ? `${name} \xB7 v${version}` : name;
1739
+ };
1740
+ function DocChip({
1741
+ docRef,
1742
+ actions,
1743
+ nameOfDoc,
1744
+ testId
1745
+ }) {
1746
+ const label = docName(docRef, nameOfDoc);
1747
+ const cls = "inline-flex max-w-full items-center gap-1 rounded-md border border-border px-1.5 py-0.5 text-xs";
1748
+ return actions?.openDocument ? /* @__PURE__ */ jsxs(
1749
+ "button",
1750
+ {
1751
+ type: "button",
1752
+ onClick: () => actions.openDocument?.(docRef),
1753
+ className: cn(cls, "hover:border-primary/40 hover:text-primary"),
1754
+ title: docRef,
1755
+ "data-testid": testId,
1756
+ children: [
1757
+ /* @__PURE__ */ jsx(FileText, { className: "size-3 shrink-0" }),
1758
+ " ",
1759
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: label })
1760
+ ]
1761
+ }
1762
+ ) : /* @__PURE__ */ jsxs("span", { className: cls, title: docRef, "data-testid": testId, children: [
1763
+ /* @__PURE__ */ jsx(FileText, { className: "size-3 shrink-0" }),
1764
+ " ",
1765
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: label })
1766
+ ] });
1767
+ }
1768
+ function AssigneeLine({
1769
+ run,
1770
+ stepId,
1771
+ l,
1772
+ testId
1773
+ }) {
1774
+ const name = run.assigneeOf(stepId);
1775
+ const phase = run.phaseOf(stepId);
1776
+ if (!name || !["ready", "waiting", "blocked"].includes(phase)) return null;
1777
+ return /* @__PURE__ */ jsxs(
1778
+ "p",
1779
+ {
1780
+ className: "flex items-center gap-1.5 text-xs text-muted-foreground",
1781
+ "data-testid": `${testId}-assignee`,
1782
+ children: [
1783
+ /* @__PURE__ */ jsx(UserRound, { className: "size-3.5" }),
1784
+ " ",
1785
+ l.waitingFor.replace("{name}", name)
1786
+ ]
1787
+ }
1788
+ );
1789
+ }
1790
+ function SentBackNote({
1791
+ state,
1792
+ l,
1793
+ testId
1794
+ }) {
1795
+ if (!state?.sentBack) return null;
1796
+ return /* @__PURE__ */ jsxs(
1797
+ "p",
1798
+ {
1799
+ className: "flex items-center gap-1.5 rounded-md bg-amber-500/10 px-2 py-1 text-xs text-amber-700 dark:text-amber-300",
1800
+ "data-testid": `${testId}-sent-back`,
1801
+ children: [
1802
+ /* @__PURE__ */ jsx(CornerUpLeft, { className: "size-3.5" }),
1803
+ " ",
1804
+ l.sentBackHere
1805
+ ]
1806
+ }
1807
+ );
1808
+ }
1809
+ function SendBackControl({
1810
+ run,
1811
+ step,
1812
+ l,
1813
+ testId
1814
+ }) {
1815
+ const states = Object.fromEntries(
1816
+ run.steps.map((s) => [s.id, run.stateOf(s.id) ?? { status: "pending" }])
1817
+ );
1818
+ const targets = sendBackTargets(run.steps, states, step.id);
1819
+ const [open, setOpen] = useState(false);
1820
+ const [to, setTo] = useState(targets[0]?.id ?? "");
1821
+ const [note, setNote] = useState("");
1822
+ if (!run.sendBack || run.phaseOf(step.id) !== "ready" || targets.length === 0) return null;
1823
+ if (!open)
1824
+ return /* @__PURE__ */ jsxs(
1825
+ Button,
1826
+ {
1827
+ variant: "outline",
1828
+ size: "sm",
1829
+ onClick: () => setOpen(true),
1830
+ disabled: run.busy,
1831
+ "data-testid": `${testId}-send-back`,
1832
+ children: [
1833
+ /* @__PURE__ */ jsx(CornerUpLeft, { className: "size-4" }),
1834
+ " ",
1835
+ l.sendBack
1836
+ ]
1837
+ }
1838
+ );
1839
+ const target = targets.some((t) => t.id === to) ? to : targets[0].id;
1840
+ return /* @__PURE__ */ jsxs(
1841
+ "div",
1842
+ {
1843
+ className: "space-y-2 rounded-md border border-border p-3",
1844
+ "data-testid": `${testId}-send-back-form`,
1845
+ children: [
1846
+ /* @__PURE__ */ jsxs("label", { className: "block space-y-1 text-xs font-medium", children: [
1847
+ l.sendBackTo,
1848
+ /* @__PURE__ */ jsx(
1849
+ "select",
1850
+ {
1851
+ className: "flex h-8 w-full rounded-md border border-input bg-background px-2 text-sm",
1852
+ value: target,
1853
+ onChange: (e) => setTo(e.target.value),
1854
+ "data-testid": `${testId}-send-back-to`,
1855
+ children: targets.map((t) => /* @__PURE__ */ jsx("option", { value: t.id, children: t.title }, t.id))
1856
+ }
1857
+ )
1858
+ ] }),
1859
+ /* @__PURE__ */ jsx(
1860
+ Textarea,
1861
+ {
1862
+ rows: 3,
1863
+ value: note,
1864
+ placeholder: l.sendBackNote,
1865
+ "aria-label": l.sendBackNote,
1866
+ onChange: (e) => setNote(e.target.value),
1867
+ "data-testid": `${testId}-send-back-note`
1868
+ }
1869
+ ),
1870
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
1871
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => setOpen(false), children: l.cancel }),
1872
+ /* @__PURE__ */ jsxs(
1873
+ Button,
1874
+ {
1875
+ size: "sm",
1876
+ disabled: run.busy,
1877
+ onClick: () => void run.sendBack?.(step.id, target, note.trim() || void 0),
1878
+ "data-testid": `${testId}-send-back-confirm`,
1879
+ children: [
1880
+ /* @__PURE__ */ jsx(CornerUpLeft, { className: "size-4" }),
1881
+ " ",
1882
+ l.sendBack
1883
+ ]
1884
+ }
1885
+ )
1886
+ ] })
1887
+ ]
1888
+ }
1889
+ );
1890
+ }
1891
+ var shownValue = (v, field, nameOf) => {
1892
+ if (v === void 0 || v === null || v === "") return "\u2014";
1893
+ if (typeof v === "boolean") return v ? "\u2713" : "\u2717";
1894
+ if (field.type === "person" && typeof v === "string") return nameOf?.(v) ?? v;
1895
+ if (field.type === "select") return field.options?.find((o) => o.value === v)?.label ?? String(v);
1896
+ return typeof v === "object" ? JSON.stringify(v) : String(v);
1897
+ };
1898
+ function ApprovalEvidence({
1899
+ run,
1900
+ step,
1901
+ actions,
1902
+ nameOfDoc,
1903
+ l,
1904
+ testId
1905
+ }) {
1906
+ const state = run.stateOf(step.id);
1907
+ if (state?.status !== "done") return null;
1908
+ const approver = state.by ? run.nameOf?.(state.by) ?? l.someone : void 0;
1909
+ const evidence = state.approved ?? [];
1910
+ const titleOf = (id) => run.steps.find((s) => s.id === id)?.title ?? id;
1911
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2 rounded-md bg-muted/40 p-3", "data-testid": `${testId}-evidence`, children: [
1912
+ /* @__PURE__ */ jsxs("p", { className: "flex flex-wrap items-center gap-1.5 text-sm font-medium", children: [
1913
+ /* @__PURE__ */ jsx(ShieldCheck, { className: "size-4 text-primary" }),
1914
+ approver ? l.approvedBy.replace("{name}", approver) : null,
1915
+ state.at && /* @__PURE__ */ jsx("span", { className: "text-xs font-normal text-muted-foreground", children: l.approvedAt.replace("{date}", formatDate(state.at)) })
1916
+ ] }),
1917
+ evidence.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
1918
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: l.approvedWhat }),
1919
+ evidence.map((e) => /* @__PURE__ */ jsxs(
1920
+ "div",
1921
+ {
1922
+ className: "flex flex-wrap items-center gap-1.5 text-xs",
1923
+ "data-testid": `${testId}-evidence-${e.step}`,
1924
+ children: [
1925
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: titleOf(e.step) }),
1926
+ e.response_id && (actions?.openMessage ? /* @__PURE__ */ jsxs(
1927
+ "button",
1928
+ {
1929
+ type: "button",
1930
+ onClick: () => actions.openMessage?.(e.response_id),
1931
+ className: "inline-flex items-center gap-1 text-primary hover:underline",
1932
+ "data-testid": `${testId}-evidence-${e.step}-answer`,
1933
+ children: [
1934
+ /* @__PURE__ */ jsx(MessageSquare, { className: "size-3" }),
1935
+ " ",
1936
+ l.viewAnswer
1937
+ ]
1938
+ }
1939
+ ) : null),
1940
+ (e.documents ?? []).map((d) => /* @__PURE__ */ jsx(DocChip, { docRef: d, actions, nameOfDoc }, d))
1941
+ ]
1942
+ },
1943
+ e.step
1944
+ ))
1945
+ ] }),
1946
+ (step.fields ?? []).length > 0 && /* @__PURE__ */ jsx("dl", { className: "grid grid-cols-[auto_1fr] gap-x-3 gap-y-0.5 text-xs", children: (step.fields ?? []).map((f) => /* @__PURE__ */ jsxs("div", { className: "contents", children: [
1947
+ /* @__PURE__ */ jsx("dt", { className: "text-muted-foreground", children: f.label }),
1948
+ /* @__PURE__ */ jsx("dd", { "data-testid": `${testId}-evidence-field-${f.name}`, children: shownValue(run.inputs[f.name], f, run.nameOf) })
1949
+ ] }, f.name)) })
1950
+ ] });
1951
+ }
1952
+ function RedlineOpen({
1953
+ step,
1954
+ state,
1955
+ actions,
1956
+ l,
1957
+ testId
1958
+ }) {
1959
+ const version = state?.status === "done" ? state.documents?.[0] : void 0;
1960
+ if (!step.editDocument || !version || !actions?.openRedline) return null;
1961
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", "data-testid": `${testId}-redline`, children: [
1962
+ /* @__PURE__ */ jsxs(
1963
+ Button,
1964
+ {
1965
+ size: "sm",
1966
+ onClick: () => actions.openRedline?.(version),
1967
+ "data-testid": `${testId}-open-word`,
1968
+ children: [
1969
+ /* @__PURE__ */ jsx(FileText, { className: "size-4" }),
1970
+ " ",
1971
+ l.openInWord
1972
+ ]
1973
+ }
1974
+ ),
1975
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: l.redlineHint })
1976
+ ] });
1977
+ }
1978
+ function ActionStepBody({
1979
+ step,
1980
+ run,
1981
+ actions,
1982
+ nameOfDoc,
1983
+ heading,
1984
+ l,
1985
+ testId
1986
+ }) {
1987
+ const state = run.stateOf(step.id);
1988
+ const phase = run.phaseOf(step.id);
1989
+ const name = (id) => run.nameOf?.(id) ?? id;
1990
+ const [declining, setDeclining] = useState(false);
1991
+ const [note, setNote] = useState("");
1992
+ const a = step.action;
1993
+ const working = (text) => /* @__PURE__ */ jsxs(
1994
+ "p",
1995
+ {
1996
+ className: "flex items-center gap-2 text-sm text-muted-foreground",
1997
+ "data-testid": `${testId}-working`,
1998
+ children: [
1999
+ /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin" }),
2000
+ " ",
2001
+ text
2002
+ ]
2003
+ }
2004
+ );
2005
+ let body = null;
2006
+ if (a.type === "sign") {
2007
+ const waiting = state?.waiting;
2008
+ const signers = waiting?.signers ?? [];
2009
+ const signed = new Set(waiting?.signed ?? []);
2010
+ const mine = !actions?.me || signers.includes(actions.me) && !signed.has(actions.me);
2011
+ const signedRef = state?.status === "done" ? state.documents?.[0] : void 0;
2012
+ body = /* @__PURE__ */ jsxs(Fragment, { children: [
2013
+ (phase === "ready" || phase === "running") && working(l.preparingSign),
2014
+ phase === "waiting" && waiting?.doc_ext_id && /* @__PURE__ */ jsx(
2015
+ DocChip,
2016
+ {
2017
+ docRef: `${waiting.doc_ext_id}${waiting.version ? `@v${waiting.version}` : ""}`,
2018
+ actions,
2019
+ nameOfDoc,
2020
+ testId: `${testId}-document`
2021
+ }
2022
+ ),
2023
+ signers.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-1", "data-testid": `${testId}-signers`, children: [
2024
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: l.signers }),
2025
+ signers.map((id) => /* @__PURE__ */ jsxs(
2026
+ "p",
2027
+ {
2028
+ className: "flex items-center gap-2 text-sm",
2029
+ "data-signed": signed.has(id) || void 0,
2030
+ "data-testid": `${testId}-signer-${id}`,
2031
+ children: [
2032
+ signed.has(id) ? /* @__PURE__ */ jsx(Check, { className: "size-3.5 text-primary" }) : /* @__PURE__ */ jsx(Hourglass, { className: "size-3.5 text-muted-foreground" }),
2033
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: name(id) }),
2034
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: signed.has(id) ? l.signed : l.notSigned })
2035
+ ]
2036
+ },
2037
+ id
2038
+ ))
2039
+ ] }),
2040
+ waiting?.expires_at && phase === "waiting" && /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
2041
+ /* @__PURE__ */ jsx(Clock, { className: "size-3.5" }),
2042
+ " ",
2043
+ l.expires.replace("{date}", formatDate(waiting.expires_at))
2044
+ ] }),
2045
+ phase === "waiting" && mine && !declining && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-2", children: [
2046
+ actions?.sign && state && /* @__PURE__ */ jsxs(
2047
+ Button,
2048
+ {
2049
+ size: "sm",
2050
+ onClick: () => actions.sign?.(step, state),
2051
+ "data-testid": `${testId}-open-sign`,
2052
+ children: [
2053
+ /* @__PURE__ */ jsx(PenLine, { className: "size-4" }),
2054
+ " ",
2055
+ l.openAndSign
2056
+ ]
2057
+ }
2058
+ ),
2059
+ run.decline && /* @__PURE__ */ jsxs(
2060
+ Button,
2061
+ {
2062
+ size: "sm",
2063
+ variant: "ghost",
2064
+ onClick: () => setDeclining(true),
2065
+ "data-testid": `${testId}-decline`,
2066
+ children: [
2067
+ /* @__PURE__ */ jsx(X, { className: "size-4" }),
2068
+ " ",
2069
+ l.decline
2070
+ ]
2071
+ }
2072
+ )
2073
+ ] }),
2074
+ declining && /* @__PURE__ */ jsxs("div", { className: "space-y-2", "data-testid": `${testId}-decline-form`, children: [
2075
+ /* @__PURE__ */ jsx(
2076
+ Textarea,
2077
+ {
2078
+ rows: 2,
2079
+ value: note,
2080
+ placeholder: l.declineNote,
2081
+ "aria-label": l.declineNote,
2082
+ onChange: (e) => setNote(e.target.value),
2083
+ "data-testid": `${testId}-decline-note`
2084
+ }
2085
+ ),
2086
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
2087
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => setDeclining(false), children: l.cancel }),
2088
+ /* @__PURE__ */ jsx(
2089
+ Button,
2090
+ {
2091
+ size: "sm",
2092
+ variant: "destructive",
2093
+ disabled: run.busy,
2094
+ onClick: async () => {
2095
+ await run.decline?.(step.id, note.trim() || void 0);
2096
+ setDeclining(false);
2097
+ },
2098
+ "data-testid": `${testId}-decline-confirm`,
2099
+ children: l.declineConfirm
2100
+ }
2101
+ )
2102
+ ] })
2103
+ ] }),
2104
+ phase === "failed" && state?.declinedBy && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", "data-testid": `${testId}-declined`, children: l.declined.replace("{name}", name(state.declinedBy)) }),
2105
+ signedRef && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-2", children: actions?.openDocument ? /* @__PURE__ */ jsxs(
2106
+ Button,
2107
+ {
2108
+ size: "sm",
2109
+ variant: "outline",
2110
+ onClick: () => actions.openDocument?.(signedRef),
2111
+ "data-testid": `${testId}-signed-version`,
2112
+ children: [
2113
+ /* @__PURE__ */ jsx(ExternalLink, { className: "size-4" }),
2114
+ " ",
2115
+ l.signedVersion
2116
+ ]
2117
+ }
2118
+ ) : /* @__PURE__ */ jsx(DocChip, { docRef: signedRef, nameOfDoc }) })
2119
+ ] });
2120
+ } else if (a.type === "email_draft") {
2121
+ const draftId = state?.waiting?.draft_id ?? (typeof state?.output?.draft_id === "string" ? state.output.draft_id : void 0);
2122
+ body = /* @__PURE__ */ jsxs(Fragment, { children: [
2123
+ (phase === "ready" || phase === "running") && working(l.preparingDraft),
2124
+ phase === "waiting" && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: l.draftReady }),
2125
+ phase === "done" && /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1.5 text-sm", "data-testid": `${testId}-sent`, children: [
2126
+ /* @__PURE__ */ jsx(Check, { className: "size-4 text-primary" }),
2127
+ " ",
2128
+ l.emailSent
2129
+ ] }),
2130
+ draftId && actions?.openDraft && phase !== "done" && /* @__PURE__ */ jsxs(
2131
+ Button,
2132
+ {
2133
+ size: "sm",
2134
+ variant: "outline",
2135
+ onClick: () => actions.openDraft?.(draftId, state?.waiting?.mailbox),
2136
+ "data-testid": `${testId}-open-draft`,
2137
+ children: [
2138
+ /* @__PURE__ */ jsx(Mail, { className: "size-4" }),
2139
+ " ",
2140
+ l.openDraft
2141
+ ]
2142
+ }
2143
+ )
2144
+ ] });
2145
+ } else {
2146
+ const tags = state?.output?.tag_ext_ids ?? a.tags;
2147
+ const docs = state?.output?.doc_ext_ids ?? [];
2148
+ body = /* @__PURE__ */ jsxs(Fragment, { children: [
2149
+ (phase === "ready" || phase === "running") && working(l.reviewRunning),
2150
+ phase === "done" && /* @__PURE__ */ jsxs(Fragment, { children: [
2151
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", "data-testid": `${testId}-reviewed`, children: l.reviewDone.replace("{docs}", String(docs.length)).replace("{cols}", String(tags.length)) }),
2152
+ actions?.openReview && /* @__PURE__ */ jsxs(
2153
+ Button,
2154
+ {
2155
+ size: "sm",
2156
+ onClick: () => actions.openReview?.(tags, docs),
2157
+ "data-testid": `${testId}-open-review`,
2158
+ children: [
2159
+ /* @__PURE__ */ jsx(Table2, { className: "size-4" }),
2160
+ " ",
2161
+ l.openReview
2162
+ ]
2163
+ }
2164
+ )
2165
+ ] })
2166
+ ] });
2167
+ }
2168
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
2169
+ heading,
2170
+ /* @__PURE__ */ jsx(SentBackNote, { state, l, testId }),
2171
+ /* @__PURE__ */ jsx(AssigneeLine, { run, stepId: step.id, l, testId }),
2172
+ body,
2173
+ phase === "failed" && !state?.declinedBy && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", "data-testid": `${testId}-failed`, children: state?.error || "" })
2174
+ ] });
2175
+ }
2176
+ var DONE = "__done";
2177
+ function WorkflowStepBar({
2178
+ run,
2179
+ levels,
2180
+ doneTitle,
2181
+ doneIcon,
2182
+ icon,
2183
+ tid,
2184
+ labels
2185
+ }) {
2186
+ const l = { ...DEFAULT_STEP_BAR_LABELS, ...labels };
2187
+ const done = { id: DONE, title: doneTitle };
2188
+ const phaseOf = (id) => id === DONE ? run.isComplete ? "done" : "blocked" : run.phaseOf(id);
2189
+ const current = run.isComplete ? DONE : run.current;
2190
+ const waitingOf = (id) => {
2191
+ if (id === DONE || !["ready", "waiting"].includes(phaseOf(id))) return void 0;
2192
+ const assignee = run.assigneeOf(id);
2193
+ return assignee ? l.waitingFor.replace("{name}", assignee) : void 0;
2194
+ };
2195
+ const pill = (s, narrow = false) => {
2196
+ const phase = phaseOf(s.id);
2197
+ const isCurrent = s.id === current;
2198
+ const assignee = ["ready", "waiting"].includes(phase) ? run.assigneeOf(s.id) : void 0;
2199
+ const waiting = assignee ? l.waitingFor.replace("{name}", assignee) : void 0;
2200
+ const title = waiting ? `${s.title} \xB7 ${waiting}` : s.title;
2201
+ const name = `${title} (${l.phases[phase]})`;
2202
+ if (s.id === DONE)
2203
+ return /* @__PURE__ */ jsxs(
2204
+ "span",
2205
+ {
2206
+ className: cn(STEP_PILL, stepPillTone(phase, isCurrent)),
2207
+ "aria-current": isCurrent ? "step" : void 0,
2208
+ "aria-label": name,
2209
+ title: doneTitle,
2210
+ "data-phase": phase,
2211
+ "data-testid": tid("step-done"),
2212
+ children: [
2213
+ /* @__PURE__ */ jsx(PhaseMark, { phase: run.isComplete ? "done" : "ready", icon: doneIcon }),
2214
+ /* @__PURE__ */ jsx("span", { className: cn(STEP_LABEL, "max-w-[12rem]"), children: doneTitle })
2215
+ ]
2216
+ }
2217
+ );
2218
+ return /* @__PURE__ */ jsxs(
2219
+ "button",
2220
+ {
2221
+ type: "button",
2222
+ onClick: () => run.goTo(s.id),
2223
+ disabled: run.busy,
2224
+ className: cn(STEP_PILL, stepPillTone(phase, isCurrent), "disabled:cursor-default"),
2225
+ "aria-current": isCurrent ? "step" : void 0,
2226
+ "aria-label": name,
2227
+ title,
2228
+ "data-phase": phase,
2229
+ "data-testid": tid(`step-${s.id}`),
2230
+ children: [
2231
+ /* @__PURE__ */ jsx(PhaseMark, { phase, icon: icon(s.icon) }),
2232
+ /* @__PURE__ */ jsx(
2233
+ "span",
2234
+ {
2235
+ className: cn(STEP_LABEL, isCurrent ? "max-w-[18rem]" : "max-w-[12rem]"),
2236
+ "data-testid": tid(`step-${s.id}-label`),
2237
+ children: s.title
2238
+ }
2239
+ ),
2240
+ assignee && isCurrent && !narrow && /* @__PURE__ */ jsxs(
2241
+ "span",
2242
+ {
2243
+ className: cn(STEP_EXTRA, "max-w-[12rem]"),
2244
+ "data-testid": tid(`step-${s.id}-assignee`),
2245
+ children: [
2246
+ "\xB7 ",
2247
+ waiting
2248
+ ]
2249
+ }
2250
+ )
2251
+ ]
2252
+ }
2253
+ );
2254
+ };
2255
+ return /* @__PURE__ */ jsx(
2256
+ StepBarFrame,
2257
+ {
2258
+ levels: [...levels, [done]],
2259
+ phaseOf,
2260
+ current,
2261
+ onSelect: (id) => id !== DONE && run.goTo(id),
2262
+ isSelectable: (id) => id !== DONE,
2263
+ renderStep: (s, _level, _win, narrow) => pill(s, narrow),
2264
+ noteOf: waitingOf,
2265
+ icon: (s) => s.id === DONE ? doneIcon : icon(s.icon),
2266
+ disabled: run.busy,
2267
+ labels,
2268
+ tid,
2269
+ className: "w-full"
2270
+ }
2271
+ );
2272
+ }
2273
+ var DEFAULT_WORKFLOW_RUNNER_LABELS = {
2274
+ back: "Back",
2275
+ next: "Continue",
2276
+ skip: "Skip",
2277
+ skipStep: "Skip this step",
2278
+ finish: "Finish",
2279
+ approve: "Approve",
2280
+ working: "Working\u2026",
2281
+ stepWorking: "{step}\u2026 {elapsed}",
2282
+ starting: "Starting\u2026",
2283
+ retry: "Retry",
2284
+ failed: "This step did not finish.",
2285
+ blocked: "Starts after {steps}.",
2286
+ awaitingReview: "Waiting for approval.",
2287
+ approved: "Approved.",
2288
+ skipped: "Skipped.",
2289
+ branch: "Runs alongside; its result joins the thread",
2290
+ complete: "Done",
2291
+ restart: "Start again",
2292
+ filesHeading: "Already in the workspace ({count})",
2293
+ addedHeading: "Added for this step ({count})",
2294
+ noFiles: "No files yet.",
2295
+ fileReading: "Reading\u2026",
2296
+ fileReady: "Ready",
2297
+ fileFailed: "Could not be read",
2298
+ readingFiles: "Reading {current} of {count} files\u2026",
2299
+ markSent: "Mark as sent"
2300
+ };
2301
+ function WorkflowRunner({
2302
+ workflow,
2303
+ run,
2304
+ renderAgentOutput,
2305
+ renderReview,
2306
+ onUpload,
2307
+ uploading = false,
2308
+ files,
2309
+ renderUpload,
2310
+ renderStepNote,
2311
+ renderComplete,
2312
+ renderScope,
2313
+ renderDocuments,
2314
+ renderIcon,
2315
+ people,
2316
+ actions,
2317
+ nameOfDoc,
2318
+ hideStepBar,
2319
+ compact: compact3,
2320
+ labels,
2321
+ partsLabels,
2322
+ testIdPrefix = "workflow-run",
2323
+ className
2324
+ }) {
2325
+ const l = { ...DEFAULT_WORKFLOW_RUNNER_LABELS, ...labels };
2326
+ const pl = { ...DEFAULT_WORKFLOW_STEP_PARTS_LABELS, ...partsLabels };
2327
+ const box = compact3 ? "p-4" : "max-w-2xl p-6";
2328
+ const { steps, step, isComplete, action } = run;
2329
+ const completion = workflow.run.kind === "steps" ? workflow.run.completion : void 0;
2330
+ const tid = (suffix) => `${testIdPrefix}-${suffix}`;
2331
+ const levels = stepLevels(steps);
2332
+ const icon = (key) => key && renderIcon ? renderIcon(key) : void 0;
2333
+ const added = step?.kind === "upload" ? (files ?? []).filter((f) => f.addedFor === step.id) : [];
2334
+ const unread = added.filter((f) => f.status === "processing").length;
2335
+ const reading = action === "complete" && unread > 0;
2336
+ const nextLabel = reading ? l.readingFiles.replace("{current}", String(Math.min(added.length - unread + 1, added.length))).replace("{count}", String(added.length)) : action === "skip" ? l.skip : action === "finish" ? l.finish : action === "complete" && step?.kind === "review" ? l.approve : action === "complete" && step?.kind === "action" ? l.markSent : action === "wait" && (run.isRunning || run.busy) ? l.working : (step && !run.isLocked(step.id) ? step.nextLabel : void 0) ?? l.next;
2337
+ return /* @__PURE__ */ jsxs("div", { className: cn(compact3 ? "space-y-3" : "space-y-6", className), "data-testid": testIdPrefix, children: [
2338
+ !hideStepBar && /* @__PURE__ */ jsx(
2339
+ WorkflowStepBar,
2340
+ {
2341
+ run,
2342
+ levels,
2343
+ doneTitle: completion?.title ?? l.complete,
2344
+ doneIcon: icon(completion?.icon),
2345
+ icon,
2346
+ tid
2347
+ }
2348
+ ),
2349
+ isComplete ? renderComplete?.(run) ?? /* @__PURE__ */ jsxs(Card, { className: "mx-auto max-w-lg p-8 text-center", "data-testid": tid("done"), children: [
2350
+ /* @__PURE__ */ jsx(CheckCircle2, { className: "mx-auto size-12 text-primary" }),
2351
+ /* @__PURE__ */ jsx("h2", { className: "mt-4 text-xl font-semibold", children: completion?.title ?? l.complete }),
2352
+ completion?.description && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: completion.description }),
2353
+ /* @__PURE__ */ jsx(
2354
+ Button,
2355
+ {
2356
+ variant: "outline",
2357
+ className: "mt-6",
2358
+ onClick: run.restart,
2359
+ "data-testid": tid("restart"),
2360
+ children: l.restart
2361
+ }
2362
+ )
2363
+ ] }) : step ? /* @__PURE__ */ jsx("div", { className: "space-y-3", children: run.phaseOf(step.id) === "blocked" ? /* @__PURE__ */ jsx(Card, { className: cn(box, "p-6"), "data-testid": tid(step.id), children: /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", "data-testid": tid(`${step.id}-blocked`), children: l.blocked.replace(
2364
+ "{steps}",
2365
+ stepDependencies(steps, step.id).map((d) => steps.find((s) => s.id === d)?.title ?? d).join(", ")
2366
+ ) }) }) : step.kind === "form" ? /* @__PURE__ */ jsxs(Card, { className: cn(box, "space-y-4"), "data-testid": tid(step.id), children: [
2367
+ /* @__PURE__ */ jsx(StepHeading, { step, icon: icon(step.icon), testId: tid(`${step.id}-title`) }),
2368
+ /* @__PURE__ */ jsx(SentBackNote, { state: run.stateOf(step.id), l: pl, testId: tid(step.id) }),
2369
+ /* @__PURE__ */ jsx(AssigneeLine, { run, stepId: step.id, l: pl, testId: tid(step.id) }),
2370
+ /* @__PURE__ */ jsx("fieldset", { disabled: run.isLocked(step.id), className: "contents", children: /* @__PURE__ */ jsx(
2371
+ SchemaForm,
2372
+ {
2373
+ fields: step.inputs,
2374
+ values: run.inputs,
2375
+ onChange: run.setInput,
2376
+ errors: run.errors,
2377
+ renderScope,
2378
+ renderDocuments,
2379
+ renderIcon,
2380
+ people,
2381
+ onEnterSubmit: () => void run.next(),
2382
+ testIdPrefix: tid("field")
2383
+ }
2384
+ ) }),
2385
+ run.actionError && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", "data-testid": tid("action-error"), children: run.actionError }),
2386
+ renderStepNote?.(step, run)
2387
+ ] }) : step.kind === "upload" ? /* @__PURE__ */ jsxs(Card, { className: cn(box, "space-y-4"), "data-testid": tid(step.id), children: [
2388
+ /* @__PURE__ */ jsx(SentBackNote, { state: run.stateOf(step.id), l: pl, testId: tid(step.id) }),
2389
+ /* @__PURE__ */ jsx(AssigneeLine, { run, stepId: step.id, l: pl, testId: tid(step.id) }),
2390
+ renderUpload?.(step) ?? /* @__PURE__ */ jsxs(Fragment, { children: [
2391
+ /* @__PURE__ */ jsx(
2392
+ UploadDropzone,
2393
+ {
2394
+ onFiles: (picked) => void onUpload?.(picked, step),
2395
+ uploading,
2396
+ disabled: !onUpload,
2397
+ accept: step.accept,
2398
+ title: step.title,
2399
+ description: step.description,
2400
+ hint: step.hint,
2401
+ testId: tid(`${step.id}-drop`)
2402
+ }
2403
+ ),
2404
+ files && /* @__PURE__ */ jsx(
2405
+ UploadFiles,
2406
+ {
2407
+ added,
2408
+ others: files.filter((f) => f.addedFor !== step.id),
2409
+ l,
2410
+ testId: tid(`${step.id}-files`)
2411
+ }
2412
+ )
2413
+ ] }),
2414
+ run.actionError && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: run.actionError }),
2415
+ renderStepNote?.(step, run)
2416
+ ] }) : step.kind === "review" ? /* @__PURE__ */ jsxs(Card, { className: cn(box, "space-y-4"), "data-testid": tid(step.id), children: [
2417
+ /* @__PURE__ */ jsx(StepHeading, { step, icon: icon(step.icon) }),
2418
+ /* @__PURE__ */ jsx(SentBackNote, { state: run.stateOf(step.id), l: pl, testId: tid(step.id) }),
2419
+ /* @__PURE__ */ jsx("p", { className: "whitespace-pre-line text-sm text-muted-foreground", children: reviewInstructions(step, run.inputs) }),
2420
+ /* @__PURE__ */ jsx(AssigneeLine, { run, stepId: step.id, l: pl, testId: tid(step.id) }),
2421
+ !(run.phaseOf(step.id) === "done" && run.stateOf(step.id)?.by) && /* @__PURE__ */ jsx("p", { className: "text-sm", "data-testid": tid(`${step.id}-status`), children: run.phaseOf(step.id) === "done" ? l.approved : run.phaseOf(step.id) === "skipped" ? l.skipped : l.awaitingReview }),
2422
+ (step.fields ?? []).length > 0 && run.phaseOf(step.id) === "ready" && /* @__PURE__ */ jsx(
2423
+ SchemaForm,
2424
+ {
2425
+ fields: step.fields ?? [],
2426
+ values: run.inputs,
2427
+ onChange: run.setInput,
2428
+ errors: run.errors,
2429
+ renderScope,
2430
+ renderDocuments,
2431
+ renderIcon,
2432
+ people,
2433
+ testIdPrefix: tid("field")
2434
+ }
2435
+ ),
2436
+ /* @__PURE__ */ jsx(
2437
+ ApprovalEvidence,
2438
+ {
2439
+ run,
2440
+ step,
2441
+ actions,
2442
+ nameOfDoc,
2443
+ l: pl,
2444
+ testId: tid(step.id)
2445
+ }
2446
+ ),
2447
+ run.phaseOf(step.id) === "ready" && renderReview?.(step),
2448
+ /* @__PURE__ */ jsx(SendBackControl, { run, step, l: pl, testId: tid(step.id) }),
2449
+ run.actionError && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: run.actionError }),
2450
+ renderStepNote?.(step, run)
2451
+ ] }) : step.kind === "action" ? /* @__PURE__ */ jsxs(Card, { className: cn(box, "space-y-4"), "data-testid": tid(step.id), children: [
2452
+ /* @__PURE__ */ jsx(
2453
+ ActionStepBody,
2454
+ {
2455
+ step,
2456
+ run,
2457
+ actions,
2458
+ nameOfDoc,
2459
+ heading: /* @__PURE__ */ jsx(StepHeading, { step, icon: icon(step.icon) }),
2460
+ l: pl,
2461
+ testId: tid(step.id)
2462
+ }
2463
+ ),
2464
+ run.actionError && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", children: run.actionError }),
2465
+ renderStepNote?.(step, run)
2466
+ ] }) : /* @__PURE__ */ jsx(
2467
+ AgentStepBody,
2468
+ {
2469
+ step,
2470
+ run,
2471
+ renderAgentOutput,
2472
+ icon: icon(step.icon),
2473
+ renderStepNote,
2474
+ compact: compact3,
2475
+ extra: /* @__PURE__ */ jsxs(Fragment, { children: [
2476
+ /* @__PURE__ */ jsx(SentBackNote, { state: run.stateOf(step.id), l: pl, testId: tid(step.id) }),
2477
+ /* @__PURE__ */ jsx(
2478
+ RedlineOpen,
2479
+ {
2480
+ step,
2481
+ state: run.stateOf(step.id),
2482
+ actions,
2483
+ l: pl,
2484
+ testId: tid(step.id)
2485
+ }
2486
+ )
2487
+ ] }),
2488
+ l,
2489
+ tid
2490
+ }
2491
+ ) }, step.id) : null,
2492
+ !isComplete && /* @__PURE__ */ jsxs(
2493
+ "div",
2494
+ {
2495
+ className: cn(
2496
+ "flex items-center justify-between border-t border-border pt-4",
2497
+ compact3 && "flex-wrap gap-2 pt-3"
2498
+ ),
2499
+ children: [
2500
+ /* @__PURE__ */ jsxs(
2501
+ Button,
2502
+ {
2503
+ variant: "ghost",
2504
+ onClick: run.back,
2505
+ disabled: !run.canBack,
2506
+ "data-testid": tid("back"),
2507
+ children: [
2508
+ /* @__PURE__ */ jsx(ArrowLeft, { className: "size-4" }),
2509
+ " ",
2510
+ l.back
2511
+ ]
2512
+ }
2513
+ ),
2514
+ /* @__PURE__ */ jsxs(
2515
+ "div",
2516
+ {
2517
+ className: cn("flex items-center gap-2", compact3 && "ml-auto flex-wrap justify-end"),
2518
+ children: [
2519
+ step && action !== "skip" && // A step that commits (creates the matter, starts the thread) is what the run stands on.
2520
+ !step.commit && ["ready", "blocked", "failed"].includes(run.phaseOf(step.id)) && /* @__PURE__ */ jsx(
2521
+ Button,
2522
+ {
2523
+ variant: "ghost",
2524
+ onClick: () => void run.skip(step.id),
2525
+ disabled: run.busy,
2526
+ "data-testid": tid("skip-step"),
2527
+ children: l.skipStep
2528
+ }
2529
+ ),
2530
+ /* @__PURE__ */ jsxs(
2531
+ Button,
2532
+ {
2533
+ onClick: () => void run.next(),
2534
+ disabled: action === "wait" || reading,
2535
+ "data-testid": tid("next"),
2536
+ children: [
2537
+ nextLabel,
2538
+ reading || action === "wait" && (run.isRunning || run.busy) ? /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin" }) : /* @__PURE__ */ jsx(ArrowRight, { className: "size-4" })
2539
+ ]
2540
+ }
2541
+ )
2542
+ ]
2543
+ }
2544
+ )
2545
+ ]
2546
+ }
2547
+ )
2548
+ ] });
2549
+ }
2550
+ function UploadFiles({
2551
+ added,
2552
+ others,
2553
+ l,
2554
+ testId
2555
+ }) {
2556
+ const heading = "text-xs font-semibold uppercase tracking-wide text-muted-foreground";
2557
+ const row = (f) => /* @__PURE__ */ jsxs(
2558
+ "div",
2559
+ {
2560
+ className: "flex items-center gap-2 text-sm text-muted-foreground",
2561
+ "data-status": f.status,
2562
+ "data-testid": `${testId}-file-${f.id}`,
2563
+ children: [
2564
+ /* @__PURE__ */ jsx(FileText, { className: "size-3.5 shrink-0 text-primary" }),
2565
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate", title: f.name, children: f.name }),
2566
+ f.status === "processing" && /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1 text-xs", children: [
2567
+ /* @__PURE__ */ jsx(Loader2, { className: "size-3 animate-spin" }),
2568
+ " ",
2569
+ l.fileReading
2570
+ ] }),
2571
+ f.status === "ready" && /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1 text-xs text-primary", children: [
2572
+ /* @__PURE__ */ jsx(Check, { className: "size-3" }),
2573
+ " ",
2574
+ l.fileReady
2575
+ ] }),
2576
+ f.status === "failed" && /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-1 text-xs text-destructive", children: [
2577
+ /* @__PURE__ */ jsx(AlertTriangle, { className: "size-3" }),
2578
+ " ",
2579
+ l.fileFailed
2580
+ ] })
2581
+ ]
2582
+ },
2583
+ f.id
2584
+ );
2585
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", "data-testid": testId, children: [
2586
+ added.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-1", "data-testid": `${testId}-added`, children: [
2587
+ /* @__PURE__ */ jsx("p", { className: heading, children: l.addedHeading.replace("{count}", String(added.length)) }),
2588
+ added.map(row)
2589
+ ] }),
2590
+ added.length === 0 && others.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: l.noFiles }),
2591
+ others.length > 0 && /* @__PURE__ */ jsxs("details", { className: "space-y-1", "data-testid": `${testId}-others`, children: [
2592
+ /* @__PURE__ */ jsx("summary", { className: cn(heading, "cursor-pointer select-none"), children: l.filesHeading.replace("{count}", String(others.length)) }),
2593
+ /* @__PURE__ */ jsx("div", { className: "mt-1 max-h-48 space-y-1 overflow-y-auto", children: others.map(row) })
2594
+ ] })
2595
+ ] });
2596
+ }
2597
+ function StepHeading({
2598
+ step,
2599
+ icon,
2600
+ testId
2601
+ }) {
2602
+ return /* @__PURE__ */ jsxs("div", { className: "min-w-0", children: [
2603
+ /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-2 text-sm font-medium", "data-testid": testId, children: [
2604
+ icon && /* @__PURE__ */ jsx("span", { className: "flex size-4 items-center justify-center text-primary [&_svg]:size-4", children: icon }),
2605
+ step.title
2606
+ ] }),
2607
+ step.description && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: step.description })
2608
+ ] });
2609
+ }
2610
+ function formatElapsed(ms) {
2611
+ const total = Math.max(0, Math.floor(ms / 1e3));
2612
+ const h = Math.floor(total / 3600);
2613
+ const m = Math.floor(total % 3600 / 60);
2614
+ const sec = String(total % 60).padStart(2, "0");
2615
+ return h ? `${h}:${String(m).padStart(2, "0")}:${sec}` : `${m}:${sec}`;
2616
+ }
2617
+ function StepWorking({ title, since, label }) {
2618
+ const seen = useRef(Date.now());
2619
+ const start = since && Date.parse(since) || seen.current;
2620
+ const [now, setNow] = useState(() => Date.now());
2621
+ useEffect(() => {
2622
+ const t = setInterval(() => setNow(Date.now()), 1e3);
2623
+ return () => clearInterval(t);
2624
+ }, []);
2625
+ return /* @__PURE__ */ jsx("span", { children: label.replace("{step}", title).replace("{elapsed}", formatElapsed(now - start)) });
2626
+ }
2627
+ function AgentStepBody({
2628
+ step,
2629
+ run,
2630
+ renderAgentOutput,
2631
+ icon,
2632
+ renderStepNote,
2633
+ extra,
2634
+ compact: compact3,
2635
+ l,
2636
+ tid
2637
+ }) {
2638
+ const state = run.stateOf(step.id);
2639
+ const phase = run.phaseOf(step.id);
2640
+ const header = /* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
2641
+ /* @__PURE__ */ jsx(StepHeading, { step, icon }),
2642
+ phase === "failed" && /* @__PURE__ */ jsx("div", { className: "flex shrink-0 items-center gap-2", children: run.retry && /* @__PURE__ */ jsxs(
2643
+ Button,
2644
+ {
2645
+ variant: "outline",
2646
+ size: "sm",
2647
+ onClick: () => void run.retry?.(step.id),
2648
+ disabled: run.busy,
2649
+ "data-testid": tid(`${step.id}-retry`),
2650
+ children: [
2651
+ /* @__PURE__ */ jsx(RotateCw, { className: "size-4" }),
2652
+ " ",
2653
+ l.retry
2654
+ ]
2655
+ }
2656
+ ) })
2657
+ ] });
2658
+ const status = /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2659
+ step.branch && /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
2660
+ /* @__PURE__ */ jsx(GitBranch, { className: "size-3.5" }),
2661
+ " ",
2662
+ l.branch
2663
+ ] }),
2664
+ (phase === "ready" || phase === "running") && !state?.text && /* @__PURE__ */ jsxs(
2665
+ "p",
2666
+ {
2667
+ className: "flex items-center gap-2 text-sm text-muted-foreground",
2668
+ "data-testid": tid(`${step.id}-working`),
2669
+ children: [
2670
+ /* @__PURE__ */ jsx(Loader2, { className: "size-4 animate-spin" }),
2671
+ " ",
2672
+ phase === "ready" ? l.starting : /* @__PURE__ */ jsx(StepWorking, { title: step.title, since: state?.startedAt, label: l.stepWorking })
2673
+ ]
2674
+ }
2675
+ ),
2676
+ phase === "failed" && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive", "data-testid": tid(`${step.id}-failed`), children: state?.error || l.failed }),
2677
+ phase === "skipped" && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: l.skipped }),
2678
+ extra
2679
+ ] });
2680
+ if (step.output === "artifact" && !compact3) {
2681
+ return /* @__PURE__ */ jsxs(
2682
+ "div",
2683
+ {
2684
+ className: "grid gap-4 lg:grid-cols-[minmax(0,18rem)_minmax(0,1fr)]",
2685
+ "data-testid": tid(step.id),
2686
+ children: [
2687
+ /* @__PURE__ */ jsxs(Card, { className: "h-fit space-y-3 p-5", children: [
2688
+ header,
2689
+ status,
2690
+ renderStepNote?.(step, run)
2691
+ ] }),
2692
+ /* @__PURE__ */ jsx("div", { className: "min-w-0", children: renderAgentOutput(step, state) })
2693
+ ]
2694
+ }
2695
+ );
2696
+ }
2697
+ return /* @__PURE__ */ jsxs(Card, { className: cn("space-y-3", compact3 ? "p-4" : "p-6"), "data-testid": tid(step.id), children: [
2698
+ header,
2699
+ status,
2700
+ renderAgentOutput(step, state),
2701
+ renderStepNote?.(step, run)
2702
+ ] });
2703
+ }
2704
+
2705
+ export { ActionStepBody, ApprovalEvidence, AssigneeLine, DEFAULT_ASSIGNEE_WORDING, DEFAULT_SCHEMA_FORM_LABELS, DEFAULT_STEP_BAR_LABELS, DEFAULT_WORKFLOW_RUNNER_LABELS, DEFAULT_WORKFLOW_STEP_PARTS_LABELS, EMPTY_WORKFLOW_SCOPE, PhaseMark, SKILL_PROMPT_FALLBACK_PLACEHOLDER, SKILL_PROMPT_LABEL, STEP_EXTRA, STEP_LABEL, STEP_PILL, SchemaForm, SendBackControl, StepBarFrame, WorkflowRunner, WorkflowStepBar, activeStepId, assigneeName, buildLaunch, canAssign, composeStepPrompt, composeWorkflowPrompt, defaultInputs, dependsOn, fieldToFrontmatter, fillRunText, fillStepInputs, fromPlanAssignee, fromPlanDocumentRef, fromPlanSteps, initialRunInputs, inputsFromContext, isEmptyWorkflowScope, isIncompleteSignal, isPersonAction, isPersonStep, isRunFinished, isSteppedWorkflow, isWorkflowScope, layoutFromPlanSteps, nextAction, orderSteps, promptFieldName, reviewInstructions, runFields, runFieldsOf, runThreadTitle, sendBackTargets, skillToWorkflow, splitDocRef, stepConfigProblems, stepDependencies, stepFields, stepLevels, stepPhase, stepPillTone, stepStatesFromActivity, stepWindow, toPlanAssignee, toPlanDocumentRef, toPlanSteps, upstreamSteps, useWorkflowView, validateInputs, validateStep, validateWorkflowGraph, workflowAccepts, workflowFromFrontmatter, workflowSteps, workflowsAccepting, workflowsForHost };
2706
+ //# sourceMappingURL=chunk-WXRCMIAE.js.map
2707
+ //# sourceMappingURL=chunk-WXRCMIAE.js.map