@particle-academy/fancy-flow 0.11.0 → 0.12.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 (37) hide show
  1. package/dist/{chunk-YNOI7ONN.js → chunk-IK5DS5JP.js} +258 -51
  2. package/dist/chunk-IK5DS5JP.js.map +1 -0
  3. package/dist/{chunk-2NQT2A3I.js → chunk-L4AX73Q6.js} +5 -4
  4. package/dist/chunk-L4AX73Q6.js.map +1 -0
  5. package/dist/{chunk-3GMBLHTM.js → chunk-MFMRTRPO.js} +3 -3
  6. package/dist/{chunk-3GMBLHTM.js.map → chunk-MFMRTRPO.js.map} +1 -1
  7. package/dist/engine.cjs +3 -2
  8. package/dist/engine.cjs.map +1 -1
  9. package/dist/engine.d.cts +4 -2
  10. package/dist/engine.d.ts +4 -2
  11. package/dist/engine.js +1 -1
  12. package/dist/index.cjs +396 -215
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +4 -4
  15. package/dist/index.d.ts +4 -4
  16. package/dist/index.js +5 -5
  17. package/dist/registry/index.d.cts +132 -4
  18. package/dist/registry/index.d.ts +132 -4
  19. package/dist/registry.cjs +455 -93
  20. package/dist/registry.cjs.map +1 -1
  21. package/dist/registry.js +2 -1
  22. package/dist/runtime/index.d.cts +1 -1
  23. package/dist/runtime/index.d.ts +1 -1
  24. package/dist/runtime.cjs +3 -2
  25. package/dist/runtime.cjs.map +1 -1
  26. package/dist/runtime.js +2 -2
  27. package/dist/schema/index.d.cts +1 -1
  28. package/dist/schema/index.d.ts +1 -1
  29. package/dist/{types-BS3Gwnkq.d.cts → types-Dg2REAz5.d.cts} +6 -0
  30. package/dist/{types-BS3Gwnkq.d.ts → types-Dg2REAz5.d.ts} +6 -0
  31. package/dist/{types-BwLr5tZV.d.cts → types-DnQ4TEHN.d.cts} +1 -1
  32. package/dist/{types-CsEe3EQl.d.ts → types-DtXOwXjA.d.ts} +1 -1
  33. package/dist/ux.d.cts +2 -2
  34. package/dist/ux.d.ts +2 -2
  35. package/package.json +1 -1
  36. package/dist/chunk-2NQT2A3I.js.map +0 -1
  37. package/dist/chunk-YNOI7ONN.js.map +0 -1
package/dist/registry.cjs CHANGED
@@ -30,60 +30,46 @@ function resolveNodePorts(node, kind) {
30
30
  outputs: data?.outputs ?? resolvePortSpec(kind?.outputs, config)
31
31
  };
32
32
  }
33
- var adapter = null;
34
- var listeners = /* @__PURE__ */ new Set();
35
- function registerRichInputAdapter(next) {
36
- adapter = next;
37
- for (const l of listeners) l();
33
+
34
+ // src/registry/capabilities.ts
35
+ var llmClient = null;
36
+ function registerLlmClient(client) {
37
+ llmClient = client;
38
38
  return () => {
39
- if (adapter === next) {
40
- adapter = null;
41
- for (const l of listeners) l();
42
- }
39
+ if (llmClient === client) llmClient = null;
43
40
  };
44
41
  }
45
- function getRichInputAdapter() {
46
- return adapter;
42
+ function getLlmClient() {
43
+ return llmClient;
47
44
  }
48
- function isRichInputEnabled() {
49
- return adapter !== null && (adapter.renderDocument !== void 0 || adapter.renderEditor !== void 0);
45
+ var workflowResolver = null;
46
+ function registerWorkflowResolver(resolver) {
47
+ workflowResolver = resolver;
48
+ return () => {
49
+ if (workflowResolver === resolver) workflowResolver = null;
50
+ };
50
51
  }
51
- function onRichInputAdapterChanged(fn) {
52
- listeners.add(fn);
53
- return () => listeners.delete(fn);
52
+ function getWorkflowResolver() {
53
+ return workflowResolver;
54
54
  }
55
- function RichInputPreview({ config }) {
56
- const a = getRichInputAdapter();
57
- const title = typeof config.title === "string" && config.title.trim() !== "" ? config.title : "Untitled step";
58
- const doc = config.document;
59
- if (!a || !a.renderDocument && !a.renderEditor) {
60
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-rich-preview", children: [
61
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ff-rich-preview__title", children: title }),
62
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-rich-preview__unavailable", children: [
63
- "Add ",
64
- /* @__PURE__ */ jsxRuntime.jsx("code", { children: "@particle-academy/fancy-cms-ui" }),
65
- " +",
66
- " ",
67
- /* @__PURE__ */ jsxRuntime.jsx("code", { children: "@particle-academy/react-fancy" }),
68
- ", then",
69
- " ",
70
- /* @__PURE__ */ jsxRuntime.jsx("code", { children: 'import "@particle-academy/fancy-flow/rich-input"' }),
71
- "."
72
- ] })
73
- ] });
55
+ function capabilityStatus() {
56
+ let documentReady = false;
57
+ try {
58
+ documentReady = Boolean(globalThis.__fancyFlowDocumentAdapter);
59
+ } catch {
60
+ documentReady = false;
74
61
  }
75
- const body = doc === void 0 || doc === null ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "ff-rich-preview__unavailable", children: "Nothing authored yet." }) : a.renderDocument?.(doc) ?? null;
76
- const Frame = a.FauxClient;
77
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-rich-preview", children: [
78
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ff-rich-preview__title", children: title }),
79
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ff-rich-preview__frame", children: Frame ? /* @__PURE__ */ jsxRuntime.jsx(Frame, { ...a.frameProps ?? { variant: "browser" }, children: body }) : body })
80
- ] });
62
+ return {
63
+ llm: llmClient !== null,
64
+ workflow_resolver: workflowResolver !== null,
65
+ document: documentReady
66
+ };
81
67
  }
