flock-core 0.4.542__py3-none-any.whl → 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of flock-core might be problematic. Click here for more details.

Files changed (501) hide show
  1. flock/__init__.py +12 -217
  2. flock/agent.py +1079 -0
  3. flock/api/themes.py +71 -0
  4. flock/artifacts.py +86 -0
  5. flock/cli.py +147 -0
  6. flock/components.py +189 -0
  7. flock/dashboard/__init__.py +30 -0
  8. flock/dashboard/collector.py +559 -0
  9. flock/dashboard/events.py +188 -0
  10. flock/dashboard/graph_builder.py +563 -0
  11. flock/dashboard/launcher.py +235 -0
  12. flock/dashboard/models/graph.py +156 -0
  13. flock/dashboard/service.py +991 -0
  14. flock/dashboard/static_v2/assets/index-DFRnI_mt.js +111 -0
  15. flock/dashboard/static_v2/assets/index-fPLNdmp1.css +1 -0
  16. flock/dashboard/static_v2/index.html +13 -0
  17. flock/dashboard/websocket.py +246 -0
  18. flock/engines/__init__.py +6 -0
  19. flock/engines/dspy_engine.py +932 -0
  20. flock/examples.py +131 -0
  21. flock/frontend/README.md +778 -0
  22. flock/frontend/docs/DESIGN_SYSTEM.md +1980 -0
  23. flock/frontend/index.html +12 -0
  24. flock/frontend/package-lock.json +4337 -0
  25. flock/frontend/package.json +48 -0
  26. flock/frontend/src/App.tsx +139 -0
  27. flock/frontend/src/__tests__/integration/graph-snapshot.test.tsx +647 -0
  28. flock/frontend/src/__tests__/integration/indexeddb-persistence.test.tsx +699 -0
  29. flock/frontend/src/components/common/BuildInfo.tsx +39 -0
  30. flock/frontend/src/components/common/EmptyState.module.css +115 -0
  31. flock/frontend/src/components/common/EmptyState.tsx +128 -0
  32. flock/frontend/src/components/common/ErrorBoundary.module.css +169 -0
  33. flock/frontend/src/components/common/ErrorBoundary.tsx +118 -0
  34. flock/frontend/src/components/common/KeyboardShortcutsDialog.css +251 -0
  35. flock/frontend/src/components/common/KeyboardShortcutsDialog.tsx +151 -0
  36. flock/frontend/src/components/common/LoadingSpinner.module.css +97 -0
  37. flock/frontend/src/components/common/LoadingSpinner.tsx +29 -0
  38. flock/frontend/src/components/controls/PublishControl.css +547 -0
  39. flock/frontend/src/components/controls/PublishControl.test.tsx +543 -0
  40. flock/frontend/src/components/controls/PublishControl.tsx +432 -0
  41. flock/frontend/src/components/details/DetailWindowContainer.tsx +58 -0
  42. flock/frontend/src/components/details/LiveOutputTab.test.tsx +792 -0
  43. flock/frontend/src/components/details/LiveOutputTab.tsx +220 -0
  44. flock/frontend/src/components/details/MessageDetailWindow.tsx +439 -0
  45. flock/frontend/src/components/details/MessageHistoryTab.tsx +374 -0
  46. flock/frontend/src/components/details/NodeDetailWindow.test.tsx +501 -0
  47. flock/frontend/src/components/details/NodeDetailWindow.tsx +218 -0
  48. flock/frontend/src/components/details/RunStatusTab.tsx +348 -0
  49. flock/frontend/src/components/details/tabs.test.tsx +1015 -0
  50. flock/frontend/src/components/filters/ArtifactTypeFilter.tsx +21 -0
  51. flock/frontend/src/components/filters/CorrelationIDFilter.module.css +102 -0
  52. flock/frontend/src/components/filters/CorrelationIDFilter.test.tsx +197 -0
  53. flock/frontend/src/components/filters/CorrelationIDFilter.tsx +121 -0
  54. flock/frontend/src/components/filters/FilterFlyout.module.css +104 -0
  55. flock/frontend/src/components/filters/FilterFlyout.tsx +80 -0
  56. flock/frontend/src/components/filters/FilterPills.module.css +220 -0
  57. flock/frontend/src/components/filters/FilterPills.test.tsx +189 -0
  58. flock/frontend/src/components/filters/FilterPills.tsx +143 -0
  59. flock/frontend/src/components/filters/ProducerFilter.tsx +21 -0
  60. flock/frontend/src/components/filters/SavedFiltersControl.module.css +60 -0
  61. flock/frontend/src/components/filters/SavedFiltersControl.test.tsx +158 -0
  62. flock/frontend/src/components/filters/SavedFiltersControl.tsx +159 -0
  63. flock/frontend/src/components/filters/TagFilter.tsx +21 -0
  64. flock/frontend/src/components/filters/TimeRangeFilter.module.css +115 -0
  65. flock/frontend/src/components/filters/TimeRangeFilter.test.tsx +154 -0
  66. flock/frontend/src/components/filters/TimeRangeFilter.tsx +110 -0
  67. flock/frontend/src/components/filters/VisibilityFilter.tsx +21 -0
  68. flock/frontend/src/components/graph/AgentNode.test.tsx +77 -0
  69. flock/frontend/src/components/graph/AgentNode.tsx +324 -0
  70. flock/frontend/src/components/graph/GraphCanvas.tsx +613 -0
  71. flock/frontend/src/components/graph/MessageFlowEdge.tsx +128 -0
  72. flock/frontend/src/components/graph/MessageNode.test.tsx +64 -0
  73. flock/frontend/src/components/graph/MessageNode.tsx +129 -0
  74. flock/frontend/src/components/graph/MiniMap.tsx +47 -0
  75. flock/frontend/src/components/graph/TransformEdge.tsx +123 -0
  76. flock/frontend/src/components/layout/DashboardLayout.css +420 -0
  77. flock/frontend/src/components/layout/DashboardLayout.tsx +287 -0
  78. flock/frontend/src/components/layout/Header.module.css +88 -0
  79. flock/frontend/src/components/layout/Header.tsx +52 -0
  80. flock/frontend/src/components/modules/HistoricalArtifactsModule.module.css +288 -0
  81. flock/frontend/src/components/modules/HistoricalArtifactsModule.tsx +450 -0
  82. flock/frontend/src/components/modules/HistoricalArtifactsModuleWrapper.tsx +13 -0
  83. flock/frontend/src/components/modules/JsonAttributeRenderer.tsx +140 -0
  84. flock/frontend/src/components/modules/ModuleRegistry.test.ts +333 -0
  85. flock/frontend/src/components/modules/ModuleRegistry.ts +93 -0
  86. flock/frontend/src/components/modules/ModuleWindow.tsx +223 -0
  87. flock/frontend/src/components/modules/TraceModuleJaeger.tsx +1971 -0
  88. flock/frontend/src/components/modules/TraceModuleJaegerWrapper.tsx +13 -0
  89. flock/frontend/src/components/modules/registerModules.ts +29 -0
  90. flock/frontend/src/components/settings/AdvancedSettings.tsx +175 -0
  91. flock/frontend/src/components/settings/AppearanceSettings.tsx +185 -0
  92. flock/frontend/src/components/settings/GraphSettings.tsx +110 -0
  93. flock/frontend/src/components/settings/MultiSelect.tsx +235 -0
  94. flock/frontend/src/components/settings/SettingsPanel.css +327 -0
  95. flock/frontend/src/components/settings/SettingsPanel.tsx +131 -0
  96. flock/frontend/src/components/settings/ThemeSelector.tsx +298 -0
  97. flock/frontend/src/components/settings/TracingSettings.tsx +404 -0
  98. flock/frontend/src/hooks/useKeyboardShortcuts.ts +148 -0
  99. flock/frontend/src/hooks/useModulePersistence.test.ts +442 -0
  100. flock/frontend/src/hooks/useModulePersistence.ts +154 -0
  101. flock/frontend/src/hooks/useModules.ts +157 -0
  102. flock/frontend/src/hooks/usePersistence.ts +141 -0
  103. flock/frontend/src/main.tsx +13 -0
  104. flock/frontend/src/services/api.ts +337 -0
  105. flock/frontend/src/services/graphService.test.ts +330 -0
  106. flock/frontend/src/services/graphService.ts +75 -0
  107. flock/frontend/src/services/indexeddb.test.ts +793 -0
  108. flock/frontend/src/services/indexeddb.ts +848 -0
  109. flock/frontend/src/services/layout.test.ts +437 -0
  110. flock/frontend/src/services/layout.ts +357 -0
  111. flock/frontend/src/services/themeApplicator.ts +140 -0
  112. flock/frontend/src/services/themeService.ts +77 -0
  113. flock/frontend/src/services/websocket.ts +650 -0
  114. flock/frontend/src/store/filterStore.test.ts +250 -0
  115. flock/frontend/src/store/filterStore.ts +272 -0
  116. flock/frontend/src/store/graphStore.test.ts +570 -0
  117. flock/frontend/src/store/graphStore.ts +462 -0
  118. flock/frontend/src/store/moduleStore.test.ts +253 -0
  119. flock/frontend/src/store/moduleStore.ts +75 -0
  120. flock/frontend/src/store/settingsStore.ts +188 -0
  121. flock/frontend/src/store/streamStore.ts +68 -0
  122. flock/frontend/src/store/uiStore.test.ts +54 -0
  123. flock/frontend/src/store/uiStore.ts +122 -0
  124. flock/frontend/src/store/wsStore.ts +34 -0
  125. flock/frontend/src/styles/index.css +15 -0
  126. flock/frontend/src/styles/scrollbar.css +47 -0
  127. flock/frontend/src/styles/variables.css +488 -0
  128. flock/frontend/src/test/setup.ts +1 -0
  129. flock/frontend/src/types/filters.ts +47 -0
  130. flock/frontend/src/types/graph.ts +95 -0
  131. flock/frontend/src/types/modules.ts +10 -0
  132. flock/frontend/src/types/theme.ts +55 -0
  133. flock/frontend/src/utils/artifacts.ts +24 -0
  134. flock/frontend/src/utils/mockData.ts +98 -0
  135. flock/frontend/src/utils/performance.ts +16 -0
  136. flock/frontend/src/vite-env.d.ts +17 -0
  137. flock/frontend/tsconfig.json +27 -0
  138. flock/frontend/tsconfig.node.json +11 -0
  139. flock/frontend/vite.config.ts +25 -0
  140. flock/frontend/vitest.config.ts +11 -0
  141. flock/{core/util → helper}/cli_helper.py +9 -5
  142. flock/{core/logging → logging}/__init__.py +2 -3
  143. flock/logging/auto_trace.py +159 -0
  144. flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
  145. flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
  146. flock/{core/logging → logging}/formatters/themed_formatter.py +69 -107
  147. flock/{core/logging → logging}/logging.py +78 -61
  148. flock/{core/logging → logging}/telemetry.py +66 -26
  149. flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
  150. flock/logging/telemetry_exporter/duckdb_exporter.py +216 -0
  151. flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +13 -10
  152. flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
  153. flock/logging/trace_and_logged.py +304 -0
  154. flock/mcp/__init__.py +91 -0
  155. flock/{core/mcp/mcp_client.py → mcp/client.py} +131 -158
  156. flock/{core/mcp/mcp_config.py → mcp/config.py} +86 -132
  157. flock/mcp/manager.py +286 -0
  158. flock/mcp/servers/sse/__init__.py +1 -1
  159. flock/mcp/servers/sse/flock_sse_server.py +16 -58
  160. flock/mcp/servers/stdio/__init__.py +1 -1
  161. flock/mcp/servers/stdio/flock_stdio_server.py +13 -53
  162. flock/mcp/servers/streamable_http/flock_streamable_http_server.py +22 -67
  163. flock/mcp/servers/websockets/flock_websocket_server.py +12 -45
  164. flock/{core/mcp/flock_mcp_tool_base.py → mcp/tool.py} +24 -78
  165. flock/mcp/types/__init__.py +42 -0
  166. flock/{core/mcp → mcp}/types/callbacks.py +12 -15
  167. flock/{core/mcp → mcp}/types/factories.py +7 -6
  168. flock/{core/mcp → mcp}/types/handlers.py +13 -18
  169. flock/{core/mcp → mcp}/types/types.py +70 -74
  170. flock/{core/mcp → mcp}/util/helpers.py +3 -3
  171. flock/orchestrator.py +970 -0
  172. flock/registry.py +148 -0
  173. flock/runtime.py +262 -0
  174. flock/service.py +277 -0
  175. flock/store.py +1214 -0
  176. flock/subscription.py +111 -0
  177. flock/themes/andromeda.toml +1 -1
  178. flock/themes/apple-system-colors.toml +1 -1
  179. flock/themes/arcoiris.toml +1 -1
  180. flock/themes/atomonelight.toml +1 -1
  181. flock/themes/ayu copy.toml +1 -1
  182. flock/themes/ayu-light.toml +1 -1
  183. flock/themes/belafonte-day.toml +1 -1
  184. flock/themes/belafonte-night.toml +1 -1
  185. flock/themes/blulocodark.toml +1 -1
  186. flock/themes/breeze.toml +1 -1
  187. flock/themes/broadcast.toml +1 -1
  188. flock/themes/brogrammer.toml +1 -1
  189. flock/themes/builtin-dark.toml +1 -1
  190. flock/themes/builtin-pastel-dark.toml +1 -1
  191. flock/themes/catppuccin-latte.toml +1 -1
  192. flock/themes/catppuccin-macchiato.toml +1 -1
  193. flock/themes/catppuccin-mocha.toml +1 -1
  194. flock/themes/cga.toml +1 -1
  195. flock/themes/chalk.toml +1 -1
  196. flock/themes/ciapre.toml +1 -1
  197. flock/themes/coffee-theme.toml +1 -1
  198. flock/themes/cyberpunkscarletprotocol.toml +1 -1
  199. flock/themes/dark+.toml +1 -1
  200. flock/themes/darkermatrix.toml +1 -1
  201. flock/themes/darkmatrix.toml +2 -2
  202. flock/themes/darkside.toml +1 -1
  203. flock/themes/deep.toml +2 -2
  204. flock/themes/desert.toml +1 -1
  205. flock/themes/django.toml +1 -1
  206. flock/themes/djangosmooth.toml +1 -1
  207. flock/themes/doomone.toml +1 -1
  208. flock/themes/dotgov.toml +1 -1
  209. flock/themes/dracula+.toml +1 -1
  210. flock/themes/duckbones.toml +1 -1
  211. flock/themes/encom.toml +1 -1
  212. flock/themes/espresso.toml +1 -1
  213. flock/themes/everblush.toml +1 -1
  214. flock/themes/fairyfloss.toml +1 -1
  215. flock/themes/fideloper.toml +1 -1
  216. flock/themes/fishtank.toml +1 -1
  217. flock/themes/flexoki-light.toml +1 -1
  218. flock/themes/floraverse.toml +1 -1
  219. flock/themes/framer.toml +1 -1
  220. flock/themes/galizur.toml +1 -1
  221. flock/themes/github.toml +1 -1
  222. flock/themes/grass.toml +1 -1
  223. flock/themes/grey-green.toml +1 -1
  224. flock/themes/gruvboxlight.toml +1 -1
  225. flock/themes/guezwhoz.toml +1 -1
  226. flock/themes/harper.toml +1 -1
  227. flock/themes/hax0r-blue.toml +1 -1
  228. flock/themes/hopscotch.256.toml +1 -1
  229. flock/themes/ic-green-ppl.toml +1 -1
  230. flock/themes/iceberg-dark.toml +1 -1
  231. flock/themes/japanesque.toml +1 -1
  232. flock/themes/jubi.toml +1 -1
  233. flock/themes/kibble.toml +1 -1
  234. flock/themes/kolorit.toml +1 -1
  235. flock/themes/kurokula.toml +1 -1
  236. flock/themes/materialdesigncolors.toml +1 -1
  237. flock/themes/matrix.toml +1 -1
  238. flock/themes/mellifluous.toml +1 -1
  239. flock/themes/midnight-in-mojave.toml +1 -1
  240. flock/themes/monokai-remastered.toml +1 -1
  241. flock/themes/monokai-soda.toml +1 -1
  242. flock/themes/neon.toml +1 -1
  243. flock/themes/neopolitan.toml +5 -5
  244. flock/themes/nord-light.toml +1 -1
  245. flock/themes/ocean.toml +1 -1
  246. flock/themes/onehalfdark.toml +1 -1
  247. flock/themes/onehalflight.toml +1 -1
  248. flock/themes/palenighthc.toml +1 -1
  249. flock/themes/paulmillr.toml +1 -1
  250. flock/themes/pencildark.toml +1 -1
  251. flock/themes/pnevma.toml +1 -1
  252. flock/themes/purple-rain.toml +1 -1
  253. flock/themes/purplepeter.toml +1 -1
  254. flock/themes/raycast-dark.toml +1 -1
  255. flock/themes/red-sands.toml +1 -1
  256. flock/themes/relaxed.toml +1 -1
  257. flock/themes/retro.toml +1 -1
  258. flock/themes/rose-pine.toml +1 -1
  259. flock/themes/royal.toml +1 -1
  260. flock/themes/ryuuko.toml +1 -1
  261. flock/themes/sakura.toml +1 -1
  262. flock/themes/scarlet-protocol.toml +1 -1
  263. flock/themes/seoulbones-dark.toml +1 -1
  264. flock/themes/shades-of-purple.toml +1 -1
  265. flock/themes/smyck.toml +1 -1
  266. flock/themes/softserver.toml +1 -1
  267. flock/themes/solarized-darcula.toml +1 -1
  268. flock/themes/square.toml +1 -1
  269. flock/themes/sugarplum.toml +1 -1
  270. flock/themes/thayer-bright.toml +1 -1
  271. flock/themes/tokyonight.toml +1 -1
  272. flock/themes/tomorrow.toml +1 -1
  273. flock/themes/ubuntu.toml +1 -1
  274. flock/themes/ultradark.toml +1 -1
  275. flock/themes/ultraviolent.toml +1 -1
  276. flock/themes/unikitty.toml +1 -1
  277. flock/themes/urple.toml +1 -1
  278. flock/themes/vesper.toml +1 -1
  279. flock/themes/vimbones.toml +1 -1
  280. flock/themes/wildcherry.toml +1 -1
  281. flock/themes/wilmersdorf.toml +1 -1
  282. flock/themes/wryan.toml +1 -1
  283. flock/themes/xcodedarkhc.toml +1 -1
  284. flock/themes/xcodelight.toml +1 -1
  285. flock/themes/zenbones-light.toml +1 -1
  286. flock/themes/zenwritten-dark.toml +1 -1
  287. flock/utilities.py +301 -0
  288. flock/utility/output_utility_component.py +226 -0
  289. flock/visibility.py +107 -0
  290. flock_core-0.5.0.dist-info/METADATA +964 -0
  291. flock_core-0.5.0.dist-info/RECORD +525 -0
  292. flock_core-0.5.0.dist-info/entry_points.txt +2 -0
  293. {flock_core-0.4.542.dist-info → flock_core-0.5.0.dist-info}/licenses/LICENSE +1 -1
  294. flock/adapter/__init__.py +0 -14
  295. flock/adapter/azure_adapter.py +0 -68
  296. flock/adapter/chroma_adapter.py +0 -73
  297. flock/adapter/faiss_adapter.py +0 -97
  298. flock/adapter/pinecone_adapter.py +0 -51
  299. flock/adapter/vector_base.py +0 -47
  300. flock/cli/assets/release_notes.md +0 -140
  301. flock/cli/config.py +0 -8
  302. flock/cli/constants.py +0 -36
  303. flock/cli/create_agent.py +0 -1
  304. flock/cli/create_flock.py +0 -280
  305. flock/cli/execute_flock.py +0 -620
  306. flock/cli/load_agent.py +0 -1
  307. flock/cli/load_examples.py +0 -1
  308. flock/cli/load_flock.py +0 -192
  309. flock/cli/load_release_notes.py +0 -20
  310. flock/cli/loaded_flock_cli.py +0 -254
  311. flock/cli/manage_agents.py +0 -459
  312. flock/cli/registry_management.py +0 -889
  313. flock/cli/runner.py +0 -41
  314. flock/cli/settings.py +0 -857
  315. flock/cli/utils.py +0 -135
  316. flock/cli/view_results.py +0 -29
  317. flock/cli/yaml_editor.py +0 -396
  318. flock/config.py +0 -56
  319. flock/core/__init__.py +0 -44
  320. flock/core/api/__init__.py +0 -10
  321. flock/core/api/custom_endpoint.py +0 -45
  322. flock/core/api/endpoints.py +0 -262
  323. flock/core/api/main.py +0 -162
  324. flock/core/api/models.py +0 -101
  325. flock/core/api/run_store.py +0 -224
  326. flock/core/api/runner.py +0 -44
  327. flock/core/api/service.py +0 -214
  328. flock/core/config/flock_agent_config.py +0 -11
  329. flock/core/config/scheduled_agent_config.py +0 -40
  330. flock/core/context/context.py +0 -214
  331. flock/core/context/context_manager.py +0 -40
  332. flock/core/context/context_vars.py +0 -11
  333. flock/core/evaluation/utils.py +0 -395
  334. flock/core/execution/batch_executor.py +0 -369
  335. flock/core/execution/evaluation_executor.py +0 -438
  336. flock/core/execution/local_executor.py +0 -31
  337. flock/core/execution/opik_executor.py +0 -103
  338. flock/core/execution/temporal_executor.py +0 -166
  339. flock/core/flock.py +0 -1003
  340. flock/core/flock_agent.py +0 -1258
  341. flock/core/flock_evaluator.py +0 -60
  342. flock/core/flock_factory.py +0 -513
  343. flock/core/flock_module.py +0 -207
  344. flock/core/flock_registry.py +0 -702
  345. flock/core/flock_router.py +0 -83
  346. flock/core/flock_scheduler.py +0 -166
  347. flock/core/flock_server_manager.py +0 -136
  348. flock/core/interpreter/python_interpreter.py +0 -689
  349. flock/core/logging/live_capture.py +0 -137
  350. flock/core/logging/trace_and_logged.py +0 -59
  351. flock/core/mcp/__init__.py +0 -1
  352. flock/core/mcp/flock_mcp_server.py +0 -640
  353. flock/core/mcp/mcp_client_manager.py +0 -201
  354. flock/core/mcp/types/__init__.py +0 -1
  355. flock/core/mixin/dspy_integration.py +0 -445
  356. flock/core/mixin/prompt_parser.py +0 -125
  357. flock/core/serialization/__init__.py +0 -13
  358. flock/core/serialization/callable_registry.py +0 -52
  359. flock/core/serialization/flock_serializer.py +0 -854
  360. flock/core/serialization/json_encoder.py +0 -41
  361. flock/core/serialization/secure_serializer.py +0 -175
  362. flock/core/serialization/serializable.py +0 -342
  363. flock/core/serialization/serialization_utils.py +0 -409
  364. flock/core/util/file_path_utils.py +0 -223
  365. flock/core/util/hydrator.py +0 -309
  366. flock/core/util/input_resolver.py +0 -141
  367. flock/core/util/loader.py +0 -59
  368. flock/core/util/splitter.py +0 -219
  369. flock/di.py +0 -41
  370. flock/evaluators/__init__.py +0 -1
  371. flock/evaluators/declarative/__init__.py +0 -1
  372. flock/evaluators/declarative/declarative_evaluator.py +0 -217
  373. flock/evaluators/memory/memory_evaluator.py +0 -90
  374. flock/evaluators/test/test_case_evaluator.py +0 -38
  375. flock/evaluators/zep/zep_evaluator.py +0 -59
  376. flock/modules/__init__.py +0 -1
  377. flock/modules/assertion/__init__.py +0 -1
  378. flock/modules/assertion/assertion_module.py +0 -286
  379. flock/modules/callback/__init__.py +0 -1
  380. flock/modules/callback/callback_module.py +0 -91
  381. flock/modules/enterprise_memory/README.md +0 -99
  382. flock/modules/enterprise_memory/enterprise_memory_module.py +0 -526
  383. flock/modules/mem0/__init__.py +0 -1
  384. flock/modules/mem0/mem0_module.py +0 -126
  385. flock/modules/mem0_async/__init__.py +0 -1
  386. flock/modules/mem0_async/async_mem0_module.py +0 -126
  387. flock/modules/memory/__init__.py +0 -1
  388. flock/modules/memory/memory_module.py +0 -429
  389. flock/modules/memory/memory_parser.py +0 -125
  390. flock/modules/memory/memory_storage.py +0 -736
  391. flock/modules/output/__init__.py +0 -1
  392. flock/modules/output/output_module.py +0 -196
  393. flock/modules/performance/__init__.py +0 -1
  394. flock/modules/performance/metrics_module.py +0 -678
  395. flock/modules/zep/__init__.py +0 -1
  396. flock/modules/zep/zep_module.py +0 -192
  397. flock/platform/docker_tools.py +0 -49
  398. flock/platform/jaeger_install.py +0 -86
  399. flock/routers/__init__.py +0 -1
  400. flock/routers/agent/__init__.py +0 -1
  401. flock/routers/agent/agent_router.py +0 -236
  402. flock/routers/agent/handoff_agent.py +0 -58
  403. flock/routers/conditional/conditional_router.py +0 -486
  404. flock/routers/default/__init__.py +0 -1
  405. flock/routers/default/default_router.py +0 -80
  406. flock/routers/feedback/feedback_router.py +0 -114
  407. flock/routers/list_generator/list_generator_router.py +0 -166
  408. flock/routers/llm/__init__.py +0 -1
  409. flock/routers/llm/llm_router.py +0 -365
  410. flock/tools/__init__.py +0 -0
  411. flock/tools/azure_tools.py +0 -781
  412. flock/tools/code_tools.py +0 -167
  413. flock/tools/file_tools.py +0 -149
  414. flock/tools/github_tools.py +0 -157
  415. flock/tools/markdown_tools.py +0 -205
  416. flock/tools/system_tools.py +0 -9
  417. flock/tools/text_tools.py +0 -810
  418. flock/tools/web_tools.py +0 -92
  419. flock/tools/zendesk_tools.py +0 -501
  420. flock/webapp/__init__.py +0 -1
  421. flock/webapp/app/__init__.py +0 -0
  422. flock/webapp/app/api/__init__.py +0 -0
  423. flock/webapp/app/api/agent_management.py +0 -237
  424. flock/webapp/app/api/execution.py +0 -503
  425. flock/webapp/app/api/flock_management.py +0 -125
  426. flock/webapp/app/api/registry_viewer.py +0 -29
  427. flock/webapp/app/chat.py +0 -662
  428. flock/webapp/app/config.py +0 -104
  429. flock/webapp/app/dependencies.py +0 -117
  430. flock/webapp/app/main.py +0 -1086
  431. flock/webapp/app/middleware.py +0 -113
  432. flock/webapp/app/models_ui.py +0 -7
  433. flock/webapp/app/services/__init__.py +0 -0
  434. flock/webapp/app/services/feedback_file_service.py +0 -363
  435. flock/webapp/app/services/flock_service.py +0 -345
  436. flock/webapp/app/services/sharing_models.py +0 -81
  437. flock/webapp/app/services/sharing_store.py +0 -597
  438. flock/webapp/app/templates/theme_mapper.html +0 -326
  439. flock/webapp/app/theme_mapper.py +0 -811
  440. flock/webapp/app/utils.py +0 -85
  441. flock/webapp/run.py +0 -219
  442. flock/webapp/static/css/chat.css +0 -301
  443. flock/webapp/static/css/components.css +0 -167
  444. flock/webapp/static/css/header.css +0 -39
  445. flock/webapp/static/css/layout.css +0 -281
  446. flock/webapp/static/css/sidebar.css +0 -127
  447. flock/webapp/static/css/two-pane.css +0 -48
  448. flock/webapp/templates/base.html +0 -389
  449. flock/webapp/templates/chat.html +0 -152
  450. flock/webapp/templates/chat_settings.html +0 -19
  451. flock/webapp/templates/flock_editor.html +0 -16
  452. flock/webapp/templates/index.html +0 -12
  453. flock/webapp/templates/partials/_agent_detail_form.html +0 -93
  454. flock/webapp/templates/partials/_agent_list.html +0 -18
  455. flock/webapp/templates/partials/_agent_manager_view.html +0 -51
  456. flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
  457. flock/webapp/templates/partials/_chat_container.html +0 -15
  458. flock/webapp/templates/partials/_chat_messages.html +0 -57
  459. flock/webapp/templates/partials/_chat_settings_form.html +0 -85
  460. flock/webapp/templates/partials/_create_flock_form.html +0 -50
  461. flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
  462. flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
  463. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
  464. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
  465. flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
  466. flock/webapp/templates/partials/_env_vars_table.html +0 -23
  467. flock/webapp/templates/partials/_execution_form.html +0 -127
  468. flock/webapp/templates/partials/_execution_view_container.html +0 -28
  469. flock/webapp/templates/partials/_flock_file_list.html +0 -23
  470. flock/webapp/templates/partials/_flock_properties_form.html +0 -52
  471. flock/webapp/templates/partials/_flock_upload_form.html +0 -16
  472. flock/webapp/templates/partials/_header_flock_status.html +0 -5
  473. flock/webapp/templates/partials/_live_logs.html +0 -13
  474. flock/webapp/templates/partials/_load_manager_view.html +0 -49
  475. flock/webapp/templates/partials/_registry_table.html +0 -25
  476. flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
  477. flock/webapp/templates/partials/_results_display.html +0 -78
  478. flock/webapp/templates/partials/_settings_env_content.html +0 -9
  479. flock/webapp/templates/partials/_settings_theme_content.html +0 -14
  480. flock/webapp/templates/partials/_settings_view.html +0 -36
  481. flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
  482. flock/webapp/templates/partials/_share_link_snippet.html +0 -35
  483. flock/webapp/templates/partials/_sidebar.html +0 -74
  484. flock/webapp/templates/partials/_structured_data_view.html +0 -40
  485. flock/webapp/templates/partials/_theme_preview.html +0 -36
  486. flock/webapp/templates/registry_viewer.html +0 -84
  487. flock/webapp/templates/shared_run_page.html +0 -140
  488. flock/workflow/__init__.py +0 -0
  489. flock/workflow/activities.py +0 -237
  490. flock/workflow/agent_activities.py +0 -24
  491. flock/workflow/agent_execution_activity.py +0 -240
  492. flock/workflow/flock_workflow.py +0 -225
  493. flock/workflow/temporal_config.py +0 -96
  494. flock/workflow/temporal_setup.py +0 -60
  495. flock_core-0.4.542.dist-info/METADATA +0 -676
  496. flock_core-0.4.542.dist-info/RECORD +0 -572
  497. flock_core-0.4.542.dist-info/entry_points.txt +0 -2
  498. /flock/{core/logging → logging}/formatters/themes.py +0 -0
  499. /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
  500. /flock/{core/mcp → mcp}/util/__init__.py +0 -0
  501. {flock_core-0.4.542.dist-info → flock_core-0.5.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,991 @@
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.graph_builder import GraphAssembler
24
+ from flock.dashboard.models.graph import GraphRequest, GraphSnapshot
25
+ from flock.dashboard.websocket import WebSocketManager
26
+ from flock.logging.logging import get_logger
27
+ from flock.orchestrator import Flock
28
+ from flock.registry import type_registry
29
+ from flock.service import BlackboardHTTPService
30
+
31
+
32
+ logger = get_logger("dashboard.service")
33
+
34
+
35
+ class DashboardHTTPService(BlackboardHTTPService):
36
+ """HTTP service with WebSocket support for real-time dashboard.
37
+
38
+ Extends BlackboardHTTPService to add:
39
+ - WebSocket endpoint at /ws for real-time event streaming
40
+ - Static file serving for dashboard frontend
41
+ - Integration with DashboardEventCollector
42
+ - Optional CORS middleware for development
43
+ """
44
+
45
+ def __init__(
46
+ self,
47
+ orchestrator: Flock,
48
+ websocket_manager: WebSocketManager | None = None,
49
+ event_collector: DashboardEventCollector | None = None,
50
+ *,
51
+ use_v2: bool = False,
52
+ ) -> None:
53
+ """Initialize DashboardHTTPService.
54
+
55
+ Args:
56
+ orchestrator: Flock orchestrator instance
57
+ websocket_manager: Optional WebSocketManager (creates new if not provided)
58
+ event_collector: Optional DashboardEventCollector (creates new if not provided)
59
+ """
60
+ # Initialize base service
61
+ super().__init__(orchestrator)
62
+
63
+ # Initialize WebSocket manager and event collector
64
+ self.websocket_manager = websocket_manager or WebSocketManager()
65
+ self.event_collector = event_collector or DashboardEventCollector(
66
+ store=self.orchestrator.store
67
+ )
68
+ self.use_v2 = use_v2
69
+
70
+ # Integrate collector with WebSocket manager
71
+ self.event_collector.set_websocket_manager(self.websocket_manager)
72
+
73
+ # Graph assembler powers both dashboards by default
74
+ self.graph_assembler: GraphAssembler | None = GraphAssembler(
75
+ self.orchestrator.store, self.event_collector, self.orchestrator
76
+ )
77
+
78
+ # Configure CORS if DASHBOARD_DEV environment variable is set
79
+ if os.environ.get("DASHBOARD_DEV") == "1":
80
+ logger.info("DASHBOARD_DEV mode enabled - adding CORS middleware")
81
+ self.app.add_middleware(
82
+ CORSMiddleware,
83
+ allow_origins=["*"], # Allow all origins in dev mode
84
+ allow_credentials=True,
85
+ allow_methods=["*"],
86
+ allow_headers=["*"],
87
+ )
88
+
89
+ # IMPORTANT: Register API routes BEFORE static files!
90
+ # Static file mount acts as catch-all and must be last
91
+ self._register_control_routes()
92
+ self._register_theme_routes()
93
+ self._register_dashboard_routes()
94
+
95
+ logger.info("DashboardHTTPService initialized")
96
+
97
+ def _register_dashboard_routes(self) -> None:
98
+ """Register WebSocket endpoint and static file serving."""
99
+ app = self.app
100
+
101
+ @app.websocket("/ws")
102
+ async def websocket_endpoint(websocket: WebSocket) -> None:
103
+ """WebSocket endpoint for real-time dashboard events.
104
+
105
+ Handles connection lifecycle:
106
+ 1. Accept connection
107
+ 2. Add to WebSocketManager pool
108
+ 3. Keep connection alive
109
+ 4. Handle disconnection gracefully
110
+ """
111
+ await websocket.accept()
112
+ await self.websocket_manager.add_client(websocket)
113
+
114
+ try:
115
+ # Keep connection alive and handle incoming messages
116
+ # Dashboard clients may send heartbeat responses or control messages
117
+ while True:
118
+ # Wait for messages from client (pong responses, etc.)
119
+ try:
120
+ data = await websocket.receive_text()
121
+ # Handle client messages if needed (e.g., pong responses)
122
+ # For Phase 3, we primarily broadcast from server to client
123
+ logger.debug(f"Received message from client: {data[:100]}")
124
+ except WebSocketDisconnect:
125
+ logger.info("WebSocket client disconnected")
126
+ break
127
+ except Exception as e:
128
+ logger.warning(f"Error receiving WebSocket message: {e}")
129
+ break
130
+
131
+ except Exception as e:
132
+ logger.exception(f"WebSocket endpoint error: {e}")
133
+ finally:
134
+ # Clean up: remove client from pool
135
+ await self.websocket_manager.remove_client(websocket)
136
+
137
+ if self.graph_assembler is not None:
138
+
139
+ @app.post("/api/dashboard/graph", response_model=GraphSnapshot)
140
+ async def get_dashboard_graph(request: GraphRequest) -> GraphSnapshot:
141
+ """Return server-side assembled dashboard graph snapshot."""
142
+ return await self.graph_assembler.build_snapshot(request)
143
+
144
+ dashboard_dir = Path(__file__).parent
145
+ frontend_root = dashboard_dir.parent / ("frontend_v2" if self.use_v2 else "frontend")
146
+ static_dir = dashboard_dir / ("static_v2" if self.use_v2 else "static")
147
+
148
+ possible_dirs = [
149
+ static_dir,
150
+ frontend_root / "dist",
151
+ frontend_root / "build",
152
+ ]
153
+
154
+ for dir_path in possible_dirs:
155
+ if dir_path.exists() and dir_path.is_dir():
156
+ logger.info(f"Mounting static files from: {dir_path}")
157
+ # Mount at root to serve index.html and other frontend assets
158
+ app.mount(
159
+ "/",
160
+ StaticFiles(directory=str(dir_path), html=True),
161
+ name="dashboard-static",
162
+ )
163
+ break
164
+ else:
165
+ logger.warning(
166
+ f"No static directory found for dashboard frontend (expected one of: {possible_dirs})."
167
+ )
168
+
169
+ def _register_control_routes(self) -> None:
170
+ """Register control API endpoints for dashboard operations."""
171
+ app = self.app
172
+ orchestrator = self.orchestrator
173
+
174
+ @app.get("/api/artifact-types")
175
+ async def get_artifact_types() -> dict[str, Any]:
176
+ """Get all registered artifact types with their schemas.
177
+
178
+ Returns:
179
+ {
180
+ "artifact_types": [
181
+ {
182
+ "name": "TypeName",
183
+ "schema": {...}
184
+ },
185
+ ...
186
+ ]
187
+ }
188
+ """
189
+ artifact_types = []
190
+
191
+ for type_name in type_registry._by_name:
192
+ try:
193
+ model_class = type_registry.resolve(type_name)
194
+ # Get Pydantic schema
195
+ schema = model_class.model_json_schema()
196
+ artifact_types.append({"name": type_name, "schema": schema})
197
+ except Exception as e:
198
+ logger.warning(f"Could not get schema for {type_name}: {e}")
199
+
200
+ return {"artifact_types": artifact_types}
201
+
202
+ @app.get("/api/agents")
203
+ async def get_agents() -> dict[str, Any]:
204
+ """Get all registered agents.
205
+
206
+ Returns:
207
+ {
208
+ "agents": [
209
+ {
210
+ "name": "agent_name",
211
+ "description": "...",
212
+ "status": "ready",
213
+ "subscriptions": ["TypeA", "TypeB"],
214
+ "output_types": ["TypeC", "TypeD"]
215
+ },
216
+ ...
217
+ ]
218
+ }
219
+ """
220
+ agents = []
221
+
222
+ for agent in orchestrator.agents:
223
+ # Extract consumed types from agent subscriptions
224
+ consumed_types = []
225
+ for sub in agent.subscriptions:
226
+ consumed_types.extend(sub.type_names)
227
+
228
+ # Extract produced types from agent outputs
229
+ produced_types = [output.spec.type_name for output in agent.outputs]
230
+
231
+ agents.append(
232
+ {
233
+ "name": agent.name,
234
+ "description": agent.description or "",
235
+ "status": "ready",
236
+ "subscriptions": consumed_types,
237
+ "output_types": produced_types,
238
+ }
239
+ )
240
+
241
+ return {"agents": agents}
242
+
243
+ @app.get("/api/version")
244
+ async def get_version() -> dict[str, str]:
245
+ """Get version information for the backend and dashboard.
246
+
247
+ Returns:
248
+ {
249
+ "backend_version": "0.1.18",
250
+ "package_name": "flock-flow"
251
+ }
252
+ """
253
+ try:
254
+ backend_version = version("flock-flow")
255
+ except PackageNotFoundError:
256
+ # Fallback version if package not installed
257
+ backend_version = "0.2.0-dev"
258
+
259
+ return {"backend_version": backend_version, "package_name": "flock-flow"}
260
+
261
+ @app.post("/api/control/publish")
262
+ async def publish_artifact(body: dict[str, Any]) -> dict[str, str]:
263
+ """Publish artifact with correlation tracking.
264
+
265
+ Request body:
266
+ {
267
+ "artifact_type": "TypeName",
268
+ "content": {"field": "value", ...}
269
+ }
270
+
271
+ Returns:
272
+ {
273
+ "correlation_id": "<uuid>",
274
+ "published_at": "<iso-timestamp>"
275
+ }
276
+ """
277
+ # Validate required fields
278
+ artifact_type = body.get("artifact_type")
279
+ content = body.get("content")
280
+
281
+ if not artifact_type:
282
+ raise HTTPException(status_code=400, detail="artifact_type is required")
283
+ if content is None:
284
+ raise HTTPException(status_code=400, detail="content is required")
285
+
286
+ try:
287
+ # Resolve type from registry
288
+ model_class = type_registry.resolve(artifact_type)
289
+
290
+ # Validate content against Pydantic schema
291
+ try:
292
+ instance = model_class(**content)
293
+ except ValidationError as e:
294
+ raise HTTPException(status_code=422, detail=f"Validation error: {e!s}")
295
+
296
+ # Generate correlation ID
297
+ correlation_id = str(uuid4())
298
+
299
+ # Publish to orchestrator
300
+ artifact = await orchestrator.publish(
301
+ instance, correlation_id=correlation_id, is_dashboard=True
302
+ )
303
+
304
+ # Phase 11 Fix: Emit message_published event for dashboard visibility
305
+ # This enables virtual "orchestrator" agent to appear in both Agent View and Blackboard View
306
+ event = MessagePublishedEvent(
307
+ correlation_id=str(artifact.correlation_id),
308
+ artifact_id=str(artifact.id),
309
+ artifact_type=artifact.type,
310
+ produced_by=artifact.produced_by, # Will be "orchestrator" or similar for non-agent publishers
311
+ payload=artifact.payload,
312
+ visibility=VisibilitySpec(
313
+ kind="Public"
314
+ ), # Dashboard-published artifacts are public by default
315
+ tags=list(artifact.tags) if artifact.tags else [],
316
+ partition_key=artifact.partition_key,
317
+ version=artifact.version,
318
+ consumers=[], # Will be populated by subscription matching in frontend
319
+ )
320
+ await self.websocket_manager.broadcast(event)
321
+
322
+ return {
323
+ "correlation_id": str(artifact.correlation_id),
324
+ "published_at": artifact.created_at.isoformat(),
325
+ }
326
+
327
+ except KeyError:
328
+ raise HTTPException(
329
+ status_code=422, detail=f"Unknown artifact type: {artifact_type}"
330
+ )
331
+ except Exception as e:
332
+ logger.exception(f"Error publishing artifact: {e}")
333
+ raise HTTPException(status_code=500, detail=str(e))
334
+
335
+ @app.post("/api/control/invoke")
336
+ async def invoke_agent(body: dict[str, Any]) -> dict[str, Any]:
337
+ """Directly invoke a specific agent.
338
+
339
+ Request body:
340
+ {
341
+ "agent_name": "agent_name",
342
+ "input": {"type": "TypeName", "field": "value", ...}
343
+ }
344
+
345
+ Returns:
346
+ {
347
+ "invocation_id": "<uuid>",
348
+ "result": "success"
349
+ }
350
+ """
351
+ # Validate required fields
352
+ agent_name = body.get("agent_name")
353
+ input_data = body.get("input")
354
+
355
+ if not agent_name:
356
+ raise HTTPException(status_code=400, detail="agent_name is required")
357
+ if input_data is None:
358
+ raise HTTPException(status_code=400, detail="input is required")
359
+
360
+ try:
361
+ # Get agent from orchestrator
362
+ agent = orchestrator.get_agent(agent_name)
363
+ except KeyError:
364
+ raise HTTPException(status_code=404, detail=f"Agent not found: {agent_name}")
365
+
366
+ try:
367
+ # Parse input type and create instance
368
+ input_type = input_data.get("type")
369
+ if not input_type:
370
+ raise HTTPException(status_code=400, detail="input.type is required")
371
+
372
+ # Resolve type from registry
373
+ model_class = type_registry.resolve(input_type)
374
+
375
+ # Create payload by removing 'type' key
376
+ payload = {k: v for k, v in input_data.items() if k != "type"}
377
+
378
+ # Validate and create instance
379
+ try:
380
+ instance = model_class(**payload)
381
+ except ValidationError as e:
382
+ raise HTTPException(status_code=422, detail=f"Validation error: {e!s}")
383
+
384
+ # Invoke agent
385
+ outputs = await orchestrator.invoke(agent, instance)
386
+
387
+ # Generate invocation ID from first output or create new UUID
388
+ invocation_id = str(outputs[0].id) if outputs else str(uuid4())
389
+
390
+ # Extract correlation_id from first output (for filter automation)
391
+ correlation_id = (
392
+ str(outputs[0].correlation_id)
393
+ if outputs and outputs[0].correlation_id
394
+ else None
395
+ )
396
+
397
+ return {
398
+ "invocation_id": invocation_id,
399
+ "correlation_id": correlation_id,
400
+ "result": "success",
401
+ }
402
+
403
+ except HTTPException:
404
+ raise
405
+ except KeyError:
406
+ raise HTTPException(status_code=422, detail=f"Unknown type: {input_type}")
407
+ except Exception as e:
408
+ logger.exception(f"Error invoking agent: {e}")
409
+ raise HTTPException(status_code=500, detail=str(e))
410
+
411
+ @app.post("/api/control/pause")
412
+ async def pause_orchestrator() -> dict[str, Any]:
413
+ """Pause orchestrator (placeholder).
414
+
415
+ Returns:
416
+ 501 Not Implemented
417
+ """
418
+ raise HTTPException(status_code=501, detail="Pause functionality coming in Phase 12")
419
+
420
+ @app.post("/api/control/resume")
421
+ async def resume_orchestrator() -> dict[str, Any]:
422
+ """Resume orchestrator (placeholder).
423
+
424
+ Returns:
425
+ 501 Not Implemented
426
+ """
427
+ raise HTTPException(status_code=501, detail="Resume functionality coming in Phase 12")
428
+
429
+ @app.get("/api/traces")
430
+ async def get_traces() -> list[dict[str, Any]]:
431
+ """Get OpenTelemetry traces from DuckDB.
432
+
433
+ Returns list of trace spans in OTEL format.
434
+
435
+ Returns:
436
+ [
437
+ {
438
+ "name": "Agent.execute",
439
+ "context": {
440
+ "trace_id": "...",
441
+ "span_id": "...",
442
+ ...
443
+ },
444
+ "start_time": 1234567890,
445
+ "end_time": 1234567891,
446
+ "attributes": {...},
447
+ "status": {...}
448
+ },
449
+ ...
450
+ ]
451
+ """
452
+ import json
453
+ from pathlib import Path
454
+
455
+ import duckdb
456
+
457
+ db_path = Path(".flock/traces.duckdb")
458
+
459
+ if not db_path.exists():
460
+ logger.warning(
461
+ "Trace database not found. Make sure FLOCK_AUTO_TRACE=true FLOCK_TRACE_FILE=true"
462
+ )
463
+ return []
464
+
465
+ try:
466
+ with duckdb.connect(str(db_path), read_only=True) as conn:
467
+ # Query all spans from DuckDB
468
+ result = conn.execute("""
469
+ SELECT
470
+ trace_id, span_id, parent_id, name, service, operation,
471
+ kind, start_time, end_time, duration_ms,
472
+ status_code, status_description,
473
+ attributes, events, links, resource
474
+ FROM spans
475
+ ORDER BY start_time DESC
476
+ """).fetchall()
477
+
478
+ spans = []
479
+ for row in result:
480
+ # Reconstruct OTEL span format from DuckDB row
481
+ span = {
482
+ "name": row[3], # name
483
+ "context": {
484
+ "trace_id": row[0], # trace_id
485
+ "span_id": row[1], # span_id
486
+ "trace_flags": 0,
487
+ "trace_state": "",
488
+ },
489
+ "kind": row[6], # kind
490
+ "start_time": row[7], # start_time
491
+ "end_time": row[8], # end_time
492
+ "status": {
493
+ "status_code": row[10], # status_code
494
+ "description": row[11], # status_description
495
+ },
496
+ "attributes": json.loads(row[12]) if row[12] else {}, # attributes
497
+ "events": json.loads(row[13]) if row[13] else [], # events
498
+ "links": json.loads(row[14]) if row[14] else [], # links
499
+ "resource": json.loads(row[15]) if row[15] else {}, # resource
500
+ }
501
+
502
+ # Add parent_id if exists
503
+ if row[2]: # parent_id
504
+ span["parent_id"] = row[2]
505
+
506
+ spans.append(span)
507
+
508
+ logger.debug(f"Loaded {len(spans)} spans from DuckDB")
509
+ return spans
510
+
511
+ except Exception as e:
512
+ logger.exception(f"Error reading traces from DuckDB: {e}")
513
+ return []
514
+
515
+ @app.get("/api/traces/services")
516
+ async def get_trace_services() -> dict[str, Any]:
517
+ """Get list of unique services that have been traced.
518
+
519
+ Returns:
520
+ {
521
+ "services": ["Flock", "Agent", "DSPyEngine", ...],
522
+ "operations": ["Flock.publish", "Agent.execute", ...]
523
+ }
524
+ """
525
+ from pathlib import Path
526
+
527
+ import duckdb
528
+
529
+ db_path = Path(".flock/traces.duckdb")
530
+
531
+ if not db_path.exists():
532
+ return {"services": [], "operations": []}
533
+
534
+ try:
535
+ with duckdb.connect(str(db_path), read_only=True) as conn:
536
+ # Get unique services
537
+ services_result = conn.execute("""
538
+ SELECT DISTINCT service
539
+ FROM spans
540
+ WHERE service IS NOT NULL
541
+ ORDER BY service
542
+ """).fetchall()
543
+
544
+ # Get unique operations
545
+ operations_result = conn.execute("""
546
+ SELECT DISTINCT name
547
+ FROM spans
548
+ WHERE name IS NOT NULL
549
+ ORDER BY name
550
+ """).fetchall()
551
+
552
+ return {
553
+ "services": [row[0] for row in services_result],
554
+ "operations": [row[0] for row in operations_result],
555
+ }
556
+
557
+ except Exception as e:
558
+ logger.exception(f"Error reading trace services: {e}")
559
+ return {"services": [], "operations": []}
560
+
561
+ @app.post("/api/traces/clear")
562
+ async def clear_traces() -> dict[str, Any]:
563
+ """Clear all traces from DuckDB database.
564
+
565
+ Returns:
566
+ {
567
+ "success": true,
568
+ "deleted_count": 123,
569
+ "error": null
570
+ }
571
+ """
572
+ result = Flock.clear_traces()
573
+ if result["success"]:
574
+ logger.info(f"Cleared {result['deleted_count']} trace spans via API")
575
+ else:
576
+ logger.error(f"Failed to clear traces: {result['error']}")
577
+
578
+ return result
579
+
580
+ @app.post("/api/traces/query")
581
+ async def execute_trace_query(request: dict[str, Any]) -> dict[str, Any]:
582
+ """
583
+ Execute a DuckDB SQL query on the traces database.
584
+
585
+ Security: Only SELECT queries allowed, rate-limited.
586
+ """
587
+ from pathlib import Path
588
+
589
+ import duckdb
590
+
591
+ query = request.get("query", "").strip()
592
+
593
+ if not query:
594
+ return {"error": "Query cannot be empty", "results": [], "columns": []}
595
+
596
+ # Security: Only allow SELECT queries
597
+ query_upper = query.upper().strip()
598
+ if not query_upper.startswith("SELECT"):
599
+ return {"error": "Only SELECT queries are allowed", "results": [], "columns": []}
600
+
601
+ # Check for dangerous keywords
602
+ dangerous = ["DROP", "DELETE", "INSERT", "UPDATE", "ALTER", "CREATE", "TRUNCATE"]
603
+ if any(keyword in query_upper for keyword in dangerous):
604
+ return {
605
+ "error": "Query contains forbidden operations",
606
+ "results": [],
607
+ "columns": [],
608
+ }
609
+
610
+ db_path = Path(".flock/traces.duckdb")
611
+ if not db_path.exists():
612
+ return {"error": "Trace database not found", "results": [], "columns": []}
613
+
614
+ try:
615
+ with duckdb.connect(str(db_path), read_only=True) as conn:
616
+ result = conn.execute(query).fetchall()
617
+ columns = [desc[0] for desc in conn.description] if conn.description else []
618
+
619
+ # Convert to JSON-serializable format
620
+ results = []
621
+ for row in result:
622
+ row_dict = {}
623
+ for i, col in enumerate(columns):
624
+ val = row[i]
625
+ # Convert bytes to string, handle other types
626
+ if isinstance(val, bytes):
627
+ row_dict[col] = val.decode("utf-8")
628
+ else:
629
+ row_dict[col] = val
630
+ results.append(row_dict)
631
+
632
+ return {"results": results, "columns": columns, "row_count": len(results)}
633
+ except Exception as e:
634
+ logger.exception(f"DuckDB query error: {e}")
635
+ return {"error": str(e), "results": [], "columns": []}
636
+
637
+ @app.get("/api/traces/stats")
638
+ async def get_trace_stats() -> dict[str, Any]:
639
+ """Get statistics about the trace database.
640
+
641
+ Returns:
642
+ {
643
+ "total_spans": 123,
644
+ "total_traces": 45,
645
+ "services_count": 5,
646
+ "oldest_trace": "2025-10-07T12:00:00Z",
647
+ "newest_trace": "2025-10-07T14:30:00Z",
648
+ "database_size_mb": 12.5
649
+ }
650
+ """
651
+ from datetime import datetime
652
+ from pathlib import Path
653
+
654
+ import duckdb
655
+
656
+ db_path = Path(".flock/traces.duckdb")
657
+
658
+ if not db_path.exists():
659
+ return {
660
+ "total_spans": 0,
661
+ "total_traces": 0,
662
+ "services_count": 0,
663
+ "oldest_trace": None,
664
+ "newest_trace": None,
665
+ "database_size_mb": 0,
666
+ }
667
+
668
+ try:
669
+ with duckdb.connect(str(db_path), read_only=True) as conn:
670
+ # Get total spans
671
+ total_spans = conn.execute("SELECT COUNT(*) FROM spans").fetchone()[0]
672
+
673
+ # Get total unique traces
674
+ total_traces = conn.execute(
675
+ "SELECT COUNT(DISTINCT trace_id) FROM spans"
676
+ ).fetchone()[0]
677
+
678
+ # Get services count
679
+ services_count = conn.execute(
680
+ "SELECT COUNT(DISTINCT service) FROM spans WHERE service IS NOT NULL"
681
+ ).fetchone()[0]
682
+
683
+ # Get time range
684
+ time_range = conn.execute("""
685
+ SELECT
686
+ MIN(start_time) as oldest,
687
+ MAX(start_time) as newest
688
+ FROM spans
689
+ """).fetchone()
690
+
691
+ oldest_trace = None
692
+ newest_trace = None
693
+ if time_range and time_range[0]:
694
+ # Convert nanoseconds to datetime
695
+ oldest_trace = datetime.fromtimestamp(
696
+ time_range[0] / 1_000_000_000
697
+ ).isoformat()
698
+ newest_trace = datetime.fromtimestamp(
699
+ time_range[1] / 1_000_000_000
700
+ ).isoformat()
701
+
702
+ # Get file size
703
+ size_mb = db_path.stat().st_size / (1024 * 1024)
704
+
705
+ return {
706
+ "total_spans": total_spans,
707
+ "total_traces": total_traces,
708
+ "services_count": services_count,
709
+ "oldest_trace": oldest_trace,
710
+ "newest_trace": newest_trace,
711
+ "database_size_mb": round(size_mb, 2),
712
+ }
713
+
714
+ except Exception as e:
715
+ logger.exception(f"Error reading trace stats: {e}")
716
+ return {
717
+ "total_spans": 0,
718
+ "total_traces": 0,
719
+ "services_count": 0,
720
+ "oldest_trace": None,
721
+ "newest_trace": None,
722
+ "database_size_mb": 0,
723
+ }
724
+
725
+ @app.get("/api/streaming-history/{agent_name}")
726
+ async def get_streaming_history(agent_name: str) -> dict[str, Any]:
727
+ """Get historical streaming output for a specific agent.
728
+
729
+ Args:
730
+ agent_name: Name of the agent to get streaming history for
731
+
732
+ Returns:
733
+ {
734
+ "agent_name": "agent_name",
735
+ "events": [
736
+ {
737
+ "correlation_id": "...",
738
+ "timestamp": "...",
739
+ "agent_name": "...",
740
+ "run_id": "...",
741
+ "output_type": "llm_token",
742
+ "content": "...",
743
+ "sequence": 0,
744
+ "is_final": false
745
+ },
746
+ ...
747
+ ]
748
+ }
749
+ """
750
+ try:
751
+ history = self.websocket_manager.get_streaming_history(agent_name)
752
+ return {
753
+ "agent_name": agent_name,
754
+ "events": [event.model_dump() for event in history],
755
+ }
756
+ except Exception as e:
757
+ logger.exception(f"Failed to get streaming history for {agent_name}: {e}")
758
+ raise HTTPException(
759
+ status_code=500, detail=f"Failed to get streaming history: {e!s}"
760
+ )
761
+
762
+ @app.get("/api/artifacts/history/{node_id}")
763
+ async def get_message_history(node_id: str) -> dict[str, Any]:
764
+ """Get complete message history for a node (both produced and consumed).
765
+
766
+ Phase 4.1 Feature Gap Fix: Returns both messages produced by AND consumed by
767
+ the specified node, enabling complete message history view in MessageHistoryTab.
768
+
769
+ Args:
770
+ node_id: ID of the node (agent name or message ID)
771
+
772
+ Returns:
773
+ {
774
+ "node_id": "agent_name",
775
+ "messages": [
776
+ {
777
+ "id": "artifact-uuid",
778
+ "type": "ArtifactType",
779
+ "direction": "published"|"consumed",
780
+ "payload": {...},
781
+ "timestamp": "2025-10-11T...",
782
+ "correlation_id": "uuid",
783
+ "produced_by": "producer_name",
784
+ "consumed_at": "2025-10-11T..." (only for consumed)
785
+ },
786
+ ...
787
+ ],
788
+ "total": 123
789
+ }
790
+ """
791
+ try:
792
+ from flock.store import FilterConfig
793
+
794
+ messages = []
795
+
796
+ # 1. Get messages PRODUCED by this node
797
+ produced_filter = FilterConfig(produced_by={node_id})
798
+ produced_artifacts, _produced_count = await orchestrator.store.query_artifacts(
799
+ produced_filter, limit=100, offset=0, embed_meta=False
800
+ )
801
+
802
+ for artifact in produced_artifacts:
803
+ messages.append(
804
+ {
805
+ "id": str(artifact.id),
806
+ "type": artifact.type,
807
+ "direction": "published",
808
+ "payload": artifact.payload,
809
+ "timestamp": artifact.created_at.isoformat(),
810
+ "correlation_id": str(artifact.correlation_id)
811
+ if artifact.correlation_id
812
+ else None,
813
+ "produced_by": artifact.produced_by,
814
+ }
815
+ )
816
+
817
+ # 2. Get messages CONSUMED by this node
818
+ # Query all artifacts with consumption metadata
819
+ all_artifacts_filter = FilterConfig() # No filter = all artifacts
820
+ all_envelopes, _ = await orchestrator.store.query_artifacts(
821
+ all_artifacts_filter, limit=500, offset=0, embed_meta=True
822
+ )
823
+
824
+ for envelope in all_envelopes:
825
+ artifact = envelope.artifact
826
+ for consumption in envelope.consumptions:
827
+ if consumption.consumer == node_id:
828
+ messages.append(
829
+ {
830
+ "id": str(artifact.id),
831
+ "type": artifact.type,
832
+ "direction": "consumed",
833
+ "payload": artifact.payload,
834
+ "timestamp": artifact.created_at.isoformat(),
835
+ "correlation_id": str(artifact.correlation_id)
836
+ if artifact.correlation_id
837
+ else None,
838
+ "produced_by": artifact.produced_by,
839
+ "consumed_at": consumption.consumed_at.isoformat(),
840
+ }
841
+ )
842
+
843
+ # Sort by timestamp (most recent first)
844
+ messages.sort(key=lambda m: m.get("consumed_at", m["timestamp"]), reverse=True)
845
+
846
+ return {"node_id": node_id, "messages": messages, "total": len(messages)}
847
+
848
+ except Exception as e:
849
+ logger.exception(f"Failed to get message history for {node_id}: {e}")
850
+ raise HTTPException(status_code=500, detail=f"Failed to get message history: {e!s}")
851
+
852
+ @app.get("/api/agents/{agent_id}/runs")
853
+ async def get_agent_runs(agent_id: str) -> dict[str, Any]:
854
+ """Get run history for an agent.
855
+
856
+ Phase 4.1 Feature Gap Fix: Returns agent execution history with metrics
857
+ for display in RunStatusTab.
858
+
859
+ Args:
860
+ agent_id: ID of the agent
861
+
862
+ Returns:
863
+ {
864
+ "agent_id": "agent_name",
865
+ "runs": [
866
+ {
867
+ "run_id": "uuid",
868
+ "start_time": "2025-10-11T...",
869
+ "end_time": "2025-10-11T...",
870
+ "duration_ms": 1234,
871
+ "status": "completed"|"active"|"error",
872
+ "metrics": {
873
+ "tokens_used": 123,
874
+ "cost_usd": 0.0012,
875
+ "artifacts_produced": 5
876
+ },
877
+ "error_message": "error details" (if status=error)
878
+ },
879
+ ...
880
+ ],
881
+ "total": 50
882
+ }
883
+ """
884
+ try:
885
+ # TODO: Implement run history tracking in orchestrator
886
+ # For now, return empty array with proper structure
887
+ # This unblocks frontend development and can be enhanced later
888
+
889
+ runs = []
890
+
891
+ # FUTURE: Query run history from orchestrator or store
892
+ # Example implementation when run tracking is added:
893
+ # runs = await orchestrator.get_agent_run_history(agent_id, limit=50)
894
+
895
+ return {"agent_id": agent_id, "runs": runs, "total": len(runs)}
896
+
897
+ except Exception as e:
898
+ logger.exception(f"Failed to get run history for {agent_id}: {e}")
899
+ raise HTTPException(status_code=500, detail=f"Failed to get run history: {e!s}")
900
+
901
+ def _register_theme_routes(self) -> None:
902
+ """Register theme API endpoints for dashboard customization."""
903
+ from pathlib import Path
904
+
905
+ import toml
906
+
907
+ app = self.app
908
+ themes_dir = Path(__file__).parent.parent / "themes"
909
+
910
+ @app.get("/api/themes")
911
+ async def list_themes() -> dict[str, Any]:
912
+ """Get list of available theme names.
913
+
914
+ Returns:
915
+ {"themes": ["dracula", "nord", ...]}
916
+ """
917
+ try:
918
+ if not themes_dir.exists():
919
+ return {"themes": []}
920
+
921
+ theme_files = list(themes_dir.glob("*.toml"))
922
+ theme_names = sorted([f.stem for f in theme_files])
923
+
924
+ return {"themes": theme_names}
925
+ except Exception as e:
926
+ logger.exception(f"Failed to list themes: {e}")
927
+ raise HTTPException(status_code=500, detail=f"Failed to list themes: {e!s}")
928
+
929
+ @app.get("/api/themes/{theme_name}")
930
+ async def get_theme(theme_name: str) -> dict[str, Any]:
931
+ """Get theme data by name.
932
+
933
+ Args:
934
+ theme_name: Name of theme (without .toml extension)
935
+
936
+ Returns:
937
+ {
938
+ "name": "dracula",
939
+ "data": {
940
+ "colors": {...}
941
+ }
942
+ }
943
+ """
944
+ try:
945
+ # Sanitize theme name to prevent path traversal
946
+ theme_name = theme_name.replace("/", "").replace("\\", "").replace("..", "")
947
+
948
+ theme_path = themes_dir / f"{theme_name}.toml"
949
+
950
+ if not theme_path.exists():
951
+ raise HTTPException(status_code=404, detail=f"Theme '{theme_name}' not found")
952
+
953
+ # Load TOML theme
954
+ theme_data = toml.load(theme_path)
955
+
956
+ return {"name": theme_name, "data": theme_data}
957
+ except HTTPException:
958
+ raise
959
+ except Exception as e:
960
+ logger.exception(f"Failed to load theme '{theme_name}': {e}")
961
+ raise HTTPException(status_code=500, detail=f"Failed to load theme: {e!s}")
962
+
963
+ async def start(self) -> None:
964
+ """Start the dashboard service.
965
+
966
+ Note: For testing purposes. In production, use uvicorn.run(app).
967
+ """
968
+ logger.info("DashboardHTTPService started")
969
+ # Start heartbeat if there are clients
970
+ if len(self.websocket_manager.clients) > 0:
971
+ await self.websocket_manager.start_heartbeat()
972
+
973
+ async def stop(self) -> None:
974
+ """Stop the dashboard service and clean up resources.
975
+
976
+ Closes all WebSocket connections gracefully.
977
+ """
978
+ logger.info("Stopping DashboardHTTPService")
979
+ await self.websocket_manager.shutdown()
980
+ logger.info("DashboardHTTPService stopped")
981
+
982
+ def get_app(self) -> Any:
983
+ """Get FastAPI application instance.
984
+
985
+ Returns:
986
+ FastAPI app for testing or custom server setup
987
+ """
988
+ return self.app
989
+
990
+
991
+ __all__ = ["DashboardHTTPService"]