flock-core 0.4.543__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.543.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.543.dist-info/METADATA +0 -676
  496. flock_core-0.4.543.dist-info/RECORD +0 -572
  497. flock_core-0.4.543.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.543.dist-info → flock_core-0.5.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,563 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from collections import defaultdict
5
+ from collections.abc import Iterable, Mapping, Sequence
6
+ from datetime import datetime, timedelta, timezone
7
+
8
+ from flock.dashboard.collector import AgentSnapshot, DashboardEventCollector
9
+ from flock.dashboard.models.graph import (
10
+ GraphAgentMetrics,
11
+ GraphArtifact,
12
+ GraphEdge,
13
+ GraphFilters,
14
+ GraphMarker,
15
+ GraphNode,
16
+ GraphPosition,
17
+ GraphRequest,
18
+ GraphRun,
19
+ GraphSnapshot,
20
+ GraphState,
21
+ GraphStatistics,
22
+ GraphTimeRange,
23
+ GraphTimeRangePreset,
24
+ )
25
+ from flock.logging.auto_trace import AutoTracedMeta
26
+ from flock.orchestrator import Flock
27
+ from flock.store import (
28
+ Artifact,
29
+ BlackboardStore,
30
+ FilterConfig,
31
+ )
32
+ from flock.store import (
33
+ ArtifactEnvelope as StoreArtifactEnvelope,
34
+ )
35
+
36
+
37
+ class GraphAssembler(metaclass=AutoTracedMeta):
38
+ """Build graph snapshots for dashboard consumption."""
39
+
40
+ def __init__(
41
+ self,
42
+ store: BlackboardStore,
43
+ collector: DashboardEventCollector,
44
+ orchestrator: Flock,
45
+ ) -> None:
46
+ self._store = store
47
+ self._collector = collector
48
+ self._orchestrator = orchestrator
49
+
50
+ async def build_snapshot(self, request: GraphRequest) -> GraphSnapshot:
51
+ filters = request.filters or GraphFilters()
52
+ filter_config = self._to_filter_config(filters)
53
+ limit = max(1, request.options.limit if request.options else 500)
54
+
55
+ envelopes, total_available = await self._store.fetch_graph_artifacts(
56
+ filter_config,
57
+ limit=limit,
58
+ offset=0,
59
+ )
60
+
61
+ graph_state: GraphState = await self._collector.snapshot_graph_state()
62
+ agent_snapshots = await self._collector.snapshot_agent_registry()
63
+ artifacts = self._convert_envelopes_to_artifacts(envelopes, graph_state.consumptions)
64
+
65
+ produced_metrics, consumed_metrics = self._calculate_agent_metrics(artifacts.values())
66
+
67
+ if request.view_mode == "agent":
68
+ nodes = self._build_agent_nodes(
69
+ artifacts,
70
+ produced_metrics,
71
+ consumed_metrics,
72
+ graph_state,
73
+ agent_snapshots,
74
+ )
75
+ edges = self._derive_agent_edges(artifacts)
76
+ else:
77
+ nodes = self._build_message_nodes(artifacts)
78
+ edges = self._derive_blackboard_edges(artifacts, graph_state)
79
+
80
+ statistics = None
81
+ if request.options.include_statistics:
82
+ artifact_summary = await self._store.summarize_artifacts(filter_config)
83
+ statistics = GraphStatistics(
84
+ produced_by_agent=produced_metrics,
85
+ consumed_by_agent=consumed_metrics,
86
+ artifact_summary=artifact_summary,
87
+ )
88
+
89
+ filters_copy = filters.model_copy(deep=True)
90
+ generated_at = datetime.now(timezone.utc)
91
+
92
+ return GraphSnapshot(
93
+ generated_at=generated_at,
94
+ view_mode=request.view_mode,
95
+ filters=filters_copy,
96
+ nodes=nodes,
97
+ edges=edges,
98
+ statistics=statistics,
99
+ total_artifacts=total_available,
100
+ truncated=total_available > len(artifacts),
101
+ )
102
+
103
+ def _convert_envelopes_to_artifacts(
104
+ self,
105
+ envelopes: Sequence[StoreArtifactEnvelope],
106
+ runtime_consumptions: Mapping[str, Sequence[str]],
107
+ ) -> dict[str, GraphArtifact]:
108
+ artifacts: dict[str, GraphArtifact] = {}
109
+ for envelope in envelopes:
110
+ artifact: Artifact = envelope.artifact
111
+ artifact_id = str(artifact.id)
112
+ consumers = {record.consumer for record in envelope.consumptions}
113
+ runtime = runtime_consumptions.get(artifact_id, [])
114
+ consumers.update(runtime)
115
+
116
+ correlation_id = (
117
+ str(artifact.correlation_id) if artifact.correlation_id is not None else None
118
+ )
119
+ visibility_kind = getattr(artifact.visibility, "kind", None)
120
+ if visibility_kind is None:
121
+ cls_name = type(artifact.visibility).__name__
122
+ visibility_kind = cls_name[:-10] if cls_name.endswith("Visibility") else cls_name
123
+
124
+ artifacts[artifact_id] = GraphArtifact(
125
+ artifact_id=artifact_id,
126
+ artifact_type=artifact.type,
127
+ produced_by=artifact.produced_by,
128
+ consumed_by=sorted(consumers),
129
+ published_at=artifact.created_at,
130
+ payload=dict(artifact.payload),
131
+ correlation_id=correlation_id,
132
+ visibility_kind=visibility_kind,
133
+ tags=sorted(artifact.tags),
134
+ )
135
+ return artifacts
136
+
137
+ def _calculate_agent_metrics(
138
+ self,
139
+ artifacts: Iterable[GraphArtifact],
140
+ ) -> tuple[dict[str, GraphAgentMetrics], dict[str, GraphAgentMetrics]]:
141
+ produced_acc: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
142
+ consumed_acc: dict[str, dict[str, int]] = defaultdict(lambda: defaultdict(int))
143
+ produced_totals: dict[str, int] = defaultdict(int)
144
+ consumed_totals: dict[str, int] = defaultdict(int)
145
+
146
+ for artifact in artifacts:
147
+ producer = artifact.produced_by or "external"
148
+ produced_totals[producer] += 1
149
+ produced_acc[producer][artifact.artifact_type] += 1
150
+
151
+ for consumer in artifact.consumed_by:
152
+ consumed_totals[consumer] += 1
153
+ consumed_acc[consumer][artifact.artifact_type] += 1
154
+
155
+ produced_metrics: dict[str, GraphAgentMetrics] = {}
156
+ for agent, total in produced_totals.items():
157
+ produced_metrics[agent] = GraphAgentMetrics(
158
+ total=total,
159
+ by_type=dict(produced_acc[agent]),
160
+ )
161
+
162
+ consumed_metrics: dict[str, GraphAgentMetrics] = {}
163
+ for agent, total in consumed_totals.items():
164
+ consumed_metrics[agent] = GraphAgentMetrics(
165
+ total=total,
166
+ by_type=dict(consumed_acc[agent]),
167
+ )
168
+
169
+ return produced_metrics, consumed_metrics
170
+
171
+ def _build_agent_nodes(
172
+ self,
173
+ artifacts: Mapping[str, GraphArtifact],
174
+ produced_metrics: Mapping[str, GraphAgentMetrics],
175
+ consumed_metrics: Mapping[str, GraphAgentMetrics],
176
+ graph_state: GraphState,
177
+ agent_snapshots: Mapping[str, AgentSnapshot],
178
+ ) -> list[GraphNode]:
179
+ nodes: list[GraphNode] = []
180
+ agent_status = graph_state.agent_status
181
+ active_names: set[str] = set()
182
+
183
+ existing_names: set[str] = set()
184
+
185
+ for agent in self._orchestrator.agents:
186
+ subscriptions = sorted(
187
+ {type_name for sub in agent.subscriptions for type_name in sub.type_names}
188
+ )
189
+ output_types = sorted({output.spec.type_name for output in agent.outputs})
190
+
191
+ produced = produced_metrics.get(agent.name)
192
+ consumed = consumed_metrics.get(agent.name)
193
+ snapshot = agent_snapshots.get(agent.name)
194
+
195
+ node_data = {
196
+ "name": agent.name,
197
+ "status": agent_status.get(agent.name, "idle"),
198
+ "subscriptions": subscriptions,
199
+ "outputTypes": output_types,
200
+ "sentCount": produced.total if produced else 0,
201
+ "recvCount": consumed.total if consumed else 0,
202
+ "sentByType": produced.by_type if produced else {},
203
+ "receivedByType": consumed.by_type if consumed else {},
204
+ "streamingTokens": [],
205
+ "labels": sorted(agent.labels),
206
+ "firstSeen": snapshot.first_seen.isoformat() if snapshot else None,
207
+ "lastSeen": snapshot.last_seen.isoformat() if snapshot else None,
208
+ "signature": snapshot.signature if snapshot else None,
209
+ }
210
+
211
+ nodes.append(
212
+ GraphNode(
213
+ id=agent.name,
214
+ type="agent",
215
+ data=node_data,
216
+ position=GraphPosition(),
217
+ hidden=False,
218
+ )
219
+ )
220
+ active_names.add(agent.name)
221
+ existing_names.add(agent.name)
222
+
223
+ for name, snapshot in agent_snapshots.items():
224
+ if name in active_names:
225
+ continue
226
+
227
+ produced = produced_metrics.get(name)
228
+ consumed = consumed_metrics.get(name)
229
+
230
+ node_data = {
231
+ "name": name,
232
+ "status": "inactive",
233
+ "subscriptions": list(snapshot.subscriptions),
234
+ "outputTypes": list(snapshot.output_types),
235
+ "sentCount": produced.total if produced else 0,
236
+ "recvCount": consumed.total if consumed else 0,
237
+ "sentByType": produced.by_type if produced else {},
238
+ "receivedByType": consumed.by_type if consumed else {},
239
+ "streamingTokens": [],
240
+ "labels": list(snapshot.labels),
241
+ "firstSeen": snapshot.first_seen.isoformat(),
242
+ "lastSeen": snapshot.last_seen.isoformat(),
243
+ "signature": snapshot.signature,
244
+ }
245
+
246
+ nodes.append(
247
+ GraphNode(
248
+ id=name,
249
+ type="agent",
250
+ data=node_data,
251
+ position=GraphPosition(),
252
+ hidden=False,
253
+ )
254
+ )
255
+ existing_names.add(name)
256
+
257
+ metric_names = set(produced_metrics.keys()) | set(consumed_metrics.keys())
258
+ for name in metric_names:
259
+ if name in existing_names:
260
+ continue
261
+ produced = produced_metrics.get(name)
262
+ consumed = consumed_metrics.get(name)
263
+ node_data = {
264
+ "name": name,
265
+ "status": "unknown",
266
+ "subscriptions": [],
267
+ "outputTypes": [],
268
+ "sentCount": produced.total if produced else 0,
269
+ "recvCount": consumed.total if consumed else 0,
270
+ "sentByType": produced.by_type if produced else {},
271
+ "receivedByType": consumed.by_type if consumed else {},
272
+ "streamingTokens": [],
273
+ "labels": [],
274
+ "firstSeen": None,
275
+ "lastSeen": None,
276
+ "signature": None,
277
+ }
278
+
279
+ nodes.append(
280
+ GraphNode(
281
+ id=name,
282
+ type="agent",
283
+ data=node_data,
284
+ position=GraphPosition(),
285
+ hidden=False,
286
+ )
287
+ )
288
+ existing_names.add(name)
289
+
290
+ return nodes
291
+
292
+ def _build_message_nodes(
293
+ self,
294
+ artifacts: Mapping[str, GraphArtifact],
295
+ ) -> list[GraphNode]:
296
+ nodes: list[GraphNode] = []
297
+
298
+ for artifact in artifacts.values():
299
+ payload_preview = self._payload_preview(artifact.payload)
300
+ timestamp_ms = int(artifact.published_at.timestamp() * 1000)
301
+
302
+ node_data = {
303
+ "artifactType": artifact.artifact_type,
304
+ "payloadPreview": payload_preview,
305
+ "payload": artifact.payload,
306
+ "producedBy": artifact.produced_by,
307
+ "consumedBy": list(artifact.consumed_by),
308
+ "timestamp": timestamp_ms,
309
+ "tags": artifact.tags,
310
+ "visibilityKind": artifact.visibility_kind or "Unknown",
311
+ "correlationId": artifact.correlation_id,
312
+ }
313
+
314
+ nodes.append(
315
+ GraphNode(
316
+ id=artifact.artifact_id,
317
+ type="message",
318
+ data=node_data,
319
+ position=GraphPosition(),
320
+ hidden=False,
321
+ )
322
+ )
323
+
324
+ return nodes
325
+
326
+ def _derive_agent_edges(
327
+ self,
328
+ artifacts: Mapping[str, GraphArtifact],
329
+ ) -> list[GraphEdge]:
330
+ edge_payloads: dict[str, dict] = {}
331
+ pair_group: dict[tuple[str, str], list[str]] = defaultdict(list)
332
+
333
+ for artifact in artifacts.values():
334
+ producer = artifact.produced_by or "external"
335
+ message_type = artifact.artifact_type
336
+ for consumer in artifact.consumed_by:
337
+ edge_id = f"{producer}__{consumer}__{message_type}"
338
+ payload = edge_payloads.setdefault(
339
+ edge_id,
340
+ {
341
+ "source": producer,
342
+ "target": consumer,
343
+ "message_type": message_type,
344
+ "artifact_ids": [],
345
+ "latest_timestamp": artifact.published_at,
346
+ },
347
+ )
348
+ payload["artifact_ids"].append(artifact.artifact_id)
349
+ payload["latest_timestamp"] = max(payload["latest_timestamp"], artifact.published_at)
350
+ pair_key = tuple(sorted((producer, consumer)))
351
+ if edge_id not in pair_group[pair_key]:
352
+ pair_group[pair_key].append(edge_id)
353
+
354
+ offsets = self._calculate_label_offsets(pair_group)
355
+ edges: list[GraphEdge] = []
356
+ for edge_id, payload in edge_payloads.items():
357
+ message_type = payload["message_type"]
358
+ artifact_ids = payload["artifact_ids"]
359
+ label = f"{message_type} ({len(artifact_ids)})"
360
+ edges.append(
361
+ GraphEdge(
362
+ id=edge_id,
363
+ source=payload["source"],
364
+ target=payload["target"],
365
+ type="message_flow",
366
+ label=label,
367
+ data={
368
+ "messageType": message_type,
369
+ "messageCount": len(artifact_ids),
370
+ "artifactIds": artifact_ids,
371
+ "latestTimestamp": payload["latest_timestamp"].isoformat(),
372
+ "labelOffset": offsets.get(edge_id, 0.0),
373
+ },
374
+ marker_end=GraphMarker(),
375
+ hidden=False,
376
+ )
377
+ )
378
+
379
+ return edges
380
+
381
+ def _derive_blackboard_edges(
382
+ self,
383
+ artifacts: Mapping[str, GraphArtifact],
384
+ graph_state: GraphState,
385
+ ) -> list[GraphEdge]:
386
+ artifact_ids = set(artifacts.keys())
387
+ edge_payloads: dict[str, dict] = {}
388
+ pair_group: dict[tuple[str, str], list[str]] = defaultdict(list)
389
+
390
+ for run in self._collect_runs_for_blackboard(artifacts, graph_state):
391
+ if run.status == "active":
392
+ continue
393
+ consumed = [
394
+ artifact_id for artifact_id in run.consumed_artifacts if artifact_id in artifact_ids
395
+ ]
396
+ produced = [
397
+ artifact_id for artifact_id in run.produced_artifacts if artifact_id in artifact_ids
398
+ ]
399
+ if not consumed or not produced:
400
+ continue
401
+ for consumed_id in consumed:
402
+ for produced_id in produced:
403
+ edge_id = f"{consumed_id}__{produced_id}__{run.run_id}"
404
+ payload = edge_payloads.setdefault(
405
+ edge_id,
406
+ {
407
+ "source": consumed_id,
408
+ "target": produced_id,
409
+ "agent_name": run.agent_name,
410
+ "run_id": run.run_id,
411
+ "duration_ms": run.duration_ms,
412
+ },
413
+ )
414
+ pair_key = tuple(sorted((consumed_id, produced_id)))
415
+ if edge_id not in pair_group[pair_key]:
416
+ pair_group[pair_key].append(edge_id)
417
+
418
+ offsets = self._calculate_label_offsets(pair_group)
419
+ edges: list[GraphEdge] = []
420
+ for edge_id, payload in edge_payloads.items():
421
+ edges.append(
422
+ GraphEdge(
423
+ id=edge_id,
424
+ source=payload["source"],
425
+ target=payload["target"],
426
+ type="transformation",
427
+ label=payload["agent_name"],
428
+ data={
429
+ "transformerAgent": payload["agent_name"],
430
+ "runId": payload["run_id"],
431
+ "durationMs": payload["duration_ms"],
432
+ "labelOffset": offsets.get(edge_id, 0.0),
433
+ },
434
+ marker_end=GraphMarker(),
435
+ hidden=False,
436
+ )
437
+ )
438
+
439
+ return edges
440
+
441
+ def _payload_preview(self, payload: Mapping[str, object]) -> str:
442
+ try:
443
+ serialized = json.dumps(payload, ensure_ascii=False)
444
+ except Exception:
445
+ serialized = str(payload)
446
+ return serialized[:120]
447
+
448
+ def _to_filter_config(self, filters: GraphFilters) -> FilterConfig:
449
+ start, end = self._resolve_time_bounds(filters.time_range)
450
+ return FilterConfig(
451
+ type_names=self._optional_set(filters.artifact_types),
452
+ produced_by=self._optional_set(filters.producers),
453
+ correlation_id=filters.correlation_id or None,
454
+ tags=self._optional_set(filters.tags),
455
+ visibility=self._optional_set(filters.visibility),
456
+ start=start,
457
+ end=end,
458
+ )
459
+
460
+ def _collect_runs_for_blackboard(
461
+ self,
462
+ artifacts: Mapping[str, GraphArtifact],
463
+ graph_state: GraphState,
464
+ ) -> list[GraphRun]:
465
+ existing_runs = list(graph_state.runs)
466
+ synthetic_runs = self._build_synthetic_runs(
467
+ artifacts, graph_state.consumptions, existing_runs
468
+ )
469
+ return existing_runs + synthetic_runs
470
+
471
+ def _build_synthetic_runs(
472
+ self,
473
+ artifacts: Mapping[str, GraphArtifact],
474
+ consumptions: Mapping[str, Sequence[str]],
475
+ existing_runs: Sequence[GraphRun],
476
+ ) -> list[GraphRun]:
477
+ existing_keys = {(run.agent_name, run.correlation_id or "") for run in existing_runs}
478
+
479
+ produced_buckets: dict[tuple[str, str], list[str]] = defaultdict(list)
480
+ consumed_buckets: dict[tuple[str, str], list[str]] = defaultdict(list)
481
+
482
+ for artifact in artifacts.values():
483
+ correlation = artifact.correlation_id or ""
484
+ producer = artifact.produced_by or "external"
485
+ produced_buckets[(producer, correlation)].append(artifact.artifact_id)
486
+
487
+ consumer_list = consumptions.get(artifact.artifact_id, artifact.consumed_by)
488
+ for consumer in consumer_list:
489
+ consumed_buckets[(consumer, correlation)].append(artifact.artifact_id)
490
+
491
+ synthetic_runs: list[GraphRun] = []
492
+ counter = 0
493
+ for key, consumed in consumed_buckets.items():
494
+ produced = produced_buckets.get(key)
495
+ if not consumed or not produced:
496
+ continue
497
+ if key in existing_keys:
498
+ continue
499
+
500
+ agent_name, correlation = key
501
+ run_id = f"synthetic_{agent_name}_{correlation or 'uncorrelated'}_{counter}"
502
+ counter += 1
503
+
504
+ synthetic_runs.append(
505
+ GraphRun(
506
+ run_id=run_id,
507
+ agent_name=agent_name,
508
+ correlation_id=correlation or None,
509
+ status="completed",
510
+ consumed_artifacts=sorted(set(consumed)),
511
+ produced_artifacts=sorted(set(produced)),
512
+ )
513
+ )
514
+
515
+ return synthetic_runs
516
+
517
+ def _resolve_time_bounds(
518
+ self, time_range: GraphTimeRange
519
+ ) -> tuple[datetime | None, datetime | None]:
520
+ now = datetime.now(timezone.utc)
521
+ preset = time_range.preset
522
+
523
+ if preset == GraphTimeRangePreset.ALL:
524
+ return None, None
525
+ if preset == GraphTimeRangePreset.CUSTOM:
526
+ return self._ensure_timezone(time_range.start), self._ensure_timezone(time_range.end)
527
+
528
+ if preset == GraphTimeRangePreset.LAST_5_MIN:
529
+ delta = timedelta(minutes=5)
530
+ elif preset == GraphTimeRangePreset.LAST_1_HOUR:
531
+ delta = timedelta(hours=1)
532
+ else:
533
+ delta = timedelta(minutes=10)
534
+
535
+ return now - delta, now
536
+
537
+ @staticmethod
538
+ def _ensure_timezone(value: datetime | None) -> datetime | None:
539
+ if value is None:
540
+ return None
541
+ if value.tzinfo is None:
542
+ return value.replace(tzinfo=timezone.utc)
543
+ return value.astimezone(timezone.utc)
544
+
545
+ @staticmethod
546
+ def _optional_set(values: Sequence[str]) -> set[str] | None:
547
+ cleaned = {value for value in values if value}
548
+ return cleaned if cleaned else None
549
+
550
+ @staticmethod
551
+ def _calculate_label_offsets(groups: Mapping[tuple[str, str], list[str]]) -> dict[str, float]:
552
+ offsets: dict[str, float] = {}
553
+ for edge_ids in groups.values():
554
+ total = len(edge_ids)
555
+ if total <= 1:
556
+ for edge_id in edge_ids:
557
+ offsets[edge_id] = 0.0
558
+ continue
559
+ offset_range = min(40.0, total * 15.0)
560
+ step = offset_range / (total - 1)
561
+ for index, edge_id in enumerate(edge_ids):
562
+ offsets[edge_id] = index * step - offset_range / 2
563
+ return offsets