@particle-academy/fancy-flow 0.44.0 → 0.46.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 (78) hide show
  1. package/dist/{ConfigFieldRenderer-LotNHeG8.d.cts → ConfigFieldRenderer-Cyir8-B8.d.cts} +2 -2
  2. package/dist/{ConfigFieldRenderer-BHQDZer6.d.ts → ConfigFieldRenderer-iMkZj0VK.d.ts} +2 -2
  3. package/dist/{FlowViewer-nLqo7s2E.d.cts → FlowViewer-CY3_vHSg.d.cts} +1 -1
  4. package/dist/{FlowViewer-C4QAVgWv.d.ts → FlowViewer-DGW6t-80.d.ts} +1 -1
  5. package/dist/{capabilities-CkhKj_U_.d.ts → capabilities-DMUhnyZD.d.ts} +2 -2
  6. package/dist/{capabilities-BtJQTbnw.d.cts → capabilities-Dmd26aAs.d.cts} +2 -2
  7. package/dist/chunk-BHM5HGXH.js +79 -0
  8. package/dist/chunk-BHM5HGXH.js.map +1 -0
  9. package/dist/{chunk-DHUFUDEX.js → chunk-HFUBVT7W.js} +3 -3
  10. package/dist/{chunk-DHUFUDEX.js.map → chunk-HFUBVT7W.js.map} +1 -1
  11. package/dist/{chunk-VLATLVGH.js → chunk-L43CWD63.js} +129 -5
  12. package/dist/chunk-L43CWD63.js.map +1 -0
  13. package/dist/{chunk-A6RFLGWV.js → chunk-MH4ZW4DJ.js} +3 -3
  14. package/dist/{chunk-A6RFLGWV.js.map → chunk-MH4ZW4DJ.js.map} +1 -1
  15. package/dist/{chunk-QZTGV3ZL.js → chunk-P5SYH6M6.js} +3 -3
  16. package/dist/{chunk-QZTGV3ZL.js.map → chunk-P5SYH6M6.js.map} +1 -1
  17. package/dist/{chunk-WBXGZVLB.js → chunk-RMU6YCIQ.js} +11 -5
  18. package/dist/chunk-RMU6YCIQ.js.map +1 -0
  19. package/dist/durable/index.d.cts +490 -0
  20. package/dist/durable/index.d.ts +490 -0
  21. package/dist/durable.cjs +927 -0
  22. package/dist/durable.cjs.map +1 -0
  23. package/dist/durable.js +552 -0
  24. package/dist/durable.js.map +1 -0
  25. package/dist/engine.cjs +198 -12
  26. package/dist/engine.cjs.map +1 -1
  27. package/dist/engine.d.cts +44 -7
  28. package/dist/engine.d.ts +44 -7
  29. package/dist/engine.js +68 -12
  30. package/dist/engine.js.map +1 -1
  31. package/dist/fields/react-fancy.d.cts +4 -3
  32. package/dist/fields/react-fancy.d.ts +4 -3
  33. package/dist/index.cjs +133 -3
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.cts +12 -10
  36. package/dist/index.d.ts +12 -10
  37. package/dist/index.js +13 -88
  38. package/dist/index.js.map +1 -1
  39. package/dist/layout/index.d.cts +1 -1
  40. package/dist/layout/index.d.ts +1 -1
  41. package/dist/llm/prism.d.cts +2 -2
  42. package/dist/llm/prism.d.ts +2 -2
  43. package/dist/llm/vercel-ai.d.cts +2 -2
  44. package/dist/llm/vercel-ai.d.ts +2 -2
  45. package/dist/pause-9iT4tCEV.d.cts +96 -0
  46. package/dist/pause-9iT4tCEV.d.ts +96 -0
  47. package/dist/registry/index.d.cts +7 -6
  48. package/dist/registry/index.d.ts +7 -6
  49. package/dist/{registry-C9KMVpn7.d.cts → registry-D_kyUZEf.d.cts} +1 -1
  50. package/dist/{registry-FGCBprcn.d.ts → registry-LHB3MlR9.d.ts} +1 -1
  51. package/dist/registry.cjs +133 -3
  52. package/dist/registry.cjs.map +1 -1
  53. package/dist/registry.js +4 -4
  54. package/dist/{run-cohort-BnP6ErHi.d.ts → run-cohort-Cyeny2Uu.d.ts} +3 -34
  55. package/dist/{run-cohort-Bjv2ZU6g.d.cts → run-cohort-DdPPowJ3.d.cts} +3 -34
  56. package/dist/run-flow-1KiXhy59.d.cts +55 -0
  57. package/dist/run-flow-CQvTDBYk.d.ts +55 -0
  58. package/dist/runtime/index.d.cts +5 -3
  59. package/dist/runtime/index.d.ts +5 -3
  60. package/dist/runtime.cjs +128 -2
  61. package/dist/runtime.cjs.map +1 -1
  62. package/dist/runtime.js +3 -3
  63. package/dist/schema/index.d.cts +1 -1
  64. package/dist/schema/index.d.ts +1 -1
  65. package/dist/screens.d.cts +2 -2
  66. package/dist/screens.d.ts +2 -2
  67. package/dist/screens.js +6 -6
  68. package/dist/{types-JViq8reb.d.ts → types--S2Kzx7w.d.cts} +3 -97
  69. package/dist/{types-B78Rm69S.d.cts → types-ChkCRonF.d.ts} +3 -97
  70. package/dist/types-Jx1TwehV.d.cts +247 -0
  71. package/dist/types-Jx1TwehV.d.ts +247 -0
  72. package/dist/ux.d.cts +3 -2
  73. package/dist/ux.d.ts +3 -2
  74. package/package.json +12 -2
  75. package/dist/chunk-VLATLVGH.js.map +0 -1
  76. package/dist/chunk-WBXGZVLB.js.map +0 -1
  77. package/dist/types-sOmpCitB.d.cts +0 -109
  78. package/dist/types-sOmpCitB.d.ts +0 -109
