@kiberon-labs/behave-graph-flow 2.0.0 → 3.0.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 (251) hide show
  1. package/.storybook/manager.ts +6 -0
  2. package/.storybook/preview.ts +49 -1
  3. package/.storybook/styles.css +9 -3
  4. package/.turbo/turbo-build.log +1 -1
  5. package/CHANGELOG.md +368 -0
  6. package/dist/AnyControlImpl-Ds-CShIB.js +20 -0
  7. package/dist/AnyControlImpl-Ds-CShIB.js.map +1 -0
  8. package/dist/DocumentationBrowserPanelImpl-deZNzFX8.js +166 -0
  9. package/dist/DocumentationBrowserPanelImpl-deZNzFX8.js.map +1 -0
  10. package/dist/index.css +36 -33
  11. package/dist/index.css.map +1 -1
  12. package/dist/index.d.ts +1865 -550
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +14357 -11221
  15. package/dist/index.js.map +1 -1
  16. package/dist/noteImpl-KkrrWgJd.js +242 -0
  17. package/dist/noteImpl-KkrrWgJd.js.map +1 -0
  18. package/dist/styles.module-CvmpDkZj.css +3 -0
  19. package/dist/styles.module-CvmpDkZj.css.map +1 -0
  20. package/dist/styles.module-DZxg8aW9.js +271 -0
  21. package/dist/styles.module-DZxg8aW9.js.map +1 -0
  22. package/dist/useChangeNodeData-ChQGK7AI.js +23 -0
  23. package/dist/useChangeNodeData-ChQGK7AI.js.map +1 -0
  24. package/docs/protocol.md +43 -20
  25. package/package.json +5 -9
  26. package/src/components/FloatingToolbar/index.module.css +5 -13
  27. package/src/components/FloatingToolbar/index.tsx +9 -9
  28. package/src/components/Flow.tsx +34 -23
  29. package/src/components/contextMenus/DynamicContextMenu.tsx +85 -0
  30. package/src/components/contextMenus/NodePicker.module.css +13 -13
  31. package/src/components/contextMenus/edge.tsx +9 -95
  32. package/src/components/contextMenus/node.tsx +9 -149
  33. package/src/components/contextMenus/selection.tsx +5 -71
  34. package/src/components/controls/any/AnyControlImpl.tsx +14 -0
  35. package/src/components/controls/any/index.tsx +13 -2
  36. package/src/components/edges/index.tsx +75 -69
  37. package/src/components/layoutController/index.module.css +3 -0
  38. package/src/components/layoutController/index.tsx +24 -1
  39. package/src/components/layoutController/utils.ts +46 -3
  40. package/src/components/menubar/defaults.tsx +55 -19
  41. package/src/components/menubar/menuItem.module.css +18 -3
  42. package/src/components/menubar/menuItem.tsx +34 -1
  43. package/src/components/nodes/behave/NodeContainer.module.css +26 -25
  44. package/src/components/nodes/group/index.tsx +3 -3
  45. package/src/components/nodes/wrapper/styles.module.css +6 -32
  46. package/src/components/panels/alignment/index.module.css +0 -10
  47. package/src/components/panels/alignment/index.tsx +4 -4
  48. package/src/components/panels/base/styles.module.css +2 -2
  49. package/src/components/panels/common/PanelHeader.module.css +24 -0
  50. package/src/components/panels/common/PanelHeader.tsx +22 -0
  51. package/src/components/panels/common/SectionTitle.module.css +13 -0
  52. package/src/components/panels/common/SectionTitle.tsx +10 -0
  53. package/src/components/panels/events/EditEventPanel.tsx +14 -5
  54. package/src/components/panels/events/ManageEventsPanel.tsx +11 -8
  55. package/src/components/panels/events/styles.module.css +6 -64
  56. package/src/components/panels/graphProperties/index.tsx +125 -0
  57. package/src/components/panels/history/index.tsx +2 -2
  58. package/src/components/panels/history/styles.module.css +0 -9
  59. package/src/components/panels/keymaps/index.module.css +3 -13
  60. package/src/components/panels/keymaps/index.tsx +1 -2
  61. package/src/components/panels/layers/index.tsx +20 -15
  62. package/src/components/panels/layers/styles.module.css +9 -12
  63. package/src/components/panels/legend/index.tsx +1 -1
  64. package/src/components/panels/logs/index.module.css +25 -19
  65. package/src/components/panels/logs/index.tsx +7 -7
  66. package/src/components/panels/nodeInputs/InputsGroup.tsx +1 -0
  67. package/src/components/panels/nodeInputs/NodeSettings.tsx +2 -2
  68. package/src/components/panels/nodeInputs/NodeTitleEditor.tsx +1 -1
  69. package/src/components/panels/nodeInputs/OutputsGroup.tsx +2 -12
  70. package/src/components/panels/nodeInputs/index.module.css +99 -75
  71. package/src/components/panels/nodeInputs/index.tsx +21 -11
  72. package/src/components/panels/nodeInputs/useNodeHandlers.ts +2 -2
  73. package/src/components/panels/nodeInputs/useNodeInputsData.ts +23 -43
  74. package/src/components/panels/nodePicker/index.tsx +8 -8
  75. package/src/components/panels/panel/index.module.css +7 -7
  76. package/src/components/panels/search/index.module.css +0 -50
  77. package/src/components/panels/search/index.tsx +2 -2
  78. package/src/components/panels/systemSettings/ConversionsSettings.tsx +203 -0
  79. package/src/components/panels/systemSettings/index.tsx +221 -176
  80. package/src/components/panels/systemSettings/styles.module.css +135 -8
  81. package/src/components/panels/traces/GridLines.tsx +1 -1
  82. package/src/components/panels/traces/TimeGrid.tsx +3 -3
  83. package/src/components/panels/traces/TraceLane.tsx +1 -1
  84. package/src/components/panels/traces/index.module.css +1 -8
  85. package/src/components/panels/traces/index.tsx +8 -4
  86. package/src/components/panels/traces/useDerivedSpans.ts +241 -146
  87. package/src/components/panels/traces/utils.ts +8 -0
  88. package/src/components/panels/variables/CreateVariableScreen.tsx +3 -3
  89. package/src/components/panels/variables/ManageVariablesScreen.tsx +12 -9
  90. package/src/components/panels/variables/index.tsx +2 -2
  91. package/src/components/panels/variables/styles.module.css +4 -91
  92. package/src/components/primitives/icon.module.css +4 -4
  93. package/src/components/sockets/input/index.tsx +9 -2
  94. package/src/components/sockets/input/styles.module.css +2 -3
  95. package/src/components/sockets/output/index.tsx +10 -3
  96. package/src/components/sockets/output/styles.module.css +1 -6
  97. package/src/css/notes.css +135 -0
  98. package/src/css/prosemirror.css +3 -3
  99. package/src/css/rc-dock.css +143 -43
  100. package/src/css/rc-menu.css +56 -55
  101. package/src/css/themes/kiberon.css +127 -0
  102. package/src/css/vars.css +197 -13
  103. package/src/css/vscode-elements.css +124 -0
  104. package/src/generators/CallSubgraphGenerator.tsx +136 -0
  105. package/src/generators/CustomEventOnTriggeredGenerator.tsx +2 -2
  106. package/src/generators/GraphBoundaryGenerator.module.css +32 -0
  107. package/src/generators/GraphBoundaryGenerator.tsx +193 -0
  108. package/src/generators/SequenceGenerator.tsx +2 -2
  109. package/src/generators/SwitchOnIntegerGenerator.tsx +2 -2
  110. package/src/generators/SwitchOnStringGenerator.tsx +2 -2
  111. package/src/generators/callSubgraphSync.ts +126 -0
  112. package/src/generators/registerDefaultGenerators.ts +21 -0
  113. package/src/generators/registerDefaults.ts +26 -0
  114. package/src/hooks/useBehaveGraphFlow.ts +2 -2
  115. package/src/hooks/useFlowHandlers.ts +47 -9
  116. package/src/hooks/useWasdPan.ts +26 -4
  117. package/src/index.css +4 -16
  118. package/src/index.ts +17 -0
  119. package/src/manifest/contributionRegistry.ts +93 -0
  120. package/src/manifest/index.ts +4 -0
  121. package/src/manifest/loadManifest.ts +82 -0
  122. package/src/manifest/manifestPlugin.ts +29 -0
  123. package/src/manifest/passthroughValueType.ts +40 -0
  124. package/src/plugin/alignment/index.ts +22 -12
  125. package/src/plugin/autosave/controller.ts +366 -0
  126. package/src/plugin/autosave/index.tsx +114 -0
  127. package/src/plugin/autosave/panel/BackupPanel.tsx +141 -0
  128. package/src/plugin/autosave/panel/index.tsx +1 -0
  129. package/src/plugin/autosave/panel/styles.module.css +56 -0
  130. package/src/plugin/autosave/settings.ts +65 -0
  131. package/src/plugin/autosave/storage.ts +147 -0
  132. package/src/plugin/docs/index.tsx +2 -4
  133. package/src/plugin/docs/panel/DocumentationBrowserPanelImpl.tsx +200 -0
  134. package/src/plugin/docs/panel/index.tsx +15 -194
  135. package/src/plugin/docs/panel/styles.module.css +8 -8
  136. package/src/plugin/graphrunner/actions.ts +258 -185
  137. package/src/plugin/graphrunner/buttons.tsx +34 -26
  138. package/src/plugin/graphrunner/client.ts +4 -1
  139. package/src/plugin/graphrunner/index.tsx +29 -100
  140. package/src/plugin/graphrunner/panel.tsx +2 -2
  141. package/src/plugin/graphrunner/runController.ts +283 -0
  142. package/src/plugin/graphrunner/runner.ts +21 -192
  143. package/src/plugin/graphrunner/store.ts +14 -24
  144. package/src/plugin/graphrunner/styles.module.css +17 -57
  145. package/src/plugin/graphrunner/transport.ts +26 -0
  146. package/src/plugin/graphrunner/types.ts +21 -0
  147. package/src/plugin/graphrunner-local/execution-utils.ts +260 -80
  148. package/src/plugin/graphrunner-local/index.tsx +8 -2
  149. package/src/plugin/graphrunner-local/panel.tsx +131 -175
  150. package/src/plugin/graphrunner-local/styles.module.css +57 -76
  151. package/src/plugin/graphrunner-local/transport.ts +151 -184
  152. package/src/plugin/graphrunner-webworker/graph-executor.worker.ts +2 -0
  153. package/src/plugin/graphrunner-webworker/index.tsx +4 -10
  154. package/src/plugin/graphrunner-webworker/store.ts +9 -0
  155. package/src/plugin/kitchen-sink/index.ts +38 -0
  156. package/src/{layout/dagre.tsx → plugin/layout/dagre.ts} +17 -5
  157. package/src/{layout → plugin/layout}/elk.ts +22 -6
  158. package/src/plugin/layout/index.ts +80 -0
  159. package/src/plugin/notes/FormatToolbar.tsx +200 -0
  160. package/src/plugin/notes/index.tsx +191 -0
  161. package/src/plugin/notes/nodeActions.ts +100 -0
  162. package/src/plugin/notes/note.tsx +20 -0
  163. package/src/plugin/notes/noteImpl.tsx +89 -0
  164. package/src/plugin/realtime/realtimeRunner.ts +58 -4
  165. package/src/specifics/CustomEventOnTriggeredSpecific.tsx +2 -2
  166. package/src/specifics/CustomEventTriggerSpecific.tsx +2 -2
  167. package/src/specifics/VariableGetSpecific.tsx +2 -2
  168. package/src/specifics/VariableSetSpecific.tsx +2 -2
  169. package/src/store/actions.tsx +5 -5
  170. package/src/store/commands.ts +278 -0
  171. package/src/store/contextMenu.ts +192 -0
  172. package/src/store/conversions.ts +47 -0
  173. package/src/store/flow.tsx +23 -38
  174. package/src/store/graphMeta.ts +39 -0
  175. package/src/store/hotKeys.tsx +301 -260
  176. package/src/store/layers.ts +3 -3
  177. package/src/store/registry.ts +12 -4
  178. package/src/store/selection.ts +3 -3
  179. package/src/store/settings.ts +82 -82
  180. package/src/store/settingsSchema.ts +210 -0
  181. package/src/store/tabs.ts +5 -1
  182. package/src/store/traces.ts +3 -3
  183. package/src/system/graph.ts +11 -14
  184. package/src/system/graphSession.ts +172 -0
  185. package/src/system/index.ts +3 -0
  186. package/src/system/notifications.ts +13 -0
  187. package/src/system/persistence.ts +82 -0
  188. package/src/system/plugin.ts +28 -0
  189. package/src/system/provider.tsx +64 -0
  190. package/src/system/system.ts +518 -88
  191. package/src/system/tabLoader.tsx +70 -32
  192. package/src/system/undoRedo.ts +1 -1
  193. package/src/transformers/Uigraph.ts +5 -4
  194. package/src/transformers/contract.ts +87 -0
  195. package/src/transformers/flowToBehave.ts +13 -5
  196. package/src/types/nodes.ts +8 -3
  197. package/src/types.ts +2 -0
  198. package/src/util/autoConvert.ts +200 -0
  199. package/src/util/isValidConnection.ts +23 -2
  200. package/stories/defaults/defaultStoryProvider.tsx +17 -14
  201. package/stories/defaults/systemGenerator.ts +6 -1
  202. package/stories/{components/nodes/comment.stories.tsx → plugins/notes.stories.tsx} +24 -30
  203. package/tests/autoConvert.test.ts +329 -0
  204. package/tests/autosavePlugin.test.ts +204 -0
  205. package/tests/callSubgraphSync.test.ts +148 -0
  206. package/tests/commandRegistry.test.ts +137 -0
  207. package/tests/contract.test.ts +51 -0
  208. package/tests/contractSerialize.test.ts +62 -0
  209. package/tests/deriveSpans.test.ts +71 -0
  210. package/tests/flowToBehave.test.ts +2 -1
  211. package/tests/hotkeys.test.ts +79 -0
  212. package/tests/keepAliveLifecycle.test.ts +167 -0
  213. package/tests/loadManifest.test.ts +113 -0
  214. package/tests/noteMarkdown.test.ts +65 -0
  215. package/tests/notesPlugin.test.ts +162 -0
  216. package/tests/persistence.test.ts +51 -0
  217. package/tests/saveLoad.test.ts +7 -6
  218. package/tests/settings.test.ts +178 -0
  219. package/tests/traceStore.test.ts +46 -0
  220. package/tests/visual/README.md +2 -2
  221. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-conversation-chromium-win32.png +0 -0
  222. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-events-chromium-win32.png +0 -0
  223. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-history-chromium-win32.png +0 -0
  224. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-keymaps-chromium-win32.png +0 -0
  225. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-layers-chromium-win32.png +0 -0
  226. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-legend-chromium-win32.png +0 -0
  227. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-localGraphRunner-chromium-win32.png +0 -0
  228. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-logs-chromium-win32.png +0 -0
  229. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodeInputs-chromium-win32.png +0 -0
  230. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-nodePicker-chromium-win32.png +0 -0
  231. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-panel-chromium-win32.png +0 -0
  232. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-search-chromium-win32.png +0 -0
  233. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-systemSettings-chromium-win32.png +0 -0
  234. package/tests/visual/__screenshots__/panels.visual.test.tsx/panel-variables-chromium-win32.png +0 -0
  235. package/tests/visual/panels.visual.test.tsx +3 -3
  236. package/tests/wasdPan.test.ts +71 -0
  237. package/vitest.config.ts +1 -1
  238. package/vitest.visual.config.ts +7 -0
  239. package/.storybook/vscode.css +0 -814
  240. package/src/components/nodes/comment/FormatToolbar.tsx +0 -118
  241. package/src/components/nodes/comment/comment.tsx +0 -103
  242. package/src/components/nodes/comment/styles.module.css +0 -150
  243. package/src/components/panels/conversation/index.module.css +0 -151
  244. package/src/components/panels/conversation/index.tsx +0 -162
  245. package/src/components/panels/events/CustomEventsEditor.tsx +0 -384
  246. package/src/css/vscode.css +0 -13
  247. package/src/hooks/useDetachNodes.ts +0 -39
  248. package/src/plugin/graphrunner-webworker/types.ts +0 -17
  249. package/src/specifics/registerDefaultSpecifics.ts +0 -5
  250. package/src/store/chat.ts +0 -73
  251. package/src/store/graphRunnerClient.ts +0 -110
