@particle-academy/fancy-flow 0.10.1 → 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 (49) hide show
  1. package/dist/{chunk-2DMBJN2O.js → chunk-D6W5FMCT.js} +6 -5
  2. package/dist/chunk-D6W5FMCT.js.map +1 -0
  3. package/dist/{chunk-NCPQDVUE.js → chunk-IK5DS5JP.js} +262 -29
  4. package/dist/chunk-IK5DS5JP.js.map +1 -0
  5. package/dist/{chunk-HNBO4HP3.js → chunk-L4AX73Q6.js} +12 -5
  6. package/dist/chunk-L4AX73Q6.js.map +1 -0
  7. package/dist/{chunk-VEI743ZX.js → chunk-MFMRTRPO.js} +3 -3
  8. package/dist/{chunk-VEI743ZX.js.map → chunk-MFMRTRPO.js.map} +1 -1
  9. package/dist/{chunk-CPSOC27D.js → chunk-U5F22BHV.js} +18 -4
  10. package/dist/chunk-U5F22BHV.js.map +1 -0
  11. package/dist/engine.cjs +20 -3
  12. package/dist/engine.cjs.map +1 -1
  13. package/dist/engine.d.cts +4 -2
  14. package/dist/engine.d.ts +4 -2
  15. package/dist/engine.js +2 -2
  16. package/dist/index.cjs +417 -189
  17. package/dist/index.cjs.map +1 -1
  18. package/dist/index.d.cts +4 -4
  19. package/dist/index.d.ts +4 -4
  20. package/dist/index.js +9 -9
  21. package/dist/registry/index.d.cts +149 -5
  22. package/dist/registry/index.d.ts +149 -5
  23. package/dist/registry.cjs +475 -71
  24. package/dist/registry.cjs.map +1 -1
  25. package/dist/registry.js +3 -2
  26. package/dist/runtime/index.d.cts +1 -1
  27. package/dist/runtime/index.d.ts +1 -1
  28. package/dist/runtime.cjs +20 -3
  29. package/dist/runtime.cjs.map +1 -1
  30. package/dist/runtime.js +3 -3
  31. package/dist/schema/index.d.cts +1 -1
  32. package/dist/schema/index.d.ts +1 -1
  33. package/dist/schema.cjs +11 -3
  34. package/dist/schema.cjs.map +1 -1
  35. package/dist/schema.js +2 -2
  36. package/dist/{types-BS3Gwnkq.d.cts → types-Dg2REAz5.d.cts} +6 -0
  37. package/dist/{types-BS3Gwnkq.d.ts → types-Dg2REAz5.d.ts} +6 -0
  38. package/dist/{types-BocBFh6l.d.ts → types-DnQ4TEHN.d.cts} +16 -2
  39. package/dist/{types-DKqaUjF_.d.cts → types-DtXOwXjA.d.ts} +16 -2
  40. package/dist/ux.cjs +5 -0
  41. package/dist/ux.cjs.map +1 -1
  42. package/dist/ux.d.cts +2 -2
  43. package/dist/ux.d.ts +2 -2
  44. package/dist/ux.js +1 -1
  45. package/package.json +1 -1
  46. package/dist/chunk-2DMBJN2O.js.map +0 -1
  47. package/dist/chunk-CPSOC27D.js.map +0 -1
  48. package/dist/chunk-HNBO4HP3.js.map +0 -1
  49. package/dist/chunk-NCPQDVUE.js.map +0 -1
package/dist/registry.cjs CHANGED
@@ -30,82 +30,82 @@ 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
- var listeners2 = /* @__PURE__ */ new Set();
71
+ var aliases = /* @__PURE__ */ new Map();
72
+ var listeners = /* @__PURE__ */ new Set();
86
73
  function registerNodeKind(definition) {
87
74
  kinds.set(definition.name, definition);
75
+ for (const alias of definition.aliases ?? []) aliases.set(alias, definition.name);
88
76
  notify();
89
77
  return () => {
90
78
  if (kinds.get(definition.name) === definition) {
91
79
  kinds.delete(definition.name);
80
+ for (const alias of definition.aliases ?? []) {
81
+ if (aliases.get(alias) === definition.name) aliases.delete(alias);
82
+ }
92
83
  notify();
93
84
  }
94
85
  };
95
86
  }