@@ -1,4 +1,4 @@
1
- import { F as FlowGraph, b as FlowNode } from '../types-sOmpCitB.cjs';
1
+ import { F as FlowGraph, a as FlowNode } from '../types-Jx1TwehV.cjs';
2
2
  import '@xyflow/react';
3
3
 
4
4
  type AutoLayoutDirection = "LR" | "TB" | "RL" | "BT";
@@ -1,4 +1,4 @@
1
- import { F as FlowGraph, b as FlowNode } from '../types-sOmpCitB.js';
1
+ import { F as FlowGraph, a as FlowNode } from '../types-Jx1TwehV.js';
2
2
  import '@xyflow/react';
3
3
 
4
4
  type AutoLayoutDirection = "LR" | "TB" | "RL" | "BT";
@@ -1,5 +1,5 @@
1
- import { L as LlmClient } from '../capabilities-BtJQTbnw.cjs';
2
- import '../types-sOmpCitB.cjs';
1
+ import { L as LlmClient } from '../capabilities-Dmd26aAs.cjs';
2
+ import '../types-Jx1TwehV.cjs';
3
3
  import '@xyflow/react';
4
4
 
5
5
  /**
@@ -1,5 +1,5 @@
1
- import { L as LlmClient } from '../capabilities-CkhKj_U_.js';
2
- import '../types-sOmpCitB.js';
1
+ import { L as LlmClient } from '../capabilities-DMUhnyZD.js';
2
+ import '../types-Jx1TwehV.js';
3
3
  import '@xyflow/react';
4
4
 
5
5
  /**
@@ -1,6 +1,6 @@
1
1
  import { LanguageModel } from 'ai';
2
- import { a as LlmRouteRequest, L as LlmClient } from '../capabilities-BtJQTbnw.cjs';
3
- import '../types-sOmpCitB.cjs';
2
+ import { a as LlmRouteRequest, L as LlmClient } from '../capabilities-Dmd26aAs.cjs';
3
+ import '../types-Jx1TwehV.cjs';
4
4
  import '@xyflow/react';
5
5
 
6
6
  /**
@@ -1,6 +1,6 @@
1
1
  import { LanguageModel } from 'ai';
2
- import { a as LlmRouteRequest, L as LlmClient } from '../capabilities-CkhKj_U_.js';
3
- import '../types-sOmpCitB.js';
2
+ import { a as LlmRouteRequest, L as LlmClient } from '../capabilities-DMUhnyZD.js';
3
+ import '../types-Jx1TwehV.js';
4
4
  import '@xyflow/react';
5
5
 
6
6
  /**
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The human-pause contract.
3
+ *
4
+ * A workflow that waits for a person is not an error, but it travels the same
5
+ * channel as one: the executor aborts, the engine records a reason string, and
6
+ * the durable runner decides whether that string meant "failed" or "waiting".
7
+ *
8
+ * That seam existed before this module, as two `str_starts_with` checks in the
9
+ * Laravel run job against constants owned by two BUILTIN executors. It worked,
10
+ * and it was invisible: a third-party human-input node had no way to announce
11
+ * that it pauses, and nothing stopped a refactor from removing the mechanism
12
+ * out from under published packages. Reported by the MOIC Suite consumer, who
13
+ * needed exactly that and had to reach for a private constant to get it.
14
+ *
15
+ * So the encoding is now public, typed, and versioned by prefix rather than
16
+ * implied. The wire format stays a plain string on purpose — it survives the
17
+ * existing abort → `RunResult.error` path unchanged, crosses a queue boundary,
18
+ * and decodes identically in PHP, none of which a thrown class would do.
19
+ *
20
+ * @see decodePause — the one function a durable runner needs.
21
+ */
22
+ /**
23
+ * What the run is waiting for.
24
+ *
25
+ * `approval` and `input` are the shapes both runtimes ship. The type stays open
26
+ * because the whole point is that a marketplace node can define its own —
27
+ * a signature step, a payment confirmation, a review queue — and a runner that
28
+ * does not recognise one should report it rather than guess.
29
+ */
30
+ type PauseAwaiting = "approval" | "input" | (string & {});
31
+ /** A run halted, waiting for a person. */
32
+ type PauseSignal = {
33
+ /** The node that paused — where a submission gets injected on resume. */
34
+ nodeId: string;
35
+ awaiting: PauseAwaiting;
36
+ /**
37
+ * Kind-supplied context for whoever renders the wait — a form schema, the
38
+ * question being asked, a diff to approve. Must be JSON-serializable: it
39
+ * crosses a queue boundary and, for durable runs, a database column.
40
+ */
41
+ detail?: unknown;
42
+ };
43
+ /** Marks a reason string as a pause rather than a failure. */
44
+ declare const PAUSE_PREFIX = "fancy-flow:pause:";
45
+ /**
46
+ * Reason prefixes shipped before this contract, kept decodable forever.
47
+ *
48
+ * These are what `DurableApprovalExecutor` and `DurableUserInputExecutor`
49
+ * emitted, and they are written into the `error` column of every run that
50
+ * paused under an older version. Dropping them would strand those runs
51
+ * mid-flight — a resume path that only works for new runs is not a resume path.
52
+ */
53
+ declare const LEGACY_PAUSE_PREFIXES: ReadonlyArray<readonly [string, PauseAwaiting]>;
54
+ /**
55
+ * Encode a pause as the reason string an executor aborts with.
56
+ *
57
+ * The payload is JSON rather than delimited fields because a node id may
58
+ * contain a colon, and a positional encoding that breaks on user data is the
59
+ * kind of bug that only shows up in someone else's graph.
60
+ */
61
+ declare function encodePause(signal: PauseSignal): string;
62
+ /**
63
+ * Decode a run's error reason into a pause, or null if it was a real failure.
64
+ *
65
+ * This is the whole contract from a runner's side: call it on `result.error`,
66
+ * and if it returns non-null, persist the run as waiting on `signal.nodeId`
67
+ * instead of failing it. Accepts the legacy prefixes, so a runner written
68
+ * against this handles runs that paused under an older version.
69
+ */
70
+ declare function decodePause(reason: string | null | undefined): PauseSignal | null;
71
+ /** True when a run's error reason is actually a pause. */
72
+ declare function isPause(reason: string | null | undefined): boolean;
73
+ /**
74
+ * Abort the current node as a pause.
75
+ *
76
+ * Called from inside an executor with its own context. Node authors should
77
+ * reach for this rather than hand-encoding a reason, so the format stays ours
78
+ * to change:
79
+ *
80
+ * ```ts
81
+ * const values = ctx.inputs.values;
82
+ * if (values === undefined) pauseForHuman(ctx, "input", { fields });
83
+ * return values;
84
+ * ```
85
+ *
86
+ * Note the `undefined` check — an empty submission (`{}`) is a real answer and
87
+ * must resume. Truthiness here pauses forever on an empty form.
88
+ */
89
+ declare function pauseForHuman(ctx: {
90
+ node: {
91
+ id: string;
92
+ };
93
+ abort: (reason?: string) => never;
94
+ }, awaiting: PauseAwaiting, detail?: unknown): never;
95
+
96
+ export { LEGACY_PAUSE_PREFIXES as L, type PauseAwaiting as P, PAUSE_PREFIX as a, type PauseSignal as b, decodePause as d, encodePause as e, isPause as i, pauseForHuman as p };
@@ -0,0 +1,96 @@
1
+ /**
2
+ * The human-pause contract.
3
+ *
4
+ * A workflow that waits for a person is not an error, but it travels the same
5
+ * channel as one: the executor aborts, the engine records a reason string, and
6
+ * the durable runner decides whether that string meant "failed" or "waiting".
7
+ *
8
+ * That seam existed before this module, as two `str_starts_with` checks in the
9
+ * Laravel run job against constants owned by two BUILTIN executors. It worked,
10
+ * and it was invisible: a third-party human-input node had no way to announce
11
+ * that it pauses, and nothing stopped a refactor from removing the mechanism
12
+ * out from under published packages. Reported by the MOIC Suite consumer, who
13
+ * needed exactly that and had to reach for a private constant to get it.
14
+ *
15
+ * So the encoding is now public, typed, and versioned by prefix rather than
16
+ * implied. The wire format stays a plain string on purpose — it survives the
17
+ * existing abort → `RunResult.error` path unchanged, crosses a queue boundary,
18
+ * and decodes identically in PHP, none of which a thrown class would do.
19
+ *
20
+ * @see decodePause — the one function a durable runner needs.
21
+ */
22
+ /**
23
+ * What the run is waiting for.
24
+ *
25
+ * `approval` and `input` are the shapes both runtimes ship. The type stays open
26
+ * because the whole point is that a marketplace node can define its own —
27
+ * a signature step, a payment confirmation, a review queue — and a runner that
28
+ * does not recognise one should report it rather than guess.
29
+ */
30
+ type PauseAwaiting = "approval" | "input" | (string & {});
31
+ /** A run halted, waiting for a person. */
32
+ type PauseSignal = {
33
+ /** The node that paused — where a submission gets injected on resume. */
34
+ nodeId: string;
35
+ awaiting: PauseAwaiting;
36
+ /**
37
+ * Kind-supplied context for whoever renders the wait — a form schema, the
38
+ * question being asked, a diff to approve. Must be JSON-serializable: it
39
+ * crosses a queue boundary and, for durable runs, a database column.
40
+ */
41
+ detail?: unknown;
42
+ };
43
+ /** Marks a reason string as a pause rather than a failure. */
44
+ declare const PAUSE_PREFIX = "fancy-flow:pause:";
45
+ /**
46
+ * Reason prefixes shipped before this contract, kept decodable forever.
47
+ *
48
+ * These are what `DurableApprovalExecutor` and `DurableUserInputExecutor`
49
+ * emitted, and they are written into the `error` column of every run that
50
+ * paused under an older version. Dropping them would strand those runs
51
+ * mid-flight — a resume path that only works for new runs is not a resume path.
52
+ */
53
+ declare const LEGACY_PAUSE_PREFIXES: ReadonlyArray<readonly [string, PauseAwaiting]>;
54
+ /**
55
+ * Encode a pause as the reason string an executor aborts with.
56
+ *
57
+ * The payload is JSON rather than delimited fields because a node id may
58
+ * contain a colon, and a positional encoding that breaks on user data is the
59
+ * kind of bug that only shows up in someone else's graph.
60
+ */
61
+ declare function encodePause(signal: PauseSignal): string;
62
+ /**
63
+ * Decode a run's error reason into a pause, or null if it was a real failure.
64
+ *
65
+ * This is the whole contract from a runner's side: call it on `result.error`,
66
+ * and if it returns non-null, persist the run as waiting on `signal.nodeId`
67
+ * instead of failing it. Accepts the legacy prefixes, so a runner written
68
+ * against this handles runs that paused under an older version.
69
+ */
70
+ declare function decodePause(reason: string | null | undefined): PauseSignal | null;
71
+ /** True when a run's error reason is actually a pause. */
72
+ declare function isPause(reason: string | null | undefined): boolean;
73
+ /**
74
+ * Abort the current node as a pause.
75
+ *
76
+ * Called from inside an executor with its own context. Node authors should
77
+ * reach for this rather than hand-encoding a reason, so the format stays ours
78
+ * to change:
79
+ *
80
+ * ```ts
81
+ * const values = ctx.inputs.values;
82
+ * if (values === undefined) pauseForHuman(ctx, "input", { fields });
83
+ * return values;
84
+ * ```
85
+ *
86
+ * Note the `undefined` check — an empty submission (`{}`) is a real answer and
87
+ * must resume. Truthiness here pauses forever on an empty form.
88
+ */
89
+ declare function pauseForHuman(ctx: {
90
+ node: {
91
+ id: string;
92
+ };
93
+ abort: (reason?: string) => never;
94
+ }, awaiting: PauseAwaiting, detail?: unknown): never;
95
+
96
+ export { LEGACY_PAUSE_PREFIXES as L, type PauseAwaiting as P, PAUSE_PREFIX as a, type PauseSignal as b, decodePause as d, encodePause as e, isPause as i, pauseForHuman as p };
@@ -1,12 +1,13 @@
1
- import { d as NodeKindDefinition, j as PortSpec } from '../types-B78Rm69S.cjs';
2
- export { a as ConfigField, q as CredentialConfigField, D as DocumentConfigField, E as ExpressionConfigField, J as JsonConfigField, K as KeyValueConfigField, L as LEGACY_PAUSE_PREFIXES, c as NodeCategory, r as NumberConfigField, h as PAUSE_PREFIX, P as PauseAwaiting, i as PauseSignal, R as RenderBodyContext, t as RepeaterConfigField, u as RepeaterRowField, w as SelectConfigField, x as SwitchConfigField, T as TextConfigField, y as TextareaConfigField, m as decodePause, n as encodePause, o as isPause, p as pauseForHuman } from '../types-B78Rm69S.cjs';
3
- import { P as PortDescriptor, b as FlowNode, N as NodeExecutor } from '../types-sOmpCitB.cjs';
1
+ import { N as NodeKindDefinition, P as PortSpec } from '../types--S2Kzx7w.cjs';
2
+ export { C as ConfigField, j as CredentialConfigField, D as DocumentConfigField, E as ExpressionConfigField, J as JsonConfigField, K as KeyValueConfigField, a as NodeCategory, k as NumberConfigField, R as RenderBodyContext, l as RepeaterConfigField, m as RepeaterRowField, n as SelectConfigField, o as SwitchConfigField, T as TextConfigField, p as TextareaConfigField } from '../types--S2Kzx7w.cjs';
3
+ import { P as PortDescriptor, a as FlowNode, N as NodeExecutor } from '../types-Jx1TwehV.cjs';
4
4
  import { Connection, Edge, NodeProps, NodeTypes } from '@xyflow/react';
