langflower 0.0.8 → 0.0.10

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 (221) hide show
  1. package/package.json +2 -2
  2. package/ui-dist/{chunk-Y6VMBLSJ.js → chunk-7UMVYN3A.js} +1 -1
  3. package/ui-dist/{chunk-6DQ2XSRW.js → chunk-MDGXX73Z.js} +3 -3
  4. package/ui-dist/index.html +2 -2
  5. package/ui-dist/main-Z7CPFXFA.js +313 -0
  6. package/ui-dist/styles-2TMDAUD7.css +1 -0
  7. package/vendor/common-nodes/dist/ai/chat-completion-stream.d.ts +2 -0
  8. package/vendor/common-nodes/dist/ai/critique/node.d.ts +246 -0
  9. package/vendor/common-nodes/dist/ai/critique/node.js +2 -2
  10. package/vendor/common-nodes/dist/ai/fake-llm/node.d.ts +246 -0
  11. package/vendor/common-nodes/dist/ai/features/chat-completion-stream.d.ts +53 -0
  12. package/vendor/common-nodes/dist/ai/features/chat-completion-stream.js +1 -0
  13. package/vendor/common-nodes/dist/ai/features/llm-loop/autokick-recovery.d.ts +13 -0
  14. package/vendor/common-nodes/dist/ai/features/llm-loop/autokick-recovery.js +17 -0
  15. package/vendor/common-nodes/dist/ai/features/llm-loop/classify-llm-failure.d.ts +2 -0
  16. package/vendor/common-nodes/dist/ai/features/llm-loop/classify-llm-failure.js +113 -0
  17. package/vendor/common-nodes/dist/ai/features/llm-loop/dead-loop-detector.d.ts +28 -0
  18. package/vendor/common-nodes/dist/ai/features/llm-loop/dead-loop-detector.js +194 -0
  19. package/vendor/common-nodes/dist/ai/features/llm-loop/live-get-tools.d.ts +9 -0
  20. package/vendor/common-nodes/dist/ai/features/llm-loop/live-get-tools.js +21 -0
  21. package/vendor/common-nodes/dist/ai/features/llm-loop/llm-loop-reducer.d.ts +2 -0
  22. package/vendor/common-nodes/dist/ai/features/llm-loop/llm-loop-reducer.js +204 -0
  23. package/vendor/common-nodes/dist/ai/features/llm-loop/llm-loop-types.d.ts +120 -0
  24. package/vendor/common-nodes/dist/ai/features/llm-loop/llm-loop-types.js +39 -0
  25. package/vendor/common-nodes/dist/ai/features/llm-loop/normalize-llm-recovery-policy.d.ts +2 -0
  26. package/vendor/common-nodes/dist/ai/features/llm-loop/normalize-llm-recovery-policy.js +59 -0
  27. package/vendor/common-nodes/dist/ai/features/llm-loop/operators/normalize-tool-result.d.ts +1 -0
  28. package/vendor/common-nodes/dist/ai/features/llm-loop/operators/normalize-tool-result.js +7 -0
  29. package/vendor/common-nodes/dist/ai/features/llm-loop/operators/observe-provider-stream.d.ts +43 -0
  30. package/vendor/common-nodes/dist/ai/features/llm-loop/operators/observe-provider-stream.js +96 -0
  31. package/vendor/common-nodes/dist/ai/features/llm-loop/run-agent-loop.d.ts +29 -0
  32. package/vendor/common-nodes/dist/ai/features/llm-loop/run-agent-loop.js +44 -0
  33. package/vendor/common-nodes/dist/ai/features/llm-loop/run-llm-loop.d.ts +74 -0
  34. package/vendor/common-nodes/dist/ai/features/llm-loop/run-llm-loop.js +658 -0
  35. package/vendor/common-nodes/dist/ai/features/llm-role-preset.d.ts +69 -0
  36. package/vendor/common-nodes/dist/ai/features/llm-role-preset.js +265 -0
  37. package/vendor/common-nodes/dist/ai/features/llm-session/llm-session-shell.d.ts +141 -0
  38. package/vendor/common-nodes/dist/ai/features/llm-session/llm-session-shell.js +193 -0
  39. package/vendor/common-nodes/dist/ai/features/llm-session/run-session-machine.d.ts +21 -0
  40. package/vendor/common-nodes/dist/ai/features/llm-session/run-session-machine.js +118 -0
  41. package/vendor/common-nodes/dist/ai/features/openai/context-length-error.d.ts +14 -0
  42. package/vendor/common-nodes/dist/ai/features/openai/context-length-error.js +63 -0
  43. package/vendor/common-nodes/dist/ai/features/openai/create-chat-completion-stream.d.ts +19 -0
  44. package/vendor/common-nodes/dist/ai/features/openai/create-chat-completion-stream.js +182 -0
  45. package/vendor/common-nodes/dist/ai/features/openai/list-provider-models.d.ts +28 -0
  46. package/vendor/common-nodes/dist/ai/features/openai/list-provider-models.js +52 -0
  47. package/vendor/common-nodes/dist/ai/features/openai/llm-context-compaction.d.ts +31 -0
  48. package/vendor/common-nodes/dist/ai/features/openai/llm-context-compaction.js +326 -0
  49. package/vendor/common-nodes/dist/ai/features/openai/normalize-compaction-params.d.ts +14 -0
  50. package/vendor/common-nodes/dist/ai/features/openai/normalize-compaction-params.js +21 -0
  51. package/vendor/common-nodes/dist/ai/features/openai/prepare-chat-completion.d.ts +30 -0
  52. package/vendor/common-nodes/dist/ai/features/openai/prepare-chat-completion.js +132 -0
  53. package/vendor/common-nodes/dist/ai/features/path-choice/control-tools.d.ts +20 -0
  54. package/vendor/common-nodes/dist/ai/features/path-choice/control-tools.js +70 -0
  55. package/vendor/common-nodes/dist/ai/features/path-choice/run-reactive-path-choice-loop.d.ts +32 -0
  56. package/vendor/common-nodes/dist/ai/features/path-choice/run-reactive-path-choice-loop.js +93 -0
  57. package/vendor/common-nodes/dist/ai/features/prompt/build-effective-system-prompt.d.ts +9 -0
  58. package/vendor/common-nodes/dist/ai/features/prompt/build-effective-system-prompt.js +21 -0
  59. package/vendor/common-nodes/dist/ai/features/prompt/normalize-max-iterations.d.ts +13 -0
  60. package/vendor/common-nodes/dist/ai/features/prompt/normalize-max-iterations.js +23 -0
  61. package/vendor/common-nodes/dist/ai/features/prompt/resolve-chat-provider-model.d.ts +10 -0
  62. package/vendor/common-nodes/dist/ai/features/prompt/resolve-chat-provider-model.js +8 -0
  63. package/vendor/common-nodes/dist/ai/features/run-host-services.d.ts +54 -0
  64. package/vendor/common-nodes/dist/ai/features/run-host-services.js +29 -0
  65. package/vendor/common-nodes/dist/ai/features/scripted-chat-completion-stream.d.ts +17 -0
  66. package/vendor/common-nodes/dist/ai/features/scripted-chat-completion-stream.js +61 -0
  67. package/vendor/common-nodes/dist/ai/features/sub-agent-protocol.d.ts +40 -0
  68. package/vendor/common-nodes/dist/ai/features/sub-agent-protocol.js +44 -0
  69. package/vendor/common-nodes/dist/ai/features/ui-schema/llm-compaction-ui-schema.d.ts +14 -0
  70. package/vendor/common-nodes/dist/ai/features/ui-schema/llm-compaction-ui-schema.js +18 -0
  71. package/vendor/common-nodes/dist/ai/features/ui-schema/llm-panel-ui-schema.d.ts +80 -0
  72. package/vendor/common-nodes/dist/ai/features/ui-schema/llm-panel-ui-schema.js +76 -0
  73. package/vendor/common-nodes/dist/ai/features/ui-schema/llm-recovery-ui-schema.d.ts +113 -0
  74. package/vendor/common-nodes/dist/ai/features/ui-schema/llm-recovery-ui-schema.js +131 -0
  75. package/vendor/common-nodes/dist/ai/features/wait-for-subagent-result.d.ts +7 -0
  76. package/vendor/common-nodes/dist/ai/features/wait-for-subagent-result.js +31 -0
  77. package/vendor/common-nodes/dist/ai/llm-loop/autokick-recovery.d.ts +13 -0
  78. package/vendor/common-nodes/dist/ai/llm-loop/autokick-recovery.js +17 -0
  79. package/vendor/common-nodes/dist/ai/llm-loop/dead-loop-detector.d.ts +28 -0
  80. package/vendor/common-nodes/dist/ai/llm-loop/dead-loop-detector.js +194 -0
  81. package/vendor/common-nodes/dist/ai/llm-loop/llm-loop-reducer.js +36 -0
  82. package/vendor/common-nodes/dist/ai/llm-loop/llm-loop-types.d.ts +35 -0
  83. package/vendor/common-nodes/dist/ai/llm-loop/llm-loop-types.js +19 -0
  84. package/vendor/common-nodes/dist/ai/llm-loop/normalize-llm-recovery-policy.js +52 -9
  85. package/vendor/common-nodes/dist/ai/llm-loop/operators/observe-provider-stream.d.ts +8 -2
  86. package/vendor/common-nodes/dist/ai/llm-loop/operators/observe-provider-stream.js +43 -13
  87. package/vendor/common-nodes/dist/ai/llm-loop/run-llm-loop.d.ts +3 -5
  88. package/vendor/common-nodes/dist/ai/llm-loop/run-llm-loop.js +116 -1
  89. package/vendor/common-nodes/dist/ai/llm-recovery-ui-schema.d.ts +82 -0
  90. package/vendor/common-nodes/dist/ai/llm-recovery-ui-schema.js +95 -0
  91. package/vendor/common-nodes/dist/ai/llm-session-shell.d.ts +4 -9
  92. package/vendor/common-nodes/dist/ai/llm-session-shell.js +2 -4
  93. package/vendor/common-nodes/dist/ai/nodes/critique/node.d.ts +588 -0
  94. package/vendor/common-nodes/dist/ai/nodes/critique/node.js +227 -0
  95. package/vendor/common-nodes/dist/ai/nodes/fake-llm/node.d.ts +571 -0
  96. package/vendor/common-nodes/dist/ai/nodes/fake-llm/node.js +201 -0
  97. package/vendor/common-nodes/dist/ai/nodes/openai-llm/node.d.ts +587 -0
  98. package/vendor/common-nodes/dist/ai/nodes/openai-llm/node.js +84 -0
  99. package/vendor/common-nodes/dist/ai/nodes/review/node.d.ts +593 -0
  100. package/vendor/common-nodes/dist/ai/nodes/review/node.js +227 -0
  101. package/vendor/common-nodes/dist/ai/nodes/sub-agent/node.d.ts +630 -0
  102. package/vendor/common-nodes/dist/ai/nodes/sub-agent/node.js +417 -0
  103. package/vendor/common-nodes/dist/ai/openai/create-chat-completion-stream.js +6 -0
  104. package/vendor/common-nodes/dist/ai/openai/prepare-chat-completion.d.ts +2 -0
  105. package/vendor/common-nodes/dist/ai/openai/prepare-chat-completion.js +18 -0
  106. package/vendor/common-nodes/dist/ai/openai-llm/node.d.ts +246 -0
  107. package/vendor/common-nodes/dist/ai/review/node.d.ts +246 -0
  108. package/vendor/common-nodes/dist/ai/review/node.js +2 -2
  109. package/vendor/common-nodes/dist/ai/sub-agent/node.d.ts +246 -0
  110. package/vendor/common-nodes/dist/ai/sub-agent/node.js +2 -2
  111. package/vendor/common-nodes/dist/catalog.js +9 -5
  112. package/vendor/common-nodes/dist/compile-custom-nodes/node.d.ts +21 -0
  113. package/vendor/common-nodes/dist/compile-custom-nodes/node.js +45 -0
  114. package/vendor/common-nodes/dist/crawl/crawl/node.js +1 -1
  115. package/vendor/common-nodes/dist/crawl/fetch-url/node.js +1 -1
  116. package/vendor/common-nodes/dist/flow/repeat/node.js +6 -1
  117. package/vendor/common-nodes/dist/langflower-tools/emit-registration-tools.d.ts +18 -0
  118. package/vendor/common-nodes/dist/langflower-tools/emit-registration-tools.js +29 -0
  119. package/vendor/common-nodes/dist/langflower-tools/node.d.ts +20 -0
  120. package/vendor/common-nodes/dist/langflower-tools/node.js +77 -0
  121. package/vendor/common-nodes/dist/mcp/mcp-http/node.d.ts +4 -4
  122. package/vendor/common-nodes/dist/mcp/mcp-http/node.js +9 -10
  123. package/vendor/common-nodes/dist/mcp/mcp-stdio/node.d.ts +4 -4
  124. package/vendor/common-nodes/dist/mcp/mcp-stdio/node.js +9 -10
  125. package/vendor/common-nodes/dist/text/append-file/node.js +1 -1
  126. package/vendor/common-nodes/dist/text/read-file/node.js +1 -1
  127. package/vendor/common-nodes/dist/text/write-file/node.js +1 -1
  128. package/vendor/common-nodes/dist/tools/collect-agent-tool-handles.d.ts +4 -4
  129. package/vendor/common-nodes/dist/tools/collect-agent-tool-handles.js +3 -11
  130. package/vendor/common-nodes/dist/tools/inventory-tool-round.d.ts +1 -11
  131. package/vendor/common-nodes/dist/tools/inventory-tool-round.js +0 -84
  132. package/vendor/common-nodes/dist/tools/tool-collection/node.d.ts +20 -0
  133. package/vendor/common-nodes/dist/tools/tool-collection/node.js +42 -0
  134. package/vendor/common-nodes/package.json +9 -13
  135. package/vendor/compiler/dist/compile-project-nodes.d.ts +2 -1
  136. package/vendor/compiler/dist/compile-project-nodes.js +35 -6
  137. package/vendor/compiler/dist/resolve-host-types.d.ts +0 -5
  138. package/vendor/compiler/dist/resolve-host-types.js +0 -8
  139. package/vendor/compiler/package.json +1 -1
  140. package/vendor/node-sdk/dist/node-factory/define-llm-node/default-llm-ports.d.ts +3 -7
  141. package/vendor/node-sdk/dist/node-factory/define-llm-node/default-llm-ports.js +4 -44
  142. package/vendor/node-sdk/dist/node-factory/define-llm-node/define-llm-node.d.ts +4 -6
  143. package/vendor/node-sdk/dist/node-factory/define-llm-node/define-llm-node.js +4 -6
  144. package/vendor/node-sdk/dist/node-factory/define-llm-node/llm-inventory-wire.d.ts +1 -0
  145. package/vendor/node-sdk/dist/node-factory/define-llm-node/llm-inventory-wire.js +1 -0
  146. package/vendor/node-sdk/dist/node-factory/define-llm-node/recovery-notice.d.ts +8 -0
  147. package/vendor/node-sdk/dist/node-factory/define-llm-node/recovery-notice.js +14 -0
  148. package/vendor/node-sdk/dist/node-factory/define-mcp/mcp-handle.d.ts +3 -4
  149. package/vendor/node-sdk/dist/node-factory/define-mcp/mcp-handle.js +1 -1
  150. package/vendor/node-sdk/dist/node-factory/define-reactive-node/types.d.ts +2 -4
  151. package/vendor/node-sdk/dist/node-factory/define-reactive-node/ui-schema-inference.d.ts +1 -1
  152. package/vendor/node-sdk/package.json +1 -1
  153. package/vendor/runtime/dist/port-feed-override.d.ts +13 -0
  154. package/vendor/runtime/dist/port-feed-override.js +21 -0
  155. package/vendor/runtime/dist/port-signal-from-response.d.ts +15 -0
  156. package/vendor/runtime/dist/port-signal-from-response.js +48 -0
  157. package/vendor/runtime/dist/runtime-editor.d.ts +5 -1
  158. package/vendor/runtime/dist/runtime-editor.js +19 -0
  159. package/vendor/runtime/dist/runtime-runner.d.ts +8 -3
  160. package/vendor/runtime/dist/runtime-runner.js +108 -87
  161. package/vendor/runtime/dist/runtime.d.ts +3 -1
  162. package/vendor/runtime/dist/runtime.js +1 -0
  163. package/vendor/runtime/dist/testing/workflows/workflow-events.d.ts +10 -6
  164. package/vendor/runtime/dist/testing/workflows/workflow-events.js +21 -31
  165. package/vendor/runtime/dist/types.d.ts +52 -40
  166. package/vendor/runtime/dist/types.js +5 -1
  167. package/vendor/runtime/package.json +1 -1
  168. package/vendor/server/dist/bridge/attach-langflower-bridge.js +3 -2
  169. package/vendor/server/dist/bridge/bridge-event-log.js +17 -102
  170. package/vendor/server/dist/bridge/build-execution-context.d.ts +5 -2
  171. package/vendor/server/dist/bridge/build-execution-context.js +48 -8
  172. package/vendor/server/dist/bridge/forward-runner-event.d.ts +1 -1
  173. package/vendor/server/dist/bridge/forward-runner-event.js +7 -12
  174. package/vendor/server/dist/bridge/get-live-wired-tools.d.ts +13 -0
  175. package/vendor/server/dist/bridge/get-live-wired-tools.js +83 -0
  176. package/vendor/server/dist/bridge/langflower-tools-rpc.d.ts +7 -0
  177. package/vendor/server/dist/bridge/langflower-tools-rpc.js +29 -0
  178. package/vendor/server/dist/bridge/wire-custom-palette-handlers.d.ts +2 -1
  179. package/vendor/server/dist/bridge/wire-custom-palette-handlers.js +3 -5
  180. package/vendor/server/dist/bridge/wire-runner-handlers.js +17 -10
  181. package/vendor/server/dist/bridge/wire-workflow-handlers.js +23 -9
  182. package/vendor/server/dist/checkpoint/run-checkpoint-session.d.ts +1 -1
  183. package/vendor/server/dist/checkpoint/run-checkpoint-session.js +10 -9
  184. package/vendor/server/dist/palette/compile-and-hot-swap-custom-nodes.d.ts +10 -0
  185. package/vendor/server/dist/palette/compile-and-hot-swap-custom-nodes.js +23 -0
  186. package/vendor/server/dist/session/reset-session-execution-feed.d.ts +6 -0
  187. package/vendor/server/dist/session/reset-session-execution-feed.js +8 -0
  188. package/vendor/server/dist/workflow/apply-editor-mutation.d.ts +5 -0
  189. package/vendor/server/dist/workflow/apply-editor-mutation.js +32 -0
  190. package/vendor/server/skeleton/instructions.md +9 -5
  191. package/vendor/server/skeleton/nodes/my-nodes/README.md +4 -3
  192. package/vendor/server/skeleton/nodes/my-nodes/package.json +1 -1
  193. package/vendor/server/skeleton/skills/langflower-helper/SKILL.md +82 -38
  194. package/vendor/server/skeleton/skills/langflower-helper/architecture.md +29 -10
  195. package/vendor/server/skeleton/skills/langflower-helper/layout.md +16 -15
  196. package/vendor/server/skeleton/skills/langflower-node-writer/SKILL.md +19 -9
  197. package/vendor/server/skeleton/skills/langflower-workflow-writer/SKILL.md +23 -15
  198. package/vendor/server/skeleton/workflows/kb-create.json +15 -43
  199. package/vendor/server/skeleton/workflows/kb-navigate.json +8 -22
  200. package/vendor/server/skeleton/workflows/simple-coder.json +18 -46
  201. package/vendor/server/skeleton/workflows/starter.json +35 -22
  202. package/vendor/shared/dist/execution/derive-run-settle-outcome.d.ts +1 -1
  203. package/vendor/shared/dist/execution/derive-run-settle-outcome.js +4 -3
  204. package/vendor/shared/dist/execution/derive-run-settle-outcome.test.js +18 -15
  205. package/vendor/shared/dist/langflower-bus-config.d.ts +6 -14
  206. package/vendor/shared/dist/langflower-bus-config.js +4 -12
  207. package/vendor/shared/dist/langflower-config/resolve-wired-tool-options.js +6 -0
  208. package/vendor/shared/dist/langflower-config/resolve-wired-tool-options.test.js +30 -0
  209. package/vendor/shared/dist/langflower-ws-waits.d.ts +9 -14
  210. package/vendor/shared/dist/langflower-ws-waits.js +14 -14
  211. package/vendor/websocket-bridge/dist/bridge-codec.d.ts +1 -0
  212. package/vendor/websocket-bridge/dist/bridge-codec.js +11 -11
  213. package/vendor/websocket-bridge/dist/bridge-frame.js +1 -1
  214. package/vendor/websocket-bridge/dist/bridge-subjects.d.ts +1 -1
  215. package/vendor/websocket-bridge/dist/bridge-subjects.js +2 -2
  216. package/vendor/websocket-bridge/dist/bridge-types.d.ts +13 -1
  217. package/vendor/websocket-bridge/dist/create-client.js +3 -3
  218. package/vendor/websocket-bridge/dist/create-server.js +12 -6
  219. package/vendor/websocket-bridge/package.json +8 -0
  220. package/ui-dist/main-ECEWAM6V.js +0 -311
  221. package/ui-dist/styles-EOXODOI7.css +0 -1