87
+ function resolveKindId(id2) {
88
+ if (kinds.has(id2)) return id2;
89
+ const canonical = aliases.get(id2);
90
+ return canonical && kinds.has(canonical) ? canonical : null;
91
+ }
96
92
  function getNodeKind(name) {
97
- return kinds.get(name) ?? null;
93
+ const canonical = resolveKindId(name);
94
+ return canonical ? kinds.get(canonical) ?? null : null;
95
+ }
96
+ function kindIds(kind) {
97
+ return [kind.name, ...kind.aliases ?? []];
98
98
  }
99
99
  function listNodeKinds(category) {
100
100
  const all = Array.from(kinds.values());
101
101
  return category ? all.filter((k) => k.category === category) : all;
102
102
  }
103
103
  function onNodeKindsChanged(listener) {
104
- listeners2.add(listener);
105
- return () => listeners2.delete(listener);
104
+ listeners.add(listener);
105
+ return () => listeners.delete(listener);
106
106
  }
107
107
  function notify() {
108
- for (const l of listeners2) l();
108
+ for (const l of listeners) l();
109
109
  }
110
110
  function defaultConfigFor(kind) {
111
111
  const fromKind = kind.defaultConfig ? { ...kind.defaultConfig } : {};
@@ -219,6 +219,315 @@ function categoryAccent(category) {
219
219
  }
220
220
  }
221
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
+
222
531
  // node_modules/classcat/index.js