5
- import { b as LlmRoute } from '../capabilities-BtJQTbnw.cjs';
6
- export { C as CapabilityId, L as LlmClient, c as LlmRouteChoice, a 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-BtJQTbnw.cjs';
5
+ import { b as LlmRoute } from '../capabilities-Dmd26aAs.cjs';
6
+ export { C as CapabilityId, L as LlmClient, c as LlmRouteChoice, a as LlmRouteRequest, d as WorkflowResolution, e as WorkflowResolutionFailure, W as WorkflowResolver, f as capabilityStatus, g as getLlmClient, h as getWorkflowResolver, i as isResolutionFailure, r as registerLlmClient, j as registerWorkflowResolver } from '../capabilities-Dmd26aAs.cjs';
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
- export { N as NodeKindPresentation, c as categoryAccent, b as clearNodeKindOverrides, d as defaultConfigFor, g as getNodeKind, k as kindIds, l as listNodeKinds, o as onNodeKindsChanged, e as overrideNodeKind, r as registerNodeKind, a as resolveKindId, v as validateConfig } from '../registry-C9KMVpn7.cjs';
10
+ export { N as NodeKindPresentation, c as categoryAccent, b as clearNodeKindOverrides, d as defaultConfigFor, g as getNodeKind, k as kindIds, l as listNodeKinds, o as onNodeKindsChanged, e as overrideNodeKind, r as registerNodeKind, a as resolveKindId, v as validateConfig } from '../registry-D_kyUZEf.cjs';
10
11
 
11
12
  /**
12
13
  * Connection validation — the single rule that decides whether an edge between
@@ -1,12 +1,13 @@
1
- import { d as NodeKindDefinition, j as PortSpec } from '../types-JViq8reb.js';
2
- export { a as ConfigField, q as CredentialConfigField, D as DocumentConfigField, E as ExpressionConfigField, J as JsonConfigField, K as KeyValueConfigField, L as LEGACY_PAUSE_PREFIXES, c as NodeCategory, r as NumberConfigField, h as PAUSE_PREFIX, P as PauseAwaiting, i as PauseSignal, R as RenderBodyContext, t as RepeaterConfigField, u as RepeaterRowField, w as SelectConfigField, x as SwitchConfigField, T as TextConfigField, y as TextareaConfigField, m as decodePause, n as encodePause, o as isPause, p as pauseForHuman } from '../types-JViq8reb.js';
3
- import { P as PortDescriptor, b as FlowNode, N as NodeExecutor } from '../types-sOmpCitB.js';
1
+ import { N as NodeKindDefinition, P as PortSpec } from '../types-ChkCRonF.js';
2
+ export { C as ConfigField, j as CredentialConfigField, D as DocumentConfigField, E as ExpressionConfigField, J as JsonConfigField, K as KeyValueConfigField, a as NodeCategory, k as NumberConfigField, R as RenderBodyContext, l as RepeaterConfigField, m as RepeaterRowField, n as SelectConfigField, o as SwitchConfigField, T as TextConfigField, p as TextareaConfigField } from '../types-ChkCRonF.js';
3
+ import { P as PortDescriptor, a as FlowNode, N as NodeExecutor } from '../types-Jx1TwehV.js';
4
4
  import { Connection, Edge, NodeProps, NodeTypes } from '@xyflow/react';
5
- import { b as LlmRoute } from '../capabilities-CkhKj_U_.js';
6
- export { C as CapabilityId, L as LlmClient, c as LlmRouteChoice, a 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-CkhKj_U_.js';
5
+ import { b as LlmRoute } from '../capabilities-DMUhnyZD.js';
6
+ export { C as CapabilityId, L as LlmClient, c as LlmRouteChoice, a as LlmRouteRequest, d as WorkflowResolution, e as WorkflowResolutionFailure, W as WorkflowResolver, f as capabilityStatus, g as getLlmClient, h as getWorkflowResolver, i as isResolutionFailure, r as registerLlmClient, j as registerWorkflowResolver } from '../capabilities-DMUhnyZD.js';
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
- export { N as NodeKindPresentation, c as categoryAccent, b as clearNodeKindOverrides, d as defaultConfigFor, g as getNodeKind, k as kindIds, l as listNodeKinds, o as onNodeKindsChanged, e as overrideNodeKind, r as registerNodeKind, a as resolveKindId, v as validateConfig } from '../registry-FGCBprcn.js';
10
+ export { N as NodeKindPresentation, c as categoryAccent, b as clearNodeKindOverrides, d as defaultConfigFor, g as getNodeKind, k as kindIds, l as listNodeKinds, o as onNodeKindsChanged, e as overrideNodeKind, r as registerNodeKind, a as resolveKindId, v as validateConfig } from '../registry-LHB3MlR9.js';
10
11
 
11
12
  /**
12
13
  * Connection validation — the single rule that decides whether an edge between
@@ -1,4 +1,4 @@
1
- import { d as NodeKindDefinition } from './types-B78Rm69S.cjs';
1
+ import { N as NodeKindDefinition } from './types--S2Kzx7w.cjs';
2
2
 
3
3
  /**
4
4
  * The presentation fields a consumer may override on someone else's node kind.
@@ -1,4 +1,4 @@
1
- import { d as NodeKindDefinition } from './types-JViq8reb.js';
1
+ import { N as NodeKindDefinition } from './types-ChkCRonF.js';
2
2
 
3
3
  /**
4
4
  * The presentation fields a consumer may override on someone else's node kind.
package/dist/registry.cjs CHANGED
@@ -323,10 +323,121 @@ function pauseForHuman(ctx, awaiting, detail) {
323
323
  return ctx.abort(encodePause({ nodeId: ctx.node.id, awaiting, detail }));
324
324
  }
325
325
 
326
+ // src/runtime/run-identity.ts
327
+ function escapeSegment(value) {
328
+ return value.replace(/%/g, "%25").replace(/\//g, "%2F").replace(/#/g, "%23");
329
+ }
330
+ function renderSegment(value, occurrence) {
331
+ const escaped = escapeSegment(value);
332
+ return occurrence === void 0 || occurrence === null ? escaped : `${escaped}#${occurrence}`;
333
+ }
334
+ function instant(value) {
335
+ const ms = Date.parse(value);
336
+ if (Number.isNaN(ms)) {
337
+ throw new Error(
338
+ `RunIdentity: firstAttemptAt is not a parseable timestamp: ${JSON.stringify(value)}`
339
+ );
340
+ }
341
+ return ms;
342
+ }
343
+ var RunIdentity = class _RunIdentity {
344
+ constructor(runKey, path = [], attempt = 1, firstAttemptAt = (/* @__PURE__ */ new Date()).toISOString()) {
345
+ if (!runKey || runKey.trim() === "") {
346
+ throw new Error("RunIdentity: runKey must be a non-empty string.");
347
+ }
348
+ this.runKey = runKey;
349
+ this.path = Object.freeze([...path]);
350
+ this.attempt = Math.max(1, Math.trunc(attempt));
351
+ this.firstAttemptAt = firstAttemptAt;
352
+ Object.freeze(this);
353
+ }
354
+ /**
355
+ * The identity of one execution of one node — stable across retries of that
356
+ * execution, distinct from every other execution of the same node.
357
+ *
358
+ * Pass `occurrence` when an executor runs the same node more than once at the
359
+ * same level (a loop body, one item of a fan-out it drives itself).
360
+ */
361
+ stepKey(nodeId, occurrence) {
362
+ return `${this.runKey}:${[...this.path, renderSegment(nodeId, occurrence)].join("/")}`;
363
+ }
364
+ /**
365
+ * A child identity for work nested inside this step.
366
+ *
367
+ * `subflow` pushes the invoking node's id, so a node inside the child graph
368
+ * cannot collide with a same-named node in the parent. Attempt and
369
+ * `firstAttemptAt` are carried down unchanged: the nested work happens inside
370
+ * this step's attempt, and shares its clock.
371
+ */
372
+ descend(segment, occurrence) {
373
+ return new _RunIdentity(
374
+ this.runKey,
375
+ [...this.path, renderSegment(segment, occurrence)],
376
+ this.attempt,
377
+ this.firstAttemptAt
378
+ );
379
+ }
380
+ /** A copy on a different attempt, first-attempt clock preserved. */
381
+ withAttempt(attempt, firstAttemptAt) {
382
+ return new _RunIdentity(
383
+ this.runKey,
384
+ this.path,
385
+ attempt,
386
+ firstAttemptAt ?? this.firstAttemptAt
387
+ );
388
+ }
389
+ /**
390
+ * May this attempt reuse the step key and still be deduplicated?
391
+ *
392
+ * Providers forget idempotency keys — Stripe after 24 hours. Past that
393
+ * window, resending the key creates a second charge and sending a fresh one
394
+ * creates a second charge, so **the caller must refuse rather than pick
395
+ * between them**: a loud stuck run beats a silent double write.
396
+ *
397
+ * `true` on attempt 1 whatever the elapsed time — nothing was sent on an
398
+ * earlier attempt, so there is nothing for the provider to have forgotten.
399
+ * That is what lets a run park on a human gate for a week and then write.
400
+ *
401
+ * `windowSeconds: null` means the provider does not expire keys. `0` means
402
+ * it does not dedupe at all, so no retry may reuse a key — it is a real
403
+ * window, not an absent one, and the two must not be conflated: reading `0`
404
+ * as `null` turns "this provider does not dedupe" into "this provider
405
+ * dedupes forever", which is the more dangerous of the two by a distance.
406
+ */
407
+ isReplaySafe(windowSeconds, now2 = /* @__PURE__ */ new Date()) {
408
+ if (this.attempt <= 1) return true;
409
+ if (windowSeconds === null || windowSeconds === void 0) return true;
410
+ if (windowSeconds <= 0) return false;
411
+ const nowMs = typeof now2 === "string" ? instant(now2) : now2.getTime();
412
+ const elapsedSeconds = Math.max(0, (nowMs - instant(this.firstAttemptAt)) / 1e3);
413
+ return elapsedSeconds <= windowSeconds;
414
+ }
415
+ toJSON() {
416
+ return {
417
+ runKey: this.runKey,
418
+ path: [...this.path],
419
+ attempt: this.attempt,
420
+ firstAttemptAt: this.firstAttemptAt
421
+ };
422
+ }
423
+ /** Rebuild from a queue payload. */
424
+ static from(value) {
425
+ if (value instanceof _RunIdentity) return value;
426
+ if (typeof value === "string") return new _RunIdentity(value);
427
+ return new _RunIdentity(
428
+ value.runKey,
429
+ value.path ?? [],
430
+ value.attempt ?? 1,
431
+ value.firstAttemptAt ?? (/* @__PURE__ */ new Date()).toISOString()
432
+ );
433
+ }
434
+ };
435
+
326
436
  // src/runtime/run-flow.ts
