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

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

Potentially problematic release.


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

Files changed (501) hide show
  1. flock/__init__.py +12 -217
  2. flock/agent.py +1079 -0
  3. flock/api/themes.py +71 -0
  4. flock/artifacts.py +86 -0
  5. flock/cli.py +147 -0
  6. flock/components.py +189 -0
  7. flock/dashboard/__init__.py +30 -0
  8. flock/dashboard/collector.py +559 -0
  9. flock/dashboard/events.py +188 -0
  10. flock/dashboard/graph_builder.py +563 -0
  11. flock/dashboard/launcher.py +235 -0
  12. flock/dashboard/models/graph.py +156 -0
  13. flock/dashboard/service.py +991 -0
  14. flock/dashboard/static_v2/assets/index-DFRnI_mt.js +111 -0
  15. flock/dashboard/static_v2/assets/index-fPLNdmp1.css +1 -0
  16. flock/dashboard/static_v2/index.html +13 -0
  17. flock/dashboard/websocket.py +246 -0
  18. flock/engines/__init__.py +6 -0
  19. flock/engines/dspy_engine.py +932 -0
  20. flock/examples.py +131 -0
  21. flock/frontend/README.md +778 -0
  22. flock/frontend/docs/DESIGN_SYSTEM.md +1980 -0
  23. flock/frontend/index.html +12 -0
  24. flock/frontend/package-lock.json +4337 -0
  25. flock/frontend/package.json +48 -0
  26. flock/frontend/src/App.tsx +139 -0
  27. flock/frontend/src/__tests__/integration/graph-snapshot.test.tsx +647 -0
  28. flock/frontend/src/__tests__/integration/indexeddb-persistence.test.tsx +699 -0
  29. flock/frontend/src/components/common/BuildInfo.tsx +39 -0
  30. flock/frontend/src/components/common/EmptyState.module.css +115 -0
  31. flock/frontend/src/components/common/EmptyState.tsx +128 -0
  32. flock/frontend/src/components/common/ErrorBoundary.module.css +169 -0
  33. flock/frontend/src/components/common/ErrorBoundary.tsx +118 -0
  34. flock/frontend/src/components/common/KeyboardShortcutsDialog.css +251 -0
  35. flock/frontend/src/components/common/KeyboardShortcutsDialog.tsx +151 -0
  36. flock/frontend/src/components/common/LoadingSpinner.module.css +97 -0
  37. flock/frontend/src/components/common/LoadingSpinner.tsx +29 -0
  38. flock/frontend/src/components/controls/PublishControl.css +547 -0
  39. flock/frontend/src/components/controls/PublishControl.test.tsx +543 -0
  40. flock/frontend/src/components/controls/PublishControl.tsx +432 -0
  41. flock/frontend/src/components/details/DetailWindowContainer.tsx +58 -0
  42. flock/frontend/src/components/details/LiveOutputTab.test.tsx +792 -0
  43. flock/frontend/src/components/details/LiveOutputTab.tsx +220 -0
  44. flock/frontend/src/components/details/MessageDetailWindow.tsx +439 -0
  45. flock/frontend/src/components/details/MessageHistoryTab.tsx +374 -0
  46. flock/frontend/src/components/details/NodeDetailWindow.test.tsx +501 -0
  47. flock/frontend/src/components/details/NodeDetailWindow.tsx +218 -0
  48. flock/frontend/src/components/details/RunStatusTab.tsx +348 -0
  49. flock/frontend/src/components/details/tabs.test.tsx +1015 -0
  50. flock/frontend/src/components/filters/ArtifactTypeFilter.tsx +21 -0
  51. flock/frontend/src/components/filters/CorrelationIDFilter.module.css +102 -0
  52. flock/frontend/src/components/filters/CorrelationIDFilter.test.tsx +197 -0
  53. flock/frontend/src/components/filters/CorrelationIDFilter.tsx +121 -0
  54. flock/frontend/src/components/filters/FilterFlyout.module.css +104 -0
  55. flock/frontend/src/components/filters/FilterFlyout.tsx +80 -0
  56. flock/frontend/src/components/filters/FilterPills.module.css +220 -0
  57. flock/frontend/src/components/filters/FilterPills.test.tsx +189 -0
  58. flock/frontend/src/components/filters/FilterPills.tsx +143 -0
  59. flock/frontend/src/components/filters/ProducerFilter.tsx +21 -0
  60. flock/frontend/src/components/filters/SavedFiltersControl.module.css +60 -0
  61. flock/frontend/src/components/filters/SavedFiltersControl.test.tsx +158 -0
  62. flock/frontend/src/components/filters/SavedFiltersControl.tsx +159 -0
  63. flock/frontend/src/components/filters/TagFilter.tsx +21 -0
  64. flock/frontend/src/components/filters/TimeRangeFilter.module.css +115 -0
  65. flock/frontend/src/components/filters/TimeRangeFilter.test.tsx +154 -0
  66. flock/frontend/src/components/filters/TimeRangeFilter.tsx +110 -0
  67. flock/frontend/src/components/filters/VisibilityFilter.tsx +21 -0
  68. flock/frontend/src/components/graph/AgentNode.test.tsx +77 -0
  69. flock/frontend/src/components/graph/AgentNode.tsx +324 -0
  70. flock/frontend/src/components/graph/GraphCanvas.tsx +613 -0
  71. flock/frontend/src/components/graph/MessageFlowEdge.tsx +128 -0
  72. flock/frontend/src/components/graph/MessageNode.test.tsx +64 -0
  73. flock/frontend/src/components/graph/MessageNode.tsx +129 -0
  74. flock/frontend/src/components/graph/MiniMap.tsx +47 -0
  75. flock/frontend/src/components/graph/TransformEdge.tsx +123 -0
  76. flock/frontend/src/components/layout/DashboardLayout.css +420 -0
  77. flock/frontend/src/components/layout/DashboardLayout.tsx +287 -0
  78. flock/frontend/src/components/layout/Header.module.css +88 -0
  79. flock/frontend/src/components/layout/Header.tsx +52 -0
  80. flock/frontend/src/components/modules/HistoricalArtifactsModule.module.css +288 -0
  81. flock/frontend/src/components/modules/HistoricalArtifactsModule.tsx +450 -0
  82. flock/frontend/src/components/modules/HistoricalArtifactsModuleWrapper.tsx +13 -0
  83. flock/frontend/src/components/modules/JsonAttributeRenderer.tsx +140 -0
  84. flock/frontend/src/components/modules/ModuleRegistry.test.ts +333 -0
  85. flock/frontend/src/components/modules/ModuleRegistry.ts +93 -0
  86. flock/frontend/src/components/modules/ModuleWindow.tsx +223 -0
  87. flock/frontend/src/components/modules/TraceModuleJaeger.tsx +1971 -0
  88. flock/frontend/src/components/modules/TraceModuleJaegerWrapper.tsx +13 -0
  89. flock/frontend/src/components/modules/registerModules.ts +29 -0
  90. flock/frontend/src/components/settings/AdvancedSettings.tsx +175 -0
  91. flock/frontend/src/components/settings/AppearanceSettings.tsx +185 -0
  92. flock/frontend/src/components/settings/GraphSettings.tsx +110 -0
  93. flock/frontend/src/components/settings/MultiSelect.tsx +235 -0
  94. flock/frontend/src/components/settings/SettingsPanel.css +327 -0
  95. flock/frontend/src/components/settings/SettingsPanel.tsx +131 -0
  96. flock/frontend/src/components/settings/ThemeSelector.tsx +298 -0
  97. flock/frontend/src/components/settings/TracingSettings.tsx +404 -0
  98. flock/frontend/src/hooks/useKeyboardShortcuts.ts +148 -0
  99. flock/frontend/src/hooks/useModulePersistence.test.ts +442 -0
  100. flock/frontend/src/hooks/useModulePersistence.ts +154 -0
  101. flock/frontend/src/hooks/useModules.ts +157 -0
  102. flock/frontend/src/hooks/usePersistence.ts +141 -0
  103. flock/frontend/src/main.tsx +13 -0
  104. flock/frontend/src/services/api.ts +337 -0
  105. flock/frontend/src/services/graphService.test.ts +330 -0
  106. flock/frontend/src/services/graphService.ts +75 -0
  107. flock/frontend/src/services/indexeddb.test.ts +793 -0
  108. flock/frontend/src/services/indexeddb.ts +848 -0
  109. flock/frontend/src/services/layout.test.ts +437 -0
  110. flock/frontend/src/services/layout.ts +357 -0
  111. flock/frontend/src/services/themeApplicator.ts +140 -0
  112. flock/frontend/src/services/themeService.ts +77 -0
  113. flock/frontend/src/services/websocket.ts +650 -0
  114. flock/frontend/src/store/filterStore.test.ts +250 -0
  115. flock/frontend/src/store/filterStore.ts +272 -0
  116. flock/frontend/src/store/graphStore.test.ts +570 -0
  117. flock/frontend/src/store/graphStore.ts +462 -0
  118. flock/frontend/src/store/moduleStore.test.ts +253 -0
  119. flock/frontend/src/store/moduleStore.ts +75 -0
  120. flock/frontend/src/store/settingsStore.ts +188 -0
  121. flock/frontend/src/store/streamStore.ts +68 -0
  122. flock/frontend/src/store/uiStore.test.ts +54 -0
  123. flock/frontend/src/store/uiStore.ts +122 -0
  124. flock/frontend/src/store/wsStore.ts +34 -0
  125. flock/frontend/src/styles/index.css +15 -0
  126. flock/frontend/src/styles/scrollbar.css +47 -0
  127. flock/frontend/src/styles/variables.css +488 -0
  128. flock/frontend/src/test/setup.ts +1 -0
  129. flock/frontend/src/types/filters.ts +47 -0
  130. flock/frontend/src/types/graph.ts +95 -0
  131. flock/frontend/src/types/modules.ts +10 -0
  132. flock/frontend/src/types/theme.ts +55 -0
  133. flock/frontend/src/utils/artifacts.ts +24 -0
  134. flock/frontend/src/utils/mockData.ts +98 -0
  135. flock/frontend/src/utils/performance.ts +16 -0
  136. flock/frontend/src/vite-env.d.ts +17 -0
  137. flock/frontend/tsconfig.json +27 -0
  138. flock/frontend/tsconfig.node.json +11 -0
  139. flock/frontend/vite.config.ts +25 -0
  140. flock/frontend/vitest.config.ts +11 -0
  141. flock/{core/util → helper}/cli_helper.py +9 -5
  142. flock/{core/logging → logging}/__init__.py +2 -3
  143. flock/logging/auto_trace.py +159 -0
  144. flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
  145. flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
  146. flock/{core/logging → logging}/formatters/themed_formatter.py +69 -107
  147. flock/{core/logging → logging}/logging.py +78 -61
  148. flock/{core/logging → logging}/telemetry.py +66 -26
  149. flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
  150. flock/logging/telemetry_exporter/duckdb_exporter.py +216 -0
  151. flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +13 -10
  152. flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
  153. flock/logging/trace_and_logged.py +304 -0
  154. flock/mcp/__init__.py +91 -0
  155. flock/{core/mcp/mcp_client.py → mcp/client.py} +131 -158
  156. flock/{core/mcp/mcp_config.py → mcp/config.py} +86 -132
  157. flock/mcp/manager.py +286 -0
  158. flock/mcp/servers/sse/__init__.py +1 -1
  159. flock/mcp/servers/sse/flock_sse_server.py +16 -58
  160. flock/mcp/servers/stdio/__init__.py +1 -1
  161. flock/mcp/servers/stdio/flock_stdio_server.py +13 -53
  162. flock/mcp/servers/streamable_http/flock_streamable_http_server.py +22 -67
  163. flock/mcp/servers/websockets/flock_websocket_server.py +12 -45
  164. flock/{core/mcp/flock_mcp_tool_base.py → mcp/tool.py} +24 -78
  165. flock/mcp/types/__init__.py +42 -0
  166. flock/{core/mcp → mcp}/types/callbacks.py +12 -15
  167. flock/{core/mcp → mcp}/types/factories.py +7 -6
  168. flock/{core/mcp → mcp}/types/handlers.py +13 -18
  169. flock/{core/mcp → mcp}/types/types.py +70 -74
  170. flock/{core/mcp → mcp}/util/helpers.py +3 -3
  171. flock/orchestrator.py +970 -0
  172. flock/registry.py +148 -0
  173. flock/runtime.py +262 -0
  174. flock/service.py +277 -0
  175. flock/store.py +1214 -0
  176. flock/subscription.py +111 -0
  177. flock/themes/andromeda.toml +1 -1
  178. flock/themes/apple-system-colors.toml +1 -1
  179. flock/themes/arcoiris.toml +1 -1
  180. flock/themes/atomonelight.toml +1 -1
  181. flock/themes/ayu copy.toml +1 -1
  182. flock/themes/ayu-light.toml +1 -1
  183. flock/themes/belafonte-day.toml +1 -1
  184. flock/themes/belafonte-night.toml +1 -1
  185. flock/themes/blulocodark.toml +1 -1
  186. flock/themes/breeze.toml +1 -1
  187. flock/themes/broadcast.toml +1 -1
  188. flock/themes/brogrammer.toml +1 -1
  189. flock/themes/builtin-dark.toml +1 -1
  190. flock/themes/builtin-pastel-dark.toml +1 -1
  191. flock/themes/catppuccin-latte.toml +1 -1
  192. flock/themes/catppuccin-macchiato.toml +1 -1
  193. flock/themes/catppuccin-mocha.toml +1 -1
  194. flock/themes/cga.toml +1 -1
  195. flock/themes/chalk.toml +1 -1
  196. flock/themes/ciapre.toml +1 -1
  197. flock/themes/coffee-theme.toml +1 -1
  198. flock/themes/cyberpunkscarletprotocol.toml +1 -1
  199. flock/themes/dark+.toml +1 -1
  200. flock/themes/darkermatrix.toml +1 -1
  201. flock/themes/darkmatrix.toml +2 -2
  202. flock/themes/darkside.toml +1 -1
  203. flock/themes/deep.toml +2 -2
  204. flock/themes/desert.toml +1 -1
  205. flock/themes/django.toml +1 -1
  206. flock/themes/djangosmooth.toml +1 -1
  207. flock/themes/doomone.toml +1 -1
  208. flock/themes/dotgov.toml +1 -1
  209. flock/themes/dracula+.toml +1 -1
  210. flock/themes/duckbones.toml +1 -1
  211. flock/themes/encom.toml +1 -1
  212. flock/themes/espresso.toml +1 -1
  213. flock/themes/everblush.toml +1 -1
  214. flock/themes/fairyfloss.toml +1 -1
  215. flock/themes/fideloper.toml +1 -1
  216. flock/themes/fishtank.toml +1 -1
  217. flock/themes/flexoki-light.toml +1 -1
  218. flock/themes/floraverse.toml +1 -1
  219. flock/themes/framer.toml +1 -1
  220. flock/themes/galizur.toml +1 -1
  221. flock/themes/github.toml +1 -1
  222. flock/themes/grass.toml +1 -1
  223. flock/themes/grey-green.toml +1 -1
  224. flock/themes/gruvboxlight.toml +1 -1
  225. flock/themes/guezwhoz.toml +1 -1
  226. flock/themes/harper.toml +1 -1
  227. flock/themes/hax0r-blue.toml +1 -1
  228. flock/themes/hopscotch.256.toml +1 -1
  229. flock/themes/ic-green-ppl.toml +1 -1
  230. flock/themes/iceberg-dark.toml +1 -1
  231. flock/themes/japanesque.toml +1 -1
  232. flock/themes/jubi.toml +1 -1
  233. flock/themes/kibble.toml +1 -1
  234. flock/themes/kolorit.toml +1 -1
  235. flock/themes/kurokula.toml +1 -1
  236. flock/themes/materialdesigncolors.toml +1 -1
  237. flock/themes/matrix.toml +1 -1
  238. flock/themes/mellifluous.toml +1 -1
  239. flock/themes/midnight-in-mojave.toml +1 -1
  240. flock/themes/monokai-remastered.toml +1 -1
  241. flock/themes/monokai-soda.toml +1 -1
  242. flock/themes/neon.toml +1 -1
  243. flock/themes/neopolitan.toml +5 -5
  244. flock/themes/nord-light.toml +1 -1
  245. flock/themes/ocean.toml +1 -1
  246. flock/themes/onehalfdark.toml +1 -1
  247. flock/themes/onehalflight.toml +1 -1
  248. flock/themes/palenighthc.toml +1 -1
  249. flock/themes/paulmillr.toml +1 -1
  250. flock/themes/pencildark.toml +1 -1
  251. flock/themes/pnevma.toml +1 -1
  252. flock/themes/purple-rain.toml +1 -1
  253. flock/themes/purplepeter.toml +1 -1
  254. flock/themes/raycast-dark.toml +1 -1
  255. flock/themes/red-sands.toml +1 -1
  256. flock/themes/relaxed.toml +1 -1
  257. flock/themes/retro.toml +1 -1
  258. flock/themes/rose-pine.toml +1 -1
  259. flock/themes/royal.toml +1 -1
  260. flock/themes/ryuuko.toml +1 -1
  261. flock/themes/sakura.toml +1 -1
  262. flock/themes/scarlet-protocol.toml +1 -1
  263. flock/themes/seoulbones-dark.toml +1 -1
  264. flock/themes/shades-of-purple.toml +1 -1
  265. flock/themes/smyck.toml +1 -1
  266. flock/themes/softserver.toml +1 -1
  267. flock/themes/solarized-darcula.toml +1 -1
  268. flock/themes/square.toml +1 -1
  269. flock/themes/sugarplum.toml +1 -1
  270. flock/themes/thayer-bright.toml +1 -1
  271. flock/themes/tokyonight.toml +1 -1
  272. flock/themes/tomorrow.toml +1 -1
  273. flock/themes/ubuntu.toml +1 -1
  274. flock/themes/ultradark.toml +1 -1
  275. flock/themes/ultraviolent.toml +1 -1
  276. flock/themes/unikitty.toml +1 -1
  277. flock/themes/urple.toml +1 -1
  278. flock/themes/vesper.toml +1 -1
  279. flock/themes/vimbones.toml +1 -1
  280. flock/themes/wildcherry.toml +1 -1
  281. flock/themes/wilmersdorf.toml +1 -1
  282. flock/themes/wryan.toml +1 -1
  283. flock/themes/xcodedarkhc.toml +1 -1
  284. flock/themes/xcodelight.toml +1 -1
  285. flock/themes/zenbones-light.toml +1 -1
  286. flock/themes/zenwritten-dark.toml +1 -1
  287. flock/utilities.py +301 -0
  288. flock/utility/output_utility_component.py +226 -0
  289. flock/visibility.py +107 -0
  290. flock_core-0.5.0.dist-info/METADATA +964 -0
  291. flock_core-0.5.0.dist-info/RECORD +525 -0
  292. flock_core-0.5.0.dist-info/entry_points.txt +2 -0
  293. {flock_core-0.4.542.dist-info → flock_core-0.5.0.dist-info}/licenses/LICENSE +1 -1
  294. flock/adapter/__init__.py +0 -14
  295. flock/adapter/azure_adapter.py +0 -68
  296. flock/adapter/chroma_adapter.py +0 -73
  297. flock/adapter/faiss_adapter.py +0 -97
  298. flock/adapter/pinecone_adapter.py +0 -51
  299. flock/adapter/vector_base.py +0 -47
  300. flock/cli/assets/release_notes.md +0 -140
  301. flock/cli/config.py +0 -8
  302. flock/cli/constants.py +0 -36
  303. flock/cli/create_agent.py +0 -1
  304. flock/cli/create_flock.py +0 -280
  305. flock/cli/execute_flock.py +0 -620
  306. flock/cli/load_agent.py +0 -1
  307. flock/cli/load_examples.py +0 -1
  308. flock/cli/load_flock.py +0 -192
  309. flock/cli/load_release_notes.py +0 -20
  310. flock/cli/loaded_flock_cli.py +0 -254
  311. flock/cli/manage_agents.py +0 -459
  312. flock/cli/registry_management.py +0 -889
  313. flock/cli/runner.py +0 -41
  314. flock/cli/settings.py +0 -857
  315. flock/cli/utils.py +0 -135
  316. flock/cli/view_results.py +0 -29
  317. flock/cli/yaml_editor.py +0 -396
  318. flock/config.py +0 -56
  319. flock/core/__init__.py +0 -44
  320. flock/core/api/__init__.py +0 -10
  321. flock/core/api/custom_endpoint.py +0 -45
  322. flock/core/api/endpoints.py +0 -262
  323. flock/core/api/main.py +0 -162
  324. flock/core/api/models.py +0 -101
  325. flock/core/api/run_store.py +0 -224
  326. flock/core/api/runner.py +0 -44
  327. flock/core/api/service.py +0 -214
  328. flock/core/config/flock_agent_config.py +0 -11
  329. flock/core/config/scheduled_agent_config.py +0 -40
  330. flock/core/context/context.py +0 -214
  331. flock/core/context/context_manager.py +0 -40
  332. flock/core/context/context_vars.py +0 -11
  333. flock/core/evaluation/utils.py +0 -395
  334. flock/core/execution/batch_executor.py +0 -369
  335. flock/core/execution/evaluation_executor.py +0 -438
  336. flock/core/execution/local_executor.py +0 -31
  337. flock/core/execution/opik_executor.py +0 -103
  338. flock/core/execution/temporal_executor.py +0 -166
  339. flock/core/flock.py +0 -1003
  340. flock/core/flock_agent.py +0 -1258
  341. flock/core/flock_evaluator.py +0 -60
  342. flock/core/flock_factory.py +0 -513
  343. flock/core/flock_module.py +0 -207
  344. flock/core/flock_registry.py +0 -702
  345. flock/core/flock_router.py +0 -83
  346. flock/core/flock_scheduler.py +0 -166
  347. flock/core/flock_server_manager.py +0 -136
  348. flock/core/interpreter/python_interpreter.py +0 -689
  349. flock/core/logging/live_capture.py +0 -137
  350. flock/core/logging/trace_and_logged.py +0 -59
  351. flock/core/mcp/__init__.py +0 -1
  352. flock/core/mcp/flock_mcp_server.py +0 -640
  353. flock/core/mcp/mcp_client_manager.py +0 -201
  354. flock/core/mcp/types/__init__.py +0 -1
  355. flock/core/mixin/dspy_integration.py +0 -445
  356. flock/core/mixin/prompt_parser.py +0 -125
  357. flock/core/serialization/__init__.py +0 -13
  358. flock/core/serialization/callable_registry.py +0 -52
  359. flock/core/serialization/flock_serializer.py +0 -854
  360. flock/core/serialization/json_encoder.py +0 -41
  361. flock/core/serialization/secure_serializer.py +0 -175
  362. flock/core/serialization/serializable.py +0 -342
  363. flock/core/serialization/serialization_utils.py +0 -409
  364. flock/core/util/file_path_utils.py +0 -223
  365. flock/core/util/hydrator.py +0 -309
  366. flock/core/util/input_resolver.py +0 -141
  367. flock/core/util/loader.py +0 -59
  368. flock/core/util/splitter.py +0 -219
  369. flock/di.py +0 -41
  370. flock/evaluators/__init__.py +0 -1
  371. flock/evaluators/declarative/__init__.py +0 -1
  372. flock/evaluators/declarative/declarative_evaluator.py +0 -217
  373. flock/evaluators/memory/memory_evaluator.py +0 -90
  374. flock/evaluators/test/test_case_evaluator.py +0 -38
  375. flock/evaluators/zep/zep_evaluator.py +0 -59
  376. flock/modules/__init__.py +0 -1
  377. flock/modules/assertion/__init__.py +0 -1
  378. flock/modules/assertion/assertion_module.py +0 -286
  379. flock/modules/callback/__init__.py +0 -1
  380. flock/modules/callback/callback_module.py +0 -91
  381. flock/modules/enterprise_memory/README.md +0 -99
  382. flock/modules/enterprise_memory/enterprise_memory_module.py +0 -526
  383. flock/modules/mem0/__init__.py +0 -1
  384. flock/modules/mem0/mem0_module.py +0 -126
  385. flock/modules/mem0_async/__init__.py +0 -1
  386. flock/modules/mem0_async/async_mem0_module.py +0 -126
  387. flock/modules/memory/__init__.py +0 -1
  388. flock/modules/memory/memory_module.py +0 -429
  389. flock/modules/memory/memory_parser.py +0 -125
  390. flock/modules/memory/memory_storage.py +0 -736
  391. flock/modules/output/__init__.py +0 -1
  392. flock/modules/output/output_module.py +0 -196
  393. flock/modules/performance/__init__.py +0 -1
  394. flock/modules/performance/metrics_module.py +0 -678
  395. flock/modules/zep/__init__.py +0 -1
  396. flock/modules/zep/zep_module.py +0 -192
  397. flock/platform/docker_tools.py +0 -49
  398. flock/platform/jaeger_install.py +0 -86
  399. flock/routers/__init__.py +0 -1
  400. flock/routers/agent/__init__.py +0 -1
  401. flock/routers/agent/agent_router.py +0 -236
  402. flock/routers/agent/handoff_agent.py +0 -58
  403. flock/routers/conditional/conditional_router.py +0 -486
  404. flock/routers/default/__init__.py +0 -1
  405. flock/routers/default/default_router.py +0 -80
  406. flock/routers/feedback/feedback_router.py +0 -114
  407. flock/routers/list_generator/list_generator_router.py +0 -166
  408. flock/routers/llm/__init__.py +0 -1
  409. flock/routers/llm/llm_router.py +0 -365
  410. flock/tools/__init__.py +0 -0
  411. flock/tools/azure_tools.py +0 -781
  412. flock/tools/code_tools.py +0 -167
  413. flock/tools/file_tools.py +0 -149
  414. flock/tools/github_tools.py +0 -157
  415. flock/tools/markdown_tools.py +0 -205
  416. flock/tools/system_tools.py +0 -9
  417. flock/tools/text_tools.py +0 -810
  418. flock/tools/web_tools.py +0 -92
  419. flock/tools/zendesk_tools.py +0 -501
  420. flock/webapp/__init__.py +0 -1
  421. flock/webapp/app/__init__.py +0 -0
  422. flock/webapp/app/api/__init__.py +0 -0
  423. flock/webapp/app/api/agent_management.py +0 -237
  424. flock/webapp/app/api/execution.py +0 -503
  425. flock/webapp/app/api/flock_management.py +0 -125
  426. flock/webapp/app/api/registry_viewer.py +0 -29
  427. flock/webapp/app/chat.py +0 -662
  428. flock/webapp/app/config.py +0 -104
  429. flock/webapp/app/dependencies.py +0 -117
  430. flock/webapp/app/main.py +0 -1086
  431. flock/webapp/app/middleware.py +0 -113
  432. flock/webapp/app/models_ui.py +0 -7
  433. flock/webapp/app/services/__init__.py +0 -0
  434. flock/webapp/app/services/feedback_file_service.py +0 -363
  435. flock/webapp/app/services/flock_service.py +0 -345
  436. flock/webapp/app/services/sharing_models.py +0 -81
  437. flock/webapp/app/services/sharing_store.py +0 -597
  438. flock/webapp/app/templates/theme_mapper.html +0 -326
  439. flock/webapp/app/theme_mapper.py +0 -811
  440. flock/webapp/app/utils.py +0 -85
  441. flock/webapp/run.py +0 -219
  442. flock/webapp/static/css/chat.css +0 -301
  443. flock/webapp/static/css/components.css +0 -167
  444. flock/webapp/static/css/header.css +0 -39
  445. flock/webapp/static/css/layout.css +0 -281
  446. flock/webapp/static/css/sidebar.css +0 -127
  447. flock/webapp/static/css/two-pane.css +0 -48
  448. flock/webapp/templates/base.html +0 -389
  449. flock/webapp/templates/chat.html +0 -152
  450. flock/webapp/templates/chat_settings.html +0 -19
  451. flock/webapp/templates/flock_editor.html +0 -16
  452. flock/webapp/templates/index.html +0 -12
  453. flock/webapp/templates/partials/_agent_detail_form.html +0 -93
  454. flock/webapp/templates/partials/_agent_list.html +0 -18
  455. flock/webapp/templates/partials/_agent_manager_view.html +0 -51
  456. flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
  457. flock/webapp/templates/partials/_chat_container.html +0 -15
  458. flock/webapp/templates/partials/_chat_messages.html +0 -57
  459. flock/webapp/templates/partials/_chat_settings_form.html +0 -85
  460. flock/webapp/templates/partials/_create_flock_form.html +0 -50
  461. flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
  462. flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
  463. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
  464. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
  465. flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
  466. flock/webapp/templates/partials/_env_vars_table.html +0 -23
  467. flock/webapp/templates/partials/_execution_form.html +0 -127
  468. flock/webapp/templates/partials/_execution_view_container.html +0 -28
  469. flock/webapp/templates/partials/_flock_file_list.html +0 -23
  470. flock/webapp/templates/partials/_flock_properties_form.html +0 -52
  471. flock/webapp/templates/partials/_flock_upload_form.html +0 -16
  472. flock/webapp/templates/partials/_header_flock_status.html +0 -5
  473. flock/webapp/templates/partials/_live_logs.html +0 -13
  474. flock/webapp/templates/partials/_load_manager_view.html +0 -49
  475. flock/webapp/templates/partials/_registry_table.html +0 -25
  476. flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
  477. flock/webapp/templates/partials/_results_display.html +0 -78
  478. flock/webapp/templates/partials/_settings_env_content.html +0 -9
  479. flock/webapp/templates/partials/_settings_theme_content.html +0 -14
  480. flock/webapp/templates/partials/_settings_view.html +0 -36
  481. flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
  482. flock/webapp/templates/partials/_share_link_snippet.html +0 -35
  483. flock/webapp/templates/partials/_sidebar.html +0 -74
  484. flock/webapp/templates/partials/_structured_data_view.html +0 -40
  485. flock/webapp/templates/partials/_theme_preview.html +0 -36
  486. flock/webapp/templates/registry_viewer.html +0 -84
  487. flock/webapp/templates/shared_run_page.html +0 -140
  488. flock/workflow/__init__.py +0 -0
  489. flock/workflow/activities.py +0 -237
  490. flock/workflow/agent_activities.py +0 -24
  491. flock/workflow/agent_execution_activity.py +0 -240
  492. flock/workflow/flock_workflow.py +0 -225
  493. flock/workflow/temporal_config.py +0 -96
  494. flock/workflow/temporal_setup.py +0 -60
  495. flock_core-0.4.542.dist-info/METADATA +0 -676
  496. flock_core-0.4.542.dist-info/RECORD +0 -572
  497. flock_core-0.4.542.dist-info/entry_points.txt +0 -2
  498. /flock/{core/logging → logging}/formatters/themes.py +0 -0
  499. /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
  500. /flock/{core/mcp → mcp}/util/__init__.py +0 -0
  501. {flock_core-0.4.542.dist-info → flock_core-0.5.0.dist-info}/WHEEL +0 -0
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"]
@@ -0,0 +1,226 @@
1
+ # src/flock/components/utility/output_utility_component.py
2
+ """Output formatting and display functionality for agents using unified component architecture."""
3
+
4
+ import re
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from pydantic import Field
8
+
9
+ from flock.components import AgentComponent, AgentComponentConfig
10
+ from flock.logging.formatters.themed_formatter import (
11
+ ThemedAgentResultFormatter,
12
+ )
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
20
+
21
+
22
+ logger = get_logger("components.utility.output")
23
+
24
+
25
+ class OutputUtilityConfig(AgentComponentConfig):
26
+ """Configuration for output formatting and display."""
27
+
28
+ theme: OutputTheme = Field(
29
+ default=OutputTheme.catppuccin_mocha, description="Theme for output formatting"
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")
33
+ truncate_long_values: bool = Field(
34
+ default=True, description="Whether to truncate long values in display"
35
+ )
36
+ show_metadata: bool = Field(
37
+ default=True, description="Whether to show metadata like timestamps"
38
+ )
39
+ format_code_blocks: bool = Field(
40
+ default=True,
41
+ description="Whether to apply syntax highlighting to code blocks",
42
+ )
43
+ custom_formatters: dict[str, str] = Field(
44
+ default_factory=dict,
45
+ description="Custom formatters for specific output types",
46
+ )
47
+ no_output: bool = Field(
48
+ default=False,
49
+ description="Whether to suppress output",
50
+ )
51
+ print_context: bool = Field(
52
+ default=False,
53
+ description="Whether to print the context",
54
+ )
55
+
56
+
57
+ class OutputUtilityComponent(AgentComponent):
58
+ """Utility component that handles output formatting and display."""
59
+
60
+ config: OutputUtilityConfig = Field(
61
+ default_factory=OutputUtilityConfig, description="Output configuration"
62
+ )
63
+
64
+ def __init__(self, name: str = "output", config: OutputUtilityConfig | None = None, **data):
65
+ if config is None:
66
+ config = OutputUtilityConfig()
67
+ super().__init__(name=name, config=config, **data)
68
+ self._formatter = ThemedAgentResultFormatter(
69
+ theme=self.config.theme,
70
+ max_length=self.config.max_length,
71
+ render_table=self.config.render_table,
72
+ )
73
+
74
+ def _format_value(self, value: Any, key: str) -> str:
75
+ """Format a single value based on its type and configuration."""
76
+ # Check for custom formatter
77
+ if key in self.config.custom_formatters:
78
+ formatter_name = self.config.custom_formatters[key]
79
+ if hasattr(self, f"_format_{formatter_name}"):
80
+ return getattr(self, f"_format_{formatter_name}")(value)
81
+
82
+ # Default formatting based on type
83
+ if isinstance(value, dict):
84
+ return self._format_dict(value)
85
+ if isinstance(value, list):
86
+ return self._format_list(value)
87
+ if isinstance(value, str) and self.config.format_code_blocks:
88
+ return self._format_potential_code(value)
89
+ return str(value)
90
+
91
+ def _format_dict(self, d: dict[str, Any], indent: int = 0) -> str:
92
+ """Format a dictionary with proper indentation."""
93
+ if not d:
94
+ return "{}"
95
+
96
+ items = []
97
+ prefix = " " * indent
98
+ for key, value in d.items():
99
+ if self.config.truncate_long_values and isinstance(value, str) and len(value) > 100:
100
+ value = value[:97] + "..."
101
+ formatted_value = self._format_value(value, key)
102
+ items.append(f"{prefix} {key}: {formatted_value}")
103
+
104
+ return "{\n" + "\n".join(items) + f"\n{prefix}}}"
105
+
106
+ def _format_list(self, lst: list[Any]) -> str:
107
+ """Format a list with proper structure."""
108
+ if not lst:
109
+ return "[]"
110
+
111
+ if len(lst) <= 3:
112
+ return str(lst)
113
+
114
+ # For longer lists, show first few items and count
115
+ preview = [str(item) for item in lst[:3]]
116
+ return f"[{', '.join(preview)}, ... ({len(lst)} total)]"
117
+
118
+ def _format_potential_code(self, text: str) -> str:
119
+ """Apply syntax highlighting to potential code blocks."""
120
+
121
+ # Simple pattern matching for code blocks
122
+ def replace_code_block(match):
123
+ language = match.group(1) or "text"
124
+ code = match.group(2)
125
+ return f"[CODE:{language}]\n{code}\n[/CODE]"
126
+
127
+ # Replace markdown-style code blocks
128
+ return re.sub(r"```(\w+)?\n(.*?)\n```", replace_code_block, text, flags=re.DOTALL)
129
+
130
+ async def on_post_evaluate(
131
+ self, agent: "Agent", ctx: Context, inputs: EvalInputs, result: EvalResult
132
+ ) -> dict[str, Any]:
133
+ """Format and display the output."""
134
+ logger.debug("Formatting and displaying output")
135
+
136
+ streaming_live_handled = False
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
+
145
+ if streaming_live_handled:
146
+ ctx.state.pop("_flock_stream_live_active", None)
147
+
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)
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)
164
+ if streaming_live_handled:
165
+ logger.debug("Skipping static table because streaming rendered live output.")
166
+ return result
167
+
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
+ )
184
+
185
+ logger.debug("Formatting and displaying output to console.")
186
+
187
+ if self.config.print_context and ctx:
188
+ # Add context snapshot if requested (be careful with large contexts)
189
+ try:
190
+ # Create a copy or select relevant parts to avoid modifying original result dict directly
191
+ display_result = result.copy()
192
+ display_result["context_snapshot"] = ctx.to_dict() # Potential performance hit
193
+ except Exception:
194
+ display_result = result.copy()
195
+ display_result["context_snapshot"] = "[Error serializing context]"
196
+ result_to_display = display_result
197
+ else:
198
+ result_to_display = result
199
+
200
+ if not hasattr(self, "_formatter") or self._formatter is None:
201
+ self._formatter = ThemedAgentResultFormatter(
202
+ theme=self.config.theme,
203
+ max_length=self.config.max_length,
204
+ render_table=self.config.render_table,
205
+ )
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
+ )
212
+
213
+ return result # Return the original, unmodified result
214
+
215
+ def update_theme(self, new_theme: OutputTheme) -> None:
216
+ """Update the output theme."""
217
+ self.config.theme = new_theme
218
+ self._formatter = ThemedAgentResultFormatter(
219
+ theme=self.config.theme,
220
+ max_length=self.config.max_length,
221
+ render_table=self.config.render_table,
222
+ )
223
+
224
+ def add_custom_formatter(self, key: str, formatter_name: str) -> None:
225
+ """Add a custom formatter for a specific output key."""
226
+ self.config.custom_formatters[key] = formatter_name
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
+ ]