@particle-academy/fancy-flow 0.16.0 → 0.18.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.
@@ -1,12 +1,61 @@
1
1
  import { a as NodeKindDefinition, P as PortSpec } from '../types-D_jOR3Z2.cjs';
2
2
  export { C as ConfigField, b as CredentialConfigField, D as DocumentConfigField, E as ExpressionConfigField, J as JsonConfigField, K as KeyValueConfigField, N as NodeCategory, c as NumberConfigField, R as RenderBodyContext, d as RepeaterConfigField, e as RepeaterRowField, S as SelectConfigField, f as SwitchConfigField, T as TextConfigField, g as TextareaConfigField } from '../types-D_jOR3Z2.cjs';
3
- import { b as FlowNode, P as PortDescriptor, N as NodeExecutor } from '../types-sOmpCitB.cjs';
3
+ import { P as PortDescriptor, b as FlowNode, N as NodeExecutor } from '../types-sOmpCitB.cjs';
4
+ import { Connection, Edge, NodeProps, NodeTypes } from '@xyflow/react';
4
5
  import { b as LlmRoute } from '../capabilities-ChE3Xi8R.cjs';
5
6
  export { C as CapabilityId, a as LlmClient, c as LlmRouteChoice, L as LlmRouteRequest, W as WorkflowResolution, d as WorkflowResolutionFailure, e as WorkflowResolver, f as capabilityStatus, g as getLlmClient, h as getWorkflowResolver, i as isResolutionFailure, r as registerLlmClient, j as registerWorkflowResolver } from '../capabilities-ChE3Xi8R.cjs';
6
7
  export { L as LEGACY_PAUSE_PREFIXES, a as PAUSE_PREFIX, P as PauseAwaiting, b as PauseSignal, d as decodePause, e as encodePause, i as isPause, p as pauseForHuman } from '../pause-9iT4tCEV.cjs';
7
8
  import * as react from 'react';
8
9
  import { ComponentType, ReactNode } from 'react';
9
- import { NodeProps, NodeTypes } from '@xyflow/react';
10
+
11
+ /**
12
+ * Connection validation — the single rule that decides whether an edge between
13
+ * two ports is allowed. Lives here (React-free, registry-adjacent) so BOTH the
14
+ * canvas (`<FlowCanvas isValidConnection>`) and the agent bridge's
15
+ * `flow_connect` tool call the same function: a connection the canvas refuses
16
+ * is a connection an agent cannot sneak past, and vice versa — no drift.
17
+ *
18
+ * `PortDescriptor.type` has always documented itself as being "for hosts that
19
+ * want to validate connections", but nothing consumed it. This wires it up.
20
+ */
21
+ /**
22
+ * Wildcard port type — matches any other type. A port that declares no `type`
23
+ * is treated as a wildcard too, so existing flows (whose ports are untyped)
24
+ * validate exactly as before.
25
+ */
26
+ declare const ANY_PORT_TYPE = "any";
27
+ /** Decides whether a source-output port may feed a target-input port. */
28
+ type PortCompatibility = (source: PortDescriptor | undefined, target: PortDescriptor | undefined) => boolean;
29
+ /**
30
+ * Default compatibility rule: allow the connection UNLESS both ports declare a
31
+ * concrete, differing type. An absent type or the `any` wildcard matches
32
+ * anything — so typed ports get enforced while untyped ports stay permissive,
33
+ * making this a safe default that never breaks an existing untyped graph.
34
+ */
35
+ declare const defaultPortCompatibility: PortCompatibility;
36
+ type ConnectionValidatorOptions = {
37
+ /** Override the type-compatibility rule. Defaults to {@link defaultPortCompatibility}. */
38
+ compatible?: PortCompatibility;
39
+ /**
40
+ * Allow a node's own output to feed its own input (a self-loop). Default
41
+ * false — self-connections are almost always an accident, and the rare flow
42
+ * that wants one can opt in.
43
+ */
44
+ allowSelfConnection?: boolean;
45
+ };
46
+ /**
47
+ * Build an `isValidConnection` predicate that enforces port-type compatibility
48
+ * against a live node list. Pass it straight to `<FlowCanvas isValidConnection>`
49
+ * or reuse it to gate an agent's `flow_connect`.
50
+ *
51
+ * `getNodes` is called on every check so the validator always sees the current
52
+ * graph (wire it to a ref/state getter, not a snapshot).
53
+ *
54
+ * A connection is rejected when: an endpoint is missing; it is a self-loop and
55
+ * `allowSelfConnection` is off; either node is unknown; or the resolved ports
56
+ * are type-incompatible. Nodes/ports with no declared type validate as before.
57
+ */
58
+ declare function createConnectionValidator(getNodes: () => FlowNode[], options?: ConnectionValidatorOptions): (connection: Connection | Edge) => boolean;
10
59
 