327
437
  async function runFlow(graph, executors, onEvent = () => {
328
438
  }, options = {}) {
329
- const { signal, initialInputs = {}, timeoutMs, depth = 0 } = options;
439
+ const { signal, initialInputs = {}, timeoutMs, depth = 0, resumeOutputs = {} } = options;
440
+ const run = options.run === void 0 ? void 0 : RunIdentity.from(options.run);
330
441
  const outputs = {};
331
442
  const portValues = /* @__PURE__ */ new Map();
332
443
  const completed = /* @__PURE__ */ new Set();
@@ -344,6 +455,18 @@ async function runFlow(graph, executors, onEvent = () => {
344
455
  for (const node of order) {
345
456
  if (signal?.aborted) throw new Error("aborted");
346
457
  if (errors.length) break;
458
+ if (Object.prototype.hasOwnProperty.call(resumeOutputs, node.id)) {
459
+ const stored = resumeOutputs[node.id];
460
+ outputs[node.id] = stored;
461
+ const activated = activatedPorts(node, stored);
462
+ for (const portId of activated.ports) {
463
+ portValues.set(`${node.id}:${portId}`, activated.value);
464
+ onEvent({ type: "node-output", nodeId: node.id, portId, value: activated.value });
465
+ }
466
+ completed.add(node.id);
467
+ onEvent({ type: "node-status", nodeId: node.id, status: "done", text: "resumed" });
468
+ continue;
469
+ }
347
470
  const incoming = incomingByNode.get(node.id) ?? [];
348
471
  if (incoming.length > 0) {
349
472
  const anyActive = incoming.some((e) => portValues.has(`${e.source}:${e.sourceHandle ?? "out"}`));
@@ -383,7 +506,8 @@ async function runFlow(graph, executors, onEvent = () => {
383
506
  throw new Error(reason ?? "aborted");
384
507
  },
385
508
  emit: onEvent,
386
- depth
509
+ depth,
510
+ run
387
511
  })
388
512
  );
389
513
  outputs[node.id] = result;
@@ -536,7 +660,13 @@ var subflowExecutor = async (ctx) => {
536
660
  // require no configuration at all.
537
661
  __parent: ctx.inputs
538
662
  },
539
- depth: depth + 1
663
+ depth: depth + 1,
664
+ // Push THIS node onto the identity path, so a node inside the child graph
665
+ // cannot share an idempotency key with a same-named node in the parent —
666
+ // or with the same child graph invoked from a different parent node.
667
+ // Attempt and the first-attempt clock ride down unchanged: the child's
668
+ // work happens inside this node's attempt.
669
+ run: ctx.run?.descend(ctx.node.id)
540
670
  }
541
671
  );
542
672
  if (!result.ok) {