flock-core 0.5.0b28__py3-none-any.whl → 0.5.0b51__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 (469) hide show
  1. flock/__init__.py +12 -217
  2. flock/agent.py +678 -0
  3. flock/api/themes.py +71 -0
  4. flock/artifacts.py +79 -0
  5. flock/cli.py +75 -0
  6. flock/components.py +173 -0
  7. flock/dashboard/__init__.py +28 -0
  8. flock/dashboard/collector.py +283 -0
  9. flock/dashboard/events.py +182 -0
  10. flock/dashboard/launcher.py +230 -0
  11. flock/dashboard/service.py +537 -0
  12. flock/dashboard/websocket.py +235 -0
  13. flock/engines/__init__.py +6 -0
  14. flock/engines/dspy_engine.py +856 -0
  15. flock/examples.py +128 -0
  16. flock/frontend/README.md +678 -0
  17. flock/frontend/docs/DESIGN_SYSTEM.md +1980 -0
  18. flock/frontend/index.html +12 -0
  19. flock/frontend/package-lock.json +4347 -0
  20. flock/frontend/package.json +48 -0
  21. flock/frontend/src/App.tsx +79 -0
  22. flock/frontend/src/__tests__/e2e/critical-scenarios.test.tsx +587 -0
  23. flock/frontend/src/__tests__/integration/filtering-e2e.test.tsx +387 -0
  24. flock/frontend/src/__tests__/integration/graph-rendering.test.tsx +640 -0
  25. flock/frontend/src/__tests__/integration/indexeddb-persistence.test.tsx +699 -0
  26. flock/frontend/src/components/common/BuildInfo.tsx +39 -0
  27. flock/frontend/src/components/common/EmptyState.module.css +115 -0
  28. flock/frontend/src/components/common/EmptyState.tsx +128 -0
  29. flock/frontend/src/components/common/ErrorBoundary.module.css +169 -0
  30. flock/frontend/src/components/common/ErrorBoundary.tsx +118 -0
  31. flock/frontend/src/components/common/KeyboardShortcutsDialog.css +251 -0
  32. flock/frontend/src/components/common/KeyboardShortcutsDialog.tsx +151 -0
  33. flock/frontend/src/components/common/LoadingSpinner.module.css +97 -0
  34. flock/frontend/src/components/common/LoadingSpinner.tsx +29 -0
  35. flock/frontend/src/components/controls/PublishControl.css +547 -0
  36. flock/frontend/src/components/controls/PublishControl.test.tsx +543 -0
  37. flock/frontend/src/components/controls/PublishControl.tsx +432 -0
  38. flock/frontend/src/components/details/DetailWindowContainer.tsx +62 -0
  39. flock/frontend/src/components/details/LiveOutputTab.test.tsx +792 -0
  40. flock/frontend/src/components/details/LiveOutputTab.tsx +220 -0
  41. flock/frontend/src/components/details/MessageHistoryTab.tsx +299 -0
  42. flock/frontend/src/components/details/NodeDetailWindow.test.tsx +501 -0
  43. flock/frontend/src/components/details/NodeDetailWindow.tsx +218 -0
  44. flock/frontend/src/components/details/RunStatusTab.tsx +307 -0
  45. flock/frontend/src/components/details/tabs.test.tsx +1015 -0
  46. flock/frontend/src/components/filters/CorrelationIDFilter.module.css +102 -0
  47. flock/frontend/src/components/filters/CorrelationIDFilter.test.tsx +197 -0
  48. flock/frontend/src/components/filters/CorrelationIDFilter.tsx +121 -0
  49. flock/frontend/src/components/filters/FilterBar.module.css +29 -0
  50. flock/frontend/src/components/filters/FilterBar.test.tsx +133 -0
  51. flock/frontend/src/components/filters/FilterBar.tsx +33 -0
  52. flock/frontend/src/components/filters/FilterPills.module.css +79 -0
  53. flock/frontend/src/components/filters/FilterPills.test.tsx +173 -0
  54. flock/frontend/src/components/filters/FilterPills.tsx +67 -0
  55. flock/frontend/src/components/filters/TimeRangeFilter.module.css +91 -0
  56. flock/frontend/src/components/filters/TimeRangeFilter.test.tsx +154 -0
  57. flock/frontend/src/components/filters/TimeRangeFilter.tsx +105 -0
  58. flock/frontend/src/components/graph/AgentNode.test.tsx +75 -0
  59. flock/frontend/src/components/graph/AgentNode.tsx +322 -0
  60. flock/frontend/src/components/graph/GraphCanvas.tsx +406 -0
  61. flock/frontend/src/components/graph/MessageFlowEdge.tsx +128 -0
  62. flock/frontend/src/components/graph/MessageNode.test.tsx +62 -0
  63. flock/frontend/src/components/graph/MessageNode.tsx +116 -0
  64. flock/frontend/src/components/graph/MiniMap.tsx +47 -0
  65. flock/frontend/src/components/graph/TransformEdge.tsx +123 -0
  66. flock/frontend/src/components/layout/DashboardLayout.css +407 -0
  67. flock/frontend/src/components/layout/DashboardLayout.tsx +300 -0
  68. flock/frontend/src/components/layout/Header.module.css +88 -0
  69. flock/frontend/src/components/layout/Header.tsx +52 -0
  70. flock/frontend/src/components/modules/EventLogModule.test.tsx +401 -0
  71. flock/frontend/src/components/modules/EventLogModule.tsx +396 -0
  72. flock/frontend/src/components/modules/EventLogModuleWrapper.tsx +17 -0
  73. flock/frontend/src/components/modules/ModuleRegistry.test.ts +333 -0
  74. flock/frontend/src/components/modules/ModuleRegistry.ts +85 -0
  75. flock/frontend/src/components/modules/ModuleWindow.tsx +155 -0
  76. flock/frontend/src/components/modules/registerModules.ts +20 -0
  77. flock/frontend/src/components/settings/AdvancedSettings.tsx +175 -0
  78. flock/frontend/src/components/settings/AppearanceSettings.tsx +185 -0
  79. flock/frontend/src/components/settings/GraphSettings.tsx +110 -0
  80. flock/frontend/src/components/settings/SettingsPanel.css +327 -0
  81. flock/frontend/src/components/settings/SettingsPanel.tsx +131 -0
  82. flock/frontend/src/components/settings/ThemeSelector.tsx +298 -0
  83. flock/frontend/src/hooks/useKeyboardShortcuts.ts +148 -0
  84. flock/frontend/src/hooks/useModulePersistence.test.ts +442 -0
  85. flock/frontend/src/hooks/useModulePersistence.ts +154 -0
  86. flock/frontend/src/hooks/useModules.ts +139 -0
  87. flock/frontend/src/hooks/usePersistence.ts +139 -0
  88. flock/frontend/src/main.tsx +13 -0
  89. flock/frontend/src/services/api.ts +213 -0
  90. flock/frontend/src/services/indexeddb.test.ts +793 -0
  91. flock/frontend/src/services/indexeddb.ts +794 -0
  92. flock/frontend/src/services/layout.test.ts +437 -0
  93. flock/frontend/src/services/layout.ts +146 -0
  94. flock/frontend/src/services/themeApplicator.ts +140 -0
  95. flock/frontend/src/services/themeService.ts +77 -0
  96. flock/frontend/src/services/websocket.test.ts +595 -0
  97. flock/frontend/src/services/websocket.ts +685 -0
  98. flock/frontend/src/store/filterStore.test.ts +242 -0
  99. flock/frontend/src/store/filterStore.ts +103 -0
  100. flock/frontend/src/store/graphStore.test.ts +186 -0
  101. flock/frontend/src/store/graphStore.ts +414 -0
  102. flock/frontend/src/store/moduleStore.test.ts +253 -0
  103. flock/frontend/src/store/moduleStore.ts +57 -0
  104. flock/frontend/src/store/settingsStore.ts +188 -0
  105. flock/frontend/src/store/streamStore.ts +68 -0
  106. flock/frontend/src/store/uiStore.test.ts +54 -0
  107. flock/frontend/src/store/uiStore.ts +110 -0
  108. flock/frontend/src/store/wsStore.ts +34 -0
  109. flock/frontend/src/styles/index.css +15 -0
  110. flock/frontend/src/styles/scrollbar.css +47 -0
  111. flock/frontend/src/styles/variables.css +488 -0
  112. flock/frontend/src/test/setup.ts +1 -0
  113. flock/frontend/src/types/filters.ts +14 -0
  114. flock/frontend/src/types/graph.ts +55 -0
  115. flock/frontend/src/types/modules.ts +7 -0
  116. flock/frontend/src/types/theme.ts +55 -0
  117. flock/frontend/src/utils/mockData.ts +85 -0
  118. flock/frontend/src/utils/performance.ts +16 -0
  119. flock/frontend/src/utils/transforms.test.ts +860 -0
  120. flock/frontend/src/utils/transforms.ts +323 -0
  121. flock/frontend/src/vite-env.d.ts +17 -0
  122. flock/frontend/tsconfig.json +27 -0
  123. flock/frontend/tsconfig.node.json +11 -0
  124. flock/frontend/vite.config.ts +25 -0
  125. flock/frontend/vitest.config.ts +11 -0
  126. flock/{core/util → helper}/cli_helper.py +4 -3
  127. flock/{core/logging → logging}/__init__.py +2 -3
  128. flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
  129. flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
  130. flock/{core/logging → logging}/formatters/themed_formatter.py +69 -115
  131. flock/{core/logging → logging}/logging.py +77 -61
  132. flock/{core/logging → logging}/telemetry.py +20 -26
  133. flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
  134. flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +6 -9
  135. flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
  136. flock/{core/logging → logging}/trace_and_logged.py +20 -24
  137. flock/mcp/__init__.py +91 -0
  138. flock/{core/mcp/mcp_client.py → mcp/client.py} +103 -154
  139. flock/{core/mcp/mcp_config.py → mcp/config.py} +62 -117
  140. flock/mcp/manager.py +255 -0
  141. flock/mcp/servers/sse/__init__.py +1 -1
  142. flock/mcp/servers/sse/flock_sse_server.py +11 -53
  143. flock/mcp/servers/stdio/__init__.py +1 -1
  144. flock/mcp/servers/stdio/flock_stdio_server.py +8 -48
  145. flock/mcp/servers/streamable_http/flock_streamable_http_server.py +17 -62
  146. flock/mcp/servers/websockets/flock_websocket_server.py +7 -40
  147. flock/{core/mcp/flock_mcp_tool.py → mcp/tool.py} +16 -26
  148. flock/mcp/types/__init__.py +42 -0
  149. flock/{core/mcp → mcp}/types/callbacks.py +9 -15
  150. flock/{core/mcp → mcp}/types/factories.py +7 -6
  151. flock/{core/mcp → mcp}/types/handlers.py +13 -18
  152. flock/{core/mcp → mcp}/types/types.py +70 -74
  153. flock/{core/mcp → mcp}/util/helpers.py +1 -1
  154. flock/orchestrator.py +645 -0
  155. flock/registry.py +148 -0
  156. flock/runtime.py +262 -0
  157. flock/service.py +140 -0
  158. flock/store.py +69 -0
  159. flock/subscription.py +111 -0
  160. flock/themes/andromeda.toml +1 -1
  161. flock/themes/apple-system-colors.toml +1 -1
  162. flock/themes/arcoiris.toml +1 -1
  163. flock/themes/atomonelight.toml +1 -1
  164. flock/themes/ayu copy.toml +1 -1
  165. flock/themes/ayu-light.toml +1 -1
  166. flock/themes/belafonte-day.toml +1 -1
  167. flock/themes/belafonte-night.toml +1 -1
  168. flock/themes/blulocodark.toml +1 -1
  169. flock/themes/breeze.toml +1 -1
  170. flock/themes/broadcast.toml +1 -1
  171. flock/themes/brogrammer.toml +1 -1
  172. flock/themes/builtin-dark.toml +1 -1
  173. flock/themes/builtin-pastel-dark.toml +1 -1
  174. flock/themes/catppuccin-latte.toml +1 -1
  175. flock/themes/catppuccin-macchiato.toml +1 -1
  176. flock/themes/catppuccin-mocha.toml +1 -1
  177. flock/themes/cga.toml +1 -1
  178. flock/themes/chalk.toml +1 -1
  179. flock/themes/ciapre.toml +1 -1
  180. flock/themes/coffee-theme.toml +1 -1
  181. flock/themes/cyberpunkscarletprotocol.toml +1 -1
  182. flock/themes/dark+.toml +1 -1
  183. flock/themes/darkermatrix.toml +1 -1
  184. flock/themes/darkside.toml +1 -1
  185. flock/themes/desert.toml +1 -1
  186. flock/themes/django.toml +1 -1
  187. flock/themes/djangosmooth.toml +1 -1
  188. flock/themes/doomone.toml +1 -1
  189. flock/themes/dotgov.toml +1 -1
  190. flock/themes/dracula+.toml +1 -1
  191. flock/themes/duckbones.toml +1 -1
  192. flock/themes/encom.toml +1 -1
  193. flock/themes/espresso.toml +1 -1
  194. flock/themes/everblush.toml +1 -1
  195. flock/themes/fairyfloss.toml +1 -1
  196. flock/themes/fideloper.toml +1 -1
  197. flock/themes/fishtank.toml +1 -1
  198. flock/themes/flexoki-light.toml +1 -1
  199. flock/themes/floraverse.toml +1 -1
  200. flock/themes/framer.toml +1 -1
  201. flock/themes/galizur.toml +1 -1
  202. flock/themes/github.toml +1 -1
  203. flock/themes/grass.toml +1 -1
  204. flock/themes/grey-green.toml +1 -1
  205. flock/themes/gruvboxlight.toml +1 -1
  206. flock/themes/guezwhoz.toml +1 -1
  207. flock/themes/harper.toml +1 -1
  208. flock/themes/hax0r-blue.toml +1 -1
  209. flock/themes/hopscotch.256.toml +1 -1
  210. flock/themes/ic-green-ppl.toml +1 -1
  211. flock/themes/iceberg-dark.toml +1 -1
  212. flock/themes/japanesque.toml +1 -1
  213. flock/themes/jubi.toml +1 -1
  214. flock/themes/kibble.toml +1 -1
  215. flock/themes/kolorit.toml +1 -1
  216. flock/themes/kurokula.toml +1 -1
  217. flock/themes/materialdesigncolors.toml +1 -1
  218. flock/themes/matrix.toml +1 -1
  219. flock/themes/mellifluous.toml +1 -1
  220. flock/themes/midnight-in-mojave.toml +1 -1
  221. flock/themes/monokai-remastered.toml +1 -1
  222. flock/themes/monokai-soda.toml +1 -1
  223. flock/themes/neon.toml +1 -1
  224. flock/themes/neopolitan.toml +1 -1
  225. flock/themes/nord-light.toml +1 -1
  226. flock/themes/ocean.toml +1 -1
  227. flock/themes/onehalfdark.toml +1 -1
  228. flock/themes/onehalflight.toml +1 -1
  229. flock/themes/palenighthc.toml +1 -1
  230. flock/themes/paulmillr.toml +1 -1
  231. flock/themes/pencildark.toml +1 -1
  232. flock/themes/pnevma.toml +1 -1
  233. flock/themes/purple-rain.toml +1 -1
  234. flock/themes/purplepeter.toml +1 -1
  235. flock/themes/raycast-dark.toml +1 -1
  236. flock/themes/red-sands.toml +1 -1
  237. flock/themes/relaxed.toml +1 -1
  238. flock/themes/retro.toml +1 -1
  239. flock/themes/rose-pine.toml +1 -1
  240. flock/themes/royal.toml +1 -1
  241. flock/themes/ryuuko.toml +1 -1
  242. flock/themes/sakura.toml +1 -1
  243. flock/themes/scarlet-protocol.toml +1 -1
  244. flock/themes/seoulbones-dark.toml +1 -1
  245. flock/themes/shades-of-purple.toml +1 -1
  246. flock/themes/smyck.toml +1 -1
  247. flock/themes/softserver.toml +1 -1
  248. flock/themes/solarized-darcula.toml +1 -1
  249. flock/themes/square.toml +1 -1
  250. flock/themes/sugarplum.toml +1 -1
  251. flock/themes/thayer-bright.toml +1 -1
  252. flock/themes/tokyonight.toml +1 -1
  253. flock/themes/tomorrow.toml +1 -1
  254. flock/themes/ubuntu.toml +1 -1
  255. flock/themes/ultradark.toml +1 -1
  256. flock/themes/ultraviolent.toml +1 -1
  257. flock/themes/unikitty.toml +1 -1
  258. flock/themes/urple.toml +1 -1
  259. flock/themes/vesper.toml +1 -1
  260. flock/themes/vimbones.toml +1 -1
  261. flock/themes/wildcherry.toml +1 -1
  262. flock/themes/wilmersdorf.toml +1 -1
  263. flock/themes/wryan.toml +1 -1
  264. flock/themes/xcodedarkhc.toml +1 -1
  265. flock/themes/xcodelight.toml +1 -1
  266. flock/themes/zenbones-light.toml +1 -1
  267. flock/themes/zenwritten-dark.toml +1 -1
  268. flock/utilities.py +301 -0
  269. flock/{components/utility → utility}/output_utility_component.py +68 -53
  270. flock/visibility.py +107 -0
  271. flock_core-0.5.0b51.dist-info/METADATA +747 -0
  272. flock_core-0.5.0b51.dist-info/RECORD +508 -0
  273. flock_core-0.5.0b51.dist-info/entry_points.txt +2 -0
  274. {flock_core-0.5.0b28.dist-info → flock_core-0.5.0b51.dist-info}/licenses/LICENSE +1 -1
  275. flock/adapter/__init__.py +0 -14
  276. flock/adapter/azure_adapter.py +0 -68
  277. flock/adapter/chroma_adapter.py +0 -73
  278. flock/adapter/faiss_adapter.py +0 -97
  279. flock/adapter/pinecone_adapter.py +0 -51
  280. flock/adapter/vector_base.py +0 -47
  281. flock/cli/assets/release_notes.md +0 -140
  282. flock/cli/config.py +0 -8
  283. flock/cli/constants.py +0 -36
  284. flock/cli/create_agent.py +0 -1
  285. flock/cli/create_flock.py +0 -280
  286. flock/cli/execute_flock.py +0 -620
  287. flock/cli/load_agent.py +0 -1
  288. flock/cli/load_examples.py +0 -1
  289. flock/cli/load_flock.py +0 -192
  290. flock/cli/load_release_notes.py +0 -20
  291. flock/cli/loaded_flock_cli.py +0 -254
  292. flock/cli/manage_agents.py +0 -459
  293. flock/cli/registry_management.py +0 -889
  294. flock/cli/runner.py +0 -41
  295. flock/cli/settings.py +0 -857
  296. flock/cli/utils.py +0 -135
  297. flock/cli/view_results.py +0 -29
  298. flock/cli/yaml_editor.py +0 -396
  299. flock/components/__init__.py +0 -30
  300. flock/components/evaluation/__init__.py +0 -9
  301. flock/components/evaluation/declarative_evaluation_component.py +0 -606
  302. flock/components/routing/__init__.py +0 -15
  303. flock/components/routing/conditional_routing_component.py +0 -494
  304. flock/components/routing/default_routing_component.py +0 -103
  305. flock/components/routing/llm_routing_component.py +0 -206
  306. flock/components/utility/__init__.py +0 -22
  307. flock/components/utility/example_utility_component.py +0 -250
  308. flock/components/utility/feedback_utility_component.py +0 -206
  309. flock/components/utility/memory_utility_component.py +0 -550
  310. flock/components/utility/metrics_utility_component.py +0 -700
  311. flock/config.py +0 -61
  312. flock/core/__init__.py +0 -110
  313. flock/core/agent/__init__.py +0 -16
  314. flock/core/agent/default_agent.py +0 -216
  315. flock/core/agent/flock_agent_components.py +0 -104
  316. flock/core/agent/flock_agent_execution.py +0 -101
  317. flock/core/agent/flock_agent_integration.py +0 -260
  318. flock/core/agent/flock_agent_lifecycle.py +0 -186
  319. flock/core/agent/flock_agent_serialization.py +0 -381
  320. flock/core/api/__init__.py +0 -10
  321. flock/core/api/custom_endpoint.py +0 -45
  322. flock/core/api/endpoints.py +0 -254
  323. flock/core/api/main.py +0 -162
  324. flock/core/api/models.py +0 -97
  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/component/__init__.py +0 -15
  329. flock/core/component/agent_component_base.py +0 -309
  330. flock/core/component/evaluation_component.py +0 -62
  331. flock/core/component/routing_component.py +0 -74
  332. flock/core/component/utility_component.py +0 -69
  333. flock/core/config/flock_agent_config.py +0 -58
  334. flock/core/config/scheduled_agent_config.py +0 -40
  335. flock/core/context/context.py +0 -213
  336. flock/core/context/context_manager.py +0 -37
  337. flock/core/context/context_vars.py +0 -10
  338. flock/core/evaluation/utils.py +0 -396
  339. flock/core/execution/batch_executor.py +0 -369
  340. flock/core/execution/evaluation_executor.py +0 -438
  341. flock/core/execution/local_executor.py +0 -31
  342. flock/core/execution/opik_executor.py +0 -103
  343. flock/core/execution/temporal_executor.py +0 -164
  344. flock/core/flock.py +0 -634
  345. flock/core/flock_agent.py +0 -336
  346. flock/core/flock_factory.py +0 -613
  347. flock/core/flock_scheduler.py +0 -166
  348. flock/core/flock_server_manager.py +0 -136
  349. flock/core/interpreter/python_interpreter.py +0 -689
  350. flock/core/mcp/__init__.py +0 -1
  351. flock/core/mcp/flock_mcp_server.py +0 -680
  352. flock/core/mcp/mcp_client_manager.py +0 -201
  353. flock/core/mcp/types/__init__.py +0 -1
  354. flock/core/mixin/dspy_integration.py +0 -403
  355. flock/core/mixin/prompt_parser.py +0 -125
  356. flock/core/orchestration/__init__.py +0 -15
  357. flock/core/orchestration/flock_batch_processor.py +0 -94
  358. flock/core/orchestration/flock_evaluator.py +0 -113
  359. flock/core/orchestration/flock_execution.py +0 -295
  360. flock/core/orchestration/flock_initialization.py +0 -149
  361. flock/core/orchestration/flock_server_manager.py +0 -67
  362. flock/core/orchestration/flock_web_server.py +0 -117
  363. flock/core/registry/__init__.py +0 -45
  364. flock/core/registry/agent_registry.py +0 -69
  365. flock/core/registry/callable_registry.py +0 -139
  366. flock/core/registry/component_discovery.py +0 -142
  367. flock/core/registry/component_registry.py +0 -64
  368. flock/core/registry/config_mapping.py +0 -64
  369. flock/core/registry/decorators.py +0 -137
  370. flock/core/registry/registry_hub.py +0 -205
  371. flock/core/registry/server_registry.py +0 -57
  372. flock/core/registry/type_registry.py +0 -86
  373. flock/core/serialization/__init__.py +0 -13
  374. flock/core/serialization/callable_registry.py +0 -52
  375. flock/core/serialization/flock_serializer.py +0 -832
  376. flock/core/serialization/json_encoder.py +0 -41
  377. flock/core/serialization/secure_serializer.py +0 -175
  378. flock/core/serialization/serializable.py +0 -342
  379. flock/core/serialization/serialization_utils.py +0 -412
  380. flock/core/util/file_path_utils.py +0 -223
  381. flock/core/util/hydrator.py +0 -309
  382. flock/core/util/input_resolver.py +0 -164
  383. flock/core/util/loader.py +0 -59
  384. flock/core/util/splitter.py +0 -219
  385. flock/di.py +0 -27
  386. flock/platform/docker_tools.py +0 -49
  387. flock/platform/jaeger_install.py +0 -86
  388. flock/webapp/__init__.py +0 -1
  389. flock/webapp/app/__init__.py +0 -0
  390. flock/webapp/app/api/__init__.py +0 -0
  391. flock/webapp/app/api/agent_management.py +0 -241
  392. flock/webapp/app/api/execution.py +0 -709
  393. flock/webapp/app/api/flock_management.py +0 -129
  394. flock/webapp/app/api/registry_viewer.py +0 -30
  395. flock/webapp/app/chat.py +0 -665
  396. flock/webapp/app/config.py +0 -104
  397. flock/webapp/app/dependencies.py +0 -117
  398. flock/webapp/app/main.py +0 -1070
  399. flock/webapp/app/middleware.py +0 -113
  400. flock/webapp/app/models_ui.py +0 -7
  401. flock/webapp/app/services/__init__.py +0 -0
  402. flock/webapp/app/services/feedback_file_service.py +0 -363
  403. flock/webapp/app/services/flock_service.py +0 -337
  404. flock/webapp/app/services/sharing_models.py +0 -81
  405. flock/webapp/app/services/sharing_store.py +0 -762
  406. flock/webapp/app/templates/theme_mapper.html +0 -326
  407. flock/webapp/app/theme_mapper.py +0 -812
  408. flock/webapp/app/utils.py +0 -85
  409. flock/webapp/run.py +0 -215
  410. flock/webapp/static/css/chat.css +0 -301
  411. flock/webapp/static/css/components.css +0 -167
  412. flock/webapp/static/css/header.css +0 -39
  413. flock/webapp/static/css/layout.css +0 -46
  414. flock/webapp/static/css/sidebar.css +0 -127
  415. flock/webapp/static/css/two-pane.css +0 -48
  416. flock/webapp/templates/base.html +0 -200
  417. flock/webapp/templates/chat.html +0 -152
  418. flock/webapp/templates/chat_settings.html +0 -19
  419. flock/webapp/templates/flock_editor.html +0 -16
  420. flock/webapp/templates/index.html +0 -12
  421. flock/webapp/templates/partials/_agent_detail_form.html +0 -93
  422. flock/webapp/templates/partials/_agent_list.html +0 -18
  423. flock/webapp/templates/partials/_agent_manager_view.html +0 -51
  424. flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
  425. flock/webapp/templates/partials/_chat_container.html +0 -15
  426. flock/webapp/templates/partials/_chat_messages.html +0 -57
  427. flock/webapp/templates/partials/_chat_settings_form.html +0 -85
  428. flock/webapp/templates/partials/_create_flock_form.html +0 -50
  429. flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
  430. flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
  431. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
  432. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
  433. flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
  434. flock/webapp/templates/partials/_env_vars_table.html +0 -23
  435. flock/webapp/templates/partials/_execution_form.html +0 -118
  436. flock/webapp/templates/partials/_execution_view_container.html +0 -28
  437. flock/webapp/templates/partials/_flock_file_list.html +0 -23
  438. flock/webapp/templates/partials/_flock_properties_form.html +0 -52
  439. flock/webapp/templates/partials/_flock_upload_form.html +0 -16
  440. flock/webapp/templates/partials/_header_flock_status.html +0 -5
  441. flock/webapp/templates/partials/_load_manager_view.html +0 -49
  442. flock/webapp/templates/partials/_registry_table.html +0 -25
  443. flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
  444. flock/webapp/templates/partials/_results_display.html +0 -78
  445. flock/webapp/templates/partials/_settings_env_content.html +0 -9
  446. flock/webapp/templates/partials/_settings_theme_content.html +0 -14
  447. flock/webapp/templates/partials/_settings_view.html +0 -36
  448. flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
  449. flock/webapp/templates/partials/_share_link_snippet.html +0 -35
  450. flock/webapp/templates/partials/_sidebar.html +0 -74
  451. flock/webapp/templates/partials/_streaming_results_container.html +0 -195
  452. flock/webapp/templates/partials/_structured_data_view.html +0 -40
  453. flock/webapp/templates/partials/_theme_preview.html +0 -36
  454. flock/webapp/templates/registry_viewer.html +0 -84
  455. flock/webapp/templates/shared_run_page.html +0 -140
  456. flock/workflow/__init__.py +0 -0
  457. flock/workflow/activities.py +0 -196
  458. flock/workflow/agent_activities.py +0 -24
  459. flock/workflow/agent_execution_activity.py +0 -202
  460. flock/workflow/flock_workflow.py +0 -214
  461. flock/workflow/temporal_config.py +0 -96
  462. flock/workflow/temporal_setup.py +0 -68
  463. flock_core-0.5.0b28.dist-info/METADATA +0 -274
  464. flock_core-0.5.0b28.dist-info/RECORD +0 -561
  465. flock_core-0.5.0b28.dist-info/entry_points.txt +0 -2
  466. /flock/{core/logging → logging}/formatters/themes.py +0 -0
  467. /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
  468. /flock/{core/mcp → mcp}/util/__init__.py +0 -0
  469. {flock_core-0.5.0b28.dist-info → flock_core-0.5.0b51.dist-info}/WHEEL +0 -0