11
60
  /**
12
61
  * Port resolution — the single place a node's ports are derived, shared by
@@ -231,4 +280,4 @@ declare const BUILTIN_KINDS: NodeKindDefinition[];
231
280
  */
232
281
  declare function buildNodeTypes(): NodeTypes;
233
282
 
234
- export { BUILTIN_KINDS, DEFAULT_MAX_DEPTH, LlmRoute, NodeKindDefinition, PortSpec, RegistryNode, type RichInputAdapter, RichInputPreview, type SubflowMode, buildNodeTypes, categoryAccent, declaredRoutes, defaultConfigFor, getNodeKind, getRichInputAdapter, isRichInputEnabled, kindIds, listNodeKinds, llmRouterExecutor as llmBranchExecutor, llmRouterExecutor, nodeConfig, onNodeKindsChanged, onRichInputAdapterChanged, registerBuiltinKinds, registerNodeKind, registerRichInputAdapter, resolveFallbackPort, resolveKindId, resolveNodePorts, resolvePortSpec, subflowExecutor, subflowMode, subflowPorts, validateConfig };
283
+ export { ANY_PORT_TYPE, BUILTIN_KINDS, type ConnectionValidatorOptions, DEFAULT_MAX_DEPTH, LlmRoute, NodeKindDefinition, type PortCompatibility, PortSpec, RegistryNode, type RichInputAdapter, RichInputPreview, type SubflowMode, buildNodeTypes, categoryAccent, createConnectionValidator, declaredRoutes, defaultConfigFor, defaultPortCompatibility, getNodeKind, getRichInputAdapter, isRichInputEnabled, kindIds, listNodeKinds, llmRouterExecutor as llmBranchExecutor, llmRouterExecutor, nodeConfig, onNodeKindsChanged, onRichInputAdapterChanged, registerBuiltinKinds, registerNodeKind, registerRichInputAdapter, resolveFallbackPort, resolveKindId, resolveNodePorts, resolvePortSpec, subflowExecutor, subflowMode, subflowPorts, validateConfig };
@@ -1,12 +1,61 @@
1
1
  import { a as NodeKindDefinition, P as PortSpec } from '../types-NerkPtHS.js';
2
2
  export { C as ConfigField, b as CredentialConfigField, D as DocumentConfigField, E as ExpressionConfigField, J as JsonConfigField, K as KeyValueConfigField, N as NodeCategory, c as NumberConfigField, R as RenderBodyContext, d as RepeaterConfigField, e as RepeaterRowField, S as SelectConfigField, f as SwitchConfigField, T as TextConfigField, g as TextareaConfigField } from '../types-NerkPtHS.js';
3
- import { b as FlowNode, P as PortDescriptor, N as NodeExecutor } from '../types-sOmpCitB.js';
3
+ import { P as PortDescriptor, b as FlowNode, N as NodeExecutor } from '../types-sOmpCitB.js';
4
+ import { Connection, Edge, NodeProps, NodeTypes } from '@xyflow/react';
4
5
  import { b as LlmRoute } from '../capabilities-DUhAc-EJ.js';
5
6
  export { C as CapabilityId, a as LlmClient, c as LlmRouteChoice, L as LlmRouteRequest, W as WorkflowResolution, d as WorkflowResolutionFailure, e as WorkflowResolver, f as capabilityStatus, g as getLlmClient, h as getWorkflowResolver, i as isResolutionFailure, r as registerLlmClient, j as registerWorkflowResolver } from '../capabilities-DUhAc-EJ.js';
