flock-core 0.5.0b28__py3-none-any.whl → 0.5.0b51__py3-none-any.whl
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.
Potentially problematic release.
This version of flock-core might be problematic. Click here for more details.
- flock/__init__.py +12 -217
- flock/agent.py +678 -0
- flock/api/themes.py +71 -0
- flock/artifacts.py +79 -0
- flock/cli.py +75 -0
- flock/components.py +173 -0
- flock/dashboard/__init__.py +28 -0
- flock/dashboard/collector.py +283 -0
- flock/dashboard/events.py +182 -0
- flock/dashboard/launcher.py +230 -0
- flock/dashboard/service.py +537 -0
- flock/dashboard/websocket.py +235 -0
- flock/engines/__init__.py +6 -0
- flock/engines/dspy_engine.py +856 -0
- flock/examples.py +128 -0
- flock/frontend/README.md +678 -0
- flock/frontend/docs/DESIGN_SYSTEM.md +1980 -0
- flock/frontend/index.html +12 -0
- flock/frontend/package-lock.json +4347 -0
- flock/frontend/package.json +48 -0
- flock/frontend/src/App.tsx +79 -0
- flock/frontend/src/__tests__/e2e/critical-scenarios.test.tsx +587 -0
- flock/frontend/src/__tests__/integration/filtering-e2e.test.tsx +387 -0
- flock/frontend/src/__tests__/integration/graph-rendering.test.tsx +640 -0
- flock/frontend/src/__tests__/integration/indexeddb-persistence.test.tsx +699 -0
- flock/frontend/src/components/common/BuildInfo.tsx +39 -0
- flock/frontend/src/components/common/EmptyState.module.css +115 -0
- flock/frontend/src/components/common/EmptyState.tsx +128 -0
- flock/frontend/src/components/common/ErrorBoundary.module.css +169 -0
- flock/frontend/src/components/common/ErrorBoundary.tsx +118 -0
- flock/frontend/src/components/common/KeyboardShortcutsDialog.css +251 -0
- flock/frontend/src/components/common/KeyboardShortcutsDialog.tsx +151 -0
- flock/frontend/src/components/common/LoadingSpinner.module.css +97 -0
- flock/frontend/src/components/common/LoadingSpinner.tsx +29 -0
- flock/frontend/src/components/controls/PublishControl.css +547 -0
- flock/frontend/src/components/controls/PublishControl.test.tsx +543 -0
- flock/frontend/src/components/controls/PublishControl.tsx +432 -0
- flock/frontend/src/components/details/DetailWindowContainer.tsx +62 -0
- flock/frontend/src/components/details/LiveOutputTab.test.tsx +792 -0
- flock/frontend/src/components/details/LiveOutputTab.tsx +220 -0
- flock/frontend/src/components/details/MessageHistoryTab.tsx +299 -0
- flock/frontend/src/components/details/NodeDetailWindow.test.tsx +501 -0
- flock/frontend/src/components/details/NodeDetailWindow.tsx +218 -0
- flock/frontend/src/components/details/RunStatusTab.tsx +307 -0
- flock/frontend/src/components/details/tabs.test.tsx +1015 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.module.css +102 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.test.tsx +197 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.tsx +121 -0
- flock/frontend/src/components/filters/FilterBar.module.css +29 -0
- flock/frontend/src/components/filters/FilterBar.test.tsx +133 -0
- flock/frontend/src/components/filters/FilterBar.tsx +33 -0
- flock/frontend/src/components/filters/FilterPills.module.css +79 -0
- flock/frontend/src/components/filters/FilterPills.test.tsx +173 -0
- flock/frontend/src/components/filters/FilterPills.tsx +67 -0
- flock/frontend/src/components/filters/TimeRangeFilter.module.css +91 -0
- flock/frontend/src/components/filters/TimeRangeFilter.test.tsx +154 -0
- flock/frontend/src/components/filters/TimeRangeFilter.tsx +105 -0
- flock/frontend/src/components/graph/AgentNode.test.tsx +75 -0
- flock/frontend/src/components/graph/AgentNode.tsx +322 -0
- flock/frontend/src/components/graph/GraphCanvas.tsx +406 -0
- flock/frontend/src/components/graph/MessageFlowEdge.tsx +128 -0
- flock/frontend/src/components/graph/MessageNode.test.tsx +62 -0
- flock/frontend/src/components/graph/MessageNode.tsx +116 -0
- flock/frontend/src/components/graph/MiniMap.tsx +47 -0
- flock/frontend/src/components/graph/TransformEdge.tsx +123 -0
- flock/frontend/src/components/layout/DashboardLayout.css +407 -0
- flock/frontend/src/components/layout/DashboardLayout.tsx +300 -0
- flock/frontend/src/components/layout/Header.module.css +88 -0
- flock/frontend/src/components/layout/Header.tsx +52 -0
- flock/frontend/src/components/modules/EventLogModule.test.tsx +401 -0
- flock/frontend/src/components/modules/EventLogModule.tsx +396 -0
- flock/frontend/src/components/modules/EventLogModuleWrapper.tsx +17 -0
- flock/frontend/src/components/modules/ModuleRegistry.test.ts +333 -0
- flock/frontend/src/components/modules/ModuleRegistry.ts +85 -0
- flock/frontend/src/components/modules/ModuleWindow.tsx +155 -0
- flock/frontend/src/components/modules/registerModules.ts +20 -0
- flock/frontend/src/components/settings/AdvancedSettings.tsx +175 -0
- flock/frontend/src/components/settings/AppearanceSettings.tsx +185 -0
- flock/frontend/src/components/settings/GraphSettings.tsx +110 -0
- flock/frontend/src/components/settings/SettingsPanel.css +327 -0
- flock/frontend/src/components/settings/SettingsPanel.tsx +131 -0
- flock/frontend/src/components/settings/ThemeSelector.tsx +298 -0
- flock/frontend/src/hooks/useKeyboardShortcuts.ts +148 -0
- flock/frontend/src/hooks/useModulePersistence.test.ts +442 -0
- flock/frontend/src/hooks/useModulePersistence.ts +154 -0
- flock/frontend/src/hooks/useModules.ts +139 -0
- flock/frontend/src/hooks/usePersistence.ts +139 -0
- flock/frontend/src/main.tsx +13 -0
- flock/frontend/src/services/api.ts +213 -0
- flock/frontend/src/services/indexeddb.test.ts +793 -0
- flock/frontend/src/services/indexeddb.ts +794 -0
- flock/frontend/src/services/layout.test.ts +437 -0
- flock/frontend/src/services/layout.ts +146 -0
- flock/frontend/src/services/themeApplicator.ts +140 -0
- flock/frontend/src/services/themeService.ts +77 -0
- flock/frontend/src/services/websocket.test.ts +595 -0
- flock/frontend/src/services/websocket.ts +685 -0
- flock/frontend/src/store/filterStore.test.ts +242 -0
- flock/frontend/src/store/filterStore.ts +103 -0
- flock/frontend/src/store/graphStore.test.ts +186 -0
- flock/frontend/src/store/graphStore.ts +414 -0
- flock/frontend/src/store/moduleStore.test.ts +253 -0
- flock/frontend/src/store/moduleStore.ts +57 -0
- flock/frontend/src/store/settingsStore.ts +188 -0
- flock/frontend/src/store/streamStore.ts +68 -0
- flock/frontend/src/store/uiStore.test.ts +54 -0
- flock/frontend/src/store/uiStore.ts +110 -0
- flock/frontend/src/store/wsStore.ts +34 -0
- flock/frontend/src/styles/index.css +15 -0
- flock/frontend/src/styles/scrollbar.css +47 -0
- flock/frontend/src/styles/variables.css +488 -0
- flock/frontend/src/test/setup.ts +1 -0
- flock/frontend/src/types/filters.ts +14 -0
- flock/frontend/src/types/graph.ts +55 -0
- flock/frontend/src/types/modules.ts +7 -0
- flock/frontend/src/types/theme.ts +55 -0
- flock/frontend/src/utils/mockData.ts +85 -0
- flock/frontend/src/utils/performance.ts +16 -0
- flock/frontend/src/utils/transforms.test.ts +860 -0
- flock/frontend/src/utils/transforms.ts +323 -0
- flock/frontend/src/vite-env.d.ts +17 -0
- flock/frontend/tsconfig.json +27 -0
- flock/frontend/tsconfig.node.json +11 -0
- flock/frontend/vite.config.ts +25 -0
- flock/frontend/vitest.config.ts +11 -0
- flock/{core/util → helper}/cli_helper.py +4 -3
- flock/{core/logging → logging}/__init__.py +2 -3
- flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
- flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
- flock/{core/logging → logging}/formatters/themed_formatter.py +69 -115
- flock/{core/logging → logging}/logging.py +77 -61
- flock/{core/logging → logging}/telemetry.py +20 -26
- flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
- flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +6 -9
- flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
- flock/{core/logging → logging}/trace_and_logged.py +20 -24
- flock/mcp/__init__.py +91 -0
- flock/{core/mcp/mcp_client.py → mcp/client.py} +103 -154
- flock/{core/mcp/mcp_config.py → mcp/config.py} +62 -117
- flock/mcp/manager.py +255 -0
- flock/mcp/servers/sse/__init__.py +1 -1
- flock/mcp/servers/sse/flock_sse_server.py +11 -53
- flock/mcp/servers/stdio/__init__.py +1 -1
- flock/mcp/servers/stdio/flock_stdio_server.py +8 -48
- flock/mcp/servers/streamable_http/flock_streamable_http_server.py +17 -62
- flock/mcp/servers/websockets/flock_websocket_server.py +7 -40
- flock/{core/mcp/flock_mcp_tool.py → mcp/tool.py} +16 -26
- flock/mcp/types/__init__.py +42 -0
- flock/{core/mcp → mcp}/types/callbacks.py +9 -15
- flock/{core/mcp → mcp}/types/factories.py +7 -6
- flock/{core/mcp → mcp}/types/handlers.py +13 -18
- flock/{core/mcp → mcp}/types/types.py +70 -74
- flock/{core/mcp → mcp}/util/helpers.py +1 -1
- flock/orchestrator.py +645 -0
- flock/registry.py +148 -0
- flock/runtime.py +262 -0
- flock/service.py +140 -0
- flock/store.py +69 -0
- flock/subscription.py +111 -0
- flock/themes/andromeda.toml +1 -1
- flock/themes/apple-system-colors.toml +1 -1
- flock/themes/arcoiris.toml +1 -1
- flock/themes/atomonelight.toml +1 -1
- flock/themes/ayu copy.toml +1 -1
- flock/themes/ayu-light.toml +1 -1
- flock/themes/belafonte-day.toml +1 -1
- flock/themes/belafonte-night.toml +1 -1
- flock/themes/blulocodark.toml +1 -1
- flock/themes/breeze.toml +1 -1
- flock/themes/broadcast.toml +1 -1
- flock/themes/brogrammer.toml +1 -1
- flock/themes/builtin-dark.toml +1 -1
- flock/themes/builtin-pastel-dark.toml +1 -1
- flock/themes/catppuccin-latte.toml +1 -1
- flock/themes/catppuccin-macchiato.toml +1 -1
- flock/themes/catppuccin-mocha.toml +1 -1
- flock/themes/cga.toml +1 -1
- flock/themes/chalk.toml +1 -1
- flock/themes/ciapre.toml +1 -1
- flock/themes/coffee-theme.toml +1 -1
- flock/themes/cyberpunkscarletprotocol.toml +1 -1
- flock/themes/dark+.toml +1 -1
- flock/themes/darkermatrix.toml +1 -1
- flock/themes/darkside.toml +1 -1
- flock/themes/desert.toml +1 -1
- flock/themes/django.toml +1 -1
- flock/themes/djangosmooth.toml +1 -1
- flock/themes/doomone.toml +1 -1
- flock/themes/dotgov.toml +1 -1
- flock/themes/dracula+.toml +1 -1
- flock/themes/duckbones.toml +1 -1
- flock/themes/encom.toml +1 -1
- flock/themes/espresso.toml +1 -1
- flock/themes/everblush.toml +1 -1
- flock/themes/fairyfloss.toml +1 -1
- flock/themes/fideloper.toml +1 -1
- flock/themes/fishtank.toml +1 -1
- flock/themes/flexoki-light.toml +1 -1
- flock/themes/floraverse.toml +1 -1
- flock/themes/framer.toml +1 -1
- flock/themes/galizur.toml +1 -1
- flock/themes/github.toml +1 -1
- flock/themes/grass.toml +1 -1
- flock/themes/grey-green.toml +1 -1
- flock/themes/gruvboxlight.toml +1 -1
- flock/themes/guezwhoz.toml +1 -1
- flock/themes/harper.toml +1 -1
- flock/themes/hax0r-blue.toml +1 -1
- flock/themes/hopscotch.256.toml +1 -1
- flock/themes/ic-green-ppl.toml +1 -1
- flock/themes/iceberg-dark.toml +1 -1
- flock/themes/japanesque.toml +1 -1
- flock/themes/jubi.toml +1 -1
- flock/themes/kibble.toml +1 -1
- flock/themes/kolorit.toml +1 -1
- flock/themes/kurokula.toml +1 -1
- flock/themes/materialdesigncolors.toml +1 -1
- flock/themes/matrix.toml +1 -1
- flock/themes/mellifluous.toml +1 -1
- flock/themes/midnight-in-mojave.toml +1 -1
- flock/themes/monokai-remastered.toml +1 -1
- flock/themes/monokai-soda.toml +1 -1
- flock/themes/neon.toml +1 -1
- flock/themes/neopolitan.toml +1 -1
- flock/themes/nord-light.toml +1 -1
- flock/themes/ocean.toml +1 -1
- flock/themes/onehalfdark.toml +1 -1
- flock/themes/onehalflight.toml +1 -1
- flock/themes/palenighthc.toml +1 -1
- flock/themes/paulmillr.toml +1 -1
- flock/themes/pencildark.toml +1 -1
- flock/themes/pnevma.toml +1 -1
- flock/themes/purple-rain.toml +1 -1
- flock/themes/purplepeter.toml +1 -1
- flock/themes/raycast-dark.toml +1 -1
- flock/themes/red-sands.toml +1 -1
- flock/themes/relaxed.toml +1 -1
- flock/themes/retro.toml +1 -1
- flock/themes/rose-pine.toml +1 -1
- flock/themes/royal.toml +1 -1
- flock/themes/ryuuko.toml +1 -1
- flock/themes/sakura.toml +1 -1
- flock/themes/scarlet-protocol.toml +1 -1
- flock/themes/seoulbones-dark.toml +1 -1
- flock/themes/shades-of-purple.toml +1 -1
- flock/themes/smyck.toml +1 -1
- flock/themes/softserver.toml +1 -1
- flock/themes/solarized-darcula.toml +1 -1
- flock/themes/square.toml +1 -1
- flock/themes/sugarplum.toml +1 -1
- flock/themes/thayer-bright.toml +1 -1
- flock/themes/tokyonight.toml +1 -1
- flock/themes/tomorrow.toml +1 -1
- flock/themes/ubuntu.toml +1 -1
- flock/themes/ultradark.toml +1 -1
- flock/themes/ultraviolent.toml +1 -1
- flock/themes/unikitty.toml +1 -1
- flock/themes/urple.toml +1 -1
- flock/themes/vesper.toml +1 -1
- flock/themes/vimbones.toml +1 -1
- flock/themes/wildcherry.toml +1 -1
- flock/themes/wilmersdorf.toml +1 -1
- flock/themes/wryan.toml +1 -1
- flock/themes/xcodedarkhc.toml +1 -1
- flock/themes/xcodelight.toml +1 -1
- flock/themes/zenbones-light.toml +1 -1
- flock/themes/zenwritten-dark.toml +1 -1
- flock/utilities.py +301 -0
- flock/{components/utility → utility}/output_utility_component.py +68 -53
- flock/visibility.py +107 -0
- flock_core-0.5.0b51.dist-info/METADATA +747 -0
- flock_core-0.5.0b51.dist-info/RECORD +508 -0
- flock_core-0.5.0b51.dist-info/entry_points.txt +2 -0
- {flock_core-0.5.0b28.dist-info → flock_core-0.5.0b51.dist-info}/licenses/LICENSE +1 -1
- flock/adapter/__init__.py +0 -14
- flock/adapter/azure_adapter.py +0 -68
- flock/adapter/chroma_adapter.py +0 -73
- flock/adapter/faiss_adapter.py +0 -97
- flock/adapter/pinecone_adapter.py +0 -51
- flock/adapter/vector_base.py +0 -47
- flock/cli/assets/release_notes.md +0 -140
- flock/cli/config.py +0 -8
- flock/cli/constants.py +0 -36
- flock/cli/create_agent.py +0 -1
- flock/cli/create_flock.py +0 -280
- flock/cli/execute_flock.py +0 -620
- flock/cli/load_agent.py +0 -1
- flock/cli/load_examples.py +0 -1
- flock/cli/load_flock.py +0 -192
- flock/cli/load_release_notes.py +0 -20
- flock/cli/loaded_flock_cli.py +0 -254
- flock/cli/manage_agents.py +0 -459
- flock/cli/registry_management.py +0 -889
- flock/cli/runner.py +0 -41
- flock/cli/settings.py +0 -857
- flock/cli/utils.py +0 -135
- flock/cli/view_results.py +0 -29
- flock/cli/yaml_editor.py +0 -396
- flock/components/__init__.py +0 -30
- flock/components/evaluation/__init__.py +0 -9
- flock/components/evaluation/declarative_evaluation_component.py +0 -606
- flock/components/routing/__init__.py +0 -15
- flock/components/routing/conditional_routing_component.py +0 -494
- flock/components/routing/default_routing_component.py +0 -103
- flock/components/routing/llm_routing_component.py +0 -206
- flock/components/utility/__init__.py +0 -22
- flock/components/utility/example_utility_component.py +0 -250
- flock/components/utility/feedback_utility_component.py +0 -206
- flock/components/utility/memory_utility_component.py +0 -550
- flock/components/utility/metrics_utility_component.py +0 -700
- flock/config.py +0 -61
- flock/core/__init__.py +0 -110
- flock/core/agent/__init__.py +0 -16
- flock/core/agent/default_agent.py +0 -216
- flock/core/agent/flock_agent_components.py +0 -104
- flock/core/agent/flock_agent_execution.py +0 -101
- flock/core/agent/flock_agent_integration.py +0 -260
- flock/core/agent/flock_agent_lifecycle.py +0 -186
- flock/core/agent/flock_agent_serialization.py +0 -381
- flock/core/api/__init__.py +0 -10
- flock/core/api/custom_endpoint.py +0 -45
- flock/core/api/endpoints.py +0 -254
- flock/core/api/main.py +0 -162
- flock/core/api/models.py +0 -97
- flock/core/api/run_store.py +0 -224
- flock/core/api/runner.py +0 -44
- flock/core/api/service.py +0 -214
- flock/core/component/__init__.py +0 -15
- flock/core/component/agent_component_base.py +0 -309
- flock/core/component/evaluation_component.py +0 -62
- flock/core/component/routing_component.py +0 -74
- flock/core/component/utility_component.py +0 -69
- flock/core/config/flock_agent_config.py +0 -58
- flock/core/config/scheduled_agent_config.py +0 -40
- flock/core/context/context.py +0 -213
- flock/core/context/context_manager.py +0 -37
- flock/core/context/context_vars.py +0 -10
- flock/core/evaluation/utils.py +0 -396
- flock/core/execution/batch_executor.py +0 -369
- flock/core/execution/evaluation_executor.py +0 -438
- flock/core/execution/local_executor.py +0 -31
- flock/core/execution/opik_executor.py +0 -103
- flock/core/execution/temporal_executor.py +0 -164
- flock/core/flock.py +0 -634
- flock/core/flock_agent.py +0 -336
- flock/core/flock_factory.py +0 -613
- flock/core/flock_scheduler.py +0 -166
- flock/core/flock_server_manager.py +0 -136
- flock/core/interpreter/python_interpreter.py +0 -689
- flock/core/mcp/__init__.py +0 -1
- flock/core/mcp/flock_mcp_server.py +0 -680
- flock/core/mcp/mcp_client_manager.py +0 -201
- flock/core/mcp/types/__init__.py +0 -1
- flock/core/mixin/dspy_integration.py +0 -403
- flock/core/mixin/prompt_parser.py +0 -125
- flock/core/orchestration/__init__.py +0 -15
- flock/core/orchestration/flock_batch_processor.py +0 -94
- flock/core/orchestration/flock_evaluator.py +0 -113
- flock/core/orchestration/flock_execution.py +0 -295
- flock/core/orchestration/flock_initialization.py +0 -149
- flock/core/orchestration/flock_server_manager.py +0 -67
- flock/core/orchestration/flock_web_server.py +0 -117
- flock/core/registry/__init__.py +0 -45
- flock/core/registry/agent_registry.py +0 -69
- flock/core/registry/callable_registry.py +0 -139
- flock/core/registry/component_discovery.py +0 -142
- flock/core/registry/component_registry.py +0 -64
- flock/core/registry/config_mapping.py +0 -64
- flock/core/registry/decorators.py +0 -137
- flock/core/registry/registry_hub.py +0 -205
- flock/core/registry/server_registry.py +0 -57
- flock/core/registry/type_registry.py +0 -86
- flock/core/serialization/__init__.py +0 -13
- flock/core/serialization/callable_registry.py +0 -52
- flock/core/serialization/flock_serializer.py +0 -832
- flock/core/serialization/json_encoder.py +0 -41
- flock/core/serialization/secure_serializer.py +0 -175
- flock/core/serialization/serializable.py +0 -342
- flock/core/serialization/serialization_utils.py +0 -412
- flock/core/util/file_path_utils.py +0 -223
- flock/core/util/hydrator.py +0 -309
- flock/core/util/input_resolver.py +0 -164
- flock/core/util/loader.py +0 -59
- flock/core/util/splitter.py +0 -219
- flock/di.py +0 -27
- flock/platform/docker_tools.py +0 -49
- flock/platform/jaeger_install.py +0 -86
- flock/webapp/__init__.py +0 -1
- flock/webapp/app/__init__.py +0 -0
- flock/webapp/app/api/__init__.py +0 -0
- flock/webapp/app/api/agent_management.py +0 -241
- flock/webapp/app/api/execution.py +0 -709
- flock/webapp/app/api/flock_management.py +0 -129
- flock/webapp/app/api/registry_viewer.py +0 -30
- flock/webapp/app/chat.py +0 -665
- flock/webapp/app/config.py +0 -104
- flock/webapp/app/dependencies.py +0 -117
- flock/webapp/app/main.py +0 -1070
- flock/webapp/app/middleware.py +0 -113
- flock/webapp/app/models_ui.py +0 -7
- flock/webapp/app/services/__init__.py +0 -0
- flock/webapp/app/services/feedback_file_service.py +0 -363
- flock/webapp/app/services/flock_service.py +0 -337
- flock/webapp/app/services/sharing_models.py +0 -81
- flock/webapp/app/services/sharing_store.py +0 -762
- flock/webapp/app/templates/theme_mapper.html +0 -326
- flock/webapp/app/theme_mapper.py +0 -812
- flock/webapp/app/utils.py +0 -85
- flock/webapp/run.py +0 -215
- flock/webapp/static/css/chat.css +0 -301
- flock/webapp/static/css/components.css +0 -167
- flock/webapp/static/css/header.css +0 -39
- flock/webapp/static/css/layout.css +0 -46
- flock/webapp/static/css/sidebar.css +0 -127
- flock/webapp/static/css/two-pane.css +0 -48
- flock/webapp/templates/base.html +0 -200
- flock/webapp/templates/chat.html +0 -152
- flock/webapp/templates/chat_settings.html +0 -19
- flock/webapp/templates/flock_editor.html +0 -16
- flock/webapp/templates/index.html +0 -12
- flock/webapp/templates/partials/_agent_detail_form.html +0 -93
- flock/webapp/templates/partials/_agent_list.html +0 -18
- flock/webapp/templates/partials/_agent_manager_view.html +0 -51
- flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
- flock/webapp/templates/partials/_chat_container.html +0 -15
- flock/webapp/templates/partials/_chat_messages.html +0 -57
- flock/webapp/templates/partials/_chat_settings_form.html +0 -85
- flock/webapp/templates/partials/_create_flock_form.html +0 -50
- flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
- flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
- flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
- flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
- flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
- flock/webapp/templates/partials/_env_vars_table.html +0 -23
- flock/webapp/templates/partials/_execution_form.html +0 -118
- flock/webapp/templates/partials/_execution_view_container.html +0 -28
- flock/webapp/templates/partials/_flock_file_list.html +0 -23
- flock/webapp/templates/partials/_flock_properties_form.html +0 -52
- flock/webapp/templates/partials/_flock_upload_form.html +0 -16
- flock/webapp/templates/partials/_header_flock_status.html +0 -5
- flock/webapp/templates/partials/_load_manager_view.html +0 -49
- flock/webapp/templates/partials/_registry_table.html +0 -25
- flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
- flock/webapp/templates/partials/_results_display.html +0 -78
- flock/webapp/templates/partials/_settings_env_content.html +0 -9
- flock/webapp/templates/partials/_settings_theme_content.html +0 -14
- flock/webapp/templates/partials/_settings_view.html +0 -36
- flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
- flock/webapp/templates/partials/_share_link_snippet.html +0 -35
- flock/webapp/templates/partials/_sidebar.html +0 -74
- flock/webapp/templates/partials/_streaming_results_container.html +0 -195
- flock/webapp/templates/partials/_structured_data_view.html +0 -40
- flock/webapp/templates/partials/_theme_preview.html +0 -36
- flock/webapp/templates/registry_viewer.html +0 -84
- flock/webapp/templates/shared_run_page.html +0 -140
- flock/workflow/__init__.py +0 -0
- flock/workflow/activities.py +0 -196
- flock/workflow/agent_activities.py +0 -24
- flock/workflow/agent_execution_activity.py +0 -202
- flock/workflow/flock_workflow.py +0 -214
- flock/workflow/temporal_config.py +0 -96
- flock/workflow/temporal_setup.py +0 -68
- flock_core-0.5.0b28.dist-info/METADATA +0 -274
- flock_core-0.5.0b28.dist-info/RECORD +0 -561
- flock_core-0.5.0b28.dist-info/entry_points.txt +0 -2
- /flock/{core/logging → logging}/formatters/themes.py +0 -0
- /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
- /flock/{core/mcp → mcp}/util/__init__.py +0 -0
- {flock_core-0.5.0b28.dist-info → flock_core-0.5.0b51.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,685 @@
|
|
|
1
|
+
import { useWSStore } from '../store/wsStore';
|
|
2
|
+
import { useGraphStore } from '../store/graphStore';
|
|
3
|
+
|
|
4
|
+
interface WebSocketMessage {
|
|
5
|
+
event_type: 'agent_activated' | 'message_published' | 'streaming_output' | 'agent_completed' | 'agent_error';
|
|
6
|
+
timestamp: string;
|
|
7
|
+
correlation_id: string;
|
|
8
|
+
session_id: string;
|
|
9
|
+
data: any;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface StoreInterface {
|
|
13
|
+
addAgent: (agent: any) => void;
|
|
14
|
+
updateAgent: (id: string, updates: any) => void;
|
|
15
|
+
addMessage: (message: any) => void;
|
|
16
|
+
updateMessage: (id: string, updates: any) => void;
|
|
17
|
+
batchUpdate?: (update: any) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class WebSocketClient {
|
|
21
|
+
ws: WebSocket | null = null;
|
|
22
|
+
private reconnectTimeout: number | null = null;
|
|
23
|
+
private reconnectAttempt = 0;
|
|
24
|
+
private maxReconnectDelay = 30000; // 30 seconds
|
|
25
|
+
private connectionTimeout: number | null = null;
|
|
26
|
+
private connectionTimeoutMs = 10000; // 10 seconds
|
|
27
|
+
private messageBuffer: any[] = [];
|
|
28
|
+
private maxBufferSize = 100;
|
|
29
|
+
private eventHandlers: Map<string, ((data: any) => void)[]> = new Map();
|
|
30
|
+
private url: string;
|
|
31
|
+
private shouldReconnect = true;
|
|
32
|
+
private heartbeatInterval: number | null = null;
|
|
33
|
+
private heartbeatTimeout: number | null = null;
|
|
34
|
+
private connectionStatus: 'connecting' | 'connected' | 'disconnected' | 'disconnecting' | 'error' = 'disconnected';
|
|
35
|
+
private store: StoreInterface;
|
|
36
|
+
private enableHeartbeat: boolean;
|
|
37
|
+
|
|
38
|
+
constructor(url: string, mockStore?: StoreInterface) {
|
|
39
|
+
this.url = url;
|
|
40
|
+
this.store = mockStore || {
|
|
41
|
+
addAgent: (agent: any) => useGraphStore.getState().addAgent(agent),
|
|
42
|
+
updateAgent: (id: string, updates: any) => useGraphStore.getState().updateAgent(id, updates),
|
|
43
|
+
addMessage: (message: any) => useGraphStore.getState().addMessage(message),
|
|
44
|
+
updateMessage: (id: string, updates: any) => useGraphStore.getState().updateMessage(id, updates),
|
|
45
|
+
batchUpdate: (update: any) => useGraphStore.getState().batchUpdate(update),
|
|
46
|
+
};
|
|
47
|
+
// Phase 11 Fix: Disable heartbeat entirely - it causes unnecessary disconnects
|
|
48
|
+
// WebSocket auto-reconnects on real network issues without needing heartbeat
|
|
49
|
+
// The heartbeat was closing connections every 2min when backend didn't respond to pings
|
|
50
|
+
this.enableHeartbeat = false;
|
|
51
|
+
this.setupEventHandlers();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private setupEventHandlers(): void {
|
|
55
|
+
// Handler for agent_activated: create/update agent in graph AND create Run
|
|
56
|
+
this.on('agent_activated', (data) => {
|
|
57
|
+
const agents = useGraphStore.getState().agents;
|
|
58
|
+
const messages = useGraphStore.getState().messages;
|
|
59
|
+
const existingAgent = agents.get(data.agent_id);
|
|
60
|
+
|
|
61
|
+
// Count received messages by type
|
|
62
|
+
const receivedByType = { ...(existingAgent?.receivedByType || {}) };
|
|
63
|
+
if (data.consumed_artifacts && data.consumed_artifacts.length > 0) {
|
|
64
|
+
// Look up each consumed artifact and count by type
|
|
65
|
+
data.consumed_artifacts.forEach((artifactId: string) => {
|
|
66
|
+
const message = messages.get(artifactId);
|
|
67
|
+
if (message) {
|
|
68
|
+
receivedByType[message.type] = (receivedByType[message.type] || 0) + 1;
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Bug Fix #2: Preserve sentCount/recvCount if agent already exists
|
|
74
|
+
// Otherwise counters get reset to 0 on each activation
|
|
75
|
+
const agent = {
|
|
76
|
+
id: data.agent_id,
|
|
77
|
+
name: data.agent_name,
|
|
78
|
+
status: 'running' as const,
|
|
79
|
+
subscriptions: data.consumed_types || [],
|
|
80
|
+
lastActive: Date.now(),
|
|
81
|
+
sentCount: existingAgent?.sentCount || 0, // Preserve existing count
|
|
82
|
+
recvCount: (existingAgent?.recvCount || 0) + (data.consumed_artifacts?.length || 0), // Add new consumed artifacts
|
|
83
|
+
outputTypes: data.produced_types || [], // Get output types from backend
|
|
84
|
+
receivedByType, // Track per-type received counts
|
|
85
|
+
sentByType: existingAgent?.sentByType || {}, // Preserve sent counts
|
|
86
|
+
};
|
|
87
|
+
this.store.addAgent(agent);
|
|
88
|
+
|
|
89
|
+
// Phase 11 Bug Fix: Record actual consumption to track filtering
|
|
90
|
+
// This enables showing "(3, filtered: 1)" on edges
|
|
91
|
+
if (data.consumed_artifacts && data.consumed_artifacts.length > 0) {
|
|
92
|
+
useGraphStore.getState().recordConsumption(data.consumed_artifacts, data.agent_id);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Create Run object for Blackboard View edges
|
|
96
|
+
// Bug Fix: Use run_id from backend (unique per agent activation) instead of correlation_id
|
|
97
|
+
const run = {
|
|
98
|
+
run_id: data.run_id || `run_${Date.now()}`, // data.run_id is ctx.task_id from backend
|
|
99
|
+
agent_name: data.agent_name,
|
|
100
|
+
correlation_id: data.correlation_id, // Separate field for grouping runs
|
|
101
|
+
status: 'active' as const,
|
|
102
|
+
consumed_artifacts: data.consumed_artifacts || [],
|
|
103
|
+
produced_artifacts: [], // Will be populated on message_published
|
|
104
|
+
started_at: new Date().toISOString(),
|
|
105
|
+
};
|
|
106
|
+
if (this.store.batchUpdate) {
|
|
107
|
+
this.store.batchUpdate({ runs: [run] });
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Handler for message_published: update existing streaming message or create new one
|
|
112
|
+
this.on('message_published', (data) => {
|
|
113
|
+
// Finalize or create the message
|
|
114
|
+
const messages = useGraphStore.getState().messages;
|
|
115
|
+
const streamingMessageId = `streaming_${data.produced_by}_${data.correlation_id}`;
|
|
116
|
+
const existingMessage = messages.get(streamingMessageId);
|
|
117
|
+
|
|
118
|
+
if (existingMessage) {
|
|
119
|
+
// Update existing streaming message with final data
|
|
120
|
+
const finalMessage = {
|
|
121
|
+
...existingMessage,
|
|
122
|
+
id: data.artifact_id, // Replace temp ID with real artifact ID
|
|
123
|
+
type: data.artifact_type,
|
|
124
|
+
payload: data.payload,
|
|
125
|
+
isStreaming: false, // Streaming complete
|
|
126
|
+
streamingText: '', // Clear streaming text
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// Use store action to properly update (triggers graph regeneration)
|
|
130
|
+
useGraphStore.getState().finalizeStreamingMessage(streamingMessageId, finalMessage);
|
|
131
|
+
} else {
|
|
132
|
+
// No streaming message - create new message directly
|
|
133
|
+
const message = {
|
|
134
|
+
id: data.artifact_id,
|
|
135
|
+
type: data.artifact_type,
|
|
136
|
+
payload: data.payload,
|
|
137
|
+
timestamp: data.timestamp ? new Date(data.timestamp).getTime() : Date.now(),
|
|
138
|
+
correlationId: data.correlation_id || '',
|
|
139
|
+
producedBy: data.produced_by,
|
|
140
|
+
isStreaming: false,
|
|
141
|
+
};
|
|
142
|
+
this.store.addMessage(message);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Update producer agent counters (outputTypes come from agent_activated event now)
|
|
146
|
+
const producer = useGraphStore.getState().agents.get(data.produced_by);
|
|
147
|
+
if (producer) {
|
|
148
|
+
// Track sent count by type
|
|
149
|
+
const sentByType = { ...(producer.sentByType || {}) };
|
|
150
|
+
sentByType[data.artifact_type] = (sentByType[data.artifact_type] || 0) + 1;
|
|
151
|
+
|
|
152
|
+
this.store.updateAgent(data.produced_by, {
|
|
153
|
+
sentCount: (producer.sentCount || 0) + 1,
|
|
154
|
+
lastActive: Date.now(),
|
|
155
|
+
sentByType,
|
|
156
|
+
});
|
|
157
|
+
} else {
|
|
158
|
+
// Producer doesn't exist as a registered agent - create virtual agent
|
|
159
|
+
// This handles orchestrator-published artifacts (e.g., initial Idea from dashboard PublishControl)
|
|
160
|
+
this.store.addAgent({
|
|
161
|
+
id: data.produced_by,
|
|
162
|
+
name: data.produced_by,
|
|
163
|
+
status: 'idle' as const,
|
|
164
|
+
subscriptions: [],
|
|
165
|
+
lastActive: Date.now(),
|
|
166
|
+
sentCount: 1,
|
|
167
|
+
recvCount: 0,
|
|
168
|
+
outputTypes: [data.artifact_type], // Virtual agents get type from their first message
|
|
169
|
+
sentByType: { [data.artifact_type]: 1 },
|
|
170
|
+
receivedByType: {},
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Phase 11 Bug Fix: Increment consumers' recv count instead of setting to 1
|
|
175
|
+
if (data.consumers && Array.isArray(data.consumers)) {
|
|
176
|
+
const agents = useGraphStore.getState().agents;
|
|
177
|
+
data.consumers.forEach((consumerId: string) => {
|
|
178
|
+
const consumer = agents.get(consumerId);
|
|
179
|
+
if (consumer) {
|
|
180
|
+
this.store.updateAgent(consumerId, {
|
|
181
|
+
recvCount: (consumer.recvCount || 0) + 1,
|
|
182
|
+
lastActive: Date.now(),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Update Run with produced artifact for Blackboard View edges
|
|
189
|
+
// Bug Fix: Find Run by agent_name + correlation_id since run_id is not in message_published event
|
|
190
|
+
if (data.correlation_id && this.store.batchUpdate) {
|
|
191
|
+
const runs = useGraphStore.getState().runs;
|
|
192
|
+
// Find the active Run for this agent + correlation_id
|
|
193
|
+
const run = Array.from(runs.values()).find(
|
|
194
|
+
r => r.agent_name === data.produced_by &&
|
|
195
|
+
r.correlation_id === data.correlation_id &&
|
|
196
|
+
r.status === 'active'
|
|
197
|
+
);
|
|
198
|
+
if (run) {
|
|
199
|
+
// Add artifact to produced_artifacts if not already present
|
|
200
|
+
if (!run.produced_artifacts.includes(data.artifact_id)) {
|
|
201
|
+
const updatedRun = {
|
|
202
|
+
...run,
|
|
203
|
+
produced_artifacts: [...run.produced_artifacts, data.artifact_id],
|
|
204
|
+
};
|
|
205
|
+
this.store.batchUpdate({ runs: [updatedRun] });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Handler for streaming_output: update live output (Phase 6)
|
|
212
|
+
this.on('streaming_output', (data) => {
|
|
213
|
+
// Phase 6: Update detail window live output
|
|
214
|
+
console.log('[WebSocket] Streaming output:', data);
|
|
215
|
+
// Update agent to show it's active and track streaming tokens for news ticker
|
|
216
|
+
if (data.agent_name && data.output_type === 'llm_token') {
|
|
217
|
+
const agents = useGraphStore.getState().agents;
|
|
218
|
+
const agent = agents.get(data.agent_name);
|
|
219
|
+
const currentTokens = agent?.streamingTokens || [];
|
|
220
|
+
|
|
221
|
+
// Keep only last 6 tokens (news ticker effect)
|
|
222
|
+
const updatedTokens = [...currentTokens, data.content].slice(-6);
|
|
223
|
+
|
|
224
|
+
this.store.updateAgent(data.agent_name, {
|
|
225
|
+
lastActive: Date.now(),
|
|
226
|
+
streamingTokens: updatedTokens,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Create/update streaming message node for blackboard view
|
|
231
|
+
if (data.output_type === 'llm_token' && data.agent_name && data.correlation_id) {
|
|
232
|
+
const messages = useGraphStore.getState().messages;
|
|
233
|
+
// Use agent_name + correlation_id as temporary ID
|
|
234
|
+
const streamingMessageId = `streaming_${data.agent_name}_${data.correlation_id}`;
|
|
235
|
+
const existingMessage = messages.get(streamingMessageId);
|
|
236
|
+
|
|
237
|
+
if (existingMessage) {
|
|
238
|
+
// Append token to existing streaming message using updateMessage
|
|
239
|
+
// This updates the messages Map without flooding the events array
|
|
240
|
+
this.store.updateMessage(streamingMessageId, {
|
|
241
|
+
streamingText: (existingMessage.streamingText || '') + data.content,
|
|
242
|
+
timestamp: data.timestamp ? new Date(data.timestamp).getTime() : Date.now(),
|
|
243
|
+
});
|
|
244
|
+
} else if (data.sequence === 0 || !existingMessage) {
|
|
245
|
+
// Look up agent's typical output type
|
|
246
|
+
const agents = useGraphStore.getState().agents;
|
|
247
|
+
const agent = agents.get(data.agent_name);
|
|
248
|
+
const outputType = agent?.outputTypes?.[0] || 'output';
|
|
249
|
+
|
|
250
|
+
// Create new streaming message on first token
|
|
251
|
+
const streamingMessage = {
|
|
252
|
+
id: streamingMessageId,
|
|
253
|
+
type: outputType, // Use agent's known output type
|
|
254
|
+
payload: {},
|
|
255
|
+
timestamp: data.timestamp ? new Date(data.timestamp).getTime() : Date.now(),
|
|
256
|
+
correlationId: data.correlation_id || '',
|
|
257
|
+
producedBy: data.agent_name,
|
|
258
|
+
isStreaming: true,
|
|
259
|
+
streamingText: data.content,
|
|
260
|
+
};
|
|
261
|
+
this.store.addMessage(streamingMessage);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Note: The actual output storage is handled by LiveOutputTab's event listener
|
|
266
|
+
// This handler is for store updates only
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
// Handler for agent_completed: update agent status to idle
|
|
270
|
+
this.on('agent_completed', (data) => {
|
|
271
|
+
this.store.updateAgent(data.agent_name, {
|
|
272
|
+
status: 'idle',
|
|
273
|
+
lastActive: Date.now(),
|
|
274
|
+
streamingTokens: [], // Clear news ticker on completion
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Update Run status to completed for Blackboard View edges
|
|
278
|
+
// Bug Fix: Use run_id from event data (agent_completed has run_id)
|
|
279
|
+
if (data.run_id && this.store.batchUpdate) {
|
|
280
|
+
const runs = useGraphStore.getState().runs;
|
|
281
|
+
const run = runs.get(data.run_id);
|
|
282
|
+
if (run) {
|
|
283
|
+
const updatedRun = {
|
|
284
|
+
...run,
|
|
285
|
+
status: 'completed' as const,
|
|
286
|
+
completed_at: new Date().toISOString(),
|
|
287
|
+
duration_ms: data.duration_ms,
|
|
288
|
+
};
|
|
289
|
+
this.store.batchUpdate({ runs: [updatedRun] });
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Handler for agent_error: update agent status to error
|
|
295
|
+
this.on('agent_error', (data) => {
|
|
296
|
+
this.store.updateAgent(data.agent_name, {
|
|
297
|
+
status: 'error',
|
|
298
|
+
lastActive: Date.now(),
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Update Run status to error
|
|
302
|
+
// Bug Fix: Use run_id from event data (agent_error has run_id)
|
|
303
|
+
if (data.run_id && this.store.batchUpdate) {
|
|
304
|
+
const runs = useGraphStore.getState().runs;
|
|
305
|
+
const run = runs.get(data.run_id);
|
|
306
|
+
if (run) {
|
|
307
|
+
const updatedRun = {
|
|
308
|
+
...run,
|
|
309
|
+
status: 'error' as const,
|
|
310
|
+
completed_at: new Date().toISOString(),
|
|
311
|
+
error_message: data.error_message || 'Unknown error',
|
|
312
|
+
};
|
|
313
|
+
this.store.batchUpdate({ runs: [updatedRun] });
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Handler for ping: respond with pong
|
|
319
|
+
this.on('ping', () => {
|
|
320
|
+
this.send({ type: 'pong', timestamp: Date.now() });
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
connect(): void {
|
|
325
|
+
if (this.ws?.readyState === WebSocket.OPEN || this.ws?.readyState === WebSocket.CONNECTING) {
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
this.connectionStatus = 'connecting';
|
|
331
|
+
if (typeof useWSStore !== 'undefined') {
|
|
332
|
+
useWSStore.getState().setStatus('connecting');
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
this.ws = new WebSocket(this.url);
|
|
336
|
+
|
|
337
|
+
// Set connection timeout
|
|
338
|
+
this.connectionTimeout = window.setTimeout(() => {
|
|
339
|
+
console.warn('[WebSocket] Connection timeout');
|
|
340
|
+
if (this.ws && this.ws.readyState !== WebSocket.OPEN) {
|
|
341
|
+
this.ws.close();
|
|
342
|
+
this.connectionStatus = 'error';
|
|
343
|
+
if (typeof useWSStore !== 'undefined') {
|
|
344
|
+
useWSStore.getState().setStatus('disconnected');
|
|
345
|
+
useWSStore.getState().setError('Connection timeout');
|
|
346
|
+
}
|
|
347
|
+
if (this.shouldReconnect) {
|
|
348
|
+
this.reconnect();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}, this.connectionTimeoutMs);
|
|
352
|
+
|
|
353
|
+
this.ws.onopen = () => {
|
|
354
|
+
console.log('[WebSocket] Connected');
|
|
355
|
+
|
|
356
|
+
// Clear connection timeout
|
|
357
|
+
if (this.connectionTimeout !== null) {
|
|
358
|
+
clearTimeout(this.connectionTimeout);
|
|
359
|
+
this.connectionTimeout = null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
this.connectionStatus = 'connected';
|
|
363
|
+
if (typeof useWSStore !== 'undefined') {
|
|
364
|
+
useWSStore.getState().setStatus('connected');
|
|
365
|
+
useWSStore.getState().setError(null);
|
|
366
|
+
useWSStore.getState().resetAttempts();
|
|
367
|
+
}
|
|
368
|
+
this.reconnectAttempt = 0;
|
|
369
|
+
this.flushBuffer();
|
|
370
|
+
if (this.enableHeartbeat) {
|
|
371
|
+
this.startHeartbeat();
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
this.ws.onmessage = (event: MessageEvent) => {
|
|
376
|
+
this.handleMessage(event);
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
this.ws.onerror = (error) => {
|
|
380
|
+
console.error('[WebSocket] Error:', error);
|
|
381
|
+
// Keep connection status as error even after close event
|
|
382
|
+
this.connectionStatus = 'error';
|
|
383
|
+
if (typeof useWSStore !== 'undefined') {
|
|
384
|
+
useWSStore.getState().setError('Connection error');
|
|
385
|
+
useWSStore.getState().setStatus('disconnected');
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
this.ws.onclose = (event) => {
|
|
390
|
+
console.log('[WebSocket] Closed:', event.code, event.reason);
|
|
391
|
+
this.stopHeartbeat();
|
|
392
|
+
|
|
393
|
+
// Don't override error status
|
|
394
|
+
if (this.connectionStatus !== 'error') {
|
|
395
|
+
if (this.shouldReconnect && event.code !== 1000) {
|
|
396
|
+
this.connectionStatus = 'connecting'; // Will be reconnecting
|
|
397
|
+
if (typeof useWSStore !== 'undefined') {
|
|
398
|
+
useWSStore.getState().setStatus('reconnecting');
|
|
399
|
+
}
|
|
400
|
+
this.reconnect();
|
|
401
|
+
} else {
|
|
402
|
+
this.connectionStatus = 'disconnected';
|
|
403
|
+
if (typeof useWSStore !== 'undefined') {
|
|
404
|
+
useWSStore.getState().setStatus('disconnected');
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
} catch (error) {
|
|
410
|
+
console.error('[WebSocket] Connection failed:', error);
|
|
411
|
+
this.connectionStatus = 'error';
|
|
412
|
+
if (typeof useWSStore !== 'undefined') {
|
|
413
|
+
useWSStore.getState().setStatus('disconnected');
|
|
414
|
+
useWSStore.getState().setError(error instanceof Error ? error.message : 'Connection failed');
|
|
415
|
+
}
|
|
416
|
+
if (this.shouldReconnect) {
|
|
417
|
+
this.reconnect();
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
private reconnect(): void {
|
|
423
|
+
if (this.reconnectTimeout !== null) {
|
|
424
|
+
return; // Already scheduled
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// Exponential backoff: 1s, 2s, 4s, 8s, max 30s
|
|
428
|
+
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempt), this.maxReconnectDelay);
|
|
429
|
+
|
|
430
|
+
if (typeof useWSStore !== 'undefined') {
|
|
431
|
+
useWSStore.getState().incrementAttempts();
|
|
432
|
+
}
|
|
433
|
+
this.reconnectAttempt++;
|
|
434
|
+
|
|
435
|
+
console.log(`[WebSocket] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempt})`);
|
|
436
|
+
|
|
437
|
+
this.reconnectTimeout = window.setTimeout(() => {
|
|
438
|
+
this.reconnectTimeout = null;
|
|
439
|
+
this.connect();
|
|
440
|
+
}, delay);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
private handleMessage(event: MessageEvent): void {
|
|
444
|
+
try {
|
|
445
|
+
const data = JSON.parse(event.data);
|
|
446
|
+
|
|
447
|
+
// Handle direct type field (for ping/pong)
|
|
448
|
+
if (data.type === 'ping') {
|
|
449
|
+
this.send({ type: 'pong', timestamp: Date.now() });
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if (data.type === 'pong') {
|
|
454
|
+
this.resetHeartbeatTimeout();
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Handle WebSocketMessage envelope
|
|
459
|
+
const message: WebSocketMessage = data;
|
|
460
|
+
|
|
461
|
+
// Handle pong as event_type
|
|
462
|
+
if (message.event_type === 'pong' as any) {
|
|
463
|
+
this.resetHeartbeatTimeout();
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Determine if this is an envelope or raw data
|
|
468
|
+
// If it has event_type, it's an envelope; use message.data
|
|
469
|
+
// Otherwise, it's raw data (for tests)
|
|
470
|
+
const eventData = message.event_type ? message.data : data;
|
|
471
|
+
|
|
472
|
+
// Try to detect event type from data
|
|
473
|
+
let eventType = message.event_type;
|
|
474
|
+
if (!eventType) {
|
|
475
|
+
// Infer event type from data structure for test compatibility
|
|
476
|
+
if (data.agent_id && data.consumed_types) {
|
|
477
|
+
eventType = 'agent_activated';
|
|
478
|
+
} else if (data.artifact_id && data.artifact_type) {
|
|
479
|
+
eventType = 'message_published';
|
|
480
|
+
} else if (data.run_id && data.output_type) {
|
|
481
|
+
eventType = 'streaming_output';
|
|
482
|
+
} else if (data.run_id && data.duration_ms !== undefined) {
|
|
483
|
+
eventType = 'agent_completed';
|
|
484
|
+
} else if (data.run_id && data.error_type) {
|
|
485
|
+
eventType = 'agent_error';
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Dispatch to registered handlers
|
|
490
|
+
if (eventType) {
|
|
491
|
+
const handlers = this.eventHandlers.get(eventType);
|
|
492
|
+
if (handlers) {
|
|
493
|
+
handlers.forEach((handler) => {
|
|
494
|
+
try {
|
|
495
|
+
handler(eventData);
|
|
496
|
+
} catch (error) {
|
|
497
|
+
console.error(`[WebSocket] Handler error for ${eventType}:`, error);
|
|
498
|
+
}
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
} catch (error) {
|
|
503
|
+
console.error('[WebSocket] Failed to parse message:', error);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
send(message: any): void {
|
|
508
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
509
|
+
try {
|
|
510
|
+
this.ws.send(JSON.stringify(message));
|
|
511
|
+
} catch (error) {
|
|
512
|
+
console.error('[WebSocket] Send failed:', error);
|
|
513
|
+
this.bufferMessage(message);
|
|
514
|
+
}
|
|
515
|
+
} else {
|
|
516
|
+
this.bufferMessage(message);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
private bufferMessage(message: any): void {
|
|
521
|
+
if (this.messageBuffer.length >= this.maxBufferSize) {
|
|
522
|
+
this.messageBuffer.shift(); // Remove oldest message
|
|
523
|
+
}
|
|
524
|
+
this.messageBuffer.push(message);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
private flushBuffer(): void {
|
|
528
|
+
if (this.messageBuffer.length === 0) {
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
console.log(`[WebSocket] Flushing ${this.messageBuffer.length} buffered messages`);
|
|
533
|
+
|
|
534
|
+
const messages = [...this.messageBuffer];
|
|
535
|
+
this.messageBuffer = [];
|
|
536
|
+
|
|
537
|
+
messages.forEach((message) => {
|
|
538
|
+
// Send directly to avoid re-buffering
|
|
539
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
540
|
+
try {
|
|
541
|
+
this.ws.send(JSON.stringify(message));
|
|
542
|
+
} catch (error) {
|
|
543
|
+
console.error('[WebSocket] Failed to send buffered message:', error);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
on(eventType: string, handler: (data: any) => void): void {
|
|
550
|
+
if (!this.eventHandlers.has(eventType)) {
|
|
551
|
+
this.eventHandlers.set(eventType, []);
|
|
552
|
+
}
|
|
553
|
+
this.eventHandlers.get(eventType)!.push(handler);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
off(eventType: string, handler: (data: any) => void): void {
|
|
557
|
+
const handlers = this.eventHandlers.get(eventType);
|
|
558
|
+
if (handlers) {
|
|
559
|
+
const index = handlers.indexOf(handler);
|
|
560
|
+
if (index > -1) {
|
|
561
|
+
handlers.splice(index, 1);
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
private startHeartbeat(): void {
|
|
567
|
+
this.stopHeartbeat();
|
|
568
|
+
|
|
569
|
+
// Send ping every 2 minutes
|
|
570
|
+
this.heartbeatInterval = window.setInterval(() => {
|
|
571
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
572
|
+
this.send({ type: 'ping' });
|
|
573
|
+
|
|
574
|
+
// Set timeout for pong response (10 seconds)
|
|
575
|
+
this.heartbeatTimeout = window.setTimeout(() => {
|
|
576
|
+
console.warn('[WebSocket] Heartbeat timeout, closing connection');
|
|
577
|
+
this.ws?.close();
|
|
578
|
+
}, 10000);
|
|
579
|
+
}
|
|
580
|
+
}, 120000);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
private stopHeartbeat(): void {
|
|
584
|
+
if (this.heartbeatInterval !== null) {
|
|
585
|
+
clearInterval(this.heartbeatInterval);
|
|
586
|
+
this.heartbeatInterval = null;
|
|
587
|
+
}
|
|
588
|
+
if (this.heartbeatTimeout !== null) {
|
|
589
|
+
clearTimeout(this.heartbeatTimeout);
|
|
590
|
+
this.heartbeatTimeout = null;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
private resetHeartbeatTimeout(): void {
|
|
595
|
+
if (this.heartbeatTimeout !== null) {
|
|
596
|
+
clearTimeout(this.heartbeatTimeout);
|
|
597
|
+
this.heartbeatTimeout = null;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
disconnect(): void {
|
|
602
|
+
this.shouldReconnect = false;
|
|
603
|
+
this.connectionStatus = 'disconnecting';
|
|
604
|
+
|
|
605
|
+
if (this.reconnectTimeout !== null) {
|
|
606
|
+
clearTimeout(this.reconnectTimeout);
|
|
607
|
+
this.reconnectTimeout = null;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
if (this.connectionTimeout !== null) {
|
|
611
|
+
clearTimeout(this.connectionTimeout);
|
|
612
|
+
this.connectionTimeout = null;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
this.stopHeartbeat();
|
|
616
|
+
|
|
617
|
+
if (this.ws) {
|
|
618
|
+
this.ws.close();
|
|
619
|
+
this.ws = null;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Status will be set to 'disconnected' by onclose handler
|
|
623
|
+
// Don't override it here to maintain proper status flow
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
reconnectManually(): void {
|
|
627
|
+
this.shouldReconnect = true;
|
|
628
|
+
this.reconnectAttempt = 0;
|
|
629
|
+
if (typeof useWSStore !== 'undefined') {
|
|
630
|
+
useWSStore.getState().resetAttempts();
|
|
631
|
+
}
|
|
632
|
+
this.connect();
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Test helper methods
|
|
636
|
+
isConnected(): boolean {
|
|
637
|
+
return this.ws?.readyState === WebSocket.OPEN && this.connectionStatus !== 'error';
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
getConnectionStatus(): string {
|
|
641
|
+
return this.connectionStatus;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
getBufferedMessageCount(): number {
|
|
645
|
+
return this.messageBuffer.length;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
getStatus(): string {
|
|
649
|
+
if (!this.ws) return 'disconnected';
|
|
650
|
+
|
|
651
|
+
switch (this.ws.readyState) {
|
|
652
|
+
case WebSocket.CONNECTING:
|
|
653
|
+
return 'connecting';
|
|
654
|
+
case WebSocket.OPEN:
|
|
655
|
+
return 'connected';
|
|
656
|
+
case WebSocket.CLOSING:
|
|
657
|
+
return 'disconnecting';
|
|
658
|
+
case WebSocket.CLOSED:
|
|
659
|
+
return 'disconnected';
|
|
660
|
+
default:
|
|
661
|
+
return 'disconnected';
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// Singleton instance
|
|
667
|
+
let wsClient: WebSocketClient | null = null;
|
|
668
|
+
|
|
669
|
+
export const getWebSocketClient = (url?: string): WebSocketClient => {
|
|
670
|
+
if (!wsClient && url) {
|
|
671
|
+
wsClient = new WebSocketClient(url);
|
|
672
|
+
}
|
|
673
|
+
if (!wsClient) {
|
|
674
|
+
throw new Error('WebSocket client not initialized');
|
|
675
|
+
}
|
|
676
|
+
return wsClient;
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
export const initializeWebSocket = (url: string): WebSocketClient => {
|
|
680
|
+
if (wsClient) {
|
|
681
|
+
wsClient.disconnect();
|
|
682
|
+
}
|
|
683
|
+
wsClient = new WebSocketClient(url);
|
|
684
|
+
return wsClient;
|
|
685
|
+
};
|