flock-core 0.4.543__py3-none-any.whl → 0.5.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of flock-core might be problematic. Click here for more details.
- flock/__init__.py +12 -217
- flock/agent.py +1079 -0
- flock/api/themes.py +71 -0
- flock/artifacts.py +86 -0
- flock/cli.py +147 -0
- flock/components.py +189 -0
- flock/dashboard/__init__.py +30 -0
- flock/dashboard/collector.py +559 -0
- flock/dashboard/events.py +188 -0
- flock/dashboard/graph_builder.py +563 -0
- flock/dashboard/launcher.py +235 -0
- flock/dashboard/models/graph.py +156 -0
- flock/dashboard/service.py +991 -0
- flock/dashboard/static_v2/assets/index-DFRnI_mt.js +111 -0
- flock/dashboard/static_v2/assets/index-fPLNdmp1.css +1 -0
- flock/dashboard/static_v2/index.html +13 -0
- flock/dashboard/websocket.py +246 -0
- flock/engines/__init__.py +6 -0
- flock/engines/dspy_engine.py +932 -0
- flock/examples.py +131 -0
- flock/frontend/README.md +778 -0
- flock/frontend/docs/DESIGN_SYSTEM.md +1980 -0
- flock/frontend/index.html +12 -0
- flock/frontend/package-lock.json +4337 -0
- flock/frontend/package.json +48 -0
- flock/frontend/src/App.tsx +139 -0
- flock/frontend/src/__tests__/integration/graph-snapshot.test.tsx +647 -0
- flock/frontend/src/__tests__/integration/indexeddb-persistence.test.tsx +699 -0
- flock/frontend/src/components/common/BuildInfo.tsx +39 -0
- flock/frontend/src/components/common/EmptyState.module.css +115 -0
- flock/frontend/src/components/common/EmptyState.tsx +128 -0
- flock/frontend/src/components/common/ErrorBoundary.module.css +169 -0
- flock/frontend/src/components/common/ErrorBoundary.tsx +118 -0
- flock/frontend/src/components/common/KeyboardShortcutsDialog.css +251 -0
- flock/frontend/src/components/common/KeyboardShortcutsDialog.tsx +151 -0
- flock/frontend/src/components/common/LoadingSpinner.module.css +97 -0
- flock/frontend/src/components/common/LoadingSpinner.tsx +29 -0
- flock/frontend/src/components/controls/PublishControl.css +547 -0
- flock/frontend/src/components/controls/PublishControl.test.tsx +543 -0
- flock/frontend/src/components/controls/PublishControl.tsx +432 -0
- flock/frontend/src/components/details/DetailWindowContainer.tsx +58 -0
- flock/frontend/src/components/details/LiveOutputTab.test.tsx +792 -0
- flock/frontend/src/components/details/LiveOutputTab.tsx +220 -0
- flock/frontend/src/components/details/MessageDetailWindow.tsx +439 -0
- flock/frontend/src/components/details/MessageHistoryTab.tsx +374 -0
- flock/frontend/src/components/details/NodeDetailWindow.test.tsx +501 -0
- flock/frontend/src/components/details/NodeDetailWindow.tsx +218 -0
- flock/frontend/src/components/details/RunStatusTab.tsx +348 -0
- flock/frontend/src/components/details/tabs.test.tsx +1015 -0
- flock/frontend/src/components/filters/ArtifactTypeFilter.tsx +21 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.module.css +102 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.test.tsx +197 -0
- flock/frontend/src/components/filters/CorrelationIDFilter.tsx +121 -0
- flock/frontend/src/components/filters/FilterFlyout.module.css +104 -0
- flock/frontend/src/components/filters/FilterFlyout.tsx +80 -0
- flock/frontend/src/components/filters/FilterPills.module.css +220 -0
- flock/frontend/src/components/filters/FilterPills.test.tsx +189 -0
- flock/frontend/src/components/filters/FilterPills.tsx +143 -0
- flock/frontend/src/components/filters/ProducerFilter.tsx +21 -0
- flock/frontend/src/components/filters/SavedFiltersControl.module.css +60 -0
- flock/frontend/src/components/filters/SavedFiltersControl.test.tsx +158 -0
- flock/frontend/src/components/filters/SavedFiltersControl.tsx +159 -0
- flock/frontend/src/components/filters/TagFilter.tsx +21 -0
- flock/frontend/src/components/filters/TimeRangeFilter.module.css +115 -0
- flock/frontend/src/components/filters/TimeRangeFilter.test.tsx +154 -0
- flock/frontend/src/components/filters/TimeRangeFilter.tsx +110 -0
- flock/frontend/src/components/filters/VisibilityFilter.tsx +21 -0
- flock/frontend/src/components/graph/AgentNode.test.tsx +77 -0
- flock/frontend/src/components/graph/AgentNode.tsx +324 -0
- flock/frontend/src/components/graph/GraphCanvas.tsx +613 -0
- flock/frontend/src/components/graph/MessageFlowEdge.tsx +128 -0
- flock/frontend/src/components/graph/MessageNode.test.tsx +64 -0
- flock/frontend/src/components/graph/MessageNode.tsx +129 -0
- flock/frontend/src/components/graph/MiniMap.tsx +47 -0
- flock/frontend/src/components/graph/TransformEdge.tsx +123 -0
- flock/frontend/src/components/layout/DashboardLayout.css +420 -0
- flock/frontend/src/components/layout/DashboardLayout.tsx +287 -0
- flock/frontend/src/components/layout/Header.module.css +88 -0
- flock/frontend/src/components/layout/Header.tsx +52 -0
- flock/frontend/src/components/modules/HistoricalArtifactsModule.module.css +288 -0
- flock/frontend/src/components/modules/HistoricalArtifactsModule.tsx +450 -0
- flock/frontend/src/components/modules/HistoricalArtifactsModuleWrapper.tsx +13 -0
- flock/frontend/src/components/modules/JsonAttributeRenderer.tsx +140 -0
- flock/frontend/src/components/modules/ModuleRegistry.test.ts +333 -0
- flock/frontend/src/components/modules/ModuleRegistry.ts +93 -0
- flock/frontend/src/components/modules/ModuleWindow.tsx +223 -0
- flock/frontend/src/components/modules/TraceModuleJaeger.tsx +1971 -0
- flock/frontend/src/components/modules/TraceModuleJaegerWrapper.tsx +13 -0
- flock/frontend/src/components/modules/registerModules.ts +29 -0
- flock/frontend/src/components/settings/AdvancedSettings.tsx +175 -0
- flock/frontend/src/components/settings/AppearanceSettings.tsx +185 -0
- flock/frontend/src/components/settings/GraphSettings.tsx +110 -0
- flock/frontend/src/components/settings/MultiSelect.tsx +235 -0
- flock/frontend/src/components/settings/SettingsPanel.css +327 -0
- flock/frontend/src/components/settings/SettingsPanel.tsx +131 -0
- flock/frontend/src/components/settings/ThemeSelector.tsx +298 -0
- flock/frontend/src/components/settings/TracingSettings.tsx +404 -0
- flock/frontend/src/hooks/useKeyboardShortcuts.ts +148 -0
- flock/frontend/src/hooks/useModulePersistence.test.ts +442 -0
- flock/frontend/src/hooks/useModulePersistence.ts +154 -0
- flock/frontend/src/hooks/useModules.ts +157 -0
- flock/frontend/src/hooks/usePersistence.ts +141 -0
- flock/frontend/src/main.tsx +13 -0
- flock/frontend/src/services/api.ts +337 -0
- flock/frontend/src/services/graphService.test.ts +330 -0
- flock/frontend/src/services/graphService.ts +75 -0
- flock/frontend/src/services/indexeddb.test.ts +793 -0
- flock/frontend/src/services/indexeddb.ts +848 -0
- flock/frontend/src/services/layout.test.ts +437 -0
- flock/frontend/src/services/layout.ts +357 -0
- flock/frontend/src/services/themeApplicator.ts +140 -0
- flock/frontend/src/services/themeService.ts +77 -0
- flock/frontend/src/services/websocket.ts +650 -0
- flock/frontend/src/store/filterStore.test.ts +250 -0
- flock/frontend/src/store/filterStore.ts +272 -0
- flock/frontend/src/store/graphStore.test.ts +570 -0
- flock/frontend/src/store/graphStore.ts +462 -0
- flock/frontend/src/store/moduleStore.test.ts +253 -0
- flock/frontend/src/store/moduleStore.ts +75 -0
- flock/frontend/src/store/settingsStore.ts +188 -0
- flock/frontend/src/store/streamStore.ts +68 -0
- flock/frontend/src/store/uiStore.test.ts +54 -0
- flock/frontend/src/store/uiStore.ts +122 -0
- flock/frontend/src/store/wsStore.ts +34 -0
- flock/frontend/src/styles/index.css +15 -0
- flock/frontend/src/styles/scrollbar.css +47 -0
- flock/frontend/src/styles/variables.css +488 -0
- flock/frontend/src/test/setup.ts +1 -0
- flock/frontend/src/types/filters.ts +47 -0
- flock/frontend/src/types/graph.ts +95 -0
- flock/frontend/src/types/modules.ts +10 -0
- flock/frontend/src/types/theme.ts +55 -0
- flock/frontend/src/utils/artifacts.ts +24 -0
- flock/frontend/src/utils/mockData.ts +98 -0
- flock/frontend/src/utils/performance.ts +16 -0
- flock/frontend/src/vite-env.d.ts +17 -0
- flock/frontend/tsconfig.json +27 -0
- flock/frontend/tsconfig.node.json +11 -0
- flock/frontend/vite.config.ts +25 -0
- flock/frontend/vitest.config.ts +11 -0
- flock/{core/util → helper}/cli_helper.py +9 -5
- flock/{core/logging → logging}/__init__.py +2 -3
- flock/logging/auto_trace.py +159 -0
- flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
- flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
- flock/{core/logging → logging}/formatters/themed_formatter.py +69 -107
- flock/{core/logging → logging}/logging.py +78 -61
- flock/{core/logging → logging}/telemetry.py +66 -26
- flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
- flock/logging/telemetry_exporter/duckdb_exporter.py +216 -0
- flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +13 -10
- flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
- flock/logging/trace_and_logged.py +304 -0
- flock/mcp/__init__.py +91 -0
- flock/{core/mcp/mcp_client.py → mcp/client.py} +131 -158
- flock/{core/mcp/mcp_config.py → mcp/config.py} +86 -132
- flock/mcp/manager.py +286 -0
- flock/mcp/servers/sse/__init__.py +1 -1
- flock/mcp/servers/sse/flock_sse_server.py +16 -58
- flock/mcp/servers/stdio/__init__.py +1 -1
- flock/mcp/servers/stdio/flock_stdio_server.py +13 -53
- flock/mcp/servers/streamable_http/flock_streamable_http_server.py +22 -67
- flock/mcp/servers/websockets/flock_websocket_server.py +12 -45
- flock/{core/mcp/flock_mcp_tool_base.py → mcp/tool.py} +24 -78
- flock/mcp/types/__init__.py +42 -0
- flock/{core/mcp → mcp}/types/callbacks.py +12 -15
- flock/{core/mcp → mcp}/types/factories.py +7 -6
- flock/{core/mcp → mcp}/types/handlers.py +13 -18
- flock/{core/mcp → mcp}/types/types.py +70 -74
- flock/{core/mcp → mcp}/util/helpers.py +3 -3
- flock/orchestrator.py +970 -0
- flock/registry.py +148 -0
- flock/runtime.py +262 -0
- flock/service.py +277 -0
- flock/store.py +1214 -0
- flock/subscription.py +111 -0
- flock/themes/andromeda.toml +1 -1
- flock/themes/apple-system-colors.toml +1 -1
- flock/themes/arcoiris.toml +1 -1
- flock/themes/atomonelight.toml +1 -1
- flock/themes/ayu copy.toml +1 -1
- flock/themes/ayu-light.toml +1 -1
- flock/themes/belafonte-day.toml +1 -1
- flock/themes/belafonte-night.toml +1 -1
- flock/themes/blulocodark.toml +1 -1
- flock/themes/breeze.toml +1 -1
- flock/themes/broadcast.toml +1 -1
- flock/themes/brogrammer.toml +1 -1
- flock/themes/builtin-dark.toml +1 -1
- flock/themes/builtin-pastel-dark.toml +1 -1
- flock/themes/catppuccin-latte.toml +1 -1
- flock/themes/catppuccin-macchiato.toml +1 -1
- flock/themes/catppuccin-mocha.toml +1 -1
- flock/themes/cga.toml +1 -1
- flock/themes/chalk.toml +1 -1
- flock/themes/ciapre.toml +1 -1
- flock/themes/coffee-theme.toml +1 -1
- flock/themes/cyberpunkscarletprotocol.toml +1 -1
- flock/themes/dark+.toml +1 -1
- flock/themes/darkermatrix.toml +1 -1
- flock/themes/darkmatrix.toml +2 -2
- flock/themes/darkside.toml +1 -1
- flock/themes/deep.toml +2 -2
- flock/themes/desert.toml +1 -1
- flock/themes/django.toml +1 -1
- flock/themes/djangosmooth.toml +1 -1
- flock/themes/doomone.toml +1 -1
- flock/themes/dotgov.toml +1 -1
- flock/themes/dracula+.toml +1 -1
- flock/themes/duckbones.toml +1 -1
- flock/themes/encom.toml +1 -1
- flock/themes/espresso.toml +1 -1
- flock/themes/everblush.toml +1 -1
- flock/themes/fairyfloss.toml +1 -1
- flock/themes/fideloper.toml +1 -1
- flock/themes/fishtank.toml +1 -1
- flock/themes/flexoki-light.toml +1 -1
- flock/themes/floraverse.toml +1 -1
- flock/themes/framer.toml +1 -1
- flock/themes/galizur.toml +1 -1
- flock/themes/github.toml +1 -1
- flock/themes/grass.toml +1 -1
- flock/themes/grey-green.toml +1 -1
- flock/themes/gruvboxlight.toml +1 -1
- flock/themes/guezwhoz.toml +1 -1
- flock/themes/harper.toml +1 -1
- flock/themes/hax0r-blue.toml +1 -1
- flock/themes/hopscotch.256.toml +1 -1
- flock/themes/ic-green-ppl.toml +1 -1
- flock/themes/iceberg-dark.toml +1 -1
- flock/themes/japanesque.toml +1 -1
- flock/themes/jubi.toml +1 -1
- flock/themes/kibble.toml +1 -1
- flock/themes/kolorit.toml +1 -1
- flock/themes/kurokula.toml +1 -1
- flock/themes/materialdesigncolors.toml +1 -1
- flock/themes/matrix.toml +1 -1
- flock/themes/mellifluous.toml +1 -1
- flock/themes/midnight-in-mojave.toml +1 -1
- flock/themes/monokai-remastered.toml +1 -1
- flock/themes/monokai-soda.toml +1 -1
- flock/themes/neon.toml +1 -1
- flock/themes/neopolitan.toml +5 -5
- flock/themes/nord-light.toml +1 -1
- flock/themes/ocean.toml +1 -1
- flock/themes/onehalfdark.toml +1 -1
- flock/themes/onehalflight.toml +1 -1
- flock/themes/palenighthc.toml +1 -1
- flock/themes/paulmillr.toml +1 -1
- flock/themes/pencildark.toml +1 -1
- flock/themes/pnevma.toml +1 -1
- flock/themes/purple-rain.toml +1 -1
- flock/themes/purplepeter.toml +1 -1
- flock/themes/raycast-dark.toml +1 -1
- flock/themes/red-sands.toml +1 -1
- flock/themes/relaxed.toml +1 -1
- flock/themes/retro.toml +1 -1
- flock/themes/rose-pine.toml +1 -1
- flock/themes/royal.toml +1 -1
- flock/themes/ryuuko.toml +1 -1
- flock/themes/sakura.toml +1 -1
- flock/themes/scarlet-protocol.toml +1 -1
- flock/themes/seoulbones-dark.toml +1 -1
- flock/themes/shades-of-purple.toml +1 -1
- flock/themes/smyck.toml +1 -1
- flock/themes/softserver.toml +1 -1
- flock/themes/solarized-darcula.toml +1 -1
- flock/themes/square.toml +1 -1
- flock/themes/sugarplum.toml +1 -1
- flock/themes/thayer-bright.toml +1 -1
- flock/themes/tokyonight.toml +1 -1
- flock/themes/tomorrow.toml +1 -1
- flock/themes/ubuntu.toml +1 -1
- flock/themes/ultradark.toml +1 -1
- flock/themes/ultraviolent.toml +1 -1
- flock/themes/unikitty.toml +1 -1
- flock/themes/urple.toml +1 -1
- flock/themes/vesper.toml +1 -1
- flock/themes/vimbones.toml +1 -1
- flock/themes/wildcherry.toml +1 -1
- flock/themes/wilmersdorf.toml +1 -1
- flock/themes/wryan.toml +1 -1
- flock/themes/xcodedarkhc.toml +1 -1
- flock/themes/xcodelight.toml +1 -1
- flock/themes/zenbones-light.toml +1 -1
- flock/themes/zenwritten-dark.toml +1 -1
- flock/utilities.py +301 -0
- flock/utility/output_utility_component.py +226 -0
- flock/visibility.py +107 -0
- flock_core-0.5.0.dist-info/METADATA +964 -0
- flock_core-0.5.0.dist-info/RECORD +525 -0
- flock_core-0.5.0.dist-info/entry_points.txt +2 -0
- {flock_core-0.4.543.dist-info → flock_core-0.5.0.dist-info}/licenses/LICENSE +1 -1
- flock/adapter/__init__.py +0 -14
- flock/adapter/azure_adapter.py +0 -68
- flock/adapter/chroma_adapter.py +0 -73
- flock/adapter/faiss_adapter.py +0 -97
- flock/adapter/pinecone_adapter.py +0 -51
- flock/adapter/vector_base.py +0 -47
- flock/cli/assets/release_notes.md +0 -140
- flock/cli/config.py +0 -8
- flock/cli/constants.py +0 -36
- flock/cli/create_agent.py +0 -1
- flock/cli/create_flock.py +0 -280
- flock/cli/execute_flock.py +0 -620
- flock/cli/load_agent.py +0 -1
- flock/cli/load_examples.py +0 -1
- flock/cli/load_flock.py +0 -192
- flock/cli/load_release_notes.py +0 -20
- flock/cli/loaded_flock_cli.py +0 -254
- flock/cli/manage_agents.py +0 -459
- flock/cli/registry_management.py +0 -889
- flock/cli/runner.py +0 -41
- flock/cli/settings.py +0 -857
- flock/cli/utils.py +0 -135
- flock/cli/view_results.py +0 -29
- flock/cli/yaml_editor.py +0 -396
- flock/config.py +0 -56
- flock/core/__init__.py +0 -44
- flock/core/api/__init__.py +0 -10
- flock/core/api/custom_endpoint.py +0 -45
- flock/core/api/endpoints.py +0 -262
- flock/core/api/main.py +0 -162
- flock/core/api/models.py +0 -101
- flock/core/api/run_store.py +0 -224
- flock/core/api/runner.py +0 -44
- flock/core/api/service.py +0 -214
- flock/core/config/flock_agent_config.py +0 -11
- flock/core/config/scheduled_agent_config.py +0 -40
- flock/core/context/context.py +0 -214
- flock/core/context/context_manager.py +0 -40
- flock/core/context/context_vars.py +0 -11
- flock/core/evaluation/utils.py +0 -395
- flock/core/execution/batch_executor.py +0 -369
- flock/core/execution/evaluation_executor.py +0 -438
- flock/core/execution/local_executor.py +0 -31
- flock/core/execution/opik_executor.py +0 -103
- flock/core/execution/temporal_executor.py +0 -166
- flock/core/flock.py +0 -1003
- flock/core/flock_agent.py +0 -1258
- flock/core/flock_evaluator.py +0 -60
- flock/core/flock_factory.py +0 -513
- flock/core/flock_module.py +0 -207
- flock/core/flock_registry.py +0 -702
- flock/core/flock_router.py +0 -83
- flock/core/flock_scheduler.py +0 -166
- flock/core/flock_server_manager.py +0 -136
- flock/core/interpreter/python_interpreter.py +0 -689
- flock/core/logging/live_capture.py +0 -137
- flock/core/logging/trace_and_logged.py +0 -59
- flock/core/mcp/__init__.py +0 -1
- flock/core/mcp/flock_mcp_server.py +0 -640
- flock/core/mcp/mcp_client_manager.py +0 -201
- flock/core/mcp/types/__init__.py +0 -1
- flock/core/mixin/dspy_integration.py +0 -445
- flock/core/mixin/prompt_parser.py +0 -125
- flock/core/serialization/__init__.py +0 -13
- flock/core/serialization/callable_registry.py +0 -52
- flock/core/serialization/flock_serializer.py +0 -854
- flock/core/serialization/json_encoder.py +0 -41
- flock/core/serialization/secure_serializer.py +0 -175
- flock/core/serialization/serializable.py +0 -342
- flock/core/serialization/serialization_utils.py +0 -409
- flock/core/util/file_path_utils.py +0 -223
- flock/core/util/hydrator.py +0 -309
- flock/core/util/input_resolver.py +0 -141
- flock/core/util/loader.py +0 -59
- flock/core/util/splitter.py +0 -219
- flock/di.py +0 -41
- flock/evaluators/__init__.py +0 -1
- flock/evaluators/declarative/__init__.py +0 -1
- flock/evaluators/declarative/declarative_evaluator.py +0 -217
- flock/evaluators/memory/memory_evaluator.py +0 -90
- flock/evaluators/test/test_case_evaluator.py +0 -38
- flock/evaluators/zep/zep_evaluator.py +0 -59
- flock/modules/__init__.py +0 -1
- flock/modules/assertion/__init__.py +0 -1
- flock/modules/assertion/assertion_module.py +0 -286
- flock/modules/callback/__init__.py +0 -1
- flock/modules/callback/callback_module.py +0 -91
- flock/modules/enterprise_memory/README.md +0 -99
- flock/modules/enterprise_memory/enterprise_memory_module.py +0 -526
- flock/modules/mem0/__init__.py +0 -1
- flock/modules/mem0/mem0_module.py +0 -126
- flock/modules/mem0_async/__init__.py +0 -1
- flock/modules/mem0_async/async_mem0_module.py +0 -126
- flock/modules/memory/__init__.py +0 -1
- flock/modules/memory/memory_module.py +0 -429
- flock/modules/memory/memory_parser.py +0 -125
- flock/modules/memory/memory_storage.py +0 -736
- flock/modules/output/__init__.py +0 -1
- flock/modules/output/output_module.py +0 -196
- flock/modules/performance/__init__.py +0 -1
- flock/modules/performance/metrics_module.py +0 -678
- flock/modules/zep/__init__.py +0 -1
- flock/modules/zep/zep_module.py +0 -192
- flock/platform/docker_tools.py +0 -49
- flock/platform/jaeger_install.py +0 -86
- flock/routers/__init__.py +0 -1
- flock/routers/agent/__init__.py +0 -1
- flock/routers/agent/agent_router.py +0 -236
- flock/routers/agent/handoff_agent.py +0 -58
- flock/routers/conditional/conditional_router.py +0 -486
- flock/routers/default/__init__.py +0 -1
- flock/routers/default/default_router.py +0 -80
- flock/routers/feedback/feedback_router.py +0 -114
- flock/routers/list_generator/list_generator_router.py +0 -166
- flock/routers/llm/__init__.py +0 -1
- flock/routers/llm/llm_router.py +0 -365
- flock/tools/__init__.py +0 -0
- flock/tools/azure_tools.py +0 -781
- flock/tools/code_tools.py +0 -167
- flock/tools/file_tools.py +0 -149
- flock/tools/github_tools.py +0 -157
- flock/tools/markdown_tools.py +0 -205
- flock/tools/system_tools.py +0 -9
- flock/tools/text_tools.py +0 -810
- flock/tools/web_tools.py +0 -92
- flock/tools/zendesk_tools.py +0 -501
- flock/webapp/__init__.py +0 -1
- flock/webapp/app/__init__.py +0 -0
- flock/webapp/app/api/__init__.py +0 -0
- flock/webapp/app/api/agent_management.py +0 -237
- flock/webapp/app/api/execution.py +0 -503
- flock/webapp/app/api/flock_management.py +0 -125
- flock/webapp/app/api/registry_viewer.py +0 -29
- flock/webapp/app/chat.py +0 -662
- flock/webapp/app/config.py +0 -104
- flock/webapp/app/dependencies.py +0 -117
- flock/webapp/app/main.py +0 -1086
- flock/webapp/app/middleware.py +0 -113
- flock/webapp/app/models_ui.py +0 -7
- flock/webapp/app/services/__init__.py +0 -0
- flock/webapp/app/services/feedback_file_service.py +0 -363
- flock/webapp/app/services/flock_service.py +0 -345
- flock/webapp/app/services/sharing_models.py +0 -81
- flock/webapp/app/services/sharing_store.py +0 -597
- flock/webapp/app/templates/theme_mapper.html +0 -326
- flock/webapp/app/theme_mapper.py +0 -811
- flock/webapp/app/utils.py +0 -85
- flock/webapp/run.py +0 -219
- flock/webapp/static/css/chat.css +0 -301
- flock/webapp/static/css/components.css +0 -167
- flock/webapp/static/css/header.css +0 -39
- flock/webapp/static/css/layout.css +0 -281
- flock/webapp/static/css/sidebar.css +0 -127
- flock/webapp/static/css/two-pane.css +0 -48
- flock/webapp/templates/base.html +0 -389
- flock/webapp/templates/chat.html +0 -152
- flock/webapp/templates/chat_settings.html +0 -19
- flock/webapp/templates/flock_editor.html +0 -16
- flock/webapp/templates/index.html +0 -12
- flock/webapp/templates/partials/_agent_detail_form.html +0 -93
- flock/webapp/templates/partials/_agent_list.html +0 -18
- flock/webapp/templates/partials/_agent_manager_view.html +0 -51
- flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
- flock/webapp/templates/partials/_chat_container.html +0 -15
- flock/webapp/templates/partials/_chat_messages.html +0 -57
- flock/webapp/templates/partials/_chat_settings_form.html +0 -85
- flock/webapp/templates/partials/_create_flock_form.html +0 -50
- flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
- flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
- flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
- flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
- flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
- flock/webapp/templates/partials/_env_vars_table.html +0 -23
- flock/webapp/templates/partials/_execution_form.html +0 -127
- flock/webapp/templates/partials/_execution_view_container.html +0 -28
- flock/webapp/templates/partials/_flock_file_list.html +0 -23
- flock/webapp/templates/partials/_flock_properties_form.html +0 -52
- flock/webapp/templates/partials/_flock_upload_form.html +0 -16
- flock/webapp/templates/partials/_header_flock_status.html +0 -5
- flock/webapp/templates/partials/_live_logs.html +0 -13
- flock/webapp/templates/partials/_load_manager_view.html +0 -49
- flock/webapp/templates/partials/_registry_table.html +0 -25
- flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
- flock/webapp/templates/partials/_results_display.html +0 -78
- flock/webapp/templates/partials/_settings_env_content.html +0 -9
- flock/webapp/templates/partials/_settings_theme_content.html +0 -14
- flock/webapp/templates/partials/_settings_view.html +0 -36
- flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
- flock/webapp/templates/partials/_share_link_snippet.html +0 -35
- flock/webapp/templates/partials/_sidebar.html +0 -74
- flock/webapp/templates/partials/_structured_data_view.html +0 -40
- flock/webapp/templates/partials/_theme_preview.html +0 -36
- flock/webapp/templates/registry_viewer.html +0 -84
- flock/webapp/templates/shared_run_page.html +0 -140
- flock/workflow/__init__.py +0 -0
- flock/workflow/activities.py +0 -237
- flock/workflow/agent_activities.py +0 -24
- flock/workflow/agent_execution_activity.py +0 -240
- flock/workflow/flock_workflow.py +0 -225
- flock/workflow/temporal_config.py +0 -96
- flock/workflow/temporal_setup.py +0 -60
- flock_core-0.4.543.dist-info/METADATA +0 -676
- flock_core-0.4.543.dist-info/RECORD +0 -572
- flock_core-0.4.543.dist-info/entry_points.txt +0 -2
- /flock/{core/logging → logging}/formatters/themes.py +0 -0
- /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
- /flock/{core/mcp → mcp}/util/__init__.py +0 -0
- {flock_core-0.4.543.dist-info → flock_core-0.5.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
"""DashboardEventCollector - captures agent lifecycle events for real-time dashboard.
|
|
2
|
+
|
|
3
|
+
This component hooks into the agent execution lifecycle to emit WebSocket events.
|
|
4
|
+
Phase 1: Events stored in in-memory buffer (max 100 events).
|
|
5
|
+
Phase 3: Extended to emit via WebSocket using WebSocketManager.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import asyncio
|
|
9
|
+
import hashlib
|
|
10
|
+
import json
|
|
11
|
+
import traceback
|
|
12
|
+
from collections import defaultdict, deque
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
from datetime import datetime, timezone
|
|
15
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
16
|
+
|
|
17
|
+
from pydantic import PrivateAttr
|
|
18
|
+
|
|
19
|
+
from flock.components import AgentComponent
|
|
20
|
+
from flock.dashboard.events import (
|
|
21
|
+
AgentActivatedEvent,
|
|
22
|
+
AgentCompletedEvent,
|
|
23
|
+
AgentErrorEvent,
|
|
24
|
+
MessagePublishedEvent,
|
|
25
|
+
SubscriptionInfo,
|
|
26
|
+
VisibilitySpec,
|
|
27
|
+
)
|
|
28
|
+
from flock.dashboard.models.graph import GraphRun, GraphState
|
|
29
|
+
from flock.logging.logging import get_logger
|
|
30
|
+
from flock.runtime import Context
|
|
31
|
+
from flock.store import AgentSnapshotRecord, BlackboardStore
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
logger = get_logger("dashboard.collector")
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING: # pragma: no cover - type hints only
|
|
37
|
+
from flock.agent import Agent
|
|
38
|
+
from flock.artifacts import Artifact
|
|
39
|
+
from flock.dashboard.websocket import WebSocketManager
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(slots=True)
|
|
43
|
+
class RunRecord:
|
|
44
|
+
run_id: str
|
|
45
|
+
agent_name: str
|
|
46
|
+
correlation_id: str = ""
|
|
47
|
+
status: str = "active"
|
|
48
|
+
consumed_artifacts: list[str] = field(default_factory=list)
|
|
49
|
+
produced_artifacts: list[str] = field(default_factory=list)
|
|
50
|
+
duration_ms: float | None = None
|
|
51
|
+
started_at: datetime | None = None
|
|
52
|
+
completed_at: datetime | None = None
|
|
53
|
+
metrics: dict[str, Any] = field(default_factory=dict)
|
|
54
|
+
error_message: str | None = None
|
|
55
|
+
|
|
56
|
+
def to_graph_run(self) -> GraphRun:
|
|
57
|
+
status = self.status if self.status in {"active", "completed", "error"} else "active"
|
|
58
|
+
return GraphRun(
|
|
59
|
+
run_id=self.run_id,
|
|
60
|
+
agent_name=self.agent_name,
|
|
61
|
+
correlation_id=self.correlation_id or None,
|
|
62
|
+
status=status, # type: ignore[arg-type]
|
|
63
|
+
consumed_artifacts=list(self.consumed_artifacts),
|
|
64
|
+
produced_artifacts=list(self.produced_artifacts),
|
|
65
|
+
duration_ms=self.duration_ms,
|
|
66
|
+
started_at=self.started_at,
|
|
67
|
+
completed_at=self.completed_at,
|
|
68
|
+
metrics=dict(self.metrics),
|
|
69
|
+
error_message=self.error_message,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass(slots=True)
|
|
74
|
+
class AgentSnapshot:
|
|
75
|
+
name: str
|
|
76
|
+
description: str
|
|
77
|
+
subscriptions: list[str]
|
|
78
|
+
output_types: list[str]
|
|
79
|
+
labels: list[str]
|
|
80
|
+
first_seen: datetime
|
|
81
|
+
last_seen: datetime
|
|
82
|
+
signature: str
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class DashboardEventCollector(AgentComponent):
|
|
86
|
+
"""Collects agent lifecycle events for dashboard visualization.
|
|
87
|
+
|
|
88
|
+
Implements AgentComponent interface to hook into agent execution:
|
|
89
|
+
- on_pre_consume: emits agent_activated
|
|
90
|
+
- on_post_publish: emits message_published
|
|
91
|
+
- on_terminate: emits agent_completed
|
|
92
|
+
- on_error: emits agent_error
|
|
93
|
+
|
|
94
|
+
Phase 1: Events stored in in-memory deque (max 100, LRU eviction).
|
|
95
|
+
Phase 3: Emits events via WebSocket using WebSocketManager.
|
|
96
|
+
"""
|
|
97
|
+
|
|
98
|
+
# Use PrivateAttr for non-Pydantic fields (AgentComponent extends BaseModel)
|
|
99
|
+
_events: deque[
|
|
100
|
+
AgentActivatedEvent | MessagePublishedEvent | AgentCompletedEvent | AgentErrorEvent
|
|
101
|
+
] = PrivateAttr(default=None)
|
|
102
|
+
|
|
103
|
+
# Track run start times for duration calculation
|
|
104
|
+
_run_start_times: dict[str, float] = PrivateAttr(default_factory=dict)
|
|
105
|
+
|
|
106
|
+
# WebSocketManager for broadcasting events
|
|
107
|
+
_websocket_manager: Optional["WebSocketManager"] = PrivateAttr(default=None)
|
|
108
|
+
|
|
109
|
+
# Graph assembly helpers
|
|
110
|
+
_graph_lock: asyncio.Lock = PrivateAttr(default_factory=asyncio.Lock)
|
|
111
|
+
_run_registry: dict[str, RunRecord] = PrivateAttr(default_factory=dict)
|
|
112
|
+
_artifact_consumers: dict[str, set[str]] = PrivateAttr(default_factory=lambda: defaultdict(set))
|
|
113
|
+
_agent_status: dict[str, str] = PrivateAttr(default_factory=dict)
|
|
114
|
+
_agent_snapshots: dict[str, AgentSnapshot] = PrivateAttr(default_factory=dict)
|
|
115
|
+
|
|
116
|
+
def __init__(self, *, store: BlackboardStore | None = None, **data):
|
|
117
|
+
super().__init__(**data)
|
|
118
|
+
# In-memory buffer with max 100 events (LRU eviction)
|
|
119
|
+
self._events = deque(maxlen=100)
|
|
120
|
+
self._run_start_times = {}
|
|
121
|
+
self._websocket_manager = None
|
|
122
|
+
self._graph_lock = asyncio.Lock()
|
|
123
|
+
self._run_registry = {}
|
|
124
|
+
self._artifact_consumers = defaultdict(set)
|
|
125
|
+
self._agent_status = {}
|
|
126
|
+
self._store: BlackboardStore | None = store
|
|
127
|
+
self._persistent_loaded = False
|
|
128
|
+
self._agent_snapshots = {}
|
|
129
|
+
|
|
130
|
+
def set_websocket_manager(self, manager: "WebSocketManager") -> None:
|
|
131
|
+
"""Set WebSocketManager for broadcasting events.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
manager: WebSocketManager instance to use for broadcasting
|
|
135
|
+
"""
|
|
136
|
+
self._websocket_manager = manager
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def events(self) -> deque:
|
|
140
|
+
"""Access events buffer."""
|
|
141
|
+
return self._events
|
|
142
|
+
|
|
143
|
+
async def on_pre_consume(
|
|
144
|
+
self, agent: "Agent", ctx: Context, inputs: list["Artifact"]
|
|
145
|
+
) -> list["Artifact"]:
|
|
146
|
+
"""Emit agent_activated event when agent begins consuming.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
agent: The agent that is consuming
|
|
150
|
+
ctx: Execution context with correlation_id
|
|
151
|
+
inputs: Artifacts being consumed
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Unmodified inputs (pass-through)
|
|
155
|
+
"""
|
|
156
|
+
# Record start time for duration calculation
|
|
157
|
+
self._run_start_times[ctx.task_id] = datetime.now(timezone.utc).timestamp()
|
|
158
|
+
|
|
159
|
+
# Extract consumed types and artifact IDs
|
|
160
|
+
consumed_types = list({artifact.type for artifact in inputs})
|
|
161
|
+
consumed_artifacts = [str(artifact.id) for artifact in inputs]
|
|
162
|
+
|
|
163
|
+
# Extract produced types from agent outputs
|
|
164
|
+
produced_types = [output.spec.type_name for output in agent.outputs]
|
|
165
|
+
|
|
166
|
+
correlation_id = str(ctx.correlation_id) if ctx.correlation_id else ""
|
|
167
|
+
async with self._graph_lock:
|
|
168
|
+
run = self._ensure_run_record(
|
|
169
|
+
run_id=ctx.task_id,
|
|
170
|
+
agent_name=agent.name,
|
|
171
|
+
correlation_id=correlation_id,
|
|
172
|
+
ensure_started=True,
|
|
173
|
+
)
|
|
174
|
+
run.status = "active"
|
|
175
|
+
for artifact_id in consumed_artifacts:
|
|
176
|
+
if artifact_id not in run.consumed_artifacts:
|
|
177
|
+
run.consumed_artifacts.append(artifact_id)
|
|
178
|
+
self._artifact_consumers[artifact_id].add(agent.name)
|
|
179
|
+
self._agent_status[agent.name] = "running"
|
|
180
|
+
await self._update_agent_snapshot_locked(agent)
|
|
181
|
+
|
|
182
|
+
# Build subscription info from agent's subscriptions
|
|
183
|
+
subscription_info = SubscriptionInfo(from_agents=[], channels=[], mode="both")
|
|
184
|
+
|
|
185
|
+
if agent.subscriptions:
|
|
186
|
+
# Get first subscription's config (agents typically have one)
|
|
187
|
+
sub = agent.subscriptions[0]
|
|
188
|
+
subscription_info.from_agents = list(sub.from_agents) if sub.from_agents else []
|
|
189
|
+
subscription_info.channels = list(sub.channels) if sub.channels else []
|
|
190
|
+
subscription_info.mode = sub.mode
|
|
191
|
+
|
|
192
|
+
# Create and store event
|
|
193
|
+
event = AgentActivatedEvent(
|
|
194
|
+
correlation_id=correlation_id,
|
|
195
|
+
agent_name=agent.name,
|
|
196
|
+
agent_id=agent.name,
|
|
197
|
+
run_id=ctx.task_id, # Unique ID for this agent run
|
|
198
|
+
consumed_types=consumed_types,
|
|
199
|
+
consumed_artifacts=consumed_artifacts,
|
|
200
|
+
produced_types=produced_types,
|
|
201
|
+
subscription_info=subscription_info,
|
|
202
|
+
labels=list(agent.labels),
|
|
203
|
+
tenant_id=agent.tenant_id,
|
|
204
|
+
max_concurrency=agent.max_concurrency,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
self._events.append(event)
|
|
208
|
+
logger.info(f"Agent activated: {agent.name} (correlation_id={event.correlation_id})")
|
|
209
|
+
|
|
210
|
+
# Broadcast via WebSocket if manager is configured
|
|
211
|
+
if self._websocket_manager:
|
|
212
|
+
await self._websocket_manager.broadcast(event)
|
|
213
|
+
else:
|
|
214
|
+
logger.warning("WebSocket manager not configured, event not broadcast")
|
|
215
|
+
|
|
216
|
+
return inputs
|
|
217
|
+
|
|
218
|
+
async def on_post_publish(self, agent: "Agent", ctx: Context, artifact: "Artifact") -> None:
|
|
219
|
+
"""Emit message_published event when artifact is published.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
agent: The agent that published the artifact
|
|
223
|
+
ctx: Execution context with correlation_id
|
|
224
|
+
artifact: The published artifact
|
|
225
|
+
"""
|
|
226
|
+
# Convert visibility to VisibilitySpec
|
|
227
|
+
visibility_spec = self._convert_visibility(artifact.visibility)
|
|
228
|
+
correlation_id = str(ctx.correlation_id) if ctx.correlation_id else ""
|
|
229
|
+
artifact_id = str(artifact.id)
|
|
230
|
+
|
|
231
|
+
async with self._graph_lock:
|
|
232
|
+
run = self._ensure_run_record(
|
|
233
|
+
run_id=ctx.task_id,
|
|
234
|
+
agent_name=agent.name,
|
|
235
|
+
correlation_id=correlation_id,
|
|
236
|
+
ensure_started=True,
|
|
237
|
+
)
|
|
238
|
+
run.status = "active"
|
|
239
|
+
if artifact_id not in run.produced_artifacts:
|
|
240
|
+
run.produced_artifacts.append(artifact_id)
|
|
241
|
+
await self._update_agent_snapshot_locked(agent)
|
|
242
|
+
|
|
243
|
+
# Create and store event
|
|
244
|
+
event = MessagePublishedEvent(
|
|
245
|
+
correlation_id=correlation_id,
|
|
246
|
+
artifact_id=str(artifact.id),
|
|
247
|
+
artifact_type=artifact.type,
|
|
248
|
+
produced_by=artifact.produced_by,
|
|
249
|
+
payload=artifact.payload,
|
|
250
|
+
visibility=visibility_spec,
|
|
251
|
+
tags=list(artifact.tags) if artifact.tags else [],
|
|
252
|
+
partition_key=artifact.partition_key,
|
|
253
|
+
version=artifact.version,
|
|
254
|
+
consumers=[], # Phase 1: empty, Phase 3: compute from subscription matching
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
self._events.append(event)
|
|
258
|
+
logger.info(
|
|
259
|
+
f"Message published: {artifact.type} by {artifact.produced_by} (correlation_id={event.correlation_id})"
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# Broadcast via WebSocket if manager is configured
|
|
263
|
+
if self._websocket_manager:
|
|
264
|
+
await self._websocket_manager.broadcast(event)
|
|
265
|
+
else:
|
|
266
|
+
logger.warning("WebSocket manager not configured, event not broadcast")
|
|
267
|
+
|
|
268
|
+
async def on_terminate(self, agent: "Agent", ctx: Context) -> None:
|
|
269
|
+
"""Emit agent_completed event when agent finishes successfully.
|
|
270
|
+
|
|
271
|
+
Args:
|
|
272
|
+
agent: The agent that completed
|
|
273
|
+
ctx: Execution context with final state
|
|
274
|
+
"""
|
|
275
|
+
# Calculate duration
|
|
276
|
+
start_time = self._run_start_times.get(ctx.task_id)
|
|
277
|
+
if start_time:
|
|
278
|
+
duration_ms = (datetime.now(timezone.utc).timestamp() - start_time) * 1000
|
|
279
|
+
del self._run_start_times[ctx.task_id]
|
|
280
|
+
else:
|
|
281
|
+
duration_ms = 0.0
|
|
282
|
+
|
|
283
|
+
# Extract artifacts produced from context state (if tracked)
|
|
284
|
+
artifacts_produced = ctx.state.get("artifacts_produced", [])
|
|
285
|
+
if not isinstance(artifacts_produced, list):
|
|
286
|
+
artifacts_produced = []
|
|
287
|
+
|
|
288
|
+
# Extract metrics from context state (if tracked)
|
|
289
|
+
metrics = ctx.state.get("metrics", {})
|
|
290
|
+
if not isinstance(metrics, dict):
|
|
291
|
+
metrics = {}
|
|
292
|
+
|
|
293
|
+
# Create and store event
|
|
294
|
+
event = AgentCompletedEvent(
|
|
295
|
+
correlation_id=str(ctx.correlation_id) if ctx.correlation_id else "",
|
|
296
|
+
agent_name=agent.name,
|
|
297
|
+
run_id=ctx.task_id,
|
|
298
|
+
duration_ms=duration_ms,
|
|
299
|
+
artifacts_produced=artifacts_produced,
|
|
300
|
+
metrics=metrics,
|
|
301
|
+
final_state=dict(ctx.state),
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
self._events.append(event)
|
|
305
|
+
|
|
306
|
+
async with self._graph_lock:
|
|
307
|
+
correlation_id = str(ctx.correlation_id) if ctx.correlation_id else ""
|
|
308
|
+
run = self._ensure_run_record(
|
|
309
|
+
run_id=ctx.task_id,
|
|
310
|
+
agent_name=agent.name,
|
|
311
|
+
correlation_id=correlation_id,
|
|
312
|
+
ensure_started=True,
|
|
313
|
+
)
|
|
314
|
+
run.status = "completed"
|
|
315
|
+
run.duration_ms = duration_ms
|
|
316
|
+
run.metrics = dict(metrics)
|
|
317
|
+
run.completed_at = datetime.now(timezone.utc)
|
|
318
|
+
for artifact_id in artifacts_produced:
|
|
319
|
+
if artifact_id not in run.produced_artifacts:
|
|
320
|
+
run.produced_artifacts.append(artifact_id)
|
|
321
|
+
self._agent_status[agent.name] = "idle"
|
|
322
|
+
await self._update_agent_snapshot_locked(agent)
|
|
323
|
+
|
|
324
|
+
# Broadcast via WebSocket if manager is configured
|
|
325
|
+
if self._websocket_manager:
|
|
326
|
+
await self._websocket_manager.broadcast(event)
|
|
327
|
+
|
|
328
|
+
async def on_error(self, agent: "Agent", ctx: Context, error: Exception) -> None:
|
|
329
|
+
"""Emit agent_error event when agent execution fails.
|
|
330
|
+
|
|
331
|
+
Args:
|
|
332
|
+
agent: The agent that failed
|
|
333
|
+
ctx: Execution context
|
|
334
|
+
error: The exception that was raised
|
|
335
|
+
"""
|
|
336
|
+
# Get error details
|
|
337
|
+
error_type = type(error).__name__
|
|
338
|
+
error_message = str(error)
|
|
339
|
+
# Use traceback.format_exception to get traceback from exception object
|
|
340
|
+
error_traceback = "".join(
|
|
341
|
+
traceback.format_exception(type(error), error, error.__traceback__)
|
|
342
|
+
)
|
|
343
|
+
failed_at = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
|
|
344
|
+
|
|
345
|
+
# Clean up start time tracking
|
|
346
|
+
if ctx.task_id in self._run_start_times:
|
|
347
|
+
del self._run_start_times[ctx.task_id]
|
|
348
|
+
|
|
349
|
+
# Create and store event
|
|
350
|
+
event = AgentErrorEvent(
|
|
351
|
+
correlation_id=str(ctx.correlation_id) if ctx.correlation_id else "",
|
|
352
|
+
agent_name=agent.name,
|
|
353
|
+
run_id=ctx.task_id,
|
|
354
|
+
error_type=error_type,
|
|
355
|
+
error_message=error_message,
|
|
356
|
+
traceback=error_traceback,
|
|
357
|
+
failed_at=failed_at,
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
self._events.append(event)
|
|
361
|
+
|
|
362
|
+
async with self._graph_lock:
|
|
363
|
+
correlation_id = str(ctx.correlation_id) if ctx.correlation_id else ""
|
|
364
|
+
run = self._ensure_run_record(
|
|
365
|
+
run_id=ctx.task_id,
|
|
366
|
+
agent_name=agent.name,
|
|
367
|
+
correlation_id=correlation_id,
|
|
368
|
+
ensure_started=True,
|
|
369
|
+
)
|
|
370
|
+
run.status = "error"
|
|
371
|
+
run.error_message = error_message
|
|
372
|
+
run.completed_at = datetime.now(timezone.utc)
|
|
373
|
+
self._agent_status[agent.name] = "error"
|
|
374
|
+
await self._update_agent_snapshot_locked(agent)
|
|
375
|
+
|
|
376
|
+
# Broadcast via WebSocket if manager is configured
|
|
377
|
+
if self._websocket_manager:
|
|
378
|
+
await self._websocket_manager.broadcast(event)
|
|
379
|
+
|
|
380
|
+
async def snapshot_graph_state(self) -> GraphState:
|
|
381
|
+
"""Return a thread-safe snapshot of runs, consumptions, and agent status."""
|
|
382
|
+
async with self._graph_lock:
|
|
383
|
+
consumptions = {
|
|
384
|
+
artifact_id: sorted(consumers)
|
|
385
|
+
for artifact_id, consumers in self._artifact_consumers.items()
|
|
386
|
+
}
|
|
387
|
+
runs = [record.to_graph_run() for record in self._run_registry.values()]
|
|
388
|
+
agent_status = dict(self._agent_status)
|
|
389
|
+
return GraphState(consumptions=consumptions, runs=runs, agent_status=agent_status)
|
|
390
|
+
|
|
391
|
+
async def snapshot_agent_registry(self) -> dict[str, AgentSnapshot]:
|
|
392
|
+
"""Return a snapshot of all known agents (active and inactive)."""
|
|
393
|
+
await self.load_persistent_snapshots()
|
|
394
|
+
async with self._graph_lock:
|
|
395
|
+
return {
|
|
396
|
+
name: self._clone_snapshot(snapshot)
|
|
397
|
+
for name, snapshot in self._agent_snapshots.items()
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async def load_persistent_snapshots(self) -> None:
|
|
401
|
+
if self._store is None or self._persistent_loaded:
|
|
402
|
+
return
|
|
403
|
+
records = await self._store.load_agent_snapshots()
|
|
404
|
+
async with self._graph_lock:
|
|
405
|
+
for record in records:
|
|
406
|
+
self._agent_snapshots[record.agent_name] = AgentSnapshot(
|
|
407
|
+
name=record.agent_name,
|
|
408
|
+
description=record.description,
|
|
409
|
+
subscriptions=list(record.subscriptions),
|
|
410
|
+
output_types=list(record.output_types),
|
|
411
|
+
labels=list(record.labels),
|
|
412
|
+
first_seen=record.first_seen,
|
|
413
|
+
last_seen=record.last_seen,
|
|
414
|
+
signature=record.signature,
|
|
415
|
+
)
|
|
416
|
+
self._persistent_loaded = True
|
|
417
|
+
|
|
418
|
+
async def clear_agent_registry(self) -> None:
|
|
419
|
+
"""Clear cached agent metadata (for explicit resets)."""
|
|
420
|
+
async with self._graph_lock:
|
|
421
|
+
self._agent_snapshots.clear()
|
|
422
|
+
if self._store is not None:
|
|
423
|
+
await self._store.clear_agent_snapshots()
|
|
424
|
+
|
|
425
|
+
def _ensure_run_record(
|
|
426
|
+
self,
|
|
427
|
+
*,
|
|
428
|
+
run_id: str,
|
|
429
|
+
agent_name: str,
|
|
430
|
+
correlation_id: str,
|
|
431
|
+
ensure_started: bool = False,
|
|
432
|
+
) -> RunRecord:
|
|
433
|
+
"""Internal helper. Caller must hold _graph_lock."""
|
|
434
|
+
run = self._run_registry.get(run_id)
|
|
435
|
+
if not run:
|
|
436
|
+
run = RunRecord(
|
|
437
|
+
run_id=run_id,
|
|
438
|
+
agent_name=agent_name,
|
|
439
|
+
correlation_id=correlation_id,
|
|
440
|
+
started_at=datetime.now(timezone.utc) if ensure_started else None,
|
|
441
|
+
)
|
|
442
|
+
self._run_registry[run_id] = run
|
|
443
|
+
else:
|
|
444
|
+
run.agent_name = agent_name
|
|
445
|
+
if correlation_id:
|
|
446
|
+
run.correlation_id = correlation_id
|
|
447
|
+
if ensure_started and run.started_at is None:
|
|
448
|
+
run.started_at = datetime.now(timezone.utc)
|
|
449
|
+
return run
|
|
450
|
+
|
|
451
|
+
async def _update_agent_snapshot_locked(self, agent: "Agent") -> None:
|
|
452
|
+
now = datetime.now(timezone.utc)
|
|
453
|
+
description = agent.description or ""
|
|
454
|
+
subscriptions = sorted(
|
|
455
|
+
{
|
|
456
|
+
type_name
|
|
457
|
+
for subscription in getattr(agent, "subscriptions", [])
|
|
458
|
+
for type_name in getattr(subscription, "type_names", [])
|
|
459
|
+
}
|
|
460
|
+
)
|
|
461
|
+
output_types = sorted(
|
|
462
|
+
{
|
|
463
|
+
output.spec.type_name
|
|
464
|
+
for output in getattr(agent, "outputs", [])
|
|
465
|
+
if getattr(output, "spec", None) is not None
|
|
466
|
+
and getattr(output.spec, "type_name", "")
|
|
467
|
+
}
|
|
468
|
+
)
|
|
469
|
+
labels = sorted(agent.labels)
|
|
470
|
+
|
|
471
|
+
signature_payload = {
|
|
472
|
+
"description": description,
|
|
473
|
+
"subscriptions": subscriptions,
|
|
474
|
+
"output_types": output_types,
|
|
475
|
+
"labels": labels,
|
|
476
|
+
}
|
|
477
|
+
signature = hashlib.sha256(
|
|
478
|
+
json.dumps(signature_payload, sort_keys=True).encode("utf-8")
|
|
479
|
+
).hexdigest()
|
|
480
|
+
|
|
481
|
+
snapshot = self._agent_snapshots.get(agent.name)
|
|
482
|
+
if snapshot is None:
|
|
483
|
+
snapshot = AgentSnapshot(
|
|
484
|
+
name=agent.name,
|
|
485
|
+
description=description,
|
|
486
|
+
subscriptions=subscriptions,
|
|
487
|
+
output_types=output_types,
|
|
488
|
+
labels=labels,
|
|
489
|
+
first_seen=now,
|
|
490
|
+
last_seen=now,
|
|
491
|
+
signature=signature,
|
|
492
|
+
)
|
|
493
|
+
self._agent_snapshots[agent.name] = snapshot
|
|
494
|
+
else:
|
|
495
|
+
snapshot.description = description
|
|
496
|
+
snapshot.subscriptions = subscriptions
|
|
497
|
+
snapshot.output_types = output_types
|
|
498
|
+
snapshot.labels = labels
|
|
499
|
+
snapshot.last_seen = now
|
|
500
|
+
snapshot.signature = signature
|
|
501
|
+
|
|
502
|
+
if self._store is not None:
|
|
503
|
+
record = self._snapshot_to_record(snapshot)
|
|
504
|
+
await self._store.upsert_agent_snapshot(record)
|
|
505
|
+
|
|
506
|
+
@staticmethod
|
|
507
|
+
def _clone_snapshot(snapshot: AgentSnapshot) -> AgentSnapshot:
|
|
508
|
+
return AgentSnapshot(
|
|
509
|
+
name=snapshot.name,
|
|
510
|
+
description=snapshot.description,
|
|
511
|
+
subscriptions=list(snapshot.subscriptions),
|
|
512
|
+
output_types=list(snapshot.output_types),
|
|
513
|
+
labels=list(snapshot.labels),
|
|
514
|
+
first_seen=snapshot.first_seen,
|
|
515
|
+
last_seen=snapshot.last_seen,
|
|
516
|
+
signature=snapshot.signature,
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
def _snapshot_to_record(self, snapshot: AgentSnapshot) -> AgentSnapshotRecord:
|
|
520
|
+
return AgentSnapshotRecord(
|
|
521
|
+
agent_name=snapshot.name,
|
|
522
|
+
description=snapshot.description,
|
|
523
|
+
subscriptions=list(snapshot.subscriptions),
|
|
524
|
+
output_types=list(snapshot.output_types),
|
|
525
|
+
labels=list(snapshot.labels),
|
|
526
|
+
first_seen=snapshot.first_seen,
|
|
527
|
+
last_seen=snapshot.last_seen,
|
|
528
|
+
signature=snapshot.signature,
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
def _convert_visibility(self, visibility) -> VisibilitySpec:
|
|
532
|
+
"""Convert flock.visibility.Visibility to VisibilitySpec.
|
|
533
|
+
|
|
534
|
+
Args:
|
|
535
|
+
visibility: Visibility object from artifact
|
|
536
|
+
|
|
537
|
+
Returns:
|
|
538
|
+
VisibilitySpec for event serialization
|
|
539
|
+
"""
|
|
540
|
+
# Get visibility kind from class name, stripping "Visibility" suffix
|
|
541
|
+
class_name = type(visibility).__name__
|
|
542
|
+
kind = class_name[: -len("Visibility")] if class_name.endswith("Visibility") else class_name
|
|
543
|
+
|
|
544
|
+
spec = VisibilitySpec(kind=kind)
|
|
545
|
+
|
|
546
|
+
# Extract type-specific fields
|
|
547
|
+
if kind == "Private":
|
|
548
|
+
spec.agents = list(visibility.agents) if hasattr(visibility, "agents") else []
|
|
549
|
+
elif kind == "Labelled":
|
|
550
|
+
spec.required_labels = (
|
|
551
|
+
list(visibility.required_labels) if hasattr(visibility, "required_labels") else []
|
|
552
|
+
)
|
|
553
|
+
elif kind == "Tenant":
|
|
554
|
+
spec.tenant_id = visibility.tenant_id if hasattr(visibility, "tenant_id") else None
|
|
555
|
+
|
|
556
|
+
return spec
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
__all__ = ["AgentSnapshot", "DashboardEventCollector"]
|