6
7
  export { L as LEGACY_PAUSE_PREFIXES, a as PAUSE_PREFIX, P as PauseAwaiting, b as PauseSignal, d as decodePause, e as encodePause, i as isPause, p as pauseForHuman } from '../pause-9iT4tCEV.js';
7
8
  import * as react from 'react';
8
9
  import { ComponentType, ReactNode } from 'react';
9
- import { NodeProps, NodeTypes } from '@xyflow/react';
10
+
11
+ /**
12
+ * Connection validation — the single rule that decides whether an edge between
13
+ * two ports is allowed. Lives here (React-free, registry-adjacent) so BOTH the
14
+ * canvas (`<FlowCanvas isValidConnection>`) and the agent bridge's
15
+ * `flow_connect` tool call the same function: a connection the canvas refuses
16
+ * is a connection an agent cannot sneak past, and vice versa — no drift.
17
+ *
18
+ * `PortDescriptor.type` has always documented itself as being "for hosts that
19
+ * want to validate connections", but nothing consumed it. This wires it up.
20
+ */
21
+ /**
22
+ * Wildcard port type — matches any other type. A port that declares no `type`
23
+ * is treated as a wildcard too, so existing flows (whose ports are untyped)
24
+ * validate exactly as before.
25
+ */
26
+ declare const ANY_PORT_TYPE = "any";
27
+ /** Decides whether a source-output port may feed a target-input port. */
28
+ type PortCompatibility = (source: PortDescriptor | undefined, target: PortDescriptor | undefined) => boolean;
29
+ /**
30
+ * Default compatibility rule: allow the connection UNLESS both ports declare a
31
+ * concrete, differing type. An absent type or the `any` wildcard matches
32
+ * anything — so typed ports get enforced while untyped ports stay permissive,
33
+ * making this a safe default that never breaks an existing untyped graph.
34
+ */
35
+ declare const defaultPortCompatibility: PortCompatibility;
36
+ type ConnectionValidatorOptions = {
37
+ /** Override the type-compatibility rule. Defaults to {@link defaultPortCompatibility}. */
38
+ compatible?: PortCompatibility;
39
+ /**
40
+ * Allow a node's own output to feed its own input (a self-loop). Default
41
+ * false — self-connections are almost always an accident, and the rare flow
42
+ * that wants one can opt in.
43
+ */
44
+ allowSelfConnection?: boolean;
45
+ };
46
+ /**
47
+ * Build an `isValidConnection` predicate that enforces port-type compatibility
48
+ * against a live node list. Pass it straight to `<FlowCanvas isValidConnection>`
49
+ * or reuse it to gate an agent's `flow_connect`.
50
+ *
51
+ * `getNodes` is called on every check so the validator always sees the current
52
+ * graph (wire it to a ref/state getter, not a snapshot).
53
+ *
54
+ * A connection is rejected when: an endpoint is missing; it is a self-loop and
55
+ * `allowSelfConnection` is off; either node is unknown; or the resolved ports
56
+ * are type-incompatible. Nodes/ports with no declared type validate as before.
57
+ */
58
+ declare function createConnectionValidator(getNodes: () => FlowNode[], options?: ConnectionValidatorOptions): (connection: Connection | Edge) => boolean;
10
59
 
11
60
  /**
12
61
  * Port resolution — the single place a node's ports are derived, shared by
@@ -231,4 +280,4 @@ declare const BUILTIN_KINDS: NodeKindDefinition[];
231
280
  */
232
281
  declare function buildNodeTypes(): NodeTypes;
233
282
 
