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,283 @@
1
+ """DashboardEventCollector - captures agent lifecycle events for real-time dashboard.
2
+
3
+ This component hooks into the agent execution lifecycle to emit WebSocket events.
4
+ Phase 1: Events stored in in-memory buffer (max 100 events).
5
+ Phase 3: Extended to emit via WebSocket using WebSocketManager.
6
+ """
7
+
8
+ import traceback
9
+ from collections import deque
10
+ from datetime import datetime, timezone
11
+ from typing import TYPE_CHECKING, Optional
12
+
13
+ from pydantic import PrivateAttr
14
+
15
+ from flock.components import AgentComponent
16
+ from flock.dashboard.events import (
17
+ AgentActivatedEvent,
18
+ AgentCompletedEvent,
19
+ AgentErrorEvent,
20
+ MessagePublishedEvent,
21
+ SubscriptionInfo,
22
+ VisibilitySpec,
23
+ )
24
+ from flock.logging.logging import get_logger
25
+ from flock.runtime import Context
26
+
27
+
28
+ logger = get_logger("dashboard.collector")
29
+
30
+ if TYPE_CHECKING: # pragma: no cover - type hints only
31
+ from flock.agent import Agent
32
+ from flock.artifacts import Artifact
33
+ from flock.dashboard.websocket import WebSocketManager
34
+
35
+
36
+ class DashboardEventCollector(AgentComponent):
37
+ """Collects agent lifecycle events for dashboard visualization.
38
+
39
+ Implements AgentComponent interface to hook into agent execution:
40
+ - on_pre_consume: emits agent_activated
41
+ - on_post_publish: emits message_published
42
+ - on_terminate: emits agent_completed
43
+ - on_error: emits agent_error
44
+
45
+ Phase 1: Events stored in in-memory deque (max 100, LRU eviction).
46
+ Phase 3: Emits events via WebSocket using WebSocketManager.
47
+ """
48
+
49
+ # Use PrivateAttr for non-Pydantic fields (AgentComponent extends BaseModel)
50
+ _events: deque[
51
+ AgentActivatedEvent | MessagePublishedEvent | AgentCompletedEvent | AgentErrorEvent
52
+ ] = PrivateAttr(default=None)
53
+
54
+ # Track run start times for duration calculation
55
+ _run_start_times: dict[str, float] = PrivateAttr(default_factory=dict)
56
+
57
+ # WebSocketManager for broadcasting events
58
+ _websocket_manager: Optional["WebSocketManager"] = PrivateAttr(default=None)
59
+
60
+ def __init__(self, **data):
61
+ super().__init__(**data)
62
+ # In-memory buffer with max 100 events (LRU eviction)
63
+ self._events = deque(maxlen=100)
64
+ self._run_start_times = {}
65
+ self._websocket_manager = None
66
+
67
+ def set_websocket_manager(self, manager: "WebSocketManager") -> None:
68
+ """Set WebSocketManager for broadcasting events.
69
+
70
+ Args:
71
+ manager: WebSocketManager instance to use for broadcasting
72
+ """
73
+ self._websocket_manager = manager
74
+
75
+ @property
76
+ def events(self) -> deque:
77
+ """Access events buffer."""
78
+ return self._events
79
+
80
+ async def on_pre_consume(
81
+ self, agent: "Agent", ctx: Context, inputs: list["Artifact"]
82
+ ) -> list["Artifact"]:
83
+ """Emit agent_activated event when agent begins consuming.
84
+
85
+ Args:
86
+ agent: The agent that is consuming
87
+ ctx: Execution context with correlation_id
88
+ inputs: Artifacts being consumed
89
+
90
+ Returns:
91
+ Unmodified inputs (pass-through)
92
+ """
93
+ # Record start time for duration calculation
94
+ self._run_start_times[ctx.task_id] = datetime.now(timezone.utc).timestamp()
95
+
96
+ # Extract consumed types and artifact IDs
97
+ consumed_types = list({artifact.type for artifact in inputs})
98
+ consumed_artifacts = [str(artifact.id) for artifact in inputs]
99
+
100
+ # Extract produced types from agent outputs
101
+ produced_types = [output.spec.type_name for output in agent.outputs]
102
+
103
+ # Build subscription info from agent's subscriptions
104
+ subscription_info = SubscriptionInfo(from_agents=[], channels=[], mode="both")
105
+
106
+ if agent.subscriptions:
107
+ # Get first subscription's config (agents typically have one)
108
+ sub = agent.subscriptions[0]
109
+ subscription_info.from_agents = list(sub.from_agents) if sub.from_agents else []
110
+ subscription_info.channels = list(sub.channels) if sub.channels else []
111
+ subscription_info.mode = sub.mode
112
+
113
+ # Create and store event
114
+ event = AgentActivatedEvent(
115
+ correlation_id=str(ctx.correlation_id) if ctx.correlation_id else "",
116
+ agent_name=agent.name,
117
+ agent_id=agent.name,
118
+ run_id=ctx.task_id, # Unique ID for this agent run
119
+ consumed_types=consumed_types,
120
+ consumed_artifacts=consumed_artifacts,
121
+ produced_types=produced_types,
122
+ subscription_info=subscription_info,
123
+ labels=list(agent.labels),
124
+ tenant_id=agent.tenant_id,
125
+ max_concurrency=agent.max_concurrency,
126
+ )
127
+
128
+ self._events.append(event)
129
+ logger.info(f"Agent activated: {agent.name} (correlation_id={event.correlation_id})")
130
+
131
+ # Broadcast via WebSocket if manager is configured
132
+ if self._websocket_manager:
133
+ await self._websocket_manager.broadcast(event)
134
+ else:
135
+ logger.warning("WebSocket manager not configured, event not broadcast")
136
+
137
+ return inputs
138
+
139
+ async def on_post_publish(self, agent: "Agent", ctx: Context, artifact: "Artifact") -> None:
140
+ """Emit message_published event when artifact is published.
141
+
142
+ Args:
143
+ agent: The agent that published the artifact
144
+ ctx: Execution context with correlation_id
145
+ artifact: The published artifact
146
+ """
147
+ # Convert visibility to VisibilitySpec
148
+ visibility_spec = self._convert_visibility(artifact.visibility)
149
+
150
+ # Create and store event
151
+ event = MessagePublishedEvent(
152
+ correlation_id=str(ctx.correlation_id) if ctx.correlation_id else "",
153
+ artifact_id=str(artifact.id),
154
+ artifact_type=artifact.type,
155
+ produced_by=artifact.produced_by,
156
+ payload=artifact.payload,
157
+ visibility=visibility_spec,
158
+ tags=list(artifact.tags) if artifact.tags else [],
159
+ partition_key=artifact.partition_key,
160
+ version=artifact.version,
161
+ consumers=[], # Phase 1: empty, Phase 3: compute from subscription matching
162
+ )
163
+
164
+ self._events.append(event)
165
+ logger.info(
166
+ f"Message published: {artifact.type} by {artifact.produced_by} (correlation_id={event.correlation_id})"
167
+ )
168
+
169
+ # Broadcast via WebSocket if manager is configured
170
+ if self._websocket_manager:
171
+ await self._websocket_manager.broadcast(event)
172
+ else:
173
+ logger.warning("WebSocket manager not configured, event not broadcast")
174
+
175
+ async def on_terminate(self, agent: "Agent", ctx: Context) -> None:
176
+ """Emit agent_completed event when agent finishes successfully.
177
+
178
+ Args:
179
+ agent: The agent that completed
180
+ ctx: Execution context with final state
181
+ """
182
+ # Calculate duration
183
+ start_time = self._run_start_times.get(ctx.task_id)
184
+ if start_time:
185
+ duration_ms = (datetime.now(timezone.utc).timestamp() - start_time) * 1000
186
+ del self._run_start_times[ctx.task_id]
187
+ else:
188
+ duration_ms = 0.0
189
+
190
+ # Extract artifacts produced from context state (if tracked)
191
+ artifacts_produced = ctx.state.get("artifacts_produced", [])
192
+ if not isinstance(artifacts_produced, list):
193
+ artifacts_produced = []
194
+
195
+ # Extract metrics from context state (if tracked)
196
+ metrics = ctx.state.get("metrics", {})
197
+ if not isinstance(metrics, dict):
198
+ metrics = {}
199
+
200
+ # Create and store event
201
+ event = AgentCompletedEvent(
202
+ correlation_id=str(ctx.correlation_id) if ctx.correlation_id else "",
203
+ agent_name=agent.name,
204
+ run_id=ctx.task_id,
205
+ duration_ms=duration_ms,
206
+ artifacts_produced=artifacts_produced,
207
+ metrics=metrics,
208
+ final_state=dict(ctx.state),
209
+ )
210
+
211
+ self._events.append(event)
212
+
213
+ # Broadcast via WebSocket if manager is configured
214
+ if self._websocket_manager:
215
+ await self._websocket_manager.broadcast(event)
216
+
217
+ async def on_error(self, agent: "Agent", ctx: Context, error: Exception) -> None:
218
+ """Emit agent_error event when agent execution fails.
219
+
220
+ Args:
221
+ agent: The agent that failed
222
+ ctx: Execution context
223
+ error: The exception that was raised
224
+ """
225
+ # Get error details
226
+ error_type = type(error).__name__
227
+ error_message = str(error)
228
+ # Use traceback.format_exception to get traceback from exception object
229
+ error_traceback = "".join(
230
+ traceback.format_exception(type(error), error, error.__traceback__)
231
+ )
232
+ failed_at = datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
233
+
234
+ # Clean up start time tracking
235
+ if ctx.task_id in self._run_start_times:
236
+ del self._run_start_times[ctx.task_id]
237
+
238
+ # Create and store event
239
+ event = AgentErrorEvent(
240
+ correlation_id=str(ctx.correlation_id) if ctx.correlation_id else "",
241
+ agent_name=agent.name,
242
+ run_id=ctx.task_id,
243
+ error_type=error_type,
244
+ error_message=error_message,
245
+ traceback=error_traceback,
246
+ failed_at=failed_at,
247
+ )
248
+
249
+ self._events.append(event)
250
+
251
+ # Broadcast via WebSocket if manager is configured
252
+ if self._websocket_manager:
253
+ await self._websocket_manager.broadcast(event)
254
+
255
+ def _convert_visibility(self, visibility) -> VisibilitySpec:
256
+ """Convert flock.visibility.Visibility to VisibilitySpec.
257
+
258
+ Args:
259
+ visibility: Visibility object from artifact
260
+
261
+ Returns:
262
+ VisibilitySpec for event serialization
263
+ """
264
+ # Get visibility kind from class name, stripping "Visibility" suffix
265
+ class_name = type(visibility).__name__
266
+ kind = class_name[: -len("Visibility")] if class_name.endswith("Visibility") else class_name
267
+
268
+ spec = VisibilitySpec(kind=kind)
269
+
270
+ # Extract type-specific fields
271
+ if kind == "Private":
272
+ spec.agents = list(visibility.agents) if hasattr(visibility, "agents") else []
273
+ elif kind == "Labelled":
274
+ spec.required_labels = (
275
+ list(visibility.required_labels) if hasattr(visibility, "required_labels") else []
276
+ )
277
+ elif kind == "Tenant":
278
+ spec.tenant_id = visibility.tenant_id if hasattr(visibility, "tenant_id") else None
279
+
280
+ return spec
281
+
282
+
283
+ __all__ = ["DashboardEventCollector"]
@@ -0,0 +1,182 @@
1
+ """Event models for real-time dashboard.
2
+
3
+ Defines 5 core event types that capture agent execution lifecycle.
4
+ All schemas match DATA_MODEL.md specification exactly.
5
+ """
6
+
7
+ from datetime import datetime, timezone
8
+ from typing import Any
9
+
10
+ from pydantic import BaseModel, Field
11
+
12
+
13
+ class SubscriptionInfo(BaseModel):
14
+ """Subscription configuration for an agent."""
15
+
16
+ from_agents: list[str] = Field(default_factory=list)
17
+ channels: list[str] = Field(default_factory=list)
18
+ mode: str = "both" # "both" | "events" | "direct"
19
+
20
+
21
+ class VisibilitySpec(BaseModel):
22
+ """Visibility specification for artifacts.
23
+
24
+ Matches visibility types from flock.visibility module.
25
+ """
26
+
27
+ kind: str # "Public" | "Private" | "Labelled" | "Tenant" | "After"
28
+ agents: list[str] | None = None # For Private
29
+ required_labels: list[str] | None = None # For Labelled
30
+ tenant_id: str | None = None # For Tenant
31
+
32
+
33
+ class AgentActivatedEvent(BaseModel):
34
+ """Event emitted when agent begins consuming artifacts.
35
+
36
+ Corresponds to on_pre_consume lifecycle hook.
37
+ Schema per DATA_MODEL.md lines 53-66.
38
+ """
39
+
40
+ # Event metadata
41
+ correlation_id: str
42
+ timestamp: str = Field(
43
+ default_factory=lambda: datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
44
+ )
45
+
46
+ # Agent identification
47
+ agent_name: str
48
+ agent_id: str # Same as agent.name (unique per orchestrator)
49
+ run_id: str # Context.task_id (unique per agent activation)
50
+
51
+ # Consumption info
52
+ consumed_types: list[str] # Artifact types being consumed
53
+ consumed_artifacts: list[str] # Artifact IDs being consumed
54
+
55
+ # Production info
56
+ produced_types: list[str] # Artifact types this agent can produce
57
+
58
+ # Subscription configuration
59
+ subscription_info: SubscriptionInfo
60
+
61
+ # Agent metadata
62
+ labels: list[str]
63
+ tenant_id: str | None = None
64
+ max_concurrency: int = 1
65
+
66
+
67
+ class MessagePublishedEvent(BaseModel):
68
+ """Event emitted when artifact is published to blackboard.
69
+
70
+ Corresponds to on_post_publish lifecycle hook.
71
+ Schema per DATA_MODEL.md lines 100-115.
72
+ """
73
+
74
+ # Event metadata
75
+ correlation_id: str
76
+ timestamp: str = Field(
77
+ default_factory=lambda: datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
78
+ )
79
+
80
+ # Artifact identification
81
+ artifact_id: str # UUID
82
+ artifact_type: str # "Movie", "Tagline", etc.
83
+
84
+ # Provenance
85
+ produced_by: str # agent.name or "external"
86
+
87
+ # Content
88
+ payload: dict[str, Any] # Full artifact.payload
89
+
90
+ # Access control
91
+ visibility: VisibilitySpec
92
+ tags: list[str] = Field(default_factory=list)
93
+ partition_key: str | None = None
94
+ version: int = 1
95
+
96
+ # Flow tracking (Phase 1: empty, Phase 3: computed from subscription matching)
97
+ consumers: list[str] = Field(default_factory=list) # [agent.name]
98
+
99
+
100
+ class StreamingOutputEvent(BaseModel):
101
+ """Event emitted when agent generates LLM tokens or logs.
102
+
103
+ For Phase 1: This is optional and not fully implemented.
104
+ Schema per DATA_MODEL.md lines 152-159.
105
+ """
106
+
107
+ # Event metadata
108
+ correlation_id: str
109
+ timestamp: str = Field(
110
+ default_factory=lambda: datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
111
+ )
112
+
113
+ # Agent identification
114
+ agent_name: str
115
+ run_id: str # Context.task_id
116
+
117
+ # Output info
118
+ output_type: str # "llm_token" | "log" | "stdout" | "stderr"
119
+ content: str # Token text or log line
120
+ sequence: int # Monotonic sequence for ordering
121
+ is_final: bool = False # True when agent completes this output stream
122
+
123
+
124
+ class AgentCompletedEvent(BaseModel):
125
+ """Event emitted when agent execution finishes successfully.
126
+
127
+ Corresponds to on_terminate lifecycle hook.
128
+ Schema per DATA_MODEL.md lines 205-212.
129
+ """
130
+
131
+ # Event metadata
132
+ correlation_id: str
133
+ timestamp: str = Field(
134
+ default_factory=lambda: datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
135
+ )
136
+
137
+ # Agent identification
138
+ agent_name: str
139
+ run_id: str # Context.task_id
140
+
141
+ # Execution metrics
142
+ duration_ms: float # Execution time in milliseconds
143
+ artifacts_produced: list[str] = Field(default_factory=list) # [artifact_id]
144
+
145
+ # Metrics and state
146
+ metrics: dict[str, Any] = Field(default_factory=dict) # {"tokens_used": 1234, "cost": 0.05}
147
+ final_state: dict[str, Any] = Field(default_factory=dict) # Context.state snapshot
148
+
149
+
150
+ class AgentErrorEvent(BaseModel):
151
+ """Event emitted when agent execution fails.
152
+
153
+ Corresponds to on_error lifecycle hook.
154
+ Schema per DATA_MODEL.md lines 247-253.
155
+ """
156
+
157
+ # Event metadata
158
+ correlation_id: str
159
+ timestamp: str = Field(
160
+ default_factory=lambda: datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
161
+ )
162
+
163
+ # Agent identification
164
+ agent_name: str
165
+ run_id: str # Context.task_id
166
+
167
+ # Error details
168
+ error_type: str # Exception class name
169
+ error_message: str # Exception message
170
+ traceback: str # Full Python traceback
171
+ failed_at: str # ISO timestamp of failure
172
+
173
+
174
+ __all__ = [
175
+ "AgentActivatedEvent",
176
+ "AgentCompletedEvent",
177
+ "AgentErrorEvent",
178
+ "MessagePublishedEvent",
179
+ "StreamingOutputEvent",
180
+ "SubscriptionInfo",
181
+ "VisibilitySpec",
182
+ ]
@@ -0,0 +1,230 @@
1
+ """DashboardLauncher - manages npm lifecycle and browser launch for the dashboard.
2
+
3
+ This module handles:
4
+ - npm dependency installation (first run)
5
+ - npm dev server (DASHBOARD_DEV=1) or production build
6
+ - Automatic browser launch
7
+ - Process cleanup on shutdown
8
+ """
9
+
10
+ import os
11
+ import subprocess
12
+ import sys
13
+ import time
14
+ import webbrowser
15
+ from pathlib import Path
16
+
17
+
18
+ # Frontend directory location (adjacent to this dashboard package)
19
+ FRONTEND_DIR = Path(__file__).parent.parent.parent.parent / "frontend"
20
+
21
+
22
+ class DashboardLauncher:
23
+ """Manages dashboard frontend lifecycle.
24
+
25
+ Responsibilities:
26
+ - Ensure npm dependencies installed
27
+ - Start npm dev server (dev mode) or build for production
28
+ - Launch browser automatically
29
+ - Clean up npm processes on shutdown
30
+
31
+ Usage:
32
+ launcher = DashboardLauncher(port=8000)
33
+ launcher.start() # Starts npm and opens browser
34
+ # ... orchestrator runs ...
35
+ launcher.stop() # Cleanup
36
+
37
+ Or as context manager:
38
+ with DashboardLauncher(port=8000):
39
+ # orchestrator.serve() runs
40
+ pass # Automatically cleaned up
41
+ """
42
+
43
+ def __init__(self, port: int = 8000, frontend_dir: Path | None = None):
44
+ """Initialize dashboard launcher.
45
+
46
+ Args:
47
+ port: HTTP port where dashboard will be served (default: 8000)
48
+ frontend_dir: Optional frontend directory path (defaults to FRONTEND_DIR)
49
+ """
50
+ self.port = port
51
+ self.frontend_dir = frontend_dir or FRONTEND_DIR
52
+ self.dev_mode = os.getenv("DASHBOARD_DEV", "0") == "1"
53
+ self._npm_process: subprocess.Popen | None = None
54
+
55
+ def _ensure_npm_dependencies(self) -> None:
56
+ """Ensure npm dependencies are installed.
57
+
58
+ Runs 'npm install'.
59
+ """
60
+
61
+ print(f"[Dashboard] Installing npm dependencies in {self.frontend_dir}...")
62
+ try:
63
+ subprocess.run(
64
+ [self._get_npm_command(), "install"],
65
+ cwd=self.frontend_dir,
66
+ check=True,
67
+ capture_output=True,
68
+ text=True,
69
+ )
70
+ print("[Dashboard] npm dependencies installed successfully")
71
+ except subprocess.CalledProcessError as e:
72
+ print(f"[Dashboard] Warning: npm install failed: {e.stderr}")
73
+ print("[Dashboard] Continuing anyway, dashboard may not work correctly")
74
+ except FileNotFoundError:
75
+ print("[Dashboard] Error: npm not found. Please install Node.js and npm.")
76
+ print("[Dashboard] Dashboard will not be available.")
77
+
78
+ def _get_npm_command(self) -> str:
79
+ """Get npm command (npm or npm.cmd on Windows)."""
80
+ return "npm.cmd" if sys.platform == "win32" else "npm"
81
+
82
+ def _start_npm_process(self) -> None:
83
+ """Start npm dev server or production build based on mode."""
84
+ if self.dev_mode:
85
+ self._start_dev_server()
86
+ else:
87
+ self._build_production()
88
+
89
+ def _start_dev_server(self) -> None:
90
+ """Start npm dev server for hot-reload development."""
91
+ print(f"[Dashboard] Starting dev server (DASHBOARD_DEV=1) on port {self.port}...")
92
+
93
+ try:
94
+ self._npm_process = subprocess.Popen(
95
+ [self._get_npm_command(), "run", "dev"],
96
+ cwd=self.frontend_dir,
97
+ stdout=subprocess.PIPE,
98
+ stderr=subprocess.PIPE,
99
+ text=True,
100
+ )
101
+ print(f"[Dashboard] Dev server started (PID: {self._npm_process.pid})")
102
+ except FileNotFoundError:
103
+ print("[Dashboard] Error: npm not found. Dev server not started.")
104
+
105
+ def _build_production(self) -> None:
106
+ """Build frontend for production (static files)."""
107
+ print("[Dashboard] Building frontend for production...")
108
+
109
+ try:
110
+ subprocess.run(
111
+ [self._get_npm_command(), "run", "build"],
112
+ cwd=self.frontend_dir,
113
+ check=True,
114
+ capture_output=True,
115
+ text=True,
116
+ )
117
+ print("[Dashboard] Production build completed")
118
+
119
+ # Copy build output from frontend/dist to src/flock/dashboard/static
120
+ self._copy_build_output()
121
+
122
+ except subprocess.CalledProcessError as e:
123
+ print(f"[Dashboard] Warning: Production build failed: {e.stderr}")
124
+ print("[Dashboard] Dashboard may not be available")
125
+ except FileNotFoundError:
126
+ print("[Dashboard] Error: npm not found. Build skipped.")
127
+
128
+ def _copy_build_output(self) -> None:
129
+ """Copy built frontend files from frontend/dist to dashboard/static."""
130
+ import shutil
131
+
132
+ source_dir = self.frontend_dir / "dist"
133
+ # Dashboard directory is src/flock/dashboard
134
+ target_dir = Path(__file__).parent / "static"
135
+
136
+ if not source_dir.exists():
137
+ print(f"[Dashboard] Warning: Build output not found at {source_dir}")
138
+ return
139
+
140
+ # Remove old static files if they exist
141
+ if target_dir.exists():
142
+ shutil.rmtree(target_dir)
143
+
144
+ # Copy dist to static
145
+ print(f"[Dashboard] Copying build output from {source_dir} to {target_dir}")
146
+ shutil.copytree(source_dir, target_dir)
147
+ print(f"[Dashboard] Static files ready at {target_dir}")
148
+
149
+ def _launch_browser(self) -> None:
150
+ """Launch browser to dashboard URL after brief delay.
151
+
152
+ Waits 2 seconds to allow server to start before opening browser.
153
+ Catches errors gracefully (e.g., headless environments).
154
+ """
155
+ # Wait for server to start
156
+ time.sleep(2)
157
+
158
+ dashboard_url = f"http://localhost:{self.port}"
159
+ print(f"[Dashboard] Opening browser to {dashboard_url}...")
160
+
161
+ try:
162
+ webbrowser.open(dashboard_url)
163
+ print("[Dashboard] Browser launched successfully")
164
+ except Exception as e:
165
+ print(f"[Dashboard] Could not launch browser: {e}")
166
+ print(f"[Dashboard] Please open {dashboard_url} manually")
167
+
168
+ def start(self) -> None:
169
+ """Start dashboard: install deps, start npm, launch browser.
170
+
171
+ This method:
172
+ 1. Ensures npm dependencies are installed
173
+ 2. Starts npm dev server (dev mode) or builds production
174
+ 3. Launches browser to dashboard URL
175
+ """
176
+ print(f"[Dashboard] Starting dashboard on port {self.port}")
177
+ print(f"[Dashboard] Mode: {'DEVELOPMENT' if self.dev_mode else 'PRODUCTION'}")
178
+ print(f"[Dashboard] Frontend directory: {self.frontend_dir}")
179
+
180
+ # Step 1: Ensure dependencies installed
181
+ self._ensure_npm_dependencies()
182
+
183
+ # Step 2: Start npm process
184
+ self._start_npm_process()
185
+
186
+ # Step 3: Launch browser
187
+ self._launch_browser()
188
+
189
+ def stop(self) -> None:
190
+ """Stop dashboard and cleanup npm processes.
191
+
192
+ Attempts graceful termination, falls back to kill if needed.
193
+ """
194
+ if self._npm_process is None:
195
+ return
196
+
197
+ print("[Dashboard] Stopping npm process...")
198
+
199
+ try:
200
+ # Try graceful termination first
201
+ self._npm_process.terminate()
202
+
203
+ # Wait up to 5 seconds for process to exit
204
+ for _ in range(5):
205
+ if self._npm_process.poll() is not None:
206
+ print("[Dashboard] npm process stopped gracefully")
207
+ return
208
+ time.sleep(1)
209
+
210
+ # Force kill if still running
211
+ print("[Dashboard] npm process did not stop gracefully, forcing kill...")
212
+ self._npm_process.kill()
213
+ self._npm_process.wait(timeout=2)
214
+ print("[Dashboard] npm process killed")
215
+
216
+ except Exception as e:
217
+ print(f"[Dashboard] Error stopping npm process: {e}")
218
+
219
+ finally:
220
+ self._npm_process = None
221
+
222
+ def __enter__(self):
223
+ """Context manager entry: start dashboard."""
224
+ self.start()
225
+ return self
226
+
227
+ def __exit__(self, exc_type, exc_val, exc_tb):
228
+ """Context manager exit: stop dashboard."""
229
+ self.stop()
230
+ return False # Don't suppress exceptions