flock/utilities.py ADDED
@@ -0,0 +1,301 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ """Built-in utility components for metrics and logging."""
5
+
6
+ import asyncio
7
+ import contextlib
8
+ import json
9
+ import sys
10
+ import time
11
+ from collections.abc import Iterable, Mapping, MutableMapping, Sequence
12
+ from typing import TYPE_CHECKING, Any
13
+
14
+ from rich.console import Console
15
+ from rich.json import JSON
16
+ from rich.live import Live
17
+ from rich.panel import Panel
18
+ from rich.pretty import Pretty
19
+ from rich.table import Table
20
+ from rich.text import Text
21
+
22
+ from flock.components import AgentComponent
23
+
24
+
25
+ if TYPE_CHECKING:
26
+ from flock.runtime import Context, EvalInputs, EvalResult
27
+
28
+
29
+ class MetricsUtility(AgentComponent):
30
+ """Records simple runtime metrics per agent execution."""
31
+
32
+ name: str | None = "metrics"
33
+
34
+ async def on_pre_evaluate(self, agent, ctx: Context, inputs: EvalInputs) -> EvalInputs:
35
+ ctx.state.setdefault("metrics", {})[f"{agent.name}:start"] = time.perf_counter()
36
+ return inputs
37
+
38
+ async def on_post_evaluate(
39
+ self, agent, ctx: Context, inputs: EvalInputs, result: EvalResult
40
+ ) -> EvalResult:
41
+ metrics = ctx.state.setdefault("metrics", {})
42
+ start = metrics.get(f"{agent.name}:start")
43
+ if start:
44
+ metrics[f"{agent.name}:duration_ms"] = (time.perf_counter() - start) * 1000
45
+ result.metrics.update({k: v for k, v in metrics.items() if k.endswith("duration_ms")})
46
+ return result
47
+
48
+
49
+ class LoggingUtility(AgentComponent):
50
+ """Rich-powered logging with optional streaming previews."""
51
+
52
+ name: str | None = "logs"
53
+
54
+ def __init__(
55
+ self,
56
+ console: Console | None = None,
57
+ *,
58
+ highlight_json: bool = True,
59
+ stream_tokens: bool = True,
60
+ ) -> None:
61
+ super().__init__()
62
+ if console is None:
63
+ console = Console(
64
+ file=sys.stdout,
65
+ force_terminal=True,
66
+ highlight=False,
67
+ log_time=True,
68
+ log_path=False,
69
+ )
70
+ self._console = console
71
+ self._highlight_json = highlight_json
72
+ self._stream_tokens = stream_tokens
73
+ self._stream_context: dict[str, tuple[asyncio.Queue, asyncio.Task]] = {}
74
+
75
+ async def on_initialize(self, agent, ctx: Context) -> None:
76
+ self._console.log(f"[{agent.name}] start task={ctx.task_id}")
77
+ await super().on_initialize(agent, ctx)
78
+
79
+ async def on_pre_consume(self, agent, ctx: Context, inputs: list[Any]):
80
+ summary = ", ".join(self._summarize_artifact(art) for art in inputs) or "<none>"
81
+ self._console.log(f"[{agent.name}] consume n={len(inputs)} artifacts -> {summary}")
82
+ self._render_artifacts(agent.name, inputs, role="input")
83
+ return await super().on_pre_consume(agent, ctx, inputs)
84
+
85
+ async def on_pre_evaluate(self, agent, ctx: Context, inputs: EvalInputs) -> EvalInputs:
86
+ if self._stream_tokens:
87
+ self._maybe_start_stream(agent, ctx)
88
+ return await super().on_pre_evaluate(agent, ctx, inputs)
89
+
90
+ async def on_post_evaluate(
91
+ self, agent, ctx: Context, inputs: EvalInputs, result: EvalResult
92
+ ) -> EvalResult:
93
+ self._render_metrics(agent.name, result.metrics)
94
+ self._render_artifacts(agent.name, result.artifacts or inputs.artifacts, role="output")
95
+ if result.logs:
96
+ self._render_logs(agent.name, result.logs)
97
+ awaited = await super().on_post_evaluate(agent, ctx, inputs, result)
98
+ if self._stream_tokens:
99
+ await self._finalize_stream(agent, ctx)
100
+ return awaited
101
+
102
+ async def on_post_publish(self, agent, ctx: Context, artifact):
103
+ visibility = getattr(artifact.visibility, "kind", "Public")
104
+ subtitle = f"visibility={visibility}"
105
+ panel = self._build_artifact_panel(artifact, role="published", subtitle=subtitle)
106
+ self._console.print(panel)
107
+ await super().on_post_publish(agent, ctx, artifact)
108
+
109
+ async def on_error(self, agent, ctx: Context, error: Exception) -> None:
110
+ self._console.log(f"[{agent.name}] error {error!r}", style="bold red")
111
+ if self._stream_tokens:
112
+ await self._abort_stream(agent, ctx)
113
+ await super().on_error(agent, ctx, error)
114
+
115
+ async def on_terminate(self, agent, ctx: Context) -> None:
116
+ if self._stream_tokens:
117
+ await self._abort_stream(agent, ctx)
118
+ self._console.log(f"[{agent.name}] end task={ctx.task_id}")
119
+ await super().on_terminate(agent, ctx)
120
+
121
+ # ------------------------------------------------------------------
122
+ # Rendering helpers
123
+
124
+ def _render_artifacts(self, agent_name: str, artifacts: Sequence[Any], *, role: str) -> None:
125
+ for artifact in artifacts:
126
+ panel = self._build_artifact_panel(artifact, role=role)
127
+ self._console.print(panel)
128
+
129
+ def _build_artifact_panel(
130
+ self, artifact: Any, *, role: str, subtitle: str | None = None
131
+ ) -> Panel:
132
+ title = f"{role} • {self._summarize_artifact(artifact)}"
133
+ if subtitle is None:
134
+ produced_by = getattr(artifact, "produced_by", None)
135
+ visibility = getattr(artifact, "visibility", None)
136
+ visibility_name = getattr(visibility, "kind", None)
137
+ pieces = []
138
+ if produced_by:
139
+ pieces.append(f"from={produced_by}")
140
+ if visibility_name:
141
+ pieces.append(f"visibility={visibility_name}")
142
+ subtitle = " | ".join(pieces)
143
+
144
+ payload = getattr(artifact, "payload", None)
145
+ renderable = self._render_payload(payload)
146
+ return Panel(renderable, title=title, subtitle=subtitle, border_style="cyan")
147
+
148
+ def _render_payload(self, payload: Any):
149
+ if payload is None:
150
+ return Pretty(payload)
151
+ if isinstance(payload, Mapping):
152
+ if self._highlight_json:
153
+ try:
154
+ return JSON.from_data(payload, indent=2, sort_keys=True)
155
+ except Exception: # nosec B110 - pragma: no cover - serialization guard
156
+ pass
157
+ return Pretty(dict(payload))
158
+ if isinstance(payload, (list, tuple, set)):
159
+ return Pretty(payload)
160
+ if hasattr(payload, "model_dump"):
161
+ model_dict = payload.model_dump()
162
+ return JSON.from_data(model_dict, indent=2, sort_keys=True)
163
+ return Pretty(payload)
164
+
165
+ def _render_metrics(self, agent_name: str, metrics: Mapping[str, Any]) -> None:
166
+ if not metrics:
167
+ return
168
+ table = Table(title=f"{agent_name} metrics", box=None, show_header=False)
169
+ for key, value in metrics.items():
170
+ display = f"{value:.2f}" if isinstance(value, (int, float)) else str(value)
171
+ table.add_row(key, display)
172
+ self._console.print(table)
173
+
174
+ def _render_logs(self, agent_name: str, logs: Iterable[str]) -> None:
175
+ if not logs:
176
+ return
177
+ textual: list[str] = []
178
+ json_sections: list[JSON] = []
179
+ for line in logs:
180
+ if line.startswith("dspy.output="):
181
+ _, _, payload = line.partition("=")
182
+ try:
183
+ json_sections.append(
184
+ JSON.from_data(json.loads(payload), indent=2, sort_keys=True)
185
+ )
186
+ continue
187
+ except json.JSONDecodeError:
188
+ textual.append(line)
189
+ else:
190
+ textual.append(line)
191
+ for payload in json_sections:
192
+ panel = Panel(payload, title=f"{agent_name} ▸ dspy.output", border_style="green")
193
+ self._console.print(panel)
194
+ if textual:
195
+ body = Text("\n".join(textual) + "\n")
196
+ panel = Panel(body, title=f"{agent_name} logs", border_style="magenta")
197
+ self._console.print(panel)
198
+
199
+ def _summarize_artifact(self, artifact: Any) -> str:
200
+ try:
201
+ art_id = getattr(artifact, "id", None)
202
+ prefix = str(art_id)[:8] if art_id else "?"
203
+ art_type = getattr(artifact, "type", type(artifact).__name__)
204
+ return f"{art_type}@{prefix}"
205
+ except Exception: # pragma: no cover - defensive
206
+ return repr(artifact)
207
+
208
+ # ------------------------------------------------------------------
209
+ # Streaming support
210
+
211
+ def _maybe_start_stream(self, agent, ctx: Context) -> None:
212
+ stream_key = self._stream_key(agent, ctx)
213
+ if stream_key in self._stream_context:
214
+ return
215
+ queue: asyncio.Queue = asyncio.Queue()
216
+ self._attach_stream_queue(ctx.state, queue)
217
+ task = asyncio.create_task(self._consume_stream(agent.name, stream_key, queue))
218
+ self._stream_context[stream_key] = (queue, task)
219
+
220
+ async def _finalize_stream(self, agent, ctx: Context) -> None:
221
+ stream_key = self._stream_key(agent, ctx)
222
+ record = self._stream_context.pop(stream_key, None)
223
+ self._detach_stream_queue(ctx.state)
224
+ if not record:
225
+ return
226
+ queue, task = record
227
+ if not task.done():
228
+ await queue.put({"kind": "end"})
229
+ try:
230
+ await asyncio.wait_for(task, timeout=2.0)
231
+ except asyncio.TimeoutError: # pragma: no cover - defensive cancel
232
+ task.cancel()
233
+
234
+ async def _abort_stream(self, agent, ctx: Context) -> None:
235
+ stream_key = self._stream_key(agent, ctx)
236
+ record = self._stream_context.pop(stream_key, None)
237
+ self._detach_stream_queue(ctx.state)
238
+ if not record:
239
+ return
240
+ queue, task = record
241
+ if not task.done():
242
+ await queue.put({"kind": "end", "error": "aborted"})
243
+ task.cancel()
244
+ with contextlib.suppress(asyncio.CancelledError):
245
+ await task
246
+
247
+ async def _consume_stream(self, agent_name: str, stream_key: str, queue: asyncio.Queue) -> None:
248
+ body = Text()
249
+ live: Live | None = None
250
+ try:
251
+ while True:
252
+ event = await queue.get()
253
+ if event is None or event.get("kind") == "end":
254
+ break
255
+ kind = event.get("kind")
256
+ if live is None:
257
+ live_panel = Panel(body, title=f"{agent_name} ▸ streaming", border_style="cyan")
258
+ live = Live(
259
+ live_panel,
260
+ console=self._console,
261
+ refresh_per_second=12,
262
+ transient=True,
263
+ )
264
+ live.__enter__()
265
+ if kind == "chunk":
266
+ chunk = event.get("chunk") or ""
267
+ body.append(chunk)
268
+ elif kind == "status":
269
+ message = event.get("message") or ""
270
+ stage = event.get("stage")
271
+ line = f"[{stage}] {message}" if stage else message
272
+ body.append(f"\n{line}\n", style="dim")
273
+ elif kind == "error":
274
+ message = event.get("message") or ""
275
+ body.append(f"\n⚠ {message}\n", style="bold red")
276
+ if live is not None:
277
+ live.update(Panel(body, title=f"{agent_name} ▸ streaming", border_style="cyan"))
278
+ finally:
279
+ if live is not None:
280
+ live.__exit__(None, None, None)
281
+ if body.plain:
282
+ self._console.print(
283
+ Panel(body, title=f"{agent_name} ▸ stream transcript", border_style="cyan")
284
+ )
285
+
286
+ def _stream_key(self, agent, ctx: Context) -> str:
287
+ return f"{ctx.task_id}:{agent.name}"
288
+
289
+ def _attach_stream_queue(self, state: MutableMapping[str, Any], queue: asyncio.Queue) -> None:
290
+ state.setdefault("_logging", {})["stream_queue"] = queue
291
+
292
+ def _detach_stream_queue(self, state: MutableMapping[str, Any]) -> None:
293
+ try:
294
+ logging_state = state.get("_logging")
295
+ if isinstance(logging_state, MutableMapping):
296
+ logging_state.pop("stream_queue", None)
297
+ except Exception: # nosec B110 - pragma: no cover - defensive
298
+ pass
299
+
300
+
301
+ __all__ = ["LoggingUtility", "MetricsUtility"]
@@ -6,19 +6,18 @@ from typing import TYPE_CHECKING, Any
6
6
 