234
- export { BUILTIN_KINDS, DEFAULT_MAX_DEPTH, LlmRoute, NodeKindDefinition, PortSpec, RegistryNode, type RichInputAdapter, RichInputPreview, type SubflowMode, buildNodeTypes, categoryAccent, declaredRoutes, defaultConfigFor, getNodeKind, getRichInputAdapter, isRichInputEnabled, kindIds, listNodeKinds, llmRouterExecutor as llmBranchExecutor, llmRouterExecutor, nodeConfig, onNodeKindsChanged, onRichInputAdapterChanged, registerBuiltinKinds, registerNodeKind, registerRichInputAdapter, resolveFallbackPort, resolveKindId, resolveNodePorts, resolvePortSpec, subflowExecutor, subflowMode, subflowPorts, validateConfig };
283
+ export { ANY_PORT_TYPE, BUILTIN_KINDS, type ConnectionValidatorOptions, DEFAULT_MAX_DEPTH, LlmRoute, NodeKindDefinition, type PortCompatibility, PortSpec, RegistryNode, type RichInputAdapter, RichInputPreview, type SubflowMode, buildNodeTypes, categoryAccent, createConnectionValidator, declaredRoutes, defaultConfigFor, defaultPortCompatibility, getNodeKind, getRichInputAdapter, isRichInputEnabled, kindIds, listNodeKinds, llmRouterExecutor as llmBranchExecutor, llmRouterExecutor, nodeConfig, onNodeKindsChanged, onRichInputAdapterChanged, registerBuiltinKinds, registerNodeKind, registerRichInputAdapter, resolveFallbackPort, resolveKindId, resolveNodePorts, resolvePortSpec, subflowExecutor, subflowMode, subflowPorts, validateConfig };
package/dist/registry.cjs CHANGED
@@ -31,80 +31,6 @@ function resolveNodePorts(node, kind) {
31
31
  };
32
32
  }
33
33
 
34
- // src/registry/capabilities.ts
35
- var llmClient = null;
36
- function registerLlmClient(client) {
37
- llmClient = client;
38
- return () => {
39
- if (llmClient === client) llmClient = null;
40
- };
41
- }
42
- function getLlmClient() {
43
- return llmClient;
44
- }
45
- function isResolutionFailure(value) {
46
- return typeof value === "object" && value !== null && "reason" in value && value.reason !== void 0;
47
- }
48
- var workflowResolver = null;
49
- function registerWorkflowResolver(resolver) {
50
- workflowResolver = resolver;
51
- return () => {
52
- if (workflowResolver === resolver) workflowResolver = null;
53
- };
54
- }
55
- function getWorkflowResolver() {
56
- return workflowResolver;
57
- }
58
- function capabilityStatus() {
59
- let documentReady = false;
60
- try {
61
- documentReady = Boolean(globalThis.__fancyFlowDocumentAdapter);
62
- } catch {
63
- documentReady = false;
64
- }
65
- return {
66
- llm: llmClient !== null,
67
- workflow_resolver: workflowResolver !== null,
68
- document: documentReady
69
- };
70
- }
71
-
72
- // src/registry/pause.ts
73
- var PAUSE_PREFIX = "fancy-flow:pause:";
74
- var LEGACY_PAUSE_PREFIXES = [
75
- ["awaiting-approval:", "approval"],
76
- ["awaiting-input:", "input"]
77
- ];
78
- function encodePause(signal) {
79
- const { nodeId, awaiting, detail } = signal;
80
- return PAUSE_PREFIX + JSON.stringify(detail === void 0 ? { nodeId, awaiting } : { nodeId, awaiting, detail });
81
- }
82
- function decodePause(reason) {
83
- if (typeof reason !== "string") return null;
84
- if (reason.startsWith(PAUSE_PREFIX)) {
85
- const body = reason.slice(PAUSE_PREFIX.length);
86
- try {
87
- const parsed = JSON.parse(body);
88
- if (typeof parsed?.nodeId !== "string" || typeof parsed?.awaiting !== "string") return null;
89
- return "detail" in parsed ? { nodeId: parsed.nodeId, awaiting: parsed.awaiting, detail: parsed.detail } : { nodeId: parsed.nodeId, awaiting: parsed.awaiting };
90
- } catch {
91
- return null;
92
- }
93
- }
94
- for (const [prefix, awaiting] of LEGACY_PAUSE_PREFIXES) {
95
- if (reason.startsWith(prefix)) {
96
- return { nodeId: reason.slice(prefix.length), awaiting };
97
- }
98
- }
99
- return null;
100
- }
101
- function isPause(reason) {
102
- return decodePause(reason) !== null;
103
- }
104
- function pauseForHuman(ctx, awaiting, detail) {
105
- return ctx.abort(encodePause({ nodeId: ctx.node.id, awaiting, detail }));
106
- }
107
-
108
34
  // src/registry/registry.ts
109
35
  var kinds = /* @__PURE__ */ new Map();
110
36
  var aliases = /* @__PURE__ */ new Map();