@@ -0,0 +1,13 @@
1
+ import type { ToolHandle } from '@langflower/node-sdk';
2
+ import type { LangflowerSession } from '../session/langflower-session.js';
3
+ /**
4
+ * Activate current editor `tools` / `subagent-registration` outputs
5
+ * (including post-swap instances) so a later inbound peek in
6
+ * {@link getLiveWiredTools} sees live handles.
7
+ */
8
+ export declare const refreshLiveWiredToolPacks: (session: LangflowerSession) => void;
9
+ /**
10
+ * Walk inbound `tools` edges on **current** editor instances (post-swap).
11
+ * Does not emit on the LLM tools port (ADR-016 session must not reset).
12
+ */
13
+ export declare const getLiveWiredTools: (session: LangflowerSession, agentNodeId: string) => readonly ToolHandle[];
@@ -0,0 +1,83 @@
1
+ const isRecord = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
2
+ const isToolHandle = (value) => {
3
+ if (!isRecord(value)) {
4
+ return false;
5
+ }
6
+ return (typeof value.toolId === 'string' &&
7
+ typeof value.name === 'string' &&
8
+ typeof value.description === 'string' &&
9
+ typeof value.inputSchema === 'object' &&
10
+ value.inputSchema !== null &&
11
+ typeof value.invoke === 'function');
12
+ };
13
+ /** Flatten multi-wire values that may be single handles or arrays (packs). */
14
+ const flattenToolHandles = (wired) => wired.flatMap((item) => {
15
+ if (Array.isArray(item)) {
16
+ return item.filter(isToolHandle);
17
+ }
18
+ if (isToolHandle(item)) {
19
+ return [item];
20
+ }
21
+ return [];
22
+ });
23
+ const resolveToolsOutput = (outputs, portId) => {
24
+ const direct = outputs[portId];
25
+ if (direct !== undefined) {
26
+ return direct;
27
+ }
28
+ return Object.values(outputs).find((port) => port.meta.portId === portId);
29
+ };
30
+ /**
31
+ * One-shot subscribe to activate a freshly swapped instance without
32
+ * connecting into the LLM `tools` input.
33
+ */
34
+ const peekPortValue = (port) => {
35
+ let peeked;
36
+ const sub = port.value$.subscribe((value) => {
37
+ peeked = value;
38
+ });
39
+ sub.unsubscribe();
40
+ return peeked;
41
+ };
42
+ /**
43
+ * Activate current editor `tools` / `subagent-registration` outputs
44
+ * (including post-swap instances) so a later inbound peek in
45
+ * {@link getLiveWiredTools} sees live handles.
46
+ */
47
+ export const refreshLiveWiredToolPacks = (session) => {
48
+ for (const node of session.runtime.editor.getNodes()) {
49
+ for (const portId of ['tools', 'subagent-registration']) {
50
+ const port = resolveToolsOutput(node.outputs, portId);
51
+ if (port !== undefined) {
52
+ peekPortValue(port);
53
+ }
54
+ }
55
+ }
56
+ };
57
+ /**
58
+ * Walk inbound `tools` edges on **current** editor instances (post-swap).
59
+ * Does not emit on the LLM tools port (ADR-016 session must not reset).
60
+ */
61
+ export const getLiveWiredTools = (session, agentNodeId) => {
62
+ const editor = session.runtime.editor;
63
+ const inbound = editor
64
+ .getEdges()
65
+ .filter((edge) => String(edge.toNodeId) === agentNodeId &&
66
+ edge.toPort[0] === 'tools');
67
+ const packs = [];
68
+ for (const edge of inbound) {
69
+ const source = editor.getNode(edge.fromNodeId);
70
+ if (source === false) {
71
+ continue;
72
+ }
73
+ const port = resolveToolsOutput(source.outputs, edge.fromPort[0]);
74
+ if (port === undefined) {
75
+ continue;
76
+ }
77
+ const peeked = peekPortValue(port);
78
+ if (peeked !== undefined) {
79
+ packs.push(peeked);
80
+ }
81
+ }
82
+ return flattenToolHandles(packs);
83
+ };
@@ -0,0 +1,7 @@
1
+ import type { LangflowerBusRequest } from '@langflower/common-nodes/ai/run-host-services';
2
+ import type { LangflowerBridge } from './langflower-bridge.types.js';
3
+ /**
4
+ * Wait-then-emit RPC (MCP `emitAction` shape) over the **server** bridge.
5
+ * Does not import `@langflower/mcp`.
6
+ */
7
+ export declare const createLangflowerToolsRpc: (bridge: LangflowerBridge) => LangflowerBusRequest;
@@ -0,0 +1,29 @@
1
+ import { waitBusEvent } from '@langflower/shared/langflower-ws-waits';
2
+ /**
3
+ * In-process bus intents this pack may emit. Compile only for now.
4
+ * Later: `editor.addNode.requested`, `editor.removeNode.requested`,
5
+ * `editor.addEdge.requested`, `editor.removeEdge.requested`.
6
+ */
7
+ const LANGFLOWER_TOOLS_INTENTS = ['customPalette.update.requested'];
8
+ const isAllowedIntent = (intent) => LANGFLOWER_TOOLS_INTENTS.includes(intent);
9
+ /**
10
+ * Wait-then-emit RPC (MCP `emitAction` shape) over the **server** bridge.
11
+ * Does not import `@langflower/mcp`.
12
+ */
13
+ export const createLangflowerToolsRpc = (bridge) => {
14
+ return async (intent, payload) => {
15
+ if (!isAllowedIntent(intent)) {
16
+ throw new Error(`Langflower Tools does not allow intent: ${intent}`);
17
+ }
18
+ const resultPromise = waitBusEvent(bridge['customPalette.snapshot'], {
19
+ predicate: (snapshot) => snapshot.status !== 'compiling',
20
+ });
21
+ const body = payload !== null &&
22
+ typeof payload === 'object' &&
23
+ !Array.isArray(payload)
24
+ ? payload
25
+ : {};
26
+ bridge.injectInbound(intent, body, 'langflower-tools');
27
+ return resultPromise;
28
+ };
29
+ };
@@ -1,4 +1,5 @@
1
1
  import type { Subscription } from 'rxjs';