@@ -0,0 +1,193 @@
1
+ import React, { useCallback, useEffect, useMemo } from 'react';
2
+ import { useStore } from 'zustand';
3
+ import {
4
+ VscodeButton,
5
+ VscodeOption,
6
+ VscodeSingleSelect,
7
+ VscodeTextfield
8
+ } from '@vscode-elements/react-elements';
9
+ import { Trash } from 'iconoir-react';
10
+ import { useGraph } from '@/system/provider';
11
+ import type { SocketGeneratorRenderProps } from '@/store/socketGenerator';
12
+ import type { Socket } from '@/types';
13
+ import type { IBehaveNode } from '@/types/nodes.js';
14
+ import type { NodeSpecJSON } from '@kiberon-labs/behave-graph/Graphs/IO/NodeSpecJSON';
15
+ import { v4 as uuidv4 } from 'uuid';
16
+ import {
17
+ GRAPH_INPUT_TYPE,
18
+ GRAPH_OUTPUT_TYPE,
19
+ paramId,
20
+ type ContractParam
21
+ } from '@/transformers/contract';
22
+ import styles from './GraphBoundaryGenerator.module.css';
23
+
24
+ type BoundaryKind = 'input' | 'output';
25
+
26
+ export function getGraphInputGenerator() {
27
+ return {
28
+ name: `${GRAPH_INPUT_TYPE}.socketGenerator`,
29
+ check: (spec: NodeSpecJSON) => spec?.type === GRAPH_INPUT_TYPE,
30
+ render: (props: SocketGeneratorRenderProps) => (
31
+ <GraphBoundaryGenerator {...props} kind="input" />
32
+ )
33
+ };
34
+ }
35
+
36
+ export function getGraphOutputGenerator() {
37
+ return {
38
+ name: `${GRAPH_OUTPUT_TYPE}.socketGenerator`,
39
+ check: (spec: NodeSpecJSON) => spec?.type === GRAPH_OUTPUT_TYPE,
40
+ render: (props: SocketGeneratorRenderProps) => (
41
+ <GraphBoundaryGenerator {...props} kind="output" />
42
+ )
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Properties-panel editor for a subgraph boundary node's contract: add / name /
48
+ * type the inputs (`graph/input`) or outputs (`graph/output`). The configured
49
+ * params are mirrored onto the node's dynamic ports , `graph/input` exposes them
50
+ * as outputs, `graph/output` as inputs (the `flow` socket is static on the core
51
+ * node).
52
+ */
53
+ const GraphBoundaryGenerator: React.FC<
54
+ SocketGeneratorRenderProps & { kind: BoundaryKind }
55
+ > = ({ node, kind }) => {
56
+ const session = useGraph();
57
+ // Select the stable `values` object then derive , a freshly built array inside
58
+ // the selector would change identity every render and loop.
59
+ const values = useStore(session.editor.registry, (s) => s.values);
60
+ const valueTypes = useMemo(
61
+ () => Object.keys(values).filter((t) => t !== 'flow'),
62
+ [values]
63
+ );
64
+
65
+ const params: ContractParam[] = useMemo(
66
+ () =>
67
+ Array.isArray(node.data.configuration?.parameters)
68
+ ? (node.data.configuration!.parameters as ContractParam[])
69
+ : [],
70
+ [node.data]
71
+ );
72
+ const paramsKey = JSON.stringify(params);
73
+
74
+ // Mirror params onto dynamic ports. The socket identity (name/key/handle id)
75
+ // is the stable param id; the display label is the editable name.
76
+ useEffect(() => {
77
+ const sockets: Socket[] = params.map((param) => {
78
+ const id = paramId(param);
79
+ return {
80
+ name: id,
81
+ key: id,
82
+ label: param.name || id,
83
+ valueType: param.valueTypeName || 'string'
84
+ };
85
+ });
86
+ session.nodeStore.getState().setNodes((prev) =>
87
+ prev.map((n) => {
88
+ if (n.id !== node.id) return n;
89
+ return {
90
+ ...n,
91
+ data: {
92
+ ...n.data,
93
+ dynamicPorts: {
94
+ ...n.data?.dynamicPorts,
95
+ ...(kind === 'input' ? { outputs: sockets } : { inputs: sockets })
96
+ }
97
+ }
98
+ } as IBehaveNode;
99
+ })
100
+ );
101
+ // eslint-disable-next-line react-hooks/exhaustive-deps
102
+ }, [paramsKey, kind, node.id, session.nodeStore]);
103
+
104
+ const update = useCallback(
105
+ (next: ContractParam[]) => {
106
+ session.nodeStore.getState().setNodes((prev) =>
107
+ prev.map((n) => {
108
+ if (n.id !== node.id) return n;
109
+ return {
110
+ ...n,
111
+ data: {
112
+ ...n.data,
113
+ configuration: { ...n.data?.configuration, parameters: next }
114
+ }
115
+ } as IBehaveNode;
116
+ })
117
+ );
118
+ },
119
+ [node.id, session]
120
+ );
121
+
122
+ const addParam = useCallback(() => {
123
+ const base = kind === 'input' ? 'in' : 'out';
124
+ const existing = new Set(params.map((p) => p.name));
125
+ let i = params.length + 1;
126
+ let name = `${base}${i}`;
127
+ while (existing.has(name)) name = `${base}${++i}`;
128
+ update([
129
+ ...params,
130
+ { id: uuidv4(), name, valueTypeName: valueTypes[0] ?? 'string' }
131
+ ]);
132
+ }, [kind, params, update, valueTypes]);
133
+
134
+ const setName = useCallback(
135
+ (index: number, name: string) =>
136
+ update(params.map((p, i) => (i === index ? { ...p, name } : p))),
137
+ [params, update]
138
+ );
139
+
140
+ const setType = useCallback(
141
+ (index: number, valueTypeName: string) =>
142
+ update(params.map((p, i) => (i === index ? { ...p, valueTypeName } : p))),
143
+ [params, update]
144
+ );
145
+
146
+ const remove = useCallback(
147
+ (index: number) => update(params.filter((_, i) => i !== index)),
148
+ [params, update]
149
+ );
150
+
151
+ return (
152
+ <div className={styles.list}>
153
+ {params.map((param, index) => (
154
+ <div key={param.id ?? index} className={styles.param}>
155
+ <div className={styles.topRow}>
156
+ <VscodeTextfield
157
+ className={styles.name}
158
+ value={param.name}
159
+ placeholder={`${kind} name`}
160
+ onChange={(e: any) =>
161
+ setName(index, String(e?.target?.value ?? ''))
162
+ }
163
+ />
164
+ <VscodeButton
165
+ secondary
166
+ iconOnly
167
+ title={`Remove ${kind}`}
168
+ onClick={() => remove(index)}
169
+ >
170
+ <Trash />
171
+ </VscodeButton>
172
+ </div>
173
+ <VscodeSingleSelect
174
+ className={styles.type}
175
+ value={param.valueTypeName}
176
+ onChange={(e: any) =>
177
+ setType(index, String(e?.target?.value ?? 'string'))
178
+ }
179
+ >
180
+ {valueTypes.map((t) => (
181
+ <VscodeOption key={t} value={t}>
182
+ {t}
183
+ </VscodeOption>
184
+ ))}
185
+ </VscodeSingleSelect>
186
+ </div>
187
+ ))}
188
+ <VscodeButton secondary onClick={addParam}>
189
+ + Add {kind}
190
+ </VscodeButton>
191
+ </div>
192
+ );
193
+ };
@@ -2,7 +2,7 @@ import React, { useCallback } from 'react';
2
2
  import { VscodeButton } from '@vscode-elements/react-elements';
3
3
  import { Plus, Minus } from 'iconoir-react';
4
4
 
5
- import { useSystem } from '@/system/provider';
5
+ import { useGraph } from '@/system/provider';
6
6
  import type { SocketGeneratorRenderProps } from '@/store/socketGenerator';
7
7
  import type { NodeSpecJSON } from '@kiberon-labs/behave-graph/Graphs/IO/NodeSpecJSON';
8
8
 
@@ -18,7 +18,7 @@ export function getSequenceGenerator() {
18
18
  }
19
19
 
20
20
  const SequenceGenerator: React.FC<SocketGeneratorRenderProps> = ({ node }) => {
21
- const system = useSystem();
21
+ const system = useGraph();
22
22
 
23
23
  const numOutputs = node.data.configuration?.numOutputs ?? 0;
24
24
 
@@ -7,7 +7,7 @@ import {
7
7
  } from '@vscode-elements/react-elements';
8
8
  import { Plus, Minus } from 'iconoir-react';
9
9
 
10
- import { useSystem } from '@/system/provider';
10
+ import { useGraph } from '@/system/provider';
11
11
  import type { SocketGeneratorRenderProps } from '@/store/socketGenerator';
12
12
  import type { NodeSpecJSON } from '@kiberon-labs/behave-graph/Graphs/IO/NodeSpecJSON';
13
13
 
@@ -24,7 +24,7 @@ export function getSwitchOnIntegerGenerator() {
24
24
  const SwitchOnIntegerGenerator: React.FC<SocketGeneratorRenderProps> = ({
25
25
  node
26
26
  }) => {
27
- const system = useSystem();
27
+ const system = useGraph();
28
28
 
29
29
  const numCases = node.data.configuration?.numCases ?? 0;
30
30
  const caseLabels = node.data.configuration?.caseLabels ?? {};
@@ -7,7 +7,7 @@ import {
7
7
  } from '@vscode-elements/react-elements';
8
8
  import { Plus, Minus } from 'iconoir-react';
9
9
 
10
- import { useSystem } from '@/system/provider';
10
+ import { useGraph } from '@/system/provider';
11
11
  import type { SocketGeneratorRenderProps } from '@/store/socketGenerator';
12
12
  import type { NodeSpecJSON } from '@kiberon-labs/behave-graph/Graphs/IO/NodeSpecJSON';
13
13
 
@@ -24,7 +24,7 @@ export function getSwitchOnStringGenerator() {
24
24
  const SwitchOnStringGenerator: React.FC<SocketGeneratorRenderProps> = ({
25
25
  node
26
26
  }) => {
27
- const system = useSystem();
27
+ const system = useGraph();
28
28
 
29
29
  const numCases = node.data.configuration?.numCases ?? 0;
30
30
  const caseLabels = node.data.configuration?.caseLabels ?? {};
@@ -0,0 +1,126 @@
1
+ import type { System } from '@/system/system';
2
+ import type { GraphSession } from '@/system/graphSession';
3
+ import type { IBehaveNode } from '@/types/nodes';
4
+ import {
5
+ CALL_SUBGRAPH_TYPE,
6
+ contractToParams,
7
+ deriveContract,
8
+ paramsToSockets,
9
+ type ContractParam
10
+ } from '@/transformers/contract';
11
+
12
+ /**
13
+ * Stable string key for a session's contract (its graph/input + graph/output
14
+ * params). Used to detect *contract* changes and ignore unrelated node edits
15
+ * (e.g. dragging), so we only repropagate when the boundary actually changes.
16
+ */
17
+ const contractKey = (session: GraphSession): string =>
18
+ JSON.stringify(deriveContract(session.nodeStore.getState().nodes));
19
+
20
+ /**
21
+ * Reconcile every Call Subgraph node in every open graph with the *current*
22
+ * contract of the subgraph it references. Idempotent: a node is only rewritten
23
+ * when its stored contract/ports actually differ, so this converges and never
24
+ * loops (rewriting a call node does not change its host graph's contract).
25
+ */
26
+ const resyncAllCallNodes = (editor: System): void => {
27
+ const sessions = editor.activeGraph.getState().sessions;
28
+
29
+ for (const target of Object.values(sessions)) {
30
+ let mutated = false;
31
+
32
+ const nextNodes = target.nodeStore.getState().nodes.map((node) => {
33
+ if (node.data?.type !== CALL_SUBGRAPH_TYPE) return node;
34
+
35
+ const subgraphId = String(node.data?.configuration?.subgraphId ?? '');
36
+ const subgraph = subgraphId ? sessions[subgraphId] : undefined;
37
+ // Referenced graph not open (e.g. its tab was closed): leave the last
38
+ // known contract in place rather than wiping the node's ports.
39
+ if (!subgraph) return node;
40
+
41
+ const contract = deriveContract(subgraph.nodeStore.getState().nodes);
42
+ const inputs: ContractParam[] = contractToParams(contract.graphInputs);
43
+ const outputs: ContractParam[] = contractToParams(contract.graphOutputs);
44
+ const inputSockets = paramsToSockets(inputs);
45
+ const outputSockets = paramsToSockets(outputs);
46
+
47
+ const currentConfig = {
48
+ inputs: node.data?.configuration?.inputs ?? [],
49
+ outputs: node.data?.configuration?.outputs ?? []
50
+ };
51
+ const currentPorts = {
52
+ inputs: node.data?.dynamicPorts?.inputs ?? [],
53
+ outputs: node.data?.dynamicPorts?.outputs ?? []
54
+ };
55
+
56
+ if (
57
+ JSON.stringify(currentConfig) === JSON.stringify({ inputs, outputs }) &&
58
+ JSON.stringify(currentPorts) ===
59
+ JSON.stringify({ inputs: inputSockets, outputs: outputSockets })
60
+ ) {
61
+ return node;
62
+ }
63
+
64
+ mutated = true;
65
+ return {
66
+ ...node,
67
+ data: {
68
+ ...node.data,
69
+ configuration: {
70
+ ...node.data?.configuration,
71
+ inputs,
72
+ outputs
73
+ },
74
+ dynamicPorts: {
75
+ ...node.data?.dynamicPorts,
76
+ inputs: inputSockets,
77
+ outputs: outputSockets
78
+ }
79
+ }
80
+ } as IBehaveNode;
81
+ });
82
+
83
+ if (mutated) target.nodeStore.getState().setNodes(() => nextNodes);
84
+ }
85
+ };
86
+
87
+ /**
88
+ * Keep Call Subgraph nodes live with the graphs they reference. Without this a
89
+ * call node only captures the subgraph's contract at selection time; editing the
90
+ * subgraph's inputs/outputs afterwards would leave callers stale.
91
+ *
92
+ * Implemented as an editor session extension: every open graph (existing and
93
+ * future) watches its own contract, and when it changes we repropagate to all
94
+ * call nodes referencing it across every graph. Returns a disposer.
95
+ */
96
+ export function setupCallSubgraphSync(editor: System): () => void {
97
+ const unsubscribes = new Map<string, () => void>();
98
+
99
+ const watch = (session: GraphSession) => {
100
+ if (unsubscribes.has(session.id)) return;
101
+
102
+ let last = contractKey(session);
103
+ const unsub = session.nodeStore.subscribe(() => {
104
+ const next = contractKey(session);
105
+ if (next === last) return; // ignore non-contract edits (drags, etc.)
106
+ last = next;
107
+ resyncAllCallNodes(editor);
108
+ });
109
+
110
+ unsubscribes.set(session.id, unsub);
111
+ session.onDispose(() => {
112
+ unsubscribes.get(session.id)?.();
113
+ unsubscribes.delete(session.id);
114
+ });
115
+ };
116
+
117
+ const unregister = editor.registerSessionExtension(watch);
118
+ // Reconcile graphs already open (e.g. a project loaded from disk) once.
119
+ resyncAllCallNodes(editor);
120
+
121
+ return () => {
122
+ unregister();
123
+ for (const unsub of unsubscribes.values()) unsub();
124
+ unsubscribes.clear();
125
+ };
126
+ }
@@ -3,6 +3,11 @@ import { getSwitchOnStringGenerator } from '../generators/SwitchOnStringGenerato
3
3
  import { getSwitchOnIntegerGenerator } from '../generators/SwitchOnIntegerGenerator';
4
4
  import { getCustomEventOnTriggeredGenerator } from '../generators/CustomEventOnTriggeredGenerator';
5
5
  import { getSequenceGenerator } from '../generators/SequenceGenerator';
6
+ import {
7
+ getGraphInputGenerator,
8
+ getGraphOutputGenerator
9
+ } from '../generators/GraphBoundaryGenerator';
10
+ import { getCallSubgraphGenerator } from '../generators/CallSubgraphGenerator';
6
11
 
7
12
  export function registerDefaultSocketGenerators(system: System): () => void {
8
13
  const store = system.socketGeneratorStore.getState();
@@ -19,6 +24,15 @@ export function registerDefaultSocketGenerators(system: System): () => void {
19
24
  const sequence = getSequenceGenerator();
20
25
  store.registerGenerator(sequence);
21
26
 
27
+ const graphInput = getGraphInputGenerator();
28
+ store.registerGenerator(graphInput);
29
+
30
+ const graphOutput = getGraphOutputGenerator();
31
+ store.registerGenerator(graphOutput);
32
+
33
+ const callSubgraph = getCallSubgraphGenerator();
34
+ store.registerGenerator(callSubgraph);
35
+
22
36
  return () => {
23
37
  system.socketGeneratorStore
24
38
  .getState()
@@ -30,5 +44,12 @@ export function registerDefaultSocketGenerators(system: System): () => void {
30
44
  .getState()
31
45
  .unregisterGenerator(customEventOnTriggered.name);
32
46
  system.socketGeneratorStore.getState().unregisterGenerator(sequence.name);
47
+ system.socketGeneratorStore.getState().unregisterGenerator(graphInput.name);
48
+ system.socketGeneratorStore
49
+ .getState()
50
+ .unregisterGenerator(graphOutput.name);
51
+ system.socketGeneratorStore
52
+ .getState()
53
+ .unregisterGenerator(callSubgraph.name);
33
54
  };
34
55
  }
@@ -0,0 +1,26 @@
1
+ import type { System } from '@/system/system';
2
+ import { registerDefaultSocketGenerators } from './registerDefaultGenerators';
3
+ import { setupCallSubgraphSync } from './callSubgraphSync';
4
+
5
+ /** Editors that already had their built-in content registered. */
6
+ const initialized = new WeakSet<System>();
7
+
8
+ /**
9
+ * Register the editor's built-in content , the default socket generators and the
10
+ * call-subgraph contract sync , on an editor instance.
11
+ *
12
+ * Idempotent per editor: safe to call from every graph-canvas mount (multiple
13
+ * tabs) without double-registering. The default content is editor-lifetime; the
14
+ * subgraph sync's per-session subscriptions clean themselves up on session
15
+ * dispose.
16
+ *
17
+ * A host that wants a blank or fully custom editor can simply not rely on the
18
+ * canvas's auto-call and register its own content instead.
19
+ */
20
+ export function registerDefaults(editor: System): void {
21
+ if (initialized.has(editor)) return;
22
+ initialized.add(editor);
23
+
24
+ registerDefaultSocketGenerators(editor);
25
+ setupCallSubgraphSync(editor);
26
+ }
@@ -4,7 +4,7 @@ import { behaveToFlow } from '../transformers/behaveToFlow.js';
4
4
  import { flowToBehave } from '../transformers/flowToBehave.js';
5
5
  import { autoLayout } from '../util/autoLayout.js';
6
6
  import { hasPositionMetaData } from '../util/hasPositionMetaData.js';
7
- import { useSystem } from '@/system/provider.js';
7
+ import { useGraph } from '@/system/provider.js';
8
8
  import { useStore } from 'zustand';
9
9
 
10
10
  /**
@@ -22,7 +22,7 @@ export const useBehaveGraphFlow = ({
22
22
  specJson: NodeSpecJSON[] | undefined;
23
23
  }) => {
24
24
  const [graphJson, setStoredGraphJson] = useState<GraphJSON | undefined>();
25
- const sys = useSystem();
25
+ const sys = useGraph();
26
26
 
27
27
  const {
28
28
  nodes,
@@ -15,9 +15,10 @@ import type {
15
15
  import { v4 as uuidv4 } from 'uuid';
16
16
 
17
17
  import { calculateNewEdge } from '../util/calculateNewEdge.js';
18
+ import { buildConverterInsertion } from '../util/autoConvert.js';
18
19
  import { getNodePickerFilters } from '../util/getPickerFilters.js';
19
20
  import { useBehaveGraphFlow } from './useBehaveGraphFlow.js';
20
- import { useSystem } from '@/system/provider.js';
21
+ import { useGraph } from '@/system/provider.js';
21
22
  import type { ExtendedNodeSpecJSON } from '@/components/contextMenus/NodePicker.js';
22
23
  import {
23
24
  addFloatingTab,
@@ -56,7 +57,7 @@ export const useFlowHandlers = ({
56
57
  nodes: Node[];
57
58
  specJSON: NodeSpecJSON[] | undefined;
58
59
  }) => {
59
- const sys = useSystem();
60
+ const sys = useGraph();
60
61
  const [lastConnectStart, setLastConnectStart] =
61
62
  useState<OnConnectStartParams>();
62
63
  const [nodePickerVisibility, setNodePickerVisibility] =
@@ -66,6 +67,43 @@ export const useFlowHandlers = ({
66
67
  if (connection.source === null) return;
67
68
  if (connection.target === null) return;
68
69
 
70
+ // Auto-convert: if the value types differ but a converter node exists,
71
+ // splice the converter in between instead of a direct edge.
72
+ const autoConvert = sys.editor.systemSettings.getState().autoConvert;
73
+ if (autoConvert && specJSON) {
74
+ const insertion = buildConverterInsertion(
75
+ connection,
76
+ sys.nodeStore.getState().nodes,
77
+ specJSON,
78
+ sys.editor.conversionStore.getState().conversions
79
+ );
80
+ if (insertion) {
81
+ const { node, edges: convEdges } = insertion;
82
+ sys.undoManager.execute({
83
+ name: 'Auto-convert connection',
84
+ execute: () => {
85
+ sys.nodeStore.getState().addNode(node);
86
+ sys.edgeStore
87
+ .getState()
88
+ .setEdges([...sys.edgeStore.getState().edges, ...convEdges]);
89
+ convEdges.forEach((e) => sys.pubsub.publish('edge:added', e));
90
+ },
91
+ undo: () => {
92
+ const ids = new Set(convEdges.map((e) => e.id));
93
+ sys.nodeStore
94
+ .getState()
95
+ .setNodes((ns) => ns.filter((n) => n.id !== node.id));
96
+ sys.edgeStore
97
+ .getState()
98
+ .setEdges(
99
+ sys.edgeStore.getState().edges.filter((e) => !ids.has(e.id))
100
+ );
101
+ }
102
+ });
103
+ return;
104
+ }
105
+ }
106
+
69
107
  const newEdge = {
70
108
  id: uuidv4(),
71
109
  source: connection.source,
@@ -81,7 +119,7 @@ export const useFlowHandlers = ({
81
119
  }
82
120
  ]);
83
121
  },
84
- [onEdgesChange]
122
+ [onEdgesChange, sys, specJSON]
85
123
  );
86
124
 
87
125
  const closeNodePicker = useCallback(() => {
@@ -89,9 +127,9 @@ export const useFlowHandlers = ({
89
127
  setNodePickerVisibility(undefined);
90
128
 
91
129
  // Close the nodepicker panel from rc-dock
92
- const currentLayout = sys.tabStore.getState().layout;
130
+ const currentLayout = sys.editor.tabStore.getState().layout;
93
131
  const newLayout = removeTabFromLayout(currentLayout, 'nodepicker');
94
- sys.tabStore.getState().setLayout(newLayout);
132
+ sys.editor.tabStore.getState().setLayout(newLayout);
95
133
  }, [sys]);
96
134
 
97
135
  const handleAddNode = useCallback(
@@ -171,7 +209,7 @@ export const useFlowHandlers = ({
171
209
  sys.refStore.getState().setRef('nodePickerPosition', screenPos);
172
210
 
173
211
  // Open as floating rc-dock panel
174
- const currentLayout = sys.tabStore.getState().layout;
212
+ const currentLayout = sys.editor.tabStore.getState().layout;
175
213
 
176
214
  // Close existing nodepicker if open
177
215
  const existingPanel = findTabInLayout(currentLayout, 'nodepicker');
@@ -194,7 +232,7 @@ export const useFlowHandlers = ({
194
232
  height: 500
195
233
  });
196
234
 
197
- sys.tabStore.getState().setLayout(newLayout);
235
+ sys.editor.tabStore.getState().setLayout(newLayout);
198
236
  } else {
199
237
  setLastConnectStart(undefined);
200
238
  }
@@ -217,7 +255,7 @@ export const useFlowHandlers = ({
217
255
  sys.refStore.getState().setRef('nodePickerPosition', screenPos);
218
256
 
219
257
  // Open as floating rc-dock panel
220
- const currentLayout = sys.tabStore.getState().layout;
258
+ const currentLayout = sys.editor.tabStore.getState().layout;
221
259
 
222
260
  // Close existing nodepicker if open
223
261
  const existingPanel = findTabInLayout(currentLayout, 'nodepicker');
@@ -240,7 +278,7 @@ export const useFlowHandlers = ({
240
278
  height: 500
241
279
  });
242
280
 
243
- sys.tabStore.getState().setLayout(newLayout);
281
+ sys.editor.tabStore.getState().setLayout(newLayout);
244
282
  },
245
283
  [sys]
246
284
  );
@@ -9,11 +9,33 @@ export type UseWasdPanOptions = {
9
9
  enabled?: boolean;
10
10
  };
11
11
 
12
- const isEventFromEditable = (event: KeyboardEvent) => {
12
+ const isEditableElement = (element: HTMLElement): boolean => {
13
+ const tag = element.tagName?.toLowerCase();
14
+ // Contenteditable surfaces (e.g. the notes plugin's prosemirror editor) are
15
+ // divs, so checking the tag alone is not enough — WASD must type, not pan.
16
+ return (
17
+ tag === 'input' ||
18
+ tag === 'textarea' ||
19
+ tag === 'select' ||
20
+ element.isContentEditable
21
+ );
22
+ };
23
+
24
+ export const isEventFromEditable = (event: KeyboardEvent) => {
25
+ // Events from inside a web component are retargeted: at the window listener
26
+ // `event.target` is the custom-element host (e.g. the conversation panel's
27
+ // <vscode-textfield>), not the <input> in its shadow DOM. composedPath()
28
+ // exposes the real target chain, so walk it and check every element.
29
+ const path =
30
+ typeof event.composedPath === 'function' ? event.composedPath() : [];
31
+ if (path.length > 0) {
32
+ return path.some(
33
+ (node) => node instanceof HTMLElement && isEditableElement(node)
34
+ );
35
+ }
13
36
  const target = event.target as HTMLElement | null;
14
- const tag = target?.tagName?.toLowerCase();
15
- //Just a patch to avoid pan when focus is in input/textarea or contenteditable element
16
- return tag !== 'div';
37
+ if (!target) return false;
38
+ return isEditableElement(target);
17
39
  };
18
40
 
19
41
  export const useWasdPan = ({
package/src/index.css CHANGED
@@ -2,11 +2,13 @@
2
2
  @import 'rc-menu/assets/index.css';
3
3
  @import "rc-dock/dist/rc-dock-dark.css";
4
4
 
5
- @import "./css/vscode.css";
6
5
  @import "./css/vars.css";
6
+ @import "./css/themes/kiberon.css";
7
+ @import "./css/vscode-elements.css";
7
8
  @import "./css/rc-dock.css";
8
9
  @import "./css/rc-menu.css";
9
10
  @import "./css/prosemirror.css";
11
+ @import "./css/notes.css";
10
12
 
11
13
  *{
12
14
  box-sizing: border-box;
@@ -61,21 +63,7 @@
61
63
  color: var(--colors-fg);
62
64
  }
63
65
 
64
- .rc-menu-sub .rc-menu-item {
65
- padding: 0.1em !important;
66
- }
67
-
68
- .rc-menu-item {
69
- color: var(--colors-fg);
70
- padding: 0 1em !important;
71
- }
72
-
73
-
74
- .rc-menu-horizontal {
75
- padding: 0.2em;
76
- display: flex;
77
- gap: 0.4em;
78
- }
66
+ /* rc-menu styling lives in css/rc-menu.css */
79
67
 
80
68
 
81
69
  .container {