@@ -258,6 +184,114 @@ function categoryAccent(category) {
258
184
  }
259
185
  }
260
186
 
187
+ // src/registry/connection.ts
188
+ var ANY_PORT_TYPE = "any";
189
+ var defaultPortCompatibility = (source, target) => {
190
+ const s = source?.type;
191
+ const t = target?.type;
192
+ if (!s || !t) return true;
193
+ if (s === ANY_PORT_TYPE || t === ANY_PORT_TYPE) return true;
194
+ return s === t;
195
+ };
196
+ function portsFor(node, side) {
197
+ const kind = getNodeKind(node.data?.kind ?? node.type);
198
+ const resolved = resolveNodePorts(node, kind ?? void 0);
199
+ return resolved[side] ?? [];
200
+ }
201
+ function findPort(ports, handleId) {
202
+ if (handleId == null) return ports.length === 1 ? ports[0] : void 0;
203
+ return ports.find((p) => p.id === handleId);
204
+ }
205
+ function createConnectionValidator(getNodes, options = {}) {
206
+ const compatible = options.compatible ?? defaultPortCompatibility;
207
+ return (connection) => {
208
+ const { source, target, sourceHandle, targetHandle } = connection;
209
+ if (!source || !target) return false;
210
+ if (!options.allowSelfConnection && source === target) return false;
211
+ const nodes = getNodes();
212
+ const src = nodes.find((n) => n.id === source);
213
+ const tgt = nodes.find((n) => n.id === target);
214
+ if (!src || !tgt) return false;
215
+ const outPort = findPort(portsFor(src, "outputs"), sourceHandle);
216
+ const inPort = findPort(portsFor(tgt, "inputs"), targetHandle);
217
+ return compatible(outPort, inPort);
218
+ };
219
+ }
220
+
221
+ // src/registry/capabilities.ts
222
+ var llmClient = null;
223
+ function registerLlmClient(client) {
224
+ llmClient = client;
225
+ return () => {
226
+ if (llmClient === client) llmClient = null;
227
+ };
228
+ }
229
+ function getLlmClient() {
230
+ return llmClient;
231
+ }
232
+ function isResolutionFailure(value) {
233
+ return typeof value === "object" && value !== null && "reason" in value && value.reason !== void 0;
234
+ }
235
+ var workflowResolver = null;
236
+ function registerWorkflowResolver(resolver) {
237
+ workflowResolver = resolver;
238
+ return () => {
239
+ if (workflowResolver === resolver) workflowResolver = null;
240
+ };
241
+ }
242
+ function getWorkflowResolver() {
243
+ return workflowResolver;
244
+ }
245
+ function capabilityStatus() {
246
+ let documentReady = false;
247
+ try {
248
+ documentReady = Boolean(globalThis.__fancyFlowDocumentAdapter);
249
+ } catch {
250
+ documentReady = false;
251
+ }
252
+ return {
253
+ llm: llmClient !== null,
254
+ workflow_resolver: workflowResolver !== null,
255
+ document: documentReady
256
+ };
257
+ }
258
+
259
+ // src/registry/pause.ts
260
+ var PAUSE_PREFIX = "fancy-flow:pause:";
261
+ var LEGACY_PAUSE_PREFIXES = [
262
+ ["awaiting-approval:", "approval"],
263
+ ["awaiting-input:", "input"]
264
+ ];
265
+ function encodePause(signal) {
266
+ const { nodeId, awaiting, detail } = signal;
267
+ return PAUSE_PREFIX + JSON.stringify(detail === void 0 ? { nodeId, awaiting } : { nodeId, awaiting, detail });
268
+ }
269
+ function decodePause(reason) {
270
+ if (typeof reason !== "string") return null;
271
+ if (reason.startsWith(PAUSE_PREFIX)) {
272
+ const body = reason.slice(PAUSE_PREFIX.length);
273
+ try {
274
+ const parsed = JSON.parse(body);
275
+ if (typeof parsed?.nodeId !== "string" || typeof parsed?.awaiting !== "string") return null;
276
+ return "detail" in parsed ? { nodeId: parsed.nodeId, awaiting: parsed.awaiting, detail: parsed.detail } : { nodeId: parsed.nodeId, awaiting: parsed.awaiting };
277
+ } catch {
278
+ return null;
279
+ }
280
+ }
281
+ for (const [prefix, awaiting] of LEGACY_PAUSE_PREFIXES) {
282
+ if (reason.startsWith(prefix)) {
283
+ return { nodeId: reason.slice(prefix.length), awaiting };
284
+ }
285
+ }
286
+ return null;
287
+ }
288
+ function isPause(reason) {
289
+ return decodePause(reason) !== null;
290
+ }
291
+ function pauseForHuman(ctx, awaiting, detail) {
292
+ return ctx.abort(encodePause({ nodeId: ctx.node.id, awaiting, detail }));
293
+ }
294
+
261
295
  // src/runtime/run-flow.ts
