@geenius/ai-workflow 0.1.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/README.md +69 -1
  3. package/package.json +60 -27
  4. package/packages/convex/dist/index.d.ts +363 -0
  5. package/packages/convex/dist/index.js +200 -0
  6. package/packages/convex/dist/index.js.map +1 -0
  7. package/packages/react/dist/index.d.ts +514 -0
  8. package/packages/react/dist/index.js +2633 -0
  9. package/packages/react/dist/index.js.map +1 -0
  10. package/packages/react-css/{src/styles.css → dist/index.css} +107 -253
  11. package/packages/react-css/dist/index.css.map +1 -0
  12. package/packages/react-css/dist/index.d.ts +533 -0
  13. package/packages/react-css/dist/index.js +2620 -0
  14. package/packages/react-css/dist/index.js.map +1 -0
  15. package/packages/shared/dist/index.d.ts +1305 -0
  16. package/packages/shared/dist/index.js +1594 -0
  17. package/packages/shared/dist/index.js.map +1 -0
  18. package/packages/solidjs/dist/index.d.ts +492 -0
  19. package/packages/solidjs/dist/index.js +2552 -0
  20. package/packages/solidjs/dist/index.js.map +1 -0
  21. package/packages/solidjs-css/{src/styles.css → dist/index.css} +107 -253
  22. package/packages/solidjs-css/dist/index.css.map +1 -0
  23. package/packages/solidjs-css/dist/index.d.ts +509 -0
  24. package/packages/solidjs-css/dist/index.js +2493 -0
  25. package/packages/solidjs-css/dist/index.js.map +1 -0
  26. package/.changeset/config.json +0 -11
  27. package/.github/CODEOWNERS +0 -1
  28. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -16
  29. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -11
  30. package/.github/PULL_REQUEST_TEMPLATE.md +0 -10
  31. package/.github/dependabot.yml +0 -11
  32. package/.github/workflows/ci.yml +0 -23
  33. package/.github/workflows/release.yml +0 -29
  34. package/.nvmrc +0 -1
  35. package/.project/ACCOUNT.yaml +0 -4
  36. package/.project/IDEAS.yaml +0 -7
  37. package/.project/PROJECT.yaml +0 -11
  38. package/.project/ROADMAP.yaml +0 -15
  39. package/CODE_OF_CONDUCT.md +0 -16
  40. package/CONTRIBUTING.md +0 -26
  41. package/SECURITY.md +0 -15
  42. package/SUPPORT.md +0 -8
  43. package/packages/convex/README.md +0 -1
  44. package/packages/convex/package.json +0 -12
  45. package/packages/convex/src/convex.config.ts +0 -3
  46. package/packages/convex/src/index.ts +0 -3
  47. package/packages/convex/src/mutations.ts +0 -36
  48. package/packages/convex/src/queries.ts +0 -19
  49. package/packages/convex/src/schema.ts +0 -24
  50. package/packages/convex/tsconfig.json +0 -25
  51. package/packages/react/README.md +0 -1
  52. package/packages/react/package.json +0 -46
  53. package/packages/react/src/components/ApprovalModal.tsx +0 -47
  54. package/packages/react/src/components/StepConfigPanel.tsx +0 -67
  55. package/packages/react/src/components/StepConnector.tsx +0 -47
  56. package/packages/react/src/components/StepNode.tsx +0 -38
  57. package/packages/react/src/components/StepPalette.tsx +0 -48
  58. package/packages/react/src/components/WorkflowCanvas.tsx +0 -42
  59. package/packages/react/src/components/WorkflowRunPanel.tsx +0 -64
  60. package/packages/react/src/components/WorkflowToolbar.tsx +0 -43
  61. package/packages/react/src/components/index.ts +0 -9
  62. package/packages/react/src/hooks/index.ts +0 -10
  63. package/packages/react/src/hooks/useApprovalGate.ts +0 -59
  64. package/packages/react/src/hooks/useWorkflow.ts +0 -39
  65. package/packages/react/src/hooks/useWorkflowBuilder.ts +0 -121
  66. package/packages/react/src/hooks/useWorkflowRun.ts +0 -75
  67. package/packages/react/src/hooks/useWorkflowStep.ts +0 -52
  68. package/packages/react/src/hooks/useWorkflowTemplates.ts +0 -54
  69. package/packages/react/src/index.ts +0 -16
  70. package/packages/react/src/pages/WorkflowBuilderPage.tsx +0 -81
  71. package/packages/react/src/pages/WorkflowRunsPage.tsx +0 -59
  72. package/packages/react/src/pages/index.ts +0 -3
  73. package/packages/react/tsconfig.json +0 -1
  74. package/packages/react/tsup.config.ts +0 -7
  75. package/packages/react-css/README.md +0 -1
  76. package/packages/react-css/package.json +0 -44
  77. package/packages/react-css/src/components/ApprovalModal.tsx +0 -6
  78. package/packages/react-css/src/components/StepConfigPanel.tsx +0 -7
  79. package/packages/react-css/src/components/StepConnector.tsx +0 -6
  80. package/packages/react-css/src/components/StepNode.tsx +0 -7
  81. package/packages/react-css/src/components/StepPalette.tsx +0 -6
  82. package/packages/react-css/src/components/WorkflowCanvas.tsx +0 -6
  83. package/packages/react-css/src/components/WorkflowRunPanel.tsx +0 -9
  84. package/packages/react-css/src/components/WorkflowToolbar.tsx +0 -4
  85. package/packages/react-css/src/components/index.ts +0 -9
  86. package/packages/react-css/src/hooks/index.ts +0 -3
  87. package/packages/react-css/src/hooks/useWorkflow.ts +0 -39
  88. package/packages/react-css/src/hooks/useWorkflowBuilder.ts +0 -121
  89. package/packages/react-css/src/index.ts +0 -7
  90. package/packages/react-css/src/pages/WorkflowBuilderPage.tsx +0 -16
  91. package/packages/react-css/src/pages/WorkflowRunsPage.tsx +0 -6
  92. package/packages/react-css/src/pages/index.ts +0 -3
  93. package/packages/react-css/tsconfig.json +0 -26
  94. package/packages/react-css/tsup.config.ts +0 -2
  95. package/packages/shared/README.md +0 -1
  96. package/packages/shared/package.json +0 -56
  97. package/packages/shared/src/__tests__/ai-workflow.test.ts +0 -217
  98. package/packages/shared/src/config.ts +0 -49
  99. package/packages/shared/src/convex/index.ts +0 -2
  100. package/packages/shared/src/convex/schemas.ts +0 -42
  101. package/packages/shared/src/engine.test.ts +0 -1
  102. package/packages/shared/src/engine.ts +0 -295
  103. package/packages/shared/src/index.ts +0 -43
  104. package/packages/shared/src/steps.ts +0 -68
  105. package/packages/shared/src/templates.ts +0 -172
  106. package/packages/shared/src/types.ts +0 -237
  107. package/packages/shared/src/utils/cost.ts +0 -79
  108. package/packages/shared/src/utils/dag.ts +0 -133
  109. package/packages/shared/src/utils/index.ts +0 -5
  110. package/packages/shared/src/utils/interpolation.ts +0 -53
  111. package/packages/shared/src/validators.ts +0 -215
  112. package/packages/shared/tsconfig.json +0 -1
  113. package/packages/shared/tsup.config.ts +0 -5
  114. package/packages/shared/vitest.config.ts +0 -4
  115. package/packages/solidjs/README.md +0 -1
  116. package/packages/solidjs/package.json +0 -45
  117. package/packages/solidjs/src/components/ApprovalModal.tsx +0 -18
  118. package/packages/solidjs/src/components/StepConfigPanel.tsx +0 -14
  119. package/packages/solidjs/src/components/StepConnector.tsx +0 -11
  120. package/packages/solidjs/src/components/StepNode.tsx +0 -12
  121. package/packages/solidjs/src/components/StepPalette.tsx +0 -22
  122. package/packages/solidjs/src/components/WorkflowCanvas.tsx +0 -23
  123. package/packages/solidjs/src/components/WorkflowRunPanel.tsx +0 -18
  124. package/packages/solidjs/src/components/WorkflowToolbar.tsx +0 -13
  125. package/packages/solidjs/src/components/index.ts +0 -9
  126. package/packages/solidjs/src/index.ts +0 -7
  127. package/packages/solidjs/src/pages/WorkflowBuilderPage.tsx +0 -37
  128. package/packages/solidjs/src/pages/WorkflowRunsPage.tsx +0 -20
  129. package/packages/solidjs/src/pages/index.ts +0 -3
  130. package/packages/solidjs/src/primitives/createApprovalGate.ts +0 -29
  131. package/packages/solidjs/src/primitives/createWorkflow.ts +0 -28
  132. package/packages/solidjs/src/primitives/createWorkflowBuilder.ts +0 -56
  133. package/packages/solidjs/src/primitives/createWorkflowRun.ts +0 -32
  134. package/packages/solidjs/src/primitives/createWorkflowStep.ts +0 -23
  135. package/packages/solidjs/src/primitives/createWorkflowTemplates.ts +0 -28
  136. package/packages/solidjs/src/primitives/index.ts +0 -8
  137. package/packages/solidjs/tsconfig.json +0 -1
  138. package/packages/solidjs/tsup.config.ts +0 -7
  139. package/packages/solidjs-css/README.md +0 -1
  140. package/packages/solidjs-css/package.json +0 -43
  141. package/packages/solidjs-css/src/components/ApprovalModal.tsx +0 -6
  142. package/packages/solidjs-css/src/components/StepConfigPanel.tsx +0 -7
  143. package/packages/solidjs-css/src/components/StepConnector.tsx +0 -6
  144. package/packages/solidjs-css/src/components/StepNode.tsx +0 -7
  145. package/packages/solidjs-css/src/components/StepPalette.tsx +0 -7
  146. package/packages/solidjs-css/src/components/WorkflowCanvas.tsx +0 -7
  147. package/packages/solidjs-css/src/components/WorkflowRunPanel.tsx +0 -8
  148. package/packages/solidjs-css/src/components/WorkflowToolbar.tsx +0 -5
  149. package/packages/solidjs-css/src/components/index.ts +0 -9
  150. package/packages/solidjs-css/src/index.ts +0 -7
  151. package/packages/solidjs-css/src/pages/WorkflowBuilderPage.tsx +0 -2
  152. package/packages/solidjs-css/src/pages/WorkflowRunsPage.tsx +0 -7
  153. package/packages/solidjs-css/src/pages/index.ts +0 -3
  154. package/packages/solidjs-css/src/primitives/createWorkflow.ts +0 -28
  155. package/packages/solidjs-css/src/primitives/createWorkflowBuilder.ts +0 -56
  156. package/packages/solidjs-css/src/primitives/index.ts +0 -1
  157. package/packages/solidjs-css/tsconfig.json +0 -27
  158. package/packages/solidjs-css/tsup.config.ts +0 -2
  159. package/pnpm-workspace.yaml +0 -2
