flock-core 0.5.0b28__py3-none-any.whl → 0.5.56b0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of flock-core might be problematic. Click here for more details.
- flock/__init__.py +12 -217
- flock/agent.py +678 -0
- flock/api/themes.py +71 -0
- flock/artifacts.py +79 -0
- flock/cli.py +75 -0
- flock/components.py +173 -0
- flock/dashboard/__init__.py +28 -0
- flock/dashboard/collector.py +283 -0
- flock/dashboard/events.py +182 -0
- flock/dashboard/launcher.py +230 -0
- flock/dashboard/service.py +537 -0
- flock/dashboard/websocket.py +235 -0
- flock/engines/__init__.py +6 -0
- flock/engines/dspy_engine.py +856 -0
- flock/examples.py +128 -0
- flock/{core/util → helper}/cli_helper.py +4 -3
- flock/{core/logging → logging}/__init__.py +2 -3
- flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
- flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
- flock/{core/logging → logging}/formatters/themed_formatter.py +69 -115
- flock/{core/logging → logging}/logging.py +77 -61
- flock/{core/logging → logging}/telemetry.py +20 -26
- flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
- flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +6 -9
- flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
- flock/{core/logging → logging}/trace_and_logged.py +20 -24
- flock/mcp/__init__.py +91 -0
- flock/{core/mcp/mcp_client.py → mcp/client.py} +103 -154
- flock/{core/mcp/mcp_config.py → mcp/config.py} +62 -117
- flock/mcp/manager.py +255 -0
- flock/mcp/servers/sse/__init__.py +1 -1
- flock/mcp/servers/sse/flock_sse_server.py +11 -53
- flock/mcp/servers/stdio/__init__.py +1 -1
- flock/mcp/servers/stdio/flock_stdio_server.py +8 -48
- flock/mcp/servers/streamable_http/flock_streamable_http_server.py +17 -62
- flock/mcp/servers/websockets/flock_websocket_server.py +7 -40
- flock/{core/mcp/flock_mcp_tool.py → mcp/tool.py} +16 -26
- flock/mcp/types/__init__.py +42 -0
- flock/{core/mcp → mcp}/types/callbacks.py +9 -15
- flock/{core/mcp → mcp}/types/factories.py +7 -6
- flock/{core/mcp → mcp}/types/handlers.py +13 -18
- flock/{core/mcp → mcp}/types/types.py +70 -74
- flock/{core/mcp → mcp}/util/helpers.py +1 -1
- flock/orchestrator.py +645 -0
- flock/registry.py +148 -0
- flock/runtime.py +262 -0
- flock/service.py +140 -0
- flock/store.py +69 -0
- flock/subscription.py +111 -0
- flock/themes/andromeda.toml +1 -1
- flock/themes/apple-system-colors.toml +1 -1
- flock/themes/arcoiris.toml +1 -1
- flock/themes/atomonelight.toml +1 -1
- flock/themes/ayu copy.toml +1 -1
- flock/themes/ayu-light.toml +1 -1
- flock/themes/belafonte-day.toml +1 -1
- flock/themes/belafonte-night.toml +1 -1
- flock/themes/blulocodark.toml +1 -1
- flock/themes/breeze.toml +1 -1
- flock/themes/broadcast.toml +1 -1
- flock/themes/brogrammer.toml +1 -1
- flock/themes/builtin-dark.toml +1 -1
- flock/themes/builtin-pastel-dark.toml +1 -1
- flock/themes/catppuccin-latte.toml +1 -1
- flock/themes/catppuccin-macchiato.toml +1 -1
- flock/themes/catppuccin-mocha.toml +1 -1
- flock/themes/cga.toml +1 -1
- flock/themes/chalk.toml +1 -1
- flock/themes/ciapre.toml +1 -1
- flock/themes/coffee-theme.toml +1 -1
- flock/themes/cyberpunkscarletprotocol.toml +1 -1
- flock/themes/dark+.toml +1 -1
- flock/themes/darkermatrix.toml +1 -1
- flock/themes/darkside.toml +1 -1
- flock/themes/desert.toml +1 -1
- flock/themes/django.toml +1 -1
- flock/themes/djangosmooth.toml +1 -1
- flock/themes/doomone.toml +1 -1
- flock/themes/dotgov.toml +1 -1
- flock/themes/dracula+.toml +1 -1
- flock/themes/duckbones.toml +1 -1
- flock/themes/encom.toml +1 -1
- flock/themes/espresso.toml +1 -1
- flock/themes/everblush.toml +1 -1
- flock/themes/fairyfloss.toml +1 -1
- flock/themes/fideloper.toml +1 -1
- flock/themes/fishtank.toml +1 -1
- flock/themes/flexoki-light.toml +1 -1
- flock/themes/floraverse.toml +1 -1
- flock/themes/framer.toml +1 -1
- flock/themes/galizur.toml +1 -1
- flock/themes/github.toml +1 -1
- flock/themes/grass.toml +1 -1
- flock/themes/grey-green.toml +1 -1
- flock/themes/gruvboxlight.toml +1 -1
- flock/themes/guezwhoz.toml +1 -1
- flock/themes/harper.toml +1 -1
- flock/themes/hax0r-blue.toml +1 -1
- flock/themes/hopscotch.256.toml +1 -1
- flock/themes/ic-green-ppl.toml +1 -1
- flock/themes/iceberg-dark.toml +1 -1
- flock/themes/japanesque.toml +1 -1
- flock/themes/jubi.toml +1 -1
- flock/themes/kibble.toml +1 -1
- flock/themes/kolorit.toml +1 -1
- flock/themes/kurokula.toml +1 -1
- flock/themes/materialdesigncolors.toml +1 -1
- flock/themes/matrix.toml +1 -1
- flock/themes/mellifluous.toml +1 -1
- flock/themes/midnight-in-mojave.toml +1 -1
- flock/themes/monokai-remastered.toml +1 -1
- flock/themes/monokai-soda.toml +1 -1
- flock/themes/neon.toml +1 -1
- flock/themes/neopolitan.toml +1 -1
- flock/themes/nord-light.toml +1 -1
- flock/themes/ocean.toml +1 -1
- flock/themes/onehalfdark.toml +1 -1
- flock/themes/onehalflight.toml +1 -1
- flock/themes/palenighthc.toml +1 -1
- flock/themes/paulmillr.toml +1 -1
- flock/themes/pencildark.toml +1 -1
- flock/themes/pnevma.toml +1 -1
- flock/themes/purple-rain.toml +1 -1
- flock/themes/purplepeter.toml +1 -1
- flock/themes/raycast-dark.toml +1 -1
- flock/themes/red-sands.toml +1 -1
- flock/themes/relaxed.toml +1 -1
- flock/themes/retro.toml +1 -1
- flock/themes/rose-pine.toml +1 -1
- flock/themes/royal.toml +1 -1
- flock/themes/ryuuko.toml +1 -1
- flock/themes/sakura.toml +1 -1
- flock/themes/scarlet-protocol.toml +1 -1
- flock/themes/seoulbones-dark.toml +1 -1
- flock/themes/shades-of-purple.toml +1 -1
- flock/themes/smyck.toml +1 -1
- flock/themes/softserver.toml +1 -1
- flock/themes/solarized-darcula.toml +1 -1
- flock/themes/square.toml +1 -1
- flock/themes/sugarplum.toml +1 -1
- flock/themes/thayer-bright.toml +1 -1
- flock/themes/tokyonight.toml +1 -1
- flock/themes/tomorrow.toml +1 -1
- flock/themes/ubuntu.toml +1 -1
- flock/themes/ultradark.toml +1 -1
- flock/themes/ultraviolent.toml +1 -1
- flock/themes/unikitty.toml +1 -1
- flock/themes/urple.toml +1 -1
- flock/themes/vesper.toml +1 -1
- flock/themes/vimbones.toml +1 -1
- flock/themes/wildcherry.toml +1 -1
- flock/themes/wilmersdorf.toml +1 -1
- flock/themes/wryan.toml +1 -1
- flock/themes/xcodedarkhc.toml +1 -1
- flock/themes/xcodelight.toml +1 -1
- flock/themes/zenbones-light.toml +1 -1
- flock/themes/zenwritten-dark.toml +1 -1
- flock/utilities.py +301 -0
- flock/{components/utility → utility}/output_utility_component.py +68 -53
- flock/visibility.py +107 -0
- flock_core-0.5.56b0.dist-info/METADATA +747 -0
- flock_core-0.5.56b0.dist-info/RECORD +398 -0
- flock_core-0.5.56b0.dist-info/entry_points.txt +2 -0
- {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.dist-info}/licenses/LICENSE +1 -1
- flock/adapter/__init__.py +0 -14
- flock/adapter/azure_adapter.py +0 -68
- flock/adapter/chroma_adapter.py +0 -73
- flock/adapter/faiss_adapter.py +0 -97
- flock/adapter/pinecone_adapter.py +0 -51
- flock/adapter/vector_base.py +0 -47
- flock/cli/assets/release_notes.md +0 -140
- flock/cli/config.py +0 -8
- flock/cli/constants.py +0 -36
- flock/cli/create_agent.py +0 -1
- flock/cli/create_flock.py +0 -280
- flock/cli/execute_flock.py +0 -620
- flock/cli/load_agent.py +0 -1
- flock/cli/load_examples.py +0 -1
- flock/cli/load_flock.py +0 -192
- flock/cli/load_release_notes.py +0 -20
- flock/cli/loaded_flock_cli.py +0 -254
- flock/cli/manage_agents.py +0 -459
- flock/cli/registry_management.py +0 -889
- flock/cli/runner.py +0 -41
- flock/cli/settings.py +0 -857
- flock/cli/utils.py +0 -135
- flock/cli/view_results.py +0 -29
- flock/cli/yaml_editor.py +0 -396
- flock/components/__init__.py +0 -30
- flock/components/evaluation/__init__.py +0 -9
- flock/components/evaluation/declarative_evaluation_component.py +0 -606
- flock/components/routing/__init__.py +0 -15
- flock/components/routing/conditional_routing_component.py +0 -494
- flock/components/routing/default_routing_component.py +0 -103
- flock/components/routing/llm_routing_component.py +0 -206
- flock/components/utility/__init__.py +0 -22
- flock/components/utility/example_utility_component.py +0 -250
- flock/components/utility/feedback_utility_component.py +0 -206
- flock/components/utility/memory_utility_component.py +0 -550
- flock/components/utility/metrics_utility_component.py +0 -700
- flock/config.py +0 -61
- flock/core/__init__.py +0 -110
- flock/core/agent/__init__.py +0 -16
- flock/core/agent/default_agent.py +0 -216
- flock/core/agent/flock_agent_components.py +0 -104
- flock/core/agent/flock_agent_execution.py +0 -101
- flock/core/agent/flock_agent_integration.py +0 -260
- flock/core/agent/flock_agent_lifecycle.py +0 -186
- flock/core/agent/flock_agent_serialization.py +0 -381
- flock/core/api/__init__.py +0 -10
- flock/core/api/custom_endpoint.py +0 -45
- flock/core/api/endpoints.py +0 -254
- flock/core/api/main.py +0 -162
- flock/core/api/models.py +0 -97
- flock/core/api/run_store.py +0 -224
- flock/core/api/runner.py +0 -44
- flock/core/api/service.py +0 -214
- flock/core/component/__init__.py +0 -15
- flock/core/component/agent_component_base.py +0 -309
- flock/core/component/evaluation_component.py +0 -62
- flock/core/component/routing_component.py +0 -74
- flock/core/component/utility_component.py +0 -69
- flock/core/config/flock_agent_config.py +0 -58
- flock/core/config/scheduled_agent_config.py +0 -40
- flock/core/context/context.py +0 -213
- flock/core/context/context_manager.py +0 -37
- flock/core/context/context_vars.py +0 -10
- flock/core/evaluation/utils.py +0 -396
- flock/core/execution/batch_executor.py +0 -369
- flock/core/execution/evaluation_executor.py +0 -438
- flock/core/execution/local_executor.py +0 -31
- flock/core/execution/opik_executor.py +0 -103
- flock/core/execution/temporal_executor.py +0 -164
- flock/core/flock.py +0 -634
- flock/core/flock_agent.py +0 -336
- flock/core/flock_factory.py +0 -613
- flock/core/flock_scheduler.py +0 -166
- flock/core/flock_server_manager.py +0 -136
- flock/core/interpreter/python_interpreter.py +0 -689
- flock/core/mcp/__init__.py +0 -1
- flock/core/mcp/flock_mcp_server.py +0 -680
- flock/core/mcp/mcp_client_manager.py +0 -201
- flock/core/mcp/types/__init__.py +0 -1
- flock/core/mixin/dspy_integration.py +0 -403
- flock/core/mixin/prompt_parser.py +0 -125
- flock/core/orchestration/__init__.py +0 -15
- flock/core/orchestration/flock_batch_processor.py +0 -94
- flock/core/orchestration/flock_evaluator.py +0 -113
- flock/core/orchestration/flock_execution.py +0 -295
- flock/core/orchestration/flock_initialization.py +0 -149
- flock/core/orchestration/flock_server_manager.py +0 -67
- flock/core/orchestration/flock_web_server.py +0 -117
- flock/core/registry/__init__.py +0 -45
- flock/core/registry/agent_registry.py +0 -69
- flock/core/registry/callable_registry.py +0 -139
- flock/core/registry/component_discovery.py +0 -142
- flock/core/registry/component_registry.py +0 -64
- flock/core/registry/config_mapping.py +0 -64
- flock/core/registry/decorators.py +0 -137
- flock/core/registry/registry_hub.py +0 -205
- flock/core/registry/server_registry.py +0 -57
- flock/core/registry/type_registry.py +0 -86
- flock/core/serialization/__init__.py +0 -13
- flock/core/serialization/callable_registry.py +0 -52
- flock/core/serialization/flock_serializer.py +0 -832
- flock/core/serialization/json_encoder.py +0 -41
- flock/core/serialization/secure_serializer.py +0 -175
- flock/core/serialization/serializable.py +0 -342
- flock/core/serialization/serialization_utils.py +0 -412
- flock/core/util/file_path_utils.py +0 -223
- flock/core/util/hydrator.py +0 -309
- flock/core/util/input_resolver.py +0 -164
- flock/core/util/loader.py +0 -59
- flock/core/util/splitter.py +0 -219
- flock/di.py +0 -27
- flock/platform/docker_tools.py +0 -49
- flock/platform/jaeger_install.py +0 -86
- flock/webapp/__init__.py +0 -1
- flock/webapp/app/__init__.py +0 -0
- flock/webapp/app/api/__init__.py +0 -0
- flock/webapp/app/api/agent_management.py +0 -241
- flock/webapp/app/api/execution.py +0 -709
- flock/webapp/app/api/flock_management.py +0 -129
- flock/webapp/app/api/registry_viewer.py +0 -30
- flock/webapp/app/chat.py +0 -665
- flock/webapp/app/config.py +0 -104
- flock/webapp/app/dependencies.py +0 -117
- flock/webapp/app/main.py +0 -1070
- flock/webapp/app/middleware.py +0 -113
- flock/webapp/app/models_ui.py +0 -7
- flock/webapp/app/services/__init__.py +0 -0
- flock/webapp/app/services/feedback_file_service.py +0 -363
- flock/webapp/app/services/flock_service.py +0 -337
- flock/webapp/app/services/sharing_models.py +0 -81
- flock/webapp/app/services/sharing_store.py +0 -762
- flock/webapp/app/templates/theme_mapper.html +0 -326
- flock/webapp/app/theme_mapper.py +0 -812
- flock/webapp/app/utils.py +0 -85
- flock/webapp/run.py +0 -215
- flock/webapp/static/css/chat.css +0 -301
- flock/webapp/static/css/components.css +0 -167
- flock/webapp/static/css/header.css +0 -39
- flock/webapp/static/css/layout.css +0 -46
- flock/webapp/static/css/sidebar.css +0 -127
- flock/webapp/static/css/two-pane.css +0 -48
- flock/webapp/templates/base.html +0 -200
- flock/webapp/templates/chat.html +0 -152
- flock/webapp/templates/chat_settings.html +0 -19
- flock/webapp/templates/flock_editor.html +0 -16
- flock/webapp/templates/index.html +0 -12
- flock/webapp/templates/partials/_agent_detail_form.html +0 -93
- flock/webapp/templates/partials/_agent_list.html +0 -18
- flock/webapp/templates/partials/_agent_manager_view.html +0 -51
- flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
- flock/webapp/templates/partials/_chat_container.html +0 -15
- flock/webapp/templates/partials/_chat_messages.html +0 -57
- flock/webapp/templates/partials/_chat_settings_form.html +0 -85
- flock/webapp/templates/partials/_create_flock_form.html +0 -50
- flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
- flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
- flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
- flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
- flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
- flock/webapp/templates/partials/_env_vars_table.html +0 -23
- flock/webapp/templates/partials/_execution_form.html +0 -118
- flock/webapp/templates/partials/_execution_view_container.html +0 -28
- flock/webapp/templates/partials/_flock_file_list.html +0 -23
- flock/webapp/templates/partials/_flock_properties_form.html +0 -52
- flock/webapp/templates/partials/_flock_upload_form.html +0 -16
- flock/webapp/templates/partials/_header_flock_status.html +0 -5
- flock/webapp/templates/partials/_load_manager_view.html +0 -49
- flock/webapp/templates/partials/_registry_table.html +0 -25
- flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
- flock/webapp/templates/partials/_results_display.html +0 -78
- flock/webapp/templates/partials/_settings_env_content.html +0 -9
- flock/webapp/templates/partials/_settings_theme_content.html +0 -14
- flock/webapp/templates/partials/_settings_view.html +0 -36
- flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
- flock/webapp/templates/partials/_share_link_snippet.html +0 -35
- flock/webapp/templates/partials/_sidebar.html +0 -74
- flock/webapp/templates/partials/_streaming_results_container.html +0 -195
- flock/webapp/templates/partials/_structured_data_view.html +0 -40
- flock/webapp/templates/partials/_theme_preview.html +0 -36
- flock/webapp/templates/registry_viewer.html +0 -84
- flock/webapp/templates/shared_run_page.html +0 -140
- flock/workflow/__init__.py +0 -0
- flock/workflow/activities.py +0 -196
- flock/workflow/agent_activities.py +0 -24
- flock/workflow/agent_execution_activity.py +0 -202
- flock/workflow/flock_workflow.py +0 -214
- flock/workflow/temporal_config.py +0 -96
- flock/workflow/temporal_setup.py +0 -68
- flock_core-0.5.0b28.dist-info/METADATA +0 -274
- flock_core-0.5.0b28.dist-info/RECORD +0 -561
- flock_core-0.5.0b28.dist-info/entry_points.txt +0 -2
- /flock/{core/logging → logging}/formatters/themes.py +0 -0
- /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
- /flock/{core/mcp → mcp}/util/__init__.py +0 -0
- {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
"""DashboardHTTPService - extends BlackboardHTTPService with WebSocket support.
|
|
2
|
+
|
|
3
|
+
Provides real-time dashboard capabilities by:
|
|
4
|
+
1. Mounting WebSocket endpoint at /ws
|
|
5
|
+
2. Serving static files for dashboard frontend
|
|
6
|
+
3. Integrating DashboardEventCollector with WebSocketManager
|
|
7
|
+
4. Supporting CORS for development mode (DASHBOARD_DEV=1)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
from uuid import uuid4
|
|
15
|
+
|
|
16
|
+
from fastapi import HTTPException, WebSocket, WebSocketDisconnect
|
|
17
|
+
from fastapi.middleware.cors import CORSMiddleware
|
|
18
|
+
from fastapi.staticfiles import StaticFiles
|
|
19
|
+
from pydantic import ValidationError
|
|
20
|
+
|
|
21
|
+
from flock.dashboard.collector import DashboardEventCollector
|
|
22
|
+
from flock.dashboard.events import MessagePublishedEvent, VisibilitySpec
|
|
23
|
+
from flock.dashboard.websocket import WebSocketManager
|
|
24
|
+
from flock.logging.logging import get_logger
|
|
25
|
+
from flock.orchestrator import Flock
|
|
26
|
+
from flock.registry import type_registry
|
|
27
|
+
from flock.service import BlackboardHTTPService
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
logger = get_logger("dashboard.service")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class DashboardHTTPService(BlackboardHTTPService):
|
|
34
|
+
"""HTTP service with WebSocket support for real-time dashboard.
|
|
35
|
+
|
|
36
|
+
Extends BlackboardHTTPService to add:
|
|
37
|
+
- WebSocket endpoint at /ws for real-time event streaming
|
|
38
|
+
- Static file serving for dashboard frontend
|
|
39
|
+
- Integration with DashboardEventCollector
|
|
40
|
+
- Optional CORS middleware for development
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
orchestrator: Flock,
|
|
46
|
+
websocket_manager: WebSocketManager | None = None,
|
|
47
|
+
event_collector: DashboardEventCollector | None = None,
|
|
48
|
+
) -> None:
|
|
49
|
+
"""Initialize DashboardHTTPService.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
orchestrator: Flock orchestrator instance
|
|
53
|
+
websocket_manager: Optional WebSocketManager (creates new if not provided)
|
|
54
|
+
event_collector: Optional DashboardEventCollector (creates new if not provided)
|
|
55
|
+
"""
|
|
56
|
+
# Initialize base service
|
|
57
|
+
super().__init__(orchestrator)
|
|
58
|
+
|
|
59
|
+
# Initialize WebSocket manager and event collector
|
|
60
|
+
self.websocket_manager = websocket_manager or WebSocketManager()
|
|
61
|
+
self.event_collector = event_collector or DashboardEventCollector()
|
|
62
|
+
|
|
63
|
+
# Integrate collector with WebSocket manager
|
|
64
|
+
self.event_collector.set_websocket_manager(self.websocket_manager)
|
|
65
|
+
|
|
66
|
+
# Configure CORS if DASHBOARD_DEV environment variable is set
|
|
67
|
+
if os.environ.get("DASHBOARD_DEV") == "1":
|
|
68
|
+
logger.info("DASHBOARD_DEV mode enabled - adding CORS middleware")
|
|
69
|
+
self.app.add_middleware(
|
|
70
|
+
CORSMiddleware,
|
|
71
|
+
allow_origins=["*"], # Allow all origins in dev mode
|
|
72
|
+
allow_credentials=True,
|
|
73
|
+
allow_methods=["*"],
|
|
74
|
+
allow_headers=["*"],
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# IMPORTANT: Register API routes BEFORE static files!
|
|
78
|
+
# Static file mount acts as catch-all and must be last
|
|
79
|
+
self._register_control_routes()
|
|
80
|
+
self._register_theme_routes()
|
|
81
|
+
self._register_dashboard_routes()
|
|
82
|
+
|
|
83
|
+
logger.info("DashboardHTTPService initialized")
|
|
84
|
+
|
|
85
|
+
def _register_dashboard_routes(self) -> None:
|
|
86
|
+
"""Register WebSocket endpoint and static file serving."""
|
|
87
|
+
app = self.app
|
|
88
|
+
|
|
89
|
+
@app.websocket("/ws")
|
|
90
|
+
async def websocket_endpoint(websocket: WebSocket) -> None:
|
|
91
|
+
"""WebSocket endpoint for real-time dashboard events.
|
|
92
|
+
|
|
93
|
+
Handles connection lifecycle:
|
|
94
|
+
1. Accept connection
|
|
95
|
+
2. Add to WebSocketManager pool
|
|
96
|
+
3. Keep connection alive
|
|
97
|
+
4. Handle disconnection gracefully
|
|
98
|
+
"""
|
|
99
|
+
await websocket.accept()
|
|
100
|
+
await self.websocket_manager.add_client(websocket)
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
# Keep connection alive and handle incoming messages
|
|
104
|
+
# Dashboard clients may send heartbeat responses or control messages
|
|
105
|
+
while True:
|
|
106
|
+
# Wait for messages from client (pong responses, etc.)
|
|
107
|
+
try:
|
|
108
|
+
data = await websocket.receive_text()
|
|
109
|
+
# Handle client messages if needed (e.g., pong responses)
|
|
110
|
+
# For Phase 3, we primarily broadcast from server to client
|
|
111
|
+
logger.debug(f"Received message from client: {data[:100]}")
|
|
112
|
+
except WebSocketDisconnect:
|
|
113
|
+
logger.info("WebSocket client disconnected")
|
|
114
|
+
break
|
|
115
|
+
except Exception as e:
|
|
116
|
+
logger.warning(f"Error receiving WebSocket message: {e}")
|
|
117
|
+
break
|
|
118
|
+
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logger.exception(f"WebSocket endpoint error: {e}")
|
|
121
|
+
finally:
|
|
122
|
+
# Clean up: remove client from pool
|
|
123
|
+
await self.websocket_manager.remove_client(websocket)
|
|
124
|
+
|
|
125
|
+
# Serve static files for dashboard frontend
|
|
126
|
+
# Look for static files in dashboard directory
|
|
127
|
+
dashboard_dir = Path(__file__).parent
|
|
128
|
+
static_dir = dashboard_dir / "static"
|
|
129
|
+
|
|
130
|
+
# Also check for 'dist' or 'build' directories (common build output names)
|
|
131
|
+
possible_dirs = [static_dir, dashboard_dir / "dist", dashboard_dir / "build"]
|
|
132
|
+
|
|
133
|
+
for dir_path in possible_dirs:
|
|
134
|
+
if dir_path.exists() and dir_path.is_dir():
|
|
135
|
+
logger.info(f"Mounting static files from: {dir_path}")
|
|
136
|
+
# Mount at root to serve index.html and other frontend assets
|
|
137
|
+
app.mount(
|
|
138
|
+
"/",
|
|
139
|
+
StaticFiles(directory=str(dir_path), html=True),
|
|
140
|
+
name="dashboard-static",
|
|
141
|
+
)
|
|
142
|
+
break
|
|
143
|
+
else:
|
|
144
|
+
logger.warning(
|
|
145
|
+
f"No static directory found in {dashboard_dir}. "
|
|
146
|
+
"Dashboard frontend will not be served. "
|
|
147
|
+
"Expected directories: static/, dist/, or build/"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
def _register_control_routes(self) -> None:
|
|
151
|
+
"""Register control API endpoints for dashboard operations."""
|
|
152
|
+
app = self.app
|
|
153
|
+
orchestrator = self.orchestrator
|
|
154
|
+
|
|
155
|
+
@app.get("/api/artifact-types")
|
|
156
|
+
async def get_artifact_types() -> dict[str, Any]:
|
|
157
|
+
"""Get all registered artifact types with their schemas.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
{
|
|
161
|
+
"artifact_types": [
|
|
162
|
+
{
|
|
163
|
+
"name": "TypeName",
|
|
164
|
+
"schema": {...}
|
|
165
|
+
},
|
|
166
|
+
...
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
"""
|
|
170
|
+
artifact_types = []
|
|
171
|
+
|
|
172
|
+
for type_name in type_registry._by_name:
|
|
173
|
+
try:
|
|
174
|
+
model_class = type_registry.resolve(type_name)
|
|
175
|
+
# Get Pydantic schema
|
|
176
|
+
schema = model_class.model_json_schema()
|
|
177
|
+
artifact_types.append({"name": type_name, "schema": schema})
|
|
178
|
+
except Exception as e:
|
|
179
|
+
logger.warning(f"Could not get schema for {type_name}: {e}")
|
|
180
|
+
|
|
181
|
+
return {"artifact_types": artifact_types}
|
|
182
|
+
|
|
183
|
+
@app.get("/api/agents")
|
|
184
|
+
async def get_agents() -> dict[str, Any]:
|
|
185
|
+
"""Get all registered agents.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
{
|
|
189
|
+
"agents": [
|
|
190
|
+
{
|
|
191
|
+
"name": "agent_name",
|
|
192
|
+
"description": "...",
|
|
193
|
+
"status": "ready",
|
|
194
|
+
"subscriptions": ["TypeA", "TypeB"],
|
|
195
|
+
"output_types": ["TypeC", "TypeD"]
|
|
196
|
+
},
|
|
197
|
+
...
|
|
198
|
+
]
|
|
199
|
+
}
|
|
200
|
+
"""
|
|
201
|
+
agents = []
|
|
202
|
+
|
|
203
|
+
for agent in orchestrator.agents:
|
|
204
|
+
# Extract consumed types from agent subscriptions
|
|
205
|
+
consumed_types = []
|
|
206
|
+
for sub in agent.subscriptions:
|
|
207
|
+
consumed_types.extend(sub.type_names)
|
|
208
|
+
|
|
209
|
+
# Extract produced types from agent outputs
|
|
210
|
+
produced_types = [output.spec.type_name for output in agent.outputs]
|
|
211
|
+
|
|
212
|
+
agents.append(
|
|
213
|
+
{
|
|
214
|
+
"name": agent.name,
|
|
215
|
+
"description": agent.description or "",
|
|
216
|
+
"status": "ready",
|
|
217
|
+
"subscriptions": consumed_types,
|
|
218
|
+
"output_types": produced_types,
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
return {"agents": agents}
|
|
223
|
+
|
|
224
|
+
@app.get("/api/version")
|
|
225
|
+
async def get_version() -> dict[str, str]:
|
|
226
|
+
"""Get version information for the backend and dashboard.
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
{
|
|
230
|
+
"backend_version": "0.1.18",
|
|
231
|
+
"package_name": "flock-flow"
|
|
232
|
+
}
|
|
233
|
+
"""
|
|
234
|
+
try:
|
|
235
|
+
backend_version = version("flock-flow")
|
|
236
|
+
except PackageNotFoundError:
|
|
237
|
+
# Fallback version if package not installed
|
|
238
|
+
backend_version = "0.2.0-dev"
|
|
239
|
+
|
|
240
|
+
return {"backend_version": backend_version, "package_name": "flock-flow"}
|
|
241
|
+
|
|
242
|
+
@app.post("/api/control/publish")
|
|
243
|
+
async def publish_artifact(body: dict[str, Any]) -> dict[str, str]:
|
|
244
|
+
"""Publish artifact with correlation tracking.
|
|
245
|
+
|
|
246
|
+
Request body:
|
|
247
|
+
{
|
|
248
|
+
"artifact_type": "TypeName",
|
|
249
|
+
"content": {"field": "value", ...}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
{
|
|
254
|
+
"correlation_id": "<uuid>",
|
|
255
|
+
"published_at": "<iso-timestamp>"
|
|
256
|
+
}
|
|
257
|
+
"""
|
|
258
|
+
# Validate required fields
|
|
259
|
+
artifact_type = body.get("artifact_type")
|
|
260
|
+
content = body.get("content")
|
|
261
|
+
|
|
262
|
+
if not artifact_type:
|
|
263
|
+
raise HTTPException(status_code=400, detail="artifact_type is required")
|
|
264
|
+
if content is None:
|
|
265
|
+
raise HTTPException(status_code=400, detail="content is required")
|
|
266
|
+
|
|
267
|
+
try:
|
|
268
|
+
# Resolve type from registry
|
|
269
|
+
model_class = type_registry.resolve(artifact_type)
|
|
270
|
+
|
|
271
|
+
# Validate content against Pydantic schema
|
|
272
|
+
try:
|
|
273
|
+
instance = model_class(**content)
|
|
274
|
+
except ValidationError as e:
|
|
275
|
+
raise HTTPException(status_code=422, detail=f"Validation error: {e!s}")
|
|
276
|
+
|
|
277
|
+
# Generate correlation ID
|
|
278
|
+
correlation_id = str(uuid4())
|
|
279
|
+
|
|
280
|
+
# Publish to orchestrator
|
|
281
|
+
artifact = await orchestrator.publish(
|
|
282
|
+
instance, correlation_id=correlation_id, is_dashboard=True
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
# Phase 11 Fix: Emit message_published event for dashboard visibility
|
|
286
|
+
# This enables virtual "orchestrator" agent to appear in both Agent View and Blackboard View
|
|
287
|
+
event = MessagePublishedEvent(
|
|
288
|
+
correlation_id=str(artifact.correlation_id),
|
|
289
|
+
artifact_id=str(artifact.id),
|
|
290
|
+
artifact_type=artifact.type,
|
|
291
|
+
produced_by=artifact.produced_by, # Will be "orchestrator" or similar for non-agent publishers
|
|
292
|
+
payload=artifact.payload,
|
|
293
|
+
visibility=VisibilitySpec(
|
|
294
|
+
kind="Public"
|
|
295
|
+
), # Dashboard-published artifacts are public by default
|
|
296
|
+
tags=list(artifact.tags) if artifact.tags else [],
|
|
297
|
+
partition_key=artifact.partition_key,
|
|
298
|
+
version=artifact.version,
|
|
299
|
+
consumers=[], # Will be populated by subscription matching in frontend
|
|
300
|
+
)
|
|
301
|
+
await self.websocket_manager.broadcast(event)
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
"correlation_id": str(artifact.correlation_id),
|
|
305
|
+
"published_at": artifact.created_at.isoformat(),
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
except KeyError:
|
|
309
|
+
raise HTTPException(
|
|
310
|
+
status_code=422, detail=f"Unknown artifact type: {artifact_type}"
|
|
311
|
+
)
|
|
312
|
+
except Exception as e:
|
|
313
|
+
logger.exception(f"Error publishing artifact: {e}")
|
|
314
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
315
|
+
|
|
316
|
+
@app.post("/api/control/invoke")
|
|
317
|
+
async def invoke_agent(body: dict[str, Any]) -> dict[str, Any]:
|
|
318
|
+
"""Directly invoke a specific agent.
|
|
319
|
+
|
|
320
|
+
Request body:
|
|
321
|
+
{
|
|
322
|
+
"agent_name": "agent_name",
|
|
323
|
+
"input": {"type": "TypeName", "field": "value", ...}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
{
|
|
328
|
+
"invocation_id": "<uuid>",
|
|
329
|
+
"result": "success"
|
|
330
|
+
}
|
|
331
|
+
"""
|
|
332
|
+
# Validate required fields
|
|
333
|
+
agent_name = body.get("agent_name")
|
|
334
|
+
input_data = body.get("input")
|
|
335
|
+
|
|
336
|
+
if not agent_name:
|
|
337
|
+
raise HTTPException(status_code=400, detail="agent_name is required")
|
|
338
|
+
if input_data is None:
|
|
339
|
+
raise HTTPException(status_code=400, detail="input is required")
|
|
340
|
+
|
|
341
|
+
try:
|
|
342
|
+
# Get agent from orchestrator
|
|
343
|
+
agent = orchestrator.get_agent(agent_name)
|
|
344
|
+
except KeyError:
|
|
345
|
+
raise HTTPException(status_code=404, detail=f"Agent not found: {agent_name}")
|
|
346
|
+
|
|
347
|
+
try:
|
|
348
|
+
# Parse input type and create instance
|
|
349
|
+
input_type = input_data.get("type")
|
|
350
|
+
if not input_type:
|
|
351
|
+
raise HTTPException(status_code=400, detail="input.type is required")
|
|
352
|
+
|
|
353
|
+
# Resolve type from registry
|
|
354
|
+
model_class = type_registry.resolve(input_type)
|
|
355
|
+
|
|
356
|
+
# Create payload by removing 'type' key
|
|
357
|
+
payload = {k: v for k, v in input_data.items() if k != "type"}
|
|
358
|
+
|
|
359
|
+
# Validate and create instance
|
|
360
|
+
try:
|
|
361
|
+
instance = model_class(**payload)
|
|
362
|
+
except ValidationError as e:
|
|
363
|
+
raise HTTPException(status_code=422, detail=f"Validation error: {e!s}")
|
|
364
|
+
|
|
365
|
+
# Invoke agent
|
|
366
|
+
outputs = await orchestrator.invoke(agent, instance)
|
|
367
|
+
|
|
368
|
+
# Generate invocation ID from first output or create new UUID
|
|
369
|
+
invocation_id = str(outputs[0].id) if outputs else str(uuid4())
|
|
370
|
+
|
|
371
|
+
# Extract correlation_id from first output (for filter automation)
|
|
372
|
+
correlation_id = (
|
|
373
|
+
str(outputs[0].correlation_id)
|
|
374
|
+
if outputs and outputs[0].correlation_id
|
|
375
|
+
else None
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
"invocation_id": invocation_id,
|
|
380
|
+
"correlation_id": correlation_id,
|
|
381
|
+
"result": "success",
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
except HTTPException:
|
|
385
|
+
raise
|
|
386
|
+
except KeyError:
|
|
387
|
+
raise HTTPException(status_code=422, detail=f"Unknown type: {input_type}")
|
|
388
|
+
except Exception as e:
|
|
389
|
+
logger.exception(f"Error invoking agent: {e}")
|
|
390
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
391
|
+
|
|
392
|
+
@app.post("/api/control/pause")
|
|
393
|
+
async def pause_orchestrator() -> dict[str, Any]:
|
|
394
|
+
"""Pause orchestrator (placeholder).
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
501 Not Implemented
|
|
398
|
+
"""
|
|
399
|
+
raise HTTPException(status_code=501, detail="Pause functionality coming in Phase 12")
|
|
400
|
+
|
|
401
|
+
@app.post("/api/control/resume")
|
|
402
|
+
async def resume_orchestrator() -> dict[str, Any]:
|
|
403
|
+
"""Resume orchestrator (placeholder).
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
501 Not Implemented
|
|
407
|
+
"""
|
|
408
|
+
raise HTTPException(status_code=501, detail="Resume functionality coming in Phase 12")
|
|
409
|
+
|
|
410
|
+
@app.get("/api/streaming-history/{agent_name}")
|
|
411
|
+
async def get_streaming_history(agent_name: str) -> dict[str, Any]:
|
|
412
|
+
"""Get historical streaming output for a specific agent.
|
|
413
|
+
|
|
414
|
+
Args:
|
|
415
|
+
agent_name: Name of the agent to get streaming history for
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
{
|
|
419
|
+
"agent_name": "agent_name",
|
|
420
|
+
"events": [
|
|
421
|
+
{
|
|
422
|
+
"correlation_id": "...",
|
|
423
|
+
"timestamp": "...",
|
|
424
|
+
"agent_name": "...",
|
|
425
|
+
"run_id": "...",
|
|
426
|
+
"output_type": "llm_token",
|
|
427
|
+
"content": "...",
|
|
428
|
+
"sequence": 0,
|
|
429
|
+
"is_final": false
|
|
430
|
+
},
|
|
431
|
+
...
|
|
432
|
+
]
|
|
433
|
+
}
|
|
434
|
+
"""
|
|
435
|
+
try:
|
|
436
|
+
history = self.websocket_manager.get_streaming_history(agent_name)
|
|
437
|
+
return {
|
|
438
|
+
"agent_name": agent_name,
|
|
439
|
+
"events": [event.model_dump() for event in history],
|
|
440
|
+
}
|
|
441
|
+
except Exception as e:
|
|
442
|
+
logger.exception(f"Failed to get streaming history for {agent_name}: {e}")
|
|
443
|
+
raise HTTPException(
|
|
444
|
+
status_code=500, detail=f"Failed to get streaming history: {e!s}"
|
|
445
|
+
)
|
|
446
|
+
|
|
447
|
+
def _register_theme_routes(self) -> None:
|
|
448
|
+
"""Register theme API endpoints for dashboard customization."""
|
|
449
|
+
from pathlib import Path
|
|
450
|
+
|
|
451
|
+
import toml
|
|
452
|
+
|
|
453
|
+
app = self.app
|
|
454
|
+
themes_dir = Path(__file__).parent.parent / "themes"
|
|
455
|
+
|
|
456
|
+
@app.get("/api/themes")
|
|
457
|
+
async def list_themes() -> dict[str, Any]:
|
|
458
|
+
"""Get list of available theme names.
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
{"themes": ["dracula", "nord", ...]}
|
|
462
|
+
"""
|
|
463
|
+
try:
|
|
464
|
+
if not themes_dir.exists():
|
|
465
|
+
return {"themes": []}
|
|
466
|
+
|
|
467
|
+
theme_files = list(themes_dir.glob("*.toml"))
|
|
468
|
+
theme_names = sorted([f.stem for f in theme_files])
|
|
469
|
+
|
|
470
|
+
return {"themes": theme_names}
|
|
471
|
+
except Exception as e:
|
|
472
|
+
logger.exception(f"Failed to list themes: {e}")
|
|
473
|
+
raise HTTPException(status_code=500, detail=f"Failed to list themes: {e!s}")
|
|
474
|
+
|
|
475
|
+
@app.get("/api/themes/{theme_name}")
|
|
476
|
+
async def get_theme(theme_name: str) -> dict[str, Any]:
|
|
477
|
+
"""Get theme data by name.
|
|
478
|
+
|
|
479
|
+
Args:
|
|
480
|
+
theme_name: Name of theme (without .toml extension)
|
|
481
|
+
|
|
482
|
+
Returns:
|
|
483
|
+
{
|
|
484
|
+
"name": "dracula",
|
|
485
|
+
"data": {
|
|
486
|
+
"colors": {...}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
"""
|
|
490
|
+
try:
|
|
491
|
+
# Sanitize theme name to prevent path traversal
|
|
492
|
+
theme_name = theme_name.replace("/", "").replace("\\", "").replace("..", "")
|
|
493
|
+
|
|
494
|
+
theme_path = themes_dir / f"{theme_name}.toml"
|
|
495
|
+
|
|
496
|
+
if not theme_path.exists():
|
|
497
|
+
raise HTTPException(status_code=404, detail=f"Theme '{theme_name}' not found")
|
|
498
|
+
|
|
499
|
+
# Load TOML theme
|
|
500
|
+
theme_data = toml.load(theme_path)
|
|
501
|
+
|
|
502
|
+
return {"name": theme_name, "data": theme_data}
|
|
503
|
+
except HTTPException:
|
|
504
|
+
raise
|
|
505
|
+
except Exception as e:
|
|
506
|
+
logger.exception(f"Failed to load theme '{theme_name}': {e}")
|
|
507
|
+
raise HTTPException(status_code=500, detail=f"Failed to load theme: {e!s}")
|
|
508
|
+
|
|
509
|
+
async def start(self) -> None:
|
|
510
|
+
"""Start the dashboard service.
|
|
511
|
+
|
|
512
|
+
Note: For testing purposes. In production, use uvicorn.run(app).
|
|
513
|
+
"""
|
|
514
|
+
logger.info("DashboardHTTPService started")
|
|
515
|
+
# Start heartbeat if there are clients
|
|
516
|
+
if len(self.websocket_manager.clients) > 0:
|
|
517
|
+
await self.websocket_manager.start_heartbeat()
|
|
518
|
+
|
|
519
|
+
async def stop(self) -> None:
|
|
520
|
+
"""Stop the dashboard service and clean up resources.
|
|
521
|
+
|
|
522
|
+
Closes all WebSocket connections gracefully.
|
|
523
|
+
"""
|
|
524
|
+
logger.info("Stopping DashboardHTTPService")
|
|
525
|
+
await self.websocket_manager.shutdown()
|
|
526
|
+
logger.info("DashboardHTTPService stopped")
|
|
527
|
+
|
|
528
|
+
def get_app(self):
|
|
529
|
+
"""Get FastAPI application instance.
|
|
530
|
+
|
|
531
|
+
Returns:
|
|
532
|
+
FastAPI app for testing or custom server setup
|
|
533
|
+
"""
|
|
534
|
+
return self.app
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
__all__ = ["DashboardHTTPService"]
|