223
532
  function cc(names) {
224
533
  if (typeof names === "string" || typeof names === "number") return "" + names;
@@ -9276,7 +9585,8 @@ var HTTP_METHODS = [
9276
9585
  var KINDS = [
9277
9586
  // ───────────── Triggers ─────────────
9278
9587
  {
9279
- name: "manual_trigger",
9588
+ name: "@particle-academy/manual_trigger",
9589
+ aliases: ["manual_trigger", "@fancy/manual_trigger"],
9280
9590
  category: "trigger",
9281
9591
  label: "Manual",
9282
9592
  description: "Entry point fired when the user clicks Run.",
@@ -9285,7 +9595,8 @@ var KINDS = [
9285
9595
  outputs: [{ id: "out" }]
9286
9596
  },
9287
9597
  {
9288
- name: "webhook_trigger",
9598
+ name: "@particle-academy/webhook_trigger",
9599
+ aliases: ["webhook_trigger", "@fancy/webhook_trigger"],
9289
9600
  category: "trigger",
9290
9601
  label: "Webhook",
9291
9602
  description: "Triggered by an inbound HTTP request to a host-provided URL.",
@@ -9302,7 +9613,8 @@ var KINDS = [
9302
9613
  ]
9303
9614
  },
9304
9615
  {
9305
- name: "schedule_trigger",
9616
+ name: "@particle-academy/schedule_trigger",
9617
+ aliases: ["schedule_trigger", "@fancy/schedule_trigger"],
9306
9618
  category: "trigger",
9307
9619
  label: "Schedule",
9308
9620
  description: "Fires on a cron schedule (host-implemented).",
@@ -9322,7 +9634,8 @@ var KINDS = [
9322
9634
  ]
9323
9635
  },
9324
9636
  {
9325
- name: "user_input",
9637
+ name: "@particle-academy/user_input",
9638
+ aliases: ["user_input", "@fancy/user_input"],
9326
9639
  category: "human",
9327
9640
  label: "User Input",
9328
9641
  description: "Pause the flow until the user submits the configured form.",
@@ -9362,7 +9675,8 @@ var KINDS = [
9362
9675
  ]
9363
9676
  },
9364
9677
  {
9365
- name: "rich_user_input",
9678
+ name: "@particle-academy/rich_user_input",
9679
+ aliases: ["rich_user_input", "@fancy/rich_user_input"],
9366
9680
  category: "human",
9367
9681
  label: "Rich User Input",
9368
9682
  description: "Pause the flow on a fully authored page \u2014 content, required reading, multi-section forms.",
@@ -9385,9 +9699,61 @@ var KINDS = [
9385
9699
  // what the person hitting this step will actually see.
9386
9700
  renderBody: (ctx) => ReactExports.createElement(RichInputPreview, { config: ctx.config ?? {} })
9387
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
+ },
9388
9753
  // ───────────── Logic ─────────────
9389
9754
  {
9390
- name: "branch",
9755
+ name: "@particle-academy/branch",
9756
+ aliases: ["branch", "@fancy/branch"],
9391
9757
  category: "logic",
9392
9758
  label: "Branch",
9393
9759
  description: "Multi-way branch on a condition or value.",
@@ -9399,7 +9765,8 @@ var KINDS = [
9399
9765
  ]
9400
9766
  },
9401
9767
  {
9402
- name: "switch_case",
9768
+ name: "@particle-academy/switch_case",
9769
+ aliases: ["switch_case", "@fancy/switch_case"],
9403
9770
  category: "logic",
9404
9771
  label: "Switch",
9405
9772
  description: "Route to one of N labelled outputs based on a key.",
@@ -9426,7 +9793,8 @@ var KINDS = [
9426
9793
  ]
9427
9794
  },
9428
9795
  {
9429
- name: "for_each",
9796
+ name: "@particle-academy/for_each",
9797
+ aliases: ["for_each", "@fancy/for_each"],
9430
9798
  category: "logic",
9431
9799
  label: "For Each",
9432
9800
  description: "Iterate over a list, emitting each item on `item`.",
@@ -9439,7 +9807,8 @@ var KINDS = [
9439
9807
  ]
9440
9808
  },
9441
9809
  {
9442
- name: "merge",
9810
+ name: "@particle-academy/merge",
9811
+ aliases: ["merge", "@fancy/merge"],
9443
9812
  category: "logic",
9444
9813
  label: "Merge",
9445
9814
  description: "Combine multiple inputs into one object or array.",
@@ -9457,7 +9826,8 @@ var KINDS = [
9457
9826
  ]
9458
9827
  },
9459
9828
  {
9460
- name: "wait",
9829
+ name: "@particle-academy/wait",
9830
+ aliases: ["wait", "@fancy/wait"],
9461
9831
  category: "logic",
9462
9832
  label: "Wait",
9463
9833
  description: "Sleep or wait for an external event.",
@@ -9474,7 +9844,8 @@ var KINDS = [
9474
9844
  ]
9475
9845
  },
9476
9846
  {
9477
- name: "transform",
9847
+ name: "@particle-academy/transform",
9848
+ aliases: ["transform", "@fancy/transform"],
9478
9849
  category: "logic",
9479
9850
  label: "Transform",
9480
9851
  description: "Reshape data with an expression.",
@@ -9491,7 +9862,8 @@ var KINDS = [
9491
9862
  },
9492
9863
  // ───────────── Data ─────────────
9493
9864
  {
9494
- name: "memory_store",
9865
+ name: "@particle-academy/memory_store",
9866
+ aliases: ["memory_store", "@fancy/memory_store"],
9495
9867
  category: "data",
9496
9868
  label: "Memory Store",
9497
9869
  description: "Read or write per-conversation memory.",
@@ -9511,7 +9883,8 @@ var KINDS = [
9511
9883
  ]
9512
9884
  },
9513
9885
  {
9514
- name: "data_store",
9886
+ name: "@particle-academy/data_store",
9887
+ aliases: ["data_store", "@fancy/data_store"],
9515
9888
  category: "data",
9516
9889
  label: "Data Store",
9517
9890
  description: "Key-value or table read/write against a host store.",
@@ -9539,7 +9912,8 @@ var KINDS = [
9539
9912
  ]
9540
9913
  },
9541
9914
  {
9542
- name: "variable",
9915
+ name: "@particle-academy/variable",
9916
+ aliases: ["variable", "@fancy/variable"],
9543
9917
  category: "data",
9544
9918
  label: "Variable",
9545
9919
  description: "Workflow-scoped value used by other nodes.",
@@ -9551,7 +9925,8 @@ var KINDS = [
9551
9925
  },
9552
9926
  // ───────────── AI ─────────────
9553
9927
  {
9554
- name: "llm_call",
9928
+ name: "@particle-academy/llm_call",
9929
+ aliases: ["llm_call", "@fancy/llm_call"],
9555
9930
  category: "ai",
9556
9931
  label: "LLM Call",
9557
9932
  description: "Send a prompt + context to a model and receive a response.",
@@ -9578,7 +9953,8 @@ var KINDS = [
9578
9953
  ]
9579
9954
  },
9580
9955
  {
9581
- name: "llm_branch",
9956
+ name: "@particle-academy/llm_branch",
9957
+ aliases: ["llm_branch", "@fancy/llm_branch"],
9582
9958
  category: "ai",
9583
9959
  label: "LLM Router",
9584
9960
  description: "Let a model choose which route the flow takes.",
@@ -9587,6 +9963,10 @@ var KINDS = [
9587
9963
  // Each declared route is a port. The executor returns `{ __port: id }`
9588
9964
  // (or `Port.only(id)` on the PHP runtime) to pick one.
9589
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,
9590
9970
  configSchema: [
9591
9971
  {
9592
9972
  type: "textarea",
@@ -9648,7 +10028,8 @@ var KINDS = [
9648
10028
  ]
9649
10029
  },
9650
10030
  {
9651
- name: "tool_use",
10031
+ name: "@particle-academy/tool_use",
10032
+ aliases: ["tool_use", "@fancy/tool_use"],
9652
10033
  category: "ai",
9653
10034
  label: "Tool Use",
9654
10035
  description: "Hand control to a host-registered tool by name.",
@@ -9659,7 +10040,8 @@ var KINDS = [
9659
10040
  ]
9660
10041
  },
9661
10042
  {
9662
- name: "embed_search",
10043
+ name: "@particle-academy/embed_search",
10044
+ aliases: ["embed_search", "@fancy/embed_search"],
9663
10045
  category: "ai",
9664
10046
  label: "Embed & Search",
9665
10047
  description: "Embed a query and search a vector store.",
@@ -9672,7 +10054,8 @@ var KINDS = [
9672
10054
  },
9673
10055
  // ───────────── IO ─────────────
9674
10056
  {
9675
- name: "api_request",
10057
+ name: "@particle-academy/api_request",
10058
+ aliases: ["api_request", "@fancy/api_request"],
9676
10059
  category: "io",
9677
10060
  label: "API Request",
9678
10061
  description: "HTTP request to any URL.",
@@ -9686,7 +10069,8 @@ var KINDS = [
9686
10069
  ]
9687
10070
  },
9688
10071
  {
9689
- name: "webhook_out",
10072
+ name: "@particle-academy/webhook_out",
10073
+ aliases: ["webhook_out", "@fancy/webhook_out"],
9690
10074
  category: "io",
9691
10075
  label: "Send Webhook",
9692
10076
  description: "POST a payload to a configured URL.",
@@ -9699,7 +10083,8 @@ var KINDS = [
9699
10083
  },
9700
10084
  // ───────────── Human ─────────────
9701
10085
  {
9702
- name: "human_approval",
10086
+ name: "@particle-academy/human_approval",
10087
+ aliases: ["human_approval", "@fancy/human_approval"],
9703
10088
  category: "human",
9704
10089
  label: "Human Approval",
9705
10090
  description: "Pause until a human approves or denies.",
@@ -9713,7 +10098,8 @@ var KINDS = [
9713
10098
  ]
9714
10099
  },
9715
10100
  {
9716
- name: "notify",
10101
+ name: "@particle-academy/notify",
10102
+ aliases: ["notify", "@fancy/notify"],
9717
10103
  category: "human",
9718
10104
  label: "Notify",
9719
10105
  description: "Send a message via Slack / email / SMS / etc.",
@@ -9737,7 +10123,8 @@ var KINDS = [
9737
10123
  },
9738
10124
  // ───────────── Output ─────────────
9739
10125
  {
9740
- name: "output",
10126
+ name: "@particle-academy/output",
10127
+ aliases: ["output", "@fancy/output"],
9741
10128
  category: "output",
9742
10129
  label: "Output",
9743
10130
  description: "Terminal node \u2014 captures the workflow's result.",
@@ -9746,7 +10133,8 @@ var KINDS = [
9746
10133
  outputs: []
9747
10134
  },
9748
10135
  {
9749
- name: "log",
10136
+ name: "@particle-academy/log",
10137
+ aliases: ["log", "@fancy/log"],
9750
10138
  category: "output",
9751
10139
  label: "Log",
9752
10140
  description: "Send to the run feed.",
@@ -9773,28 +10161,44 @@ var BUILTIN_KINDS = KINDS;
9773
10161
  // src/registry/index.ts
9774
10162
  function buildNodeTypes() {
9775
10163
  const map = {};
9776
- for (const k of listNodeKinds()) map[k.name] = RegistryNode;
10164
+ for (const k of listNodeKinds()) {
10165
+ for (const id2 of kindIds(k)) map[id2] = RegistryNode;
10166
+ }
9777
10167
  return map;
9778
10168
  }
9779
10169
 
9780
10170
  exports.BUILTIN_KINDS = BUILTIN_KINDS;
10171
+ exports.DEFAULT_MAX_DEPTH = DEFAULT_MAX_DEPTH;
9781
10172
  exports.RegistryNode = RegistryNode;
9782
10173
  exports.RichInputPreview = RichInputPreview;
9783
10174
  exports.buildNodeTypes = buildNodeTypes;
10175
+ exports.capabilityStatus = capabilityStatus;
9784
10176
  exports.categoryAccent = categoryAccent;
10177
+ exports.declaredRoutes = declaredRoutes;
9785
10178
  exports.defaultConfigFor = defaultConfigFor;
10179
+ exports.getLlmClient = getLlmClient;
9786
10180
  exports.getNodeKind = getNodeKind;
9787
10181
  exports.getRichInputAdapter = getRichInputAdapter;
10182
+ exports.getWorkflowResolver = getWorkflowResolver;
9788
10183
  exports.isRichInputEnabled = isRichInputEnabled;
10184
+ exports.kindIds = kindIds;
9789
10185
  exports.listNodeKinds = listNodeKinds;
10186
+ exports.llmBranchExecutor = llmBranchExecutor;
9790
10187
  exports.nodeConfig = nodeConfig;
9791
10188
  exports.onNodeKindsChanged = onNodeKindsChanged;
9792
10189
  exports.onRichInputAdapterChanged = onRichInputAdapterChanged;
9793
10190
  exports.registerBuiltinKinds = registerBuiltinKinds;
10191
+ exports.registerLlmClient = registerLlmClient;
9794
10192
  exports.registerNodeKind = registerNodeKind;
9795
10193
  exports.registerRichInputAdapter = registerRichInputAdapter;
10194
+ exports.registerWorkflowResolver = registerWorkflowResolver;
10195
+ exports.resolveFallbackPort = resolveFallbackPort;
10196
+ exports.resolveKindId = resolveKindId;
9796
10197
  exports.resolveNodePorts = resolveNodePorts;
9797
10198
  exports.resolvePortSpec = resolvePortSpec;
10199
+ exports.subflowExecutor = subflowExecutor;
10200
+ exports.subflowMode = subflowMode;
10201
+ exports.subflowPorts = subflowPorts;
9798
10202
  exports.validateConfig = validateConfig;
9799
10203
  //# sourceMappingURL=registry.cjs.map
9800
10204
  //# sourceMappingURL=registry.cjs.map