7
7
  from pydantic import Field
8
8
 
9
- from flock.core.component.agent_component_base import AgentComponentConfig
10
- from flock.core.component.utility_component import UtilityComponent
11
- from flock.core.context.context import FlockContext
12
- from flock.core.context.context_vars import FLOCK_BATCH_SILENT_MODE
13
- from flock.core.logging.formatters.themed_formatter import (
9
+ from flock.components import AgentComponent, AgentComponentConfig
10
+ from flock.logging.formatters.themed_formatter import (
14
11
  ThemedAgentResultFormatter,
15
12
  )
16
- from flock.core.logging.formatters.themes import OutputTheme
17
- from flock.core.logging.logging import get_logger
18
- from flock.core.registry import flock_component
13
+ from flock.logging.formatters.themes import OutputTheme
14
+ from flock.logging.logging import get_logger
15
+ from flock.runtime import Context, EvalInputs, EvalResult
16
+
17
+
18
+ if TYPE_CHECKING: # pragma: no cover - type checking only
19
+ from flock.agent import Agent
19
20
 
20
- if TYPE_CHECKING:
21
- from flock.core.flock_agent import FlockAgent
22
21
 
23
22
  logger = get_logger("components.utility.output")
24
23
 
@@ -27,14 +26,10 @@ class OutputUtilityConfig(AgentComponentConfig):
27
26
  """Configuration for output formatting and display."""
28
27
 
29
28
  theme: OutputTheme = Field(
30
- default=OutputTheme.afterglow, description="Theme for output formatting"
31
- )
32
- render_table: bool = Field(
33
- default=False, description="Whether to render output as a table"
34
- )
35
- max_length: int = Field(
36
- default=1000, description="Maximum length for displayed output"
29
+ default=OutputTheme.catppuccin_mocha, description="Theme for output formatting"
37
30
  )
31
+ render_table: bool = Field(default=True, description="Whether to render output as a table")
32
+ max_length: int = Field(default=1000, description="Maximum length for displayed output")
38
33
  truncate_long_values: bool = Field(
39
34
  default=True, description="Whether to truncate long values in display"
40
35
  )
@@ -59,8 +54,7 @@ class OutputUtilityConfig(AgentComponentConfig):
59
54
  )
