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.

Files changed (359) 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.56b0.dist-info/METADATA +747 -0
  162. flock_core-0.5.56b0.dist-info/RECORD +398 -0
  163. flock_core-0.5.56b0.dist-info/entry_points.txt +2 -0
  164. {flock_core-0.5.0b28.dist-info → flock_core-0.5.56b0.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 -22
  197. flock/components/utility/example_utility_component.py +0 -250
  198. flock/components/utility/feedback_utility_component.py +0 -206
  199. flock/components/utility/memory_utility_component.py +0 -550
  200. flock/components/utility/metrics_utility_component.py +0 -700
  201. flock/config.py +0 -61
  202. flock/core/__init__.py +0 -110
  203. flock/core/agent/__init__.py +0 -16
  204. flock/core/agent/default_agent.py +0 -216
  205. flock/core/agent/flock_agent_components.py +0 -104
  206. flock/core/agent/flock_agent_execution.py +0 -101
  207. flock/core/agent/flock_agent_integration.py +0 -260
  208. flock/core/agent/flock_agent_lifecycle.py +0 -186
  209. flock/core/agent/flock_agent_serialization.py +0 -381
  210. flock/core/api/__init__.py +0 -10
  211. flock/core/api/custom_endpoint.py +0 -45
  212. flock/core/api/endpoints.py +0 -254
  213. flock/core/api/main.py +0 -162
  214. flock/core/api/models.py +0 -97
  215. flock/core/api/run_store.py +0 -224
  216. flock/core/api/runner.py +0 -44
  217. flock/core/api/service.py +0 -214
  218. flock/core/component/__init__.py +0 -15
  219. flock/core/component/agent_component_base.py +0 -309
  220. flock/core/component/evaluation_component.py +0 -62
  221. flock/core/component/routing_component.py +0 -74
  222. flock/core/component/utility_component.py +0 -69
  223. flock/core/config/flock_agent_config.py +0 -58
  224. flock/core/config/scheduled_agent_config.py +0 -40
  225. flock/core/context/context.py +0 -213
  226. flock/core/context/context_manager.py +0 -37
  227. flock/core/context/context_vars.py +0 -10
  228. flock/core/evaluation/utils.py +0 -396
  229. flock/core/execution/batch_executor.py +0 -369
  230. flock/core/execution/evaluation_executor.py +0 -438
  231. flock/core/execution/local_executor.py +0 -31
  232. flock/core/execution/opik_executor.py +0 -103
  233. flock/core/execution/temporal_executor.py +0 -164
  234. flock/core/flock.py +0 -634
  235. flock/core/flock_agent.py +0 -336
  236. flock/core/flock_factory.py +0 -613
  237. flock/core/flock_scheduler.py +0 -166
  238. flock/core/flock_server_manager.py +0 -136
  239. flock/core/interpreter/python_interpreter.py +0 -689
  240. flock/core/mcp/__init__.py +0 -1
  241. flock/core/mcp/flock_mcp_server.py +0 -680
  242. flock/core/mcp/mcp_client_manager.py +0 -201
  243. flock/core/mcp/types/__init__.py +0 -1
  244. flock/core/mixin/dspy_integration.py +0 -403
  245. flock/core/mixin/prompt_parser.py +0 -125
  246. flock/core/orchestration/__init__.py +0 -15
  247. flock/core/orchestration/flock_batch_processor.py +0 -94
  248. flock/core/orchestration/flock_evaluator.py +0 -113
  249. flock/core/orchestration/flock_execution.py +0 -295
  250. flock/core/orchestration/flock_initialization.py +0 -149
  251. flock/core/orchestration/flock_server_manager.py +0 -67
  252. flock/core/orchestration/flock_web_server.py +0 -117
  253. flock/core/registry/__init__.py +0 -45
  254. flock/core/registry/agent_registry.py +0 -69
  255. flock/core/registry/callable_registry.py +0 -139
  256. flock/core/registry/component_discovery.py +0 -142
  257. flock/core/registry/component_registry.py +0 -64
  258. flock/core/registry/config_mapping.py +0 -64
  259. flock/core/registry/decorators.py +0 -137
  260. flock/core/registry/registry_hub.py +0 -205
  261. flock/core/registry/server_registry.py +0 -57
  262. flock/core/registry/type_registry.py +0 -86
  263. flock/core/serialization/__init__.py +0 -13
  264. flock/core/serialization/callable_registry.py +0 -52
  265. flock/core/serialization/flock_serializer.py +0 -832
  266. flock/core/serialization/json_encoder.py +0 -41
  267. flock/core/serialization/secure_serializer.py +0 -175
  268. flock/core/serialization/serializable.py +0 -342
  269. flock/core/serialization/serialization_utils.py +0 -412
  270. flock/core/util/file_path_utils.py +0 -223
  271. flock/core/util/hydrator.py +0 -309
  272. flock/core/util/input_resolver.py +0 -164
  273. flock/core/util/loader.py +0 -59
  274. flock/core/util/splitter.py +0 -219
  275. flock/di.py +0 -27
  276. flock/platform/docker_tools.py +0 -49
  277. flock/platform/jaeger_install.py +0 -86
  278. flock/webapp/__init__.py +0 -1
  279. flock/webapp/app/__init__.py +0 -0
  280. flock/webapp/app/api/__init__.py +0 -0
  281. flock/webapp/app/api/agent_management.py +0 -241
  282. flock/webapp/app/api/execution.py +0 -709
  283. flock/webapp/app/api/flock_management.py +0 -129
  284. flock/webapp/app/api/registry_viewer.py +0 -30
  285. flock/webapp/app/chat.py +0 -665
  286. flock/webapp/app/config.py +0 -104
  287. flock/webapp/app/dependencies.py +0 -117
  288. flock/webapp/app/main.py +0 -1070
  289. flock/webapp/app/middleware.py +0 -113
  290. flock/webapp/app/models_ui.py +0 -7
  291. flock/webapp/app/services/__init__.py +0 -0
  292. flock/webapp/app/services/feedback_file_service.py +0 -363
  293. flock/webapp/app/services/flock_service.py +0 -337
  294. flock/webapp/app/services/sharing_models.py +0 -81
  295. flock/webapp/app/services/sharing_store.py +0 -762
  296. flock/webapp/app/templates/theme_mapper.html +0 -326
  297. flock/webapp/app/theme_mapper.py +0 -812
  298. flock/webapp/app/utils.py +0 -85
  299. flock/webapp/run.py +0 -215
  300. flock/webapp/static/css/chat.css +0 -301
  301. flock/webapp/static/css/components.css +0 -167
  302. flock/webapp/static/css/header.css +0 -39
  303. flock/webapp/static/css/layout.css +0 -46
  304. flock/webapp/static/css/sidebar.css +0 -127
  305. flock/webapp/static/css/two-pane.css +0 -48
  306. flock/webapp/templates/base.html +0 -200
  307. flock/webapp/templates/chat.html +0 -152
  308. flock/webapp/templates/chat_settings.html +0 -19
  309. flock/webapp/templates/flock_editor.html +0 -16
  310. flock/webapp/templates/index.html +0 -12
  311. flock/webapp/templates/partials/_agent_detail_form.html +0 -93
  312. flock/webapp/templates/partials/_agent_list.html +0 -18
  313. flock/webapp/templates/partials/_agent_manager_view.html +0 -51
  314. flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
  315. flock/webapp/templates/partials/_chat_container.html +0 -15
  316. flock/webapp/templates/partials/_chat_messages.html +0 -57
  317. flock/webapp/templates/partials/_chat_settings_form.html +0 -85
  318. flock/webapp/templates/partials/_create_flock_form.html +0 -50
  319. flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
  320. flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
  321. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
  322. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
  323. flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
  324. flock/webapp/templates/partials/_env_vars_table.html +0 -23
  325. flock/webapp/templates/partials/_execution_form.html +0 -118
  326. flock/webapp/templates/partials/_execution_view_container.html +0 -28
  327. flock/webapp/templates/partials/_flock_file_list.html +0 -23
  328. flock/webapp/templates/partials/_flock_properties_form.html +0 -52
  329. flock/webapp/templates/partials/_flock_upload_form.html +0 -16
  330. flock/webapp/templates/partials/_header_flock_status.html +0 -5
  331. flock/webapp/templates/partials/_load_manager_view.html +0 -49
  332. flock/webapp/templates/partials/_registry_table.html +0 -25
  333. flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
  334. flock/webapp/templates/partials/_results_display.html +0 -78
  335. flock/webapp/templates/partials/_settings_env_content.html +0 -9
  336. flock/webapp/templates/partials/_settings_theme_content.html +0 -14
  337. flock/webapp/templates/partials/_settings_view.html +0 -36
  338. flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
  339. flock/webapp/templates/partials/_share_link_snippet.html +0 -35
  340. flock/webapp/templates/partials/_sidebar.html +0 -74
  341. flock/webapp/templates/partials/_streaming_results_container.html +0 -195
  342. flock/webapp/templates/partials/_structured_data_view.html +0 -40
  343. flock/webapp/templates/partials/_theme_preview.html +0 -36
  344. flock/webapp/templates/registry_viewer.html +0 -84
  345. flock/webapp/templates/shared_run_page.html +0 -140
  346. flock/workflow/__init__.py +0 -0
  347. flock/workflow/activities.py +0 -196
  348. flock/workflow/agent_activities.py +0 -24
  349. flock/workflow/agent_execution_activity.py +0 -202
  350. flock/workflow/flock_workflow.py +0 -214
  351. flock/workflow/temporal_config.py +0 -96
  352. flock/workflow/temporal_setup.py +0 -68
  353. flock_core-0.5.0b28.dist-info/METADATA +0 -274
  354. flock_core-0.5.0b28.dist-info/RECORD +0 -561
  355. flock_core-0.5.0b28.dist-info/entry_points.txt +0 -2
  356. /flock/{core/logging → logging}/formatters/themes.py +0 -0
  357. /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
  358. /flock/{core/mcp → mcp}/util/__init__.py +0 -0
  359. {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"]