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,587 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* End-to-End Tests for Critical Dashboard Scenarios (Frontend)
|
|
3
|
+
*
|
|
4
|
+
* Tests the 4 critical scenarios from SDD_COMPLETION.md (lines 444-493):
|
|
5
|
+
* 1. End-to-End Agent Execution Visualization (WebSocket → stores → React Flow rendering)
|
|
6
|
+
* 2. WebSocket Reconnection After Backend Restart (client-side resilience)
|
|
7
|
+
* 3. Correlation ID Filtering (autocomplete → filter → graph updates)
|
|
8
|
+
* 4. IndexedDB LRU Eviction (storage quota management with custom mocking)
|
|
9
|
+
*
|
|
10
|
+
* SPECIFICATION: docs/specs/003-real-time-dashboard/SDD_COMPLETION.md Section: Critical Test Scenarios
|
|
11
|
+
*
|
|
12
|
+
* These tests validate the complete frontend flow from WebSocket events through
|
|
13
|
+
* Zustand stores to React Flow graph visualization.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import 'fake-indexeddb/auto'; // Polyfills global IndexedDB for tests
|
|
17
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
18
|
+
import { waitFor, act } from '@testing-library/react';
|
|
19
|
+
import { WebSocketClient } from '../../services/websocket';
|
|
20
|
+
import { useGraphStore } from '../../store/graphStore';
|
|
21
|
+
import { useFilterStore } from '../../store/filterStore';
|
|
22
|
+
import { useWSStore } from '../../store/wsStore';
|
|
23
|
+
import { indexedDBService } from '../../services/indexeddb';
|
|
24
|
+
import { CorrelationIdMetadata } from '../../types/filters';
|
|
25
|
+
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// Mock WebSocket Client
|
|
28
|
+
// ============================================================================
|
|
29
|
+
|
|
30
|
+
class MockWebSocket {
|
|
31
|
+
url: string;
|
|
32
|
+
readyState: number;
|
|
33
|
+
onopen: ((event: Event) => void) | null = null;
|
|
34
|
+
onmessage: ((event: MessageEvent) => void) | null = null;
|
|
35
|
+
onerror: ((event: Event) => void) | null = null;
|
|
36
|
+
onclose: ((event: CloseEvent) => void) | null = null;
|
|
37
|
+
|
|
38
|
+
static CONNECTING = 0;
|
|
39
|
+
static OPEN = 1;
|
|
40
|
+
static CLOSING = 2;
|
|
41
|
+
static CLOSED = 3;
|
|
42
|
+
|
|
43
|
+
constructor(url: string) {
|
|
44
|
+
this.url = url;
|
|
45
|
+
this.readyState = MockWebSocket.CONNECTING;
|
|
46
|
+
|
|
47
|
+
// Auto-connect after construction (unless disabled for testing)
|
|
48
|
+
if (MockWebSocket.autoConnect) {
|
|
49
|
+
setTimeout(() => {
|
|
50
|
+
this.readyState = MockWebSocket.OPEN;
|
|
51
|
+
this.onopen?.(new Event('open'));
|
|
52
|
+
}, 0);
|
|
53
|
+
} else {
|
|
54
|
+
// If auto-connect is disabled, simulate connection failure
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
this.readyState = MockWebSocket.CLOSED;
|
|
57
|
+
// Only fire close event (not error) to avoid setting error status
|
|
58
|
+
// This allows the client to keep retrying with exponential backoff
|
|
59
|
+
this.onclose?.(new CloseEvent('close', { code: 1006, reason: 'Connection failed' }));
|
|
60
|
+
}, 0);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
send(_data: string) {
|
|
65
|
+
if (this.readyState !== MockWebSocket.OPEN) {
|
|
66
|
+
throw new Error('WebSocket is not open');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
close() {
|
|
71
|
+
this.readyState = MockWebSocket.CLOSED;
|
|
72
|
+
this.onclose?.(new CloseEvent('close'));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Test helper: simulate receiving message
|
|
76
|
+
simulateMessage(data: any) {
|
|
77
|
+
if (this.readyState === MockWebSocket.OPEN) {
|
|
78
|
+
this.onmessage?.(new MessageEvent('message', { data: JSON.stringify(data) }));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Test helper: simulate connection error
|
|
83
|
+
simulateError() {
|
|
84
|
+
this.onerror?.(new Event('error'));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Test helper: simulate disconnection
|
|
88
|
+
simulateDisconnect() {
|
|
89
|
+
this.readyState = MockWebSocket.CLOSED;
|
|
90
|
+
this.onclose?.(new CloseEvent('close'));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Test helper: simulate reconnection
|
|
94
|
+
simulateReconnect() {
|
|
95
|
+
this.readyState = MockWebSocket.OPEN;
|
|
96
|
+
this.onopen?.(new Event('open'));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Test helper: prevent auto-connection (for testing reconnection retries)
|
|
100
|
+
preventAutoConnect() {
|
|
101
|
+
(this.constructor as any).autoConnect = false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
static autoConnect = true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ============================================================================
|
|
108
|
+
// Mock Storage Quota API for LRU Testing
|
|
109
|
+
// ============================================================================
|
|
110
|
+
|
|
111
|
+
class MockStorageManager {
|
|
112
|
+
private mockUsage: number = 0;
|
|
113
|
+
private mockQuota: number = 50 * 1024 * 1024; // 50MB default quota
|
|
114
|
+
private usageSequence: number[] = [];
|
|
115
|
+
private sequenceIndex: number = 0;
|
|
116
|
+
|
|
117
|
+
async estimate(): Promise<{ usage: number; quota: number }> {
|
|
118
|
+
// Use fixed sequence if configured, otherwise return current usage
|
|
119
|
+
let currentUsage = this.mockUsage;
|
|
120
|
+
if (this.usageSequence.length > 0) {
|
|
121
|
+
currentUsage = this.usageSequence[Math.min(this.sequenceIndex, this.usageSequence.length - 1)]!;
|
|
122
|
+
this.sequenceIndex++;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
usage: currentUsage,
|
|
127
|
+
quota: this.mockQuota,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Test helpers
|
|
132
|
+
setUsage(bytes: number) {
|
|
133
|
+
this.mockUsage = bytes;
|
|
134
|
+
this.sequenceIndex = 0;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
setQuota(bytes: number) {
|
|
138
|
+
this.mockQuota = bytes;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Set a sequence of usage values to return (simulates deletion reducing usage)
|
|
142
|
+
setUsageSequence(sequence: number[]) {
|
|
143
|
+
this.usageSequence = sequence;
|
|
144
|
+
this.sequenceIndex = 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
getUsagePercentage(): number {
|
|
148
|
+
return this.mockQuota > 0 ? this.mockUsage / this.mockQuota : 0;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ============================================================================
|
|
153
|
+
// Test Setup and Fixtures
|
|
154
|
+
// ============================================================================
|
|
155
|
+
|
|
156
|
+
describe('Critical E2E Scenarios (Frontend)', () => {
|
|
157
|
+
let mockWs: any;
|
|
158
|
+
let mockStorageManager: MockStorageManager;
|
|
159
|
+
let wsClient: WebSocketClient;
|
|
160
|
+
|
|
161
|
+
beforeEach(() => {
|
|
162
|
+
// Reset all stores
|
|
163
|
+
const graphStore = useGraphStore.getState();
|
|
164
|
+
graphStore.agents.clear();
|
|
165
|
+
graphStore.messages.clear();
|
|
166
|
+
graphStore.events = [];
|
|
167
|
+
graphStore.runs.clear();
|
|
168
|
+
graphStore.consumptions.clear();
|
|
169
|
+
|
|
170
|
+
const filterStore = useFilterStore.getState();
|
|
171
|
+
filterStore.clearFilters();
|
|
172
|
+
filterStore.updateAvailableCorrelationIds([]);
|
|
173
|
+
|
|
174
|
+
const wsStore = useWSStore.getState();
|
|
175
|
+
wsStore.setStatus('disconnected');
|
|
176
|
+
wsStore.setError(null);
|
|
177
|
+
wsStore.resetAttempts();
|
|
178
|
+
|
|
179
|
+
// Setup mock WebSocket
|
|
180
|
+
(globalThis as any).WebSocket = MockWebSocket;
|
|
181
|
+
MockWebSocket.autoConnect = true; // Reset auto-connect flag
|
|
182
|
+
|
|
183
|
+
// Setup mock storage manager
|
|
184
|
+
mockStorageManager = new MockStorageManager();
|
|
185
|
+
if (!navigator.storage) {
|
|
186
|
+
(navigator as any).storage = {};
|
|
187
|
+
}
|
|
188
|
+
navigator.storage.estimate = mockStorageManager.estimate.bind(mockStorageManager);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
afterEach(() => {
|
|
192
|
+
// Cleanup
|
|
193
|
+
if (wsClient) {
|
|
194
|
+
wsClient.disconnect();
|
|
195
|
+
}
|
|
196
|
+
vi.clearAllTimers();
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// ==========================================================================
|
|
200
|
+
// Scenario 1: End-to-End Agent Execution Visualization
|
|
201
|
+
// ==========================================================================
|
|
202
|
+
|
|
203
|
+
describe('Scenario 1: End-to-End Agent Execution Visualization', () => {
|
|
204
|
+
it('should render complete agent execution flow from WebSocket events', async () => {
|
|
205
|
+
/**
|
|
206
|
+
* GIVEN: WebSocket client connected
|
|
207
|
+
* WHEN: Receive agent_activated → message_published → agent_completed sequence
|
|
208
|
+
* THEN: Graph nodes and edges are created correctly
|
|
209
|
+
* AND: Agent status transitions are tracked
|
|
210
|
+
*/
|
|
211
|
+
|
|
212
|
+
// Setup: Create WebSocket client
|
|
213
|
+
wsClient = new WebSocketClient('ws://localhost:8000/ws');
|
|
214
|
+
wsClient.connect();
|
|
215
|
+
|
|
216
|
+
await waitFor(() => expect(wsClient.isConnected()).toBe(true));
|
|
217
|
+
mockWs = wsClient.ws as any;
|
|
218
|
+
|
|
219
|
+
// Step 1: Agent activated (raw format for test compatibility)
|
|
220
|
+
await act(async () => {
|
|
221
|
+
mockWs.simulateMessage({
|
|
222
|
+
agent_id: 'test_agent',
|
|
223
|
+
agent_name: 'test_agent',
|
|
224
|
+
run_id: 'run_123',
|
|
225
|
+
consumed_types: ['Input'],
|
|
226
|
+
consumed_artifacts: ['input-1'],
|
|
227
|
+
correlation_id: 'test-correlation-1',
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
await waitFor(() => {
|
|
232
|
+
const agents = useGraphStore.getState().agents;
|
|
233
|
+
expect(agents.has('test_agent')).toBe(true);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Verify agent state
|
|
237
|
+
const agent = useGraphStore.getState().agents.get('test_agent');
|
|
238
|
+
expect(agent?.status).toBe('running');
|
|
239
|
+
expect(agent?.recvCount).toBe(1);
|
|
240
|
+
|
|
241
|
+
// Step 2: Message published (raw format for test compatibility)
|
|
242
|
+
await act(async () => {
|
|
243
|
+
mockWs.simulateMessage({
|
|
244
|
+
artifact_id: 'output-1',
|
|
245
|
+
artifact_type: 'Output',
|
|
246
|
+
produced_by: 'test_agent',
|
|
247
|
+
payload: { result: 'success' },
|
|
248
|
+
correlation_id: 'test-correlation-1',
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
await waitFor(() => {
|
|
253
|
+
const messages = useGraphStore.getState().messages;
|
|
254
|
+
expect(messages.has('output-1')).toBe(true);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Verify message state
|
|
258
|
+
const message = useGraphStore.getState().messages.get('output-1');
|
|
259
|
+
expect(message?.type).toBe('Output');
|
|
260
|
+
expect(message?.producedBy).toBe('test_agent');
|
|
261
|
+
|
|
262
|
+
// Step 3: Agent completed (raw format for test compatibility)
|
|
263
|
+
await act(async () => {
|
|
264
|
+
mockWs.simulateMessage({
|
|
265
|
+
agent_name: 'test_agent',
|
|
266
|
+
run_id: 'run_123',
|
|
267
|
+
duration_ms: 150,
|
|
268
|
+
artifacts_produced: ['output-1'],
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
await waitFor(() => {
|
|
273
|
+
const agent = useGraphStore.getState().agents.get('test_agent');
|
|
274
|
+
expect(agent?.status).toBe('idle');
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Verify final state
|
|
278
|
+
const finalState = useGraphStore.getState();
|
|
279
|
+
expect(finalState.agents.size).toBe(1);
|
|
280
|
+
expect(finalState.messages.size).toBe(1);
|
|
281
|
+
expect(finalState.runs.size).toBeGreaterThan(0);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// ==========================================================================
|
|
286
|
+
// Scenario 2: WebSocket Reconnection After Backend Restart
|
|
287
|
+
// ==========================================================================
|
|
288
|
+
|
|
289
|
+
describe('Scenario 2: WebSocket Reconnection After Backend Restart', () => {
|
|
290
|
+
it('should handle connection loss and automatic reconnection with exponential backoff', { timeout: 20000 }, async () => {
|
|
291
|
+
/**
|
|
292
|
+
* GIVEN: Active WebSocket connection
|
|
293
|
+
* WHEN: Connection is lost (backend restart simulation)
|
|
294
|
+
* THEN: Client attempts reconnection with exponential backoff (1s, 2s, 4s, 8s)
|
|
295
|
+
* AND: Successfully reconnects when backend is available
|
|
296
|
+
* AND: Reconnect attempts counter is reset to 0
|
|
297
|
+
*
|
|
298
|
+
* APPROACH: Use MockWebSocket with controlled auto-connect behavior (no fake timers).
|
|
299
|
+
* Wait for real time to pass to validate exponential backoff intervals.
|
|
300
|
+
*/
|
|
301
|
+
|
|
302
|
+
// Step 1: Connect WebSocket client (using MockWebSocket)
|
|
303
|
+
wsClient = new WebSocketClient('ws://localhost:8000/ws');
|
|
304
|
+
wsClient.connect();
|
|
305
|
+
|
|
306
|
+
await waitFor(() => expect(wsClient.isConnected()).toBe(true), { timeout: 5000 });
|
|
307
|
+
mockWs = wsClient.ws as any;
|
|
308
|
+
|
|
309
|
+
// Step 2: Simulate connection loss
|
|
310
|
+
await act(async () => {
|
|
311
|
+
// Disable auto-connect so reconnection attempts fail
|
|
312
|
+
MockWebSocket.autoConnect = false;
|
|
313
|
+
// Simulate abnormal disconnect (code != 1000 triggers reconnection)
|
|
314
|
+
mockWs.readyState = MockWebSocket.CLOSED;
|
|
315
|
+
mockWs.onclose?.(new CloseEvent('close', { code: 1006, reason: 'Server shutdown' }));
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// Wait for reconnection status
|
|
319
|
+
await waitFor(() => {
|
|
320
|
+
expect(wsClient.isConnected()).toBe(false);
|
|
321
|
+
expect(useWSStore.getState().status).toBe('reconnecting');
|
|
322
|
+
}, { timeout: 2000 });
|
|
323
|
+
|
|
324
|
+
// Record initial attempt count
|
|
325
|
+
const initialAttempts = useWSStore.getState().reconnectAttempts;
|
|
326
|
+
expect(initialAttempts).toBeGreaterThan(0);
|
|
327
|
+
|
|
328
|
+
// Step 3: Wait for multiple reconnection attempts with exponential backoff
|
|
329
|
+
// Backoff: 1s, 2s, 4s, 8s
|
|
330
|
+
// Total time for 4 attempts: ~15s
|
|
331
|
+
// We'll wait 8s to capture 3-4 attempts (1s + 2s + 4s = 7s + margin)
|
|
332
|
+
await new Promise((resolve) => setTimeout(resolve, 8000));
|
|
333
|
+
|
|
334
|
+
// Verify reconnection attempts increased
|
|
335
|
+
const attemptsAfterBackoff = useWSStore.getState().reconnectAttempts;
|
|
336
|
+
expect(attemptsAfterBackoff).toBeGreaterThanOrEqual(3);
|
|
337
|
+
expect(useWSStore.getState().status).toBe('reconnecting');
|
|
338
|
+
|
|
339
|
+
// Step 4: Re-enable auto-connect to allow successful reconnection
|
|
340
|
+
await act(async () => {
|
|
341
|
+
MockWebSocket.autoConnect = true;
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
// Wait for next reconnection attempt to succeed (max 8s for next backoff)
|
|
345
|
+
await waitFor(() => {
|
|
346
|
+
expect(wsClient.isConnected()).toBe(true);
|
|
347
|
+
expect(useWSStore.getState().status).toBe('connected');
|
|
348
|
+
}, { timeout: 10000 });
|
|
349
|
+
|
|
350
|
+
// Step 5: Verify reconnect counter is reset
|
|
351
|
+
const finalStore = useWSStore.getState();
|
|
352
|
+
expect(finalStore.reconnectAttempts).toBe(0);
|
|
353
|
+
|
|
354
|
+
// Cleanup: Reset auto-connect for other tests
|
|
355
|
+
MockWebSocket.autoConnect = true;
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
// ==========================================================================
|
|
360
|
+
// Scenario 3: Correlation ID Filtering
|
|
361
|
+
// ==========================================================================
|
|
362
|
+
|
|
363
|
+
describe('Scenario 3: Correlation ID Filtering', () => {
|
|
364
|
+
it('should filter graph nodes and edges by selected correlation ID', { timeout: 10000 }, async () => {
|
|
365
|
+
/**
|
|
366
|
+
* GIVEN: Multiple artifacts with different correlation IDs
|
|
367
|
+
* WHEN: User selects a correlation ID filter
|
|
368
|
+
* THEN: Only artifacts/agents with matching correlation ID are visible
|
|
369
|
+
* AND: Graph edges are updated to reflect filtered view
|
|
370
|
+
*/
|
|
371
|
+
|
|
372
|
+
// Setup: Connect WebSocket
|
|
373
|
+
wsClient = new WebSocketClient('ws://localhost:8000/ws');
|
|
374
|
+
wsClient.connect();
|
|
375
|
+
await waitFor(() => expect(wsClient.isConnected()).toBe(true));
|
|
376
|
+
mockWs = wsClient.ws as any;
|
|
377
|
+
|
|
378
|
+
// Setup: Receive events with 3 different correlation IDs
|
|
379
|
+
const correlationIds = ['abc-123-xxx', 'abc-456-yyy', 'def-789-zzz'];
|
|
380
|
+
|
|
381
|
+
for (let i = 0; i < correlationIds.length; i++) {
|
|
382
|
+
await act(async () => {
|
|
383
|
+
mockWs.simulateMessage({
|
|
384
|
+
artifact_id: `artifact-${i}`,
|
|
385
|
+
artifact_type: 'TestOutput',
|
|
386
|
+
produced_by: 'test_agent',
|
|
387
|
+
payload: { index: i },
|
|
388
|
+
correlation_id: correlationIds[i],
|
|
389
|
+
timestamp: new Date(Date.now() + i * 1000).toISOString(),
|
|
390
|
+
});
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Wait for all messages to be added to the store
|
|
395
|
+
await waitFor(() => {
|
|
396
|
+
const messages = useGraphStore.getState().messages;
|
|
397
|
+
expect(messages.size).toBe(3);
|
|
398
|
+
}, { timeout: 5000 });
|
|
399
|
+
|
|
400
|
+
// Update available correlation IDs
|
|
401
|
+
const metadata: CorrelationIdMetadata[] = correlationIds.map((id, index) => ({
|
|
402
|
+
correlation_id: id,
|
|
403
|
+
first_seen: Date.now() + index * 1000,
|
|
404
|
+
artifact_count: 1,
|
|
405
|
+
run_count: 1,
|
|
406
|
+
}));
|
|
407
|
+
useFilterStore.getState().updateAvailableCorrelationIds(metadata);
|
|
408
|
+
|
|
409
|
+
// Generate graph with all events (use fresh state)
|
|
410
|
+
await act(async () => {
|
|
411
|
+
useGraphStore.getState().generateBlackboardViewGraph();
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Wait for nodes to be generated
|
|
415
|
+
await waitFor(() => {
|
|
416
|
+
const nodes = useGraphStore.getState().nodes;
|
|
417
|
+
const visibleNodes = nodes.filter((n) => !n.hidden);
|
|
418
|
+
expect(visibleNodes.length).toBe(3);
|
|
419
|
+
}, { timeout: 5000 });
|
|
420
|
+
|
|
421
|
+
// Apply correlation ID filter (use fresh state)
|
|
422
|
+
await act(async () => {
|
|
423
|
+
useFilterStore.getState().setCorrelationId('abc-123-xxx');
|
|
424
|
+
useGraphStore.getState().applyFilters();
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// Verify: Only 1 node visible (the one with matching correlation ID)
|
|
428
|
+
await waitFor(() => {
|
|
429
|
+
const filteredNodes = useGraphStore.getState().nodes.filter((n) => !n.hidden);
|
|
430
|
+
expect(filteredNodes.length).toBe(1);
|
|
431
|
+
expect(filteredNodes[0]?.id).toBe('artifact-0');
|
|
432
|
+
}, { timeout: 5000 });
|
|
433
|
+
|
|
434
|
+
// Verify: Edges connected to hidden nodes are also hidden
|
|
435
|
+
const visibleEdges = useGraphStore.getState().edges.filter((e) => !e.hidden);
|
|
436
|
+
// Should be 0 since we don't have transformation edges without runs
|
|
437
|
+
expect(visibleEdges.length).toBe(0);
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
// ==========================================================================
|
|
442
|
+
// Scenario 4: IndexedDB LRU Eviction
|
|
443
|
+
// ==========================================================================
|
|
444
|
+
|
|
445
|
+
describe('Scenario 4: IndexedDB LRU Eviction', () => {
|
|
446
|
+
beforeEach(async () => {
|
|
447
|
+
// Clean up IndexedDB between tests
|
|
448
|
+
if (indexedDBService.db) {
|
|
449
|
+
indexedDBService.db.close();
|
|
450
|
+
indexedDBService.db = null;
|
|
451
|
+
}
|
|
452
|
+
// Delete the database to ensure fresh state
|
|
453
|
+
if (typeof indexedDB !== 'undefined') {
|
|
454
|
+
const deleteRequest = indexedDB.deleteDatabase('flock_dashboard_v1');
|
|
455
|
+
await new Promise<void>((resolve, reject) => {
|
|
456
|
+
deleteRequest.onsuccess = () => resolve();
|
|
457
|
+
deleteRequest.onerror = () => reject(deleteRequest.error);
|
|
458
|
+
deleteRequest.onblocked = () => resolve(); // Continue even if blocked
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
it('should evict oldest sessions when storage quota exceeds 80% threshold', async () => {
|
|
464
|
+
/**
|
|
465
|
+
* GIVEN: Storage usage at 84% (above 80% threshold)
|
|
466
|
+
* WHEN: LRU eviction is triggered
|
|
467
|
+
* THEN: Oldest sessions are evicted until usage drops to 60% target
|
|
468
|
+
* AND: Most recent sessions are preserved
|
|
469
|
+
* AND: Current session data is preserved
|
|
470
|
+
*/
|
|
471
|
+
|
|
472
|
+
const quota = 50 * 1024 * 1024; // 50MB
|
|
473
|
+
mockStorageManager.setQuota(quota);
|
|
474
|
+
|
|
475
|
+
// Setup: Configure usage sequence to simulate eviction progress
|
|
476
|
+
// Start at 84% (42MB), after deleting 2 sessions reach 60% (30MB)
|
|
477
|
+
const initialUsage = quota * 0.84; // 42MB
|
|
478
|
+
const afterDelete1 = quota * 0.72; // 36MB (after deleting 1 session)
|
|
479
|
+
const afterDelete2 = quota * 0.60; // 30MB (after deleting 2 sessions - target reached)
|
|
480
|
+
|
|
481
|
+
mockStorageManager.setUsageSequence([
|
|
482
|
+
initialUsage, // First loop iteration: 84% > 60%, delete session-0
|
|
483
|
+
afterDelete1, // Second loop iteration: 72% > 60%, delete session-1
|
|
484
|
+
afterDelete2, // Third loop iteration: 60% <= 60%, BREAK (should not delete)
|
|
485
|
+
afterDelete2, // Any additional calls stay at 60%
|
|
486
|
+
afterDelete2,
|
|
487
|
+
afterDelete2,
|
|
488
|
+
afterDelete2,
|
|
489
|
+
afterDelete2,
|
|
490
|
+
]);
|
|
491
|
+
|
|
492
|
+
// Initialize IndexedDB service for testing
|
|
493
|
+
await indexedDBService.initialize();
|
|
494
|
+
|
|
495
|
+
// Create 5 sessions with different timestamps (oldest first)
|
|
496
|
+
for (let i = 0; i < 5; i++) {
|
|
497
|
+
const sessionId = `session-${i}`;
|
|
498
|
+
const timestamp = new Date(Date.now() - (5 - i) * 60000).toISOString(); // Each 1 minute apart
|
|
499
|
+
|
|
500
|
+
// Store in IndexedDB
|
|
501
|
+
await indexedDBService.saveSession({
|
|
502
|
+
session_id: sessionId,
|
|
503
|
+
created_at: timestamp,
|
|
504
|
+
last_activity: timestamp,
|
|
505
|
+
artifact_count: 0,
|
|
506
|
+
run_count: 0,
|
|
507
|
+
size_estimate_bytes: 6 * 1024 * 1024, // 6MB per session
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// Verify sessions were saved
|
|
512
|
+
const savedSessions = await indexedDBService.getAllSessions();
|
|
513
|
+
console.log(`[Test] Saved ${savedSessions.length} sessions before eviction`);
|
|
514
|
+
expect(savedSessions.length).toBe(5); // Verify all 5 sessions were saved
|
|
515
|
+
|
|
516
|
+
// Trigger eviction
|
|
517
|
+
await act(async () => {
|
|
518
|
+
await indexedDBService.evictOldSessions();
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
// Verify: Old sessions were evicted
|
|
522
|
+
const remainingSessions = await indexedDBService.getAllSessions();
|
|
523
|
+
console.log(`[Test] ${remainingSessions.length} sessions remaining after eviction`);
|
|
524
|
+
|
|
525
|
+
// Should have evicted 2 oldest sessions (session-0, session-1), keeping 3 (session-2, session-3, session-4)
|
|
526
|
+
expect(remainingSessions.length).toBe(3);
|
|
527
|
+
|
|
528
|
+
// Verify: Most recent sessions are preserved
|
|
529
|
+
const remainingIds = remainingSessions.map((s: any) => s.session_id);
|
|
530
|
+
|
|
531
|
+
// Most recent session should be preserved
|
|
532
|
+
expect(remainingIds).toContain('session-4');
|
|
533
|
+
expect(remainingIds).toContain('session-3');
|
|
534
|
+
expect(remainingIds).toContain('session-2');
|
|
535
|
+
|
|
536
|
+
// Oldest sessions should be gone
|
|
537
|
+
expect(remainingIds).not.toContain('session-0');
|
|
538
|
+
expect(remainingIds).not.toContain('session-1');
|
|
539
|
+
|
|
540
|
+
console.log(`[LRU] Evicted ${5 - remainingSessions.length} oldest sessions`);
|
|
541
|
+
console.log(`[LRU] Preserved sessions: ${remainingIds.join(', ')}`);
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it('should preserve current session and most recent 10 sessions during eviction', async () => {
|
|
545
|
+
const quota = 50 * 1024 * 1024;
|
|
546
|
+
mockStorageManager.setQuota(quota);
|
|
547
|
+
|
|
548
|
+
const initialUsage = quota * 0.85; // 42.5MB
|
|
549
|
+
|
|
550
|
+
// Configure usage sequence: Start at 85%, delete 5 sessions to reach 60%
|
|
551
|
+
const usageSequence = [
|
|
552
|
+
initialUsage, // First iteration: 85% > 60%, delete session-0
|
|
553
|
+
quota * 0.80, // Second iteration: 80% > 60%, delete session-1
|
|
554
|
+
quota * 0.75, // Third iteration: 75% > 60%, delete session-2
|
|
555
|
+
quota * 0.70, // Fourth iteration: 70% > 60%, delete session-3
|
|
556
|
+
quota * 0.65, // Fifth iteration: 65% > 60%, delete session-4
|
|
557
|
+
quota * 0.60, // Sixth iteration: 60% <= 60%, BREAK
|
|
558
|
+
quota * 0.60, // Stay at target
|
|
559
|
+
];
|
|
560
|
+
mockStorageManager.setUsageSequence(usageSequence);
|
|
561
|
+
|
|
562
|
+
// Initialize IndexedDB service for testing
|
|
563
|
+
await indexedDBService.initialize();
|
|
564
|
+
|
|
565
|
+
// Create 15 sessions
|
|
566
|
+
for (let i = 0; i < 15; i++) {
|
|
567
|
+
const timestamp = new Date(Date.now() - (15 - i) * 60000).toISOString();
|
|
568
|
+
await indexedDBService.saveSession({
|
|
569
|
+
session_id: `session-${i}`,
|
|
570
|
+
created_at: timestamp,
|
|
571
|
+
last_activity: timestamp,
|
|
572
|
+
artifact_count: 0,
|
|
573
|
+
run_count: 0,
|
|
574
|
+
size_estimate_bytes: 3 * 1024 * 1024, // 3MB per session
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Evict
|
|
579
|
+
await indexedDBService.evictOldSessions();
|
|
580
|
+
|
|
581
|
+
const sessions = await indexedDBService.getAllSessions();
|
|
582
|
+
|
|
583
|
+
// Should have deleted 5 oldest sessions, keeping 10 most recent
|
|
584
|
+
expect(sessions.length).toBe(10);
|
|
585
|
+
});
|
|
586
|
+
});
|
|
587
|
+
});
|