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,794 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IndexedDB Persistence Service for Flock Flow Dashboard
|
|
3
|
+
*
|
|
4
|
+
* Provides persistent storage for dashboard data with LRU eviction strategy.
|
|
5
|
+
* Implements separate layout storage for Agent View and Blackboard View.
|
|
6
|
+
*
|
|
7
|
+
* SPECIFICATION: docs/specs/003-real-time-dashboard/DATA_MODEL.md Section 3 & 6
|
|
8
|
+
*
|
|
9
|
+
* Database: flock_dashboard_v1 (version 1)
|
|
10
|
+
* Object Stores:
|
|
11
|
+
* - agents: Agent metadata and history (key: agent_id)
|
|
12
|
+
* - artifacts: Message data and lineage (key: artifact_id)
|
|
13
|
+
* - runs: Agent execution records (key: run_id)
|
|
14
|
+
* - layout_agent_view: Node positions for Agent View (key: node_id)
|
|
15
|
+
* - layout_blackboard_view: Node positions for Blackboard View (key: node_id)
|
|
16
|
+
* - module_instances: Module window positions and state (key: instance_id)
|
|
17
|
+
* - sessions: Session metadata for LRU eviction (key: session_id)
|
|
18
|
+
* - filters: Saved filter presets (key: filter_id)
|
|
19
|
+
*
|
|
20
|
+
* LRU Strategy:
|
|
21
|
+
* - Trigger eviction at 80% quota (EVICTION_THRESHOLD)
|
|
22
|
+
* - Evict until 60% quota (EVICTION_TARGET)
|
|
23
|
+
* - Evict oldest sessions first (based on created_at)
|
|
24
|
+
* - Typical session: ~154 KB, ~324 sessions before eviction
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
// Note: idb library is available for production use, but we use raw IndexedDB API
|
|
28
|
+
// for better compatibility with test mocks
|
|
29
|
+
|
|
30
|
+
// Database constants
|
|
31
|
+
const DB_NAME = 'flock_dashboard_v1';
|
|
32
|
+
const DB_VERSION = 1;
|
|
33
|
+
const EVICTION_THRESHOLD = 0.8; // Trigger eviction at 80% quota
|
|
34
|
+
const EVICTION_TARGET = 0.6; // Evict until 60% quota
|
|
35
|
+
const MAX_RECORDS_PER_STORE = 500; // LRU record limit per store
|
|
36
|
+
|
|
37
|
+
// Type definitions matching DATA_MODEL.md Section 3.2
|
|
38
|
+
|
|
39
|
+
interface AgentRecord {
|
|
40
|
+
agent_id: string; // PRIMARY KEY
|
|
41
|
+
agent_name: string;
|
|
42
|
+
labels: string[];
|
|
43
|
+
tenant_id: string | null; // INDEXED
|
|
44
|
+
max_concurrency: number;
|
|
45
|
+
consumes_types: string[];
|
|
46
|
+
from_agents: string[];
|
|
47
|
+
channels: string[];
|
|
48
|
+
run_history: string[]; // [run_id] - last 100 runs
|
|
49
|
+
total_runs: number;
|
|
50
|
+
total_errors: number;
|
|
51
|
+
first_seen: string; // ISO timestamp
|
|
52
|
+
last_active: string; // INDEXED - for LRU eviction
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
interface ArtifactRecord {
|
|
56
|
+
artifact_id: string; // PRIMARY KEY (UUID)
|
|
57
|
+
artifact_type: string; // INDEXED
|
|
58
|
+
produced_by: string; // INDEXED
|
|
59
|
+
correlation_id: string; // INDEXED - critical for filtering
|
|
60
|
+
payload: Record<string, any>;
|
|
61
|
+
payload_preview: string;
|
|
62
|
+
visibility: VisibilitySpec;
|
|
63
|
+
tags: string[];
|
|
64
|
+
partition_key: string | null;
|
|
65
|
+
version: number;
|
|
66
|
+
consumed_by: string[];
|
|
67
|
+
derived_from: string[];
|
|
68
|
+
published_at: string; // INDEXED - for time range filtering
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface VisibilitySpec {
|
|
72
|
+
kind: string;
|
|
73
|
+
[key: string]: any;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface RunRecord {
|
|
77
|
+
run_id: string; // PRIMARY KEY (Context.task_id)
|
|
78
|
+
agent_name: string; // INDEXED
|
|
79
|
+
correlation_id: string; // INDEXED
|
|
80
|
+
status: 'active' | 'completed' | 'error';
|
|
81
|
+
started_at: string; // INDEXED - for time range filtering
|
|
82
|
+
completed_at: string | null;
|
|
83
|
+
duration_ms: number | null;
|
|
84
|
+
consumed_artifacts: string[];
|
|
85
|
+
produced_artifacts: string[];
|
|
86
|
+
output_stream: OutputChunk[]; // Stored as JSON array
|
|
87
|
+
metrics: Record<string, number>;
|
|
88
|
+
final_state: Record<string, any>;
|
|
89
|
+
error_type: string | null;
|
|
90
|
+
error_message: string | null;
|
|
91
|
+
traceback: string | null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
interface OutputChunk {
|
|
95
|
+
sequence: number;
|
|
96
|
+
output_type: string;
|
|
97
|
+
content: string;
|
|
98
|
+
timestamp: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
interface ModuleInstanceRecord {
|
|
102
|
+
instance_id: string; // PRIMARY KEY
|
|
103
|
+
type: string; // Module type (e.g., 'eventLog')
|
|
104
|
+
position: { x: number; y: number };
|
|
105
|
+
size: { width: number; height: number };
|
|
106
|
+
visible: boolean;
|
|
107
|
+
created_at: string; // ISO timestamp
|
|
108
|
+
updated_at: string; // ISO timestamp
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface LayoutRecord {
|
|
112
|
+
node_id: string; // PRIMARY KEY (agent_name or artifact_id)
|
|
113
|
+
x: number;
|
|
114
|
+
y: number;
|
|
115
|
+
last_updated: string; // ISO timestamp
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
interface SessionRecord {
|
|
119
|
+
session_id: string; // PRIMARY KEY
|
|
120
|
+
created_at: string; // INDEXED - for LRU eviction
|
|
121
|
+
last_activity: string; // ISO timestamp
|
|
122
|
+
artifact_count: number;
|
|
123
|
+
run_count: number;
|
|
124
|
+
size_estimate_bytes: number; // Approximate storage size
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Future use: Saved filter presets
|
|
128
|
+
export interface FilterRecord {
|
|
129
|
+
filter_id: string; // PRIMARY KEY
|
|
130
|
+
name: string;
|
|
131
|
+
filters: Record<string, any>;
|
|
132
|
+
created_at: string;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Helper to wrap IDBRequest in Promise
|
|
136
|
+
function promisifyRequest<T>(request: IDBRequest<T>): Promise<T> {
|
|
137
|
+
return new Promise((resolve, reject) => {
|
|
138
|
+
request.onsuccess = () => resolve(request.result);
|
|
139
|
+
request.onerror = () => reject(request.error);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* IndexedDB Service for persistent dashboard storage
|
|
145
|
+
* Implements LRU eviction and graceful degradation
|
|
146
|
+
*/
|
|
147
|
+
export class IndexedDBService {
|
|
148
|
+
db: IDBDatabase | null = null;
|
|
149
|
+
private inMemoryStore: Map<string, Map<string, any>> = new Map();
|
|
150
|
+
private available = false;
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Initialize database with schema and indexes
|
|
154
|
+
*/
|
|
155
|
+
async initialize(): Promise<void> {
|
|
156
|
+
// Check if IndexedDB is available
|
|
157
|
+
if (typeof indexedDB === 'undefined') {
|
|
158
|
+
console.warn('[IndexedDB] IndexedDB not available, using in-memory fallback');
|
|
159
|
+
this.available = false;
|
|
160
|
+
this.initializeInMemoryStores();
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
// Use indexedDB.open directly to work with test mocks
|
|
166
|
+
const request = indexedDB.open(DB_NAME, DB_VERSION);
|
|
167
|
+
|
|
168
|
+
this.db = await new Promise<any>(async (resolve, reject) => {
|
|
169
|
+
request.onerror = () => {
|
|
170
|
+
reject(request.error);
|
|
171
|
+
};
|
|
172
|
+
request.onsuccess = () => {
|
|
173
|
+
resolve(request.result);
|
|
174
|
+
};
|
|
175
|
+
request.onupgradeneeded = (event: IDBVersionChangeEvent) => {
|
|
176
|
+
const db = (event.target as IDBOpenDBRequest).result;
|
|
177
|
+
const storeNames: string[] = [];
|
|
178
|
+
|
|
179
|
+
// Helper to add index name to store's indexNames (mock workaround)
|
|
180
|
+
const addIndexName = (store: any, indexName: string) => {
|
|
181
|
+
if (Array.isArray(store.indexNames) && !store.indexNames.includes(indexName)) {
|
|
182
|
+
store.indexNames.push(indexName);
|
|
183
|
+
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// Create agents store with indexes
|
|
187
|
+
if (!db.objectStoreNames.contains('agents')) {
|
|
188
|
+
const agentsStore = db.createObjectStore('agents', { keyPath: 'agent_id' });
|
|
189
|
+
agentsStore.createIndex('last_active', 'last_active', { unique: false });
|
|
190
|
+
addIndexName(agentsStore, 'last_active');
|
|
191
|
+
agentsStore.createIndex('tenant_id', 'tenant_id', { unique: false });
|
|
192
|
+
addIndexName(agentsStore, 'tenant_id');
|
|
193
|
+
storeNames.push('agents');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Create artifacts store with indexes
|
|
197
|
+
if (!db.objectStoreNames.contains('artifacts')) {
|
|
198
|
+
const artifactsStore = db.createObjectStore('artifacts', { keyPath: 'artifact_id' });
|
|
199
|
+
artifactsStore.createIndex('correlation_id', 'correlation_id', { unique: false });
|
|
200
|
+
addIndexName(artifactsStore, 'correlation_id');
|
|
201
|
+
artifactsStore.createIndex('published_at', 'published_at', { unique: false });
|
|
202
|
+
addIndexName(artifactsStore, 'published_at');
|
|
203
|
+
artifactsStore.createIndex('artifact_type', 'artifact_type', { unique: false });
|
|
204
|
+
addIndexName(artifactsStore, 'artifact_type');
|
|
205
|
+
artifactsStore.createIndex('produced_by', 'produced_by', { unique: false });
|
|
206
|
+
addIndexName(artifactsStore, 'produced_by');
|
|
207
|
+
storeNames.push('artifacts');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Create runs store with indexes
|
|
211
|
+
if (!db.objectStoreNames.contains('runs')) {
|
|
212
|
+
const runsStore = db.createObjectStore('runs', { keyPath: 'run_id' });
|
|
213
|
+
runsStore.createIndex('agent_name', 'agent_name', { unique: false });
|
|
214
|
+
addIndexName(runsStore, 'agent_name');
|
|
215
|
+
runsStore.createIndex('correlation_id', 'correlation_id', { unique: false });
|
|
216
|
+
addIndexName(runsStore, 'correlation_id');
|
|
217
|
+
runsStore.createIndex('started_at', 'started_at', { unique: false });
|
|
218
|
+
addIndexName(runsStore, 'started_at');
|
|
219
|
+
storeNames.push('runs');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Create layout stores (no indexes needed)
|
|
223
|
+
if (!db.objectStoreNames.contains('layout_agent_view')) {
|
|
224
|
+
db.createObjectStore('layout_agent_view', { keyPath: 'node_id' });
|
|
225
|
+
storeNames.push('layout_agent_view');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!db.objectStoreNames.contains('layout_blackboard_view')) {
|
|
229
|
+
db.createObjectStore('layout_blackboard_view', { keyPath: 'node_id' });
|
|
230
|
+
storeNames.push('layout_blackboard_view');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Create sessions store with index
|
|
234
|
+
if (!db.objectStoreNames.contains('sessions')) {
|
|
235
|
+
const sessionsStore = db.createObjectStore('sessions', { keyPath: 'session_id' });
|
|
236
|
+
sessionsStore.createIndex('created_at', 'created_at', { unique: false });
|
|
237
|
+
addIndexName(sessionsStore, 'created_at');
|
|
238
|
+
storeNames.push('sessions');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Create module_instances store
|
|
242
|
+
if (!db.objectStoreNames.contains('module_instances')) {
|
|
243
|
+
db.createObjectStore('module_instances', { keyPath: 'instance_id' });
|
|
244
|
+
storeNames.push('module_instances');
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Create filters store
|
|
248
|
+
if (!db.objectStoreNames.contains('filters')) {
|
|
249
|
+
db.createObjectStore('filters', { keyPath: 'filter_id' });
|
|
250
|
+
storeNames.push('filters');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// No mock workaround needed - fake-indexeddb properly implements objectStoreNames
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// For test mocks using setTimeout with fake timers, we need to advance time
|
|
257
|
+
// Check if we're in a test environment with fake timers (vi global exists)
|
|
258
|
+
if (typeof (globalThis as any).vi !== 'undefined') {
|
|
259
|
+
try {
|
|
260
|
+
// Synchronously run all pending timers to allow mock IndexedDB to execute
|
|
261
|
+
(globalThis as any).vi.runAllTimers();
|
|
262
|
+
} catch (e) {
|
|
263
|
+
// Not using fake timers, ignore
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
this.available = true;
|
|
269
|
+
console.log('[IndexedDB] Database initialized:', DB_NAME, 'version', DB_VERSION);
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.error('[IndexedDB] Initialization failed, using in-memory fallback:', error);
|
|
272
|
+
this.available = false;
|
|
273
|
+
this.initializeInMemoryStores();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Initialize in-memory stores as fallback
|
|
279
|
+
*/
|
|
280
|
+
private initializeInMemoryStores(): void {
|
|
281
|
+
const storeNames = [
|
|
282
|
+
'agents',
|
|
283
|
+
'artifacts',
|
|
284
|
+
'runs',
|
|
285
|
+
'layout_agent_view',
|
|
286
|
+
'layout_blackboard_view',
|
|
287
|
+
'module_instances',
|
|
288
|
+
'sessions',
|
|
289
|
+
'filters',
|
|
290
|
+
];
|
|
291
|
+
storeNames.forEach((name) => {
|
|
292
|
+
this.inMemoryStore.set(name, new Map());
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Check if IndexedDB is available
|
|
298
|
+
*/
|
|
299
|
+
isAvailable(): boolean {
|
|
300
|
+
return this.available;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ============================================================================
|
|
304
|
+
// CRUD Operations - Agents Store
|
|
305
|
+
// ============================================================================
|
|
306
|
+
|
|
307
|
+
async saveAgent(agent: AgentRecord): Promise<void> {
|
|
308
|
+
if (!this.db) {
|
|
309
|
+
this.inMemoryStore.get('agents')?.set(agent.agent_id, agent);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
try {
|
|
314
|
+
const tx = this.db.transaction('agents', 'readwrite');
|
|
315
|
+
const store = tx.objectStore('agents');
|
|
316
|
+
await promisifyRequest(store.put(agent));
|
|
317
|
+
await this.checkAndEvictByRecordLimit('agents', 'last_active');
|
|
318
|
+
} catch (error) {
|
|
319
|
+
console.error('[IndexedDB] Failed to save agent:', error);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async getAgent(agentId: string): Promise<AgentRecord | undefined> {
|
|
324
|
+
if (!this.db) {
|
|
325
|
+
return this.inMemoryStore.get('agents')?.get(agentId);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
const tx = this.db.transaction('agents', 'readonly');
|
|
330
|
+
const store = tx.objectStore('agents');
|
|
331
|
+
return await promisifyRequest(store.get(agentId));
|
|
332
|
+
} catch (error) {
|
|
333
|
+
console.error('[IndexedDB] Failed to get agent:', error);
|
|
334
|
+
return undefined;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async deleteAgent(agentId: string): Promise<void> {
|
|
339
|
+
if (!this.db) {
|
|
340
|
+
this.inMemoryStore.get('agents')?.delete(agentId);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
const tx = this.db.transaction('agents', 'readwrite');
|
|
346
|
+
const store = tx.objectStore('agents');
|
|
347
|
+
await promisifyRequest(store.delete(agentId));
|
|
348
|
+
} catch (error) {
|
|
349
|
+
console.error('[IndexedDB] Failed to delete agent:', error);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ============================================================================
|
|
354
|
+
// CRUD Operations - Artifacts Store
|
|
355
|
+
// ============================================================================
|
|
356
|
+
|
|
357
|
+
async saveArtifact(artifact: ArtifactRecord): Promise<void> {
|
|
358
|
+
if (!this.db) {
|
|
359
|
+
this.inMemoryStore.get('artifacts')?.set(artifact.artifact_id, artifact);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
try {
|
|
364
|
+
const tx = this.db.transaction('artifacts', 'readwrite');
|
|
365
|
+
const store = tx.objectStore('artifacts');
|
|
366
|
+
await promisifyRequest(store.put(artifact));
|
|
367
|
+
await this.checkAndEvictByRecordLimit('artifacts', 'published_at');
|
|
368
|
+
} catch (error) {
|
|
369
|
+
console.error('[IndexedDB] Failed to save artifact:', error);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async getArtifact(artifactId: string): Promise<ArtifactRecord | undefined> {
|
|
374
|
+
if (!this.db) {
|
|
375
|
+
return this.inMemoryStore.get('artifacts')?.get(artifactId);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
try {
|
|
379
|
+
const tx = this.db.transaction('artifacts', 'readonly');
|
|
380
|
+
const store = tx.objectStore('artifacts');
|
|
381
|
+
return await promisifyRequest(store.get(artifactId));
|
|
382
|
+
} catch (error) {
|
|
383
|
+
console.error('[IndexedDB] Failed to get artifact:', error);
|
|
384
|
+
return undefined;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Query artifacts by correlation_id using index (O(log n))
|
|
390
|
+
*/
|
|
391
|
+
async getArtifactsByCorrelationId(correlationId: string): Promise<ArtifactRecord[]> {
|
|
392
|
+
if (!this.db) {
|
|
393
|
+
const artifacts = this.inMemoryStore.get('artifacts');
|
|
394
|
+
if (!artifacts) return [];
|
|
395
|
+
return Array.from(artifacts.values()).filter((a) => a.correlation_id === correlationId);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
try {
|
|
399
|
+
const tx = this.db.transaction('artifacts', 'readonly');
|
|
400
|
+
const store = tx.objectStore('artifacts');
|
|
401
|
+
const index = store.index('correlation_id');
|
|
402
|
+
return await promisifyRequest(index.getAll(correlationId));
|
|
403
|
+
} catch (error) {
|
|
404
|
+
console.error('[IndexedDB] Failed to query artifacts by correlation_id:', error);
|
|
405
|
+
return [];
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Query artifacts by time range using published_at index (O(log n))
|
|
411
|
+
*/
|
|
412
|
+
async getArtifactsByTimeRange(startTime: string, endTime: string): Promise<ArtifactRecord[]> {
|
|
413
|
+
if (!this.db) {
|
|
414
|
+
const artifacts = this.inMemoryStore.get('artifacts');
|
|
415
|
+
if (!artifacts) return [];
|
|
416
|
+
return Array.from(artifacts.values()).filter(
|
|
417
|
+
(a) => a.published_at >= startTime && a.published_at <= endTime
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
try {
|
|
422
|
+
const range = IDBKeyRange.bound(startTime, endTime);
|
|
423
|
+
const tx = this.db.transaction('artifacts', 'readonly');
|
|
424
|
+
const store = tx.objectStore('artifacts');
|
|
425
|
+
const index = store.index('published_at');
|
|
426
|
+
return await promisifyRequest(index.getAll(range));
|
|
427
|
+
} catch (error) {
|
|
428
|
+
console.error('[IndexedDB] Failed to query artifacts by time range:', error);
|
|
429
|
+
return [];
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// ============================================================================
|
|
434
|
+
// CRUD Operations - Runs Store
|
|
435
|
+
// ============================================================================
|
|
436
|
+
|
|
437
|
+
async saveRun(run: RunRecord): Promise<void> {
|
|
438
|
+
if (!this.db) {
|
|
439
|
+
this.inMemoryStore.get('runs')?.set(run.run_id, run);
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
try {
|
|
444
|
+
const tx = this.db.transaction('runs', 'readwrite');
|
|
445
|
+
const store = tx.objectStore('runs');
|
|
446
|
+
await promisifyRequest(store.put(run));
|
|
447
|
+
await this.checkAndEvictByRecordLimit('runs', 'started_at');
|
|
448
|
+
} catch (error) {
|
|
449
|
+
console.error('[IndexedDB] Failed to save run:', error);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async getRun(runId: string): Promise<RunRecord | undefined> {
|
|
454
|
+
if (!this.db) {
|
|
455
|
+
return this.inMemoryStore.get('runs')?.get(runId);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
try {
|
|
459
|
+
const tx = this.db.transaction('runs', 'readonly');
|
|
460
|
+
const store = tx.objectStore('runs');
|
|
461
|
+
return await promisifyRequest(store.get(runId));
|
|
462
|
+
} catch (error) {
|
|
463
|
+
console.error('[IndexedDB] Failed to get run:', error);
|
|
464
|
+
return undefined;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// ============================================================================
|
|
469
|
+
// Layout Persistence - Agent View
|
|
470
|
+
// ============================================================================
|
|
471
|
+
|
|
472
|
+
async saveAgentViewLayout(layout: LayoutRecord): Promise<void> {
|
|
473
|
+
if (!this.db) {
|
|
474
|
+
this.inMemoryStore.get('layout_agent_view')?.set(layout.node_id, layout);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
try {
|
|
479
|
+
const tx = this.db.transaction('layout_agent_view', 'readwrite');
|
|
480
|
+
const store = tx.objectStore('layout_agent_view');
|
|
481
|
+
await promisifyRequest(store.put(layout));
|
|
482
|
+
} catch (error) {
|
|
483
|
+
console.error('[IndexedDB] Failed to save agent view layout:', error);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async getAgentViewLayout(nodeId: string): Promise<LayoutRecord | undefined> {
|
|
488
|
+
if (!this.db) {
|
|
489
|
+
return this.inMemoryStore.get('layout_agent_view')?.get(nodeId);
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
try {
|
|
493
|
+
const tx = this.db.transaction('layout_agent_view', 'readonly');
|
|
494
|
+
const store = tx.objectStore('layout_agent_view');
|
|
495
|
+
return await promisifyRequest(store.get(nodeId));
|
|
496
|
+
} catch (error) {
|
|
497
|
+
console.error('[IndexedDB] Failed to get agent view layout:', error);
|
|
498
|
+
return undefined;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
async getAllAgentViewLayouts(): Promise<LayoutRecord[]> {
|
|
503
|
+
if (!this.db) {
|
|
504
|
+
const layouts = this.inMemoryStore.get('layout_agent_view');
|
|
505
|
+
return layouts ? Array.from(layouts.values()) : [];
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
try {
|
|
509
|
+
const tx = this.db.transaction('layout_agent_view', 'readonly');
|
|
510
|
+
const store = tx.objectStore('layout_agent_view');
|
|
511
|
+
return await promisifyRequest(store.getAll());
|
|
512
|
+
} catch (error) {
|
|
513
|
+
console.error('[IndexedDB] Failed to get all agent view layouts:', error);
|
|
514
|
+
return [];
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// ============================================================================
|
|
519
|
+
// Layout Persistence - Blackboard View
|
|
520
|
+
// ============================================================================
|
|
521
|
+
|
|
522
|
+
async saveBlackboardViewLayout(layout: LayoutRecord): Promise<void> {
|
|
523
|
+
if (!this.db) {
|
|
524
|
+
this.inMemoryStore.get('layout_blackboard_view')?.set(layout.node_id, layout);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
try {
|
|
529
|
+
const tx = this.db.transaction('layout_blackboard_view', 'readwrite');
|
|
530
|
+
const store = tx.objectStore('layout_blackboard_view');
|
|
531
|
+
await promisifyRequest(store.put(layout));
|
|
532
|
+
} catch (error) {
|
|
533
|
+
console.error('[IndexedDB] Failed to save blackboard view layout:', error);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
async getBlackboardViewLayout(nodeId: string): Promise<LayoutRecord | undefined> {
|
|
538
|
+
if (!this.db) {
|
|
539
|
+
return this.inMemoryStore.get('layout_blackboard_view')?.get(nodeId);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
try {
|
|
543
|
+
const tx = this.db.transaction('layout_blackboard_view', 'readonly');
|
|
544
|
+
const store = tx.objectStore('layout_blackboard_view');
|
|
545
|
+
return await promisifyRequest(store.get(nodeId));
|
|
546
|
+
} catch (error) {
|
|
547
|
+
console.error('[IndexedDB] Failed to get blackboard view layout:', error);
|
|
548
|
+
return undefined;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
async getAllBlackboardViewLayouts(): Promise<LayoutRecord[]> {
|
|
553
|
+
if (!this.db) {
|
|
554
|
+
const layouts = this.inMemoryStore.get('layout_blackboard_view');
|
|
555
|
+
return layouts ? Array.from(layouts.values()) : [];
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
try {
|
|
559
|
+
const tx = this.db.transaction('layout_blackboard_view', 'readonly');
|
|
560
|
+
const store = tx.objectStore('layout_blackboard_view');
|
|
561
|
+
return await promisifyRequest(store.getAll());
|
|
562
|
+
} catch (error) {
|
|
563
|
+
console.error('[IndexedDB] Failed to get all blackboard view layouts:', error);
|
|
564
|
+
return [];
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// ============================================================================
|
|
569
|
+
// Module Instance Persistence
|
|
570
|
+
// ============================================================================
|
|
571
|
+
|
|
572
|
+
async saveModuleInstance(instance: ModuleInstanceRecord): Promise<void> {
|
|
573
|
+
if (!this.db) {
|
|
574
|
+
this.inMemoryStore.get('module_instances')?.set(instance.instance_id, instance);
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
try {
|
|
579
|
+
const tx = this.db.transaction('module_instances', 'readwrite');
|
|
580
|
+
const store = tx.objectStore('module_instances');
|
|
581
|
+
await promisifyRequest(store.put(instance));
|
|
582
|
+
} catch (error) {
|
|
583
|
+
console.error('[IndexedDB] Failed to save module instance:', error);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
async getModuleInstance(instanceId: string): Promise<ModuleInstanceRecord | undefined> {
|
|
588
|
+
if (!this.db) {
|
|
589
|
+
return this.inMemoryStore.get('module_instances')?.get(instanceId);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
try {
|
|
593
|
+
const tx = this.db.transaction('module_instances', 'readonly');
|
|
594
|
+
const store = tx.objectStore('module_instances');
|
|
595
|
+
return await promisifyRequest(store.get(instanceId));
|
|
596
|
+
} catch (error) {
|
|
597
|
+
console.error('[IndexedDB] Failed to get module instance:', error);
|
|
598
|
+
return undefined;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
async getAllModuleInstances(): Promise<ModuleInstanceRecord[]> {
|
|
603
|
+
if (!this.db) {
|
|
604
|
+
const instances = this.inMemoryStore.get('module_instances');
|
|
605
|
+
return instances ? Array.from(instances.values()) : [];
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
try {
|
|
609
|
+
const tx = this.db.transaction('module_instances', 'readonly');
|
|
610
|
+
const store = tx.objectStore('module_instances');
|
|
611
|
+
return await promisifyRequest(store.getAll());
|
|
612
|
+
} catch (error) {
|
|
613
|
+
console.error('[IndexedDB] Failed to get all module instances:', error);
|
|
614
|
+
return [];
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
async deleteModuleInstance(instanceId: string): Promise<void> {
|
|
619
|
+
if (!this.db) {
|
|
620
|
+
this.inMemoryStore.get('module_instances')?.delete(instanceId);
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
try {
|
|
625
|
+
const tx = this.db.transaction('module_instances', 'readwrite');
|
|
626
|
+
const store = tx.objectStore('module_instances');
|
|
627
|
+
await promisifyRequest(store.delete(instanceId));
|
|
628
|
+
} catch (error) {
|
|
629
|
+
console.error('[IndexedDB] Failed to delete module instance:', error);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// ============================================================================
|
|
634
|
+
// Session Management
|
|
635
|
+
// ============================================================================
|
|
636
|
+
|
|
637
|
+
async saveSession(session: SessionRecord): Promise<void> {
|
|
638
|
+
if (!this.db) {
|
|
639
|
+
this.inMemoryStore.get('sessions')?.set(session.session_id, session);
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
try {
|
|
644
|
+
const tx = this.db.transaction('sessions', 'readwrite');
|
|
645
|
+
const store = tx.objectStore('sessions');
|
|
646
|
+
await promisifyRequest(store.put(session));
|
|
647
|
+
} catch (error) {
|
|
648
|
+
console.error('[IndexedDB] Failed to save session:', error);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
async getAllSessions(): Promise<SessionRecord[]> {
|
|
653
|
+
if (!this.db) {
|
|
654
|
+
const sessions = this.inMemoryStore.get('sessions');
|
|
655
|
+
return sessions ? Array.from(sessions.values()) : [];
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
try {
|
|
659
|
+
const tx = this.db.transaction('sessions', 'readonly');
|
|
660
|
+
const store = tx.objectStore('sessions');
|
|
661
|
+
return await promisifyRequest(store.getAll());
|
|
662
|
+
} catch (error) {
|
|
663
|
+
console.error('[IndexedDB] Failed to get all sessions:', error);
|
|
664
|
+
return [];
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
// ============================================================================
|
|
669
|
+
// LRU Eviction Strategy
|
|
670
|
+
// ============================================================================
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Check if eviction should be triggered (80% quota threshold)
|
|
674
|
+
*/
|
|
675
|
+
async checkShouldEvict(): Promise<boolean> {
|
|
676
|
+
if (!this.db) {
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
try {
|
|
681
|
+
if (!navigator.storage?.estimate) {
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
const estimate = await navigator.storage.estimate();
|
|
686
|
+
const usage = estimate.usage || 0;
|
|
687
|
+
const quota = estimate.quota || 0;
|
|
688
|
+
|
|
689
|
+
if (quota === 0) {
|
|
690
|
+
return false;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const percentage = usage / quota;
|
|
694
|
+
|
|
695
|
+
if (percentage > EVICTION_THRESHOLD) {
|
|
696
|
+
console.warn(
|
|
697
|
+
`[IndexedDB] Storage quota exceeded threshold: ${(percentage * 100).toFixed(1)}% (${usage}/${quota} bytes)`
|
|
698
|
+
);
|
|
699
|
+
return true;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return false;
|
|
703
|
+
} catch (error) {
|
|
704
|
+
console.error('[IndexedDB] Failed to check storage quota:', error);
|
|
705
|
+
return false;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/**
|
|
710
|
+
* Evict oldest sessions until usage reaches 60% target
|
|
711
|
+
*/
|
|
712
|
+
async evictOldSessions(): Promise<void> {
|
|
713
|
+
if (!this.db) {
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
try {
|
|
718
|
+
// Get all sessions sorted by created_at (oldest first)
|
|
719
|
+
const tx = this.db.transaction('sessions', 'readwrite');
|
|
720
|
+
const store = tx.objectStore('sessions');
|
|
721
|
+
const index = store.index('created_at');
|
|
722
|
+
const sessions = await promisifyRequest(index.getAll());
|
|
723
|
+
|
|
724
|
+
if (sessions.length === 0) {
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// Evict sessions one by one until target reached
|
|
729
|
+
for (const session of sessions) {
|
|
730
|
+
const estimate = await navigator.storage.estimate();
|
|
731
|
+
const usage = estimate.usage || 0;
|
|
732
|
+
const quota = estimate.quota || 0;
|
|
733
|
+
|
|
734
|
+
if (quota === 0) break;
|
|
735
|
+
|
|
736
|
+
const percentage = usage / quota;
|
|
737
|
+
|
|
738
|
+
// Stop if we've reached the target
|
|
739
|
+
if (percentage <= EVICTION_TARGET) {
|
|
740
|
+
break;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Delete session
|
|
744
|
+
const deleteTx = this.db.transaction('sessions', 'readwrite');
|
|
745
|
+
const deleteStore = deleteTx.objectStore('sessions');
|
|
746
|
+
await promisifyRequest(deleteStore.delete(session.session_id));
|
|
747
|
+
console.log(`[IndexedDB] Evicted session: ${session.session_id}`);
|
|
748
|
+
}
|
|
749
|
+
} catch (error) {
|
|
750
|
+
console.error('[IndexedDB] Failed to evict old sessions:', error);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Check and evict by record limit (MAX_RECORDS_PER_STORE)
|
|
756
|
+
* Evicts oldest records based on specified index
|
|
757
|
+
*/
|
|
758
|
+
private async checkAndEvictByRecordLimit(
|
|
759
|
+
storeName: string,
|
|
760
|
+
sortIndex: string
|
|
761
|
+
): Promise<void> {
|
|
762
|
+
if (!this.db) {
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
try {
|
|
767
|
+
const tx = this.db.transaction(storeName, 'readonly');
|
|
768
|
+
const store = tx.objectStore(storeName);
|
|
769
|
+
const count = await promisifyRequest(store.count());
|
|
770
|
+
|
|
771
|
+
if (count > MAX_RECORDS_PER_STORE) {
|
|
772
|
+
// Get oldest records using the sort index
|
|
773
|
+
const readTx = this.db.transaction(storeName, 'readonly');
|
|
774
|
+
const readStore = readTx.objectStore(storeName);
|
|
775
|
+
const index = readStore.index(sortIndex);
|
|
776
|
+
const keys = await promisifyRequest(index.getAllKeys());
|
|
777
|
+
|
|
778
|
+
// Delete oldest records until we're under the limit
|
|
779
|
+
const numToDelete = count - MAX_RECORDS_PER_STORE;
|
|
780
|
+
const writeTx = this.db.transaction(storeName, 'readwrite');
|
|
781
|
+
const writeStore = writeTx.objectStore(storeName);
|
|
782
|
+
|
|
783
|
+
for (let i = 0; i < numToDelete && i < keys.length; i++) {
|
|
784
|
+
await promisifyRequest(writeStore.delete(keys[i]!));
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
} catch (error) {
|
|
788
|
+
console.error(`[IndexedDB] Failed to evict by record limit for ${storeName}:`, error);
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// Singleton instance
|
|
794
|
+
export const indexedDBService = new IndexedDBService();
|