@@ -0,0 +1,2620 @@
1
+ import { GeeniusError, ErrorCode } from '@geenius/tools/errors';
2
+ import { z } from 'zod/v4';
3
+ import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
4
+ import { jsxs, jsx } from 'react/jsx-runtime';
5
+
6
+ // ../shared/dist/index.js
7
+ function isRecord(value) {
8
+ return Boolean(value && typeof value === "object");
9
+ }
10
+ function resolvePath(obj, path) {
11
+ return path.split(".").reduce((acc, key) => {
12
+ if (isRecord(acc)) {
13
+ return acc[key];
14
+ }
15
+ return void 0;
16
+ }, obj);
17
+ }
18
+ function interpolate(template, vars) {
19
+ return template.replace(/\{\{([^}]+)\}\}/g, (match, expr) => {
20
+ const [path, defaultValue] = expr.trim().split("|").map((s) => s.trim());
21
+ const val = resolvePath(vars, path);
22
+ if (val !== void 0 && val !== null) {
23
+ return typeof val === "object" ? JSON.stringify(val) : String(val);
24
+ }
25
+ return defaultValue ?? "";
26
+ });
27
+ }
28
+ var WorkflowError = class _WorkflowError extends GeeniusError {
29
+ /**
30
+ * Create a structured workflow error.
31
+ *
32
+ * @param options Error metadata, message, and optional structured context.
33
+ * @returns A typed workflow error instance.
34
+ */
35
+ constructor(options) {
36
+ super({
37
+ code: options.code ?? ErrorCode.INTERNAL,
38
+ message: options.message,
39
+ cause: options.cause,
40
+ context: options.context
41
+ });
42
+ this.name = "WorkflowError";
43
+ Object.setPrototypeOf(this, _WorkflowError.prototype);
44
+ }
45
+ };
46
+ var WorkflowConfigurationError = class _WorkflowConfigurationError extends WorkflowError {
47
+ /**
48
+ * Create a configuration error for invalid workflow setup.
49
+ *
50
+ * @param message Human-readable explanation of the invalid configuration.
51
+ * @param context Optional structured metadata describing the failing config area.
52
+ * @returns A typed workflow configuration error.
53
+ */
54
+ constructor(message, context) {
55
+ super({
56
+ code: ErrorCode.INVALID_CONFIG,
57
+ message,
58
+ context
59
+ });
60
+ this.name = "WorkflowConfigurationError";
61
+ Object.setPrototypeOf(this, _WorkflowConfigurationError.prototype);
62
+ }
63
+ };
64
+ var WorkflowExecutionError = class _WorkflowExecutionError extends WorkflowError {
65
+ /**
66
+ * Create an execution error for a workflow runtime failure.
67
+ *
68
+ * @param message Human-readable explanation of the runtime failure.
69
+ * @param context Optional structured metadata describing the failing execution path.
70
+ * @param cause Optional underlying exception that triggered the runtime failure.
71
+ * @returns A typed workflow execution error.
72
+ */
73
+ constructor(message, context, cause) {
74
+ super({
75
+ code: ErrorCode.INTERNAL,
76
+ message,
77
+ cause,
78
+ context
79
+ });
80
+ this.name = "WorkflowExecutionError";
81
+ Object.setPrototypeOf(this, _WorkflowExecutionError.prototype);
82
+ }
83
+ };
84
+ var WorkflowGraphError = class _WorkflowGraphError extends WorkflowError {
85
+ /**
86
+ * Create a graph-validation error for an invalid workflow topology.
87
+ *
88
+ * @param message Human-readable explanation of the graph validation failure.
89
+ * @param context Optional structured metadata describing the invalid topology.
90
+ * @returns A typed workflow graph error.
91
+ */
92
+ constructor(message, context) {
93
+ super({
94
+ code: ErrorCode.VALIDATION,
95
+ message,
96
+ context
97
+ });
98
+ this.name = "WorkflowGraphError";
99
+ Object.setPrototypeOf(this, _WorkflowGraphError.prototype);
100
+ }
101
+ };
102
+ var WorkflowExpressionError = class _WorkflowExpressionError extends WorkflowError {
103
+ /**
104
+ * Create an expression error for a failed transform or condition expression.
105
+ *
106
+ * @param message Human-readable explanation of the expression failure.
107
+ * @param context Optional structured metadata including the source expression.
108
+ * @param cause Optional underlying parse or runtime error.
109
+ * @returns A typed workflow expression error.
110
+ */
111
+ constructor(message, context, cause) {
112
+ super({
113
+ code: ErrorCode.VALIDATION,
114
+ message,
115
+ cause,
116
+ context
117
+ });
118
+ this.name = "WorkflowExpressionError";
119
+ Object.setPrototypeOf(this, _WorkflowExpressionError.prototype);
120
+ }
121
+ };
122
+ function normalizeWorkflowError(error, fallbackMessage, context) {
123
+ if (error instanceof WorkflowError) {
124
+ return error;
125
+ }
126
+ if (error instanceof GeeniusError) {
127
+ return new WorkflowError({
128
+ code: error.code,
129
+ message: error.message,
130
+ cause: error,
131
+ context
132
+ });
133
+ }
134
+ if (error instanceof Error) {
135
+ return new WorkflowExecutionError(error.message, context, error);
136
+ }
137
+ return new WorkflowExecutionError(fallbackMessage, context, error);
138
+ }
139
+ function isRecord2(value) {
140
+ return Boolean(value && typeof value === "object");
141
+ }
142
+ function isExpressionOperand(value) {
143
+ return value === null || value === void 0 || typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean";
144
+ }
145
+ function isRelationalOperand(value) {
146
+ return typeof value === "string" || typeof value === "number" || typeof value === "bigint" || typeof value === "boolean";
147
+ }
148
+ function toPropertyKey(value) {
149
+ if (typeof value === "string" || typeof value === "number" || typeof value === "symbol") {
150
+ return value;
151
+ }
152
+ throw createExpressionError(
153
+ "Computed property access expects a string, number, or symbol key."
154
+ );
155
+ }
156
+ function createExpressionError(message, expression, cause) {
157
+ return new WorkflowExpressionError(
158
+ message,
159
+ expression ? { expression } : void 0,
160
+ cause
161
+ );
162
+ }
163
+ var FORBIDDEN_PROPERTIES = /* @__PURE__ */ new Set([
164
+ "__proto__",
165
+ "prototype",
166
+ "constructor",
167
+ "caller",
168
+ "callee",
169
+ "arguments"
170
+ ]);
171
+ var SAFE_GLOBALS = Object.freeze({
172
+ Math,
173
+ JSON: Object.freeze({
174
+ parse: JSON.parse.bind(JSON),
175
+ stringify: JSON.stringify.bind(JSON)
176
+ }),
177
+ Date: Object.freeze({
178
+ now: Date.now.bind(Date),
179
+ parse: Date.parse.bind(Date),
180
+ UTC: Date.UTC.bind(Date)
181
+ }),
182
+ Number: (value) => Number(value),
183
+ String: (value) => String(value),
184
+ Boolean: (value) => Boolean(value)
185
+ });
186
+ function isIdentifierStart(char) {
187
+ return /[A-Za-z_$]/.test(char);
188
+ }
189
+ function isIdentifierPart(char) {
190
+ return /[A-Za-z0-9_$]/.test(char);
191
+ }
192
+ function isDigit(char) {
193
+ return /[0-9]/.test(char);
194
+ }
195
+ function tokenize(expression) {
196
+ const tokens = [];
197
+ let index = 0;
198
+ const push = (type, value) => {
199
+ tokens.push({ type, value });
200
+ };
201
+ while (index < expression.length) {
202
+ const char = expression[index];
203
+ if (/\s/.test(char)) {
204
+ index++;
205
+ continue;
206
+ }
207
+ const three = expression.slice(index, index + 3);
208
+ if (three === "...") {
209
+ push("operator", "...");
210
+ index += 3;
211
+ continue;
212
+ }
213
+ const multi = [
214
+ "===",
215
+ "!==",
216
+ ">=",
217
+ "<=",
218
+ "&&",
219
+ "||",
220
+ "??",
221
+ "==",
222
+ "!=",
223
+ "**"
224
+ ];
225
+ const matched = multi.find((op) => expression.startsWith(op, index));
226
+ if (matched) {
227
+ push("operator", matched);
228
+ index += matched.length;
229
+ continue;
230
+ }
231
+ if (isDigit(char) || char === "." && isDigit(expression[index + 1] ?? "")) {
232
+ let end = index + 1;
233
+ while (end < expression.length && /[0-9.]/.test(expression[end])) end++;
234
+ push("number", expression.slice(index, end));
235
+ index = end;
236
+ continue;
237
+ }
238
+ if (char === '"' || char === "'") {
239
+ const quote = char;
240
+ let end = index + 1;
241
+ let value = "";
242
+ while (end < expression.length) {
243
+ const current = expression[end];
244
+ if (current === "\\") {
245
+ const next = expression[end + 1];
246
+ if (next === void 0)
247
+ throw createExpressionError(
248
+ "Unterminated string literal.",
249
+ expression
250
+ );
251
+ const escapes = {
252
+ n: "\n",
253
+ r: "\r",
254
+ t: " ",
255
+ b: "\b",
256
+ f: "\f",
257
+ "\\": "\\",
258
+ '"': '"',
259
+ "'": "'"
260
+ };
261
+ value += escapes[next] ?? next;
262
+ end += 2;
263
+ continue;
264
+ }
265
+ if (current === quote) break;
266
+ value += current;
267
+ end++;
268
+ }
269
+ if (expression[end] !== quote)
270
+ throw createExpressionError("Unterminated string literal.", expression);
271
+ push("string", value);
272
+ index = end + 1;
273
+ continue;
274
+ }
275
+ if (isIdentifierStart(char)) {
276
+ let end = index + 1;
277
+ while (end < expression.length && isIdentifierPart(expression[end]))
278
+ end++;
279
+ push("identifier", expression.slice(index, end));
280
+ index = end;
281
+ continue;
282
+ }
283
+ const punctuators = ["(", ")", "{", "}", "[", "]", ".", ",", ":", "?"];
284
+ if (punctuators.includes(char)) {
285
+ push("punctuator", char);
286
+ index++;
287
+ continue;
288
+ }
289
+ const operators = ["+", "-", "*", "/", "%", "!", "<", ">", "="];
290
+ if (operators.includes(char)) {
291
+ push("operator", char);
292
+ index++;
293
+ continue;
294
+ }
295
+ throw createExpressionError(
296
+ `Unsupported token in expression: ${char}`,
297
+ expression
298
+ );
299
+ }
300
+ tokens.push({ type: "eof", value: "" });
301
+ return tokens;
302
+ }
303
+ var ExpressionParser = class {
304
+ tokens;
305
+ index = 0;
306
+ context;
307
+ constructor(expression, context) {
308
+ this.tokens = tokenize(expression);
309
+ this.context = context;
310
+ }
311
+ evaluate() {
312
+ const result = this.parseConditional();
313
+ this.expect("eof");
314
+ return this.unwrap(result);
315
+ }
316
+ current() {
317
+ return this.tokens[this.index] ?? { type: "eof", value: "" };
318
+ }
319
+ advance() {
320
+ const token = this.current();
321
+ this.index++;
322
+ return token;
323
+ }
324
+ match(type, value) {
325
+ const token = this.current();
326
+ if (token.type !== type) return false;
327
+ if (value !== void 0 && token.value !== value) return false;
328
+ this.index++;
329
+ return true;
330
+ }
331
+ expect(type, value) {
332
+ const token = this.current();
333
+ if (token.type !== type || value !== void 0 && token.value !== value) {
334
+ throw createExpressionError(
335
+ `Unexpected token "${token.value}" in expression.`
336
+ );
337
+ }
338
+ this.index++;
339
+ return token;
340
+ }
341
+ unwrap(value) {
342
+ if (this.isReference(value)) return value.value;
343
+ return value;
344
+ }
345
+ unwrapOperand(value) {
346
+ const operand = this.unwrap(value);
347
+ if (!isExpressionOperand(operand)) {
348
+ throw createExpressionError(
349
+ "Expression operand must resolve to a primitive value."
350
+ );
351
+ }
352
+ return operand;
353
+ }
354
+ relationalOperand(value) {
355
+ const operand = this.unwrap(value);
356
+ if (!isRelationalOperand(operand)) {
357
+ throw createExpressionError(
358
+ "Relational comparisons require primitive operands."
359
+ );
360
+ }
361
+ return operand;
362
+ }
363
+ isReference(value) {
364
+ return isRecord2(value) && value.__ref === true;
365
+ }
366
+ toReference(base, key) {
367
+ if (typeof key === "string" && FORBIDDEN_PROPERTIES.has(key)) {
368
+ throw createExpressionError(
369
+ `Access to property "${key}" is not allowed.`
370
+ );
371
+ }
372
+ if (base === null || base === void 0) {
373
+ throw createExpressionError(
374
+ `Cannot read property "${String(key)}" of ${base}.`
375
+ );
376
+ }
377
+ return {
378
+ __ref: true,
379
+ base,
380
+ key,
381
+ value: Reflect.get(Object(base), key)
382
+ };
383
+ }
384
+ resolveIdentifier(name) {
385
+ if (name === "true") return true;
386
+ if (name === "false") return false;
387
+ if (name === "null") return null;
388
+ if (name === "undefined") return void 0;
389
+ if (Object.prototype.hasOwnProperty.call(this.context, name)) {
390
+ return this.context[name];
391
+ }
392
+ if (Object.prototype.hasOwnProperty.call(SAFE_GLOBALS, name)) {
393
+ return SAFE_GLOBALS[name];
394
+ }
395
+ throw createExpressionError(`Unknown identifier in expression: ${name}.`);
396
+ }
397
+ parseConditional() {
398
+ let test = this.parseNullish();
399
+ if (this.match("punctuator", "?")) {
400
+ const consequent = this.parseConditional();
401
+ this.expect("punctuator", ":");
402
+ const alternate = this.parseConditional();
403
+ test = this.unwrap(test) ? consequent : alternate;
404
+ }
405
+ return test;
406
+ }
407
+ parseNullish() {
408
+ let left = this.parseLogicalOr();
409
+ while (this.match("operator", "??")) {
410
+ const right = this.parseLogicalOr();
411
+ left = this.unwrap(left) ?? right;
412
+ }
413
+ return left;
414
+ }
415
+ parseLogicalOr() {
416
+ let left = this.parseLogicalAnd();
417
+ while (this.match("operator", "||")) {
418
+ const right = this.parseLogicalAnd();
419
+ left = this.unwrap(left) || right;
420
+ }
421
+ return left;
422
+ }
423
+ parseLogicalAnd() {
424
+ let left = this.parseEquality();
425
+ while (this.match("operator", "&&")) {
426
+ const right = this.parseEquality();
427
+ left = this.unwrap(left) && right;
428
+ }
429
+ return left;
430
+ }
431
+ parseEquality() {
432
+ let left = this.parseComparison();
433
+ while (true) {
434
+ if (this.match("operator", "===")) {
435
+ const right = this.parseComparison();
436
+ left = this.unwrap(left) === this.unwrap(right);
437
+ continue;
438
+ }
439
+ if (this.match("operator", "!==")) {
440
+ const right = this.parseComparison();
441
+ left = this.unwrap(left) !== this.unwrap(right);
442
+ continue;
443
+ }
444
+ if (this.match("operator", "==")) {
445
+ const right = this.parseComparison();
446
+ left = this.unwrap(left) == this.unwrap(right);
447
+ continue;
448
+ }
449
+ if (this.match("operator", "!=")) {
450
+ const right = this.parseComparison();
451
+ left = this.unwrap(left) != this.unwrap(right);
452
+ continue;
453
+ }
454
+ return left;
455
+ }
456
+ }
457
+ compare(left, right, operator) {
458
+ const leftValue = this.relationalOperand(left);
459
+ const rightValue = this.relationalOperand(right);
460
+ switch (operator) {
461
+ case "<":
462
+ return leftValue < rightValue;
463
+ case "<=":
464
+ return leftValue <= rightValue;
465
+ case ">":
466
+ return leftValue > rightValue;
467
+ case ">=":
468
+ return leftValue >= rightValue;
469
+ }
470
+ }
471
+ add(left, right) {
472
+ const leftValue = this.unwrapOperand(left);
473
+ const rightValue = this.unwrapOperand(right);
474
+ return typeof leftValue === "string" || typeof rightValue === "string" ? String(leftValue) + String(rightValue) : Number(leftValue) + Number(rightValue);
475
+ }
476
+ subtract(left, right) {
477
+ return Number(this.unwrapOperand(left)) - Number(this.unwrapOperand(right));
478
+ }
479
+ multiply(left, right) {
480
+ return Number(this.unwrapOperand(left)) * Number(this.unwrapOperand(right));
481
+ }
482
+ divide(left, right) {
483
+ return Number(this.unwrapOperand(left)) / Number(this.unwrapOperand(right));
484
+ }
485
+ remainder(left, right) {
486
+ return Number(this.unwrapOperand(left)) % Number(this.unwrapOperand(right));
487
+ }
488
+ exponent(left, right) {
489
+ return Number(this.unwrapOperand(left)) ** Number(this.unwrapOperand(right));
490
+ }
491
+ parseComparison() {
492
+ let left = this.parseAdditive();
493
+ while (true) {
494
+ if (this.match("operator", "<")) {
495
+ const right = this.parseAdditive();
496
+ left = this.compare(left, right, "<");
497
+ continue;
498
+ }
499
+ if (this.match("operator", "<=")) {
500
+ const right = this.parseAdditive();
501
+ left = this.compare(left, right, "<=");
502
+ continue;
503
+ }
504
+ if (this.match("operator", ">")) {
505
+ const right = this.parseAdditive();
506
+ left = this.compare(left, right, ">");
507
+ continue;
508
+ }
509
+ if (this.match("operator", ">=")) {
510
+ const right = this.parseAdditive();
511
+ left = this.compare(left, right, ">=");
512
+ continue;
513
+ }
514
+ return left;
515
+ }
516
+ }
517
+ parseAdditive() {
518
+ let left = this.parseMultiplicative();
519
+ while (true) {
520
+ if (this.match("operator", "+")) {
521
+ const right = this.parseMultiplicative();
522
+ left = this.add(left, right);
523
+ continue;
524
+ }
525
+ if (this.match("operator", "-")) {
526
+ const right = this.parseMultiplicative();
527
+ left = this.subtract(left, right);
528
+ continue;
529
+ }
530
+ return left;
531
+ }
532
+ }
533
+ parseMultiplicative() {
534
+ let left = this.parseUnary();
535
+ while (true) {
536
+ if (this.match("operator", "*")) {
537
+ const right = this.parseUnary();
538
+ left = this.multiply(left, right);
539
+ continue;
540
+ }
541
+ if (this.match("operator", "/")) {
542
+ const right = this.parseUnary();
543
+ left = this.divide(left, right);
544
+ continue;
545
+ }
546
+ if (this.match("operator", "%")) {
547
+ const right = this.parseUnary();
548
+ left = this.remainder(left, right);
549
+ continue;
550
+ }
551
+ if (this.match("operator", "**")) {
552
+ const right = this.parseUnary();
553
+ left = this.exponent(left, right);
554
+ continue;
555
+ }
556
+ return left;
557
+ }
558
+ }
559
+ parseUnary() {
560
+ if (this.match("operator", "!")) {
561
+ return !this.unwrap(this.parseUnary());
562
+ }
563
+ if (this.match("operator", "+")) {
564
+ return Number(this.unwrap(this.parseUnary()));
565
+ }
566
+ if (this.match("operator", "-")) {
567
+ return -Number(this.unwrap(this.parseUnary()));
568
+ }
569
+ if (this.current().type === "identifier" && this.current().value === "typeof") {
570
+ this.advance();
571
+ return typeof this.unwrap(this.parseUnary());
572
+ }
573
+ return this.parseCallMember();
574
+ }
575
+ parseCallMember() {
576
+ let value = this.parsePrimary();
577
+ while (true) {
578
+ if (this.match("punctuator", ".")) {
579
+ const property = this.expect("identifier").value;
580
+ const target = this.isReference(value) ? value.value : value;
581
+ value = this.toReference(target, property);
582
+ continue;
583
+ }
584
+ if (this.match("punctuator", "[")) {
585
+ const property = this.parseConditional();
586
+ this.expect("punctuator", "]");
587
+ const target = this.isReference(value) ? value.value : value;
588
+ value = this.toReference(target, toPropertyKey(this.unwrap(property)));
589
+ continue;
590
+ }
591
+ if (this.match("punctuator", "(")) {
592
+ const args = [];
593
+ if (!this.match("punctuator", ")")) {
594
+ do {
595
+ args.push(this.unwrap(this.parseConditional()));
596
+ } while (this.match("punctuator", ","));
597
+ this.expect("punctuator", ")");
598
+ }
599
+ const callee = this.isReference(value) ? value.value : value;
600
+ if (typeof callee !== "function") {
601
+ throw createExpressionError(
602
+ "Expression attempted to call a non-function value."
603
+ );
604
+ }
605
+ const thisArg = this.isReference(value) ? value.base : void 0;
606
+ value = callee.apply(thisArg, args);
607
+ continue;
608
+ }
609
+ return value;
610
+ }
611
+ }
612
+ parsePrimary() {
613
+ const token = this.current();
614
+ if (token.type === "number") {
615
+ this.advance();
616
+ return Number(token.value);
617
+ }
618
+ if (token.type === "string") {
619
+ this.advance();
620
+ return token.value;
621
+ }
622
+ if (token.type === "identifier") {
623
+ this.advance();
624
+ return this.resolveIdentifier(token.value);
625
+ }
626
+ if (this.match("punctuator", "(")) {
627
+ const value = this.parseConditional();
628
+ this.expect("punctuator", ")");
629
+ return value;
630
+ }
631
+ if (this.match("punctuator", "[")) {
632
+ const items = [];
633
+ if (!this.match("punctuator", "]")) {
634
+ do {
635
+ if (this.match("operator", "...")) {
636
+ const spread = this.unwrap(this.parseConditional());
637
+ if (Array.isArray(spread)) {
638
+ items.push(...spread);
639
+ } else {
640
+ throw createExpressionError(
641
+ "Array spread expects an array value."
642
+ );
643
+ }
644
+ } else {
645
+ items.push(this.unwrap(this.parseConditional()));
646
+ }
647
+ } while (this.match("punctuator", ","));
648
+ this.expect("punctuator", "]");
649
+ }
650
+ return items;
651
+ }
652
+ if (this.match("punctuator", "{")) {
653
+ const out = {};
654
+ if (!this.match("punctuator", "}")) {
655
+ do {
656
+ if (this.match("operator", "...")) {
657
+ const spread = this.unwrap(this.parseConditional());
658
+ if (spread && typeof spread === "object") {
659
+ Object.assign(out, spread);
660
+ } else {
661
+ throw createExpressionError(
662
+ "Object spread expects an object value."
663
+ );
664
+ }
665
+ } else {
666
+ let key;
667
+ if (this.current().type === "string" || this.current().type === "number") {
668
+ key = String(this.advance().value);
669
+ } else {
670
+ key = this.expect("identifier").value;
671
+ }
672
+ if (this.match("punctuator", ":")) {
673
+ out[key] = this.unwrap(this.parseConditional());
674
+ } else {
675
+ out[key] = this.resolveIdentifier(key);
676
+ }
677
+ }
678
+ } while (this.match("punctuator", ","));
679
+ this.expect("punctuator", "}");
680
+ }
681
+ return out;
682
+ }
683
+ throw createExpressionError(
684
+ `Unexpected token "${token.value}" in expression.`
685
+ );
686
+ }
687
+ };
688
+ function evaluateExpression(expression, context) {
689
+ const trimmed = expression.trim();
690
+ if (!trimmed)
691
+ throw createExpressionError("Expression cannot be empty.", expression);
692
+ try {
693
+ return new ExpressionParser(trimmed, context).evaluate();
694
+ } catch (error) {
695
+ throw error instanceof WorkflowExpressionError ? error : createExpressionError(
696
+ "Failed to evaluate workflow expression.",
697
+ expression,
698
+ error
699
+ );
700
+ }
701
+ }
702
+ function formatCost(usd) {
703
+ if (usd < 0.01) return `$${(usd * 100).toFixed(2)}\xA2`;
704
+ return `$${usd.toFixed(4)}`;
705
+ }
706
+ var WorkflowEngine = class {
707
+ options;
708
+ cancelled = false;
709
+ constructor(options) {
710
+ this.options = options;
711
+ }
712
+ /**
713
+ * Cancel the in-flight execution loop before the next step begins.
714
+ */
715
+ cancel() {
716
+ this.cancelled = true;
717
+ }
718
+ /**
719
+ * Execute a workflow definition from start to finish and capture the full
720
+ * run state for UI or persistence layers.
721
+ */
722
+ async execute(definition, input = {}) {
723
+ this.cancelled = false;
724
+ const run = {
725
+ id: crypto.randomUUID?.() ?? String(Date.now()),
726
+ workflowId: definition.id,
727
+ workflowVersion: definition.version,
728
+ status: "running",
729
+ input,
730
+ variables: { ...definition.variables, ...input },
731
+ stepResults: [],
732
+ currentStepIndex: 0,
733
+ triggeredBy: "",
734
+ startedAt: Date.now()
735
+ };
736
+ const sortedSteps = this.topoSort(definition);
737
+ for (let i = 0; i < sortedSteps.length; i++) {
738
+ if (this.cancelled) {
739
+ run.status = "cancelled";
740
+ break;
741
+ }
742
+ run.currentStepIndex = i;
743
+ const stepDef = sortedSteps[i];
744
+ const result = await this.executeStep(stepDef, run.variables, definition);
745
+ run.stepResults.push(result);
746
+ this.options.onStepComplete?.(result);
747
+ if (result.status === "failed" && !stepDef.optional) {
748
+ run.status = "failed";
749
+ run.error = result.error;
750
+ break;
751
+ }
752
+ if (result.output !== void 0 && result.output !== null && "outputVar" in stepDef.config) {
753
+ if (stepDef.config.outputVar) {
754
+ run.variables[stepDef.config.outputVar] = result.output;
755
+ }
756
+ }
757
+ if (stepDef.config.type === "condition") {
758
+ const nextStepId = Boolean(result.output) ? stepDef.config.trueStepId : stepDef.config.falseStepId;
759
+ const targetIndex = sortedSteps.findIndex((s) => s.id === nextStepId);
760
+ if (targetIndex > i) {
761
+ for (let j = i + 1; j < targetIndex; j++) {
762
+ run.stepResults.push({
763
+ stepId: sortedSteps[j].id,
764
+ stepName: sortedSteps[j].name,
765
+ type: sortedSteps[j].type,
766
+ status: "skipped",
767
+ durationMs: 0,
768
+ startedAt: Date.now()
769
+ });
770
+ }
771
+ i = targetIndex - 1;
772
+ }
773
+ }
774
+ }
775
+ if (run.status === "running") {
776
+ run.status = "completed";
777
+ }
778
+ run.completedAt = Date.now();
779
+ return run;
780
+ }
781
+ /**
782
+ * Execute one step with retry handling and normalized result capture.
783
+ */
784
+ async executeStep(stepDef, variables, _definition) {
785
+ const start = Date.now();
786
+ const baseResult = {
787
+ stepId: stepDef.id,
788
+ stepName: stepDef.name,
789
+ type: stepDef.type,
790
+ status: "running",
791
+ durationMs: 0,
792
+ startedAt: start
793
+ };
794
+ let attempts = 0;
795
+ const maxAttempts = stepDef.retries?.maxAttempts ?? 1;
796
+ while (attempts < maxAttempts) {
797
+ attempts++;
798
+ try {
799
+ const output = await this.runStepLogic(stepDef, variables);
800
+ return {
801
+ ...baseResult,
802
+ status: "completed",
803
+ output,
804
+ durationMs: Date.now() - start,
805
+ completedAt: Date.now()
806
+ };
807
+ } catch (err) {
808
+ if (attempts >= maxAttempts) {
809
+ const workflowError = normalizeWorkflowError(
810
+ err,
811
+ `Workflow step "${stepDef.name}" failed.`,
812
+ { stepId: stepDef.id, workflowId: _definition.id }
813
+ );
814
+ return {
815
+ ...baseResult,
816
+ status: "failed",
817
+ error: workflowError.message,
818
+ durationMs: Date.now() - start,
819
+ completedAt: Date.now()
820
+ };
821
+ }
822
+ const backoff = stepDef.retries?.backoffMs ?? 1e3;
823
+ await new Promise((r) => setTimeout(r, backoff * 2 ** (attempts - 1)));
824
+ }
825
+ }
826
+ return {
827
+ ...baseResult,
828
+ status: "failed",
829
+ error: "Max retries exceeded",
830
+ durationMs: Date.now() - start
831
+ };
832
+ }
833
+ /**
834
+ * Run the concrete logic for an individual step type.
835
+ */
836
+ async runStepLogic(stepDef, variables) {
837
+ const config = stepDef.config;
838
+ switch (config.type) {
839
+ case "llm-call": {
840
+ if (!this.options.callLLM) {
841
+ throw new WorkflowConfigurationError(
842
+ "No LLM function provided for llm-call steps.",
843
+ {
844
+ stepId: stepDef.id
845
+ }
846
+ );
847
+ }
848
+ const userPrompt = this.interpolate(
849
+ config.userPromptTemplate,
850
+ variables
851
+ );
852
+ const result = await this.options.callLLM(
853
+ config.systemPrompt,
854
+ userPrompt,
855
+ config.model
856
+ );
857
+ if (config.parseJson) {
858
+ try {
859
+ return JSON.parse(result.content);
860
+ } catch {
861
+ return result.content;
862
+ }
863
+ }
864
+ return result.content;
865
+ }
866
+ case "transform": {
867
+ const scope = this.buildExpressionScope(config.inputVars, variables);
868
+ return evaluateExpression(config.expression, scope);
869
+ }
870
+ case "condition": {
871
+ return Boolean(evaluateExpression(config.expression, variables));
872
+ }
873
+ case "human-approval": {
874
+ if (!this.options.onApprovalRequired) {
875
+ return true;
876
+ }
877
+ return this.options.onApprovalRequired(config.message, stepDef.id);
878
+ }
879
+ case "webhook": {
880
+ if (!this.options.callWebhook) {
881
+ throw new WorkflowConfigurationError(
882
+ "No webhook function provided for webhook steps.",
883
+ {
884
+ stepId: stepDef.id
885
+ }
886
+ );
887
+ }
888
+ const body = config.bodyTemplate ? interpolate(config.bodyTemplate, variables) : void 0;
889
+ return this.options.callWebhook(
890
+ config.url,
891
+ config.method,
892
+ body,
893
+ config.headers
894
+ );
895
+ }
896
+ case "delay": {
897
+ await new Promise((r) => setTimeout(r, config.durationMs));
898
+ return null;
899
+ }
900
+ case "custom": {
901
+ const handler = this.options.customHandlers?.[config.handler];
902
+ if (!handler) {
903
+ throw new WorkflowConfigurationError(
904
+ `No handler registered for custom step "${config.handler}".`,
905
+ { stepId: stepDef.id }
906
+ );
907
+ }
908
+ return handler(config.params, variables);
909
+ }
910
+ default:
911
+ throw new WorkflowExecutionError(
912
+ `Unsupported step type "${config.type}".`,
913
+ {
914
+ stepId: stepDef.id
915
+ }
916
+ );
917
+ }
918
+ }
919
+ /** Interpolate `{{variable}}` references in a string. */
920
+ interpolate(template, vars) {
921
+ return interpolate(template, vars);
922
+ }
923
+ buildExpressionScope(inputVars, variables) {
924
+ const scope = {};
925
+ for (const name of inputVars) {
926
+ scope[name] = variables[name];
927
+ }
928
+ return scope;
929
+ }
930
+ /** Topological sort of steps based on declared workflow connections. */
931
+ topoSort(def) {
932
+ const stepMap = new Map(def.steps.map((s) => [s.id, s]));
933
+ const inDegree = /* @__PURE__ */ new Map();
934
+ const adjacency = /* @__PURE__ */ new Map();
935
+ for (const step of def.steps) {
936
+ inDegree.set(step.id, 0);
937
+ adjacency.set(step.id, []);
938
+ }
939
+ for (const conn of def.connections) {
940
+ adjacency.get(conn.fromStepId)?.push(conn.toStepId);
941
+ inDegree.set(conn.toStepId, (inDegree.get(conn.toStepId) ?? 0) + 1);
942
+ }
943
+ const queue = [];
944
+ for (const [id, degree] of inDegree) {
945
+ if (degree === 0) queue.push(id);
946
+ }
947
+ const sorted = [];
948
+ while (queue.length > 0) {
949
+ const id = queue.shift();
950
+ const step = stepMap.get(id);
951
+ if (step) sorted.push(step);
952
+ for (const neighbor of adjacency.get(id) ?? []) {
953
+ const newDegree = (inDegree.get(neighbor) ?? 1) - 1;
954
+ inDegree.set(neighbor, newDegree);
955
+ if (newDegree === 0) queue.push(neighbor);
956
+ }
957
+ }
958
+ if (sorted.length !== def.steps.length) {
959
+ throw new WorkflowGraphError(
960
+ "Workflow definition contains a cycle and cannot be executed.",
961
+ {
962
+ workflowId: def.id
963
+ }
964
+ );
965
+ }
966
+ return sorted;
967
+ }
968
+ };
969
+ var llmCallConfigSchema = z.object({
970
+ type: z.literal("llm-call"),
971
+ model: z.string().optional(),
972
+ systemPrompt: z.string().min(1, "System prompt is required"),
973
+ userPromptTemplate: z.string().min(1, "User prompt template is required"),
974
+ temperature: z.number().min(0).max(2).optional(),
975
+ maxTokens: z.number().int().positive().optional(),
976
+ parseJson: z.boolean().optional(),
977
+ outputVar: z.string().min(1, "Output variable name is required")
978
+ });
979
+ var transformConfigSchema = z.object({
980
+ type: z.literal("transform"),
981
+ expression: z.string().min(1, "Expression is required"),
982
+ inputVars: z.array(z.string()).min(1, "At least one input variable is required"),
983
+ outputVar: z.string().min(1)
984
+ });
985
+ var conditionConfigSchema = z.object({
986
+ type: z.literal("condition"),
987
+ expression: z.string().min(1),
988
+ trueStepId: z.string().min(1),
989
+ falseStepId: z.string().min(1)
990
+ });
991
+ var humanApprovalConfigSchema = z.object({
992
+ type: z.literal("human-approval"),
993
+ message: z.string().min(1, "Approval message is required"),
994
+ approvers: z.array(z.string()).optional(),
995
+ autoApproveAfterMs: z.number().positive().optional()
996
+ });
997
+ var webhookConfigSchema = z.object({
998
+ type: z.literal("webhook"),
999
+ url: z.string().url("Valid URL required"),
1000
+ method: z.enum(["GET", "POST", "PUT", "DELETE"]),
1001
+ headers: z.record(z.string(), z.string()).optional(),
1002
+ bodyTemplate: z.string().optional(),
1003
+ outputVar: z.string().min(1)
1004
+ });
1005
+ var delayConfigSchema = z.object({
1006
+ type: z.literal("delay"),
1007
+ durationMs: z.number().int().positive("Duration must be positive")
1008
+ });
1009
+ var parallelConfigSchema = z.object({
1010
+ type: z.literal("parallel"),
1011
+ stepIds: z.array(z.string()).min(2, "Parallel needs at least 2 steps"),
1012
+ failurePolicy: z.enum(["fail-fast", "continue"])
1013
+ });
1014
+ var loopConfigSchema = z.object({
1015
+ type: z.literal("loop"),
1016
+ stepIds: z.array(z.string()).min(1),
1017
+ iterateVar: z.string().min(1),
1018
+ itemVar: z.string().min(1),
1019
+ maxIterations: z.number().int().positive().optional()
1020
+ });
1021
+ var subWorkflowConfigSchema = z.object({
1022
+ type: z.literal("sub-workflow"),
1023
+ workflowId: z.string().min(1),
1024
+ inputMapping: z.record(z.string(), z.string()),
1025
+ outputVar: z.string().min(1)
1026
+ });
1027
+ var customStepConfigSchema = z.object({
1028
+ type: z.literal("custom"),
1029
+ handler: z.string().min(1, "Handler name is required"),
1030
+ params: z.record(z.string(), z.unknown()),
1031
+ outputVar: z.string().optional()
1032
+ });
1033
+ var stepConfigSchema = z.discriminatedUnion("type", [
1034
+ llmCallConfigSchema,
1035
+ transformConfigSchema,
1036
+ conditionConfigSchema,
1037
+ humanApprovalConfigSchema,
1038
+ webhookConfigSchema,
1039
+ delayConfigSchema,
1040
+ parallelConfigSchema,
1041
+ loopConfigSchema,
1042
+ subWorkflowConfigSchema,
1043
+ customStepConfigSchema
1044
+ ]);
1045
+ var workflowStepDefSchema = z.object({
1046
+ id: z.string().min(1),
1047
+ name: z.string().min(1, "Step name is required"),
1048
+ type: z.enum(["llm-call", "transform", "condition", "human-approval", "webhook", "delay", "parallel", "loop", "sub-workflow", "custom"]),
1049
+ config: stepConfigSchema,
1050
+ position: z.object({ x: z.number(), y: z.number() }).optional(),
1051
+ optional: z.boolean().optional(),
1052
+ retries: z.object({ maxAttempts: z.number().int().positive(), backoffMs: z.number().positive() }).optional()
1053
+ });
1054
+ var stepConnectionSchema = z.object({
1055
+ fromStepId: z.string().min(1),
1056
+ toStepId: z.string().min(1),
1057
+ label: z.string().optional(),
1058
+ condition: z.string().optional()
1059
+ });
1060
+ var workflowVariableSchema = z.object({
1061
+ name: z.string().min(1),
1062
+ type: z.enum(["string", "number", "boolean", "object", "array"]),
1063
+ description: z.string().optional(),
1064
+ required: z.boolean().optional(),
1065
+ default: z.unknown().optional()
1066
+ });
1067
+ var workflowDefinitionSchema = z.object({
1068
+ id: z.string().min(1),
1069
+ name: z.string().min(1, "Workflow name is required").max(120, "Name too long"),
1070
+ description: z.string().max(2e3).optional(),
1071
+ version: z.number().int().positive(),
1072
+ status: z.enum(["draft", "active", "paused", "archived"]),
1073
+ steps: z.array(workflowStepDefSchema).min(1, "At least one step is required"),
1074
+ connections: z.array(stepConnectionSchema),
1075
+ inputSchema: z.array(workflowVariableSchema).optional(),
1076
+ variables: z.record(z.string(), z.unknown()).optional(),
1077
+ tags: z.array(z.string()).optional(),
1078
+ createdBy: z.string(),
1079
+ createdAt: z.number(),
1080
+ updatedAt: z.number()
1081
+ });
1082
+ var stepResultSchema = z.object({
1083
+ stepId: z.string(),
1084
+ stepName: z.string(),
1085
+ type: z.enum(["llm-call", "transform", "condition", "human-approval", "webhook", "delay", "parallel", "loop", "sub-workflow", "custom"]),
1086
+ status: z.enum(["pending", "running", "completed", "failed", "skipped", "waiting-approval"]),
1087
+ input: z.record(z.string(), z.unknown()).optional(),
1088
+ output: z.unknown().optional(),
1089
+ error: z.string().optional(),
1090
+ durationMs: z.number(),
1091
+ tokens: z.number().optional(),
1092
+ costUsd: z.number().optional(),
1093
+ startedAt: z.number(),
1094
+ completedAt: z.number().optional()
1095
+ });
1096
+ var workflowRunSchema = z.object({
1097
+ id: z.string(),
1098
+ workflowId: z.string(),
1099
+ workflowVersion: z.number(),
1100
+ status: z.enum(["pending", "running", "paused", "completed", "failed", "cancelled"]),
1101
+ input: z.record(z.string(), z.unknown()),
1102
+ variables: z.record(z.string(), z.unknown()),
1103
+ stepResults: z.array(stepResultSchema),
1104
+ currentStepIndex: z.number(),
1105
+ error: z.string().optional(),
1106
+ triggeredBy: z.string(),
1107
+ startedAt: z.number(),
1108
+ completedAt: z.number().optional()
1109
+ });
1110
+ function validateWorkflow(data) {
1111
+ return workflowDefinitionSchema.safeParse(data);
1112
+ }
1113
+ function validateRun(data) {
1114
+ return workflowRunSchema.safeParse(data);
1115
+ }
1116
+ function uid() {
1117
+ return `step_tpl_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
1118
+ }
1119
+ function wfId() {
1120
+ return `wf_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
1121
+ }
1122
+ var contentPipeline = {
1123
+ id: "tpl-content-pipeline",
1124
+ name: "Content Pipeline",
1125
+ description: "Generate, review, and publish content with AI refinement and human approval.",
1126
+ category: "content",
1127
+ tags: ["content", "blog", "marketing"],
1128
+ create: (createdBy) => {
1129
+ const draft = uid(), refine = uid(), approve = uid(), publish = uid();
1130
+ const steps = [
1131
+ { id: draft, name: "Generate Draft", type: "llm-call", config: { type: "llm-call", systemPrompt: "You are a content writer.", userPromptTemplate: "Write a blog post about {{topic}}. Tone: {{tone}}.", outputVar: "draft" }, position: { x: 100, y: 200 } },
1132
+ { id: refine, name: "Refine Content", type: "llm-call", config: { type: "llm-call", systemPrompt: "You are an editor. Improve clarity, grammar, and engagement.", userPromptTemplate: "Polish this draft:\n\n{{draft}}", outputVar: "polished" }, position: { x: 350, y: 200 } },
1133
+ { id: approve, name: "Human Review", type: "human-approval", config: { type: "human-approval", message: "Please review the polished content before publishing." }, position: { x: 600, y: 200 } },
1134
+ { id: publish, name: "Format for Publishing", type: "transform", config: { type: "transform", expression: '({ title: polished.split("\\n")[0], body: polished, publishedAt: Date.now() })', inputVars: ["polished"], outputVar: "publishData" }, position: { x: 850, y: 200 } }
1135
+ ];
1136
+ const connections = [
1137
+ { fromStepId: draft, toStepId: refine },
1138
+ { fromStepId: refine, toStepId: approve },
1139
+ { fromStepId: approve, toStepId: publish }
1140
+ ];
1141
+ return { id: wfId(), name: "Content Pipeline", description: "AI-powered content generation with editorial review.", version: 1, status: "draft", steps, connections, inputSchema: [{ name: "topic", type: "string", required: true }, { name: "tone", type: "string", required: false, default: "professional" }], tags: ["content"], createdBy, createdAt: Date.now(), updatedAt: Date.now() };
1142
+ }
1143
+ };
1144
+ var dataEnrichment = {
1145
+ id: "tpl-data-enrichment",
1146
+ name: "Data Enrichment",
1147
+ description: "Fetch external data, enrich with AI analysis, and transform for downstream use.",
1148
+ category: "data",
1149
+ tags: ["data", "analytics", "enrichment"],
1150
+ create: (createdBy) => {
1151
+ const fetch_ = uid(), analyze = uid(), transform = uid();
1152
+ const steps = [
1153
+ { id: fetch_, name: "Fetch Data", type: "webhook", config: { type: "webhook", url: "https://api.example.com/data-source", method: "GET", outputVar: "rawData" }, position: { x: 100, y: 200 } },
1154
+ { id: analyze, name: "AI Analysis", type: "llm-call", config: { type: "llm-call", systemPrompt: "You are a data analyst. Analyze the data and extract key insights.", userPromptTemplate: "Analyze this dataset and return a JSON summary with key metrics:\n\n{{rawData}}", parseJson: true, outputVar: "analysis" }, position: { x: 400, y: 200 } },
1155
+ { id: transform, name: "Transform Output", type: "transform", config: { type: "transform", expression: "({ ...analysis, enrichedAt: Date.now(), source: dataSourceUrl })", inputVars: ["analysis", "dataSourceUrl"], outputVar: "enrichedData" }, position: { x: 700, y: 200 } }
1156
+ ];
1157
+ const connections = [
1158
+ { fromStepId: fetch_, toStepId: analyze },
1159
+ { fromStepId: analyze, toStepId: transform }
1160
+ ];
1161
+ return { id: wfId(), name: "Data Enrichment", version: 1, status: "draft", steps, connections, inputSchema: [{ name: "dataSourceUrl", type: "string", required: true }], tags: ["data"], createdBy, createdAt: Date.now(), updatedAt: Date.now() };
1162
+ }
1163
+ };
1164
+ var approvalChain = {
1165
+ id: "tpl-approval-chain",
1166
+ name: "Approval Chain",
1167
+ description: "Multi-level approval workflow with conditional escalation.",
1168
+ category: "approval",
1169
+ tags: ["approval", "governance", "compliance"],
1170
+ create: (createdBy) => {
1171
+ const checkRisk = uid(), lowApproval = uid(), highApproval = uid(), finalize = uid();
1172
+ const steps = [
1173
+ { id: checkRisk, name: "Assess Risk Level", type: "llm-call", config: { type: "llm-call", systemPrompt: 'Assess the risk level of this request. Respond with JSON: {"riskLevel": "low"|"medium"|"high", "reason": "..."}', userPromptTemplate: "{{request}}", parseJson: true, outputVar: "riskAssessment" }, position: { x: 100, y: 200 } },
1174
+ { id: lowApproval, name: "Manager Approval", type: "human-approval", config: { type: "human-approval", message: "Low/medium risk: Manager approval needed for: {{request}}" }, position: { x: 400, y: 100 } },
1175
+ { id: highApproval, name: "Executive Approval", type: "human-approval", config: { type: "human-approval", message: "High risk: Executive approval required for: {{request}}" }, position: { x: 400, y: 300 } },
1176
+ { id: finalize, name: "Record Decision", type: "transform", config: { type: "transform", expression: "({ approved: true, approvedAt: Date.now(), riskLevel: riskAssessment.riskLevel })", inputVars: ["riskAssessment"], outputVar: "decision" }, position: { x: 700, y: 200 } }
1177
+ ];
1178
+ const connections = [
1179
+ { fromStepId: checkRisk, toStepId: lowApproval, condition: 'riskAssessment.riskLevel !== "high"' },
1180
+ { fromStepId: checkRisk, toStepId: highApproval, condition: 'riskAssessment.riskLevel === "high"' },
1181
+ { fromStepId: lowApproval, toStepId: finalize },
1182
+ { fromStepId: highApproval, toStepId: finalize }
1183
+ ];
1184
+ return { id: wfId(), name: "Approval Chain", version: 1, status: "draft", steps, connections, inputSchema: [{ name: "request", type: "string", required: true }], tags: ["approval"], createdBy, createdAt: Date.now(), updatedAt: Date.now() };
1185
+ }
1186
+ };
1187
+ var researchPipeline = {
1188
+ id: "tpl-research",
1189
+ name: "Research Pipeline",
1190
+ description: "Multi-source research with AI synthesis and summary generation.",
1191
+ category: "research",
1192
+ tags: ["research", "analysis", "summary"],
1193
+ create: (createdBy) => {
1194
+ const research = uid(), synthesize = uid(), summarize = uid();
1195
+ const steps = [
1196
+ { id: research, name: "Deep Research", type: "llm-call", config: { type: "llm-call", systemPrompt: "You are a research assistant. Provide comprehensive, well-sourced research on the given topic.", userPromptTemplate: "Research the following topic in depth: {{topic}}\n\nFocus areas: {{focusAreas}}", outputVar: "researchOutput" }, position: { x: 100, y: 200 } },
1197
+ { id: synthesize, name: "Synthesize Findings", type: "llm-call", config: { type: "llm-call", systemPrompt: "You are a synthesis expert. Combine research findings into structured insights with citations.", userPromptTemplate: "Synthesize these research findings into key themes and actionable insights:\n\n{{researchOutput}}", parseJson: true, outputVar: "synthesis" }, position: { x: 400, y: 200 } },
1198
+ { id: summarize, name: "Executive Summary", type: "llm-call", config: { type: "llm-call", systemPrompt: "Write a concise executive summary (3-5 paragraphs) suitable for senior leadership.", userPromptTemplate: "Create an executive summary from:\n\n{{synthesis}}", outputVar: "executiveSummary" }, position: { x: 700, y: 200 } }
1199
+ ];
1200
+ const connections = [
1201
+ { fromStepId: research, toStepId: synthesize },
1202
+ { fromStepId: synthesize, toStepId: summarize }
1203
+ ];
1204
+ return { id: wfId(), name: "Research Pipeline", version: 1, status: "draft", steps, connections, inputSchema: [{ name: "topic", type: "string", required: true }, { name: "focusAreas", type: "string", required: false, default: "general overview" }], tags: ["research"], createdBy, createdAt: Date.now(), updatedAt: Date.now() };
1205
+ }
1206
+ };
1207
+ var codeReviewPipeline = {
1208
+ id: "tpl-code-review",
1209
+ name: "Code Review Pipeline",
1210
+ description: "Automated code review with AI analysis, security check, and human approval.",
1211
+ category: "devops",
1212
+ tags: ["code", "review", "security", "devops"],
1213
+ create: (createdBy) => {
1214
+ const analyze = uid(), security = uid(), approve = uid(), report = uid();
1215
+ const steps = [
1216
+ { id: analyze, name: "AI Code Review", type: "llm-call", config: { type: "llm-call", systemPrompt: 'You are a senior code reviewer. Analyze the code for bugs, performance issues, and best practices. Return JSON: {"issues": [], "suggestions": [], "quality": 1-10}', userPromptTemplate: "Review this code:\n\n```{{language}}\n{{code}}\n```", parseJson: true, outputVar: "codeReview" }, position: { x: 100, y: 200 } },
1217
+ { id: security, name: "Security Scan", type: "llm-call", config: { type: "llm-call", systemPrompt: 'You are a security auditor. Check for vulnerabilities (XSS, SQL injection, secrets exposure, etc). Return JSON: {"vulnerabilities": [], "riskLevel": "low"|"medium"|"high"}', userPromptTemplate: "Security audit for:\n\n```{{language}}\n{{code}}\n```", parseJson: true, outputVar: "securityReport" }, position: { x: 100, y: 400 } },
1218
+ { id: approve, name: "Developer Approval", type: "human-approval", config: { type: "human-approval", message: "Review the AI code analysis and security scan before merging." }, position: { x: 450, y: 300 } },
1219
+ { id: report, name: "Generate Report", type: "transform", config: { type: "transform", expression: "({ codeQuality: codeReview.quality, issues: codeReview.issues.length, vulnerabilities: securityReport.vulnerabilities.length, riskLevel: securityReport.riskLevel, reviewedAt: Date.now() })", inputVars: ["codeReview", "securityReport"], outputVar: "finalReport" }, position: { x: 700, y: 300 } }
1220
+ ];
1221
+ const connections = [
1222
+ { fromStepId: analyze, toStepId: approve },
1223
+ { fromStepId: security, toStepId: approve },
1224
+ { fromStepId: approve, toStepId: report }
1225
+ ];
1226
+ return { id: wfId(), name: "Code Review Pipeline", version: 1, status: "draft", steps, connections, inputSchema: [{ name: "code", type: "string", required: true }, { name: "language", type: "string", required: false, default: "typescript" }], tags: ["devops", "review"], createdBy, createdAt: Date.now(), updatedAt: Date.now() };
1227
+ }
1228
+ };
1229
+ var leadQualification = {
1230
+ id: "tpl-lead-qualification",
1231
+ name: "Lead Qualification",
1232
+ description: "Score incoming leads, prepare a summary, and route them for follow-up.",
1233
+ category: "data",
1234
+ tags: ["sales", "crm", "lead-scoring"],
1235
+ create: (createdBy) => {
1236
+ const score = uid(), summarize = uid(), route = uid();
1237
+ const steps = [
1238
+ { id: score, name: "Score Lead", type: "llm-call", config: { type: "llm-call", systemPrompt: "You are a sales operations analyst. Score the lead from 1-10 and return JSON with score, rationale, and nextAction.", userPromptTemplate: "Lead details:\n\n{{lead}}", parseJson: true, outputVar: "leadScore" }, position: { x: 100, y: 200 } },
1239
+ { id: summarize, name: "Summarize Lead", type: "transform", config: { type: "transform", expression: '({ score: leadScore.score, summary: leadScore.rationale, priority: leadScore.score >= 8 ? "high" : "standard", updatedAt: Date.now() })', inputVars: ["leadScore"], outputVar: "leadSummary" }, position: { x: 400, y: 200 } },
1240
+ { id: route, name: "Notify Sales", type: "webhook", config: { type: "webhook", url: "https://api.example.com/crm/webhook", method: "POST", bodyTemplate: "Lead summary: {{leadSummary}}", outputVar: "salesNotification" }, position: { x: 700, y: 200 } }
1241
+ ];
1242
+ const connections = [
1243
+ { fromStepId: score, toStepId: summarize },
1244
+ { fromStepId: summarize, toStepId: route }
1245
+ ];
1246
+ return {
1247
+ id: wfId(),
1248
+ name: "Lead Qualification",
1249
+ description: "AI-assisted lead scoring and sales handoff.",
1250
+ version: 1,
1251
+ status: "draft",
1252
+ steps,
1253
+ connections,
1254
+ inputSchema: [
1255
+ { name: "lead", type: "string", required: true },
1256
+ { name: "crmWebhookUrl", type: "string", required: false, default: "https://api.example.com/crm/webhook" }
1257
+ ],
1258
+ tags: ["sales", "lead"],
1259
+ createdBy,
1260
+ createdAt: Date.now(),
1261
+ updatedAt: Date.now()
1262
+ };
1263
+ }
1264
+ };
1265
+ var incidentTriage = {
1266
+ id: "tpl-incident-triage",
1267
+ name: "Incident Triage",
1268
+ description: "Collect incident context, classify severity, and draft the response.",
1269
+ category: "devops",
1270
+ tags: ["incident", "ops", "triage"],
1271
+ create: (createdBy) => {
1272
+ const fetchIncident = uid(), classify = uid(), draftResponse = uid();
1273
+ const steps = [
1274
+ { id: fetchIncident, name: "Fetch Incident", type: "webhook", config: { type: "webhook", url: "https://api.example.com/incidents", method: "GET", outputVar: "incidentData" }, position: { x: 100, y: 200 } },
1275
+ { id: classify, name: "Classify Severity", type: "llm-call", config: { type: "llm-call", systemPrompt: "You are an incident commander. Classify the incident severity and summarize the likely impact in JSON.", userPromptTemplate: "Incident context:\n\n{{incidentData}}", parseJson: true, outputVar: "classification" }, position: { x: 400, y: 200 } },
1276
+ { id: draftResponse, name: "Draft Response", type: "transform", config: { type: "transform", expression: "({ severity: classification.severity, summary: classification.summary, draftedAt: Date.now() })", inputVars: ["classification"], outputVar: "responseDraft" }, position: { x: 700, y: 200 } }
1277
+ ];
1278
+ const connections = [
1279
+ { fromStepId: fetchIncident, toStepId: classify },
1280
+ { fromStepId: classify, toStepId: draftResponse }
1281
+ ];
1282
+ return {
1283
+ id: wfId(),
1284
+ name: "Incident Triage",
1285
+ description: "Automated incident classification and response drafting.",
1286
+ version: 1,
1287
+ status: "draft",
1288
+ steps,
1289
+ connections,
1290
+ inputSchema: [
1291
+ { name: "incidentApiUrl", type: "string", required: false, default: "https://api.example.com/incidents" }
1292
+ ],
1293
+ tags: ["incident", "ops"],
1294
+ createdBy,
1295
+ createdAt: Date.now(),
1296
+ updatedAt: Date.now()
1297
+ };
1298
+ }
1299
+ };
1300
+ var launchReadiness = {
1301
+ id: "tpl-launch-readiness",
1302
+ name: "Launch Readiness",
1303
+ description: "Prepare launch notes, request approval, and publish the rollout payload.",
1304
+ category: "approval",
1305
+ tags: ["launch", "release", "approval"],
1306
+ create: (createdBy) => {
1307
+ const prepare = uid(), approve = uid(), publish = uid();
1308
+ const steps = [
1309
+ { id: prepare, name: "Prepare Launch Notes", type: "llm-call", config: { type: "llm-call", systemPrompt: "You are a product marketing lead. Create concise launch notes with risks and highlights.", userPromptTemplate: "Launch context:\n\n{{launchBrief}}", outputVar: "launchNotes" }, position: { x: 100, y: 200 } },
1310
+ { id: approve, name: "Approval Gate", type: "human-approval", config: { type: "human-approval", message: "Review the launch notes before the rollout payload is published." }, position: { x: 400, y: 200 } },
1311
+ { id: publish, name: "Build Payload", type: "transform", config: { type: "transform", expression: '({ notes: launchNotes, approvedAt: Date.now(), releaseChannel: releaseChannel || "stable" })', inputVars: ["launchNotes", "releaseChannel"], outputVar: "launchPayload" }, position: { x: 700, y: 200 } }
1312
+ ];
1313
+ const connections = [
1314
+ { fromStepId: prepare, toStepId: approve },
1315
+ { fromStepId: approve, toStepId: publish }
1316
+ ];
1317
+ return {
1318
+ id: wfId(),
1319
+ name: "Launch Readiness",
1320
+ description: "Human-approved release preparation workflow.",
1321
+ version: 1,
1322
+ status: "draft",
1323
+ steps,
1324
+ connections,
1325
+ inputSchema: [
1326
+ { name: "launchBrief", type: "string", required: true },
1327
+ { name: "releaseChannel", type: "string", required: false, default: "stable" }
1328
+ ],
1329
+ tags: ["launch", "release"],
1330
+ createdBy,
1331
+ createdAt: Date.now(),
1332
+ updatedAt: Date.now()
1333
+ };
1334
+ }
1335
+ };
1336
+ var WORKFLOW_TEMPLATES = [
1337
+ contentPipeline,
1338
+ dataEnrichment,
1339
+ approvalChain,
1340
+ researchPipeline,
1341
+ codeReviewPipeline,
1342
+ leadQualification,
1343
+ incidentTriage,
1344
+ launchReadiness
1345
+ ];
1346
+ function getTemplate(id) {
1347
+ return WORKFLOW_TEMPLATES.find((t) => t.id === id);
1348
+ }
1349
+ function getTemplatesByCategory(category) {
1350
+ return WORKFLOW_TEMPLATES.filter((t) => t.category === category);
1351
+ }
1352
+ function useApprovalGate() {
1353
+ const [pendingApprovals, setPendingApprovals] = useState(
1354
+ []
1355
+ );
1356
+ const [history, setHistory] = useState([]);
1357
+ const requestApproval = useCallback(
1358
+ (stepId, message, approvers) => {
1359
+ const request = {
1360
+ id: `approval_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
1361
+ stepId,
1362
+ message,
1363
+ requestedAt: Date.now(),
1364
+ approvers
1365
+ };
1366
+ setPendingApprovals((current) => [...current, request]);
1367
+ return request.id;
1368
+ },
1369
+ []
1370
+ );
1371
+ const approve = useCallback((approvalId) => {
1372
+ setPendingApprovals((current) => {
1373
+ const request = current.find((item) => item.id === approvalId);
1374
+ if (request) {
1375
+ setHistory((entries) => [
1376
+ ...entries,
1377
+ { ...request, decision: "approved", decidedAt: Date.now() }
1378
+ ]);
1379
+ }
1380
+ return current.filter((item) => item.id !== approvalId);
1381
+ });
1382
+ }, []);
1383
+ const reject = useCallback((approvalId) => {
1384
+ setPendingApprovals((current) => {
1385
+ const request = current.find((item) => item.id === approvalId);
1386
+ if (request) {
1387
+ setHistory((entries) => [
1388
+ ...entries,
1389
+ { ...request, decision: "rejected", decidedAt: Date.now() }
1390
+ ]);
1391
+ }
1392
+ return current.filter((item) => item.id !== approvalId);
1393
+ });
1394
+ }, []);
1395
+ const clearHistory = useCallback(() => {
1396
+ setHistory([]);
1397
+ }, []);
1398
+ return {
1399
+ pendingApprovals,
1400
+ history,
1401
+ hasPending: pendingApprovals.length > 0,
1402
+ requestApproval,
1403
+ approve,
1404
+ reject,
1405
+ clearHistory
1406
+ };
1407
+ }
1408
+ function useWorkflow(options) {
1409
+ const [run, setRun] = useState(null);
1410
+ const [isRunning, setIsRunning] = useState(false);
1411
+ const [error, setError] = useState(null);
1412
+ const [stepResults, setStepResults] = useState([]);
1413
+ const engine = new WorkflowEngine({
1414
+ ...options,
1415
+ onStepComplete: (result) => {
1416
+ setStepResults((current) => [...current, result]);
1417
+ options.onStepComplete?.(result);
1418
+ }
1419
+ });
1420
+ const execute = useCallback(
1421
+ async (definition, input) => {
1422
+ setIsRunning(true);
1423
+ setError(null);
1424
+ setStepResults([]);
1425
+ try {
1426
+ const result = await engine.execute(definition, input);
1427
+ setRun(result);
1428
+ return result;
1429
+ } catch (caughtError) {
1430
+ const normalizedError = normalizeWorkflowError(
1431
+ caughtError,
1432
+ "Workflow execution failed.",
1433
+ { workflowId: definition.id }
1434
+ );
1435
+ setError(normalizedError);
1436
+ throw normalizedError;
1437
+ } finally {
1438
+ setIsRunning(false);
1439
+ }
1440
+ },
1441
+ []
1442
+ );
1443
+ const cancel = useCallback(() => {
1444
+ engine.cancel();
1445
+ }, []);
1446
+ const reset = useCallback(() => {
1447
+ setRun(null);
1448
+ setError(null);
1449
+ setStepResults([]);
1450
+ }, []);
1451
+ return {
1452
+ execute,
1453
+ cancel,
1454
+ run,
1455
+ isRunning,
1456
+ error,
1457
+ stepResults,
1458
+ reset
1459
+ };
1460
+ }
1461
+ function createEmptyDefinition() {
1462
+ return {
1463
+ id: crypto.randomUUID?.() ?? String(Date.now()),
1464
+ name: "New Workflow",
1465
+ version: 1,
1466
+ status: "draft",
1467
+ steps: [],
1468
+ connections: [],
1469
+ createdBy: "",
1470
+ createdAt: Date.now(),
1471
+ updatedAt: Date.now()
1472
+ };
1473
+ }
1474
+ function useWorkflowBuilder(initial) {
1475
+ const [state, setState] = useState({
1476
+ definition: { ...createEmptyDefinition(), ...initial },
1477
+ selectedStepId: null,
1478
+ isDirty: false,
1479
+ undoStack: [],
1480
+ redoStack: []
1481
+ });
1482
+ const pushUndo = useCallback(() => {
1483
+ setState((current) => ({
1484
+ ...current,
1485
+ undoStack: [...current.undoStack.slice(-20), current.definition],
1486
+ redoStack: []
1487
+ }));
1488
+ }, []);
1489
+ const addStep = useCallback(
1490
+ (step) => {
1491
+ pushUndo();
1492
+ setState((current) => ({
1493
+ ...current,
1494
+ isDirty: true,
1495
+ definition: {
1496
+ ...current.definition,
1497
+ steps: [...current.definition.steps, step],
1498
+ updatedAt: Date.now()
1499
+ }
1500
+ }));
1501
+ },
1502
+ [pushUndo]
1503
+ );
1504
+ const removeStep = useCallback(
1505
+ (stepId) => {
1506
+ pushUndo();
1507
+ setState((current) => ({
1508
+ ...current,
1509
+ isDirty: true,
1510
+ definition: {
1511
+ ...current.definition,
1512
+ steps: current.definition.steps.filter((step) => step.id !== stepId),
1513
+ connections: current.definition.connections.filter(
1514
+ (connection) => connection.fromStepId !== stepId && connection.toStepId !== stepId
1515
+ ),
1516
+ updatedAt: Date.now()
1517
+ },
1518
+ selectedStepId: current.selectedStepId === stepId ? null : current.selectedStepId
1519
+ }));
1520
+ },
1521
+ [pushUndo]
1522
+ );
1523
+ const updateStep = useCallback(
1524
+ (stepId, updates) => {
1525
+ pushUndo();
1526
+ setState((current) => ({
1527
+ ...current,
1528
+ isDirty: true,
1529
+ definition: {
1530
+ ...current.definition,
1531
+ steps: current.definition.steps.map(
1532
+ (step) => step.id === stepId ? { ...step, ...updates } : step
1533
+ ),
1534
+ updatedAt: Date.now()
1535
+ }
1536
+ }));
1537
+ },
1538
+ [pushUndo]
1539
+ );
1540
+ const addConnection = useCallback(
1541
+ (connection) => {
1542
+ pushUndo();
1543
+ setState((current) => ({
1544
+ ...current,
1545
+ isDirty: true,
1546
+ definition: {
1547
+ ...current.definition,
1548
+ connections: [...current.definition.connections, connection],
1549
+ updatedAt: Date.now()
1550
+ }
1551
+ }));
1552
+ },
1553
+ [pushUndo]
1554
+ );
1555
+ const removeConnection = useCallback(
1556
+ (fromStepId, toStepId) => {
1557
+ pushUndo();
1558
+ setState((current) => ({
1559
+ ...current,
1560
+ isDirty: true,
1561
+ definition: {
1562
+ ...current.definition,
1563
+ connections: current.definition.connections.filter(
1564
+ (connection) => !(connection.fromStepId === fromStepId && connection.toStepId === toStepId)
1565
+ ),
1566
+ updatedAt: Date.now()
1567
+ }
1568
+ }));
1569
+ },
1570
+ [pushUndo]
1571
+ );
1572
+ const selectStep = useCallback((stepId) => {
1573
+ setState((current) => ({ ...current, selectedStepId: stepId }));
1574
+ }, []);
1575
+ const undo = useCallback(() => {
1576
+ setState((current) => {
1577
+ if (current.undoStack.length === 0) {
1578
+ return current;
1579
+ }
1580
+ const previousDefinition = current.undoStack[current.undoStack.length - 1];
1581
+ return {
1582
+ ...current,
1583
+ definition: previousDefinition,
1584
+ undoStack: current.undoStack.slice(0, -1),
1585
+ redoStack: [current.definition, ...current.redoStack]
1586
+ };
1587
+ });
1588
+ }, []);
1589
+ const redo = useCallback(() => {
1590
+ setState((current) => {
1591
+ if (current.redoStack.length === 0) {
1592
+ return current;
1593
+ }
1594
+ const nextDefinition = current.redoStack[0];
1595
+ return {
1596
+ ...current,
1597
+ definition: nextDefinition,
1598
+ undoStack: [...current.undoStack, current.definition],
1599
+ redoStack: current.redoStack.slice(1)
1600
+ };
1601
+ });
1602
+ }, []);
1603
+ const setDefinition = useCallback((definition) => {
1604
+ setState((current) => ({
1605
+ ...current,
1606
+ definition,
1607
+ isDirty: false,
1608
+ undoStack: [],
1609
+ redoStack: []
1610
+ }));
1611
+ }, []);
1612
+ return {
1613
+ definition: state.definition,
1614
+ selectedStepId: state.selectedStepId,
1615
+ isDirty: state.isDirty,
1616
+ canUndo: state.undoStack.length > 0,
1617
+ canRedo: state.redoStack.length > 0,
1618
+ addStep,
1619
+ removeStep,
1620
+ updateStep,
1621
+ addConnection,
1622
+ removeConnection,
1623
+ selectStep,
1624
+ undo,
1625
+ redo,
1626
+ setDefinition
1627
+ };
1628
+ }
1629
+ function useWorkflowRun(options = {}) {
1630
+ const [run, setRun] = useState(null);
1631
+ const [currentStepIndex, setCurrentStepIndex] = useState(0);
1632
+ const [completedSteps, setCompletedSteps] = useState([]);
1633
+ const [status, setStatus] = useState("pending");
1634
+ const [progress, setProgress] = useState(0);
1635
+ const [error, setError] = useState(null);
1636
+ const timerRef = useRef(null);
1637
+ const trackRun = useCallback(
1638
+ (workflowRun) => {
1639
+ setRun(workflowRun);
1640
+ setStatus(workflowRun.status);
1641
+ setCurrentStepIndex(workflowRun.currentStepIndex);
1642
+ setCompletedSteps(workflowRun.stepResults);
1643
+ setError(workflowRun.error ?? null);
1644
+ const totalSteps = workflowRun.stepResults.length;
1645
+ const completedCount = workflowRun.stepResults.filter(
1646
+ (step) => step.status === "completed" || step.status === "skipped"
1647
+ ).length;
1648
+ setProgress(
1649
+ totalSteps > 0 ? Math.round(completedCount / totalSteps * 100) : 0
1650
+ );
1651
+ if (workflowRun.status === "completed" || workflowRun.status === "failed") {
1652
+ options.onRunComplete?.(workflowRun);
1653
+ }
1654
+ },
1655
+ [options.onRunComplete]
1656
+ );
1657
+ const updateStep = useCallback(
1658
+ (result) => {
1659
+ setCompletedSteps((current) => {
1660
+ const existingIndex = current.findIndex(
1661
+ (step) => step.stepId === result.stepId
1662
+ );
1663
+ if (existingIndex >= 0) {
1664
+ const updatedSteps = [...current];
1665
+ updatedSteps[existingIndex] = result;
1666
+ return updatedSteps;
1667
+ }
1668
+ return [...current, result];
1669
+ });
1670
+ setCurrentStepIndex((current) => current + 1);
1671
+ options.onStepComplete?.(result);
1672
+ },
1673
+ [options.onStepComplete]
1674
+ );
1675
+ const reset = useCallback(() => {
1676
+ setRun(null);
1677
+ setCurrentStepIndex(0);
1678
+ setCompletedSteps([]);
1679
+ setStatus("pending");
1680
+ setProgress(0);
1681
+ setError(null);
1682
+ if (timerRef.current) {
1683
+ clearInterval(timerRef.current);
1684
+ }
1685
+ }, []);
1686
+ useEffect(() => {
1687
+ return () => {
1688
+ if (timerRef.current) {
1689
+ clearInterval(timerRef.current);
1690
+ }
1691
+ };
1692
+ }, []);
1693
+ return {
1694
+ run,
1695
+ status,
1696
+ currentStepIndex,
1697
+ completedSteps,
1698
+ progress,
1699
+ error,
1700
+ trackRun,
1701
+ updateStep,
1702
+ reset,
1703
+ isRunning: status === "running",
1704
+ isComplete: status === "completed",
1705
+ isFailed: status === "failed"
1706
+ };
1707
+ }
1708
+ function useWorkflowStep(stepDef) {
1709
+ const [status, setStatus] = useState("pending");
1710
+ const [output, setOutput] = useState(null);
1711
+ const [error, setError] = useState(null);
1712
+ const [durationMs, setDurationMs] = useState(0);
1713
+ const updateFromResult = useCallback((result) => {
1714
+ setStatus(result.status);
1715
+ setOutput(result.output ?? null);
1716
+ setError(result.error ?? null);
1717
+ setDurationMs(result.durationMs);
1718
+ }, []);
1719
+ const reset = useCallback(() => {
1720
+ setStatus("pending");
1721
+ setOutput(null);
1722
+ setError(null);
1723
+ setDurationMs(0);
1724
+ }, []);
1725
+ const icon = useMemo(() => {
1726
+ const iconMap = {
1727
+ "llm-call": "\u{1F916}",
1728
+ transform: "\u2699\uFE0F",
1729
+ condition: "\u{1F500}",
1730
+ "human-approval": "\u{1F464}",
1731
+ webhook: "\u{1F310}",
1732
+ delay: "\u23F1\uFE0F",
1733
+ parallel: "\u26A1",
1734
+ loop: "\u{1F504}",
1735
+ "sub-workflow": "\u{1F4CB}",
1736
+ custom: "\u{1F527}"
1737
+ };
1738
+ return iconMap[stepDef.type] ?? "\u{1F4E6}";
1739
+ }, [stepDef.type]);
1740
+ const statusColor = useMemo(() => {
1741
+ const colorMap = {
1742
+ pending: "gray",
1743
+ running: "blue",
1744
+ completed: "green",
1745
+ failed: "red",
1746
+ skipped: "orange",
1747
+ "waiting-approval": "purple"
1748
+ };
1749
+ return colorMap[status];
1750
+ }, [status]);
1751
+ return {
1752
+ stepDef,
1753
+ status,
1754
+ output,
1755
+ error,
1756
+ durationMs,
1757
+ icon,
1758
+ statusColor,
1759
+ updateFromResult,
1760
+ reset,
1761
+ isComplete: status === "completed",
1762
+ isRunning: status === "running",
1763
+ isFailed: status === "failed",
1764
+ isWaiting: status === "waiting-approval",
1765
+ config: stepDef.config
1766
+ };
1767
+ }
1768
+ function useWorkflowTemplates(userId) {
1769
+ const [selectedTemplateId, setSelectedTemplateId] = useState(
1770
+ null
1771
+ );
1772
+ const [filterCategory, setFilterCategory] = useState("all");
1773
+ const [searchQuery, setSearchQuery] = useState("");
1774
+ const filteredTemplates = useMemo(() => {
1775
+ let templates = filterCategory === "all" ? WORKFLOW_TEMPLATES : getTemplatesByCategory(filterCategory);
1776
+ if (searchQuery.trim()) {
1777
+ const normalizedQuery = searchQuery.toLowerCase();
1778
+ templates = templates.filter(
1779
+ (template) => template.name.toLowerCase().includes(normalizedQuery) || template.description.toLowerCase().includes(normalizedQuery) || template.tags.some((tag) => tag.includes(normalizedQuery))
1780
+ );
1781
+ }
1782
+ return templates;
1783
+ }, [filterCategory, searchQuery]);
1784
+ const selectedTemplate = useMemo(() => {
1785
+ return selectedTemplateId ? getTemplate(selectedTemplateId) ?? null : null;
1786
+ }, [selectedTemplateId]);
1787
+ const createFromTemplate = useCallback(
1788
+ (templateId) => {
1789
+ const template = getTemplate(templateId);
1790
+ return template ? template.create(userId) : null;
1791
+ },
1792
+ [userId]
1793
+ );
1794
+ const categories = useMemo(() => {
1795
+ return [...new Set(WORKFLOW_TEMPLATES.map((template) => template.category))];
1796
+ }, []);
1797
+ return {
1798
+ templates: filteredTemplates,
1799
+ allTemplates: WORKFLOW_TEMPLATES,
1800
+ selectedTemplate,
1801
+ selectedTemplateId,
1802
+ categories,
1803
+ filterCategory,
1804
+ searchQuery,
1805
+ setSelectedTemplateId,
1806
+ setFilterCategory,
1807
+ setSearchQuery,
1808
+ createFromTemplate
1809
+ };
1810
+ }
1811
+ var NODE_WIDTH = 200;
1812
+ var NODE_HEIGHT = 80;
1813
+ function StepConnector(props) {
1814
+ const startX = props.from.x + NODE_WIDTH;
1815
+ const startY = props.from.y + NODE_HEIGHT / 2;
1816
+ const endX = props.to.x;
1817
+ const endY = props.to.y + NODE_HEIGHT / 2;
1818
+ const midX = (startX + endX) / 2;
1819
+ return /* @__PURE__ */ jsxs("g", { className: "ai-workflow__connector", children: [
1820
+ /* @__PURE__ */ jsx(
1821
+ "path",
1822
+ {
1823
+ d: `M ${startX} ${startY} C ${midX} ${startY}, ${midX} ${endY}, ${endX} ${endY}`,
1824
+ fill: "none",
1825
+ stroke: props.isActive ? "var(--primary)" : "var(--border)",
1826
+ strokeWidth: props.isActive ? 3 : 2
1827
+ }
1828
+ ),
1829
+ props.label && /* @__PURE__ */ jsx(
1830
+ "text",
1831
+ {
1832
+ x: midX,
1833
+ y: (startY + endY) / 2 - 8,
1834
+ textAnchor: "middle",
1835
+ fill: "var(--muted-foreground)",
1836
+ fontSize: "12",
1837
+ children: props.label
1838
+ }
1839
+ )
1840
+ ] });
1841
+ }
1842
+
1843
+ // src/utils/cx.ts
1844
+ function cx(...classes) {
1845
+ return classes.filter(Boolean).join(" ");
1846
+ }
1847
+ var ICONS = {
1848
+ "llm-call": "\u{1F916}",
1849
+ transform: "\u2699\uFE0F",
1850
+ condition: "\u{1F500}",
1851
+ "human-approval": "\u{1F464}",
1852
+ webhook: "\u{1F310}",
1853
+ delay: "\u23F1\uFE0F",
1854
+ parallel: "\u26A1",
1855
+ loop: "\u{1F504}",
1856
+ "sub-workflow": "\u{1F4CB}",
1857
+ custom: "\u{1F527}"
1858
+ };
1859
+ function StepNode(props) {
1860
+ const cls = cx(
1861
+ "ai-workflow__step-node",
1862
+ props.isSelected && "ai-workflow__step-node--selected",
1863
+ props.status && `ai-workflow__step-node--${props.status}`
1864
+ );
1865
+ return /* @__PURE__ */ jsxs(
1866
+ "div",
1867
+ {
1868
+ className: cx(cls, "ai-workflow__focus-visible", props.className),
1869
+ role: "button",
1870
+ tabIndex: 0,
1871
+ "aria-label": `Workflow step ${props.step.name}`,
1872
+ "aria-pressed": props.isSelected,
1873
+ onClick: props.onClick,
1874
+ onKeyDown: (event) => {
1875
+ if (event.key === "Enter" || event.key === " ") {
1876
+ event.preventDefault();
1877
+ props.onClick?.();
1878
+ }
1879
+ },
1880
+ style: props.step.position ? {
1881
+ position: "absolute",
1882
+ left: props.step.position.x,
1883
+ top: props.step.position.y
1884
+ } : void 0,
1885
+ children: [
1886
+ /* @__PURE__ */ jsxs("div", { className: "ai-workflow__step-header", children: [
1887
+ /* @__PURE__ */ jsx("span", { className: "ai-workflow__step-icon", children: ICONS[props.step.type] }),
1888
+ /* @__PURE__ */ jsx("span", { className: "ai-workflow__step-name", children: props.step.name })
1889
+ ] }),
1890
+ /* @__PURE__ */ jsx("div", { className: "ai-workflow__step-type-badge", children: props.step.type }),
1891
+ props.step.optional && /* @__PURE__ */ jsx("span", { className: "ai-workflow__step-optional-badge", children: "Optional" }),
1892
+ props.step.retries && /* @__PURE__ */ jsxs("span", { className: "ai-workflow__step-retry-badge", children: [
1893
+ "\u21BB ",
1894
+ props.step.retries.maxAttempts
1895
+ ] })
1896
+ ]
1897
+ }
1898
+ );
1899
+ }
1900
+ function WorkflowCanvas(props) {
1901
+ return /* @__PURE__ */ jsxs(
1902
+ "div",
1903
+ {
1904
+ className: "ai-workflow__canvas",
1905
+ role: "application",
1906
+ "aria-label": "Workflow canvas",
1907
+ children: [
1908
+ /* @__PURE__ */ jsx("svg", { style: { position: "absolute", inset: 0, pointerEvents: "none" }, children: props.definition.connections.map((connection, index) => {
1909
+ const fromStep = props.definition.steps.find(
1910
+ (step) => step.id === connection.fromStepId
1911
+ );
1912
+ const toStep = props.definition.steps.find(
1913
+ (step) => step.id === connection.toStepId
1914
+ );
1915
+ return fromStep?.position && toStep?.position ? /* @__PURE__ */ jsx(
1916
+ StepConnector,
1917
+ {
1918
+ from: fromStep.position,
1919
+ to: toStep.position,
1920
+ label: connection.label
1921
+ },
1922
+ `${connection.fromStepId}-${connection.toStepId}-${index}`
1923
+ ) : null;
1924
+ }) }),
1925
+ /* @__PURE__ */ jsx("div", { children: props.definition.steps.map((step) => /* @__PURE__ */ jsx(
1926
+ StepNode,
1927
+ {
1928
+ step,
1929
+ isSelected: props.selectedStepId === step.id,
1930
+ onClick: () => props.onSelectStep(
1931
+ step.id === props.selectedStepId ? null : step.id
1932
+ )
1933
+ },
1934
+ step.id
1935
+ )) }),
1936
+ props.definition.steps.length === 0 && /* @__PURE__ */ jsxs("div", { className: "ai-workflow__canvas-empty", children: [
1937
+ /* @__PURE__ */ jsx("p", { children: "No steps yet" }),
1938
+ /* @__PURE__ */ jsx("p", { children: "Drag steps from the palette or use a template" })
1939
+ ] })
1940
+ ]
1941
+ }
1942
+ );
1943
+ }
1944
+ function StepConfigPanel(props) {
1945
+ const [name, setName] = useState(props.step.name);
1946
+ const inputId = `workflow-step-name-${props.step.id}`;
1947
+ return /* @__PURE__ */ jsxs(
1948
+ "div",
1949
+ {
1950
+ className: "ai-workflow__config-panel",
1951
+ role: "complementary",
1952
+ "aria-label": "Step configuration",
1953
+ children: [
1954
+ /* @__PURE__ */ jsxs("div", { className: "ai-workflow__config-header", children: [
1955
+ /* @__PURE__ */ jsx("h3", { className: "ai-workflow__config-title", children: "Configure Step" }),
1956
+ /* @__PURE__ */ jsx(
1957
+ "button",
1958
+ {
1959
+ type: "button",
1960
+ className: cx(
1961
+ "ai-workflow__config-close",
1962
+ "ai-workflow__focus-visible"
1963
+ ),
1964
+ onClick: props.onClose,
1965
+ "aria-label": "Close step configuration",
1966
+ children: "\u2715"
1967
+ }
1968
+ )
1969
+ ] }),
1970
+ /* @__PURE__ */ jsxs("div", { className: "ai-workflow__config-body", children: [
1971
+ /* @__PURE__ */ jsx("label", { className: "ai-workflow__config-label", htmlFor: inputId, children: "Step Name" }),
1972
+ /* @__PURE__ */ jsx(
1973
+ "input",
1974
+ {
1975
+ id: inputId,
1976
+ className: "ai-workflow__config-input",
1977
+ value: name,
1978
+ onChange: (event) => setName(event.target.value),
1979
+ onBlur: () => {
1980
+ if (name.trim() && name !== props.step.name)
1981
+ props.onUpdate(props.step.id, { name: name.trim() });
1982
+ }
1983
+ }
1984
+ ),
1985
+ /* @__PURE__ */ jsxs("div", { className: "ai-workflow__config-field", children: [
1986
+ /* @__PURE__ */ jsx("span", { className: "ai-workflow__config-field-label", children: "Type" }),
1987
+ /* @__PURE__ */ jsx("span", { className: "ai-workflow__config-field-value", children: props.step.type })
1988
+ ] }),
1989
+ /* @__PURE__ */ jsxs("div", { className: "ai-workflow__config-field", children: [
1990
+ /* @__PURE__ */ jsx("span", { className: "ai-workflow__config-field-label", children: "ID" }),
1991
+ /* @__PURE__ */ jsx("code", { className: "ai-workflow__config-field-value", children: props.step.id })
1992
+ ] }),
1993
+ props.step.optional !== void 0 && /* @__PURE__ */ jsxs("label", { className: "ai-workflow__config-checkbox", children: [
1994
+ /* @__PURE__ */ jsx(
1995
+ "input",
1996
+ {
1997
+ type: "checkbox",
1998
+ checked: props.step.optional,
1999
+ onChange: (event) => props.onUpdate(props.step.id, {
2000
+ optional: event.target.checked
2001
+ })
2002
+ }
2003
+ ),
2004
+ "Optional (skip on failure)"
2005
+ ] }),
2006
+ /* @__PURE__ */ jsx("pre", { className: "ai-workflow__config-json-view", children: JSON.stringify(props.step.config, null, 2) })
2007
+ ] }),
2008
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
2009
+ "button",
2010
+ {
2011
+ type: "button",
2012
+ className: cx(
2013
+ "ai-workflow__config-delete",
2014
+ "ai-workflow__focus-visible"
2015
+ ),
2016
+ onClick: () => {
2017
+ props.onDelete(props.step.id);
2018
+ props.onClose();
2019
+ },
2020
+ children: "\u{1F5D1} Delete Step"
2021
+ }
2022
+ ) })
2023
+ ]
2024
+ }
2025
+ );
2026
+ }
2027
+ var SI = {
2028
+ pending: "\u23F3",
2029
+ running: "\u{1F504}",
2030
+ completed: "\u2705",
2031
+ failed: "\u274C",
2032
+ skipped: "\u23ED\uFE0F",
2033
+ "waiting-approval": "\u{1F464}"
2034
+ };
2035
+ function WorkflowRunPanel(props) {
2036
+ if (!props.run)
2037
+ return /* @__PURE__ */ jsx("div", { className: "ai-workflow__run-panel", children: /* @__PURE__ */ jsx("div", { className: "ai-workflow__run-empty", children: /* @__PURE__ */ jsx("p", { children: "No active run." }) }) });
2038
+ const run = props.run;
2039
+ const done = run.stepResults.filter(
2040
+ (step) => step.status === "completed"
2041
+ ).length;
2042
+ const total = run.stepResults.length;
2043
+ const progress = total > 0 ? Math.round(done / total * 100) : 0;
2044
+ const totalCost = run.stepResults.reduce(
2045
+ (sum, step) => sum + (step.costUsd ?? 0),
2046
+ 0
2047
+ );
2048
+ const totalTokens = run.stepResults.reduce(
2049
+ (sum, step) => sum + (step.tokens ?? 0),
2050
+ 0
2051
+ );
2052
+ const duration = (run.completedAt ?? Date.now()) - run.startedAt;
2053
+ return /* @__PURE__ */ jsxs("div", { className: "ai-workflow__run-panel", children: [
2054
+ /* @__PURE__ */ jsxs("div", { className: "ai-workflow__run-header", children: [
2055
+ /* @__PURE__ */ jsxs("h3", { className: "ai-workflow__run-title", children: [
2056
+ SI[run.status],
2057
+ " Run ",
2058
+ run.status
2059
+ ] }),
2060
+ props.isRunning && props.onCancel && /* @__PURE__ */ jsx(
2061
+ "button",
2062
+ {
2063
+ type: "button",
2064
+ className: cx(
2065
+ "ai-workflow__run-cancel",
2066
+ "ai-workflow__focus-visible"
2067
+ ),
2068
+ onClick: props.onCancel,
2069
+ children: "\u23F9 Cancel"
2070
+ }
2071
+ )
2072
+ ] }),
2073
+ /* @__PURE__ */ jsxs("div", { className: "ai-workflow__run-progress", children: [
2074
+ /* @__PURE__ */ jsx("div", { className: "ai-workflow__run-progress-bar", children: /* @__PURE__ */ jsx(
2075
+ "div",
2076
+ {
2077
+ className: "ai-workflow__run-progress-fill",
2078
+ style: { width: `${progress}%` }
2079
+ }
2080
+ ) }),
2081
+ /* @__PURE__ */ jsxs("span", { className: "ai-workflow__run-progress-text", children: [
2082
+ done,
2083
+ "/",
2084
+ total,
2085
+ " (",
2086
+ progress,
2087
+ "%)"
2088
+ ] })
2089
+ ] }),
2090
+ /* @__PURE__ */ jsxs("div", { className: "ai-workflow__run-stats", children: [
2091
+ /* @__PURE__ */ jsxs("span", { className: "ai-workflow__run-stat", children: [
2092
+ "\u23F1 ",
2093
+ (duration / 1e3).toFixed(1),
2094
+ "s"
2095
+ ] }),
2096
+ totalTokens > 0 && /* @__PURE__ */ jsxs("span", { className: "ai-workflow__run-stat", children: [
2097
+ "\u{1FA99} ",
2098
+ totalTokens.toLocaleString(),
2099
+ " tokens"
2100
+ ] }),
2101
+ totalCost > 0 && /* @__PURE__ */ jsxs("span", { className: "ai-workflow__run-stat", children: [
2102
+ "\u{1F4B0} ",
2103
+ formatCost(totalCost)
2104
+ ] })
2105
+ ] }),
2106
+ run.error && /* @__PURE__ */ jsx("div", { className: "ai-workflow__run-error", role: "alert", children: run.error }),
2107
+ /* @__PURE__ */ jsx("ul", { className: "ai-workflow__run-steps", children: run.stepResults.map((stepResult) => /* @__PURE__ */ jsxs(
2108
+ "li",
2109
+ {
2110
+ className: "ai-workflow__run-step-item",
2111
+ "data-status": stepResult.status,
2112
+ children: [
2113
+ /* @__PURE__ */ jsx("span", { children: SI[stepResult.status] }),
2114
+ /* @__PURE__ */ jsx("span", { children: stepResult.stepName }),
2115
+ /* @__PURE__ */ jsxs("span", { children: [
2116
+ stepResult.durationMs,
2117
+ "ms"
2118
+ ] })
2119
+ ]
2120
+ },
2121
+ stepResult.stepId
2122
+ )) })
2123
+ ] });
2124
+ }
2125
+ function getFocusableElements(container) {
2126
+ if (!container) {
2127
+ return [];
2128
+ }
2129
+ return Array.from(
2130
+ container.querySelectorAll(
2131
+ 'button:not([disabled]), [href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])'
2132
+ )
2133
+ );
2134
+ }
2135
+ function ApprovalModal(props) {
2136
+ if (!props.request) return null;
2137
+ const request = props.request;
2138
+ const titleId = `workflow-approval-title-${request.id}`;
2139
+ const messageId = `workflow-approval-message-${request.id}`;
2140
+ const dialogRef = useRef(null);
2141
+ const previousActiveElementRef = useRef(null);
2142
+ useEffect(() => {
2143
+ previousActiveElementRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;
2144
+ const dialog = dialogRef.current;
2145
+ const dismiss = () => {
2146
+ if (props.onDismiss) {
2147
+ props.onDismiss(request.id);
2148
+ return;
2149
+ }
2150
+ props.onReject(request.id);
2151
+ };
2152
+ const focusableElements = getFocusableElements(dialog);
2153
+ (focusableElements[0] ?? dialog)?.focus();
2154
+ const handleKeyDown = (event) => {
2155
+ if (!dialog) {
2156
+ return;
2157
+ }
2158
+ if (event.key === "Escape") {
2159
+ event.preventDefault();
2160
+ dismiss();
2161
+ return;
2162
+ }
2163
+ if (event.key !== "Tab") {
2164
+ return;
2165
+ }
2166
+ const currentFocusableElements = getFocusableElements(dialog);
2167
+ if (currentFocusableElements.length === 0) {
2168
+ event.preventDefault();
2169
+ dialog.focus();
2170
+ return;
2171
+ }
2172
+ const firstElement = currentFocusableElements[0];
2173
+ const lastElement = currentFocusableElements[currentFocusableElements.length - 1];
2174
+ if (event.shiftKey && document.activeElement === firstElement) {
2175
+ event.preventDefault();
2176
+ lastElement.focus();
2177
+ } else if (!event.shiftKey && document.activeElement === lastElement) {
2178
+ event.preventDefault();
2179
+ firstElement.focus();
2180
+ }
2181
+ };
2182
+ dialog?.addEventListener("keydown", handleKeyDown);
2183
+ return () => {
2184
+ dialog?.removeEventListener("keydown", handleKeyDown);
2185
+ previousActiveElementRef.current?.focus();
2186
+ previousActiveElementRef.current = null;
2187
+ };
2188
+ }, [props.onDismiss, props.onReject, request.id]);
2189
+ return /* @__PURE__ */ jsxs("div", { "data-workflow": "approval-modal", children: [
2190
+ /* @__PURE__ */ jsx("div", { className: "ai-workflow__approval-overlay", "aria-hidden": "true" }),
2191
+ /* @__PURE__ */ jsxs(
2192
+ "div",
2193
+ {
2194
+ ref: dialogRef,
2195
+ className: "ai-workflow__approval-dialog",
2196
+ role: "dialog",
2197
+ "aria-modal": "true",
2198
+ "aria-labelledby": titleId,
2199
+ "aria-describedby": messageId,
2200
+ tabIndex: -1,
2201
+ children: [
2202
+ /* @__PURE__ */ jsxs("div", { className: "ai-workflow__approval-header", children: [
2203
+ /* @__PURE__ */ jsx("span", { className: "ai-workflow__approval-icon", children: "\u{1F464}" }),
2204
+ /* @__PURE__ */ jsx("h3", { id: titleId, className: "ai-workflow__approval-title", children: "Approval Required" })
2205
+ ] }),
2206
+ /* @__PURE__ */ jsxs("div", { children: [
2207
+ /* @__PURE__ */ jsx("p", { id: messageId, className: "ai-workflow__approval-message", children: request.message }),
2208
+ /* @__PURE__ */ jsxs("div", { className: "ai-workflow__approval-meta", children: [
2209
+ /* @__PURE__ */ jsxs("span", { children: [
2210
+ "Step: ",
2211
+ request.stepId
2212
+ ] }),
2213
+ /* @__PURE__ */ jsxs("span", { children: [
2214
+ "Requested:",
2215
+ " ",
2216
+ new Date(request.requestedAt).toLocaleTimeString()
2217
+ ] })
2218
+ ] }),
2219
+ request.approvers && request.approvers.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
2220
+ /* @__PURE__ */ jsx("span", { children: "Authorized approvers: " }),
2221
+ request.approvers.map((approver, index) => /* @__PURE__ */ jsx(
2222
+ "span",
2223
+ {
2224
+ className: "ai-workflow__approval-approver-badge",
2225
+ children: approver
2226
+ },
2227
+ index
2228
+ ))
2229
+ ] })
2230
+ ] }),
2231
+ /* @__PURE__ */ jsxs("div", { className: "ai-workflow__approval-actions", children: [
2232
+ /* @__PURE__ */ jsx(
2233
+ "button",
2234
+ {
2235
+ type: "button",
2236
+ className: cx(
2237
+ "ai-workflow__approval-reject",
2238
+ "ai-workflow__focus-visible"
2239
+ ),
2240
+ onClick: () => props.onReject(request.id),
2241
+ children: "\u2715 Reject"
2242
+ }
2243
+ ),
2244
+ /* @__PURE__ */ jsx(
2245
+ "button",
2246
+ {
2247
+ type: "button",
2248
+ className: cx(
2249
+ "ai-workflow__approval-approve",
2250
+ "ai-workflow__focus-visible"
2251
+ ),
2252
+ onClick: () => props.onApprove(request.id),
2253
+ children: "\u2713 Approve"
2254
+ }
2255
+ )
2256
+ ] })
2257
+ ]
2258
+ }
2259
+ )
2260
+ ] });
2261
+ }
2262
+ function WorkflowToolbar(props) {
2263
+ return /* @__PURE__ */ jsxs(
2264
+ "div",
2265
+ {
2266
+ className: cx("ai-workflow__toolbar", props.className),
2267
+ role: "toolbar",
2268
+ "aria-label": "Workflow actions",
2269
+ children: [
2270
+ /* @__PURE__ */ jsxs("div", { className: "ai-workflow__toolbar-left", children: [
2271
+ /* @__PURE__ */ jsx("h2", { className: "ai-workflow__toolbar-title", children: props.workflowName }),
2272
+ props.isDirty && /* @__PURE__ */ jsx("span", { className: "ai-workflow__toolbar-dirty", children: "\u25CF" })
2273
+ ] }),
2274
+ /* @__PURE__ */ jsxs("div", { className: "ai-workflow__toolbar-center", children: [
2275
+ /* @__PURE__ */ jsx(
2276
+ "button",
2277
+ {
2278
+ type: "button",
2279
+ className: cx(
2280
+ "ai-workflow__toolbar-btn",
2281
+ "ai-workflow__focus-visible"
2282
+ ),
2283
+ onClick: props.onUndo,
2284
+ disabled: !props.canUndo,
2285
+ "aria-label": "Undo",
2286
+ children: "\u21B6"
2287
+ }
2288
+ ),
2289
+ /* @__PURE__ */ jsx(
2290
+ "button",
2291
+ {
2292
+ type: "button",
2293
+ className: cx(
2294
+ "ai-workflow__toolbar-btn",
2295
+ "ai-workflow__focus-visible"
2296
+ ),
2297
+ onClick: props.onRedo,
2298
+ disabled: !props.canRedo,
2299
+ "aria-label": "Redo",
2300
+ children: "\u21B7"
2301
+ }
2302
+ )
2303
+ ] }),
2304
+ /* @__PURE__ */ jsxs("div", { className: "ai-workflow__toolbar-right", children: [
2305
+ props.onExport && /* @__PURE__ */ jsx(
2306
+ "button",
2307
+ {
2308
+ type: "button",
2309
+ className: cx(
2310
+ "ai-workflow__toolbar-btn-secondary",
2311
+ "ai-workflow__focus-visible"
2312
+ ),
2313
+ onClick: props.onExport,
2314
+ "aria-label": "Export workflow",
2315
+ children: "\u{1F4E4} Export"
2316
+ }
2317
+ ),
2318
+ /* @__PURE__ */ jsx(
2319
+ "button",
2320
+ {
2321
+ type: "button",
2322
+ className: cx(
2323
+ "ai-workflow__toolbar-btn-secondary",
2324
+ "ai-workflow__focus-visible"
2325
+ ),
2326
+ onClick: props.onSave,
2327
+ disabled: !props.isDirty,
2328
+ "aria-label": "Save workflow",
2329
+ children: "\u{1F4BE} Save"
2330
+ }
2331
+ ),
2332
+ props.isRunning ? /* @__PURE__ */ jsx(
2333
+ "button",
2334
+ {
2335
+ type: "button",
2336
+ className: cx(
2337
+ "ai-workflow__toolbar-btn-danger",
2338
+ "ai-workflow__focus-visible"
2339
+ ),
2340
+ onClick: props.onCancel,
2341
+ "aria-label": "Stop workflow run",
2342
+ children: "\u23F9 Stop"
2343
+ }
2344
+ ) : /* @__PURE__ */ jsx(
2345
+ "button",
2346
+ {
2347
+ type: "button",
2348
+ className: cx(
2349
+ "ai-workflow__toolbar-btn-primary",
2350
+ "ai-workflow__focus-visible"
2351
+ ),
2352
+ onClick: props.onRun,
2353
+ "aria-label": "Run workflow",
2354
+ children: "\u25B6 Run"
2355
+ }
2356
+ )
2357
+ ] })
2358
+ ]
2359
+ }
2360
+ );
2361
+ }
2362
+ var ITEMS = [
2363
+ { type: "llm-call", label: "LLM Call", icon: "\u{1F916}", desc: "Call an AI model" },
2364
+ { type: "transform", label: "Transform", icon: "\u2699\uFE0F", desc: "Transform data" },
2365
+ {
2366
+ type: "condition",
2367
+ label: "Condition",
2368
+ icon: "\u{1F500}",
2369
+ desc: "If/else branching"
2370
+ },
2371
+ {
2372
+ type: "human-approval",
2373
+ label: "Approval",
2374
+ icon: "\u{1F464}",
2375
+ desc: "Human approval gate"
2376
+ },
2377
+ { type: "webhook", label: "Webhook", icon: "\u{1F310}", desc: "HTTP request" },
2378
+ { type: "delay", label: "Delay", icon: "\u23F1\uFE0F", desc: "Wait for duration" },
2379
+ { type: "parallel", label: "Parallel", icon: "\u26A1", desc: "Run in parallel" },
2380
+ { type: "loop", label: "Loop", icon: "\u{1F504}", desc: "Iterate items" },
2381
+ {
2382
+ type: "sub-workflow",
2383
+ label: "Sub-workflow",
2384
+ icon: "\u{1F4CB}",
2385
+ desc: "Run another workflow"
2386
+ },
2387
+ { type: "custom", label: "Custom", icon: "\u{1F527}", desc: "Custom handler" }
2388
+ ];
2389
+ function StepPalette(props) {
2390
+ return /* @__PURE__ */ jsxs(
2391
+ "div",
2392
+ {
2393
+ className: "ai-workflow__step-palette",
2394
+ role: "list",
2395
+ "aria-label": "Available workflow step types",
2396
+ children: [
2397
+ /* @__PURE__ */ jsx("h3", { className: "ai-workflow__palette-title", children: "Steps" }),
2398
+ ITEMS.map((item) => /* @__PURE__ */ jsxs(
2399
+ "button",
2400
+ {
2401
+ type: "button",
2402
+ className: cx(
2403
+ "ai-workflow__palette-item",
2404
+ "ai-workflow__focus-visible"
2405
+ ),
2406
+ role: "listitem",
2407
+ "aria-label": `Add ${item.label} step`,
2408
+ onClick: () => props.onAddStep(item.type),
2409
+ children: [
2410
+ /* @__PURE__ */ jsx("span", { className: "ai-workflow__palette-icon", children: item.icon }),
2411
+ /* @__PURE__ */ jsxs("div", { className: "ai-workflow__palette-info", children: [
2412
+ /* @__PURE__ */ jsx("span", { className: "ai-workflow__palette-label", children: item.label }),
2413
+ /* @__PURE__ */ jsx("span", { className: "ai-workflow__palette-desc", children: item.desc })
2414
+ ] })
2415
+ ]
2416
+ },
2417
+ item.type
2418
+ ))
2419
+ ]
2420
+ }
2421
+ );
2422
+ }
2423
+ function createStepConfig(type) {
2424
+ switch (type) {
2425
+ case "llm-call":
2426
+ return {
2427
+ type,
2428
+ systemPrompt: "",
2429
+ userPromptTemplate: "",
2430
+ outputVar: "result"
2431
+ };
2432
+ case "transform":
2433
+ return { type, expression: "", inputVars: [], outputVar: "result" };
2434
+ case "condition":
2435
+ return { type, expression: "", trueStepId: "", falseStepId: "" };
2436
+ case "human-approval":
2437
+ return { type, message: "" };
2438
+ case "webhook":
2439
+ return { type, url: "", method: "POST", outputVar: "result" };
2440
+ case "delay":
2441
+ return { type, durationMs: 0 };
2442
+ case "parallel":
2443
+ return { type, stepIds: [], failurePolicy: "continue" };
2444
+ case "loop":
2445
+ return {
2446
+ type,
2447
+ stepIds: [],
2448
+ iterateVar: "",
2449
+ itemVar: "",
2450
+ maxIterations: 1
2451
+ };
2452
+ case "sub-workflow":
2453
+ return { type, workflowId: "", inputMapping: {}, outputVar: "result" };
2454
+ case "custom":
2455
+ return { type, handler: "custom", params: {}, outputVar: "result" };
2456
+ }
2457
+ }
2458
+ function WorkflowBuilderPage(props) {
2459
+ const builder = useWorkflowBuilder();
2460
+ const runTracker = useWorkflowRun();
2461
+ const workflowCount = props.workflows?.length ?? 0;
2462
+ const runCount = props.workflowRuns?.length ?? 0;
2463
+ const handleAddStep = useCallback(
2464
+ (type) => {
2465
+ const step = {
2466
+ id: `step_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
2467
+ name: `New ${type} step`,
2468
+ type,
2469
+ config: createStepConfig(type),
2470
+ position: {
2471
+ x: 100 + builder.definition.steps.length * 250,
2472
+ y: 200
2473
+ }
2474
+ };
2475
+ builder.addStep(step);
2476
+ },
2477
+ [builder]
2478
+ );
2479
+ const handleRun = useCallback(async () => {
2480
+ const engine = new WorkflowEngine({
2481
+ ...props.engineOptions,
2482
+ onStepComplete: (result2) => {
2483
+ runTracker.updateStep(result2);
2484
+ }
2485
+ });
2486
+ const result = await engine.execute(builder.definition);
2487
+ runTracker.trackRun(result);
2488
+ }, [builder.definition, props.engineOptions, runTracker]);
2489
+ const selectedStep = builder.selectedStepId ? builder.definition.steps.find((step) => step.id === builder.selectedStepId) ?? null : null;
2490
+ return /* @__PURE__ */ jsxs(
2491
+ "div",
2492
+ {
2493
+ className: cx("ai-workflow__builder-page", props.className),
2494
+ "data-workflow": "builder-page",
2495
+ children: [
2496
+ /* @__PURE__ */ jsxs("div", { className: "ai-workflow__builder-summary", "data-workflow": "builder-summary", children: [
2497
+ /* @__PURE__ */ jsxs("span", { "data-workflow": "builder-workflow-count", children: [
2498
+ workflowCount,
2499
+ " workflows"
2500
+ ] }),
2501
+ /* @__PURE__ */ jsxs("span", { "data-workflow": "builder-run-count", children: [
2502
+ runCount,
2503
+ " runs"
2504
+ ] })
2505
+ ] }),
2506
+ /* @__PURE__ */ jsx(
2507
+ WorkflowToolbar,
2508
+ {
2509
+ workflowName: builder.definition.name,
2510
+ isDirty: builder.isDirty,
2511
+ isRunning: runTracker.isRunning,
2512
+ canUndo: builder.canUndo,
2513
+ canRedo: builder.canRedo,
2514
+ onSave: () => props.onSave?.(builder.definition),
2515
+ onRun: handleRun,
2516
+ onUndo: builder.undo,
2517
+ onRedo: builder.redo
2518
+ }
2519
+ ),
2520
+ /* @__PURE__ */ jsxs("div", { className: "ai-workflow__builder-layout", children: [
2521
+ /* @__PURE__ */ jsx(StepPalette, { onAddStep: handleAddStep }),
2522
+ /* @__PURE__ */ jsx(
2523
+ WorkflowCanvas,
2524
+ {
2525
+ definition: builder.definition,
2526
+ selectedStepId: builder.selectedStepId,
2527
+ onSelectStep: builder.selectStep
2528
+ }
2529
+ ),
2530
+ selectedStep ? /* @__PURE__ */ jsx(
2531
+ StepConfigPanel,
2532
+ {
2533
+ step: selectedStep,
2534
+ onUpdate: builder.updateStep,
2535
+ onDelete: builder.removeStep,
2536
+ onClose: () => builder.selectStep(null)
2537
+ }
2538
+ ) : null,
2539
+ runTracker.run ? /* @__PURE__ */ jsx(
2540
+ WorkflowRunPanel,
2541
+ {
2542
+ run: runTracker.run,
2543
+ isRunning: runTracker.isRunning
2544
+ }
2545
+ ) : null
2546
+ ] })
2547
+ ]
2548
+ }
2549
+ );
2550
+ }
2551
+ var SI2 = {
2552
+ pending: "\u23F3",
2553
+ running: "\u{1F504}",
2554
+ completed: "\u2705",
2555
+ failed: "\u274C",
2556
+ cancelled: "\u26D4",
2557
+ paused: "\u23F8\uFE0F"
2558
+ };
2559
+ function WorkflowRunsPage(props) {
2560
+ return /* @__PURE__ */ jsxs("div", { className: cx("ai-workflow__runs-page", props.className), children: [
2561
+ /* @__PURE__ */ jsxs("div", { className: "ai-workflow__runs-header", children: [
2562
+ /* @__PURE__ */ jsx("h1", { className: "ai-workflow__runs-title", children: "Workflow Runs" }),
2563
+ /* @__PURE__ */ jsxs("span", { className: "ai-workflow__runs-count", children: [
2564
+ props.runs.length,
2565
+ " runs"
2566
+ ] })
2567
+ ] }),
2568
+ props.runs.length === 0 ? /* @__PURE__ */ jsx("div", { className: "ai-workflow__runs-empty", children: /* @__PURE__ */ jsx("p", { children: "No runs yet." }) }) : /* @__PURE__ */ jsx("div", { role: "list", children: props.runs.map((run) => {
2569
+ const completedSteps = run.stepResults.filter(
2570
+ (step) => step.status === "completed"
2571
+ ).length;
2572
+ return /* @__PURE__ */ jsxs(
2573
+ "div",
2574
+ {
2575
+ className: cx(
2576
+ "ai-workflow__run-card",
2577
+ "ai-workflow__focus-visible"
2578
+ ),
2579
+ role: "listitem",
2580
+ tabIndex: 0,
2581
+ onClick: () => props.onSelectRun?.(run.id),
2582
+ onKeyDown: (event) => {
2583
+ if (event.key === "Enter" || event.key === " ") {
2584
+ event.preventDefault();
2585
+ props.onSelectRun?.(run.id);
2586
+ }
2587
+ },
2588
+ children: [
2589
+ /* @__PURE__ */ jsxs("div", { className: "ai-workflow__run-card-header", children: [
2590
+ /* @__PURE__ */ jsxs("span", { className: "ai-workflow__run-card-status", children: [
2591
+ SI2[run.status],
2592
+ " ",
2593
+ run.status
2594
+ ] }),
2595
+ /* @__PURE__ */ jsxs("span", { className: "ai-workflow__run-card-id", children: [
2596
+ run.id.slice(0, 8),
2597
+ "\u2026"
2598
+ ] })
2599
+ ] }),
2600
+ /* @__PURE__ */ jsxs("div", { className: "ai-workflow__run-card-meta", children: [
2601
+ /* @__PURE__ */ jsxs("span", { children: [
2602
+ completedSteps,
2603
+ "/",
2604
+ run.stepResults.length,
2605
+ " steps"
2606
+ ] }),
2607
+ /* @__PURE__ */ jsx("span", { children: new Date(run.startedAt).toLocaleString() })
2608
+ ] }),
2609
+ run.error && /* @__PURE__ */ jsx("p", { className: "ai-workflow__run-card-error", children: run.error })
2610
+ ]
2611
+ },
2612
+ run.id
2613
+ );
2614
+ }) })
2615
+ ] });
2616
+ }
2617
+
2618
+ export { ApprovalModal, StepConfigPanel, StepConnector, StepNode, StepPalette, WORKFLOW_TEMPLATES, WorkflowBuilderPage, WorkflowCanvas, WorkflowEngine, WorkflowRunPanel, WorkflowRunsPage, WorkflowToolbar, getTemplate, getTemplatesByCategory, useApprovalGate, useWorkflow, useWorkflowBuilder, useWorkflowRun, useWorkflowStep, useWorkflowTemplates, validateRun, validateWorkflow };
2619
+ //# sourceMappingURL=index.js.map
2620
+ //# sourceMappingURL=index.js.map