60
55
 
61
56
 
62
- @flock_component(config_class=OutputUtilityConfig)
63
- class OutputUtilityComponent(UtilityComponent):
57
+ class OutputUtilityComponent(AgentComponent):
64
58
  """Utility component that handles output formatting and display."""
65
59
 
66
60
  config: OutputUtilityConfig = Field(
@@ -88,12 +82,11 @@ class OutputUtilityComponent(UtilityComponent):
88
82
  # Default formatting based on type
89
83
  if isinstance(value, dict):
90
84
  return self._format_dict(value)
91
- elif isinstance(value, list):
85
+ if isinstance(value, list):
92
86
  return self._format_list(value)
93
- elif isinstance(value, str) and self.config.format_code_blocks:
87
+ if isinstance(value, str) and self.config.format_code_blocks:
94
88
  return self._format_potential_code(value)
95
- else:
96
- return str(value)
89
+ return str(value)
97
90
 
98
91
  def _format_dict(self, d: dict[str, Any], indent: int = 0) -> str:
99
92
  """Format a dictionary with proper indentation."""
@@ -124,6 +117,7 @@ class OutputUtilityComponent(UtilityComponent):
124
117
 
125
118
  def _format_potential_code(self, text: str) -> str:
126
119
  """Apply syntax highlighting to potential code blocks."""
120
+
127
121
  # Simple pattern matching for code blocks
128
122
  def replace_code_block(match):
129
123
  language = match.group(1) or "text"
@@ -131,57 +125,74 @@ class OutputUtilityComponent(UtilityComponent):
131
125
  return f"[CODE:{language}]\n{code}\n[/CODE]"
132
126
 
133
127
  # Replace markdown-style code blocks
134
- text = re.sub(
135
- r"```(\w+)?\n(.*?)\n```", replace_code_block, text, flags=re.DOTALL
136
- )
137
- return text
128
+ return re.sub(r"```(\w+)?\n(.*?)\n```", replace_code_block, text, flags=re.DOTALL)
138
129
 
139
130
  async def on_post_evaluate(
140
- self,
141
- agent: "FlockAgent",
142
- inputs: dict[str, Any],
143
- context: FlockContext | None = None,
144
- result: dict[str, Any] | None = None,
131
+ self, agent: "Agent", ctx: Context, inputs: EvalInputs, result: EvalResult
145
132
  ) -> dict[str, Any]:
146
133
  """Format and display the output."""
147
134
  logger.debug("Formatting and displaying output")
148
135
 
149
136
  streaming_live_handled = False
150
- if context:
151
- streaming_live_handled = bool(
152
- context.get_variable("_flock_stream_live_active", False)
153
- )
137
+ output_queued = False
138
+ streamed_artifact_id = None
139
+
140
+ if ctx:
141
+ streaming_live_handled = bool(ctx.get_variable("_flock_stream_live_active", False))
142
+ output_queued = bool(ctx.get_variable("_flock_output_queued", False))
143
+ streamed_artifact_id = ctx.get_variable("_flock_streamed_artifact_id")
144
+
154
145
  if streaming_live_handled:
155
- context.state.pop("_flock_stream_live_active", None)
146
+ ctx.state.pop("_flock_stream_live_active", None)
156
147
 
157
- # Determine if output should be suppressed
158
- is_silent = self.config.no_output or (
159
- context and context.get_variable(FLOCK_BATCH_SILENT_MODE, False)
160
- )
148
+ if output_queued:
149
+ ctx.state.pop("_flock_output_queued", None)
150
+
151
+ if streamed_artifact_id:
152
+ ctx.state.pop("_flock_streamed_artifact_id", None)
161
153
 
154
+ # If streaming was handled, we need to update the final display with the real artifact ID
155
+ if streaming_live_handled and streamed_artifact_id:
156
+ logger.debug(
157
+ f"Updating streamed display with final artifact ID: {streamed_artifact_id}"
158
+ )
159
+ # The streaming display already showed everything, we just need to update the ID
160
+ # This is handled by a final refresh in the streaming code
161
+ return result
162
+
163
+ # Skip output if streaming already handled it (and no ID to update)
162
164
  if streaming_live_handled:
163
165
  logger.debug("Skipping static table because streaming rendered live output.")
164
166
  return result
165
167
 
166
- if is_silent:
167
- logger.debug("Output suppressed (config or batch silent mode).")
168
- return result # Skip console output
168
+ # If output was queued due to concurrent stream, wait and then display
169
+ if output_queued:
170
+ # Wait for active streams to complete
171
+ orchestrator = getattr(ctx, "orchestrator", None)
172
+ if orchestrator:
173
+ import asyncio
174
+
175
+ # Wait until no streams are active
176
+ max_wait = 30 # seconds
177
+ waited = 0
178
+ while getattr(orchestrator, "_active_streams", 0) > 0 and waited < max_wait:
179
+ await asyncio.sleep(0.1)
180
+ waited += 0.1
181
+ logger.debug(
182
+ f"Queued output displayed after waiting {waited:.1f}s for streams to complete."
183
+ )
169
184
 
170
185
  logger.debug("Formatting and displaying output to console.")
171
186
 
172
- if self.config.print_context and context:
187
+ if self.config.print_context and ctx:
173
188
  # Add context snapshot if requested (be careful with large contexts)
174
189
  try:
175
190
  # Create a copy or select relevant parts to avoid modifying original result dict directly
176
191
  display_result = result.copy()
177
- display_result["context_snapshot"] = (
178
- context.to_dict()
179
- ) # Potential performance hit
192
+ display_result["context_snapshot"] = ctx.to_dict() # Potential performance hit
180
193
  except Exception:
181
194
  display_result = result.copy()
182
- display_result["context_snapshot"] = (
183
- "[Error serializing context]"
184
- )
195
+ display_result["context_snapshot"] = "[Error serializing context]"
185
196
  result_to_display = display_result
186
197
  else:
187
198
  result_to_display = result
@@ -192,8 +203,12 @@ class OutputUtilityComponent(UtilityComponent):
192
203
  max_length=self.config.max_length,
193
204
  render_table=self.config.render_table,
194
205
  )
