@flowdrop/flowdrop 1.4.0 → 1.6.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.
- package/README.md +94 -51
- package/dist/adapters/WorkflowAdapter.d.ts +1 -1
- package/dist/adapters/WorkflowAdapter.js +27 -47
- package/dist/adapters/agentspec/AgentSpecAdapter.d.ts +2 -2
- package/dist/adapters/agentspec/AgentSpecAdapter.js +122 -133
- package/dist/adapters/agentspec/agentAdapter.d.ts +2 -2
- package/dist/adapters/agentspec/agentAdapter.js +10 -10
- package/dist/adapters/agentspec/autoLayout.d.ts +52 -6
- package/dist/adapters/agentspec/autoLayout.js +118 -23
- package/dist/adapters/agentspec/componentTypeDefaults.d.ts +1 -1
- package/dist/adapters/agentspec/componentTypeDefaults.js +120 -120
- package/dist/adapters/agentspec/defaultNodeTypes.d.ts +2 -2
- package/dist/adapters/agentspec/defaultNodeTypes.js +307 -307
- package/dist/adapters/agentspec/index.d.ts +10 -10
- package/dist/adapters/agentspec/index.js +6 -6
- package/dist/adapters/agentspec/validator.d.ts +2 -2
- package/dist/adapters/agentspec/validator.js +20 -22
- package/dist/api/enhanced-client.d.ts +3 -3
- package/dist/api/enhanced-client.js +72 -73
- package/dist/chat/commandClassifier.d.ts +19 -0
- package/dist/chat/commandClassifier.js +30 -0
- package/dist/chat/index.d.ts +27 -0
- package/dist/chat/index.js +32 -0
- package/dist/chat/responseParser.d.ts +21 -0
- package/dist/chat/responseParser.js +91 -0
- package/dist/commands/batch.d.ts +18 -0
- package/dist/commands/batch.js +54 -0
- package/dist/commands/executor.d.ts +37 -0
- package/dist/commands/executor.js +1133 -0
- package/dist/commands/index.d.ts +14 -0
- package/dist/commands/index.js +17 -0
- package/dist/commands/parser.d.ts +16 -0
- package/dist/commands/parser.js +295 -0
- package/dist/commands/positioner.d.ts +19 -0
- package/dist/commands/positioner.js +33 -0
- package/dist/commands/storeIntegration.svelte.d.ts +16 -0
- package/dist/commands/storeIntegration.svelte.js +67 -0
- package/dist/commands/types.d.ts +343 -0
- package/dist/commands/types.js +45 -0
- package/dist/components/App.svelte +522 -237
- package/dist/components/App.svelte.d.ts +11 -8
- package/dist/components/CanvasBanner.stories.svelte +10 -16
- package/dist/components/CanvasBanner.stories.svelte.d.ts +1 -1
- package/dist/components/CanvasBanner.svelte +2 -2
- package/dist/components/CanvasBanner.svelte.d.ts +1 -1
- package/dist/components/CanvasController.svelte +37 -0
- package/dist/components/CanvasController.svelte.d.ts +32 -0
- package/dist/components/ConfigForm.svelte +118 -256
- package/dist/components/ConfigForm.svelte.d.ts +2 -2
- package/dist/components/ConfigMappingRow.svelte +128 -0
- package/dist/components/ConfigMappingRow.svelte.d.ts +8 -0
- package/dist/components/ConfigModal.svelte +3 -3
- package/dist/components/ConfigModal.svelte.d.ts +1 -1
- package/dist/components/ConfigPanel.stories.svelte +19 -19
- package/dist/components/ConfigPanel.stories.svelte.d.ts +1 -1
- package/dist/components/ConfigPanel.svelte +57 -19
- package/dist/components/ConfigPanel.svelte.d.ts +3 -1
- package/dist/components/ConnectionLine.svelte +4 -4
- package/dist/components/EdgeRefresher.svelte +1 -1
- package/dist/components/FlowDropEdge.stories.svelte +110 -110
- package/dist/components/FlowDropEdge.svelte +11 -19
- package/dist/components/FlowDropEdge.svelte.d.ts +1 -1
- package/dist/components/FlowDropZone.svelte +6 -9
- package/dist/components/FlowDropZone.svelte.d.ts +1 -1
- package/dist/components/LoadingSpinner.stories.svelte +13 -13
- package/dist/components/LoadingSpinner.stories.svelte.d.ts +1 -1
- package/dist/components/LoadingSpinner.svelte +3 -3
- package/dist/components/LoadingSpinner.svelte.d.ts +1 -1
- package/dist/components/Logo.stories.svelte +4 -4
- package/dist/components/Logo.stories.svelte.d.ts +1 -1
- package/dist/components/Logo.svelte +3 -9
- package/dist/components/LogsSidebar.svelte +46 -53
- package/dist/components/LogsSidebar.svelte.d.ts +1 -1
- package/dist/components/MarkdownDisplay.stories.svelte +10 -14
- package/dist/components/MarkdownDisplay.stories.svelte.d.ts +1 -1
- package/dist/components/MarkdownDisplay.svelte +4 -6
- package/dist/components/Navbar.stories.svelte +19 -19
- package/dist/components/Navbar.stories.svelte.d.ts +1 -1
- package/dist/components/Navbar.svelte +28 -49
- package/dist/components/Navbar.svelte.d.ts +2 -2
- package/dist/components/NodeSidebar.svelte +55 -135
- package/dist/components/NodeSidebar.svelte.d.ts +1 -1
- package/dist/components/NodeStatusOverlay.stories.svelte +19 -31
- package/dist/components/NodeStatusOverlay.stories.svelte.d.ts +1 -1
- package/dist/components/NodeStatusOverlay.svelte +40 -55
- package/dist/components/NodeStatusOverlay.svelte.d.ts +3 -3
- package/dist/components/NodeSwapPicker.svelte +493 -0
- package/dist/components/NodeSwapPicker.svelte.d.ts +16 -0
- package/dist/components/PipelineStatus.svelte +63 -89
- package/dist/components/PipelineStatus.svelte.d.ts +4 -4
- package/dist/components/PortCoordinateTracker.svelte +5 -7
- package/dist/components/PortCoordinateTracker.svelte.d.ts +1 -1
- package/dist/components/PortMappingRow.svelte +205 -0
- package/dist/components/PortMappingRow.svelte.d.ts +12 -0
- package/dist/components/ReadOnlyDetails.svelte +1 -1
- package/dist/components/SchemaForm.stories.svelte +53 -53
- package/dist/components/SchemaForm.stories.svelte.d.ts +1 -1
- package/dist/components/SchemaForm.svelte +24 -51
- package/dist/components/SchemaForm.svelte.d.ts +2 -2
- package/dist/components/SettingsModal.svelte +6 -9
- package/dist/components/SettingsModal.svelte.d.ts +1 -1
- package/dist/components/SettingsPanel.svelte +138 -158
- package/dist/components/SettingsPanel.svelte.d.ts +1 -1
- package/dist/components/StatusIcon.stories.svelte +16 -29
- package/dist/components/StatusIcon.stories.svelte.d.ts +1 -1
- package/dist/components/StatusIcon.svelte +19 -19
- package/dist/components/StatusIcon.svelte.d.ts +2 -2
- package/dist/components/StatusLabel.stories.svelte +8 -8
- package/dist/components/StatusLabel.stories.svelte.d.ts +1 -1
- package/dist/components/SwapMappingEditor.svelte +529 -0
- package/dist/components/SwapMappingEditor.svelte.d.ts +12 -0
- package/dist/components/ThemeToggle.stories.svelte +10 -10
- package/dist/components/ThemeToggle.stories.svelte.d.ts +1 -1
- package/dist/components/ThemeToggle.svelte +22 -33
- package/dist/components/ThemeToggle.svelte.d.ts +1 -1
- package/dist/components/UniversalNode.svelte +29 -41
- package/dist/components/UniversalNode.svelte.d.ts +3 -3
- package/dist/components/WorkflowEditor.svelte +210 -170
- package/dist/components/WorkflowEditor.svelte.d.ts +12 -4
- package/dist/components/chat/AIChatPanel.svelte +797 -0
- package/dist/components/chat/AIChatPanel.svelte.d.ts +13 -0
- package/dist/components/chat/CommandPreview.svelte +234 -0
- package/dist/components/chat/CommandPreview.svelte.d.ts +9 -0
- package/dist/components/console/CommandConsole.stories.svelte +111 -0
- package/dist/components/console/CommandConsole.stories.svelte.d.ts +27 -0
- package/dist/components/console/CommandConsole.svelte +263 -0
- package/dist/components/console/CommandConsole.svelte.d.ts +11 -0
- package/dist/components/console/ConsoleAutocomplete.svelte +142 -0
- package/dist/components/console/ConsoleAutocomplete.svelte.d.ts +21 -0
- package/dist/components/console/ConsoleInput.svelte +771 -0
- package/dist/components/console/ConsoleInput.svelte.d.ts +16 -0
- package/dist/components/console/ConsoleOutput.svelte +116 -0
- package/dist/components/console/ConsoleOutput.svelte.d.ts +11 -0
- package/dist/components/console/formatters.d.ts +26 -0
- package/dist/components/console/formatters.js +116 -0
- package/dist/components/form/FormArray.svelte +75 -132
- package/dist/components/form/FormArray.svelte.d.ts +1 -1
- package/dist/components/form/FormAutocomplete.svelte +65 -108
- package/dist/components/form/FormAutocomplete.svelte.d.ts +1 -1
- package/dist/components/form/FormCheckboxGroup.stories.svelte +13 -16
- package/dist/components/form/FormCheckboxGroup.stories.svelte.d.ts +1 -1
- package/dist/components/form/FormCheckboxGroup.svelte +2 -2
- package/dist/components/form/FormCodeEditor.svelte +42 -56
- package/dist/components/form/FormField.svelte +79 -90
- package/dist/components/form/FormField.svelte.d.ts +2 -2
- package/dist/components/form/FormFieldLight.svelte +72 -88
- package/dist/components/form/FormFieldLight.svelte.d.ts +1 -1
- package/dist/components/form/FormFieldWrapper.stories.svelte +14 -14
- package/dist/components/form/FormFieldWrapper.stories.svelte.d.ts +1 -1
- package/dist/components/form/FormFieldWrapper.svelte +2 -9
- package/dist/components/form/FormFieldWrapper.svelte.d.ts +1 -1
- package/dist/components/form/FormFieldset.svelte +3 -3
- package/dist/components/form/FormFieldset.svelte.d.ts +2 -2
- package/dist/components/form/FormMarkdownEditor.svelte +123 -156
- package/dist/components/form/FormNumberField.stories.svelte +18 -18
- package/dist/components/form/FormNumberField.stories.svelte.d.ts +1 -1
- package/dist/components/form/FormNumberField.svelte +6 -6
- package/dist/components/form/FormRangeField.stories.svelte +13 -13
- package/dist/components/form/FormRangeField.stories.svelte.d.ts +1 -1
- package/dist/components/form/FormRangeField.svelte +4 -12
- package/dist/components/form/FormSelect.stories.svelte +21 -21
- package/dist/components/form/FormSelect.stories.svelte.d.ts +1 -1
- package/dist/components/form/FormSelect.svelte +5 -5
- package/dist/components/form/FormSelect.svelte.d.ts +1 -1
- package/dist/components/form/FormTemplateEditor.svelte +126 -175
- package/dist/components/form/FormTemplateEditor.svelte.d.ts +1 -1
- package/dist/components/form/FormTextField.stories.svelte +17 -23
- package/dist/components/form/FormTextField.stories.svelte.d.ts +1 -1
- package/dist/components/form/FormTextField.svelte +4 -4
- package/dist/components/form/FormTextarea.stories.svelte +18 -21
- package/dist/components/form/FormTextarea.stories.svelte.d.ts +1 -1
- package/dist/components/form/FormTextarea.svelte +4 -4
- package/dist/components/form/FormToggle.stories.svelte +13 -16
- package/dist/components/form/FormToggle.stories.svelte.d.ts +1 -1
- package/dist/components/form/FormToggle.svelte +3 -3
- package/dist/components/form/FormUISchemaRenderer.svelte +12 -19
- package/dist/components/form/FormUISchemaRenderer.svelte.d.ts +3 -3
- package/dist/components/form/index.d.ts +19 -19
- package/dist/components/form/index.js +18 -18
- package/dist/components/form/templateAutocomplete.d.ts +2 -2
- package/dist/components/form/templateAutocomplete.js +55 -64
- package/dist/components/form/types.d.ts +6 -6
- package/dist/components/form/types.js +4 -9
- package/dist/components/icons/AlertCircleIcon.svelte +1 -6
- package/dist/components/icons/CogIcon.svelte +1 -6
- package/dist/components/interrupt/ChoicePrompt.stories.svelte +27 -27
- package/dist/components/interrupt/ChoicePrompt.stories.svelte.d.ts +1 -1
- package/dist/components/interrupt/ChoicePrompt.svelte +17 -41
- package/dist/components/interrupt/ChoicePrompt.svelte.d.ts +1 -1
- package/dist/components/interrupt/ConfirmationPrompt.stories.svelte +17 -17
- package/dist/components/interrupt/ConfirmationPrompt.stories.svelte.d.ts +1 -1
- package/dist/components/interrupt/ConfirmationPrompt.svelte +10 -16
- package/dist/components/interrupt/ConfirmationPrompt.svelte.d.ts +1 -1
- package/dist/components/interrupt/FormPrompt.svelte +10 -15
- package/dist/components/interrupt/FormPrompt.svelte.d.ts +1 -1
- package/dist/components/interrupt/InterruptBubble.svelte +87 -121
- package/dist/components/interrupt/InterruptBubble.svelte.d.ts +2 -2
- package/dist/components/interrupt/ReviewPrompt.stories.svelte +37 -37
- package/dist/components/interrupt/ReviewPrompt.stories.svelte.d.ts +1 -1
- package/dist/components/interrupt/ReviewPrompt.svelte +55 -75
- package/dist/components/interrupt/ReviewPrompt.svelte.d.ts +1 -1
- package/dist/components/interrupt/TextInputPrompt.stories.svelte +16 -17
- package/dist/components/interrupt/TextInputPrompt.stories.svelte.d.ts +1 -1
- package/dist/components/interrupt/TextInputPrompt.svelte +13 -18
- package/dist/components/interrupt/TextInputPrompt.svelte.d.ts +1 -1
- package/dist/components/interrupt/index.d.ts +6 -5
- package/dist/components/interrupt/index.js +6 -5
- package/dist/components/layouts/MainLayout.svelte +46 -84
- package/dist/components/layouts/MainLayout.svelte.d.ts +6 -6
- package/dist/components/nodes/GatewayNode.stories.svelte +64 -65
- package/dist/components/nodes/GatewayNode.svelte +37 -70
- package/dist/components/nodes/GatewayNode.svelte.d.ts +3 -3
- package/dist/components/nodes/IdeaNode.stories.svelte +25 -26
- package/dist/components/nodes/IdeaNode.svelte +22 -36
- package/dist/components/nodes/IdeaNode.svelte.d.ts +1 -1
- package/dist/components/nodes/NotesNode.stories.svelte +37 -38
- package/dist/components/nodes/NotesNode.svelte +28 -39
- package/dist/components/nodes/NotesNode.svelte.d.ts +1 -1
- package/dist/components/nodes/SimpleNode.stories.svelte +137 -138
- package/dist/components/nodes/SimpleNode.svelte +44 -74
- package/dist/components/nodes/SimpleNode.svelte.d.ts +1 -1
- package/dist/components/nodes/SquareNode.stories.svelte +75 -75
- package/dist/components/nodes/SquareNode.svelte +42 -68
- package/dist/components/nodes/SquareNode.svelte.d.ts +1 -1
- package/dist/components/nodes/TerminalNode.stories.svelte +10 -10
- package/dist/components/nodes/TerminalNode.svelte +74 -112
- package/dist/components/nodes/TerminalNode.svelte.d.ts +1 -1
- package/dist/components/nodes/ToolNode.stories.svelte +115 -116
- package/dist/components/nodes/ToolNode.svelte +31 -64
- package/dist/components/nodes/ToolNode.svelte.d.ts +1 -1
- package/dist/components/nodes/WorkflowNode.stories.svelte +84 -89
- package/dist/components/nodes/WorkflowNode.svelte +50 -103
- package/dist/components/nodes/WorkflowNode.svelte.d.ts +3 -3
- package/dist/components/playground/ChatPanel.svelte +47 -103
- package/dist/components/playground/ExecutionLogs.svelte +45 -68
- package/dist/components/playground/InputCollector.svelte +32 -51
- package/dist/components/playground/MessageBubble.stories.svelte +25 -25
- package/dist/components/playground/MessageBubble.stories.svelte.d.ts +1 -1
- package/dist/components/playground/MessageBubble.svelte +54 -70
- package/dist/components/playground/MessageBubble.svelte.d.ts +1 -1
- package/dist/components/playground/Playground.svelte +60 -91
- package/dist/components/playground/Playground.svelte.d.ts +3 -3
- package/dist/components/playground/PlaygroundModal.svelte +8 -12
- package/dist/components/playground/PlaygroundModal.svelte.d.ts +3 -3
- package/dist/components/playground/SessionManager.svelte +34 -40
- package/dist/components/playground/SessionManager.svelte.d.ts +1 -1
- package/dist/config/agentSpecEndpoints.d.ts +1 -1
- package/dist/config/agentSpecEndpoints.js +20 -20
- package/dist/config/constants.js +2 -2
- package/dist/config/defaultCategories.d.ts +1 -1
- package/dist/config/defaultCategories.js +86 -86
- package/dist/config/defaultPortConfig.d.ts +1 -1
- package/dist/config/defaultPortConfig.js +144 -144
- package/dist/config/endpoints.d.ts +12 -4
- package/dist/config/endpoints.js +70 -65
- package/dist/config/runtimeConfig.d.ts +2 -2
- package/dist/config/runtimeConfig.js +8 -8
- package/dist/core/index.d.ts +68 -63
- package/dist/core/index.js +44 -35
- package/dist/display/index.d.ts +2 -2
- package/dist/display/index.js +2 -2
- package/dist/editor/index.d.ts +64 -62
- package/dist/editor/index.js +57 -55
- package/dist/form/code.d.ts +5 -5
- package/dist/form/code.js +14 -14
- package/dist/form/fieldRegistry.d.ts +3 -3
- package/dist/form/fieldRegistry.js +9 -11
- package/dist/form/full.d.ts +8 -8
- package/dist/form/full.js +9 -9
- package/dist/form/index.d.ts +18 -18
- package/dist/form/index.js +16 -16
- package/dist/form/markdown.d.ts +4 -4
- package/dist/form/markdown.js +8 -8
- package/dist/helpers/proximityConnect.d.ts +3 -3
- package/dist/helpers/proximityConnect.js +40 -35
- package/dist/helpers/workflowEditorHelper.d.ts +8 -58
- package/dist/helpers/workflowEditorHelper.js +73 -292
- package/dist/index.d.ts +6 -6
- package/dist/index.js +6 -6
- package/dist/mocks/app-environment.js +2 -2
- package/dist/mocks/app-forms.js +1 -1
- package/dist/mocks/app-navigation.js +2 -2
- package/dist/mocks/app-stores.js +3 -3
- package/dist/playground/index.d.ts +19 -19
- package/dist/playground/index.js +16 -16
- package/dist/playground/mount.d.ts +3 -3
- package/dist/playground/mount.js +24 -24
- package/dist/registry/builtinFormats.js +13 -13
- package/dist/registry/builtinNodes.d.ts +2 -2
- package/dist/registry/builtinNodes.js +77 -77
- package/dist/registry/index.d.ts +4 -4
- package/dist/registry/index.js +4 -4
- package/dist/registry/nodeComponentRegistry.d.ts +8 -8
- package/dist/registry/nodeComponentRegistry.js +9 -11
- package/dist/registry/plugin.d.ts +2 -2
- package/dist/registry/plugin.js +11 -11
- package/dist/registry/workflowFormatRegistry.d.ts +3 -3
- package/dist/registry/workflowFormatRegistry.js +2 -2
- package/dist/schema/index.d.ts +1 -1
- package/dist/schema/index.js +2 -2
- package/dist/schemas/v1/workflow.schema.json +107 -22
- package/dist/services/agentSpecExecutionService.d.ts +3 -3
- package/dist/services/agentSpecExecutionService.js +55 -56
- package/dist/services/api.d.ts +2 -2
- package/dist/services/api.js +37 -37
- package/dist/services/apiVariableService.d.ts +1 -1
- package/dist/services/apiVariableService.js +34 -41
- package/dist/services/autoSaveService.js +8 -8
- package/dist/services/categoriesApi.d.ts +2 -2
- package/dist/services/categoriesApi.js +8 -8
- package/dist/services/chatService.d.ts +65 -0
- package/dist/services/chatService.js +131 -0
- package/dist/services/draftStorage.d.ts +1 -1
- package/dist/services/draftStorage.js +11 -11
- package/dist/services/dynamicSchemaService.d.ts +1 -1
- package/dist/services/dynamicSchemaService.js +39 -41
- package/dist/services/globalSave.d.ts +2 -2
- package/dist/services/globalSave.js +38 -41
- package/dist/services/historyService.d.ts +7 -5
- package/dist/services/historyService.js +29 -14
- package/dist/services/interruptService.d.ts +1 -1
- package/dist/services/interruptService.js +29 -35
- package/dist/services/nodeExecutionService.d.ts +1 -1
- package/dist/services/nodeExecutionService.js +44 -45
- package/dist/services/playgroundService.d.ts +1 -1
- package/dist/services/playgroundService.js +29 -29
- package/dist/services/portConfigApi.d.ts +2 -2
- package/dist/services/portConfigApi.js +8 -8
- package/dist/services/settingsService.d.ts +2 -2
- package/dist/services/settingsService.js +19 -25
- package/dist/services/toastService.d.ts +4 -4
- package/dist/services/toastService.js +33 -33
- package/dist/services/variableService.d.ts +1 -1
- package/dist/services/variableService.js +36 -36
- package/dist/services/workflowStorage.d.ts +2 -2
- package/dist/services/workflowStorage.js +13 -13
- package/dist/settings/index.d.ts +7 -7
- package/dist/settings/index.js +6 -6
- package/dist/skins/default.d.ts +1 -1
- package/dist/skins/default.js +1 -1
- package/dist/skins/index.d.ts +3 -3
- package/dist/skins/index.js +7 -7
- package/dist/skins/slate.d.ts +1 -1
- package/dist/skins/slate.js +69 -69
- package/dist/stores/categoriesStore.svelte.d.ts +1 -1
- package/dist/stores/categoriesStore.svelte.js +5 -5
- package/dist/stores/editorStateMachine.svelte.d.ts +2 -2
- package/dist/stores/editorStateMachine.svelte.js +34 -34
- package/dist/stores/historyStore.svelte.d.ts +4 -4
- package/dist/stores/historyStore.svelte.js +4 -4
- package/dist/stores/interruptStore.svelte.d.ts +3 -3
- package/dist/stores/interruptStore.svelte.js +27 -22
- package/dist/stores/playgroundStore.svelte.d.ts +3 -3
- package/dist/stores/playgroundStore.svelte.js +29 -23
- package/dist/stores/portCoordinateStore.svelte.d.ts +6 -2
- package/dist/stores/portCoordinateStore.svelte.js +30 -39
- package/dist/stores/settingsStore.svelte.d.ts +2 -2
- package/dist/stores/settingsStore.svelte.js +57 -62
- package/dist/stores/workflowStore.svelte.d.ts +34 -5
- package/dist/stores/workflowStore.svelte.js +127 -108
- package/dist/stories/CanvasDecorator.svelte +7 -10
- package/dist/stories/CanvasDecorator.svelte.d.ts +2 -2
- package/dist/stories/EdgeDecorator.svelte +28 -31
- package/dist/stories/EdgeDecorator.svelte.d.ts +1 -1
- package/dist/stories/NodeDecorator.svelte +14 -20
- package/dist/stories/NodeDecorator.svelte.d.ts +1 -1
- package/dist/stories/utils.d.ts +2 -2
- package/dist/stories/utils.js +89 -93
- package/dist/styles/base.css +16 -50
- package/dist/styles/tokens.css +10 -28
- package/dist/svelte-app.d.ts +10 -10
- package/dist/svelte-app.js +39 -39
- package/dist/themes/default.d.ts +1 -1
- package/dist/themes/default.js +4 -4
- package/dist/themes/index.d.ts +3 -3
- package/dist/themes/index.js +11 -11
- package/dist/themes/minimal.d.ts +1 -1
- package/dist/themes/minimal.js +5 -5
- package/dist/types/agentspec.d.ts +18 -18
- package/dist/types/agentspec.js +2 -2
- package/dist/types/auth.d.ts +1 -1
- package/dist/types/auth.js +6 -6
- package/dist/types/chat.d.ts +63 -0
- package/dist/types/chat.js +9 -0
- package/dist/types/config.d.ts +6 -6
- package/dist/types/events.d.ts +28 -2
- package/dist/types/events.js +2 -1
- package/dist/types/index.d.ts +40 -32
- package/dist/types/index.js +6 -6
- package/dist/types/interrupt.d.ts +6 -6
- package/dist/types/interrupt.js +21 -21
- package/dist/types/interruptState.d.ts +12 -12
- package/dist/types/interruptState.js +66 -66
- package/dist/types/playground.d.ts +7 -7
- package/dist/types/playground.js +14 -14
- package/dist/types/settings.d.ts +12 -4
- package/dist/types/settings.js +21 -23
- package/dist/types/skin.d.ts +1 -1
- package/dist/types/theme.d.ts +2 -2
- package/dist/types/uischema.d.ts +4 -4
- package/dist/types/uischema.js +3 -3
- package/dist/utils/colors.d.ts +1 -1
- package/dist/utils/colors.js +95 -97
- package/dist/utils/config.d.ts +2 -2
- package/dist/utils/config.js +48 -48
- package/dist/utils/connections.d.ts +2 -2
- package/dist/utils/connections.js +15 -15
- package/dist/utils/edgeStyling.d.ts +42 -0
- package/dist/utils/edgeStyling.js +173 -0
- package/dist/utils/errors.js +3 -3
- package/dist/utils/fetchWithAuth.d.ts +1 -1
- package/dist/utils/fetchWithAuth.js +2 -2
- package/dist/utils/handleIds.d.ts +2 -2
- package/dist/utils/handleIds.js +8 -8
- package/dist/utils/handlePositioning.d.ts +1 -1
- package/dist/utils/handlePositioning.js +2 -2
- package/dist/utils/icons.d.ts +1 -1
- package/dist/utils/icons.js +74 -74
- package/dist/utils/logger.d.ts +1 -1
- package/dist/utils/logger.js +7 -7
- package/dist/utils/nodeIds.d.ts +31 -0
- package/dist/utils/nodeIds.js +42 -0
- package/dist/utils/nodeStatus.d.ts +1 -1
- package/dist/utils/nodeStatus.js +48 -48
- package/dist/utils/nodeSwap.d.ts +221 -0
- package/dist/utils/nodeSwap.js +680 -0
- package/dist/utils/nodeTypes.d.ts +1 -1
- package/dist/utils/nodeTypes.js +20 -21
- package/dist/utils/nodeWrapper.d.ts +7 -7
- package/dist/utils/nodeWrapper.js +19 -21
- package/dist/utils/performanceUtils.d.ts +1 -1
- package/dist/utils/performanceUtils.js +1 -2
- package/dist/utils/portUtils.d.ts +2 -2
- package/dist/utils/portUtils.js +1 -1
- package/dist/utils/sanitize.js +1 -1
- package/dist/utils/uischema.d.ts +2 -2
- package/dist/utils/uischema.js +8 -8
- package/dist/utils/validation.js +8 -8
- package/package.json +12 -11
- package/dist/helpers/nodeLayoutHelper.d.ts +0 -14
- package/dist/helpers/nodeLayoutHelper.js +0 -19
|
@@ -0,0 +1,1133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command Executor
|
|
3
|
+
*
|
|
4
|
+
* Executes parsed Command objects against a CommandContext,
|
|
5
|
+
* dispatching mutations to the workflow store.
|
|
6
|
+
*
|
|
7
|
+
* @module commands/executor
|
|
8
|
+
*/
|
|
9
|
+
import { generateNodeId } from '../utils/nodeIds.js';
|
|
10
|
+
import { extractConfigDefaults } from '../utils/nodeIds.js';
|
|
11
|
+
import { computeAutoPosition } from './positioner.js';
|
|
12
|
+
import { buildHandleId, extractPortId } from '../utils/handleIds.js';
|
|
13
|
+
import { applyConnectionStyling } from '../utils/edgeStyling.js';
|
|
14
|
+
import { computeSwapPreview, executeSwap } from '../utils/nodeSwap.js';
|
|
15
|
+
import { computeAutoLayout, computeBeautifyLayout } from '../adapters/agentspec/autoLayout.js';
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Internal Helpers
|
|
18
|
+
// ============================================================================
|
|
19
|
+
/**
|
|
20
|
+
* Convert an internal namespaced node ID to a DSL short ID.
|
|
21
|
+
* "agentspec.llm_node.1" → "llm_node.1"
|
|
22
|
+
* "llm_node.1" → "llm_node.1" (no namespace, unchanged)
|
|
23
|
+
*/
|
|
24
|
+
export function toShortId(internalId) {
|
|
25
|
+
// Node IDs are <namespace>.<typeId>.<number> or <typeId>.<number>
|
|
26
|
+
// The namespace is the first segment if there are 3+ dot-separated parts
|
|
27
|
+
// and the last segment is a number
|
|
28
|
+
const parts = internalId.split('.');
|
|
29
|
+
if (parts.length >= 3) {
|
|
30
|
+
const lastPart = parts[parts.length - 1];
|
|
31
|
+
if (/^\d+$/.test(lastPart)) {
|
|
32
|
+
// Strip the first segment (namespace)
|
|
33
|
+
return parts.slice(1).join('.');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return internalId;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Convert a namespaced type ID to a short type ID.
|
|
40
|
+
* "agentspec.llm_node" → "llm_node"
|
|
41
|
+
* "llm_node" → "llm_node" (no namespace, unchanged)
|
|
42
|
+
*/
|
|
43
|
+
export function toShortTypeId(typeId) {
|
|
44
|
+
const dotIndex = typeId.indexOf('.');
|
|
45
|
+
if (dotIndex !== -1) {
|
|
46
|
+
return typeId.substring(dotIndex + 1);
|
|
47
|
+
}
|
|
48
|
+
return typeId;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Resolve a DSL short ID (e.g. "llm_node.1") to the actual workflow node.
|
|
52
|
+
* Tries direct match first, then looks for a namespaced match.
|
|
53
|
+
*/
|
|
54
|
+
export function resolveNode(shortId, nodes) {
|
|
55
|
+
// Direct match
|
|
56
|
+
const direct = nodes.find((n) => n.id === shortId);
|
|
57
|
+
if (direct)
|
|
58
|
+
return direct;
|
|
59
|
+
// Namespaced match: node ID ends with ".<shortId>"
|
|
60
|
+
return nodes.find((n) => n.id.endsWith(`.${shortId}`));
|
|
61
|
+
}
|
|
62
|
+
// ============================================================================
|
|
63
|
+
// Command Handlers
|
|
64
|
+
// ============================================================================
|
|
65
|
+
function executeAddNode(command, context) {
|
|
66
|
+
const workflow = context.getWorkflow();
|
|
67
|
+
if (!workflow) {
|
|
68
|
+
return { ok: false, error: 'No workflow loaded', code: 'NO_WORKFLOW' };
|
|
69
|
+
}
|
|
70
|
+
const metadata = context.typeMap.get(command.nodeTypeId);
|
|
71
|
+
if (!metadata) {
|
|
72
|
+
return {
|
|
73
|
+
ok: false,
|
|
74
|
+
error: `Unknown node type: ${command.nodeTypeId}`,
|
|
75
|
+
code: 'NODE_TYPE_NOT_FOUND'
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
const position = command.position ?? computeAutoPosition(workflow.nodes);
|
|
79
|
+
const nodeId = generateNodeId(metadata.id, workflow.nodes);
|
|
80
|
+
const config = extractConfigDefaults(metadata.configSchema);
|
|
81
|
+
const node = {
|
|
82
|
+
id: nodeId,
|
|
83
|
+
type: 'universalNode',
|
|
84
|
+
position,
|
|
85
|
+
deletable: true,
|
|
86
|
+
data: {
|
|
87
|
+
label: metadata.name,
|
|
88
|
+
config,
|
|
89
|
+
metadata,
|
|
90
|
+
nodeId
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
context.dispatch.addNode(node);
|
|
94
|
+
const shortId = toShortId(nodeId);
|
|
95
|
+
const resultData = {
|
|
96
|
+
nodeId: shortId,
|
|
97
|
+
type: command.nodeTypeId,
|
|
98
|
+
label: metadata.name,
|
|
99
|
+
position
|
|
100
|
+
};
|
|
101
|
+
return {
|
|
102
|
+
ok: true,
|
|
103
|
+
message: `Added ${metadata.name} as ${shortId}`,
|
|
104
|
+
data: resultData
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function executeDeleteNode(command, context) {
|
|
108
|
+
const workflow = context.getWorkflow();
|
|
109
|
+
if (!workflow) {
|
|
110
|
+
return { ok: false, error: 'No workflow loaded', code: 'NO_WORKFLOW' };
|
|
111
|
+
}
|
|
112
|
+
const node = resolveNode(command.nodeId, workflow.nodes);
|
|
113
|
+
if (!node) {
|
|
114
|
+
return {
|
|
115
|
+
ok: false,
|
|
116
|
+
error: `Node not found: ${command.nodeId}`,
|
|
117
|
+
code: 'NODE_NOT_FOUND'
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
context.dispatch.removeNode(node.id);
|
|
121
|
+
return {
|
|
122
|
+
ok: true,
|
|
123
|
+
message: `Deleted node ${toShortId(node.id)}`
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function executeRenameNode(command, context) {
|
|
127
|
+
const workflow = context.getWorkflow();
|
|
128
|
+
if (!workflow) {
|
|
129
|
+
return { ok: false, error: 'No workflow loaded', code: 'NO_WORKFLOW' };
|
|
130
|
+
}
|
|
131
|
+
const node = resolveNode(command.nodeId, workflow.nodes);
|
|
132
|
+
if (!node) {
|
|
133
|
+
return {
|
|
134
|
+
ok: false,
|
|
135
|
+
error: `Node not found: ${command.nodeId}`,
|
|
136
|
+
code: 'NODE_NOT_FOUND'
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
context.dispatch.updateNode(node.id, {
|
|
140
|
+
data: { ...node.data, label: command.label }
|
|
141
|
+
});
|
|
142
|
+
return {
|
|
143
|
+
ok: true,
|
|
144
|
+
message: `Renamed ${toShortId(node.id)} to "${command.label}"`
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Parse a raw value string into the appropriate JS type.
|
|
149
|
+
* Priority: quoted string (preserved) > JSON > number > boolean > raw string
|
|
150
|
+
*/
|
|
151
|
+
function parseConfigValue(raw) {
|
|
152
|
+
// Double-quoted string — JSON-unescape so \n, \t, \\ etc. work
|
|
153
|
+
if (raw.startsWith('"') && raw.endsWith('"')) {
|
|
154
|
+
try {
|
|
155
|
+
return JSON.parse(raw);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return raw.slice(1, -1);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// Single-quoted string — strip quotes (no escape processing)
|
|
162
|
+
if (raw.startsWith("'") && raw.endsWith("'")) {
|
|
163
|
+
return raw.slice(1, -1);
|
|
164
|
+
}
|
|
165
|
+
// Try JSON (arrays, objects, null)
|
|
166
|
+
if (raw.startsWith('[') || raw.startsWith('{') || raw === 'null') {
|
|
167
|
+
try {
|
|
168
|
+
return JSON.parse(raw);
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// fall through
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
// Number
|
|
175
|
+
if (raw !== '' && !isNaN(Number(raw))) {
|
|
176
|
+
return Number(raw);
|
|
177
|
+
}
|
|
178
|
+
// Boolean
|
|
179
|
+
if (raw === 'true')
|
|
180
|
+
return true;
|
|
181
|
+
if (raw === 'false')
|
|
182
|
+
return false;
|
|
183
|
+
// Raw string
|
|
184
|
+
return raw;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Validate a parsed config value against a ConfigProperty schema.
|
|
188
|
+
* Returns an array of validation warnings (empty if valid or no schema).
|
|
189
|
+
*/
|
|
190
|
+
function validateConfigValue(key, value, property) {
|
|
191
|
+
if (!property)
|
|
192
|
+
return [];
|
|
193
|
+
const warnings = [];
|
|
194
|
+
// Enum validation
|
|
195
|
+
if (property.enum && property.enum.length > 0) {
|
|
196
|
+
if (!property.enum.includes(value)) {
|
|
197
|
+
warnings.push({
|
|
198
|
+
type: 'enum',
|
|
199
|
+
message: `Value ${JSON.stringify(value)} is not in allowed values: ${property.enum.map((v) => JSON.stringify(v)).join(', ')}`,
|
|
200
|
+
allowedValues: property.enum
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
// Type validation
|
|
205
|
+
if (property.type) {
|
|
206
|
+
const actualType = Array.isArray(value) ? 'array' : typeof value;
|
|
207
|
+
const expectedType = property.type === 'integer' ? 'number' : property.type;
|
|
208
|
+
// Only warn if there's a genuine mismatch (null/object handled specially)
|
|
209
|
+
if (value !== null &&
|
|
210
|
+
actualType !== expectedType &&
|
|
211
|
+
!(expectedType === 'object' && actualType === 'object')) {
|
|
212
|
+
warnings.push({
|
|
213
|
+
type: 'type_mismatch',
|
|
214
|
+
message: `Expected type '${property.type}' but got '${actualType}'`,
|
|
215
|
+
expectedType: property.type,
|
|
216
|
+
actualType
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return warnings;
|
|
221
|
+
}
|
|
222
|
+
function executeSetConfig(command, context) {
|
|
223
|
+
const workflow = context.getWorkflow();
|
|
224
|
+
if (!workflow) {
|
|
225
|
+
return { ok: false, error: 'No workflow loaded', code: 'NO_WORKFLOW' };
|
|
226
|
+
}
|
|
227
|
+
const node = resolveNode(command.nodeId, workflow.nodes);
|
|
228
|
+
if (!node) {
|
|
229
|
+
return {
|
|
230
|
+
ok: false,
|
|
231
|
+
error: `Node not found: ${command.nodeId}`,
|
|
232
|
+
code: 'NODE_NOT_FOUND'
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
const parsedValue = parseConfigValue(command.value);
|
|
236
|
+
// Validate against configSchema if present
|
|
237
|
+
const metadata = node.data.metadata;
|
|
238
|
+
const configSchema = metadata?.configSchema;
|
|
239
|
+
const property = configSchema?.properties?.[command.key];
|
|
240
|
+
const warnings = validateConfigValue(command.key, parsedValue, property);
|
|
241
|
+
// In strict mode, validation warnings become errors
|
|
242
|
+
if (command.strict && warnings && warnings.length > 0) {
|
|
243
|
+
const messages = warnings.map((w) => w.message).join('; ');
|
|
244
|
+
return {
|
|
245
|
+
ok: false,
|
|
246
|
+
error: `Config validation failed for ${toShortId(node.id)}:${command.key}: ${messages}`,
|
|
247
|
+
code: 'CONFIG_VALIDATION_ERROR'
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
const updatedConfig = { ...node.data.config, [command.key]: parsedValue };
|
|
251
|
+
context.dispatch.updateNode(node.id, {
|
|
252
|
+
data: { ...node.data, config: updatedConfig }
|
|
253
|
+
});
|
|
254
|
+
const resultData = {
|
|
255
|
+
nodeId: toShortId(node.id),
|
|
256
|
+
key: command.key,
|
|
257
|
+
value: parsedValue,
|
|
258
|
+
...(warnings && warnings.length > 0 ? { warnings } : {})
|
|
259
|
+
};
|
|
260
|
+
const warningMsg = warnings && warnings.length > 0
|
|
261
|
+
? ` (warning: ${warnings.map((w) => w.message).join('; ')})`
|
|
262
|
+
: '';
|
|
263
|
+
return {
|
|
264
|
+
ok: true,
|
|
265
|
+
message: `Set ${toShortId(node.id)}:${command.key} = ${JSON.stringify(parsedValue)}${warningMsg}`,
|
|
266
|
+
data: resultData
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function executeGetConfig(command, context) {
|
|
270
|
+
const workflow = context.getWorkflow();
|
|
271
|
+
if (!workflow) {
|
|
272
|
+
return { ok: false, error: 'No workflow loaded', code: 'NO_WORKFLOW' };
|
|
273
|
+
}
|
|
274
|
+
const node = resolveNode(command.nodeId, workflow.nodes);
|
|
275
|
+
if (!node) {
|
|
276
|
+
return {
|
|
277
|
+
ok: false,
|
|
278
|
+
error: `Node not found: ${command.nodeId}`,
|
|
279
|
+
code: 'NODE_NOT_FOUND'
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
const config = node.data.config;
|
|
283
|
+
if (!config || !(command.key in config)) {
|
|
284
|
+
return {
|
|
285
|
+
ok: false,
|
|
286
|
+
error: `Config key not found: ${command.key} on ${toShortId(node.id)}`,
|
|
287
|
+
code: 'CONFIG_KEY_NOT_FOUND'
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
const resultData = {
|
|
291
|
+
nodeId: toShortId(node.id),
|
|
292
|
+
key: command.key,
|
|
293
|
+
value: config[command.key]
|
|
294
|
+
};
|
|
295
|
+
return {
|
|
296
|
+
ok: true,
|
|
297
|
+
message: `${toShortId(node.id)}:${command.key} = ${JSON.stringify(config[command.key])}`,
|
|
298
|
+
data: resultData
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
function executeInfo(command, context) {
|
|
302
|
+
const workflow = context.getWorkflow();
|
|
303
|
+
if (!workflow) {
|
|
304
|
+
return { ok: false, error: 'No workflow loaded', code: 'NO_WORKFLOW' };
|
|
305
|
+
}
|
|
306
|
+
const node = resolveNode(command.nodeId, workflow.nodes);
|
|
307
|
+
if (!node) {
|
|
308
|
+
return {
|
|
309
|
+
ok: false,
|
|
310
|
+
error: `Node not found: ${command.nodeId}`,
|
|
311
|
+
code: 'NODE_NOT_FOUND'
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
const metadata = node.data.metadata;
|
|
315
|
+
const shortId = toShortId(node.id);
|
|
316
|
+
const inputs = (metadata?.inputs ?? []).map((p) => ({
|
|
317
|
+
portId: p.id,
|
|
318
|
+
name: p.name,
|
|
319
|
+
dataType: p.dataType
|
|
320
|
+
}));
|
|
321
|
+
const staticOutputs = (metadata?.outputs ?? []).map((p) => ({
|
|
322
|
+
portId: p.id,
|
|
323
|
+
name: p.name,
|
|
324
|
+
dataType: p.dataType
|
|
325
|
+
}));
|
|
326
|
+
// Gateway nodes expose dynamic branch ports from config.branches
|
|
327
|
+
const branchOutputs = metadata?.type === 'gateway'
|
|
328
|
+
? (node.data.config?.branches ?? []).map((b) => ({
|
|
329
|
+
portId: b.name,
|
|
330
|
+
name: b.name,
|
|
331
|
+
dataType: 'trigger'
|
|
332
|
+
}))
|
|
333
|
+
: [];
|
|
334
|
+
const outputs = [...staticOutputs, ...branchOutputs];
|
|
335
|
+
// Build connected edges info
|
|
336
|
+
const connectedEdges = [];
|
|
337
|
+
for (const edge of workflow.edges) {
|
|
338
|
+
if (edge.source === node.id) {
|
|
339
|
+
connectedEdges.push({
|
|
340
|
+
edgeId: edge.id,
|
|
341
|
+
direction: 'outgoing',
|
|
342
|
+
remoteNodeId: toShortId(edge.target),
|
|
343
|
+
remotePort: extractPortId(edge.targetHandle) ?? '',
|
|
344
|
+
localPort: extractPortId(edge.sourceHandle) ?? ''
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
else if (edge.target === node.id) {
|
|
348
|
+
connectedEdges.push({
|
|
349
|
+
edgeId: edge.id,
|
|
350
|
+
direction: 'incoming',
|
|
351
|
+
remoteNodeId: toShortId(edge.source),
|
|
352
|
+
remotePort: extractPortId(edge.sourceHandle) ?? '',
|
|
353
|
+
localPort: extractPortId(edge.targetHandle) ?? ''
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
const resultData = {
|
|
358
|
+
nodeId: shortId,
|
|
359
|
+
label: node.data.label ?? metadata?.name ?? '',
|
|
360
|
+
type: metadata?.id ? toShortTypeId(metadata.id) : '',
|
|
361
|
+
position: node.position,
|
|
362
|
+
config: node.data.config ?? {},
|
|
363
|
+
inputs,
|
|
364
|
+
outputs,
|
|
365
|
+
connectedEdges
|
|
366
|
+
};
|
|
367
|
+
return {
|
|
368
|
+
ok: true,
|
|
369
|
+
message: `Info for ${shortId}`,
|
|
370
|
+
data: resultData
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
// ============================================================================
|
|
374
|
+
// Connection Operations
|
|
375
|
+
// ============================================================================
|
|
376
|
+
/**
|
|
377
|
+
* Find a port in a node's metadata by port ID, checking both inputs and outputs.
|
|
378
|
+
* Returns the port and its direction.
|
|
379
|
+
*/
|
|
380
|
+
function findPort(node, portId, preferDirection) {
|
|
381
|
+
const metadata = node.data?.metadata;
|
|
382
|
+
if (!metadata)
|
|
383
|
+
return null;
|
|
384
|
+
const outputPort = metadata.outputs?.find((p) => p.id === portId);
|
|
385
|
+
const inputPort = metadata.inputs?.find((p) => p.id === portId);
|
|
386
|
+
if (preferDirection === 'output') {
|
|
387
|
+
if (outputPort)
|
|
388
|
+
return { port: outputPort, direction: 'output' };
|
|
389
|
+
if (inputPort)
|
|
390
|
+
return { port: inputPort, direction: 'input' };
|
|
391
|
+
}
|
|
392
|
+
else if (preferDirection === 'input') {
|
|
393
|
+
if (inputPort)
|
|
394
|
+
return { port: inputPort, direction: 'input' };
|
|
395
|
+
if (outputPort)
|
|
396
|
+
return { port: outputPort, direction: 'output' };
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
if (outputPort)
|
|
400
|
+
return { port: outputPort, direction: 'output' };
|
|
401
|
+
if (inputPort)
|
|
402
|
+
return { port: inputPort, direction: 'input' };
|
|
403
|
+
}
|
|
404
|
+
// Gateway nodes have dynamic branch ports stored in config.branches, not metadata.outputs
|
|
405
|
+
if (metadata.type === 'gateway') {
|
|
406
|
+
const branches = node.data.config?.branches;
|
|
407
|
+
if (branches?.some((b) => b.name === portId)) {
|
|
408
|
+
return {
|
|
409
|
+
port: { id: portId, name: portId, dataType: 'trigger' },
|
|
410
|
+
direction: 'output'
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
416
|
+
function executeConnect(command, context) {
|
|
417
|
+
const workflow = context.getWorkflow();
|
|
418
|
+
if (!workflow) {
|
|
419
|
+
return { ok: false, error: 'No workflow loaded', code: 'NO_WORKFLOW' };
|
|
420
|
+
}
|
|
421
|
+
// Resolve both nodes
|
|
422
|
+
const sourceNode = resolveNode(command.sourceNodeId, workflow.nodes);
|
|
423
|
+
if (!sourceNode) {
|
|
424
|
+
return {
|
|
425
|
+
ok: false,
|
|
426
|
+
error: `Node not found: ${command.sourceNodeId}`,
|
|
427
|
+
code: 'NODE_NOT_FOUND'
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
const targetNode = resolveNode(command.targetNodeId, workflow.nodes);
|
|
431
|
+
if (!targetNode) {
|
|
432
|
+
return {
|
|
433
|
+
ok: false,
|
|
434
|
+
error: `Node not found: ${command.targetNodeId}`,
|
|
435
|
+
code: 'NODE_NOT_FOUND'
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
// Look up ports in metadata to determine direction.
|
|
439
|
+
// Since connections always flow output → input, prefer the output port on the
|
|
440
|
+
// source node and the input port on the target node when the same port name
|
|
441
|
+
// exists in both directions on a node.
|
|
442
|
+
const sourcePortInfo = findPort(sourceNode, command.sourcePort, 'output');
|
|
443
|
+
if (!sourcePortInfo) {
|
|
444
|
+
return {
|
|
445
|
+
ok: false,
|
|
446
|
+
error: `Port '${command.sourcePort}' not found on node ${toShortId(sourceNode.id)}`,
|
|
447
|
+
code: 'PORT_NOT_FOUND'
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
const targetPortInfo = findPort(targetNode, command.targetPort, 'input');
|
|
451
|
+
if (!targetPortInfo) {
|
|
452
|
+
return {
|
|
453
|
+
ok: false,
|
|
454
|
+
error: `Port '${command.targetPort}' not found on node ${toShortId(targetNode.id)}`,
|
|
455
|
+
code: 'PORT_NOT_FOUND'
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
// Validate directions: source port must be output, target port must be input
|
|
459
|
+
if (sourcePortInfo.direction !== 'output' || targetPortInfo.direction !== 'input') {
|
|
460
|
+
// Check if they're reversed
|
|
461
|
+
if (sourcePortInfo.direction === 'input' && targetPortInfo.direction === 'output') {
|
|
462
|
+
return {
|
|
463
|
+
ok: false,
|
|
464
|
+
error: `Connection direction reversed: '${command.sourcePort}' is an input on ${toShortId(sourceNode.id)} and '${command.targetPort}' is an output on ${toShortId(targetNode.id)}. Swap source and target.`,
|
|
465
|
+
code: 'INVALID_CONNECTION'
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
// One of them is the wrong direction
|
|
469
|
+
if (sourcePortInfo.direction !== 'output') {
|
|
470
|
+
return {
|
|
471
|
+
ok: false,
|
|
472
|
+
error: `Port '${command.sourcePort}' on ${toShortId(sourceNode.id)} is an input, not an output (per node metadata)`,
|
|
473
|
+
code: 'INVALID_CONNECTION'
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
ok: false,
|
|
478
|
+
error: `Port '${command.targetPort}' on ${toShortId(targetNode.id)} is an output, not an input (per node metadata)`,
|
|
479
|
+
code: 'INVALID_CONNECTION'
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
// Build handle IDs
|
|
483
|
+
const sourceHandle = buildHandleId(sourceNode.id, 'output', command.sourcePort);
|
|
484
|
+
const targetHandle = buildHandleId(targetNode.id, 'input', command.targetPort);
|
|
485
|
+
// Generate edge ID
|
|
486
|
+
const edgeId = `${sourceNode.id}-${sourceHandle}-${targetNode.id}-${targetHandle}`;
|
|
487
|
+
// Build edge
|
|
488
|
+
const edge = {
|
|
489
|
+
id: edgeId,
|
|
490
|
+
source: sourceNode.id,
|
|
491
|
+
target: targetNode.id,
|
|
492
|
+
sourceHandle,
|
|
493
|
+
targetHandle
|
|
494
|
+
};
|
|
495
|
+
// Apply styling
|
|
496
|
+
applyConnectionStyling(edge, sourceNode, targetNode);
|
|
497
|
+
// Dispatch
|
|
498
|
+
context.dispatch.addEdge(edge);
|
|
499
|
+
return {
|
|
500
|
+
ok: true,
|
|
501
|
+
message: `Connected ${toShortId(sourceNode.id)}:${command.sourcePort} → ${toShortId(targetNode.id)}:${command.targetPort}`
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
function executeDisconnectPorts(command, context) {
|
|
505
|
+
const workflow = context.getWorkflow();
|
|
506
|
+
if (!workflow) {
|
|
507
|
+
return { ok: false, error: 'No workflow loaded', code: 'NO_WORKFLOW' };
|
|
508
|
+
}
|
|
509
|
+
// Resolve both nodes
|
|
510
|
+
const sourceNode = resolveNode(command.sourceNodeId, workflow.nodes);
|
|
511
|
+
if (!sourceNode) {
|
|
512
|
+
return {
|
|
513
|
+
ok: false,
|
|
514
|
+
error: `Node not found: ${command.sourceNodeId}`,
|
|
515
|
+
code: 'NODE_NOT_FOUND'
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
const targetNode = resolveNode(command.targetNodeId, workflow.nodes);
|
|
519
|
+
if (!targetNode) {
|
|
520
|
+
return {
|
|
521
|
+
ok: false,
|
|
522
|
+
error: `Node not found: ${command.targetNodeId}`,
|
|
523
|
+
code: 'NODE_NOT_FOUND'
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
// Find the matching edge by checking source/target node IDs and port IDs
|
|
527
|
+
const edge = workflow.edges.find((e) => {
|
|
528
|
+
if (e.source !== sourceNode.id || e.target !== targetNode.id)
|
|
529
|
+
return false;
|
|
530
|
+
const sourcePort = extractPortId(e.sourceHandle);
|
|
531
|
+
const targetPort = extractPortId(e.targetHandle);
|
|
532
|
+
return sourcePort === command.sourcePort && targetPort === command.targetPort;
|
|
533
|
+
});
|
|
534
|
+
if (!edge) {
|
|
535
|
+
return {
|
|
536
|
+
ok: false,
|
|
537
|
+
error: `No edge found from ${toShortId(sourceNode.id)}:${command.sourcePort} to ${toShortId(targetNode.id)}:${command.targetPort}`,
|
|
538
|
+
code: 'EDGE_NOT_FOUND'
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
context.dispatch.removeEdge(edge.id);
|
|
542
|
+
return {
|
|
543
|
+
ok: true,
|
|
544
|
+
message: `Disconnected ${toShortId(sourceNode.id)}:${command.sourcePort} from ${toShortId(targetNode.id)}:${command.targetPort}`
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
function executeDisconnectNode(command, context) {
|
|
548
|
+
const workflow = context.getWorkflow();
|
|
549
|
+
if (!workflow) {
|
|
550
|
+
return { ok: false, error: 'No workflow loaded', code: 'NO_WORKFLOW' };
|
|
551
|
+
}
|
|
552
|
+
const node = resolveNode(command.nodeId, workflow.nodes);
|
|
553
|
+
if (!node) {
|
|
554
|
+
return {
|
|
555
|
+
ok: false,
|
|
556
|
+
error: `Node not found: ${command.nodeId}`,
|
|
557
|
+
code: 'NODE_NOT_FOUND'
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
// Find all edges connected to this node
|
|
561
|
+
const connectedEdges = workflow.edges.filter((e) => e.source === node.id || e.target === node.id);
|
|
562
|
+
for (const edge of connectedEdges) {
|
|
563
|
+
context.dispatch.removeEdge(edge.id);
|
|
564
|
+
}
|
|
565
|
+
return {
|
|
566
|
+
ok: true,
|
|
567
|
+
message: `Disconnected ${connectedEdges.length} edge(s) from ${toShortId(node.id)}`
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
// ============================================================================
|
|
571
|
+
// List & Help Operations
|
|
572
|
+
// ============================================================================
|
|
573
|
+
function executeListNodes(context) {
|
|
574
|
+
const workflow = context.getWorkflow();
|
|
575
|
+
if (!workflow) {
|
|
576
|
+
return { ok: false, error: 'No workflow loaded', code: 'NO_WORKFLOW' };
|
|
577
|
+
}
|
|
578
|
+
const nodes = workflow.nodes.map((n) => ({
|
|
579
|
+
nodeId: toShortId(n.id),
|
|
580
|
+
label: n.data.label ?? n.data.metadata?.name ?? '',
|
|
581
|
+
type: n.data.metadata?.id ? toShortTypeId(n.data.metadata.id) : ''
|
|
582
|
+
}));
|
|
583
|
+
const resultData = { nodes };
|
|
584
|
+
return {
|
|
585
|
+
ok: true,
|
|
586
|
+
message: nodes.length === 0
|
|
587
|
+
? 'No nodes in workflow'
|
|
588
|
+
: `${nodes.length} node(s): ${nodes.map((n) => n.nodeId).join(', ')}`,
|
|
589
|
+
data: resultData
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
function executeListEdges(context) {
|
|
593
|
+
const workflow = context.getWorkflow();
|
|
594
|
+
if (!workflow) {
|
|
595
|
+
return { ok: false, error: 'No workflow loaded', code: 'NO_WORKFLOW' };
|
|
596
|
+
}
|
|
597
|
+
const edges = workflow.edges.map((e) => ({
|
|
598
|
+
edgeId: e.id,
|
|
599
|
+
sourceNodeId: toShortId(e.source),
|
|
600
|
+
sourcePort: extractPortId(e.sourceHandle) ?? '',
|
|
601
|
+
targetNodeId: toShortId(e.target),
|
|
602
|
+
targetPort: extractPortId(e.targetHandle) ?? ''
|
|
603
|
+
}));
|
|
604
|
+
const resultData = { edges };
|
|
605
|
+
return {
|
|
606
|
+
ok: true,
|
|
607
|
+
message: edges.length === 0 ? 'No edges in workflow' : `${edges.length} edge(s)`,
|
|
608
|
+
data: resultData
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
function executeListTypes(context) {
|
|
612
|
+
const types = context.nodeTypes.map((m) => ({
|
|
613
|
+
typeId: toShortTypeId(m.id),
|
|
614
|
+
name: m.name,
|
|
615
|
+
category: m.category
|
|
616
|
+
}));
|
|
617
|
+
const resultData = { types };
|
|
618
|
+
return {
|
|
619
|
+
ok: true,
|
|
620
|
+
message: `${types.length} type(s) available`,
|
|
621
|
+
data: resultData
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
/** All command help entries */
|
|
625
|
+
export const COMMAND_HELP = [
|
|
626
|
+
{
|
|
627
|
+
name: 'add',
|
|
628
|
+
syntax: 'add <type> [at <x>,<y>]',
|
|
629
|
+
description: 'Add a new node of the specified type'
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
name: 'delete',
|
|
633
|
+
syntax: 'delete <nodeId>',
|
|
634
|
+
description: 'Delete a node and its connections'
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
name: 'rename',
|
|
638
|
+
syntax: 'rename <nodeId> <label>',
|
|
639
|
+
description: "Rename a node's display label"
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
name: 'set',
|
|
643
|
+
syntax: 'set <nodeId>:<key> <value>',
|
|
644
|
+
description: 'Set a config value on a node'
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
name: 'get',
|
|
648
|
+
syntax: 'get <nodeId>:<key>',
|
|
649
|
+
description: 'Get a config value from a node'
|
|
650
|
+
},
|
|
651
|
+
{
|
|
652
|
+
name: 'connect',
|
|
653
|
+
syntax: 'connect <nid>:<port> to <nid>:<port>',
|
|
654
|
+
description: 'Connect two node ports'
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
name: 'disconnect',
|
|
658
|
+
syntax: 'disconnect <nid>:<port> from <nid>:<port>',
|
|
659
|
+
description: 'Disconnect two node ports'
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
name: 'disconnect',
|
|
663
|
+
syntax: 'disconnect <nodeId>',
|
|
664
|
+
description: 'Disconnect all edges from a node'
|
|
665
|
+
},
|
|
666
|
+
{
|
|
667
|
+
name: 'list',
|
|
668
|
+
syntax: 'list nodes|edges|types',
|
|
669
|
+
description: 'List workflow nodes, edges, or available types'
|
|
670
|
+
},
|
|
671
|
+
{
|
|
672
|
+
name: 'info',
|
|
673
|
+
syntax: 'info <nodeId>',
|
|
674
|
+
description: 'Show detailed info about a node'
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
name: 'config',
|
|
678
|
+
syntax: 'config <nodeId>',
|
|
679
|
+
description: 'Open the config panel for a node'
|
|
680
|
+
},
|
|
681
|
+
{
|
|
682
|
+
name: 'select',
|
|
683
|
+
syntax: 'select <nodeId>',
|
|
684
|
+
description: 'Select a node on the canvas'
|
|
685
|
+
},
|
|
686
|
+
{
|
|
687
|
+
name: 'swap',
|
|
688
|
+
syntax: 'swap <nodeId> with <type>',
|
|
689
|
+
description: "Replace a node's type, preserving connections"
|
|
690
|
+
},
|
|
691
|
+
{
|
|
692
|
+
name: 'move',
|
|
693
|
+
syntax: 'move <nodeId> to <x>,<y>',
|
|
694
|
+
description: 'Move a node to a position'
|
|
695
|
+
},
|
|
696
|
+
{
|
|
697
|
+
name: 'layout',
|
|
698
|
+
syntax: 'layout auto [--direction horizontal|vertical]',
|
|
699
|
+
description: 'Auto-arrange all nodes'
|
|
700
|
+
},
|
|
701
|
+
{
|
|
702
|
+
name: 'layout',
|
|
703
|
+
syntax: 'layout beautify',
|
|
704
|
+
description: 'Normalize spacing while preserving node arrangement'
|
|
705
|
+
},
|
|
706
|
+
{ name: 'undo', syntax: 'undo', description: 'Undo the last action' },
|
|
707
|
+
{ name: 'redo', syntax: 'redo', description: 'Redo the last undone action' },
|
|
708
|
+
{
|
|
709
|
+
name: 'help',
|
|
710
|
+
syntax: 'help [<command>]',
|
|
711
|
+
description: 'Show help for all or a specific command'
|
|
712
|
+
},
|
|
713
|
+
{ name: 'clear', syntax: 'clear', description: 'Remove all nodes and edges' },
|
|
714
|
+
{
|
|
715
|
+
name: 'canvas',
|
|
716
|
+
syntax: 'canvas fitview',
|
|
717
|
+
description: 'Fit all nodes into the viewport'
|
|
718
|
+
},
|
|
719
|
+
{
|
|
720
|
+
name: 'canvas',
|
|
721
|
+
syntax: 'canvas zoom in',
|
|
722
|
+
description: 'Zoom in on the canvas'
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
name: 'canvas',
|
|
726
|
+
syntax: 'canvas zoom out',
|
|
727
|
+
description: 'Zoom out on the canvas'
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
name: 'canvas',
|
|
731
|
+
syntax: 'canvas zoom <level>',
|
|
732
|
+
description: 'Set zoom to a specific level (e.g. 1.5)'
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
name: 'canvas',
|
|
736
|
+
syntax: 'canvas pan <x>,<y>',
|
|
737
|
+
description: 'Pan the canvas to center on a position'
|
|
738
|
+
},
|
|
739
|
+
{
|
|
740
|
+
name: 'canvas',
|
|
741
|
+
syntax: 'canvas reset',
|
|
742
|
+
description: 'Reset viewport to default position and zoom'
|
|
743
|
+
}
|
|
744
|
+
];
|
|
745
|
+
function executeHelp(command) {
|
|
746
|
+
let commands;
|
|
747
|
+
if (command.command) {
|
|
748
|
+
commands = COMMAND_HELP.filter((h) => h.name === command.command);
|
|
749
|
+
if (commands.length === 0) {
|
|
750
|
+
commands = COMMAND_HELP; // Unknown command name — show all
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
else {
|
|
754
|
+
commands = COMMAND_HELP;
|
|
755
|
+
}
|
|
756
|
+
const resultData = { commands };
|
|
757
|
+
const message = commands.map((c) => ` ${c.syntax} — ${c.description}`).join('\n');
|
|
758
|
+
return {
|
|
759
|
+
ok: true,
|
|
760
|
+
message: command.command
|
|
761
|
+
? `Help for '${command.command}':\n${message}`
|
|
762
|
+
: `Available commands:\n${message}`,
|
|
763
|
+
data: resultData
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
// ============================================================================
|
|
767
|
+
// Undo, Redo, Clear, Config, Select
|
|
768
|
+
// ============================================================================
|
|
769
|
+
function executeUndo(context) {
|
|
770
|
+
const success = context.dispatch.undo();
|
|
771
|
+
if (!success) {
|
|
772
|
+
return {
|
|
773
|
+
ok: false,
|
|
774
|
+
error: 'Nothing to undo',
|
|
775
|
+
code: 'UNDO_UNAVAILABLE'
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
return { ok: true, message: 'Undone' };
|
|
779
|
+
}
|
|
780
|
+
function executeRedo(context) {
|
|
781
|
+
const success = context.dispatch.redo();
|
|
782
|
+
if (!success) {
|
|
783
|
+
return {
|
|
784
|
+
ok: false,
|
|
785
|
+
error: 'Nothing to redo',
|
|
786
|
+
code: 'REDO_UNAVAILABLE'
|
|
787
|
+
};
|
|
788
|
+
}
|
|
789
|
+
return { ok: true, message: 'Redone' };
|
|
790
|
+
}
|
|
791
|
+
function executeClear(context) {
|
|
792
|
+
const workflow = context.getWorkflow();
|
|
793
|
+
if (!workflow) {
|
|
794
|
+
return { ok: false, error: 'No workflow loaded', code: 'NO_WORKFLOW' };
|
|
795
|
+
}
|
|
796
|
+
const nodeCount = workflow.nodes.length;
|
|
797
|
+
const edgeCount = workflow.edges.length;
|
|
798
|
+
context.dispatch.batchUpdate({ nodes: [], edges: [] });
|
|
799
|
+
return {
|
|
800
|
+
ok: true,
|
|
801
|
+
message: `Cleared ${nodeCount} node(s) and ${edgeCount} edge(s)`
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
function executeConfigOpen(command, context) {
|
|
805
|
+
const workflow = context.getWorkflow();
|
|
806
|
+
if (!workflow) {
|
|
807
|
+
return { ok: false, error: 'No workflow loaded', code: 'NO_WORKFLOW' };
|
|
808
|
+
}
|
|
809
|
+
const node = resolveNode(command.nodeId, workflow.nodes);
|
|
810
|
+
if (!node) {
|
|
811
|
+
return {
|
|
812
|
+
ok: false,
|
|
813
|
+
error: `Node not found: ${command.nodeId}`,
|
|
814
|
+
code: 'NODE_NOT_FOUND'
|
|
815
|
+
};
|
|
816
|
+
}
|
|
817
|
+
if (context.dispatch.emitUIAction) {
|
|
818
|
+
context.dispatch.emitUIAction({ type: 'open_config', nodeId: node.id });
|
|
819
|
+
return {
|
|
820
|
+
ok: true,
|
|
821
|
+
message: `Opened config for ${toShortId(node.id)}`
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
return {
|
|
825
|
+
ok: true,
|
|
826
|
+
message: `Config open requested for ${toShortId(node.id)} (no UI handler)`,
|
|
827
|
+
uiActionPending: true
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
function executeSelectNode(command, context) {
|
|
831
|
+
const workflow = context.getWorkflow();
|
|
832
|
+
if (!workflow) {
|
|
833
|
+
return { ok: false, error: 'No workflow loaded', code: 'NO_WORKFLOW' };
|
|
834
|
+
}
|
|
835
|
+
const node = resolveNode(command.nodeId, workflow.nodes);
|
|
836
|
+
if (!node) {
|
|
837
|
+
return {
|
|
838
|
+
ok: false,
|
|
839
|
+
error: `Node not found: ${command.nodeId}`,
|
|
840
|
+
code: 'NODE_NOT_FOUND'
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
if (context.dispatch.emitUIAction) {
|
|
844
|
+
context.dispatch.emitUIAction({ type: 'select_node', nodeId: node.id });
|
|
845
|
+
return {
|
|
846
|
+
ok: true,
|
|
847
|
+
message: `Selected ${toShortId(node.id)}`
|
|
848
|
+
};
|
|
849
|
+
}
|
|
850
|
+
return {
|
|
851
|
+
ok: true,
|
|
852
|
+
message: `Select requested for ${toShortId(node.id)} (no UI handler)`,
|
|
853
|
+
uiActionPending: true
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
function executeSwapNode(command, context) {
|
|
857
|
+
const workflow = context.getWorkflow();
|
|
858
|
+
if (!workflow) {
|
|
859
|
+
return { ok: false, error: 'No workflow loaded', code: 'NO_WORKFLOW' };
|
|
860
|
+
}
|
|
861
|
+
const node = resolveNode(command.nodeId, workflow.nodes);
|
|
862
|
+
if (!node) {
|
|
863
|
+
return {
|
|
864
|
+
ok: false,
|
|
865
|
+
error: `Node not found: ${command.nodeId}`,
|
|
866
|
+
code: 'NODE_NOT_FOUND'
|
|
867
|
+
};
|
|
868
|
+
}
|
|
869
|
+
const newMetadata = context.typeMap.get(command.newTypeId);
|
|
870
|
+
if (!newMetadata) {
|
|
871
|
+
return {
|
|
872
|
+
ok: false,
|
|
873
|
+
error: `Unknown node type: ${command.newTypeId}`,
|
|
874
|
+
code: 'NODE_TYPE_NOT_FOUND'
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
const preview = computeSwapPreview(node, newMetadata, workflow.edges, workflow.nodes);
|
|
878
|
+
const swapResult = executeSwap(node, newMetadata, preview, workflow.nodes, workflow.edges);
|
|
879
|
+
if (context.dispatch.swapNode) {
|
|
880
|
+
context.dispatch.swapNode({
|
|
881
|
+
nodes: swapResult.updatedNodes,
|
|
882
|
+
edges: swapResult.updatedEdges
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
else {
|
|
886
|
+
context.dispatch.batchUpdate({
|
|
887
|
+
nodes: swapResult.updatedNodes,
|
|
888
|
+
edges: swapResult.updatedEdges
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
const resultData = {
|
|
892
|
+
oldNodeId: toShortId(node.id),
|
|
893
|
+
newNodeId: toShortId(preview.newNodeId),
|
|
894
|
+
newType: command.newTypeId,
|
|
895
|
+
keptEdges: preview.keptEdges.length,
|
|
896
|
+
droppedEdges: preview.droppedEdges.length,
|
|
897
|
+
hasDataLoss: preview.hasDataLoss,
|
|
898
|
+
configCarriedOver: preview.configCarriedOver,
|
|
899
|
+
configReset: preview.configReset
|
|
900
|
+
};
|
|
901
|
+
const droppedMsg = preview.droppedEdges.length > 0 ? ` (${preview.droppedEdges.length} edge(s) dropped)` : '';
|
|
902
|
+
return {
|
|
903
|
+
ok: true,
|
|
904
|
+
message: `Swapped ${toShortId(node.id)} → ${toShortId(preview.newNodeId)} (${command.newTypeId})${droppedMsg}`,
|
|
905
|
+
data: resultData
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
function executeMoveNode(command, context) {
|
|
909
|
+
const workflow = context.getWorkflow();
|
|
910
|
+
if (!workflow) {
|
|
911
|
+
return { ok: false, error: 'No workflow loaded', code: 'NO_WORKFLOW' };
|
|
912
|
+
}
|
|
913
|
+
const node = resolveNode(command.nodeId, workflow.nodes);
|
|
914
|
+
if (!node) {
|
|
915
|
+
return {
|
|
916
|
+
ok: false,
|
|
917
|
+
error: `Node not found: ${command.nodeId}`,
|
|
918
|
+
code: 'NODE_NOT_FOUND'
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
context.dispatch.updateNode(node.id, {
|
|
922
|
+
position: command.position
|
|
923
|
+
});
|
|
924
|
+
return {
|
|
925
|
+
ok: true,
|
|
926
|
+
message: `Moved ${toShortId(node.id)} to (${command.position.x}, ${command.position.y})`
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
function executeAutoLayout(command, context) {
|
|
930
|
+
const workflow = context.getWorkflow();
|
|
931
|
+
if (!workflow) {
|
|
932
|
+
return { ok: false, error: 'No workflow loaded', code: 'NO_WORKFLOW' };
|
|
933
|
+
}
|
|
934
|
+
if (workflow.nodes.length === 0) {
|
|
935
|
+
return { ok: true, message: 'No nodes to layout' };
|
|
936
|
+
}
|
|
937
|
+
const isVertical = command.direction === 'vertical';
|
|
938
|
+
// Filter out loopback edges (loop_back port) — they go backwards and
|
|
939
|
+
// would reverse the layout direction if included.
|
|
940
|
+
const layoutEdges = workflow.edges.filter((e) => !(e.targetHandle ?? '').includes('-input-loop_back'));
|
|
941
|
+
// Determine start node via in-degree: a node with no incoming edges
|
|
942
|
+
// (from non-loopback edges) is a root. Fall back to leftmost position.
|
|
943
|
+
const inDegree = new Map();
|
|
944
|
+
for (const n of workflow.nodes)
|
|
945
|
+
inDegree.set(n.id, 0);
|
|
946
|
+
for (const e of layoutEdges) {
|
|
947
|
+
inDegree.set(e.target, (inDegree.get(e.target) ?? 0) + 1);
|
|
948
|
+
}
|
|
949
|
+
const startNode = workflow.nodes.find((n) => (inDegree.get(n.id) ?? 0) === 0)?.id ??
|
|
950
|
+
workflow.nodes.reduce((leftmost, n) => (n.position.x < leftmost.position.x ? n : leftmost)).id;
|
|
951
|
+
const flow = {
|
|
952
|
+
component_type: 'flow',
|
|
953
|
+
name: 'layout',
|
|
954
|
+
start_node: startNode,
|
|
955
|
+
nodes: workflow.nodes.map((n) => ({
|
|
956
|
+
component_type: 'start_node',
|
|
957
|
+
name: n.id
|
|
958
|
+
})),
|
|
959
|
+
control_flow_connections: layoutEdges.map((e) => ({
|
|
960
|
+
name: e.id,
|
|
961
|
+
from_node: e.source,
|
|
962
|
+
to_node: e.target
|
|
963
|
+
}))
|
|
964
|
+
};
|
|
965
|
+
// Collect measured node dimensions when available
|
|
966
|
+
const nodeDimensions = new Map();
|
|
967
|
+
for (const n of workflow.nodes) {
|
|
968
|
+
const w = n.measured?.width ?? n.width;
|
|
969
|
+
const h = n.measured?.height ?? n.height;
|
|
970
|
+
if (w != null && h != null) {
|
|
971
|
+
nodeDimensions.set(n.id, { width: w, height: h });
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
const positions = computeAutoLayout(flow, {}, nodeDimensions.size > 0 ? nodeDimensions : undefined);
|
|
975
|
+
// Apply positions — swap x/y for vertical layout
|
|
976
|
+
const updatedNodes = workflow.nodes.map((n) => {
|
|
977
|
+
const pos = positions.get(n.id);
|
|
978
|
+
if (!pos)
|
|
979
|
+
return n;
|
|
980
|
+
return {
|
|
981
|
+
...n,
|
|
982
|
+
position: isVertical ? { x: pos.y, y: pos.x } : pos
|
|
983
|
+
};
|
|
984
|
+
});
|
|
985
|
+
context.dispatch.batchUpdate({ nodes: updatedNodes });
|
|
986
|
+
const direction = command.direction ?? 'horizontal';
|
|
987
|
+
return {
|
|
988
|
+
ok: true,
|
|
989
|
+
message: `Auto-layout applied to ${workflow.nodes.length} nodes (${direction})`
|
|
990
|
+
};
|
|
991
|
+
}
|
|
992
|
+
function executeBeautifyLayout(_command, context) {
|
|
993
|
+
const workflow = context.getWorkflow();
|
|
994
|
+
if (!workflow) {
|
|
995
|
+
return { ok: false, error: 'No workflow loaded', code: 'NO_WORKFLOW' };
|
|
996
|
+
}
|
|
997
|
+
if (workflow.nodes.length === 0) {
|
|
998
|
+
return { ok: true, message: 'No nodes to beautify' };
|
|
999
|
+
}
|
|
1000
|
+
// Collect current positions
|
|
1001
|
+
const currentPositions = new Map();
|
|
1002
|
+
for (const n of workflow.nodes) {
|
|
1003
|
+
currentPositions.set(n.id, { x: n.position.x, y: n.position.y });
|
|
1004
|
+
}
|
|
1005
|
+
// Collect measured node dimensions when available
|
|
1006
|
+
const nodeDimensions = new Map();
|
|
1007
|
+
for (const n of workflow.nodes) {
|
|
1008
|
+
const w = n.measured?.width ?? n.width;
|
|
1009
|
+
const h = n.measured?.height ?? n.height;
|
|
1010
|
+
if (w != null && h != null) {
|
|
1011
|
+
nodeDimensions.set(n.id, { width: w, height: h });
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
const positions = computeBeautifyLayout(currentPositions, {}, nodeDimensions.size > 0 ? nodeDimensions : undefined);
|
|
1015
|
+
// Apply positions
|
|
1016
|
+
const updatedNodes = workflow.nodes.map((n) => {
|
|
1017
|
+
const pos = positions.get(n.id);
|
|
1018
|
+
if (!pos)
|
|
1019
|
+
return n;
|
|
1020
|
+
return { ...n, position: pos };
|
|
1021
|
+
});
|
|
1022
|
+
context.dispatch.batchUpdate({ nodes: updatedNodes });
|
|
1023
|
+
return {
|
|
1024
|
+
ok: true,
|
|
1025
|
+
message: `Beautified layout for ${workflow.nodes.length} nodes`
|
|
1026
|
+
};
|
|
1027
|
+
}
|
|
1028
|
+
// ============================================================================
|
|
1029
|
+
// Canvas Viewport Commands
|
|
1030
|
+
// ============================================================================
|
|
1031
|
+
function emitCanvasAction(context, action, successMessage) {
|
|
1032
|
+
if (context.dispatch.emitUIAction) {
|
|
1033
|
+
context.dispatch.emitUIAction(action);
|
|
1034
|
+
return { ok: true, message: successMessage };
|
|
1035
|
+
}
|
|
1036
|
+
return {
|
|
1037
|
+
ok: true,
|
|
1038
|
+
message: `${successMessage} (no UI handler)`,
|
|
1039
|
+
uiActionPending: true
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
function executeCanvasFitView(context) {
|
|
1043
|
+
return emitCanvasAction(context, { type: 'canvas_fit_view' }, 'Fit view applied');
|
|
1044
|
+
}
|
|
1045
|
+
function executeCanvasZoomIn(context) {
|
|
1046
|
+
return emitCanvasAction(context, { type: 'canvas_zoom_in' }, 'Zoomed in');
|
|
1047
|
+
}
|
|
1048
|
+
function executeCanvasZoomOut(context) {
|
|
1049
|
+
return emitCanvasAction(context, { type: 'canvas_zoom_out' }, 'Zoomed out');
|
|
1050
|
+
}
|
|
1051
|
+
function executeCanvasZoomTo(command, context) {
|
|
1052
|
+
return emitCanvasAction(context, { type: 'canvas_zoom_to', level: command.level }, `Zoom set to ${command.level}`);
|
|
1053
|
+
}
|
|
1054
|
+
function executeCanvasPanTo(command, context) {
|
|
1055
|
+
return emitCanvasAction(context, { type: 'canvas_pan_to', position: command.position }, `Panned to (${command.position.x}, ${command.position.y})`);
|
|
1056
|
+
}
|
|
1057
|
+
function executeCanvasResetView(context) {
|
|
1058
|
+
return emitCanvasAction(context, { type: 'canvas_reset_view' }, 'Viewport reset');
|
|
1059
|
+
}
|
|
1060
|
+
// ============================================================================
|
|
1061
|
+
// Public API
|
|
1062
|
+
// ============================================================================
|
|
1063
|
+
/**
|
|
1064
|
+
* Execute a parsed command against a workflow context.
|
|
1065
|
+
*/
|
|
1066
|
+
export function executeCommand(command, context) {
|
|
1067
|
+
switch (command.type) {
|
|
1068
|
+
case 'add_node':
|
|
1069
|
+
return executeAddNode(command, context);
|
|
1070
|
+
case 'delete_node':
|
|
1071
|
+
return executeDeleteNode(command, context);
|
|
1072
|
+
case 'rename_node':
|
|
1073
|
+
return executeRenameNode(command, context);
|
|
1074
|
+
case 'set_config':
|
|
1075
|
+
return executeSetConfig(command, context);
|
|
1076
|
+
case 'get_config':
|
|
1077
|
+
return executeGetConfig(command, context);
|
|
1078
|
+
case 'info':
|
|
1079
|
+
return executeInfo(command, context);
|
|
1080
|
+
case 'connect':
|
|
1081
|
+
return executeConnect(command, context);
|
|
1082
|
+
case 'disconnect_ports':
|
|
1083
|
+
return executeDisconnectPorts(command, context);
|
|
1084
|
+
case 'disconnect_node':
|
|
1085
|
+
return executeDisconnectNode(command, context);
|
|
1086
|
+
case 'list_nodes':
|
|
1087
|
+
return executeListNodes(context);
|
|
1088
|
+
case 'list_edges':
|
|
1089
|
+
return executeListEdges(context);
|
|
1090
|
+
case 'list_types':
|
|
1091
|
+
return executeListTypes(context);
|
|
1092
|
+
case 'help':
|
|
1093
|
+
return executeHelp(command);
|
|
1094
|
+
case 'undo':
|
|
1095
|
+
return executeUndo(context);
|
|
1096
|
+
case 'redo':
|
|
1097
|
+
return executeRedo(context);
|
|
1098
|
+
case 'clear':
|
|
1099
|
+
return executeClear(context);
|
|
1100
|
+
case 'config_open':
|
|
1101
|
+
return executeConfigOpen(command, context);
|
|
1102
|
+
case 'select_node':
|
|
1103
|
+
return executeSelectNode(command, context);
|
|
1104
|
+
case 'swap_node':
|
|
1105
|
+
return executeSwapNode(command, context);
|
|
1106
|
+
case 'move_node':
|
|
1107
|
+
return executeMoveNode(command, context);
|
|
1108
|
+
case 'auto_layout':
|
|
1109
|
+
return executeAutoLayout(command, context);
|
|
1110
|
+
case 'beautify_layout':
|
|
1111
|
+
return executeBeautifyLayout(command, context);
|
|
1112
|
+
case 'canvas_fit_view':
|
|
1113
|
+
return executeCanvasFitView(context);
|
|
1114
|
+
case 'canvas_zoom_in':
|
|
1115
|
+
return executeCanvasZoomIn(context);
|
|
1116
|
+
case 'canvas_zoom_out':
|
|
1117
|
+
return executeCanvasZoomOut(context);
|
|
1118
|
+
case 'canvas_zoom_to':
|
|
1119
|
+
return executeCanvasZoomTo(command, context);
|
|
1120
|
+
case 'canvas_pan_to':
|
|
1121
|
+
return executeCanvasPanTo(command, context);
|
|
1122
|
+
case 'canvas_reset_view':
|
|
1123
|
+
return executeCanvasResetView(context);
|
|
1124
|
+
default: {
|
|
1125
|
+
const _exhaustive = command;
|
|
1126
|
+
return {
|
|
1127
|
+
ok: false,
|
|
1128
|
+
error: `Command not yet implemented: ${_exhaustive.type}`,
|
|
1129
|
+
code: 'UNKNOWN_COMMAND'
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
}
|