82
68
 
83
69
  // src/registry/registry.ts
84
70
  var kinds = /* @__PURE__ */ new Map();
85
71
  var aliases = /* @__PURE__ */ new Map();
86
- var listeners2 = /* @__PURE__ */ new Set();
72
+ var listeners = /* @__PURE__ */ new Set();
87
73
  function registerNodeKind(definition) {
88
74
  kinds.set(definition.name, definition);
89
75
  for (const alias of definition.aliases ?? []) aliases.set(alias, definition.name);
@@ -115,11 +101,11 @@ function listNodeKinds(category) {
115
101
  return category ? all.filter((k) => k.category === category) : all;
116
102
  }
117
103
  function onNodeKindsChanged(listener) {
118
- listeners2.add(listener);
119
- return () => listeners2.delete(listener);
104
+ listeners.add(listener);
105
+ return () => listeners.delete(listener);
120
106
  }
121
107
  function notify() {
122
- for (const l of listeners2) l();
108
+ for (const l of listeners) l();
123
109
  }
124
110
  function defaultConfigFor(kind) {
125
111
  const fromKind = kind.defaultConfig ? { ...kind.defaultConfig } : {};
@@ -233,6 +219,315 @@ function categoryAccent(category) {
233
219
  }
234
220
  }
235
221
 
222
+ // src/runtime/run-flow.ts
223
+ async function runFlow(graph, executors, onEvent = () => {
224
+ }, options = {}) {
225
+ const { signal, initialInputs = {}, timeoutMs, depth = 0 } = options;
226
+ const outputs = {};
227
+ const portValues = /* @__PURE__ */ new Map();
228
+ const completed = /* @__PURE__ */ new Set();
229
+ const errors = [];
230
+ const order = topoSort(graph);
231
+ if (order === null) {
232
+ const msg = "Cycle detected in flow graph \u2014 aborting.";
233
+ onEvent({ type: "run-error", error: msg });
234
+ return { ok: false, outputs, error: msg };
235
+ }
236
+ const incomingByNode = indexIncoming(graph.edges);
237
+ const timer2 = timeoutMs ? setTimeout(() => errors.push(`Run timed out after ${timeoutMs}ms`), timeoutMs) : null;
238
+ onEvent({ type: "run-start" });
239
+ try {
240
+ for (const node of order) {
241
+ if (signal?.aborted) throw new Error("aborted");
242
+ if (errors.length) break;
243
+ const incoming = incomingByNode.get(node.id) ?? [];
244
+ if (incoming.length > 0) {
245
+ const anyActive = incoming.some((e) => portValues.has(`${e.source}:${e.sourceHandle ?? "out"}`));
246
+ if (!anyActive) {
247
+ onEvent({ type: "node-status", nodeId: node.id, status: "idle", text: "skipped" });
248
+ continue;
249
+ }
250
+ }
251
+ if (node.type === "note") {
252
+ onEvent({ type: "node-status", nodeId: node.id, status: "idle", text: "annotation" });
253
+ continue;
254
+ }
255
+ onEvent({ type: "node-status", nodeId: node.id, status: "running" });
256
+ const inputs = collectInputs(node, incoming, portValues, initialInputs);
257
+ const exec = pickExecutor(executors, node);
258
+ if (!exec) {
259
+ const msg = `No executor registered for kind=${node.type}`;
260
+ errors.push(msg);
261
+ onEvent({ type: "node-status", nodeId: node.id, status: "error", text: msg });
262
+ onEvent({ type: "log", nodeId: node.id, level: "error", message: msg });
263
+ break;
264
+ }
265
+ try {
266
+ const result = await Promise.resolve(
267
+ exec({
268
+ node,
269
+ inputs,
270
+ abort: (reason) => {
271
+ throw new Error(reason ?? "aborted");
272
+ },
273
+ emit: onEvent,
274
+ depth
275
+ })
276
+ );
277
+ outputs[node.id] = result;
278
+ const activated = activatedPorts(node, result);
279
+ for (const portId of activated.ports) {
280
+ portValues.set(`${node.id}:${portId}`, activated.value);
281
+ onEvent({ type: "node-output", nodeId: node.id, portId, value: activated.value });
282
+ }
283
+ completed.add(node.id);
284
+ onEvent({ type: "node-status", nodeId: node.id, status: "done" });
285
+ } catch (e) {
286
+ const msg = e instanceof Error ? e.message : String(e);
287
+ errors.push(msg);
288
+ onEvent({ type: "node-status", nodeId: node.id, status: "error", text: msg });
289
+ onEvent({ type: "log", nodeId: node.id, level: "error", message: msg });
290
+ break;
291
+ }
292
+ }
293
+ } finally {
294
+ if (timer2) clearTimeout(timer2);
295
+ }
296
+ const ok = errors.length === 0;
297
+ onEvent({ type: "run-end", ok });
298
+ return ok ? { ok, outputs } : { ok, outputs, error: errors[0] };
299
+ }
300
+ function indexIncoming(edges) {
301
+ const map = /* @__PURE__ */ new Map();
302
+ for (const e of edges) {
303
+ const list = map.get(e.target) ?? [];
304
+ list.push(e);
305
+ map.set(e.target, list);
306
+ }
307
+ return map;
308
+ }
309
+ function topoSort(graph) {
310
+ const inDegree = /* @__PURE__ */ new Map();
311
+ for (const n of graph.nodes) inDegree.set(n.id, 0);
312
+ for (const e of graph.edges) inDegree.set(e.target, (inDegree.get(e.target) ?? 0) + 1);
313
+ const queue = [];
314
+ for (const [id2, d] of inDegree) if (d === 0) queue.push(id2);
315
+ const ordered = [];
316
+ while (queue.length) {
317
+ const id2 = queue.shift();
318
+ ordered.push(id2);
319
+ for (const e of graph.edges) {
320
+ if (e.source !== id2) continue;
321
+ const next = (inDegree.get(e.target) ?? 0) - 1;
322
+ inDegree.set(e.target, next);
323
+ if (next === 0) queue.push(e.target);
324
+ }
325
+ }
326
+ if (ordered.length !== graph.nodes.length) return null;
327
+ const byId = new Map(graph.nodes.map((n) => [n.id, n]));
328
+ return ordered.map((id2) => byId.get(id2)).filter(Boolean);
329
+ }
330
+ function collectInputs(node, incoming, portValues, initial) {
331
+ const inputs = { ...initial[node.id] ?? {} };
332
+ for (const e of incoming) {
333
+ const portId = e.targetHandle ?? "in";
334
+ const val = portValues.get(`${e.source}:${e.sourceHandle ?? "out"}`);
335
+ inputs[portId] = val;
336
+ }
337
+ return inputs;
338
+ }
339
+ function pickExecutor(executors, node) {
340
+ if (executors[node.id]) return executors[node.id];
341
+ if (node.type && executors[node.type]) return executors[node.type];
342
+ const kind = node.type ? getNodeKind(node.type) : null;
343
+ if (kind) {
344
+ for (const id2 of kindIds(kind)) {
345
+ if (executors[id2]) return executors[id2];
346
+ }
347
+ }
348
+ return executors["*"];
349
+ }
350
+ function activatedPorts(node, result) {
351
+ if (result && typeof result === "object") {
352
+ const r = result;
353
+ if (typeof r.__port === "string") {
354
+ return { ports: [r.__port], value: r.value };
355
+ }
356
+ if (typeof r.branch === "string") {
357
+ return { ports: [r.branch], value: r.value ?? r };
358
+ }
359
+ }
360
+ const kind = getNodeKind(node.data?.kind ?? node.type ?? "") ?? void 0;
361
+ const declared = resolveNodePorts(node, kind).outputs?.map((p) => p.id);
362
+ return { ports: declared?.length ? declared : ["out"], value: result };
363
+ }
364
+
365
+ // src/registry/subflow.ts
366
+ var DEFAULT_MAX_DEPTH = 8;
367
+ function subflowMode(config) {
368
+ const mode = config.mode;
369
+ return mode === "stream" || mode === "both" ? mode : "output";
370
+ }
371
+ function subflowPorts(config) {
372
+ const mode = subflowMode(config);
373
+ const ports = [{ id: "out", label: "result" }];
374
+ if (mode === "stream" || mode === "both") ports.unshift({ id: "stream", label: "stream" });
375
+ return ports;
376
+ }
377
+ var subflowExecutor = async (ctx) => {
378
+ const config = ctx.node.data?.config ?? {};
379
+ const ref = String(config.workflow ?? "").trim();
380
+ if (!ref) ctx.abort("subflow has no workflow reference configured");
381
+ const resolver = getWorkflowResolver();
382
+ if (!resolver) {
383
+ ctx.abort(
384
+ "No workflow resolver registered. Call registerWorkflowResolver() so subflow can find the workflow it references."
385
+ );
386
+ }
387
+ const maxDepth = Number.isFinite(config.maxDepth) ? Number(config.maxDepth) : DEFAULT_MAX_DEPTH;
388
+ const depth = ctx.depth ?? 0;
389
+ if (depth + 1 > maxDepth) {
390
+ ctx.abort(
391
+ `subflow depth limit reached (${maxDepth}) at "${ref}" \u2014 a workflow is referencing itself, directly or through a chain.`
392
+ );
393
+ }
394
+ const child = await resolver(ref);
395
+ if (!child) ctx.abort(`subflow could not resolve workflow "${ref}"`);
396
+ const mode = subflowMode(config);
397
+ const streaming = mode === "stream" || mode === "both";
398
+ const forward = (event) => {
399
+ if (!streaming) return;
400
+ const detail = event.type === "node-status" ? `${event.nodeId} ${event.status}` : event.type === "run-end" ? `finished (${event.ok ? "ok" : "failed"})` : event.type;
401
+ ctx.emit({
402
+ type: "log",
403
+ nodeId: ctx.node.id,
404
+ level: "info",
405
+ message: `[${ref}] ${detail}`
406
+ });
407
+ };
408
+ const result = await runFlow(
409
+ child,
410
+ config.executors ?? {},
411
+ forward,
412
+ {
413
+ initialInputs: config.inputs ?? {
414
+ // With no explicit mapping, hand the parent's inputs to the child's
415
+ // entry points — the obvious default, and it makes the simple case
416
+ // require no configuration at all.
417
+ __parent: ctx.inputs
418
+ },
419
+ depth: depth + 1
420
+ }
421
+ );
422
+ if (!result.ok) {
423
+ ctx.abort(`subflow "${ref}" failed: ${result.error ?? "unknown error"}`);
424
+ }
425
+ if (mode === "stream") {
426
+ return { __port: "stream", value: result.outputs };
427
+ }
428
+ if (mode === "both") {
429
+ return result.outputs;
430
+ }
431
+ return { __port: "out", value: result.outputs };
432
+ };
433
+
434
+ // src/registry/llm-branch.ts
435
+ function declaredRoutes(config) {
436
+ const raw = config.routes;
437
+ if (!Array.isArray(raw)) return [];
438
+ return raw.map((r) => ({ port: String(r?.port ?? "").trim(), description: r?.description })).filter((r) => r.port !== "");
439
+ }
440
+ function resolveFallbackPort(routes, fallbackEnabled) {
441
+ if (fallbackEnabled) return "fallback";
442
+ return routes[0]?.port ?? "out";
443
+ }
444
+ var llmBranchExecutor = async (ctx) => {
445
+ const config = ctx.node.data?.config ?? {};
446
+ const routes = declaredRoutes(config);
447
+ if (routes.length === 0) {
448
+ ctx.abort("llm_branch has no routes configured");
449
+ }
450
+ const client = getLlmClient();
451
+ if (!client) {
452
+ ctx.abort(
453
+ "No LLM client registered. Call registerLlmClient() with your provider adapter \u2014 fancy-flow ships the routing, not the model call."
454
+ );
455
+ }
456
+ const fallbackEnabled = config.fallback !== false;
457
+ const choice = await client.chooseRoute({
458
+ system: typeof config.system === "string" ? config.system : void 0,
459
+ prompt: String(config.prompt ?? ctx.inputs ?? ""),
460
+ routes,
461
+ provider: typeof config.provider === "string" ? config.provider : void 0,
462
+ model: typeof config.model === "string" ? config.model : void 0,
463
+ credential: typeof config.credential === "string" ? config.credential : void 0
464
+ });
465
+ const offered = new Set(routes.map((r) => r.port));
466
+ let port = choice?.port ?? "";
467
+ let reason = choice?.reason;
468
+ if (!offered.has(port)) {
469
+ const safe = resolveFallbackPort(routes, fallbackEnabled);
470
+ ctx.emit({
471
+ type: "log",
472
+ nodeId: ctx.node.id,
473
+ level: "warn",
474
+ message: `llm_branch: model returned "${port || "(nothing)"}", which is not a declared route. Routing to "${safe}".`
475
+ });
476
+ reason = reason ?? `unrecognised route "${port}"`;
477
+ port = safe;
478
+ }
479
+ return { __port: port, value: { route: port, reason, input: ctx.inputs } };
480
+ };
481
+ var adapter = null;
482
+ var listeners2 = /* @__PURE__ */ new Set();
483
+ function registerRichInputAdapter(next) {
484
+ adapter = next;
485
+ for (const l of listeners2) l();
486
+ return () => {
487
+ if (adapter === next) {
488
+ adapter = null;
489
+ for (const l of listeners2) l();
490
+ }
491
+ };
492
+ }
493
+ function getRichInputAdapter() {
494
+ return adapter;
495
+ }
496
+ function isRichInputEnabled() {
497
+ return adapter !== null && (adapter.renderDocument !== void 0 || adapter.renderEditor !== void 0);
498
+ }
499
+ function onRichInputAdapterChanged(fn) {
500
+ listeners2.add(fn);
501
+ return () => listeners2.delete(fn);
502
+ }
503
+ function RichInputPreview({ config }) {
504
+ const a = getRichInputAdapter();
505
+ const title = typeof config.title === "string" && config.title.trim() !== "" ? config.title : "Untitled step";
506
+ const doc = config.document;
507
+ if (!a || !a.renderDocument && !a.renderEditor) {
508
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-rich-preview", children: [
509
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ff-rich-preview__title", children: title }),
510
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-rich-preview__unavailable", children: [
511
+ "Add ",
512
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: "@particle-academy/fancy-cms-ui" }),
513
+ " +",
514
+ " ",
515
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: "@particle-academy/react-fancy" }),
516
+ ", then",
517
+ " ",
518
+ /* @__PURE__ */ jsxRuntime.jsx("code", { children: 'import "@particle-academy/fancy-flow/rich-input"' }),
519
+ "."
520
+ ] })
521
+ ] });
522
+ }
523
+ const body = doc === void 0 || doc === null ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "ff-rich-preview__unavailable", children: "Nothing authored yet." }) : a.renderDocument?.(doc) ?? null;
524
+ const Frame = a.FauxClient;
525
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ff-rich-preview", children: [
526
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ff-rich-preview__title", children: title }),
527
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "ff-rich-preview__frame", children: Frame ? /* @__PURE__ */ jsxRuntime.jsx(Frame, { ...a.frameProps ?? { variant: "browser" }, children: body }) : body })
528
+ ] });
529
+ }
530
+
236
531
  // node_modules/classcat/index.js