262
296
  async function runFlow(graph, executors, onEvent = () => {
263
297
  }, options = {}) {
@@ -9576,15 +9610,32 @@ function DefaultBody({ config, kind }) {
9576
9610
  ] })
9577
9611
  ] });
9578
9612
  }
9613
+ var truncate = (s, n = 30) => s.length > n ? s.slice(0, n - 1) + "\u2026" : s;
9614
+ function itemLabel(item) {
9615
+ if (item && typeof item === "object") {
9616
+ const o = item;
9617
+ const name = o.label ?? o.name ?? o.key ?? o.title ?? o.id;
9618
+ if (typeof name === "string" && name) return name;
9619
+ if (typeof name === "number") return String(name);
9620
+ return "item";
9621
+ }
9622
+ return String(item ?? "");
9623
+ }
9579
9624
  function previewValue(v) {
9580
- if (typeof v === "string") return v.length > 30 ? v.slice(0, 27) + "\u2026" : v;
9625
+ if (v === null || v === void 0) return "";
9626
+ if (typeof v === "string") return truncate(v);
9581
9627
  if (typeof v === "number" || typeof v === "boolean") return String(v);
9582
- try {
9583
- const j = JSON.stringify(v);
9584
- return j.length > 30 ? j.slice(0, 27) + "\u2026" : j;
9585
- } catch {
9586
- return "[object]";
9628
+ if (Array.isArray(v)) {
9629
+ if (v.length === 0) return "none";
9630
+ const names = v.slice(0, 3).map(itemLabel).filter(Boolean).join(", ");
9631
+ const rest = v.length > 3 ? `, +${v.length - 3}` : "";
9632
+ return names ? truncate(names + rest) : `${v.length} item${v.length === 1 ? "" : "s"}`;
9633
+ }
9634
+ if (typeof v === "object") {
9635
+ const keys = Object.keys(v);
9636
+ return keys.length === 0 ? "empty" : `${keys.length} field${keys.length === 1 ? "" : "s"}`;
9587
9637
  }
9638
+ return "\u2026";
9588
9639
  }
9589
9640
  function casePorts(cases) {
9590
9641
  const byPort = /* @__PURE__ */ new Map();
@@ -10342,6 +10393,7 @@ function buildNodeTypes() {
10342
10393
  return map;
10343
10394
  }
10344
10395
 
10396
+ exports.ANY_PORT_TYPE = ANY_PORT_TYPE;
10345
10397
  exports.BUILTIN_KINDS = BUILTIN_KINDS;
10346
10398
  exports.DEFAULT_MAX_DEPTH = DEFAULT_MAX_DEPTH;
10347
10399
  exports.LEGACY_PAUSE_PREFIXES = LEGACY_PAUSE_PREFIXES;
@@ -10351,9 +10403,11 @@ exports.RichInputPreview = RichInputPreview;
10351
10403
  exports.buildNodeTypes = buildNodeTypes;
10352
10404
  exports.capabilityStatus = capabilityStatus;
10353
10405
  exports.categoryAccent = categoryAccent;
10406
+ exports.createConnectionValidator = createConnectionValidator;
10354
10407
  exports.declaredRoutes = declaredRoutes;
10355
10408
  exports.decodePause = decodePause;
10356
10409
  exports.defaultConfigFor = defaultConfigFor;
10410
+ exports.defaultPortCompatibility = defaultPortCompatibility;
10357
10411
  exports.encodePause = encodePause;
10358
10412
  exports.getLlmClient = getLlmClient;
10359
10413
  exports.getNodeKind = getNodeKind;