flock-core 0.4.543__py3-none-any.whl → 0.5.0__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 +1079 -0
- flock/api/themes.py +71 -0
- flock/artifacts.py +86 -0
- flock/cli.py +147 -0
- flock/components.py +189 -0
- flock/dashboard/__init__.py +30 -0
- flock/dashboard/collector.py +559 -0
- flock/dashboard/events.py +188 -0
- flock/dashboard/graph_builder.py +563 -0
- flock/dashboard/launcher.py +235 -0
- flock/dashboard/models/graph.py +156 -0
- flock/dashboard/service.py +991 -0
- flock/dashboard/static_v2/assets/index-DFRnI_mt.js +111 -0
- flock/dashboard/static_v2/assets/index-fPLNdmp1.css +1 -0
- flock/dashboard/static_v2/index.html +13 -0
- flock/dashboard/websocket.py +246 -0
- flock/engines/__init__.py +6 -0
- flock/engines/dspy_engine.py +932 -0
- flock/examples.py +131 -0
- flock/frontend/README.md +778 -0
- flock/frontend/docs/DESIGN_SYSTEM.md +1980 -0
- flock/frontend/index.html +12 -0
- flock/frontend/package-lock.json +4337 -0
- flock/frontend/package.json +48 -0
- flock/frontend/src/App.tsx +139 -0
- flock/frontend/src/__tests__/integration/graph-snapshot.test.tsx +647 -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 +58 -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/MessageDetailWindow.tsx +439 -0
- flock/frontend/src/components/details/MessageHistoryTab.tsx +374 -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 +348 -0
- flock/frontend/src/components/details/tabs.test.tsx +1015 -0
- flock/frontend/src/components/filters/ArtifactTypeFilter.tsx +21 -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/FilterFlyout.module.css +104 -0
- flock/frontend/src/components/filters/FilterFlyout.tsx +80 -0
- flock/frontend/src/components/filters/FilterPills.module.css +220 -0
- flock/frontend/src/components/filters/FilterPills.test.tsx +189 -0
- flock/frontend/src/components/filters/FilterPills.tsx +143 -0
- flock/frontend/src/components/filters/ProducerFilter.tsx +21 -0
- flock/frontend/src/components/filters/SavedFiltersControl.module.css +60 -0
- flock/frontend/src/components/filters/SavedFiltersControl.test.tsx +158 -0
- flock/frontend/src/components/filters/SavedFiltersControl.tsx +159 -0
- flock/frontend/src/components/filters/TagFilter.tsx +21 -0
- flock/frontend/src/components/filters/TimeRangeFilter.module.css +115 -0
- flock/frontend/src/components/filters/TimeRangeFilter.test.tsx +154 -0
- flock/frontend/src/components/filters/TimeRangeFilter.tsx +110 -0
- flock/frontend/src/components/filters/VisibilityFilter.tsx +21 -0
- flock/frontend/src/components/graph/AgentNode.test.tsx +77 -0
- flock/frontend/src/components/graph/AgentNode.tsx +324 -0
- flock/frontend/src/components/graph/GraphCanvas.tsx +613 -0
- flock/frontend/src/components/graph/MessageFlowEdge.tsx +128 -0
- flock/frontend/src/components/graph/MessageNode.test.tsx +64 -0
- flock/frontend/src/components/graph/MessageNode.tsx +129 -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 +420 -0
- flock/frontend/src/components/layout/DashboardLayout.tsx +287 -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/HistoricalArtifactsModule.module.css +288 -0
- flock/frontend/src/components/modules/HistoricalArtifactsModule.tsx +450 -0
- flock/frontend/src/components/modules/HistoricalArtifactsModuleWrapper.tsx +13 -0
- flock/frontend/src/components/modules/JsonAttributeRenderer.tsx +140 -0
- flock/frontend/src/components/modules/ModuleRegistry.test.ts +333 -0
- flock/frontend/src/components/modules/ModuleRegistry.ts +93 -0
- flock/frontend/src/components/modules/ModuleWindow.tsx +223 -0
- flock/frontend/src/components/modules/TraceModuleJaeger.tsx +1971 -0
- flock/frontend/src/components/modules/TraceModuleJaegerWrapper.tsx +13 -0
- flock/frontend/src/components/modules/registerModules.ts +29 -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/MultiSelect.tsx +235 -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/components/settings/TracingSettings.tsx +404 -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 +157 -0
- flock/frontend/src/hooks/usePersistence.ts +141 -0
- flock/frontend/src/main.tsx +13 -0
- flock/frontend/src/services/api.ts +337 -0
- flock/frontend/src/services/graphService.test.ts +330 -0
- flock/frontend/src/services/graphService.ts +75 -0
- flock/frontend/src/services/indexeddb.test.ts +793 -0
- flock/frontend/src/services/indexeddb.ts +848 -0
- flock/frontend/src/services/layout.test.ts +437 -0
- flock/frontend/src/services/layout.ts +357 -0
- flock/frontend/src/services/themeApplicator.ts +140 -0
- flock/frontend/src/services/themeService.ts +77 -0
- flock/frontend/src/services/websocket.ts +650 -0
- flock/frontend/src/store/filterStore.test.ts +250 -0
- flock/frontend/src/store/filterStore.ts +272 -0
- flock/frontend/src/store/graphStore.test.ts +570 -0
- flock/frontend/src/store/graphStore.ts +462 -0
- flock/frontend/src/store/moduleStore.test.ts +253 -0
- flock/frontend/src/store/moduleStore.ts +75 -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 +122 -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 +47 -0
- flock/frontend/src/types/graph.ts +95 -0
- flock/frontend/src/types/modules.ts +10 -0
- flock/frontend/src/types/theme.ts +55 -0
- flock/frontend/src/utils/artifacts.ts +24 -0
- flock/frontend/src/utils/mockData.ts +98 -0
- flock/frontend/src/utils/performance.ts +16 -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 +9 -5
- flock/{core/logging → logging}/__init__.py +2 -3
- flock/logging/auto_trace.py +159 -0
- 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 -107
- flock/{core/logging → logging}/logging.py +78 -61
- flock/{core/logging → logging}/telemetry.py +66 -26
- flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
- flock/logging/telemetry_exporter/duckdb_exporter.py +216 -0
- flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +13 -10
- flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
- flock/logging/trace_and_logged.py +304 -0
- flock/mcp/__init__.py +91 -0
- flock/{core/mcp/mcp_client.py → mcp/client.py} +131 -158
- flock/{core/mcp/mcp_config.py → mcp/config.py} +86 -132
- flock/mcp/manager.py +286 -0
- flock/mcp/servers/sse/__init__.py +1 -1
- flock/mcp/servers/sse/flock_sse_server.py +16 -58
- flock/mcp/servers/stdio/__init__.py +1 -1
- flock/mcp/servers/stdio/flock_stdio_server.py +13 -53
- flock/mcp/servers/streamable_http/flock_streamable_http_server.py +22 -67
- flock/mcp/servers/websockets/flock_websocket_server.py +12 -45
- flock/{core/mcp/flock_mcp_tool_base.py → mcp/tool.py} +24 -78
- flock/mcp/types/__init__.py +42 -0
- flock/{core/mcp → mcp}/types/callbacks.py +12 -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 +3 -3
- flock/orchestrator.py +970 -0
- flock/registry.py +148 -0
- flock/runtime.py +262 -0
- flock/service.py +277 -0
- flock/store.py +1214 -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/darkmatrix.toml +2 -2
- flock/themes/darkside.toml +1 -1
- flock/themes/deep.toml +2 -2
- 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 +5 -5
- 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/utility/output_utility_component.py +226 -0
- flock/visibility.py +107 -0
- flock_core-0.5.0.dist-info/METADATA +964 -0
- flock_core-0.5.0.dist-info/RECORD +525 -0
- flock_core-0.5.0.dist-info/entry_points.txt +2 -0
- {flock_core-0.4.543.dist-info → flock_core-0.5.0.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/config.py +0 -56
- flock/core/__init__.py +0 -44
- flock/core/api/__init__.py +0 -10
- flock/core/api/custom_endpoint.py +0 -45
- flock/core/api/endpoints.py +0 -262
- flock/core/api/main.py +0 -162
- flock/core/api/models.py +0 -101
- flock/core/api/run_store.py +0 -224
- flock/core/api/runner.py +0 -44
- flock/core/api/service.py +0 -214
- flock/core/config/flock_agent_config.py +0 -11
- flock/core/config/scheduled_agent_config.py +0 -40
- flock/core/context/context.py +0 -214
- flock/core/context/context_manager.py +0 -40
- flock/core/context/context_vars.py +0 -11
- flock/core/evaluation/utils.py +0 -395
- 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 -166
- flock/core/flock.py +0 -1003
- flock/core/flock_agent.py +0 -1258
- flock/core/flock_evaluator.py +0 -60
- flock/core/flock_factory.py +0 -513
- flock/core/flock_module.py +0 -207
- flock/core/flock_registry.py +0 -702
- flock/core/flock_router.py +0 -83
- 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/logging/live_capture.py +0 -137
- flock/core/logging/trace_and_logged.py +0 -59
- flock/core/mcp/__init__.py +0 -1
- flock/core/mcp/flock_mcp_server.py +0 -640
- flock/core/mcp/mcp_client_manager.py +0 -201
- flock/core/mcp/types/__init__.py +0 -1
- flock/core/mixin/dspy_integration.py +0 -445
- flock/core/mixin/prompt_parser.py +0 -125
- flock/core/serialization/__init__.py +0 -13
- flock/core/serialization/callable_registry.py +0 -52
- flock/core/serialization/flock_serializer.py +0 -854
- 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 -409
- flock/core/util/file_path_utils.py +0 -223
- flock/core/util/hydrator.py +0 -309
- flock/core/util/input_resolver.py +0 -141
- flock/core/util/loader.py +0 -59
- flock/core/util/splitter.py +0 -219
- flock/di.py +0 -41
- flock/evaluators/__init__.py +0 -1
- flock/evaluators/declarative/__init__.py +0 -1
- flock/evaluators/declarative/declarative_evaluator.py +0 -217
- flock/evaluators/memory/memory_evaluator.py +0 -90
- flock/evaluators/test/test_case_evaluator.py +0 -38
- flock/evaluators/zep/zep_evaluator.py +0 -59
- flock/modules/__init__.py +0 -1
- flock/modules/assertion/__init__.py +0 -1
- flock/modules/assertion/assertion_module.py +0 -286
- flock/modules/callback/__init__.py +0 -1
- flock/modules/callback/callback_module.py +0 -91
- flock/modules/enterprise_memory/README.md +0 -99
- flock/modules/enterprise_memory/enterprise_memory_module.py +0 -526
- flock/modules/mem0/__init__.py +0 -1
- flock/modules/mem0/mem0_module.py +0 -126
- flock/modules/mem0_async/__init__.py +0 -1
- flock/modules/mem0_async/async_mem0_module.py +0 -126
- flock/modules/memory/__init__.py +0 -1
- flock/modules/memory/memory_module.py +0 -429
- flock/modules/memory/memory_parser.py +0 -125
- flock/modules/memory/memory_storage.py +0 -736
- flock/modules/output/__init__.py +0 -1
- flock/modules/output/output_module.py +0 -196
- flock/modules/performance/__init__.py +0 -1
- flock/modules/performance/metrics_module.py +0 -678
- flock/modules/zep/__init__.py +0 -1
- flock/modules/zep/zep_module.py +0 -192
- flock/platform/docker_tools.py +0 -49
- flock/platform/jaeger_install.py +0 -86
- flock/routers/__init__.py +0 -1
- flock/routers/agent/__init__.py +0 -1
- flock/routers/agent/agent_router.py +0 -236
- flock/routers/agent/handoff_agent.py +0 -58
- flock/routers/conditional/conditional_router.py +0 -486
- flock/routers/default/__init__.py +0 -1
- flock/routers/default/default_router.py +0 -80
- flock/routers/feedback/feedback_router.py +0 -114
- flock/routers/list_generator/list_generator_router.py +0 -166
- flock/routers/llm/__init__.py +0 -1
- flock/routers/llm/llm_router.py +0 -365
- flock/tools/__init__.py +0 -0
- flock/tools/azure_tools.py +0 -781
- flock/tools/code_tools.py +0 -167
- flock/tools/file_tools.py +0 -149
- flock/tools/github_tools.py +0 -157
- flock/tools/markdown_tools.py +0 -205
- flock/tools/system_tools.py +0 -9
- flock/tools/text_tools.py +0 -810
- flock/tools/web_tools.py +0 -92
- flock/tools/zendesk_tools.py +0 -501
- 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 -237
- flock/webapp/app/api/execution.py +0 -503
- flock/webapp/app/api/flock_management.py +0 -125
- flock/webapp/app/api/registry_viewer.py +0 -29
- flock/webapp/app/chat.py +0 -662
- flock/webapp/app/config.py +0 -104
- flock/webapp/app/dependencies.py +0 -117
- flock/webapp/app/main.py +0 -1086
- 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 -345
- flock/webapp/app/services/sharing_models.py +0 -81
- flock/webapp/app/services/sharing_store.py +0 -597
- flock/webapp/app/templates/theme_mapper.html +0 -326
- flock/webapp/app/theme_mapper.py +0 -811
- flock/webapp/app/utils.py +0 -85
- flock/webapp/run.py +0 -219
- 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 -281
- flock/webapp/static/css/sidebar.css +0 -127
- flock/webapp/static/css/two-pane.css +0 -48
- flock/webapp/templates/base.html +0 -389
- 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 -127
- 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/_live_logs.html +0 -13
- 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/_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 -237
- flock/workflow/agent_activities.py +0 -24
- flock/workflow/agent_execution_activity.py +0 -240
- flock/workflow/flock_workflow.py +0 -225
- flock/workflow/temporal_config.py +0 -96
- flock/workflow/temporal_setup.py +0 -60
- flock_core-0.4.543.dist-info/METADATA +0 -676
- flock_core-0.4.543.dist-info/RECORD +0 -572
- flock_core-0.4.543.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.4.543.dist-info → flock_core-0.5.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
import { useWSStore } from '../store/wsStore';
|
|
2
|
+
import { useGraphStore } from '../store/graphStore';
|
|
3
|
+
import { useFilterStore } from '../store/filterStore';
|
|
4
|
+
|
|
5
|
+
interface WebSocketMessage {
|
|
6
|
+
event_type: 'agent_activated' | 'message_published' | 'streaming_output' | 'agent_completed' | 'agent_error';
|
|
7
|
+
timestamp: string;
|
|
8
|
+
correlation_id: string;
|
|
9
|
+
session_id: string;
|
|
10
|
+
data: any;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class WebSocketClient {
|
|
14
|
+
ws: WebSocket | null = null;
|
|
15
|
+
private reconnectTimeout: number | null = null;
|
|
16
|
+
private reconnectAttempt = 0;
|
|
17
|
+
private maxReconnectDelay = 30000; // 30 seconds
|
|
18
|
+
private connectionTimeout: number | null = null;
|
|
19
|
+
private connectionTimeoutMs = 10000; // 10 seconds
|
|
20
|
+
private messageBuffer: any[] = [];
|
|
21
|
+
private maxBufferSize = 100;
|
|
22
|
+
private eventHandlers: Map<string, ((data: any) => void)[]> = new Map();
|
|
23
|
+
private url: string;
|
|
24
|
+
private shouldReconnect = true;
|
|
25
|
+
private heartbeatInterval: number | null = null;
|
|
26
|
+
private heartbeatTimeout: number | null = null;
|
|
27
|
+
private connectionStatus: 'connecting' | 'connected' | 'disconnected' | 'disconnecting' | 'error' = 'disconnected';
|
|
28
|
+
private enableHeartbeat: boolean;
|
|
29
|
+
|
|
30
|
+
// UI Optimization Migration (Phase 2 - Spec 002): Debounced graph refresh
|
|
31
|
+
private refreshTimer: number | null = null;
|
|
32
|
+
private refreshDebounceMs = 500; // 500ms batching window
|
|
33
|
+
|
|
34
|
+
constructor(url: string) {
|
|
35
|
+
this.url = url;
|
|
36
|
+
// Phase 11 Fix: Disable heartbeat entirely - it causes unnecessary disconnects
|
|
37
|
+
// WebSocket auto-reconnects on real network issues without needing heartbeat
|
|
38
|
+
// The heartbeat was closing connections every 2min when backend didn't respond to pings
|
|
39
|
+
this.enableHeartbeat = false;
|
|
40
|
+
this.setupEventHandlers();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* UI Optimization Migration (Phase 2 - Spec 002): Debounced graph refresh
|
|
45
|
+
*
|
|
46
|
+
* Batch multiple graph-changing events within 500ms window, then fetch fresh
|
|
47
|
+
* snapshot from backend. This replaces immediate regenerateGraph() calls.
|
|
48
|
+
*/
|
|
49
|
+
private scheduleGraphRefresh(): void {
|
|
50
|
+
if (this.refreshTimer !== null) {
|
|
51
|
+
clearTimeout(this.refreshTimer);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.refreshTimer = window.setTimeout(() => {
|
|
55
|
+
this.refreshTimer = null;
|
|
56
|
+
// Call the NEW async refreshCurrentView() method
|
|
57
|
+
useGraphStore.getState().refreshCurrentView().catch((error) => {
|
|
58
|
+
console.error('[WebSocket] Graph refresh failed:', error);
|
|
59
|
+
});
|
|
60
|
+
}, this.refreshDebounceMs);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private updateFilterStateFromPublishedMessage(data: any): void {
|
|
64
|
+
const filterStore = useFilterStore.getState();
|
|
65
|
+
|
|
66
|
+
const artifactType = typeof data.artifact_type === 'string' ? data.artifact_type : '';
|
|
67
|
+
const producer = typeof data.produced_by === 'string' ? data.produced_by : '';
|
|
68
|
+
const tags = Array.isArray(data.tags) ? data.tags.filter((tag: unknown) => typeof tag === 'string' && tag.length > 0) : [];
|
|
69
|
+
const visibilityKind =
|
|
70
|
+
(typeof data.visibility === 'object' && data.visibility && typeof data.visibility.kind === 'string'
|
|
71
|
+
? data.visibility.kind
|
|
72
|
+
: undefined) ||
|
|
73
|
+
(typeof data.visibility_kind === 'string' ? data.visibility_kind : undefined) ||
|
|
74
|
+
(typeof data.visibility === 'string' ? data.visibility : undefined) ||
|
|
75
|
+
'';
|
|
76
|
+
|
|
77
|
+
const nextArtifactTypes = artifactType
|
|
78
|
+
? [...filterStore.availableArtifactTypes, artifactType]
|
|
79
|
+
: [...filterStore.availableArtifactTypes];
|
|
80
|
+
const nextProducers = producer
|
|
81
|
+
? [...filterStore.availableProducers, producer]
|
|
82
|
+
: [...filterStore.availableProducers];
|
|
83
|
+
const nextTags = tags.length > 0 ? [...filterStore.availableTags, ...tags] : [...filterStore.availableTags];
|
|
84
|
+
const nextVisibility = visibilityKind
|
|
85
|
+
? [...filterStore.availableVisibility, visibilityKind]
|
|
86
|
+
: [...filterStore.availableVisibility];
|
|
87
|
+
|
|
88
|
+
filterStore.updateAvailableFacets({
|
|
89
|
+
artifactTypes: nextArtifactTypes,
|
|
90
|
+
producers: nextProducers,
|
|
91
|
+
tags: nextTags,
|
|
92
|
+
visibilities: nextVisibility,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const baseSummary =
|
|
96
|
+
filterStore.summary ?? {
|
|
97
|
+
total: 0,
|
|
98
|
+
by_type: {} as Record<string, number>,
|
|
99
|
+
by_producer: {} as Record<string, number>,
|
|
100
|
+
by_visibility: {} as Record<string, number>,
|
|
101
|
+
tag_counts: {} as Record<string, number>,
|
|
102
|
+
earliest_created_at: null as string | null,
|
|
103
|
+
latest_created_at: null as string | null,
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const timestampIso =
|
|
107
|
+
(typeof data.timestamp === 'string' ? data.timestamp : undefined) ?? new Date().toISOString();
|
|
108
|
+
|
|
109
|
+
const updatedSummary = {
|
|
110
|
+
total: baseSummary.total + 1,
|
|
111
|
+
by_type: { ...baseSummary.by_type },
|
|
112
|
+
by_producer: { ...baseSummary.by_producer },
|
|
113
|
+
by_visibility: { ...baseSummary.by_visibility },
|
|
114
|
+
tag_counts: { ...baseSummary.tag_counts },
|
|
115
|
+
earliest_created_at:
|
|
116
|
+
baseSummary.earliest_created_at === null || timestampIso < baseSummary.earliest_created_at
|
|
117
|
+
? timestampIso
|
|
118
|
+
: baseSummary.earliest_created_at,
|
|
119
|
+
latest_created_at:
|
|
120
|
+
baseSummary.latest_created_at === null || timestampIso > baseSummary.latest_created_at
|
|
121
|
+
? timestampIso
|
|
122
|
+
: baseSummary.latest_created_at,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
if (artifactType) {
|
|
126
|
+
updatedSummary.by_type[artifactType] = (updatedSummary.by_type[artifactType] || 0) + 1;
|
|
127
|
+
}
|
|
128
|
+
if (producer) {
|
|
129
|
+
updatedSummary.by_producer[producer] = (updatedSummary.by_producer[producer] || 0) + 1;
|
|
130
|
+
}
|
|
131
|
+
if (visibilityKind) {
|
|
132
|
+
updatedSummary.by_visibility[visibilityKind] = (updatedSummary.by_visibility[visibilityKind] || 0) + 1;
|
|
133
|
+
}
|
|
134
|
+
tags.forEach((tag: string) => {
|
|
135
|
+
updatedSummary.tag_counts[tag] = (updatedSummary.tag_counts[tag] || 0) + 1;
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
filterStore.setSummary(updatedSummary);
|
|
139
|
+
|
|
140
|
+
if (typeof data.correlation_id === 'string' && data.correlation_id.length > 0) {
|
|
141
|
+
const timestampMs =
|
|
142
|
+
typeof data.timestamp === 'string' ? new Date(data.timestamp).getTime() : Date.now();
|
|
143
|
+
const existing = filterStore.availableCorrelationIds.find(
|
|
144
|
+
(item) => item.correlation_id === data.correlation_id
|
|
145
|
+
);
|
|
146
|
+
const updatedRecord = existing
|
|
147
|
+
? {
|
|
148
|
+
...existing,
|
|
149
|
+
artifact_count: existing.artifact_count + 1,
|
|
150
|
+
first_seen: Math.min(existing.first_seen, timestampMs),
|
|
151
|
+
}
|
|
152
|
+
: {
|
|
153
|
+
correlation_id: data.correlation_id,
|
|
154
|
+
first_seen: timestampMs,
|
|
155
|
+
artifact_count: 1,
|
|
156
|
+
run_count: 0,
|
|
157
|
+
};
|
|
158
|
+
const nextMetadata = [
|
|
159
|
+
...filterStore.availableCorrelationIds.filter((item) => item.correlation_id !== data.correlation_id),
|
|
160
|
+
updatedRecord,
|
|
161
|
+
];
|
|
162
|
+
filterStore.updateAvailableCorrelationIds(nextMetadata);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
private setupEventHandlers(): void {
|
|
167
|
+
// Handler for agent_activated: create/update agent in graph AND create Run
|
|
168
|
+
this.on('agent_activated', (data) => {
|
|
169
|
+
// UI Optimization Migration (Phase 2 - Spec 002): DEPRECATED client-side agent tracking
|
|
170
|
+
// Backend now handles all agent data. Frontend only tracks real-time status overlay.
|
|
171
|
+
// OLD CODE REMOVED: agents Map, receivedByType tracking, addAgent(), recordConsumption()
|
|
172
|
+
// NEW BEHAVIOR: Backend refresh will include updated agent data
|
|
173
|
+
|
|
174
|
+
// Update real-time status (fast, local)
|
|
175
|
+
useGraphStore.getState().updateAgentStatus(data.agent_name, 'running');
|
|
176
|
+
|
|
177
|
+
// Schedule debounced refresh (batches within 500ms, then fetches backend snapshot)
|
|
178
|
+
this.scheduleGraphRefresh();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Handler for message_published: update existing streaming message or create new one
|
|
182
|
+
this.on('message_published', (data) => {
|
|
183
|
+
// UI Optimization Migration (Phase 2 - Spec 002): DEPRECATED client-side message tracking
|
|
184
|
+
// Backend now handles all message/artifact data. Frontend only tracks events for display.
|
|
185
|
+
// OLD CODE REMOVED: messages Map, addMessage(), updateMessage(), finalizeStreamingMessage(),
|
|
186
|
+
// agent counter updates, run tracking
|
|
187
|
+
// NEW BEHAVIOR: Backend refresh will include all updated data
|
|
188
|
+
|
|
189
|
+
// Phase 6: Finalize streaming message node if it exists
|
|
190
|
+
if (data.artifact_id) {
|
|
191
|
+
useGraphStore.getState().finalizeStreamingMessageNode(data.artifact_id);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Update filter state (still needed for filter UI)
|
|
195
|
+
this.updateFilterStateFromPublishedMessage(data);
|
|
196
|
+
|
|
197
|
+
// Add to events array for Event Log display
|
|
198
|
+
const message = {
|
|
199
|
+
id: data.artifact_id,
|
|
200
|
+
type: data.artifact_type,
|
|
201
|
+
payload: data.payload,
|
|
202
|
+
timestamp: data.timestamp ? new Date(data.timestamp).getTime() : Date.now(),
|
|
203
|
+
correlationId: data.correlation_id || '',
|
|
204
|
+
producedBy: data.produced_by,
|
|
205
|
+
tags: Array.isArray(data.tags) ? data.tags : [],
|
|
206
|
+
visibilityKind: data.visibility?.kind || data.visibility_kind || 'Unknown',
|
|
207
|
+
partitionKey: data.partition_key ?? null,
|
|
208
|
+
version: data.version ?? 1,
|
|
209
|
+
isStreaming: false,
|
|
210
|
+
};
|
|
211
|
+
useGraphStore.getState().addEvent(message);
|
|
212
|
+
|
|
213
|
+
// Schedule debounced refresh (batches multiple events within 500ms)
|
|
214
|
+
// This will replace the streaming node with the full backend snapshot
|
|
215
|
+
this.scheduleGraphRefresh();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Handler for streaming_output: update live output (Phase 6)
|
|
219
|
+
this.on('streaming_output', (data) => {
|
|
220
|
+
// Phase 6: Only log start (sequence=0) and finish (is_final=true) to reduce noise
|
|
221
|
+
if (data.sequence === 0 || data.is_final) {
|
|
222
|
+
console.log('[WebSocket] Streaming output:', data.is_final ? 'FINAL' : 'START', data);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Phase 6: Agent streaming tokens (for yellow ticker in agent nodes)
|
|
226
|
+
// Note: artifact_id is now always present (Phase 6), so we removed the !artifact_id check
|
|
227
|
+
if (data.agent_name && data.output_type === 'llm_token') {
|
|
228
|
+
const { streamingTokens } = useGraphStore.getState();
|
|
229
|
+
const currentTokens = streamingTokens.get(data.agent_name) || [];
|
|
230
|
+
|
|
231
|
+
// Keep only last 6 tokens (news ticker effect)
|
|
232
|
+
const updatedTokens = [...currentTokens, data.content].slice(-6);
|
|
233
|
+
|
|
234
|
+
useGraphStore.getState().updateStreamingTokens(data.agent_name, updatedTokens);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Phase 6: Message streaming preview (for streaming textbox in message nodes)
|
|
238
|
+
if (data.artifact_id && data.output_type === 'llm_token') {
|
|
239
|
+
// Create or update streaming message node
|
|
240
|
+
useGraphStore.getState().createOrUpdateStreamingMessageNode(
|
|
241
|
+
data.artifact_id,
|
|
242
|
+
data.content,
|
|
243
|
+
{
|
|
244
|
+
agent_name: data.agent_name,
|
|
245
|
+
correlation_id: data.correlation_id,
|
|
246
|
+
artifact_type: data.artifact_type, // Phase 6: Artifact type name for node header
|
|
247
|
+
}
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
// Finalize when streaming is complete (is_final=true)
|
|
251
|
+
if (data.is_final) {
|
|
252
|
+
useGraphStore.getState().finalizeStreamingMessageNode(data.artifact_id);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Note: The actual output storage is handled by LiveOutputTab's event listener
|
|
257
|
+
// This handler is for real-time token updates only
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// Handler for agent_completed: update agent status to idle
|
|
261
|
+
this.on('agent_completed', (data) => {
|
|
262
|
+
// UI Optimization Migration (Phase 2 - Spec 002): Use NEW updateAgentStatus()
|
|
263
|
+
// for FAST real-time updates without backend calls
|
|
264
|
+
useGraphStore.getState().updateAgentStatus(data.agent_name, 'idle');
|
|
265
|
+
useGraphStore.getState().updateStreamingTokens(data.agent_name, []); // Clear news ticker
|
|
266
|
+
|
|
267
|
+
// OLD CODE REMOVED: Run status tracking (runs Map, batchUpdate)
|
|
268
|
+
// Backend handles run data now
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
// Handler for agent_error: update agent status to error
|
|
272
|
+
this.on('agent_error', (data) => {
|
|
273
|
+
// UI Optimization Migration (Phase 2 - Spec 002): Use NEW updateAgentStatus()
|
|
274
|
+
// for FAST real-time updates without backend calls
|
|
275
|
+
useGraphStore.getState().updateAgentStatus(data.agent_name, 'error');
|
|
276
|
+
|
|
277
|
+
// OLD CODE REMOVED: Run status tracking (runs Map, batchUpdate)
|
|
278
|
+
// Backend handles run data now
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Handler for ping: respond with pong
|
|
282
|
+
this.on('ping', () => {
|
|
283
|
+
this.send({ type: 'pong', timestamp: Date.now() });
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
connect(): void {
|
|
288
|
+
if (this.ws?.readyState === WebSocket.OPEN || this.ws?.readyState === WebSocket.CONNECTING) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
this.connectionStatus = 'connecting';
|
|
294
|
+
if (typeof useWSStore !== 'undefined') {
|
|
295
|
+
useWSStore.getState().setStatus('connecting');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
this.ws = new WebSocket(this.url);
|
|
299
|
+
|
|
300
|
+
// Set connection timeout
|
|
301
|
+
this.connectionTimeout = window.setTimeout(() => {
|
|
302
|
+
console.warn('[WebSocket] Connection timeout');
|
|
303
|
+
if (this.ws && this.ws.readyState !== WebSocket.OPEN) {
|
|
304
|
+
this.ws.close();
|
|
305
|
+
this.connectionStatus = 'error';
|
|
306
|
+
if (typeof useWSStore !== 'undefined') {
|
|
307
|
+
useWSStore.getState().setStatus('disconnected');
|
|
308
|
+
useWSStore.getState().setError('Connection timeout');
|
|
309
|
+
}
|
|
310
|
+
if (this.shouldReconnect) {
|
|
311
|
+
this.reconnect();
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}, this.connectionTimeoutMs);
|
|
315
|
+
|
|
316
|
+
this.ws.onopen = () => {
|
|
317
|
+
console.log('[WebSocket] Connected');
|
|
318
|
+
|
|
319
|
+
// Clear connection timeout
|
|
320
|
+
if (this.connectionTimeout !== null) {
|
|
321
|
+
clearTimeout(this.connectionTimeout);
|
|
322
|
+
this.connectionTimeout = null;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
this.connectionStatus = 'connected';
|
|
326
|
+
if (typeof useWSStore !== 'undefined') {
|
|
327
|
+
useWSStore.getState().setStatus('connected');
|
|
328
|
+
useWSStore.getState().setError(null);
|
|
329
|
+
useWSStore.getState().resetAttempts();
|
|
330
|
+
}
|
|
331
|
+
this.reconnectAttempt = 0;
|
|
332
|
+
this.flushBuffer();
|
|
333
|
+
if (this.enableHeartbeat) {
|
|
334
|
+
this.startHeartbeat();
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
this.ws.onmessage = (event: MessageEvent) => {
|
|
339
|
+
this.handleMessage(event);
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
this.ws.onerror = (error) => {
|
|
343
|
+
console.error('[WebSocket] Error:', error);
|
|
344
|
+
// Keep connection status as error even after close event
|
|
345
|
+
this.connectionStatus = 'error';
|
|
346
|
+
if (typeof useWSStore !== 'undefined') {
|
|
347
|
+
useWSStore.getState().setError('Connection error');
|
|
348
|
+
useWSStore.getState().setStatus('disconnected');
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
this.ws.onclose = (event) => {
|
|
353
|
+
console.log('[WebSocket] Closed:', event.code, event.reason);
|
|
354
|
+
this.stopHeartbeat();
|
|
355
|
+
|
|
356
|
+
// Don't override error status
|
|
357
|
+
if (this.connectionStatus !== 'error') {
|
|
358
|
+
if (this.shouldReconnect && event.code !== 1000) {
|
|
359
|
+
this.connectionStatus = 'connecting'; // Will be reconnecting
|
|
360
|
+
if (typeof useWSStore !== 'undefined') {
|
|
361
|
+
useWSStore.getState().setStatus('reconnecting');
|
|
362
|
+
}
|
|
363
|
+
this.reconnect();
|
|
364
|
+
} else {
|
|
365
|
+
this.connectionStatus = 'disconnected';
|
|
366
|
+
if (typeof useWSStore !== 'undefined') {
|
|
367
|
+
useWSStore.getState().setStatus('disconnected');
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
} catch (error) {
|
|
373
|
+
console.error('[WebSocket] Connection failed:', error);
|
|
374
|
+
this.connectionStatus = 'error';
|
|
375
|
+
if (typeof useWSStore !== 'undefined') {
|
|
376
|
+
useWSStore.getState().setStatus('disconnected');
|
|
377
|
+
useWSStore.getState().setError(error instanceof Error ? error.message : 'Connection failed');
|
|
378
|
+
}
|
|
379
|
+
if (this.shouldReconnect) {
|
|
380
|
+
this.reconnect();
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
private reconnect(): void {
|
|
386
|
+
if (this.reconnectTimeout !== null) {
|
|
387
|
+
return; // Already scheduled
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Exponential backoff: 1s, 2s, 4s, 8s, max 30s
|
|
391
|
+
const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempt), this.maxReconnectDelay);
|
|
392
|
+
|
|
393
|
+
if (typeof useWSStore !== 'undefined') {
|
|
394
|
+
useWSStore.getState().incrementAttempts();
|
|
395
|
+
}
|
|
396
|
+
this.reconnectAttempt++;
|
|
397
|
+
|
|
398
|
+
console.log(`[WebSocket] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempt})`);
|
|
399
|
+
|
|
400
|
+
this.reconnectTimeout = window.setTimeout(() => {
|
|
401
|
+
this.reconnectTimeout = null;
|
|
402
|
+
this.connect();
|
|
403
|
+
}, delay);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
private handleMessage(event: MessageEvent): void {
|
|
407
|
+
try {
|
|
408
|
+
const data = JSON.parse(event.data);
|
|
409
|
+
|
|
410
|
+
// Handle direct type field (for ping/pong)
|
|
411
|
+
if (data.type === 'ping') {
|
|
412
|
+
this.send({ type: 'pong', timestamp: Date.now() });
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
if (data.type === 'pong') {
|
|
417
|
+
this.resetHeartbeatTimeout();
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Handle WebSocketMessage envelope
|
|
422
|
+
const message: WebSocketMessage = data;
|
|
423
|
+
|
|
424
|
+
// Handle pong as event_type
|
|
425
|
+
if (message.event_type === 'pong' as any) {
|
|
426
|
+
this.resetHeartbeatTimeout();
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Determine if this is an envelope or raw data
|
|
431
|
+
// If it has event_type, it's an envelope; use message.data
|
|
432
|
+
// Otherwise, it's raw data (for tests)
|
|
433
|
+
const eventData = message.event_type ? message.data : data;
|
|
434
|
+
|
|
435
|
+
// Try to detect event type from data
|
|
436
|
+
let eventType = message.event_type;
|
|
437
|
+
if (!eventType) {
|
|
438
|
+
// Infer event type from data structure for test compatibility
|
|
439
|
+
// IMPORTANT: Check streaming_output BEFORE message_published since streaming events
|
|
440
|
+
// now have artifact_id + artifact_type (Phase 6) but also have run_id + output_type
|
|
441
|
+
if (data.agent_id && data.consumed_types) {
|
|
442
|
+
eventType = 'agent_activated';
|
|
443
|
+
} else if (data.run_id && data.output_type) {
|
|
444
|
+
eventType = 'streaming_output';
|
|
445
|
+
} else if (data.artifact_id && data.artifact_type) {
|
|
446
|
+
eventType = 'message_published';
|
|
447
|
+
} else if (data.run_id && data.duration_ms !== undefined) {
|
|
448
|
+
eventType = 'agent_completed';
|
|
449
|
+
} else if (data.run_id && data.error_type) {
|
|
450
|
+
eventType = 'agent_error';
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Dispatch to registered handlers
|
|
455
|
+
if (eventType) {
|
|
456
|
+
const handlers = this.eventHandlers.get(eventType);
|
|
457
|
+
if (handlers) {
|
|
458
|
+
handlers.forEach((handler) => {
|
|
459
|
+
try {
|
|
460
|
+
handler(eventData);
|
|
461
|
+
} catch (error) {
|
|
462
|
+
console.error(`[WebSocket] Handler error for ${eventType}:`, error);
|
|
463
|
+
}
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
} catch (error) {
|
|
468
|
+
console.error('[WebSocket] Failed to parse message:', error);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
send(message: any): void {
|
|
473
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
474
|
+
try {
|
|
475
|
+
this.ws.send(JSON.stringify(message));
|
|
476
|
+
} catch (error) {
|
|
477
|
+
console.error('[WebSocket] Send failed:', error);
|
|
478
|
+
this.bufferMessage(message);
|
|
479
|
+
}
|
|
480
|
+
} else {
|
|
481
|
+
this.bufferMessage(message);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
private bufferMessage(message: any): void {
|
|
486
|
+
if (this.messageBuffer.length >= this.maxBufferSize) {
|
|
487
|
+
this.messageBuffer.shift(); // Remove oldest message
|
|
488
|
+
}
|
|
489
|
+
this.messageBuffer.push(message);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
private flushBuffer(): void {
|
|
493
|
+
if (this.messageBuffer.length === 0) {
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
console.log(`[WebSocket] Flushing ${this.messageBuffer.length} buffered messages`);
|
|
498
|
+
|
|
499
|
+
const messages = [...this.messageBuffer];
|
|
500
|
+
this.messageBuffer = [];
|
|
501
|
+
|
|
502
|
+
messages.forEach((message) => {
|
|
503
|
+
// Send directly to avoid re-buffering
|
|
504
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
505
|
+
try {
|
|
506
|
+
this.ws.send(JSON.stringify(message));
|
|
507
|
+
} catch (error) {
|
|
508
|
+
console.error('[WebSocket] Failed to send buffered message:', error);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
on(eventType: string, handler: (data: any) => void): void {
|
|
515
|
+
if (!this.eventHandlers.has(eventType)) {
|
|
516
|
+
this.eventHandlers.set(eventType, []);
|
|
517
|
+
}
|
|
518
|
+
this.eventHandlers.get(eventType)!.push(handler);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
off(eventType: string, handler: (data: any) => void): void {
|
|
522
|
+
const handlers = this.eventHandlers.get(eventType);
|
|
523
|
+
if (handlers) {
|
|
524
|
+
const index = handlers.indexOf(handler);
|
|
525
|
+
if (index > -1) {
|
|
526
|
+
handlers.splice(index, 1);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
private startHeartbeat(): void {
|
|
532
|
+
this.stopHeartbeat();
|
|
533
|
+
|
|
534
|
+
// Send ping every 2 minutes
|
|
535
|
+
this.heartbeatInterval = window.setInterval(() => {
|
|
536
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
537
|
+
this.send({ type: 'ping' });
|
|
538
|
+
|
|
539
|
+
// Set timeout for pong response (10 seconds)
|
|
540
|
+
this.heartbeatTimeout = window.setTimeout(() => {
|
|
541
|
+
console.warn('[WebSocket] Heartbeat timeout, closing connection');
|
|
542
|
+
this.ws?.close();
|
|
543
|
+
}, 10000);
|
|
544
|
+
}
|
|
545
|
+
}, 120000);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
private stopHeartbeat(): void {
|
|
549
|
+
if (this.heartbeatInterval !== null) {
|
|
550
|
+
clearInterval(this.heartbeatInterval);
|
|
551
|
+
this.heartbeatInterval = null;
|
|
552
|
+
}
|
|
553
|
+
if (this.heartbeatTimeout !== null) {
|
|
554
|
+
clearTimeout(this.heartbeatTimeout);
|
|
555
|
+
this.heartbeatTimeout = null;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
private resetHeartbeatTimeout(): void {
|
|
560
|
+
if (this.heartbeatTimeout !== null) {
|
|
561
|
+
clearTimeout(this.heartbeatTimeout);
|
|
562
|
+
this.heartbeatTimeout = null;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
disconnect(): void {
|
|
567
|
+
this.shouldReconnect = false;
|
|
568
|
+
this.connectionStatus = 'disconnecting';
|
|
569
|
+
|
|
570
|
+
if (this.reconnectTimeout !== null) {
|
|
571
|
+
clearTimeout(this.reconnectTimeout);
|
|
572
|
+
this.reconnectTimeout = null;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
if (this.connectionTimeout !== null) {
|
|
576
|
+
clearTimeout(this.connectionTimeout);
|
|
577
|
+
this.connectionTimeout = null;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
this.stopHeartbeat();
|
|
581
|
+
|
|
582
|
+
if (this.ws) {
|
|
583
|
+
this.ws.close();
|
|
584
|
+
this.ws = null;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Status will be set to 'disconnected' by onclose handler
|
|
588
|
+
// Don't override it here to maintain proper status flow
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
reconnectManually(): void {
|
|
592
|
+
this.shouldReconnect = true;
|
|
593
|
+
this.reconnectAttempt = 0;
|
|
594
|
+
if (typeof useWSStore !== 'undefined') {
|
|
595
|
+
useWSStore.getState().resetAttempts();
|
|
596
|
+
}
|
|
597
|
+
this.connect();
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Test helper methods
|
|
601
|
+
isConnected(): boolean {
|
|
602
|
+
return this.ws?.readyState === WebSocket.OPEN && this.connectionStatus !== 'error';
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
getConnectionStatus(): string {
|
|
606
|
+
return this.connectionStatus;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
getBufferedMessageCount(): number {
|
|
610
|
+
return this.messageBuffer.length;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
getStatus(): string {
|
|
614
|
+
if (!this.ws) return 'disconnected';
|
|
615
|
+
|
|
616
|
+
switch (this.ws.readyState) {
|
|
617
|
+
case WebSocket.CONNECTING:
|
|
618
|
+
return 'connecting';
|
|
619
|
+
case WebSocket.OPEN:
|
|
620
|
+
return 'connected';
|
|
621
|
+
case WebSocket.CLOSING:
|
|
622
|
+
return 'disconnecting';
|
|
623
|
+
case WebSocket.CLOSED:
|
|
624
|
+
return 'disconnected';
|
|
625
|
+
default:
|
|
626
|
+
return 'disconnected';
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
// Singleton instance
|
|
632
|
+
let wsClient: WebSocketClient | null = null;
|
|
633
|
+
|
|
634
|
+
export const getWebSocketClient = (url?: string): WebSocketClient => {
|
|
635
|
+
if (!wsClient && url) {
|
|
636
|
+
wsClient = new WebSocketClient(url);
|
|
637
|
+
}
|
|
638
|
+
if (!wsClient) {
|
|
639
|
+
throw new Error('WebSocket client not initialized');
|
|
640
|
+
}
|
|
641
|
+
return wsClient;
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
export const initializeWebSocket = (url: string): WebSocketClient => {
|
|
645
|
+
if (wsClient) {
|
|
646
|
+
wsClient.disconnect();
|
|
647
|
+
}
|
|
648
|
+
wsClient = new WebSocketClient(url);
|
|
649
|
+
return wsClient;
|
|
650
|
+
};
|