237
532
  function cc(names) {
238
533
  if (typeof names === "string" || typeof names === "number") return "" + names;
@@ -9290,8 +9585,8 @@ var HTTP_METHODS = [
9290
9585
  var KINDS = [
9291
9586
  // ───────────── Triggers ─────────────
9292
9587
  {
9293
- name: "@fancy/manual_trigger",
9294
- aliases: ["manual_trigger"],
9588
+ name: "@particle-academy/manual_trigger",
9589
+ aliases: ["manual_trigger", "@fancy/manual_trigger"],
9295
9590
  category: "trigger",
9296
9591
  label: "Manual",
9297
9592
  description: "Entry point fired when the user clicks Run.",
@@ -9300,8 +9595,8 @@ var KINDS = [
9300
9595
  outputs: [{ id: "out" }]
9301
9596
  },
9302
9597
  {
9303
- name: "@fancy/webhook_trigger",
9304
- aliases: ["webhook_trigger"],
9598
+ name: "@particle-academy/webhook_trigger",
9599
+ aliases: ["webhook_trigger", "@fancy/webhook_trigger"],
9305
9600
  category: "trigger",
9306
9601
  label: "Webhook",
9307
9602
  description: "Triggered by an inbound HTTP request to a host-provided URL.",
@@ -9318,8 +9613,8 @@ var KINDS = [
9318
9613
  ]
9319
9614
  },
9320
9615
  {
9321
- name: "@fancy/schedule_trigger",
9322
- aliases: ["schedule_trigger"],
9616
+ name: "@particle-academy/schedule_trigger",
9617
+ aliases: ["schedule_trigger", "@fancy/schedule_trigger"],
9323
9618
  category: "trigger",
9324
9619
  label: "Schedule",
9325
9620
  description: "Fires on a cron schedule (host-implemented).",
@@ -9339,8 +9634,8 @@ var KINDS = [
9339
9634
  ]
9340
9635
  },
9341
9636
  {
9342
- name: "@fancy/user_input",
9343
- aliases: ["user_input"],
9637
+ name: "@particle-academy/user_input",
9638
+ aliases: ["user_input", "@fancy/user_input"],
9344
9639
  category: "human",
9345
9640
  label: "User Input",
9346
9641
  description: "Pause the flow until the user submits the configured form.",
@@ -9380,8 +9675,8 @@ var KINDS = [
9380
9675
  ]
9381
9676
  },
9382
9677
  {
9383
- name: "@fancy/rich_user_input",
9384
- aliases: ["rich_user_input"],
9678
+ name: "@particle-academy/rich_user_input",
9679
+ aliases: ["rich_user_input", "@fancy/rich_user_input"],
9385
9680
  category: "human",
9386
9681
  label: "Rich User Input",
9387
9682
  description: "Pause the flow on a fully authored page \u2014 content, required reading, multi-section forms.",
@@ -9404,10 +9699,61 @@ var KINDS = [
9404
9699
  // what the person hitting this step will actually see.
9405
9700
  renderBody: (ctx) => ReactExports.createElement(RichInputPreview, { config: ctx.config ?? {} })
9406
9701
  },
9702
+ {
9703
+ name: "@particle-academy/subflow",
9704
+ aliases: ["subflow", "@fancy/subflow"],
9705
+ category: "logic",
9706
+ label: "SubFlow",
9707
+ description: "Run another workflow and bring its result \u2014 or its live progress \u2014 back into this one.",
9708
+ icon: "\u29C9",
9709
+ inputs: [{ id: "in" }],
9710
+ // The stream port only exists when something actually streams.
9711
+ outputs: (config) => subflowPorts(config ?? {}),
9712
+ // Core, not marketplace: it runs a child graph through this same engine and
9713
+ // needs nothing from outside except where workflows live.
9714
+ executor: subflowExecutor,
9715
+ configSchema: [
9716
+ {
9717
+ type: "text",
9718
+ key: "workflow",
9719
+ label: "Workflow",
9720
+ required: true,
9721
+ placeholder: "onboarding-v2",
9722
+ description: "Reference resolved by the host's registerWorkflowResolver()."
9723
+ },
9724
+ {
9725
+ type: "select",
9726
+ key: "mode",
9727
+ label: "Return",
9728
+ default: "output",
9729
+ options: [
9730
+ { value: "output", label: "Output when it finishes" },
9731
+ { value: "stream", label: "Stream progress as it runs" },
9732
+ { value: "both", label: "Both \u2014 stream, then output" }
9733
+ ],
9734
+ description: "Streaming adds a second port so a parent can show progress instead of a spinner."
9735
+ },
9736
+ {
9737
+ type: "json",
9738
+ key: "inputs",
9739
+ label: "Input mapping",
9740
+ description: "Entry-point inputs for the child run. Omit to pass this node's inputs straight through."
9741
+ },
9742
+ {
9743
+ type: "number",
9744
+ key: "maxDepth",
9745
+ label: "Max nesting depth",
9746
+ default: DEFAULT_MAX_DEPTH,
9747
+ min: 1,
9748
+ max: 32,
9749
+ description: "Guards against a workflow referencing itself."
9750
+ }
9751
+ ]
9752
+ },
9407
9753
  // ───────────── Logic ─────────────
9408
9754
  {
9409
- name: "@fancy/branch",
9410
- aliases: ["branch"],
9755
+ name: "@particle-academy/branch",
9756
+ aliases: ["branch", "@fancy/branch"],
9411
9757
  category: "logic",
9412
9758
  label: "Branch",
9413
9759
  description: "Multi-way branch on a condition or value.",
@@ -9419,8 +9765,8 @@ var KINDS = [
9419
9765
  ]
9420
9766
  },
9421
9767
  {
9422
- name: "@fancy/switch_case",
9423
- aliases: ["switch_case"],
9768
+ name: "@particle-academy/switch_case",
9769
+ aliases: ["switch_case", "@fancy/switch_case"],
9424
9770
  category: "logic",
9425
9771
  label: "Switch",
9426
9772
  description: "Route to one of N labelled outputs based on a key.",
@@ -9447,8 +9793,8 @@ var KINDS = [
9447
9793
  ]
9448
9794
  },
9449
9795
  {
9450
- name: "@fancy/for_each",
9451
- aliases: ["for_each"],
9796
+ name: "@particle-academy/for_each",
9797
+ aliases: ["for_each", "@fancy/for_each"],
9452
9798
  category: "logic",
9453
9799
  label: "For Each",
9454
9800
  description: "Iterate over a list, emitting each item on `item`.",
@@ -9461,8 +9807,8 @@ var KINDS = [
9461
9807
  ]
9462
9808
  },
9463
9809
  {
9464
- name: "@fancy/merge",
9465
- aliases: ["merge"],
9810
+ name: "@particle-academy/merge",
9811
+ aliases: ["merge", "@fancy/merge"],
9466
9812
  category: "logic",
9467
9813
  label: "Merge",
9468
9814
  description: "Combine multiple inputs into one object or array.",
@@ -9480,8 +9826,8 @@ var KINDS = [
9480
9826
  ]
9481
9827
  },
9482
9828
  {
9483
- name: "@fancy/wait",
9484
- aliases: ["wait"],
9829
+ name: "@particle-academy/wait",
9830
+ aliases: ["wait", "@fancy/wait"],
9485
9831
  category: "logic",
9486
9832
  label: "Wait",
9487
9833
  description: "Sleep or wait for an external event.",
@@ -9498,8 +9844,8 @@ var KINDS = [
9498
9844
  ]
9499
9845
  },
9500
9846
  {
9501
- name: "@fancy/transform",
9502
- aliases: ["transform"],
9847
+ name: "@particle-academy/transform",
9848
+ aliases: ["transform", "@fancy/transform"],
9503
9849
  category: "logic",
9504
9850
  label: "Transform",
9505
9851
  description: "Reshape data with an expression.",
@@ -9516,8 +9862,8 @@ var KINDS = [
9516
9862
  },
9517
9863
  // ───────────── Data ─────────────
9518
9864
  {
9519
- name: "@fancy/memory_store",
9520
- aliases: ["memory_store"],
9865
+ name: "@particle-academy/memory_store",
9866
+ aliases: ["memory_store", "@fancy/memory_store"],
9521
9867
  category: "data",
9522
9868
  label: "Memory Store",
9523
9869
  description: "Read or write per-conversation memory.",
@@ -9537,8 +9883,8 @@ var KINDS = [
9537
9883
  ]
9538
9884
  },
9539
9885
  {
9540
- name: "@fancy/data_store",
9541
- aliases: ["data_store"],
9886
+ name: "@particle-academy/data_store",
9887
+ aliases: ["data_store", "@fancy/data_store"],
9542
9888
  category: "data",
9543
9889
  label: "Data Store",
9544
9890
  description: "Key-value or table read/write against a host store.",
@@ -9566,8 +9912,8 @@ var KINDS = [
9566
9912
  ]
9567
9913
  },
9568
9914
  {
9569
- name: "@fancy/variable",
9570
- aliases: ["variable"],
9915
+ name: "@particle-academy/variable",
9916
+ aliases: ["variable", "@fancy/variable"],
9571
9917
  category: "data",
9572
9918
  label: "Variable",
9573
9919
  description: "Workflow-scoped value used by other nodes.",
@@ -9579,8 +9925,8 @@ var KINDS = [
9579
9925
  },
9580
9926
  // ───────────── AI ─────────────
9581
9927
  {
9582
- name: "@fancy/llm_call",
9583
- aliases: ["llm_call"],
9928
+ name: "@particle-academy/llm_call",
9929
+ aliases: ["llm_call", "@fancy/llm_call"],
9584
9930
  category: "ai",
9585
9931
  label: "LLM Call",
9586
9932
  description: "Send a prompt + context to a model and receive a response.",
@@ -9607,8 +9953,8 @@ var KINDS = [
9607
9953
  ]
9608
9954
  },
9609
9955
  {
9610
- name: "@fancy/llm_branch",
9611
- aliases: ["llm_branch"],
9956
+ name: "@particle-academy/llm_branch",
9957
+ aliases: ["llm_branch", "@fancy/llm_branch"],
9612
9958
  category: "ai",
9613
9959
  label: "LLM Router",
9614
9960
  description: "Let a model choose which route the flow takes.",
@@ -9617,6 +9963,10 @@ var KINDS = [
9617
9963
  // Each declared route is a port. The executor returns `{ __port: id }`
9618
9964
  // (or `Port.only(id)` on the PHP runtime) to pick one.
9619
9965
  outputs: (config) => routePorts(config?.routes, config?.fallback),
9966
+ // A shuttle, not an engine: it carries the routes out to whatever LLM
9967
+ // client the host registered and carries the choice back. No provider SDK
9968
+ // reaches core, so this stays a builtin without adding a dependency.
9969
+ executor: llmBranchExecutor,
9620
9970
  configSchema: [
9621
9971
  {
9622
9972
  type: "textarea",
@@ -9678,8 +10028,8 @@ var KINDS = [
9678
10028
  ]
9679
10029
  },
9680
10030
  {
9681
- name: "@fancy/tool_use",
9682
- aliases: ["tool_use"],
10031
+ name: "@particle-academy/tool_use",
10032
+ aliases: ["tool_use", "@fancy/tool_use"],
9683
10033
  category: "ai",
9684
10034
  label: "Tool Use",
9685
10035
  description: "Hand control to a host-registered tool by name.",
@@ -9690,8 +10040,8 @@ var KINDS = [
9690
10040
  ]
9691
10041
  },
9692
10042
  {
9693
- name: "@fancy/embed_search",
9694
- aliases: ["embed_search"],
10043
+ name: "@particle-academy/embed_search",
10044
+ aliases: ["embed_search", "@fancy/embed_search"],
9695
10045
  category: "ai",
9696
10046
  label: "Embed & Search",
9697
10047
  description: "Embed a query and search a vector store.",
@@ -9704,8 +10054,8 @@ var KINDS = [
9704
10054
  },
9705
10055
  // ───────────── IO ─────────────
9706
10056
  {
9707
- name: "@fancy/api_request",
9708
- aliases: ["api_request"],
10057
+ name: "@particle-academy/api_request",
10058
+ aliases: ["api_request", "@fancy/api_request"],
9709
10059
  category: "io",
9710
10060
  label: "API Request",
9711
10061
  description: "HTTP request to any URL.",
@@ -9719,8 +10069,8 @@ var KINDS = [
9719
10069
  ]
9720
10070
  },
9721
10071
  {
9722
- name: "@fancy/webhook_out",
9723
- aliases: ["webhook_out"],
10072
+ name: "@particle-academy/webhook_out",
10073
+ aliases: ["webhook_out", "@fancy/webhook_out"],
9724
10074
  category: "io",
9725
10075
  label: "Send Webhook",
9726
10076
  description: "POST a payload to a configured URL.",
@@ -9733,8 +10083,8 @@ var KINDS = [
9733
10083
  },
9734
10084
  // ───────────── Human ─────────────
9735
10085
  {
9736
- name: "@fancy/human_approval",
9737
- aliases: ["human_approval"],
10086
+ name: "@particle-academy/human_approval",
10087
+ aliases: ["human_approval", "@fancy/human_approval"],
9738
10088
  category: "human",
9739
10089
  label: "Human Approval",
9740
10090
  description: "Pause until a human approves or denies.",
@@ -9748,8 +10098,8 @@ var KINDS = [
9748
10098
  ]
9749
10099
  },
9750
10100
  {
9751
- name: "@fancy/notify",
9752
- aliases: ["notify"],
10101
+ name: "@particle-academy/notify",
10102
+ aliases: ["notify", "@fancy/notify"],
9753
10103
  category: "human",
9754
10104
  label: "Notify",
9755
10105
  description: "Send a message via Slack / email / SMS / etc.",
@@ -9773,8 +10123,8 @@ var KINDS = [
9773
10123
  },
9774
10124
  // ───────────── Output ─────────────
9775
10125
  {
9776
- name: "@fancy/output",
9777
- aliases: ["output"],
10126
+ name: "@particle-academy/output",
10127
+ aliases: ["output", "@fancy/output"],
9778
10128
  category: "output",
9779
10129
  label: "Output",
9780
10130
  description: "Terminal node \u2014 captures the workflow's result.",
@@ -9783,8 +10133,8 @@ var KINDS = [
9783
10133
  outputs: []
9784
10134
  },
9785
10135
  {
9786
- name: "@fancy/log",
9787
- aliases: ["log"],
10136
+ name: "@particle-academy/log",
10137
+ aliases: ["log", "@fancy/log"],
9788
10138
  category: "output",
9789
10139
  label: "Log",
9790
10140
  description: "Send to the run feed.",
@@ -9818,25 +10168,37 @@ function buildNodeTypes() {
9818
10168
  }
9819
10169
 
9820
10170
  exports.BUILTIN_KINDS = BUILTIN_KINDS;
10171
+ exports.DEFAULT_MAX_DEPTH = DEFAULT_MAX_DEPTH;
9821
10172
  exports.RegistryNode = RegistryNode;
9822
10173
  exports.RichInputPreview = RichInputPreview;
9823
10174
  exports.buildNodeTypes = buildNodeTypes;
10175
+ exports.capabilityStatus = capabilityStatus;
9824
10176
  exports.categoryAccent = categoryAccent;
10177
+ exports.declaredRoutes = declaredRoutes;
9825
10178
  exports.defaultConfigFor = defaultConfigFor;
10179
+ exports.getLlmClient = getLlmClient;
9826
10180
  exports.getNodeKind = getNodeKind;
9827
10181
  exports.getRichInputAdapter = getRichInputAdapter;
10182
+ exports.getWorkflowResolver = getWorkflowResolver;
9828
10183
  exports.isRichInputEnabled = isRichInputEnabled;
9829
10184
  exports.kindIds = kindIds;
9830
10185
  exports.listNodeKinds = listNodeKinds;
10186
+ exports.llmBranchExecutor = llmBranchExecutor;
9831
10187
  exports.nodeConfig = nodeConfig;
9832
10188
  exports.onNodeKindsChanged = onNodeKindsChanged;
9833
10189
  exports.onRichInputAdapterChanged = onRichInputAdapterChanged;
9834
10190
  exports.registerBuiltinKinds = registerBuiltinKinds;
10191
+ exports.registerLlmClient = registerLlmClient;
9835
10192
  exports.registerNodeKind = registerNodeKind;
9836
10193
  exports.registerRichInputAdapter = registerRichInputAdapter;
10194
+ exports.registerWorkflowResolver = registerWorkflowResolver;
10195
+ exports.resolveFallbackPort = resolveFallbackPort;
9837
10196
  exports.resolveKindId = resolveKindId;
9838
10197
  exports.resolveNodePorts = resolveNodePorts;
9839
10198
  exports.resolvePortSpec = resolvePortSpec;
10199
+ exports.subflowExecutor = subflowExecutor;
10200
+ exports.subflowMode = subflowMode;
10201
+ exports.subflowPorts = subflowPorts;
9840
10202
  exports.validateConfig = validateConfig;
9841
10203
  //# sourceMappingURL=registry.cjs.map
9842
10204
  //# sourceMappingURL=registry.cjs.map