flock-core 0.5.0b27__py3-none-any.whl → 0.5.0b50__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.

Files changed (357) hide show
  1. flock/__init__.py +12 -217
  2. flock/agent.py +678 -0
  3. flock/api/themes.py +71 -0
  4. flock/artifacts.py +79 -0
  5. flock/cli.py +75 -0
  6. flock/components.py +173 -0
  7. flock/dashboard/__init__.py +28 -0
  8. flock/dashboard/collector.py +283 -0
  9. flock/dashboard/events.py +182 -0
  10. flock/dashboard/launcher.py +230 -0
  11. flock/dashboard/service.py +537 -0
  12. flock/dashboard/websocket.py +235 -0
  13. flock/engines/__init__.py +6 -0
  14. flock/engines/dspy_engine.py +856 -0
  15. flock/examples.py +128 -0
  16. flock/{core/util → helper}/cli_helper.py +4 -3
  17. flock/{core/logging → logging}/__init__.py +2 -3
  18. flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
  19. flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
  20. flock/{core/logging → logging}/formatters/themed_formatter.py +69 -115
  21. flock/{core/logging → logging}/logging.py +77 -61
  22. flock/{core/logging → logging}/telemetry.py +20 -26
  23. flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
  24. flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +6 -9
  25. flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
  26. flock/{core/logging → logging}/trace_and_logged.py +20 -24
  27. flock/mcp/__init__.py +91 -0
  28. flock/{core/mcp/mcp_client.py → mcp/client.py} +103 -154
  29. flock/{core/mcp/mcp_config.py → mcp/config.py} +62 -117
  30. flock/mcp/manager.py +255 -0
  31. flock/mcp/servers/sse/__init__.py +1 -1
  32. flock/mcp/servers/sse/flock_sse_server.py +11 -53
  33. flock/mcp/servers/stdio/__init__.py +1 -1
  34. flock/mcp/servers/stdio/flock_stdio_server.py +8 -48
  35. flock/mcp/servers/streamable_http/flock_streamable_http_server.py +17 -62
  36. flock/mcp/servers/websockets/flock_websocket_server.py +7 -40
  37. flock/{core/mcp/flock_mcp_tool.py → mcp/tool.py} +16 -26
  38. flock/mcp/types/__init__.py +42 -0
  39. flock/{core/mcp → mcp}/types/callbacks.py +9 -15
  40. flock/{core/mcp → mcp}/types/factories.py +7 -6
  41. flock/{core/mcp → mcp}/types/handlers.py +13 -18
  42. flock/{core/mcp → mcp}/types/types.py +70 -74
  43. flock/{core/mcp → mcp}/util/helpers.py +1 -1
  44. flock/orchestrator.py +645 -0
  45. flock/registry.py +148 -0
  46. flock/runtime.py +262 -0
  47. flock/service.py +140 -0
  48. flock/store.py +69 -0
  49. flock/subscription.py +111 -0
  50. flock/themes/andromeda.toml +1 -1
  51. flock/themes/apple-system-colors.toml +1 -1
  52. flock/themes/arcoiris.toml +1 -1
  53. flock/themes/atomonelight.toml +1 -1
  54. flock/themes/ayu copy.toml +1 -1
  55. flock/themes/ayu-light.toml +1 -1
  56. flock/themes/belafonte-day.toml +1 -1
  57. flock/themes/belafonte-night.toml +1 -1
  58. flock/themes/blulocodark.toml +1 -1
  59. flock/themes/breeze.toml +1 -1
  60. flock/themes/broadcast.toml +1 -1
  61. flock/themes/brogrammer.toml +1 -1
  62. flock/themes/builtin-dark.toml +1 -1
  63. flock/themes/builtin-pastel-dark.toml +1 -1
  64. flock/themes/catppuccin-latte.toml +1 -1
  65. flock/themes/catppuccin-macchiato.toml +1 -1
  66. flock/themes/catppuccin-mocha.toml +1 -1
  67. flock/themes/cga.toml +1 -1
  68. flock/themes/chalk.toml +1 -1
  69. flock/themes/ciapre.toml +1 -1
  70. flock/themes/coffee-theme.toml +1 -1
  71. flock/themes/cyberpunkscarletprotocol.toml +1 -1
  72. flock/themes/dark+.toml +1 -1
  73. flock/themes/darkermatrix.toml +1 -1
  74. flock/themes/darkside.toml +1 -1
  75. flock/themes/desert.toml +1 -1
  76. flock/themes/django.toml +1 -1
  77. flock/themes/djangosmooth.toml +1 -1
  78. flock/themes/doomone.toml +1 -1
  79. flock/themes/dotgov.toml +1 -1
  80. flock/themes/dracula+.toml +1 -1
  81. flock/themes/duckbones.toml +1 -1
  82. flock/themes/encom.toml +1 -1
  83. flock/themes/espresso.toml +1 -1
  84. flock/themes/everblush.toml +1 -1
  85. flock/themes/fairyfloss.toml +1 -1
  86. flock/themes/fideloper.toml +1 -1
  87. flock/themes/fishtank.toml +1 -1
  88. flock/themes/flexoki-light.toml +1 -1
  89. flock/themes/floraverse.toml +1 -1
  90. flock/themes/framer.toml +1 -1
  91. flock/themes/galizur.toml +1 -1
  92. flock/themes/github.toml +1 -1
  93. flock/themes/grass.toml +1 -1
  94. flock/themes/grey-green.toml +1 -1
  95. flock/themes/gruvboxlight.toml +1 -1
  96. flock/themes/guezwhoz.toml +1 -1
  97. flock/themes/harper.toml +1 -1
  98. flock/themes/hax0r-blue.toml +1 -1
  99. flock/themes/hopscotch.256.toml +1 -1
  100. flock/themes/ic-green-ppl.toml +1 -1
  101. flock/themes/iceberg-dark.toml +1 -1
  102. flock/themes/japanesque.toml +1 -1
  103. flock/themes/jubi.toml +1 -1
  104. flock/themes/kibble.toml +1 -1
  105. flock/themes/kolorit.toml +1 -1
  106. flock/themes/kurokula.toml +1 -1
  107. flock/themes/materialdesigncolors.toml +1 -1
  108. flock/themes/matrix.toml +1 -1
  109. flock/themes/mellifluous.toml +1 -1
  110. flock/themes/midnight-in-mojave.toml +1 -1
  111. flock/themes/monokai-remastered.toml +1 -1
  112. flock/themes/monokai-soda.toml +1 -1
  113. flock/themes/neon.toml +1 -1
  114. flock/themes/neopolitan.toml +1 -1
  115. flock/themes/nord-light.toml +1 -1
  116. flock/themes/ocean.toml +1 -1
  117. flock/themes/onehalfdark.toml +1 -1
  118. flock/themes/onehalflight.toml +1 -1
  119. flock/themes/palenighthc.toml +1 -1
  120. flock/themes/paulmillr.toml +1 -1
  121. flock/themes/pencildark.toml +1 -1
  122. flock/themes/pnevma.toml +1 -1
  123. flock/themes/purple-rain.toml +1 -1
  124. flock/themes/purplepeter.toml +1 -1
  125. flock/themes/raycast-dark.toml +1 -1
  126. flock/themes/red-sands.toml +1 -1
  127. flock/themes/relaxed.toml +1 -1
  128. flock/themes/retro.toml +1 -1
  129. flock/themes/rose-pine.toml +1 -1
  130. flock/themes/royal.toml +1 -1
  131. flock/themes/ryuuko.toml +1 -1
  132. flock/themes/sakura.toml +1 -1
  133. flock/themes/scarlet-protocol.toml +1 -1
  134. flock/themes/seoulbones-dark.toml +1 -1
  135. flock/themes/shades-of-purple.toml +1 -1
  136. flock/themes/smyck.toml +1 -1
  137. flock/themes/softserver.toml +1 -1
  138. flock/themes/solarized-darcula.toml +1 -1
  139. flock/themes/square.toml +1 -1
  140. flock/themes/sugarplum.toml +1 -1
  141. flock/themes/thayer-bright.toml +1 -1
  142. flock/themes/tokyonight.toml +1 -1
  143. flock/themes/tomorrow.toml +1 -1
  144. flock/themes/ubuntu.toml +1 -1
  145. flock/themes/ultradark.toml +1 -1
  146. flock/themes/ultraviolent.toml +1 -1
  147. flock/themes/unikitty.toml +1 -1
  148. flock/themes/urple.toml +1 -1
  149. flock/themes/vesper.toml +1 -1
  150. flock/themes/vimbones.toml +1 -1
  151. flock/themes/wildcherry.toml +1 -1
  152. flock/themes/wilmersdorf.toml +1 -1
  153. flock/themes/wryan.toml +1 -1
  154. flock/themes/xcodedarkhc.toml +1 -1
  155. flock/themes/xcodelight.toml +1 -1
  156. flock/themes/zenbones-light.toml +1 -1
  157. flock/themes/zenwritten-dark.toml +1 -1
  158. flock/utilities.py +301 -0
  159. flock/{components/utility → utility}/output_utility_component.py +68 -53
  160. flock/visibility.py +107 -0
  161. flock_core-0.5.0b50.dist-info/METADATA +747 -0
  162. flock_core-0.5.0b50.dist-info/RECORD +398 -0
  163. flock_core-0.5.0b50.dist-info/entry_points.txt +2 -0
  164. {flock_core-0.5.0b27.dist-info → flock_core-0.5.0b50.dist-info}/licenses/LICENSE +1 -1
  165. flock/adapter/__init__.py +0 -14
  166. flock/adapter/azure_adapter.py +0 -68
  167. flock/adapter/chroma_adapter.py +0 -73
  168. flock/adapter/faiss_adapter.py +0 -97
  169. flock/adapter/pinecone_adapter.py +0 -51
  170. flock/adapter/vector_base.py +0 -47
  171. flock/cli/assets/release_notes.md +0 -140
  172. flock/cli/config.py +0 -8
  173. flock/cli/constants.py +0 -36
  174. flock/cli/create_agent.py +0 -1
  175. flock/cli/create_flock.py +0 -280
  176. flock/cli/execute_flock.py +0 -620
  177. flock/cli/load_agent.py +0 -1
  178. flock/cli/load_examples.py +0 -1
  179. flock/cli/load_flock.py +0 -192
  180. flock/cli/load_release_notes.py +0 -20
  181. flock/cli/loaded_flock_cli.py +0 -254
  182. flock/cli/manage_agents.py +0 -459
  183. flock/cli/registry_management.py +0 -889
  184. flock/cli/runner.py +0 -41
  185. flock/cli/settings.py +0 -857
  186. flock/cli/utils.py +0 -135
  187. flock/cli/view_results.py +0 -29
  188. flock/cli/yaml_editor.py +0 -396
  189. flock/components/__init__.py +0 -30
  190. flock/components/evaluation/__init__.py +0 -9
  191. flock/components/evaluation/declarative_evaluation_component.py +0 -606
  192. flock/components/routing/__init__.py +0 -15
  193. flock/components/routing/conditional_routing_component.py +0 -494
  194. flock/components/routing/default_routing_component.py +0 -103
  195. flock/components/routing/llm_routing_component.py +0 -206
  196. flock/components/utility/__init__.py +0 -15
  197. flock/components/utility/memory_utility_component.py +0 -550
  198. flock/components/utility/metrics_utility_component.py +0 -700
  199. flock/config.py +0 -61
  200. flock/core/__init__.py +0 -110
  201. flock/core/agent/__init__.py +0 -16
  202. flock/core/agent/default_agent.py +0 -180
  203. flock/core/agent/flock_agent_components.py +0 -104
  204. flock/core/agent/flock_agent_execution.py +0 -101
  205. flock/core/agent/flock_agent_integration.py +0 -260
  206. flock/core/agent/flock_agent_lifecycle.py +0 -186
  207. flock/core/agent/flock_agent_serialization.py +0 -381
  208. flock/core/api/__init__.py +0 -10
  209. flock/core/api/custom_endpoint.py +0 -45
  210. flock/core/api/endpoints.py +0 -254
  211. flock/core/api/main.py +0 -162
  212. flock/core/api/models.py +0 -97
  213. flock/core/api/run_store.py +0 -224
  214. flock/core/api/runner.py +0 -44
  215. flock/core/api/service.py +0 -214
  216. flock/core/component/__init__.py +0 -15
  217. flock/core/component/agent_component_base.py +0 -309
  218. flock/core/component/evaluation_component.py +0 -62
  219. flock/core/component/routing_component.py +0 -74
  220. flock/core/component/utility_component.py +0 -69
  221. flock/core/config/flock_agent_config.py +0 -58
  222. flock/core/config/scheduled_agent_config.py +0 -40
  223. flock/core/context/context.py +0 -213
  224. flock/core/context/context_manager.py +0 -37
  225. flock/core/context/context_vars.py +0 -10
  226. flock/core/evaluation/utils.py +0 -396
  227. flock/core/execution/batch_executor.py +0 -369
  228. flock/core/execution/evaluation_executor.py +0 -438
  229. flock/core/execution/local_executor.py +0 -31
  230. flock/core/execution/opik_executor.py +0 -103
  231. flock/core/execution/temporal_executor.py +0 -164
  232. flock/core/flock.py +0 -634
  233. flock/core/flock_agent.py +0 -336
  234. flock/core/flock_factory.py +0 -551
  235. flock/core/flock_scheduler.py +0 -166
  236. flock/core/flock_server_manager.py +0 -136
  237. flock/core/interpreter/python_interpreter.py +0 -689
  238. flock/core/mcp/__init__.py +0 -1
  239. flock/core/mcp/flock_mcp_server.py +0 -680
  240. flock/core/mcp/mcp_client_manager.py +0 -201
  241. flock/core/mcp/types/__init__.py +0 -1
  242. flock/core/mixin/dspy_integration.py +0 -403
  243. flock/core/mixin/prompt_parser.py +0 -125
  244. flock/core/orchestration/__init__.py +0 -15
  245. flock/core/orchestration/flock_batch_processor.py +0 -94
  246. flock/core/orchestration/flock_evaluator.py +0 -113
  247. flock/core/orchestration/flock_execution.py +0 -295
  248. flock/core/orchestration/flock_initialization.py +0 -149
  249. flock/core/orchestration/flock_server_manager.py +0 -67
  250. flock/core/orchestration/flock_web_server.py +0 -117
  251. flock/core/registry/__init__.py +0 -45
  252. flock/core/registry/agent_registry.py +0 -69
  253. flock/core/registry/callable_registry.py +0 -139
  254. flock/core/registry/component_discovery.py +0 -142
  255. flock/core/registry/component_registry.py +0 -64
  256. flock/core/registry/config_mapping.py +0 -64
  257. flock/core/registry/decorators.py +0 -137
  258. flock/core/registry/registry_hub.py +0 -205
  259. flock/core/registry/server_registry.py +0 -57
  260. flock/core/registry/type_registry.py +0 -86
  261. flock/core/serialization/__init__.py +0 -13
  262. flock/core/serialization/callable_registry.py +0 -52
  263. flock/core/serialization/flock_serializer.py +0 -832
  264. flock/core/serialization/json_encoder.py +0 -41
  265. flock/core/serialization/secure_serializer.py +0 -175
  266. flock/core/serialization/serializable.py +0 -342
  267. flock/core/serialization/serialization_utils.py +0 -412
  268. flock/core/util/file_path_utils.py +0 -223
  269. flock/core/util/hydrator.py +0 -309
  270. flock/core/util/input_resolver.py +0 -164
  271. flock/core/util/loader.py +0 -59
  272. flock/core/util/splitter.py +0 -219
  273. flock/di.py +0 -27
  274. flock/platform/docker_tools.py +0 -49
  275. flock/platform/jaeger_install.py +0 -86
  276. flock/webapp/__init__.py +0 -1
  277. flock/webapp/app/__init__.py +0 -0
  278. flock/webapp/app/api/__init__.py +0 -0
  279. flock/webapp/app/api/agent_management.py +0 -241
  280. flock/webapp/app/api/execution.py +0 -709
  281. flock/webapp/app/api/flock_management.py +0 -129
  282. flock/webapp/app/api/registry_viewer.py +0 -30
  283. flock/webapp/app/chat.py +0 -665
  284. flock/webapp/app/config.py +0 -104
  285. flock/webapp/app/dependencies.py +0 -117
  286. flock/webapp/app/main.py +0 -1070
  287. flock/webapp/app/middleware.py +0 -113
  288. flock/webapp/app/models_ui.py +0 -7
  289. flock/webapp/app/services/__init__.py +0 -0
  290. flock/webapp/app/services/feedback_file_service.py +0 -363
  291. flock/webapp/app/services/flock_service.py +0 -337
  292. flock/webapp/app/services/sharing_models.py +0 -81
  293. flock/webapp/app/services/sharing_store.py +0 -598
  294. flock/webapp/app/templates/theme_mapper.html +0 -326
  295. flock/webapp/app/theme_mapper.py +0 -812
  296. flock/webapp/app/utils.py +0 -85
  297. flock/webapp/run.py +0 -215
  298. flock/webapp/static/css/chat.css +0 -301
  299. flock/webapp/static/css/components.css +0 -167
  300. flock/webapp/static/css/header.css +0 -39
  301. flock/webapp/static/css/layout.css +0 -46
  302. flock/webapp/static/css/sidebar.css +0 -127
  303. flock/webapp/static/css/two-pane.css +0 -48
  304. flock/webapp/templates/base.html +0 -200
  305. flock/webapp/templates/chat.html +0 -152
  306. flock/webapp/templates/chat_settings.html +0 -19
  307. flock/webapp/templates/flock_editor.html +0 -16
  308. flock/webapp/templates/index.html +0 -12
  309. flock/webapp/templates/partials/_agent_detail_form.html +0 -93
  310. flock/webapp/templates/partials/_agent_list.html +0 -18
  311. flock/webapp/templates/partials/_agent_manager_view.html +0 -51
  312. flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
  313. flock/webapp/templates/partials/_chat_container.html +0 -15
  314. flock/webapp/templates/partials/_chat_messages.html +0 -57
  315. flock/webapp/templates/partials/_chat_settings_form.html +0 -85
  316. flock/webapp/templates/partials/_create_flock_form.html +0 -50
  317. flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
  318. flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
  319. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
  320. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
  321. flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
  322. flock/webapp/templates/partials/_env_vars_table.html +0 -23
  323. flock/webapp/templates/partials/_execution_form.html +0 -118
  324. flock/webapp/templates/partials/_execution_view_container.html +0 -28
  325. flock/webapp/templates/partials/_flock_file_list.html +0 -23
  326. flock/webapp/templates/partials/_flock_properties_form.html +0 -52
  327. flock/webapp/templates/partials/_flock_upload_form.html +0 -16
  328. flock/webapp/templates/partials/_header_flock_status.html +0 -5
  329. flock/webapp/templates/partials/_load_manager_view.html +0 -49
  330. flock/webapp/templates/partials/_registry_table.html +0 -25
  331. flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
  332. flock/webapp/templates/partials/_results_display.html +0 -78
  333. flock/webapp/templates/partials/_settings_env_content.html +0 -9
  334. flock/webapp/templates/partials/_settings_theme_content.html +0 -14
  335. flock/webapp/templates/partials/_settings_view.html +0 -36
  336. flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
  337. flock/webapp/templates/partials/_share_link_snippet.html +0 -35
  338. flock/webapp/templates/partials/_sidebar.html +0 -74
  339. flock/webapp/templates/partials/_streaming_results_container.html +0 -195
  340. flock/webapp/templates/partials/_structured_data_view.html +0 -40
  341. flock/webapp/templates/partials/_theme_preview.html +0 -36
  342. flock/webapp/templates/registry_viewer.html +0 -84
  343. flock/webapp/templates/shared_run_page.html +0 -140
  344. flock/workflow/__init__.py +0 -0
  345. flock/workflow/activities.py +0 -196
  346. flock/workflow/agent_activities.py +0 -24
  347. flock/workflow/agent_execution_activity.py +0 -202
  348. flock/workflow/flock_workflow.py +0 -214
  349. flock/workflow/temporal_config.py +0 -96
  350. flock/workflow/temporal_setup.py +0 -68
  351. flock_core-0.5.0b27.dist-info/METADATA +0 -274
  352. flock_core-0.5.0b27.dist-info/RECORD +0 -559
  353. flock_core-0.5.0b27.dist-info/entry_points.txt +0 -2
  354. /flock/{core/logging → logging}/formatters/themes.py +0 -0
  355. /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
  356. /flock/{core/mcp → mcp}/util/__init__.py +0 -0
  357. {flock_core-0.5.0b27.dist-info → flock_core-0.5.0b50.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"]