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
flock/utilities.py
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
"""Built-in utility components for metrics and logging."""
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import contextlib
|
|
8
|
+
import json
|
|
9
|
+
import sys
|
|
10
|
+
import time
|
|
11
|
+
from collections.abc import Iterable, Mapping, MutableMapping, Sequence
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
|
+
|
|
14
|
+
from rich.console import Console
|
|
15
|
+
from rich.json import JSON
|
|
16
|
+
from rich.live import Live
|
|
17
|
+
from rich.panel import Panel
|
|
18
|
+
from rich.pretty import Pretty
|
|
19
|
+
from rich.table import Table
|
|
20
|
+
from rich.text import Text
|
|
21
|
+
|
|
22
|
+
from flock.components import AgentComponent
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
if TYPE_CHECKING:
|
|
26
|
+
from flock.runtime import Context, EvalInputs, EvalResult
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class MetricsUtility(AgentComponent):
|
|
30
|
+
"""Records simple runtime metrics per agent execution."""
|
|
31
|
+
|
|
32
|
+
name: str | None = "metrics"
|
|
33
|
+
|
|
34
|
+
async def on_pre_evaluate(self, agent, ctx: Context, inputs: EvalInputs) -> EvalInputs:
|
|
35
|
+
ctx.state.setdefault("metrics", {})[f"{agent.name}:start"] = time.perf_counter()
|
|
36
|
+
return inputs
|
|
37
|
+
|
|
38
|
+
async def on_post_evaluate(
|
|
39
|
+
self, agent, ctx: Context, inputs: EvalInputs, result: EvalResult
|
|
40
|
+
) -> EvalResult:
|
|
41
|
+
metrics = ctx.state.setdefault("metrics", {})
|
|
42
|
+
start = metrics.get(f"{agent.name}:start")
|
|
43
|
+
if start:
|
|
44
|
+
metrics[f"{agent.name}:duration_ms"] = (time.perf_counter() - start) * 1000
|
|
45
|
+
result.metrics.update({k: v for k, v in metrics.items() if k.endswith("duration_ms")})
|
|
46
|
+
return result
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class LoggingUtility(AgentComponent):
|
|
50
|
+
"""Rich-powered logging with optional streaming previews."""
|
|
51
|
+
|
|
52
|
+
name: str | None = "logs"
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
console: Console | None = None,
|
|
57
|
+
*,
|
|
58
|
+
highlight_json: bool = True,
|
|
59
|
+
stream_tokens: bool = True,
|
|
60
|
+
) -> None:
|
|
61
|
+
super().__init__()
|
|
62
|
+
if console is None:
|
|
63
|
+
console = Console(
|
|
64
|
+
file=sys.stdout,
|
|
65
|
+
force_terminal=True,
|
|
66
|
+
highlight=False,
|
|
67
|
+
log_time=True,
|
|
68
|
+
log_path=False,
|
|
69
|
+
)
|
|
70
|
+
self._console = console
|
|
71
|
+
self._highlight_json = highlight_json
|
|
72
|
+
self._stream_tokens = stream_tokens
|
|
73
|
+
self._stream_context: dict[str, tuple[asyncio.Queue, asyncio.Task]] = {}
|
|
74
|
+
|
|
75
|
+
async def on_initialize(self, agent, ctx: Context) -> None:
|
|
76
|
+
self._console.log(f"[{agent.name}] start task={ctx.task_id}")
|
|
77
|
+
await super().on_initialize(agent, ctx)
|
|
78
|
+
|
|
79
|
+
async def on_pre_consume(self, agent, ctx: Context, inputs: list[Any]):
|
|
80
|
+
summary = ", ".join(self._summarize_artifact(art) for art in inputs) or "<none>"
|
|
81
|
+
self._console.log(f"[{agent.name}] consume n={len(inputs)} artifacts -> {summary}")
|
|
82
|
+
self._render_artifacts(agent.name, inputs, role="input")
|
|
83
|
+
return await super().on_pre_consume(agent, ctx, inputs)
|
|
84
|
+
|
|
85
|
+
async def on_pre_evaluate(self, agent, ctx: Context, inputs: EvalInputs) -> EvalInputs:
|
|
86
|
+
if self._stream_tokens:
|
|
87
|
+
self._maybe_start_stream(agent, ctx)
|
|
88
|
+
return await super().on_pre_evaluate(agent, ctx, inputs)
|
|
89
|
+
|
|
90
|
+
async def on_post_evaluate(
|
|
91
|
+
self, agent, ctx: Context, inputs: EvalInputs, result: EvalResult
|
|
92
|
+
) -> EvalResult:
|
|
93
|
+
self._render_metrics(agent.name, result.metrics)
|
|
94
|
+
self._render_artifacts(agent.name, result.artifacts or inputs.artifacts, role="output")
|
|
95
|
+
if result.logs:
|
|
96
|
+
self._render_logs(agent.name, result.logs)
|
|
97
|
+
awaited = await super().on_post_evaluate(agent, ctx, inputs, result)
|
|
98
|
+
if self._stream_tokens:
|
|
99
|
+
await self._finalize_stream(agent, ctx)
|
|
100
|
+
return awaited
|
|
101
|
+
|
|
102
|
+
async def on_post_publish(self, agent, ctx: Context, artifact):
|
|
103
|
+
visibility = getattr(artifact.visibility, "kind", "Public")
|
|
104
|
+
subtitle = f"visibility={visibility}"
|
|
105
|
+
panel = self._build_artifact_panel(artifact, role="published", subtitle=subtitle)
|
|
106
|
+
self._console.print(panel)
|
|
107
|
+
await super().on_post_publish(agent, ctx, artifact)
|
|
108
|
+
|
|
109
|
+
async def on_error(self, agent, ctx: Context, error: Exception) -> None:
|
|
110
|
+
self._console.log(f"[{agent.name}] error {error!r}", style="bold red")
|
|
111
|
+
if self._stream_tokens:
|
|
112
|
+
await self._abort_stream(agent, ctx)
|
|
113
|
+
await super().on_error(agent, ctx, error)
|
|
114
|
+
|
|
115
|
+
async def on_terminate(self, agent, ctx: Context) -> None:
|
|
116
|
+
if self._stream_tokens:
|
|
117
|
+
await self._abort_stream(agent, ctx)
|
|
118
|
+
self._console.log(f"[{agent.name}] end task={ctx.task_id}")
|
|
119
|
+
await super().on_terminate(agent, ctx)
|
|
120
|
+
|
|
121
|
+
# ------------------------------------------------------------------
|
|
122
|
+
# Rendering helpers
|
|
123
|
+
|
|
124
|
+
def _render_artifacts(self, agent_name: str, artifacts: Sequence[Any], *, role: str) -> None:
|
|
125
|
+
for artifact in artifacts:
|
|
126
|
+
panel = self._build_artifact_panel(artifact, role=role)
|
|
127
|
+
self._console.print(panel)
|
|
128
|
+
|
|
129
|
+
def _build_artifact_panel(
|
|
130
|
+
self, artifact: Any, *, role: str, subtitle: str | None = None
|
|
131
|
+
) -> Panel:
|
|
132
|
+
title = f"{role} • {self._summarize_artifact(artifact)}"
|
|
133
|
+
if subtitle is None:
|
|
134
|
+
produced_by = getattr(artifact, "produced_by", None)
|
|
135
|
+
visibility = getattr(artifact, "visibility", None)
|
|
136
|
+
visibility_name = getattr(visibility, "kind", None)
|
|
137
|
+
pieces = []
|
|
138
|
+
if produced_by:
|
|
139
|
+
pieces.append(f"from={produced_by}")
|
|
140
|
+
if visibility_name:
|
|
141
|
+
pieces.append(f"visibility={visibility_name}")
|
|
142
|
+
subtitle = " | ".join(pieces)
|
|
143
|
+
|
|
144
|
+
payload = getattr(artifact, "payload", None)
|
|
145
|
+
renderable = self._render_payload(payload)
|
|
146
|
+
return Panel(renderable, title=title, subtitle=subtitle, border_style="cyan")
|
|
147
|
+
|
|
148
|
+
def _render_payload(self, payload: Any):
|
|
149
|
+
if payload is None:
|
|
150
|
+
return Pretty(payload)
|
|
151
|
+
if isinstance(payload, Mapping):
|
|
152
|
+
if self._highlight_json:
|
|
153
|
+
try:
|
|
154
|
+
return JSON.from_data(payload, indent=2, sort_keys=True)
|
|
155
|
+
except Exception: # nosec B110 - pragma: no cover - serialization guard
|
|
156
|
+
pass
|
|
157
|
+
return Pretty(dict(payload))
|
|
158
|
+
if isinstance(payload, (list, tuple, set)):
|
|
159
|
+
return Pretty(payload)
|
|
160
|
+
if hasattr(payload, "model_dump"):
|
|
161
|
+
model_dict = payload.model_dump()
|
|
162
|
+
return JSON.from_data(model_dict, indent=2, sort_keys=True)
|
|
163
|
+
return Pretty(payload)
|
|
164
|
+
|
|
165
|
+
def _render_metrics(self, agent_name: str, metrics: Mapping[str, Any]) -> None:
|
|
166
|
+
if not metrics:
|
|
167
|
+
return
|
|
168
|
+
table = Table(title=f"{agent_name} metrics", box=None, show_header=False)
|
|
169
|
+
for key, value in metrics.items():
|
|
170
|
+
display = f"{value:.2f}" if isinstance(value, (int, float)) else str(value)
|
|
171
|
+
table.add_row(key, display)
|
|
172
|
+
self._console.print(table)
|
|
173
|
+
|
|
174
|
+
def _render_logs(self, agent_name: str, logs: Iterable[str]) -> None:
|
|
175
|
+
if not logs:
|
|
176
|
+
return
|
|
177
|
+
textual: list[str] = []
|
|
178
|
+
json_sections: list[JSON] = []
|
|
179
|
+
for line in logs:
|
|
180
|
+
if line.startswith("dspy.output="):
|
|
181
|
+
_, _, payload = line.partition("=")
|
|
182
|
+
try:
|
|
183
|
+
json_sections.append(
|
|
184
|
+
JSON.from_data(json.loads(payload), indent=2, sort_keys=True)
|
|
185
|
+
)
|
|
186
|
+
continue
|
|
187
|
+
except json.JSONDecodeError:
|
|
188
|
+
textual.append(line)
|
|
189
|
+
else:
|
|
190
|
+
textual.append(line)
|
|
191
|
+
for payload in json_sections:
|
|
192
|
+
panel = Panel(payload, title=f"{agent_name} ▸ dspy.output", border_style="green")
|
|
193
|
+
self._console.print(panel)
|
|
194
|
+
if textual:
|
|
195
|
+
body = Text("\n".join(textual) + "\n")
|
|
196
|
+
panel = Panel(body, title=f"{agent_name} logs", border_style="magenta")
|
|
197
|
+
self._console.print(panel)
|
|
198
|
+
|
|
199
|
+
def _summarize_artifact(self, artifact: Any) -> str:
|
|
200
|
+
try:
|
|
201
|
+
art_id = getattr(artifact, "id", None)
|
|
202
|
+
prefix = str(art_id)[:8] if art_id else "?"
|
|
203
|
+
art_type = getattr(artifact, "type", type(artifact).__name__)
|
|
204
|
+
return f"{art_type}@{prefix}"
|
|
205
|
+
except Exception: # pragma: no cover - defensive
|
|
206
|
+
return repr(artifact)
|
|
207
|
+
|
|
208
|
+
# ------------------------------------------------------------------
|
|
209
|
+
# Streaming support
|
|
210
|
+
|
|
211
|
+
def _maybe_start_stream(self, agent, ctx: Context) -> None:
|
|
212
|
+
stream_key = self._stream_key(agent, ctx)
|
|
213
|
+
if stream_key in self._stream_context:
|
|
214
|
+
return
|
|
215
|
+
queue: asyncio.Queue = asyncio.Queue()
|
|
216
|
+
self._attach_stream_queue(ctx.state, queue)
|
|
217
|
+
task = asyncio.create_task(self._consume_stream(agent.name, stream_key, queue))
|
|
218
|
+
self._stream_context[stream_key] = (queue, task)
|
|
219
|
+
|
|
220
|
+
async def _finalize_stream(self, agent, ctx: Context) -> None:
|
|
221
|
+
stream_key = self._stream_key(agent, ctx)
|
|
222
|
+
record = self._stream_context.pop(stream_key, None)
|
|
223
|
+
self._detach_stream_queue(ctx.state)
|
|
224
|
+
if not record:
|
|
225
|
+
return
|
|
226
|
+
queue, task = record
|
|
227
|
+
if not task.done():
|
|
228
|
+
await queue.put({"kind": "end"})
|
|
229
|
+
try:
|
|
230
|
+
await asyncio.wait_for(task, timeout=2.0)
|
|
231
|
+
except asyncio.TimeoutError: # pragma: no cover - defensive cancel
|
|
232
|
+
task.cancel()
|
|
233
|
+
|
|
234
|
+
async def _abort_stream(self, agent, ctx: Context) -> None:
|
|
235
|
+
stream_key = self._stream_key(agent, ctx)
|
|
236
|
+
record = self._stream_context.pop(stream_key, None)
|
|
237
|
+
self._detach_stream_queue(ctx.state)
|
|
238
|
+
if not record:
|
|
239
|
+
return
|
|
240
|
+
queue, task = record
|
|
241
|
+
if not task.done():
|
|
242
|
+
await queue.put({"kind": "end", "error": "aborted"})
|
|
243
|
+
task.cancel()
|
|
244
|
+
with contextlib.suppress(asyncio.CancelledError):
|
|
245
|
+
await task
|
|
246
|
+
|
|
247
|
+
async def _consume_stream(self, agent_name: str, stream_key: str, queue: asyncio.Queue) -> None:
|
|
248
|
+
body = Text()
|
|
249
|
+
live: Live | None = None
|
|
250
|
+
try:
|
|
251
|
+
while True:
|
|
252
|
+
event = await queue.get()
|
|
253
|
+
if event is None or event.get("kind") == "end":
|
|
254
|
+
break
|
|
255
|
+
kind = event.get("kind")
|
|
256
|
+
if live is None:
|
|
257
|
+
live_panel = Panel(body, title=f"{agent_name} ▸ streaming", border_style="cyan")
|
|
258
|
+
live = Live(
|
|
259
|
+
live_panel,
|
|
260
|
+
console=self._console,
|
|
261
|
+
refresh_per_second=12,
|
|
262
|
+
transient=True,
|
|
263
|
+
)
|
|
264
|
+
live.__enter__()
|
|
265
|
+
if kind == "chunk":
|
|
266
|
+
chunk = event.get("chunk") or ""
|
|
267
|
+
body.append(chunk)
|
|
268
|
+
elif kind == "status":
|
|
269
|
+
message = event.get("message") or ""
|
|
270
|
+
stage = event.get("stage")
|
|
271
|
+
line = f"[{stage}] {message}" if stage else message
|
|
272
|
+
body.append(f"\n{line}\n", style="dim")
|
|
273
|
+
elif kind == "error":
|
|
274
|
+
message = event.get("message") or ""
|
|
275
|
+
body.append(f"\n⚠ {message}\n", style="bold red")
|
|
276
|
+
if live is not None:
|
|
277
|
+
live.update(Panel(body, title=f"{agent_name} ▸ streaming", border_style="cyan"))
|
|
278
|
+
finally:
|
|
279
|
+
if live is not None:
|
|
280
|
+
live.__exit__(None, None, None)
|
|
281
|
+
if body.plain:
|
|
282
|
+
self._console.print(
|
|
283
|
+
Panel(body, title=f"{agent_name} ▸ stream transcript", border_style="cyan")
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
def _stream_key(self, agent, ctx: Context) -> str:
|
|
287
|
+
return f"{ctx.task_id}:{agent.name}"
|
|
288
|
+
|
|
289
|
+
def _attach_stream_queue(self, state: MutableMapping[str, Any], queue: asyncio.Queue) -> None:
|
|
290
|
+
state.setdefault("_logging", {})["stream_queue"] = queue
|
|
291
|
+
|
|
292
|
+
def _detach_stream_queue(self, state: MutableMapping[str, Any]) -> None:
|
|
293
|
+
try:
|
|
294
|
+
logging_state = state.get("_logging")
|
|
295
|
+
if isinstance(logging_state, MutableMapping):
|
|
296
|
+
logging_state.pop("stream_queue", None)
|
|
297
|
+
except Exception: # nosec B110 - pragma: no cover - defensive
|
|
298
|
+
pass
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
__all__ = ["LoggingUtility", "MetricsUtility"]
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
# src/flock/components/utility/output_utility_component.py
|
|
2
|
+
"""Output formatting and display functionality for agents using unified component architecture."""
|
|
3
|
+
|
|
4
|
+
import re
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
from pydantic import Field
|
|
8
|
+
|
|
9
|
+
from flock.components import AgentComponent, AgentComponentConfig
|
|
10
|
+
from flock.logging.formatters.themed_formatter import (
|
|
11
|
+
ThemedAgentResultFormatter,
|
|
12
|
+
)
|
|
13
|
+
from flock.logging.formatters.themes import OutputTheme
|
|
14
|
+
from flock.logging.logging import get_logger
|
|
15
|
+
from flock.runtime import Context, EvalInputs, EvalResult
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING: # pragma: no cover - type checking only
|
|
19
|
+
from flock.agent import Agent
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
logger = get_logger("components.utility.output")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class OutputUtilityConfig(AgentComponentConfig):
|
|
26
|
+
"""Configuration for output formatting and display."""
|
|
27
|
+
|
|
28
|
+
theme: OutputTheme = Field(
|
|
29
|
+
default=OutputTheme.catppuccin_mocha, description="Theme for output formatting"
|
|
30
|
+
)
|
|
31
|
+
render_table: bool = Field(default=True, description="Whether to render output as a table")
|
|
32
|
+
max_length: int = Field(default=1000, description="Maximum length for displayed output")
|
|
33
|
+
truncate_long_values: bool = Field(
|
|
34
|
+
default=True, description="Whether to truncate long values in display"
|
|
35
|
+
)
|
|
36
|
+
show_metadata: bool = Field(
|
|
37
|
+
default=True, description="Whether to show metadata like timestamps"
|
|
38
|
+
)
|
|
39
|
+
format_code_blocks: bool = Field(
|
|
40
|
+
default=True,
|
|
41
|
+
description="Whether to apply syntax highlighting to code blocks",
|
|
42
|
+
)
|
|
43
|
+
custom_formatters: dict[str, str] = Field(
|
|
44
|
+
default_factory=dict,
|
|
45
|
+
description="Custom formatters for specific output types",
|
|
46
|
+
)
|
|
47
|
+
no_output: bool = Field(
|
|
48
|
+
default=False,
|
|
49
|
+
description="Whether to suppress output",
|
|
50
|
+
)
|
|
51
|
+
print_context: bool = Field(
|
|
52
|
+
default=False,
|
|
53
|
+
description="Whether to print the context",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class OutputUtilityComponent(AgentComponent):
|
|
58
|
+
"""Utility component that handles output formatting and display."""
|
|
59
|
+
|
|
60
|
+
config: OutputUtilityConfig = Field(
|
|
61
|
+
default_factory=OutputUtilityConfig, description="Output configuration"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def __init__(self, name: str = "output", config: OutputUtilityConfig | None = None, **data):
|
|
65
|
+
if config is None:
|
|
66
|
+
config = OutputUtilityConfig()
|
|
67
|
+
super().__init__(name=name, config=config, **data)
|
|
68
|
+
self._formatter = ThemedAgentResultFormatter(
|
|
69
|
+
theme=self.config.theme,
|
|
70
|
+
max_length=self.config.max_length,
|
|
71
|
+
render_table=self.config.render_table,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
def _format_value(self, value: Any, key: str) -> str:
|
|
75
|
+
"""Format a single value based on its type and configuration."""
|
|
76
|
+
# Check for custom formatter
|
|
77
|
+
if key in self.config.custom_formatters:
|
|
78
|
+
formatter_name = self.config.custom_formatters[key]
|
|
79
|
+
if hasattr(self, f"_format_{formatter_name}"):
|
|
80
|
+
return getattr(self, f"_format_{formatter_name}")(value)
|
|
81
|
+
|
|
82
|
+
# Default formatting based on type
|
|
83
|
+
if isinstance(value, dict):
|
|
84
|
+
return self._format_dict(value)
|
|
85
|
+
if isinstance(value, list):
|
|
86
|
+
return self._format_list(value)
|
|
87
|
+
if isinstance(value, str) and self.config.format_code_blocks:
|
|
88
|
+
return self._format_potential_code(value)
|
|
89
|
+
return str(value)
|
|
90
|
+
|
|
91
|
+
def _format_dict(self, d: dict[str, Any], indent: int = 0) -> str:
|
|
92
|
+
"""Format a dictionary with proper indentation."""
|
|
93
|
+
if not d:
|
|
94
|
+
return "{}"
|
|
95
|
+
|
|
96
|
+
items = []
|
|
97
|
+
prefix = " " * indent
|
|
98
|
+
for key, value in d.items():
|
|
99
|
+
if self.config.truncate_long_values and isinstance(value, str) and len(value) > 100:
|
|
100
|
+
value = value[:97] + "..."
|
|
101
|
+
formatted_value = self._format_value(value, key)
|
|
102
|
+
items.append(f"{prefix} {key}: {formatted_value}")
|
|
103
|
+
|
|
104
|
+
return "{\n" + "\n".join(items) + f"\n{prefix}}}"
|
|
105
|
+
|
|
106
|
+
def _format_list(self, lst: list[Any]) -> str:
|
|
107
|
+
"""Format a list with proper structure."""
|
|
108
|
+
if not lst:
|
|
109
|
+
return "[]"
|
|
110
|
+
|
|
111
|
+
if len(lst) <= 3:
|
|
112
|
+
return str(lst)
|
|
113
|
+
|
|
114
|
+
# For longer lists, show first few items and count
|
|
115
|
+
preview = [str(item) for item in lst[:3]]
|
|
116
|
+
return f"[{', '.join(preview)}, ... ({len(lst)} total)]"
|
|
117
|
+
|
|
118
|
+
def _format_potential_code(self, text: str) -> str:
|
|
119
|
+
"""Apply syntax highlighting to potential code blocks."""
|
|
120
|
+
|
|
121
|
+
# Simple pattern matching for code blocks
|
|
122
|
+
def replace_code_block(match):
|
|
123
|
+
language = match.group(1) or "text"
|
|
124
|
+
code = match.group(2)
|
|
125
|
+
return f"[CODE:{language}]\n{code}\n[/CODE]"
|
|
126
|
+
|
|
127
|
+
# Replace markdown-style code blocks
|
|
128
|
+
return re.sub(r"```(\w+)?\n(.*?)\n```", replace_code_block, text, flags=re.DOTALL)
|
|
129
|
+
|
|
130
|
+
async def on_post_evaluate(
|
|
131
|
+
self, agent: "Agent", ctx: Context, inputs: EvalInputs, result: EvalResult
|
|
132
|
+
) -> dict[str, Any]:
|
|
133
|
+
"""Format and display the output."""
|
|
134
|
+
logger.debug("Formatting and displaying output")
|
|
135
|
+
|
|
136
|
+
streaming_live_handled = False
|
|
137
|
+
output_queued = False
|
|
138
|
+
streamed_artifact_id = None
|
|
139
|
+
|
|
140
|
+
if ctx:
|
|
141
|
+
streaming_live_handled = bool(ctx.get_variable("_flock_stream_live_active", False))
|
|
142
|
+
output_queued = bool(ctx.get_variable("_flock_output_queued", False))
|
|
143
|
+
streamed_artifact_id = ctx.get_variable("_flock_streamed_artifact_id")
|
|
144
|
+
|
|
145
|
+
if streaming_live_handled:
|
|
146
|
+
ctx.state.pop("_flock_stream_live_active", None)
|
|
147
|
+
|
|
148
|
+
if output_queued:
|
|
149
|
+
ctx.state.pop("_flock_output_queued", None)
|
|
150
|
+
|
|
151
|
+
if streamed_artifact_id:
|
|
152
|
+
ctx.state.pop("_flock_streamed_artifact_id", None)
|
|
153
|
+
|
|
154
|
+
# If streaming was handled, we need to update the final display with the real artifact ID
|
|
155
|
+
if streaming_live_handled and streamed_artifact_id:
|
|
156
|
+
logger.debug(
|
|
157
|
+
f"Updating streamed display with final artifact ID: {streamed_artifact_id}"
|
|
158
|
+
)
|
|
159
|
+
# The streaming display already showed everything, we just need to update the ID
|
|
160
|
+
# This is handled by a final refresh in the streaming code
|
|
161
|
+
return result
|
|
162
|
+
|
|
163
|
+
# Skip output if streaming already handled it (and no ID to update)
|
|
164
|
+
if streaming_live_handled:
|
|
165
|
+
logger.debug("Skipping static table because streaming rendered live output.")
|
|
166
|
+
return result
|
|
167
|
+
|
|
168
|
+
# If output was queued due to concurrent stream, wait and then display
|
|
169
|
+
if output_queued:
|
|
170
|
+
# Wait for active streams to complete
|
|
171
|
+
orchestrator = getattr(ctx, "orchestrator", None)
|
|
172
|
+
if orchestrator:
|
|
173
|
+
import asyncio
|
|
174
|
+
|
|
175
|
+
# Wait until no streams are active
|
|
176
|
+
max_wait = 30 # seconds
|
|
177
|
+
waited = 0
|
|
178
|
+
while getattr(orchestrator, "_active_streams", 0) > 0 and waited < max_wait:
|
|
179
|
+
await asyncio.sleep(0.1)
|
|
180
|
+
waited += 0.1
|
|
181
|
+
logger.debug(
|
|
182
|
+
f"Queued output displayed after waiting {waited:.1f}s for streams to complete."
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
logger.debug("Formatting and displaying output to console.")
|
|
186
|
+
|
|
187
|
+
if self.config.print_context and ctx:
|
|
188
|
+
# Add context snapshot if requested (be careful with large contexts)
|
|
189
|
+
try:
|
|
190
|
+
# Create a copy or select relevant parts to avoid modifying original result dict directly
|
|
191
|
+
display_result = result.copy()
|
|
192
|
+
display_result["context_snapshot"] = ctx.to_dict() # Potential performance hit
|
|
193
|
+
except Exception:
|
|
194
|
+
display_result = result.copy()
|
|
195
|
+
display_result["context_snapshot"] = "[Error serializing context]"
|
|
196
|
+
result_to_display = display_result
|
|
197
|
+
else:
|
|
198
|
+
result_to_display = result
|
|
199
|
+
|
|
200
|
+
if not hasattr(self, "_formatter") or self._formatter is None:
|
|
201
|
+
self._formatter = ThemedAgentResultFormatter(
|
|
202
|
+
theme=self.config.theme,
|
|
203
|
+
max_length=self.config.max_length,
|
|
204
|
+
render_table=self.config.render_table,
|
|
205
|
+
)
|
|
206
|
+
model = agent.model if agent.model else ctx.get_variable("model")
|
|
207
|
+
# Handle None model gracefully
|
|
208
|
+
model_display = model if model is not None else "default"
|
|
209
|
+
self._formatter.display_result(
|
|
210
|
+
result_to_display.artifacts, agent.name + " - " + model_display
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
return result # Return the original, unmodified result
|
|
214
|
+
|
|
215
|
+
def update_theme(self, new_theme: OutputTheme) -> None:
|
|
216
|
+
"""Update the output theme."""
|
|
217
|
+
self.config.theme = new_theme
|
|
218
|
+
self._formatter = ThemedAgentResultFormatter(
|
|
219
|
+
theme=self.config.theme,
|
|
220
|
+
max_length=self.config.max_length,
|
|
221
|
+
render_table=self.config.render_table,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
def add_custom_formatter(self, key: str, formatter_name: str) -> None:
|
|
225
|
+
"""Add a custom formatter for a specific output key."""
|
|
226
|
+
self.config.custom_formatters[key] = formatter_name
|
flock/visibility.py
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
"""Artifact visibility policies."""
|
|
5
|
+
|
|
6
|
+
from datetime import datetime, timedelta, timezone
|
|
7
|
+
from typing import TYPE_CHECKING, Literal
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, Field, PrivateAttr
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from collections.abc import Iterable
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AgentIdentity(BaseModel):
|
|
17
|
+
"""Minimal identity information about an agent for visibility checks."""
|
|
18
|
+
|
|
19
|
+
name: str
|
|
20
|
+
labels: set[str] = Field(default_factory=set)
|
|
21
|
+
tenant_id: str | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class Visibility(BaseModel):
|
|
25
|
+
"""Base visibility contract."""
|
|
26
|
+
|
|
27
|
+
kind: Literal["Public", "Private", "Labelled", "Tenant", "After"]
|
|
28
|
+
|
|
29
|
+
def allows(self, agent: AgentIdentity, *, now: datetime | None = None) -> bool:
|
|
30
|
+
raise NotImplementedError
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class PublicVisibility(Visibility):
|
|
34
|
+
kind: Literal["Public"] = "Public"
|
|
35
|
+
|
|
36
|
+
def allows(
|
|
37
|
+
self, agent: AgentIdentity, *, now: datetime | None = None
|
|
38
|
+
) -> bool: # pragma: no cover - trivial
|
|
39
|
+
return True
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class PrivateVisibility(Visibility):
|
|
43
|
+
kind: Literal["Private"] = "Private"
|
|
44
|
+
agents: set[str] = Field(default_factory=set)
|
|
45
|
+
|
|
46
|
+
def allows(self, agent: AgentIdentity, *, now: datetime | None = None) -> bool:
|
|
47
|
+
return agent.name in self.agents
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class LabelledVisibility(Visibility):
|
|
51
|
+
kind: Literal["Labelled"] = "Labelled"
|
|
52
|
+
required_labels: set[str] = Field(default_factory=set)
|
|
53
|
+
|
|
54
|
+
def allows(self, agent: AgentIdentity, *, now: datetime | None = None) -> bool:
|
|
55
|
+
return self.required_labels.issubset(agent.labels)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class TenantVisibility(Visibility):
|
|
59
|
+
kind: Literal["Tenant"] = "Tenant"
|
|
60
|
+
tenant_id: str | None = None
|
|
61
|
+
|
|
62
|
+
def allows(self, agent: AgentIdentity, *, now: datetime | None = None) -> bool:
|
|
63
|
+
if self.tenant_id is None:
|
|
64
|
+
return True
|
|
65
|
+
return agent.tenant_id == self.tenant_id
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class AfterVisibility(Visibility):
|
|
69
|
+
kind: Literal["After"] = "After"
|
|
70
|
+
ttl: timedelta = Field(default=timedelta())
|
|
71
|
+
then: Visibility | None = None
|
|
72
|
+
_created_at: datetime = PrivateAttr(default_factory=lambda: datetime.now(timezone.utc))
|
|
73
|
+
|
|
74
|
+
def allows(self, agent: AgentIdentity, *, now: datetime | None = None) -> bool:
|
|
75
|
+
now = now or datetime.now(timezone.utc)
|
|
76
|
+
if now - self._created_at >= self.ttl:
|
|
77
|
+
if self.then:
|
|
78
|
+
return self.then.allows(agent, now=now)
|
|
79
|
+
return True
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def ensure_visibility(value: Visibility | None) -> Visibility:
|
|
84
|
+
if value is None:
|
|
85
|
+
return PublicVisibility()
|
|
86
|
+
return value
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def only_for(*agent_names: str) -> PrivateVisibility:
|
|
90
|
+
return PrivateVisibility(agents=set(agent_names))
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def agents_from_names(names: Iterable[str]) -> set[str]: # pragma: no cover - helper
|
|
94
|
+
return set(names)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
__all__ = [
|
|
98
|
+
"AfterVisibility",
|
|
99
|
+
"AgentIdentity",
|
|
100
|
+
"LabelledVisibility",
|
|
101
|
+
"PrivateVisibility",
|
|
102
|
+
"PublicVisibility",
|
|
103
|
+
"TenantVisibility",
|
|
104
|
+
"Visibility",
|
|
105
|
+
"ensure_visibility",
|
|
106
|
+
"only_for",
|
|
107
|
+
]
|