195
- model = agent.model if agent.model else context.get_variable("model")
196
- self._formatter.display_result(result_to_display, agent.name + " - " + model)
206
+ model = agent.model if agent.model else ctx.get_variable("model")
207
+ # Handle None model gracefully
208
+ model_display = model if model is not None else "default"
209
+ self._formatter.display_result(
210
+ result_to_display.artifacts, agent.name + " - " + model_display
211
+ )
197
212
 
198
213
  return result # Return the original, unmodified result
199
214
 
flock/visibility.py ADDED
@@ -0,0 +1,107 @@
1
+ from __future__ import annotations
2
+
3
+
4
+ """Artifact visibility policies."""
5
+
6
+ from datetime import datetime, timedelta, timezone
7
+ from typing import TYPE_CHECKING, Literal
8
+
9
+ from pydantic import BaseModel, Field, PrivateAttr
10
+
11
+
12
+ if TYPE_CHECKING:
13
+ from collections.abc import Iterable
14
+
15
+
16
+ class AgentIdentity(BaseModel):
17
+ """Minimal identity information about an agent for visibility checks."""
18
+
19
+ name: str
20
+ labels: set[str] = Field(default_factory=set)
21
+ tenant_id: str | None = None
22
+
23
+
24
+ class Visibility(BaseModel):
25
+ """Base visibility contract."""
26
+
27
+ kind: Literal["Public", "Private", "Labelled", "Tenant", "After"]
28
+
29
+ def allows(self, agent: AgentIdentity, *, now: datetime | None = None) -> bool:
30
+ raise NotImplementedError
31
+
32
+
33
+ class PublicVisibility(Visibility):
34
+ kind: Literal["Public"] = "Public"
35
+
36
+ def allows(
37
+ self, agent: AgentIdentity, *, now: datetime | None = None
38
+ ) -> bool: # pragma: no cover - trivial
39
+ return True
40
+
41
+
42
+ class PrivateVisibility(Visibility):
43
+ kind: Literal["Private"] = "Private"
44
+ agents: set[str] = Field(default_factory=set)
45
+
46
+ def allows(self, agent: AgentIdentity, *, now: datetime | None = None) -> bool:
47
+ return agent.name in self.agents
48
+
49
+
50
+ class LabelledVisibility(Visibility):
51
+ kind: Literal["Labelled"] = "Labelled"
52
+ required_labels: set[str] = Field(default_factory=set)
53
+
54
+ def allows(self, agent: AgentIdentity, *, now: datetime | None = None) -> bool:
55
+ return self.required_labels.issubset(agent.labels)
56
+
57
+
58
+ class TenantVisibility(Visibility):
59
+ kind: Literal["Tenant"] = "Tenant"
60
+ tenant_id: str | None = None
61
+
62
+ def allows(self, agent: AgentIdentity, *, now: datetime | None = None) -> bool:
63
+ if self.tenant_id is None:
64
+ return True
65
+ return agent.tenant_id == self.tenant_id
66
+
67
+
68
+ class AfterVisibility(Visibility):
69
+ kind: Literal["After"] = "After"
70
+ ttl: timedelta = Field(default=timedelta())
71
+ then: Visibility | None = None
72
+ _created_at: datetime = PrivateAttr(default_factory=lambda: datetime.now(timezone.utc))
73
+
74
+ def allows(self, agent: AgentIdentity, *, now: datetime | None = None) -> bool:
75
+ now = now or datetime.now(timezone.utc)
76
+ if now - self._created_at >= self.ttl:
77
+ if self.then:
78
+ return self.then.allows(agent, now=now)
79
+ return True
80
+ return False
81
+
82
+
83
+ def ensure_visibility(value: Visibility | None) -> Visibility:
84
+ if value is None:
85
+ return PublicVisibility()
86
+ return value
87
+
88
+
89
+ def only_for(*agent_names: str) -> PrivateVisibility:
90
+ return PrivateVisibility(agents=set(agent_names))
91
+
92
+
93
+ def agents_from_names(names: Iterable[str]) -> set[str]: # pragma: no cover - helper
94
+ return set(names)
95
+
96
+
97
+ __all__ = [
98
+ "AfterVisibility",
99
+ "AgentIdentity",
100
+ "LabelledVisibility",
101
+ "PrivateVisibility",
102
+ "PublicVisibility",
103
+ "TenantVisibility",
104
+ "Visibility",
105
+ "ensure_visibility",
106
+ "only_for",
107
+ ]