2
2
  import type { ServerContext } from '../server-context.js';
3
+ import type { LangflowerSession } from '../session/langflower-session.js';
3
4
  import type { LangflowerBridge } from './langflower-bridge.types.js';
4
- export declare const wireCustomPaletteHandlers: (bridge: LangflowerBridge, context: ServerContext) => Subscription;
5
+ export declare const wireCustomPaletteHandlers: (bridge: LangflowerBridge, context: ServerContext, session: LangflowerSession) => Subscription;
@@ -1,10 +1,8 @@
1
- import { bridgeEmit } from './bridge-outbound.js';
1
+ import { compileAndHotSwapCustomNodes } from '../palette/compile-and-hot-swap-custom-nodes.js';
2
2
  import { isInboundEvent } from './inbound-guards.js';
3
- export const wireCustomPaletteHandlers = (bridge, context) => bridge['customPalette.update.requested'].subscribe(async (raw) => {
3
+ export const wireCustomPaletteHandlers = (bridge, context, session) => bridge['customPalette.update.requested'].subscribe(async (raw) => {
4
4
  if (!isInboundEvent(raw)) {
5
5
  return;
6
6
  }
7
- bridgeEmit(bridge, 'customPalette.snapshot', context.customPaletteService.compilingSnapshot());
8
- const snapshot = await context.customPaletteService.update(context.projectDir);
9
- bridgeEmit(bridge, 'customPalette.snapshot', snapshot);
7
+ await compileAndHotSwapCustomNodes(session, context, bridge);
10
8
  });
@@ -1,8 +1,12 @@
1
+ import { isPortTelemetry, isRuntimeDone, } from '@langflower/runtime';
1
2
  import { buildWorkflowFingerprint, } from '@langflower/shared/langflower.js';
2
3
  import { Subscription } from 'rxjs';
3
4
  import { listResumableCheckpoints } from '../checkpoint/list-resumable-checkpoints.js';
4
5
  import { resolveCheckpointBoundary } from '../checkpoint/resolve-checkpoint-boundary.js';
6
+ import { resetSessionExecutionFeed } from '../session/reset-session-execution-feed.js';
5
7
  import { buildContextSeeds, applyObservableContextSeeds, } from './build-execution-context.js';
8
+ import { getLiveWiredTools } from './get-live-wired-tools.js';
9
+ import { createLangflowerToolsRpc } from './langflower-tools-rpc.js';
6
10
  import { bridgeEmit, clientEmit } from './bridge-outbound.js';
7
11
  import { findClientById } from './client-index.js';
8
12
  import { isInboundEvent } from './inbound-guards.js';
@@ -35,11 +39,15 @@ export const wireRunnerHandlers = (bridge, context, session, checkpoints) => {
35
39
  const emitPermissionAsk = (payload) => {
36
40
  bridgeEmit(bridge, 'runner.permission.ask', payload);
37
41
  };
42
+ const requestLangflowerBus = createLangflowerToolsRpc(bridge);
43
+ const liveWiredTools = (agentNodeId) => getLiveWiredTools(session, agentNodeId);
38
44
  subscription.add(session.runtime.runner.events$.subscribe((event) => {
39
- const boundary = event.kind === 'output-emitted' &&
40
- typeof event.portId === 'string' &&
45
+ const [, nodeId, portId] = isPortTelemetry(event) ? event : [];
46
+ const boundary = isPortTelemetry(event) &&
47
+ event[0] === 'out' &&
48
+ typeof portId === 'string' &&
41
49
  session.activeWorkflow !== null
42
- ? resolveCheckpointBoundary(session.activeWorkflow, String(event.nodeId), event.portId)
50
+ ? resolveCheckpointBoundary(session.activeWorkflow, String(nodeId), portId)
43
51
  : undefined;
44
52
  const shouldPersist = checkpoints.observe(event, boundary === undefined
45
53
  ? undefined
@@ -57,7 +65,7 @@ export const wireRunnerHandlers = (bridge, context, session, checkpoints) => {
57
65
  })
58
66
  .catch(() => undefined);
59
67
  }
60
- if (event.kind === 'done') {
68
+ if (isRuntimeDone(event)) {
61
69
  void checkpoints
62
70
  .markCompleted()
63
71
  .then(async (summary) => {
@@ -83,7 +91,7 @@ export const wireRunnerHandlers = (bridge, context, session, checkpoints) => {
83
91
  crypto.randomUUID());
84
92
  // Lock before async seed build so workflow load cannot race in.
85
93
  session.runnerStatus = 'running';
86
- const contextSeeds = applyObservableContextSeeds(session, await buildContextSeeds(session, context, resolvedRunId, emitPermissionAsk));
94
+ const contextSeeds = applyObservableContextSeeds(session, await buildContextSeeds(session, context, resolvedRunId, emitPermissionAsk, requestLangflowerBus, liveWiredTools));
87
95
  const initialPayload = mergeSeeds(contextSeeds, clientInitialPayload);
88
96
  if (session.activeWorkflow !== null) {
89
97
  checkpoints.beginRun(resolvedRunId, session.activeWorkflow);
@@ -111,7 +119,7 @@ export const wireRunnerHandlers = (bridge, context, session, checkpoints) => {
111
119
  const resolvedRunId = (clientRunId ??
112
120
  crypto.randomUUID());
113
121
  session.runnerStatus = 'running';
114
- const contextSeeds = applyObservableContextSeeds(session, await buildContextSeeds(session, context, resolvedRunId, emitPermissionAsk));
122
+ const contextSeeds = applyObservableContextSeeds(session, await buildContextSeeds(session, context, resolvedRunId, emitPermissionAsk, requestLangflowerBus, liveWiredTools));
115
123
  const initialPayload = mergeSeeds(contextSeeds, clientInitialPayload);
116
124
  if (session.activeWorkflow !== null) {
117
125
  checkpoints.beginRun(resolvedRunId, session.activeWorkflow);
@@ -196,7 +204,7 @@ export const wireRunnerHandlers = (bridge, context, session, checkpoints) => {
196
204
  return;
197
205
  }
198
206
  session.runnerStatus = 'running';
199
- const contextSeeds = applyObservableContextSeeds(session, await buildContextSeeds(session, context, checkpoint.runId, emitPermissionAsk));
207
+ const contextSeeds = applyObservableContextSeeds(session, await buildContextSeeds(session, context, checkpoint.runId, emitPermissionAsk, requestLangflowerBus, liveWiredTools));
200
208
  checkpoints.hydrateFromCheckpoint(checkpoint, workflow);
201
209
  const resumeOptions = checkpoints.resumeOptionsFromCheckpoint(checkpoint);
202
210
  const resumed = session.runtime.runner.resume({
@@ -249,7 +257,7 @@ export const wireRunnerHandlers = (bridge, context, session, checkpoints) => {
249
257
  if (wasIdle) {
250
258
  const resolvedRunId = crypto.randomUUID();
251
259
  session.runnerStatus = 'running';
252
- const contextSeeds = applyObservableContextSeeds(session, await buildContextSeeds(session, context, resolvedRunId, emitPermissionAsk));
260
+ const contextSeeds = applyObservableContextSeeds(session, await buildContextSeeds(session, context, resolvedRunId, emitPermissionAsk, requestLangflowerBus, liveWiredTools));
253
261
  if (session.activeWorkflow !== null) {
254
262
  checkpoints.beginRun(resolvedRunId, session.activeWorkflow);
255
263
  }
@@ -286,8 +294,7 @@ export const wireRunnerHandlers = (bridge, context, session, checkpoints) => {
286
294
  if (session.runnerStatus === 'running') {
287
295
  return;
288
296
  }
289
- session.runtime.runner.clearEventLog();
290
- session.runId = undefined;
297
+ resetSessionExecutionFeed(session);
291
298
  // Re-broadcast the (now empty) feed to every tab.
292
299
  bridgeEmit(bridge, 'executionFeed.snapshot', session.buildExecutionFeed());
293
300
  }));
@@ -1,6 +1,7 @@
1
1
  import { Subscription } from 'rxjs';
2
2
  import { listResumableCheckpoints } from '../checkpoint/list-resumable-checkpoints.js';
3
3
  import { WorkflowCheckpointStore } from '../checkpoint/workflow-checkpoint-store.js';
4
+ import { resetSessionExecutionFeed } from '../session/reset-session-execution-feed.js';
4
5
  import { buildSaveCurrentPayload } from '../workflow/build-save-current-payload.js';
5
6
  import { copyWorkflowToSession } from '../workflow/copy-workflow-to-session.js';
6
7
  import { createEmptyWorkflowInSession } from '../workflow/create-empty-workflow-in-session.js';
@@ -10,11 +11,17 @@ import { bridgeEmit, clientEmit } from './bridge-outbound.js';
10
11
  import { findClientById } from './client-index.js';
11
12
  import { isInboundEvent } from './inbound-guards.js';
12
13
  /**
13
- * Composer: clear selection → current snapshot → optional catalog sync.
14
- * Sibling steps — handlers do not call these in nested order themselves.
14
+ * Composer: optional feed reset → clear selection → current snapshot →
15
+ * optional catalog sync. Sibling steps — handlers do not nest these.
15
16
  */
16
17
  const syncAfterWorkflowMutation = async (bridge, context, session, client, options) => {
17
- // 1. Clear selection (and notify tabs if it was set)
18
+ // 1. Document switch: empty feed before the new catalog so the UI never
19
+ // paints the previous run under the next workflow's nodes.
20
+ if (options.resetFeed === true) {
21
+ resetSessionExecutionFeed(session);
22
+ bridgeEmit(bridge, 'executionFeed.snapshot', session.buildExecutionFeed());
23
+ }
24
+ // 2. Clear selection (and notify tabs if it was set)
18
25
  if (options.clearSelection) {
19
26
  const hadSelection = session.selectedNodeId !== null;
20
27
  session.selectedNodeId = null;
@@ -22,19 +29,19 @@ const syncAfterWorkflowMutation = async (bridge, context, session, client, optio
22
29
  bridgeEmit(bridge, 'editor.nodeSelected', { node: null });
23
30
  }
24
31
  }
25
- // 2. Authoritative current-workflow slice (session-shared — fan out)
32
+ // 3. Authoritative current-workflow slice (session-shared — fan out)
26
33
  bridgeEmit(bridge, 'workflow.current.snapshot', {
27
34
  activeWorkflow: session.activeWorkflow,
28
35
  currentStatus: { status: session.currentStatus },
29
36
  });
30
- // 3. Resumable checkpoints for the active workflow (explicit boundaries)
37
+ // 4. Resumable checkpoints for the active workflow (explicit boundaries)
31
38
  const workflowId = session.activeWorkflowId ?? null;
32
39
  const checkpoints = await listResumableCheckpoints(new WorkflowCheckpointStore(context.projectDir), workflowId, session.activeWorkflow);
33
40
  bridgeEmit(bridge, 'runner.checkpoints.snapshot', {
34
41
  workflowId,
35
42
  checkpoints,
36
43
  });
37
- // 4. Catalog sync
44
+ // 5. Catalog sync
38
45
  if (options.catalog.kind === 'broadcast') {
39
46
  const workflows = await context.workflowService.list();
40
47
  bridgeEmit(bridge, 'workflow.list.snapshot', { workflows });
@@ -67,11 +74,13 @@ export const wireWorkflowHandlers = (bridge, context, session) => {
67
74
  return;
68
75
  }
69
76
  let clearSelection = false;
77
+ let resetFeed = false;
70
78
  if (!session.isGraphLocked()) {
71
79
  // 1. Load document into session
72
80
  const loaded = await loadWorkflowIntoSession(session, context.workflowService, context.projectDir, raw.payload.workflowId, context.resolveDefinition);
73
81
  // 2. Persist current-workflow id when load succeeded
74
82
  if (loaded.ok) {
83
+ resetFeed = true;
75
84
  await context.langflowerConfigService.setCurrentWorkflowId(raw.payload.workflowId);
76
85
  if (loaded.repaired) {
77
86
  const nodeCount = loaded.droppedNodeIds.length;
@@ -101,7 +110,7 @@ export const wireWorkflowHandlers = (bridge, context, session) => {
101
110
  });
102
111
  }
103
112
  // 3. Sync outbound
104
- await syncAfterWorkflowMutation(bridge, context, session, connected, { clearSelection, catalog: { kind: 'none' } });
113
+ await syncAfterWorkflowMutation(bridge, context, session, connected, { clearSelection, catalog: { kind: 'none' }, resetFeed });
105
114
  }));
106
115
  subscription.add(bridge['workflow.saveCurrent.requested'].subscribe(async (raw) => {
107
116
  if (!isInboundEvent(raw)) {
@@ -163,11 +172,13 @@ export const wireWorkflowHandlers = (bridge, context, session) => {
163
172
  return;
164
173
  }
165
174
  let clearSelection = false;
175
+ let resetFeed = false;
166
176
  if (!session.isGraphLocked()) {
167
- await createEmptyWorkflowInSession(session, context.workflowService, context.projectDir, context.resolveDefinition);
177
+ const created = await createEmptyWorkflowInSession(session, context.workflowService, context.projectDir, context.resolveDefinition);
168
178
  clearSelection = true;
179
+ resetFeed = created;
169
180
  }
170
- await syncAfterWorkflowMutation(bridge, context, session, connected, { clearSelection, catalog: { kind: 'none' } });
181
+ await syncAfterWorkflowMutation(bridge, context, session, connected, { clearSelection, catalog: { kind: 'none' }, resetFeed });
171
182
  }));
172
183
  subscription.add(bridge['workflow.copy.requested'].subscribe(async (raw) => {
173
184
  if (!isInboundEvent(raw)) {
@@ -179,16 +190,19 @@ export const wireWorkflowHandlers = (bridge, context, session) => {
179
190
  }
180
191
  let catalogChanged = false;
181
192
  let clearSelection = false;
193
+ let resetFeed = false;
182
194
  if (!session.isGraphLocked()) {
183
195
  const copied = await copyWorkflowToSession(session, context.workflowService, context.projectDir, raw.payload.workflowId, context.resolveDefinition);
184
196
  if (copied && session.activeWorkflowId !== undefined) {
185
197
  catalogChanged = true;
198
+ resetFeed = true;
186
199
  await context.langflowerConfigService.setCurrentWorkflowId(session.activeWorkflowId);
187
200
  }
188
201
  clearSelection = true;
189
202
  }
190
203
  await syncAfterWorkflowMutation(bridge, context, session, connected, {
191
204
  clearSelection,
205
+ resetFeed,
192
206
  catalog: catalogChanged
193
207
  ? { kind: 'broadcast' }
194
208
  : { kind: 'none' },
@@ -1,4 +1,4 @@
1
- import type { NodeId, RunId, RuntimeRunnerEvent } from '@langflower/runtime';
1
+ import { type NodeId, type RunId, type RuntimeRunnerEvent } from '@langflower/runtime';
2
2
  import { type WorkflowCheckpoint, type WorkflowCheckpointSummary, type WorkflowLoadedPayload } from '@langflower/shared/langflower.js';
3
3
  import { WorkflowCheckpointStore } from './workflow-checkpoint-store.js';
4
4
  /**
@@ -1,3 +1,4 @@
1
+ import { isPortTelemetry, } from '@langflower/runtime';
1
2
  import { buildWorkflowFingerprint, toCheckpointJsonValue, } from '@langflower/shared/langflower.js';
2
3
  import { summarizeCheckpoint, WorkflowCheckpointStore, } from './workflow-checkpoint-store.js';
3
4
  /**
@@ -56,27 +57,27 @@ export class RunCheckpointSession {
56
57
  * that should trigger an immediate persist.
57
58
  */
58
59
  observe(event, boundary) {
59
- if (this.active === undefined || event.kind !== 'output-emitted') {
60
+ if (this.active === undefined ||
61
+ !isPortTelemetry(event) ||
62
+ event[0] !== 'out') {
60
63
  return false;
61
64
  }
62
- if (String(event.runId) !== this.active.runId) {
65
+ const [, nodeIdRaw, portId, response] = event;
66
+ if (!('value' in response) || typeof portId !== 'string') {
63
67
  return false;
64
68
  }
65
- if (event.state !== 'value' || typeof event.portId !== 'string') {
66
- return false;
67
- }
68
- const jsonValue = toCheckpointJsonValue(event.value);
69
+ const jsonValue = toCheckpointJsonValue(response.value);
69
70
  if (jsonValue === undefined) {
70
- this.unsupportedValue = `${event.nodeId}.${event.portId}`;
71
+ this.unsupportedValue = `${String(nodeIdRaw)}.${portId}`;
71
72
  return false;
72
73
  }
73
- const nodeId = String(event.nodeId);
74
+ const nodeId = String(nodeIdRaw);
74
75
  let ports = this.active.outputSnapshots.get(nodeId);
75
76
  if (ports === undefined) {
76
77
  ports = new Map();
77
78
  this.active.outputSnapshots.set(nodeId, ports);
78
79
  }
79
- ports.set(event.portId, {
80
+ ports.set(portId, {
80
81
  state: 'value',
81
82
  value: jsonValue,
82
83
  });
@@ -0,0 +1,10 @@
1
+ import type { CustomPaletteSnapshotPayload } from '@langflower/shared/langflower.js';
2
+ import type { LangflowerBridge } from '../bridge/langflower-bridge.types.js';
3
+ import type { ServerContext } from '../server-context.js';
4
+ import type { LangflowerSession } from '../session/langflower-session.js';
5
+ /**
6
+ * One composer for Custom → Update and `compile_custom_nodes` (via bus
7
+ * intent `customPalette.update.requested`): compile, registry, live swap,
8
+ * then palette snapshot (and deleteEdges if ports vanished).
9
+ */
10
+ export declare const compileAndHotSwapCustomNodes: (session: LangflowerSession, context: ServerContext, bridge: LangflowerBridge) => Promise<CustomPaletteSnapshotPayload>;
@@ -0,0 +1,23 @@
1
+ import { swapCustomNodesInEditor } from '../workflow/apply-editor-mutation.js';
2
+ import { refreshLiveWiredToolPacks } from '../bridge/get-live-wired-tools.js';
3
+ import { bridgeEmit } from '../bridge/bridge-outbound.js';
4
+ /**
5
+ * One composer for Custom → Update and `compile_custom_nodes` (via bus
6
+ * intent `customPalette.update.requested`): compile, registry, live swap,
7
+ * then palette snapshot (and deleteEdges if ports vanished).
8
+ */
9
+ export const compileAndHotSwapCustomNodes = async (session, context, bridge) => {
10
+ bridgeEmit(bridge, 'customPalette.snapshot', context.customPaletteService.compilingSnapshot());
11
+ const snapshot = await context.customPaletteService.update(context.projectDir);
12
+ const customTypes = new Set(snapshot.nodes.map((node) => node.type));
13
+ const droppedEdges = swapCustomNodesInEditor(session, context.projectDir, context.resolveDefinition, customTypes);
14
+ refreshLiveWiredToolPacks(session);
15
+ if (droppedEdges.length > 0) {
16
+ bridgeEmit(bridge, 'editor.deleteEdges', droppedEdges);
17
+ bridgeEmit(bridge, 'workflow.currentStatus.snapshot', {
18
+ status: session.currentStatus,
19
+ });
20
+ }
21
+ bridgeEmit(bridge, 'customPalette.snapshot', snapshot);
22
+ return snapshot;
23
+ };
@@ -0,0 +1,6 @@
1
+ import type { LangflowerSession } from './langflower-session.js';
2
+ /**
3
+ * Drop the session run log so `buildExecutionFeed()` returns null.
4
+ * Call only when the runner is idle (document switch or user Clear).
5
+ */
6
+ export declare const resetSessionExecutionFeed: (session: LangflowerSession) => void;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Drop the session run log so `buildExecutionFeed()` returns null.
3
+ * Call only when the runner is idle (document switch or user Clear).
4
+ */
5
+ export const resetSessionExecutionFeed = (session) => {
6
+ session.runtime.runner.clearEventLog();
7
+ session.runId = undefined;
8
+ };
@@ -44,6 +44,11 @@ export declare const applyEditorRemoveEdge: (session: LangflowerSession, edgeId:
44
44
  * Composer: snapshot → runtime remove → sync session topology from editor.
45
45
  */
46
46
  export declare const applyEditorRemoveNode: (session: LangflowerSession, nodeId: NodeId) => WorkflowNodePersisted[];
47
+ /**
48
+ * Replace live editor instances whose persisted `type` is in `customTypes`.
49
+ * Types missing from the set keep the old instance. Allowed while locked.
50
+ */
51
+ export declare const swapCustomNodesInEditor: (session: LangflowerSession, projectDir: string, resolveDefinition: ResolveNodeDefinition, customTypes: ReadonlySet<string>) => readonly RuntimeEdge[];
47
52
  export type BindWorkflowResult = {
48
53
  readonly ok: true;
49
54
  readonly droppedNodeIds: readonly string[];
@@ -447,6 +447,38 @@ export const applyEditorRemoveNode = (session, nodeId) => {
447
447
  syncActiveWorkflowTopologyFromEditor(session);
448
448
  return nodeSnapshot !== undefined ? [nodeSnapshot] : [];
449
449
  };
450
+ /**
451
+ * Replace live editor instances whose persisted `type` is in `customTypes`.
452
+ * Types missing from the set keep the old instance. Allowed while locked.
453
+ */
454
+ export const swapCustomNodesInEditor = (session, projectDir, resolveDefinition, customTypes) => {
455
+ const active = session.activeWorkflow;
456
+ if (active === null) {
457
+ return [];
458
+ }
459
+ const persistedById = new Map(active.graph.nodes.map((node) => [node.id, node]));
460
+ const droppedEdges = [];
461
+ for (const runtimeNode of session.runtime.editor.getNodes()) {
462
+ const persisted = persistedById.get(runtimeNode.nodeId);
463
+ if (persisted === undefined || !customTypes.has(persisted.type)) {
464
+ continue;
465
+ }
466
+ const next = materializeRuntimeNode(projectDir, persisted, resolveDefinition);
467
+ if (next === undefined) {
468
+ continue;
469
+ }
470
+ const { nodeId: _nodeId, ...rest } = next;
471
+ const swapped = session.runtime.editor.swapNode(runtimeNode.nodeId, rest);
472
+ if (swapped === false) {
473
+ continue;
474
+ }
475
+ droppedEdges.push(...swapped.droppedEdges);
476
+ }
477
+ if (droppedEdges.length > 0) {
478
+ syncActiveWorkflowTopologyFromEditor(session);
479
+ }
480
+ return droppedEdges;
481
+ };
450
482
  /**
451
483
  * Load path: document → editor (activate/rename/rebind).
452
484
  * Unsupported nodes and invalid edges are skipped (graceful degradation);
@@ -27,11 +27,15 @@ contract.
27
27
 
28
28
  ## Load custom nodes
29
29
 
30
- In the editor Custom section, click **Update** (or reconnect). Langflower
31
- typechecks each pack (`tsc --noEmit` when `tsconfig.json` is present), then
32
- esbuild-bundles entries that passed. On success nodes appear under **Custom**.
33
- Failures write `COMPILATION_ERRORS.md` in the pack without dropping other
34
- successful custom nodes.
30
+ Call **`compile_custom_nodes`** (no args) — on starter, Helper and Writer
31
+ already have it because **Langflower Tools** is wired — or, in the editor
32
+ Custom section, click **Update** (or reconnect). Same composer: typecheck
33
+ each pack (`tsc --noEmit` when `tsconfig.json` is present), esbuild-bundle
34
+ entries that passed, hot-swap live custom instances, refresh **Custom**.
35
+ Stop is not required for already-placed custom types. Already-wired custom
36
+ tools can be invoked later in the **same run**. New types are not auto-placed
37
+ or auto-wired mid-run. Failures write `COMPILATION_ERRORS.md` in the pack
38
+ without dropping other successful custom nodes.
35
39
 
36
40
  ## Gaps
37
41
 
@@ -199,9 +199,10 @@ even though Langflower bundles with esbuild for load.
199
199
  2. `export default defineNode({ … })`, `defineToolRegistrations({ … })` for an
200
200
  LLM `tools` pack, **or** `defineReactiveNode({ … })` when exclusive `ok` /
201
201
  `fail` (or other silent-branch) ports are required.
202
- 3. Run `npm install` in the pack if peers or deps changed.
203
- 4. In the editor Custom section, click **Update** (or reconnect) — the node
204
- appears under **Custom**.
202
+ 3. Run `npm install` in the pack if author deps changed.
203
+ 4. Call **`compile_custom_nodes`** (starter Helper / Writer) or Custom →
204
+ **Update**. The type appears under **Custom**. Already-placed instances
205
+ hot-swap (no Stop). Place **new** types on the canvas yourself.
205
206
 
206
207
  ## Second shareable pack
207
208
 
@@ -5,7 +5,7 @@
5
5
  "type": "module",
6
6
  "peerDependencies": {
7
7
  "@langflower/node-sdk": "0.1.0",
8
- "@rx-evo/stateful-observable": "^0.5.2",
8
+ "@rx-evo/stateful-observable": "^0.6.0",
9
9
  "rxjs": "^7.8.1"
10
10
  }
11
11
  }