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

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

Potentially problematic release.


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

Files changed (501) hide show
  1. flock/__init__.py +12 -217
  2. flock/agent.py +1079 -0
  3. flock/api/themes.py +71 -0
  4. flock/artifacts.py +86 -0
  5. flock/cli.py +147 -0
  6. flock/components.py +189 -0
  7. flock/dashboard/__init__.py +30 -0
  8. flock/dashboard/collector.py +559 -0
  9. flock/dashboard/events.py +188 -0
  10. flock/dashboard/graph_builder.py +563 -0
  11. flock/dashboard/launcher.py +235 -0
  12. flock/dashboard/models/graph.py +156 -0
  13. flock/dashboard/service.py +991 -0
  14. flock/dashboard/static_v2/assets/index-DFRnI_mt.js +111 -0
  15. flock/dashboard/static_v2/assets/index-fPLNdmp1.css +1 -0
  16. flock/dashboard/static_v2/index.html +13 -0
  17. flock/dashboard/websocket.py +246 -0
  18. flock/engines/__init__.py +6 -0
  19. flock/engines/dspy_engine.py +932 -0
  20. flock/examples.py +131 -0
  21. flock/frontend/README.md +778 -0
  22. flock/frontend/docs/DESIGN_SYSTEM.md +1980 -0
  23. flock/frontend/index.html +12 -0
  24. flock/frontend/package-lock.json +4337 -0
  25. flock/frontend/package.json +48 -0
  26. flock/frontend/src/App.tsx +139 -0
  27. flock/frontend/src/__tests__/integration/graph-snapshot.test.tsx +647 -0
  28. flock/frontend/src/__tests__/integration/indexeddb-persistence.test.tsx +699 -0
  29. flock/frontend/src/components/common/BuildInfo.tsx +39 -0
  30. flock/frontend/src/components/common/EmptyState.module.css +115 -0
  31. flock/frontend/src/components/common/EmptyState.tsx +128 -0
  32. flock/frontend/src/components/common/ErrorBoundary.module.css +169 -0
  33. flock/frontend/src/components/common/ErrorBoundary.tsx +118 -0
  34. flock/frontend/src/components/common/KeyboardShortcutsDialog.css +251 -0
  35. flock/frontend/src/components/common/KeyboardShortcutsDialog.tsx +151 -0
  36. flock/frontend/src/components/common/LoadingSpinner.module.css +97 -0
  37. flock/frontend/src/components/common/LoadingSpinner.tsx +29 -0
  38. flock/frontend/src/components/controls/PublishControl.css +547 -0
  39. flock/frontend/src/components/controls/PublishControl.test.tsx +543 -0
  40. flock/frontend/src/components/controls/PublishControl.tsx +432 -0
  41. flock/frontend/src/components/details/DetailWindowContainer.tsx +58 -0
  42. flock/frontend/src/components/details/LiveOutputTab.test.tsx +792 -0
  43. flock/frontend/src/components/details/LiveOutputTab.tsx +220 -0
  44. flock/frontend/src/components/details/MessageDetailWindow.tsx +439 -0
  45. flock/frontend/src/components/details/MessageHistoryTab.tsx +374 -0
  46. flock/frontend/src/components/details/NodeDetailWindow.test.tsx +501 -0
  47. flock/frontend/src/components/details/NodeDetailWindow.tsx +218 -0
  48. flock/frontend/src/components/details/RunStatusTab.tsx +348 -0
  49. flock/frontend/src/components/details/tabs.test.tsx +1015 -0
  50. flock/frontend/src/components/filters/ArtifactTypeFilter.tsx +21 -0
  51. flock/frontend/src/components/filters/CorrelationIDFilter.module.css +102 -0
  52. flock/frontend/src/components/filters/CorrelationIDFilter.test.tsx +197 -0
  53. flock/frontend/src/components/filters/CorrelationIDFilter.tsx +121 -0
  54. flock/frontend/src/components/filters/FilterFlyout.module.css +104 -0
  55. flock/frontend/src/components/filters/FilterFlyout.tsx +80 -0
  56. flock/frontend/src/components/filters/FilterPills.module.css +220 -0
  57. flock/frontend/src/components/filters/FilterPills.test.tsx +189 -0
  58. flock/frontend/src/components/filters/FilterPills.tsx +143 -0
  59. flock/frontend/src/components/filters/ProducerFilter.tsx +21 -0
  60. flock/frontend/src/components/filters/SavedFiltersControl.module.css +60 -0
  61. flock/frontend/src/components/filters/SavedFiltersControl.test.tsx +158 -0
  62. flock/frontend/src/components/filters/SavedFiltersControl.tsx +159 -0
  63. flock/frontend/src/components/filters/TagFilter.tsx +21 -0
  64. flock/frontend/src/components/filters/TimeRangeFilter.module.css +115 -0
  65. flock/frontend/src/components/filters/TimeRangeFilter.test.tsx +154 -0
  66. flock/frontend/src/components/filters/TimeRangeFilter.tsx +110 -0
  67. flock/frontend/src/components/filters/VisibilityFilter.tsx +21 -0
  68. flock/frontend/src/components/graph/AgentNode.test.tsx +77 -0
  69. flock/frontend/src/components/graph/AgentNode.tsx +324 -0
  70. flock/frontend/src/components/graph/GraphCanvas.tsx +613 -0
  71. flock/frontend/src/components/graph/MessageFlowEdge.tsx +128 -0
  72. flock/frontend/src/components/graph/MessageNode.test.tsx +64 -0
  73. flock/frontend/src/components/graph/MessageNode.tsx +129 -0
  74. flock/frontend/src/components/graph/MiniMap.tsx +47 -0
  75. flock/frontend/src/components/graph/TransformEdge.tsx +123 -0
  76. flock/frontend/src/components/layout/DashboardLayout.css +420 -0
  77. flock/frontend/src/components/layout/DashboardLayout.tsx +287 -0
  78. flock/frontend/src/components/layout/Header.module.css +88 -0
  79. flock/frontend/src/components/layout/Header.tsx +52 -0
  80. flock/frontend/src/components/modules/HistoricalArtifactsModule.module.css +288 -0
  81. flock/frontend/src/components/modules/HistoricalArtifactsModule.tsx +450 -0
  82. flock/frontend/src/components/modules/HistoricalArtifactsModuleWrapper.tsx +13 -0
  83. flock/frontend/src/components/modules/JsonAttributeRenderer.tsx +140 -0
  84. flock/frontend/src/components/modules/ModuleRegistry.test.ts +333 -0
  85. flock/frontend/src/components/modules/ModuleRegistry.ts +93 -0
  86. flock/frontend/src/components/modules/ModuleWindow.tsx +223 -0
  87. flock/frontend/src/components/modules/TraceModuleJaeger.tsx +1971 -0
  88. flock/frontend/src/components/modules/TraceModuleJaegerWrapper.tsx +13 -0
  89. flock/frontend/src/components/modules/registerModules.ts +29 -0
  90. flock/frontend/src/components/settings/AdvancedSettings.tsx +175 -0
  91. flock/frontend/src/components/settings/AppearanceSettings.tsx +185 -0
  92. flock/frontend/src/components/settings/GraphSettings.tsx +110 -0
  93. flock/frontend/src/components/settings/MultiSelect.tsx +235 -0
  94. flock/frontend/src/components/settings/SettingsPanel.css +327 -0
  95. flock/frontend/src/components/settings/SettingsPanel.tsx +131 -0
  96. flock/frontend/src/components/settings/ThemeSelector.tsx +298 -0
  97. flock/frontend/src/components/settings/TracingSettings.tsx +404 -0
  98. flock/frontend/src/hooks/useKeyboardShortcuts.ts +148 -0
  99. flock/frontend/src/hooks/useModulePersistence.test.ts +442 -0
  100. flock/frontend/src/hooks/useModulePersistence.ts +154 -0
  101. flock/frontend/src/hooks/useModules.ts +157 -0
  102. flock/frontend/src/hooks/usePersistence.ts +141 -0
  103. flock/frontend/src/main.tsx +13 -0
  104. flock/frontend/src/services/api.ts +337 -0
  105. flock/frontend/src/services/graphService.test.ts +330 -0
  106. flock/frontend/src/services/graphService.ts +75 -0
  107. flock/frontend/src/services/indexeddb.test.ts +793 -0
  108. flock/frontend/src/services/indexeddb.ts +848 -0
  109. flock/frontend/src/services/layout.test.ts +437 -0
  110. flock/frontend/src/services/layout.ts +357 -0
  111. flock/frontend/src/services/themeApplicator.ts +140 -0
  112. flock/frontend/src/services/themeService.ts +77 -0
  113. flock/frontend/src/services/websocket.ts +650 -0
  114. flock/frontend/src/store/filterStore.test.ts +250 -0
  115. flock/frontend/src/store/filterStore.ts +272 -0
  116. flock/frontend/src/store/graphStore.test.ts +570 -0
  117. flock/frontend/src/store/graphStore.ts +462 -0
  118. flock/frontend/src/store/moduleStore.test.ts +253 -0
  119. flock/frontend/src/store/moduleStore.ts +75 -0
  120. flock/frontend/src/store/settingsStore.ts +188 -0
  121. flock/frontend/src/store/streamStore.ts +68 -0
  122. flock/frontend/src/store/uiStore.test.ts +54 -0
  123. flock/frontend/src/store/uiStore.ts +122 -0
  124. flock/frontend/src/store/wsStore.ts +34 -0
  125. flock/frontend/src/styles/index.css +15 -0
  126. flock/frontend/src/styles/scrollbar.css +47 -0
  127. flock/frontend/src/styles/variables.css +488 -0
  128. flock/frontend/src/test/setup.ts +1 -0
  129. flock/frontend/src/types/filters.ts +47 -0
  130. flock/frontend/src/types/graph.ts +95 -0
  131. flock/frontend/src/types/modules.ts +10 -0
  132. flock/frontend/src/types/theme.ts +55 -0
  133. flock/frontend/src/utils/artifacts.ts +24 -0
  134. flock/frontend/src/utils/mockData.ts +98 -0
  135. flock/frontend/src/utils/performance.ts +16 -0
  136. flock/frontend/src/vite-env.d.ts +17 -0
  137. flock/frontend/tsconfig.json +27 -0
  138. flock/frontend/tsconfig.node.json +11 -0
  139. flock/frontend/vite.config.ts +25 -0
  140. flock/frontend/vitest.config.ts +11 -0
  141. flock/{core/util → helper}/cli_helper.py +9 -5
  142. flock/{core/logging → logging}/__init__.py +2 -3
  143. flock/logging/auto_trace.py +159 -0
  144. flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
  145. flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
  146. flock/{core/logging → logging}/formatters/themed_formatter.py +69 -107
  147. flock/{core/logging → logging}/logging.py +78 -61
  148. flock/{core/logging → logging}/telemetry.py +66 -26
  149. flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
  150. flock/logging/telemetry_exporter/duckdb_exporter.py +216 -0
  151. flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +13 -10
  152. flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
  153. flock/logging/trace_and_logged.py +304 -0
  154. flock/mcp/__init__.py +91 -0
  155. flock/{core/mcp/mcp_client.py → mcp/client.py} +131 -158
  156. flock/{core/mcp/mcp_config.py → mcp/config.py} +86 -132
  157. flock/mcp/manager.py +286 -0
  158. flock/mcp/servers/sse/__init__.py +1 -1
  159. flock/mcp/servers/sse/flock_sse_server.py +16 -58
  160. flock/mcp/servers/stdio/__init__.py +1 -1
  161. flock/mcp/servers/stdio/flock_stdio_server.py +13 -53
  162. flock/mcp/servers/streamable_http/flock_streamable_http_server.py +22 -67
  163. flock/mcp/servers/websockets/flock_websocket_server.py +12 -45
  164. flock/{core/mcp/flock_mcp_tool_base.py → mcp/tool.py} +24 -78
  165. flock/mcp/types/__init__.py +42 -0
  166. flock/{core/mcp → mcp}/types/callbacks.py +12 -15
  167. flock/{core/mcp → mcp}/types/factories.py +7 -6
  168. flock/{core/mcp → mcp}/types/handlers.py +13 -18
  169. flock/{core/mcp → mcp}/types/types.py +70 -74
  170. flock/{core/mcp → mcp}/util/helpers.py +3 -3
  171. flock/orchestrator.py +970 -0
  172. flock/registry.py +148 -0
  173. flock/runtime.py +262 -0
  174. flock/service.py +277 -0
  175. flock/store.py +1214 -0
  176. flock/subscription.py +111 -0
  177. flock/themes/andromeda.toml +1 -1
  178. flock/themes/apple-system-colors.toml +1 -1
  179. flock/themes/arcoiris.toml +1 -1
  180. flock/themes/atomonelight.toml +1 -1
  181. flock/themes/ayu copy.toml +1 -1
  182. flock/themes/ayu-light.toml +1 -1
  183. flock/themes/belafonte-day.toml +1 -1
  184. flock/themes/belafonte-night.toml +1 -1
  185. flock/themes/blulocodark.toml +1 -1
  186. flock/themes/breeze.toml +1 -1
  187. flock/themes/broadcast.toml +1 -1
  188. flock/themes/brogrammer.toml +1 -1
  189. flock/themes/builtin-dark.toml +1 -1
  190. flock/themes/builtin-pastel-dark.toml +1 -1
  191. flock/themes/catppuccin-latte.toml +1 -1
  192. flock/themes/catppuccin-macchiato.toml +1 -1
  193. flock/themes/catppuccin-mocha.toml +1 -1
  194. flock/themes/cga.toml +1 -1
  195. flock/themes/chalk.toml +1 -1
  196. flock/themes/ciapre.toml +1 -1
  197. flock/themes/coffee-theme.toml +1 -1
  198. flock/themes/cyberpunkscarletprotocol.toml +1 -1
  199. flock/themes/dark+.toml +1 -1
  200. flock/themes/darkermatrix.toml +1 -1
  201. flock/themes/darkmatrix.toml +2 -2
  202. flock/themes/darkside.toml +1 -1
  203. flock/themes/deep.toml +2 -2
  204. flock/themes/desert.toml +1 -1
  205. flock/themes/django.toml +1 -1
  206. flock/themes/djangosmooth.toml +1 -1
  207. flock/themes/doomone.toml +1 -1
  208. flock/themes/dotgov.toml +1 -1
  209. flock/themes/dracula+.toml +1 -1
  210. flock/themes/duckbones.toml +1 -1
  211. flock/themes/encom.toml +1 -1
  212. flock/themes/espresso.toml +1 -1
  213. flock/themes/everblush.toml +1 -1
  214. flock/themes/fairyfloss.toml +1 -1
  215. flock/themes/fideloper.toml +1 -1
  216. flock/themes/fishtank.toml +1 -1
  217. flock/themes/flexoki-light.toml +1 -1
  218. flock/themes/floraverse.toml +1 -1
  219. flock/themes/framer.toml +1 -1
  220. flock/themes/galizur.toml +1 -1
  221. flock/themes/github.toml +1 -1
  222. flock/themes/grass.toml +1 -1
  223. flock/themes/grey-green.toml +1 -1
  224. flock/themes/gruvboxlight.toml +1 -1
  225. flock/themes/guezwhoz.toml +1 -1
  226. flock/themes/harper.toml +1 -1
  227. flock/themes/hax0r-blue.toml +1 -1
  228. flock/themes/hopscotch.256.toml +1 -1
  229. flock/themes/ic-green-ppl.toml +1 -1
  230. flock/themes/iceberg-dark.toml +1 -1
  231. flock/themes/japanesque.toml +1 -1
  232. flock/themes/jubi.toml +1 -1
  233. flock/themes/kibble.toml +1 -1
  234. flock/themes/kolorit.toml +1 -1
  235. flock/themes/kurokula.toml +1 -1
  236. flock/themes/materialdesigncolors.toml +1 -1
  237. flock/themes/matrix.toml +1 -1
  238. flock/themes/mellifluous.toml +1 -1
  239. flock/themes/midnight-in-mojave.toml +1 -1
  240. flock/themes/monokai-remastered.toml +1 -1
  241. flock/themes/monokai-soda.toml +1 -1
  242. flock/themes/neon.toml +1 -1
  243. flock/themes/neopolitan.toml +5 -5
  244. flock/themes/nord-light.toml +1 -1
  245. flock/themes/ocean.toml +1 -1
  246. flock/themes/onehalfdark.toml +1 -1
  247. flock/themes/onehalflight.toml +1 -1
  248. flock/themes/palenighthc.toml +1 -1
  249. flock/themes/paulmillr.toml +1 -1
  250. flock/themes/pencildark.toml +1 -1
  251. flock/themes/pnevma.toml +1 -1
  252. flock/themes/purple-rain.toml +1 -1
  253. flock/themes/purplepeter.toml +1 -1
  254. flock/themes/raycast-dark.toml +1 -1
  255. flock/themes/red-sands.toml +1 -1
  256. flock/themes/relaxed.toml +1 -1
  257. flock/themes/retro.toml +1 -1
  258. flock/themes/rose-pine.toml +1 -1
  259. flock/themes/royal.toml +1 -1
  260. flock/themes/ryuuko.toml +1 -1
  261. flock/themes/sakura.toml +1 -1
  262. flock/themes/scarlet-protocol.toml +1 -1
  263. flock/themes/seoulbones-dark.toml +1 -1
  264. flock/themes/shades-of-purple.toml +1 -1
  265. flock/themes/smyck.toml +1 -1
  266. flock/themes/softserver.toml +1 -1
  267. flock/themes/solarized-darcula.toml +1 -1
  268. flock/themes/square.toml +1 -1
  269. flock/themes/sugarplum.toml +1 -1
  270. flock/themes/thayer-bright.toml +1 -1
  271. flock/themes/tokyonight.toml +1 -1
  272. flock/themes/tomorrow.toml +1 -1
  273. flock/themes/ubuntu.toml +1 -1
  274. flock/themes/ultradark.toml +1 -1
  275. flock/themes/ultraviolent.toml +1 -1
  276. flock/themes/unikitty.toml +1 -1
  277. flock/themes/urple.toml +1 -1
  278. flock/themes/vesper.toml +1 -1
  279. flock/themes/vimbones.toml +1 -1
  280. flock/themes/wildcherry.toml +1 -1
  281. flock/themes/wilmersdorf.toml +1 -1
  282. flock/themes/wryan.toml +1 -1
  283. flock/themes/xcodedarkhc.toml +1 -1
  284. flock/themes/xcodelight.toml +1 -1
  285. flock/themes/zenbones-light.toml +1 -1
  286. flock/themes/zenwritten-dark.toml +1 -1
  287. flock/utilities.py +301 -0
  288. flock/utility/output_utility_component.py +226 -0
  289. flock/visibility.py +107 -0
  290. flock_core-0.5.0.dist-info/METADATA +964 -0
  291. flock_core-0.5.0.dist-info/RECORD +525 -0
  292. flock_core-0.5.0.dist-info/entry_points.txt +2 -0
  293. {flock_core-0.4.542.dist-info → flock_core-0.5.0.dist-info}/licenses/LICENSE +1 -1
  294. flock/adapter/__init__.py +0 -14
  295. flock/adapter/azure_adapter.py +0 -68
  296. flock/adapter/chroma_adapter.py +0 -73
  297. flock/adapter/faiss_adapter.py +0 -97
  298. flock/adapter/pinecone_adapter.py +0 -51
  299. flock/adapter/vector_base.py +0 -47
  300. flock/cli/assets/release_notes.md +0 -140
  301. flock/cli/config.py +0 -8
  302. flock/cli/constants.py +0 -36
  303. flock/cli/create_agent.py +0 -1
  304. flock/cli/create_flock.py +0 -280
  305. flock/cli/execute_flock.py +0 -620
  306. flock/cli/load_agent.py +0 -1
  307. flock/cli/load_examples.py +0 -1
  308. flock/cli/load_flock.py +0 -192
  309. flock/cli/load_release_notes.py +0 -20
  310. flock/cli/loaded_flock_cli.py +0 -254
  311. flock/cli/manage_agents.py +0 -459
  312. flock/cli/registry_management.py +0 -889
  313. flock/cli/runner.py +0 -41
  314. flock/cli/settings.py +0 -857
  315. flock/cli/utils.py +0 -135
  316. flock/cli/view_results.py +0 -29
  317. flock/cli/yaml_editor.py +0 -396
  318. flock/config.py +0 -56
  319. flock/core/__init__.py +0 -44
  320. flock/core/api/__init__.py +0 -10
  321. flock/core/api/custom_endpoint.py +0 -45
  322. flock/core/api/endpoints.py +0 -262
  323. flock/core/api/main.py +0 -162
  324. flock/core/api/models.py +0 -101
  325. flock/core/api/run_store.py +0 -224
  326. flock/core/api/runner.py +0 -44
  327. flock/core/api/service.py +0 -214
  328. flock/core/config/flock_agent_config.py +0 -11
  329. flock/core/config/scheduled_agent_config.py +0 -40
  330. flock/core/context/context.py +0 -214
  331. flock/core/context/context_manager.py +0 -40
  332. flock/core/context/context_vars.py +0 -11
  333. flock/core/evaluation/utils.py +0 -395
  334. flock/core/execution/batch_executor.py +0 -369
  335. flock/core/execution/evaluation_executor.py +0 -438
  336. flock/core/execution/local_executor.py +0 -31
  337. flock/core/execution/opik_executor.py +0 -103
  338. flock/core/execution/temporal_executor.py +0 -166
  339. flock/core/flock.py +0 -1003
  340. flock/core/flock_agent.py +0 -1258
  341. flock/core/flock_evaluator.py +0 -60
  342. flock/core/flock_factory.py +0 -513
  343. flock/core/flock_module.py +0 -207
  344. flock/core/flock_registry.py +0 -702
  345. flock/core/flock_router.py +0 -83
  346. flock/core/flock_scheduler.py +0 -166
  347. flock/core/flock_server_manager.py +0 -136
  348. flock/core/interpreter/python_interpreter.py +0 -689
  349. flock/core/logging/live_capture.py +0 -137
  350. flock/core/logging/trace_and_logged.py +0 -59
  351. flock/core/mcp/__init__.py +0 -1
  352. flock/core/mcp/flock_mcp_server.py +0 -640
  353. flock/core/mcp/mcp_client_manager.py +0 -201
  354. flock/core/mcp/types/__init__.py +0 -1
  355. flock/core/mixin/dspy_integration.py +0 -445
  356. flock/core/mixin/prompt_parser.py +0 -125
  357. flock/core/serialization/__init__.py +0 -13
  358. flock/core/serialization/callable_registry.py +0 -52
  359. flock/core/serialization/flock_serializer.py +0 -854
  360. flock/core/serialization/json_encoder.py +0 -41
  361. flock/core/serialization/secure_serializer.py +0 -175
  362. flock/core/serialization/serializable.py +0 -342
  363. flock/core/serialization/serialization_utils.py +0 -409
  364. flock/core/util/file_path_utils.py +0 -223
  365. flock/core/util/hydrator.py +0 -309
  366. flock/core/util/input_resolver.py +0 -141
  367. flock/core/util/loader.py +0 -59
  368. flock/core/util/splitter.py +0 -219
  369. flock/di.py +0 -41
  370. flock/evaluators/__init__.py +0 -1
  371. flock/evaluators/declarative/__init__.py +0 -1
  372. flock/evaluators/declarative/declarative_evaluator.py +0 -217
  373. flock/evaluators/memory/memory_evaluator.py +0 -90
  374. flock/evaluators/test/test_case_evaluator.py +0 -38
  375. flock/evaluators/zep/zep_evaluator.py +0 -59
  376. flock/modules/__init__.py +0 -1
  377. flock/modules/assertion/__init__.py +0 -1
  378. flock/modules/assertion/assertion_module.py +0 -286
  379. flock/modules/callback/__init__.py +0 -1
  380. flock/modules/callback/callback_module.py +0 -91
  381. flock/modules/enterprise_memory/README.md +0 -99
  382. flock/modules/enterprise_memory/enterprise_memory_module.py +0 -526
  383. flock/modules/mem0/__init__.py +0 -1
  384. flock/modules/mem0/mem0_module.py +0 -126
  385. flock/modules/mem0_async/__init__.py +0 -1
  386. flock/modules/mem0_async/async_mem0_module.py +0 -126
  387. flock/modules/memory/__init__.py +0 -1
  388. flock/modules/memory/memory_module.py +0 -429
  389. flock/modules/memory/memory_parser.py +0 -125
  390. flock/modules/memory/memory_storage.py +0 -736
  391. flock/modules/output/__init__.py +0 -1
  392. flock/modules/output/output_module.py +0 -196
  393. flock/modules/performance/__init__.py +0 -1
  394. flock/modules/performance/metrics_module.py +0 -678
  395. flock/modules/zep/__init__.py +0 -1
  396. flock/modules/zep/zep_module.py +0 -192
  397. flock/platform/docker_tools.py +0 -49
  398. flock/platform/jaeger_install.py +0 -86
  399. flock/routers/__init__.py +0 -1
  400. flock/routers/agent/__init__.py +0 -1
  401. flock/routers/agent/agent_router.py +0 -236
  402. flock/routers/agent/handoff_agent.py +0 -58
  403. flock/routers/conditional/conditional_router.py +0 -486
  404. flock/routers/default/__init__.py +0 -1
  405. flock/routers/default/default_router.py +0 -80
  406. flock/routers/feedback/feedback_router.py +0 -114
  407. flock/routers/list_generator/list_generator_router.py +0 -166
  408. flock/routers/llm/__init__.py +0 -1
  409. flock/routers/llm/llm_router.py +0 -365
  410. flock/tools/__init__.py +0 -0
  411. flock/tools/azure_tools.py +0 -781
  412. flock/tools/code_tools.py +0 -167
  413. flock/tools/file_tools.py +0 -149
  414. flock/tools/github_tools.py +0 -157
  415. flock/tools/markdown_tools.py +0 -205
  416. flock/tools/system_tools.py +0 -9
  417. flock/tools/text_tools.py +0 -810
  418. flock/tools/web_tools.py +0 -92
  419. flock/tools/zendesk_tools.py +0 -501
  420. flock/webapp/__init__.py +0 -1
  421. flock/webapp/app/__init__.py +0 -0
  422. flock/webapp/app/api/__init__.py +0 -0
  423. flock/webapp/app/api/agent_management.py +0 -237
  424. flock/webapp/app/api/execution.py +0 -503
  425. flock/webapp/app/api/flock_management.py +0 -125
  426. flock/webapp/app/api/registry_viewer.py +0 -29
  427. flock/webapp/app/chat.py +0 -662
  428. flock/webapp/app/config.py +0 -104
  429. flock/webapp/app/dependencies.py +0 -117
  430. flock/webapp/app/main.py +0 -1086
  431. flock/webapp/app/middleware.py +0 -113
  432. flock/webapp/app/models_ui.py +0 -7
  433. flock/webapp/app/services/__init__.py +0 -0
  434. flock/webapp/app/services/feedback_file_service.py +0 -363
  435. flock/webapp/app/services/flock_service.py +0 -345
  436. flock/webapp/app/services/sharing_models.py +0 -81
  437. flock/webapp/app/services/sharing_store.py +0 -597
  438. flock/webapp/app/templates/theme_mapper.html +0 -326
  439. flock/webapp/app/theme_mapper.py +0 -811
  440. flock/webapp/app/utils.py +0 -85
  441. flock/webapp/run.py +0 -219
  442. flock/webapp/static/css/chat.css +0 -301
  443. flock/webapp/static/css/components.css +0 -167
  444. flock/webapp/static/css/header.css +0 -39
  445. flock/webapp/static/css/layout.css +0 -281
  446. flock/webapp/static/css/sidebar.css +0 -127
  447. flock/webapp/static/css/two-pane.css +0 -48
  448. flock/webapp/templates/base.html +0 -389
  449. flock/webapp/templates/chat.html +0 -152
  450. flock/webapp/templates/chat_settings.html +0 -19
  451. flock/webapp/templates/flock_editor.html +0 -16
  452. flock/webapp/templates/index.html +0 -12
  453. flock/webapp/templates/partials/_agent_detail_form.html +0 -93
  454. flock/webapp/templates/partials/_agent_list.html +0 -18
  455. flock/webapp/templates/partials/_agent_manager_view.html +0 -51
  456. flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
  457. flock/webapp/templates/partials/_chat_container.html +0 -15
  458. flock/webapp/templates/partials/_chat_messages.html +0 -57
  459. flock/webapp/templates/partials/_chat_settings_form.html +0 -85
  460. flock/webapp/templates/partials/_create_flock_form.html +0 -50
  461. flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
  462. flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
  463. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
  464. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
  465. flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
  466. flock/webapp/templates/partials/_env_vars_table.html +0 -23
  467. flock/webapp/templates/partials/_execution_form.html +0 -127
  468. flock/webapp/templates/partials/_execution_view_container.html +0 -28
  469. flock/webapp/templates/partials/_flock_file_list.html +0 -23
  470. flock/webapp/templates/partials/_flock_properties_form.html +0 -52
  471. flock/webapp/templates/partials/_flock_upload_form.html +0 -16
  472. flock/webapp/templates/partials/_header_flock_status.html +0 -5
  473. flock/webapp/templates/partials/_live_logs.html +0 -13
  474. flock/webapp/templates/partials/_load_manager_view.html +0 -49
  475. flock/webapp/templates/partials/_registry_table.html +0 -25
  476. flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
  477. flock/webapp/templates/partials/_results_display.html +0 -78
  478. flock/webapp/templates/partials/_settings_env_content.html +0 -9
  479. flock/webapp/templates/partials/_settings_theme_content.html +0 -14
  480. flock/webapp/templates/partials/_settings_view.html +0 -36
  481. flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
  482. flock/webapp/templates/partials/_share_link_snippet.html +0 -35
  483. flock/webapp/templates/partials/_sidebar.html +0 -74
  484. flock/webapp/templates/partials/_structured_data_view.html +0 -40
  485. flock/webapp/templates/partials/_theme_preview.html +0 -36
  486. flock/webapp/templates/registry_viewer.html +0 -84
  487. flock/webapp/templates/shared_run_page.html +0 -140
  488. flock/workflow/__init__.py +0 -0
  489. flock/workflow/activities.py +0 -237
  490. flock/workflow/agent_activities.py +0 -24
  491. flock/workflow/agent_execution_activity.py +0 -240
  492. flock/workflow/flock_workflow.py +0 -225
  493. flock/workflow/temporal_config.py +0 -96
  494. flock/workflow/temporal_setup.py +0 -60
  495. flock_core-0.4.542.dist-info/METADATA +0 -676
  496. flock_core-0.4.542.dist-info/RECORD +0 -572
  497. flock_core-0.4.542.dist-info/entry_points.txt +0 -2
  498. /flock/{core/logging → logging}/formatters/themes.py +0 -0
  499. /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
  500. /flock/{core/mcp → mcp}/util/__init__.py +0 -0
  501. {flock_core-0.4.542.dist-info → flock_core-0.5.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,932 @@
1
+ """DSPy-powered engine component that mirrors the design implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ import os
8
+ from collections import OrderedDict, defaultdict
9
+ from collections.abc import Iterable, Mapping, Sequence
10
+ from contextlib import nullcontext
11
+ from typing import Any, Literal
12
+
13
+ from pydantic import BaseModel, Field
14
+
15
+ from flock.artifacts import Artifact
16
+ from flock.components import EngineComponent
17
+ from flock.dashboard.events import StreamingOutputEvent
18
+ from flock.logging.logging import get_logger
19
+ from flock.registry import type_registry
20
+ from flock.runtime import EvalInputs, EvalResult
21
+
22
+
23
+ logger = get_logger(__name__)
24
+
25
+
26
+ _live_patch_applied = False
27
+
28
+
29
+ # T071: Auto-detect test environment for streaming
30
+ def _default_stream_value() -> bool:
31
+ """Return default stream value based on environment.
32
+
33
+ Returns False in pytest (clean test output), True otherwise (rich streaming).
34
+ """
35
+ import sys
36
+
37
+ return "pytest" not in sys.modules
38
+
39
+
40
+ # Apply the Rich Live patch immediately on module import
41
+ def _apply_live_patch_on_import() -> None:
42
+ """Apply Rich Live crop_above patch when module is imported."""
43
+ try:
44
+ _ensure_live_crop_above()
45
+ except Exception:
46
+ pass # Silently ignore if Rich is not available
47
+
48
+
49
+ def _ensure_live_crop_above() -> None:
50
+ """Monkeypatch rich.live_render to support 'crop_above' overflow."""
51
+ global _live_patch_applied
52
+ if _live_patch_applied:
53
+ return
54
+ try:
55
+ from typing import Literal as _Literal
56
+
57
+ from rich import live_render as _lr
58
+ except Exception:
59
+ return
60
+
61
+ # Extend the accepted literal at runtime so type checks don't block the new option.
62
+ current_args = getattr(_lr.VerticalOverflowMethod, "__args__", ())
63
+ if "crop_above" not in current_args:
64
+ _lr.VerticalOverflowMethod = _Literal["crop", "crop_above", "ellipsis", "visible"] # type: ignore[assignment]
65
+
66
+ if getattr(_lr.LiveRender.__rich_console__, "_flock_crop_above", False):
67
+ _live_patch_applied = True
68
+ return
69
+
70
+ Segment = _lr.Segment
71
+ Text = _lr.Text
72
+ loop_last = _lr.loop_last
73
+
74
+ def _patched_rich_console(self, console, options):
75
+ renderable = self.renderable
76
+ style = console.get_style(self.style)
77
+ lines = console.render_lines(renderable, options, style=style, pad=False)
78
+ shape = Segment.get_shape(lines)
79
+
80
+ _, height = shape
81
+ max_height = options.size.height
82
+ if height > max_height:
83
+ if self.vertical_overflow == "crop":
84
+ lines = lines[:max_height]
85
+ shape = Segment.get_shape(lines)
86
+ elif self.vertical_overflow == "crop_above":
87
+ lines = lines[-max_height:]
88
+ shape = Segment.get_shape(lines)
89
+ elif self.vertical_overflow == "ellipsis" and max_height > 0:
90
+ lines = lines[: (max_height - 1)]
91
+ overflow_text = Text(
92
+ "...",
93
+ overflow="crop",
94
+ justify="center",
95
+ end="",
96
+ style="live.ellipsis",
97
+ )
98
+ lines.append(list(console.render(overflow_text)))
99
+ shape = Segment.get_shape(lines)
100
+ self._shape = shape
101
+
102
+ new_line = Segment.line()
103
+ for last, line in loop_last(lines):
104
+ yield from line
105
+ if not last:
106
+ yield new_line
107
+
108
+ _patched_rich_console._flock_crop_above = True # type: ignore[attr-defined]
109
+ _lr.LiveRender.__rich_console__ = _patched_rich_console
110
+ _live_patch_applied = True
111
+
112
+
113
+ class DSPyEngine(EngineComponent):
114
+ """Execute a minimal DSPy program backed by a hosted LLM.
115
+
116
+ Behavior intentionally mirrors ``design/dspy_engine.py`` so that orchestration
117
+ relies on the same model resolution, signature preparation, and result
118
+ normalization logic.
119
+ """
120
+
121
+ name: str | None = "dspy"
122
+ model: str | None = None
123
+ instructions: str | None = None
124
+ temperature: float = 1.0
125
+ max_tokens: int = 32000
126
+ max_tool_calls: int = 10
127
+ max_retries: int = 0
128
+ stream: bool = Field(
129
+ default_factory=lambda: _default_stream_value(),
130
+ description="Enable streaming output from the underlying DSPy program. Auto-disables in pytest.",
131
+ )
132
+ no_output: bool = Field(
133
+ default=False,
134
+ description="Disable output from the underlying DSPy program.",
135
+ )
136
+ stream_vertical_overflow: Literal["crop", "ellipsis", "crop_above", "visible"] = Field(
137
+ default="crop_above",
138
+ description=(
139
+ "Rich Live vertical overflow strategy; select how tall output is handled; 'crop_above' keeps the most recent rows visible."
140
+ ),
141
+ )
142
+ status_output_field: str = Field(
143
+ default="_status_output",
144
+ description="The field name for the status output.",
145
+ )
146
+ theme: str = Field(
147
+ default="afterglow",
148
+ description="Theme name for Rich output formatting.",
149
+ )
150
+ enable_cache: bool = Field(
151
+ default=False,
152
+ description="Enable caching of DSPy program results",
153
+ )
154
+
155
+ async def evaluate(self, agent, ctx, inputs: EvalInputs) -> EvalResult: # type: ignore[override]
156
+ if not inputs.artifacts:
157
+ return EvalResult(artifacts=[], state=dict(inputs.state))
158
+
159
+ model_name = self._resolve_model_name()
160
+ dspy_mod = self._import_dspy()
161
+
162
+ lm = dspy_mod.LM(
163
+ model=model_name,
164
+ temperature=self.temperature,
165
+ max_tokens=self.max_tokens,
166
+ cache=self.enable_cache,
167
+ num_retries=self.max_retries,
168
+ )
169
+
170
+ primary_artifact = self._select_primary_artifact(inputs.artifacts)
171
+ input_model = self._resolve_input_model(primary_artifact)
172
+ validated_input = self._validate_input_payload(input_model, primary_artifact.payload)
173
+ output_model = self._resolve_output_model(agent)
174
+
175
+ # Fetch conversation context from blackboard
176
+ context_history = await self.fetch_conversation_context(ctx)
177
+ has_context = bool(context_history) and self.should_use_context(inputs)
178
+
179
+ # Prepare signature with optional context field
180
+ signature = self._prepare_signature_with_context(
181
+ dspy_mod,
182
+ description=self.instructions or agent.description,
183
+ input_schema=input_model,
184
+ output_schema=output_model,
185
+ has_context=has_context,
186
+ )
187
+
188
+ sys_desc = self._system_description(self.instructions or agent.description)
189
+
190
+ # Pre-generate the artifact ID so it's available from the start
191
+ from uuid import uuid4
192
+
193
+ pre_generated_artifact_id = uuid4()
194
+
195
+ # Build execution payload with context
196
+ if has_context:
197
+ execution_payload = {
198
+ "input": validated_input,
199
+ "context": context_history,
200
+ }
201
+ else:
202
+ # Backwards compatible - direct input
203
+ execution_payload = validated_input
204
+
205
+ # Merge native tools with MCP tools
206
+ native_tools = list(agent.tools or [])
207
+
208
+ # Lazy-load MCP tools for this agent
209
+ try:
210
+ mcp_tools = await agent._get_mcp_tools(ctx)
211
+ logger.debug(f"Loaded {len(mcp_tools)} MCP tools for agent {agent.name}")
212
+ except Exception as e:
213
+ # Architecture Decision: AD007 - Graceful Degradation
214
+ # If MCP loading fails, continue with native tools only
215
+ logger.error(f"Failed to load MCP tools in engine: {e}", exc_info=True)
216
+ mcp_tools = []
217
+
218
+ # Combine both lists
219
+ # Architecture Decision: AD003 - MCP tools are namespaced, so no conflicts
220
+ combined_tools = native_tools + mcp_tools
221
+ logger.debug(
222
+ f"Total tools for agent {agent.name}: {len(combined_tools)} (native: {len(native_tools)}, mcp: {len(mcp_tools)})"
223
+ )
224
+
225
+ with dspy_mod.context(lm=lm):
226
+ program = self._choose_program(dspy_mod, signature, combined_tools)
227
+
228
+ # Detect if there's already an active Rich Live context
229
+ should_stream = self.stream
230
+ orchestrator = getattr(ctx, "orchestrator", None)
231
+ if orchestrator:
232
+ is_dashboard = getattr(orchestrator, "is_dashboard", False) if ctx else False
233
+ # if dashboard we always stream, streamin queue only for CLI output
234
+ if should_stream and ctx and not is_dashboard:
235
+ if not hasattr(orchestrator, "_active_streams"):
236
+ orchestrator._active_streams = 0
237
+
238
+ if orchestrator._active_streams > 0:
239
+ should_stream = False
240
+ else:
241
+ orchestrator._active_streams += 1
242
+
243
+ try:
244
+ if should_stream:
245
+ (
246
+ raw_result,
247
+ _stream_final_display_data,
248
+ ) = await self._execute_streaming(
249
+ dspy_mod,
250
+ program,
251
+ signature,
252
+ description=sys_desc,
253
+ payload=execution_payload,
254
+ agent=agent,
255
+ ctx=ctx,
256
+ pre_generated_artifact_id=pre_generated_artifact_id,
257
+ )
258
+ if not self.no_output and ctx:
259
+ ctx.state["_flock_stream_live_active"] = True
260
+ else:
261
+ orchestrator = getattr(ctx, "orchestrator", None) if ctx else None
262
+
263
+ raw_result = await self._execute_standard(
264
+ dspy_mod,
265
+ program,
266
+ description=sys_desc,
267
+ payload=execution_payload,
268
+ )
269
+ if ctx and orchestrator and getattr(orchestrator, "_active_streams", 0) > 0:
270
+ ctx.state["_flock_output_queued"] = True
271
+ finally:
272
+ if should_stream and ctx:
273
+ if orchestrator is None:
274
+ orchestrator = getattr(ctx, "orchestrator", None)
275
+ if orchestrator and hasattr(orchestrator, "_active_streams"):
276
+ orchestrator._active_streams = max(0, orchestrator._active_streams - 1)
277
+
278
+ normalized_output = self._normalize_output_payload(getattr(raw_result, "output", None))
279
+ artifacts, errors = self._materialize_artifacts(
280
+ normalized_output,
281
+ agent.outputs,
282
+ agent.name,
283
+ pre_generated_id=pre_generated_artifact_id,
284
+ )
285
+
286
+ state = dict(inputs.state)
287
+ state.setdefault("dspy", {})
288
+ state["dspy"].update({"model": model_name, "raw": normalized_output})
289
+
290
+ logs: list[str] = []
291
+ if normalized_output is not None:
292
+ try:
293
+ logs.append(f"dspy.output={json.dumps(normalized_output)}")
294
+ except TypeError:
295
+ logs.append(f"dspy.output={normalized_output!r}")
296
+ logs.extend(f"dspy.error={message}" for message in errors)
297
+
298
+ result_artifacts = artifacts if artifacts else list(inputs.artifacts)
299
+ return EvalResult(artifacts=result_artifacts, state=state, logs=logs)
300
+
301
+ # ------------------------------------------------------------------
302
+ # Helpers mirroring the design engine
303
+
304
+ def _resolve_model_name(self) -> str:
305
+ model = self.model or os.getenv("TRELLIS_MODEL") or os.getenv("OPENAI_MODEL")
306
+ if not model:
307
+ raise NotImplementedError(
308
+ "DSPyEngine requires a configured model (set TRELLIS_MODEL, OPENAI_MODEL, or pass model=...)."
309
+ )
310
+ return model
311
+
312
+ def _import_dspy(self): # pragma: no cover - import guarded by optional dependency
313
+ try:
314
+ import dspy
315
+ except Exception as exc:
316
+ raise NotImplementedError("DSPy is not installed or failed to import.") from exc
317
+ return dspy
318
+
319
+ def _select_primary_artifact(self, artifacts: Sequence[Artifact]) -> Artifact:
320
+ return artifacts[-1]
321
+
322
+ def _resolve_input_model(self, artifact: Artifact) -> type[BaseModel] | None:
323
+ try:
324
+ return type_registry.resolve(artifact.type)
325
+ except KeyError:
326
+ return None
327
+
328
+ def _resolve_output_model(self, agent) -> type[BaseModel] | None:
329
+ if not getattr(agent, "outputs", None):
330
+ return None
331
+ return agent.outputs[0].spec.model
332
+
333
+ def _validate_input_payload(
334
+ self,
335
+ schema: type[BaseModel] | None,
336
+ payload: Mapping[str, Any] | None,
337
+ ) -> dict[str, Any]:
338
+ data = dict(payload or {})
339
+ if schema is None:
340
+ return data
341
+ try:
342
+ return schema(**data).model_dump()
343
+ except Exception:
344
+ return data
345
+
346
+ def _prepare_signature_with_context(
347
+ self,
348
+ dspy_mod,
349
+ *,
350
+ description: str | None,
351
+ input_schema: type[BaseModel] | None,
352
+ output_schema: type[BaseModel] | None,
353
+ has_context: bool = False,
354
+ ) -> Any:
355
+ """Prepare DSPy signature, optionally including context field."""
356
+ fields = {
357
+ "description": (str, dspy_mod.InputField()),
358
+ }
359
+
360
+ # Add context field if we have conversation history
361
+ if has_context:
362
+ fields["context"] = (
363
+ list,
364
+ dspy_mod.InputField(
365
+ desc="Previous conversation artifacts providing context for this request"
366
+ ),
367
+ )
368
+
369
+ fields["input"] = (input_schema or dict, dspy_mod.InputField())
370
+ fields["output"] = (output_schema or dict, dspy_mod.OutputField())
371
+
372
+ signature = dspy_mod.Signature(fields)
373
+
374
+ instruction = description or "Produce a valid output that matches the 'output' schema."
375
+ if has_context:
376
+ instruction += " Consider the conversation context provided to inform your response."
377
+ instruction += " Return only JSON."
378
+
379
+ return signature.with_instructions(instruction)
380
+
381
+ def _choose_program(self, dspy_mod, signature, tools: Iterable[Any]):
382
+ tools_list = list(tools or [])
383
+ try:
384
+ if tools_list:
385
+ return dspy_mod.ReAct(signature, tools=tools_list, max_iters=self.max_tool_calls)
386
+ return dspy_mod.Predict(signature)
387
+ except Exception:
388
+ return dspy_mod.Predict(signature)
389
+
390
+ def _system_description(self, description: str | None) -> str:
391
+ if description:
392
+ return description
393
+ return "Produce a valid output that matches the 'output' schema. Return only JSON."
394
+
395
+ def _normalize_output_payload(self, raw: Any) -> dict[str, Any]:
396
+ if isinstance(raw, BaseModel):
397
+ return raw.model_dump()
398
+ if isinstance(raw, str):
399
+ text = raw.strip()
400
+ candidates: list[str] = []
401
+
402
+ # Primary attempt - full string
403
+ if text:
404
+ candidates.append(text)
405
+
406
+ # Handle DSPy streaming markers like `[[ ## output ## ]]`
407
+ if text.startswith("[[") and "]]" in text:
408
+ _, remainder = text.split("]]", 1)
409
+ remainder = remainder.strip()
410
+ if remainder:
411
+ candidates.append(remainder)
412
+
413
+ # Handle Markdown-style fenced blocks
414
+ if text.startswith("```") and text.endswith("```"):
415
+ fenced = text.strip("`").strip()
416
+ if fenced:
417
+ candidates.append(fenced)
418
+
419
+ # Extract first JSON-looking segment if present
420
+ for opener, closer in (("{", "}"), ("[", "]")):
421
+ start = text.find(opener)
422
+ end = text.rfind(closer)
423
+ if start != -1 and end != -1 and end > start:
424
+ segment = text[start : end + 1].strip()
425
+ if segment:
426
+ candidates.append(segment)
427
+
428
+ seen: set[str] = set()
429
+ for candidate in candidates:
430
+ if candidate in seen:
431
+ continue
432
+ seen.add(candidate)
433
+ try:
434
+ return json.loads(candidate)
435
+ except json.JSONDecodeError:
436
+ continue
437
+
438
+ return {"text": text}
439
+ if isinstance(raw, Mapping):
440
+ return dict(raw)
441
+ return {"value": raw}
442
+
443
+ def _materialize_artifacts(
444
+ self,
445
+ payload: dict[str, Any],
446
+ outputs: Iterable[Any],
447
+ produced_by: str,
448
+ pre_generated_id: Any = None,
449
+ ):
450
+ artifacts: list[Artifact] = []
451
+ errors: list[str] = []
452
+ for output in outputs or []:
453
+ model_cls = output.spec.model
454
+ data = self._select_output_payload(payload, model_cls, output.spec.type_name)
455
+ try:
456
+ instance = model_cls(**data)
457
+ except Exception as exc: # noqa: BLE001 - collect validation errors for logs
458
+ errors.append(str(exc))
459
+ continue
460
+
461
+ # Use the pre-generated ID if provided (for streaming), otherwise let Artifact auto-generate
462
+ artifact_kwargs = {
463
+ "type": output.spec.type_name,
464
+ "payload": instance.model_dump(),
465
+ "produced_by": produced_by,
466
+ }
467
+ if pre_generated_id is not None:
468
+ artifact_kwargs["id"] = pre_generated_id
469
+
470
+ artifacts.append(Artifact(**artifact_kwargs))
471
+ return artifacts, errors
472
+
473
+ def _select_output_payload(
474
+ self,
475
+ payload: Mapping[str, Any],
476
+ model_cls: type[BaseModel],
477
+ type_name: str,
478
+ ) -> dict[str, Any]:
479
+ candidates = [
480
+ payload.get(type_name),
481
+ payload.get(model_cls.__name__),
482
+ payload.get(model_cls.__name__.lower()),
483
+ ]
484
+ for candidate in candidates:
485
+ if isinstance(candidate, Mapping):
486
+ return dict(candidate)
487
+ if isinstance(payload, Mapping):
488
+ return dict(payload)
489
+ return {}
490
+
491
+ async def _execute_standard(
492
+ self, dspy_mod, program, *, description: str, payload: dict[str, Any]
493
+ ) -> Any:
494
+ """Execute DSPy program in standard mode (no streaming)."""
495
+ # Handle new format: {"input": ..., "context": ...}
496
+ if isinstance(payload, dict) and "input" in payload:
497
+ return program(
498
+ description=description,
499
+ input=payload["input"],
500
+ context=payload.get("context", []),
501
+ )
502
+
503
+ # Handle old format: direct payload (backwards compatible)
504
+ return program(description=description, input=payload, context=[])
505
+
506
+ async def _execute_streaming(
507
+ self,
508
+ dspy_mod,
509
+ program,
510
+ signature,
511
+ *,
512
+ description: str,
513
+ payload: dict[str, Any],
514
+ agent: Any,
515
+ ctx: Any = None,
516
+ pre_generated_artifact_id: Any = None,
517
+ ) -> Any:
518
+ """Execute DSPy program in streaming mode with Rich table updates."""
519
+ from rich.console import Console
520
+ from rich.live import Live
521
+
522
+ console = Console()
523
+
524
+ # Get WebSocketManager for frontend streaming
525
+ ws_manager = None
526
+ if ctx:
527
+ orchestrator = getattr(ctx, "orchestrator", None)
528
+ if orchestrator:
529
+ collector = getattr(orchestrator, "_dashboard_collector", None)
530
+ if collector:
531
+ ws_manager = getattr(collector, "_websocket_manager", None)
532
+
533
+ # Prepare stream listeners for output field
534
+ listeners = []
535
+ try:
536
+ streaming_mod = getattr(dspy_mod, "streaming", None)
537
+ if streaming_mod and hasattr(streaming_mod, "StreamListener"):
538
+ for name, field in signature.output_fields.items():
539
+ if field.annotation is str:
540
+ listeners.append(streaming_mod.StreamListener(signature_field_name=name))
541
+ except Exception:
542
+ listeners = []
543
+
544
+ streaming_task = dspy_mod.streamify(
545
+ program,
546
+ is_async_program=True,
547
+ stream_listeners=listeners if listeners else None,
548
+ )
549
+
550
+ # Handle new format vs old format
551
+ if isinstance(payload, dict) and "input" in payload:
552
+ stream_generator = streaming_task(
553
+ description=description,
554
+ input=payload["input"],
555
+ context=payload.get("context", []),
556
+ )
557
+ else:
558
+ # Old format - backwards compatible
559
+ stream_generator = streaming_task(description=description, input=payload, context=[])
560
+
561
+ signature_order = []
562
+ status_field = self.status_output_field
563
+ try:
564
+ signature_order = list(signature.output_fields.keys())
565
+ except Exception:
566
+ signature_order = []
567
+
568
+ # Initialize display data in full artifact format (matching OutputUtilityComponent display)
569
+ display_data: OrderedDict[str, Any] = OrderedDict()
570
+
571
+ # Use the pre-generated artifact ID that was created before execution started
572
+ display_data["id"] = str(pre_generated_artifact_id)
573
+
574
+ # Get the artifact type name from agent configuration
575
+ artifact_type_name = "output"
576
+ if hasattr(agent, "outputs") and agent.outputs:
577
+ artifact_type_name = agent.outputs[0].spec.type_name
578
+
579
+ display_data["type"] = artifact_type_name
580
+ display_data["payload"] = OrderedDict()
581
+
582
+ # Add output fields to payload section
583
+ for field_name in signature_order:
584
+ if field_name != "description": # Skip description field
585
+ display_data["payload"][field_name] = ""
586
+
587
+ display_data["produced_by"] = agent.name
588
+ display_data["correlation_id"] = (
589
+ str(ctx.correlation_id) if ctx and ctx.correlation_id else None
590
+ )
591
+ display_data["partition_key"] = None
592
+ display_data["tags"] = "set()"
593
+ display_data["visibility"] = OrderedDict([("kind", "Public")])
594
+ display_data["created_at"] = "streaming..."
595
+ display_data["version"] = 1
596
+ display_data["status"] = status_field
597
+
598
+ stream_buffers: defaultdict[str, list[str]] = defaultdict(list)
599
+ stream_buffers[status_field] = []
600
+ stream_sequence = 0 # Monotonic sequence for ordering
601
+
602
+ # Track background WebSocket broadcast tasks to prevent garbage collection
603
+ ws_broadcast_tasks: set[asyncio.Task] = set()
604
+
605
+ formatter = theme_dict = styles = agent_label = None
606
+ live_cm = nullcontext()
607
+ overflow_mode = self.stream_vertical_overflow
608
+
609
+ if not self.no_output:
610
+ _ensure_live_crop_above()
611
+ (
612
+ formatter,
613
+ theme_dict,
614
+ styles,
615
+ agent_label,
616
+ ) = self._prepare_stream_formatter(agent)
617
+ initial_panel = formatter.format_result(display_data, agent_label, theme_dict, styles)
618
+ live_cm = Live(
619
+ initial_panel,
620
+ console=console,
621
+ refresh_per_second=4,
622
+ transient=False,
623
+ vertical_overflow=overflow_mode,
624
+ )
625
+
626
+ final_result: Any = None
627
+
628
+ with live_cm as live:
629
+
630
+ def _refresh_panel() -> None:
631
+ if formatter is None or live is None:
632
+ return
633
+ live.update(formatter.format_result(display_data, agent_label, theme_dict, styles))
634
+
635
+ async for value in stream_generator:
636
+ try:
637
+ from dspy.streaming import StatusMessage, StreamResponse
638
+ from litellm import ModelResponseStream
639
+ except Exception:
640
+ StatusMessage = object # type: ignore
641
+ StreamResponse = object # type: ignore
642
+ ModelResponseStream = object # type: ignore
643
+
644
+ if isinstance(value, StatusMessage):
645
+ token = getattr(value, "message", "")
646
+ if token:
647
+ stream_buffers[status_field].append(str(token) + "\n")
648
+ display_data["status"] = "".join(stream_buffers[status_field])
649
+
650
+ # Emit to WebSocket (non-blocking to prevent deadlock)
651
+ if ws_manager and token:
652
+ try:
653
+ event = StreamingOutputEvent(
654
+ correlation_id=str(ctx.correlation_id)
655
+ if ctx and ctx.correlation_id
656
+ else "",
657
+ agent_name=agent.name,
658
+ run_id=ctx.task_id if ctx else "",
659
+ output_type="llm_token",
660
+ content=str(token + "\n"),
661
+ sequence=stream_sequence,
662
+ is_final=False,
663
+ artifact_id=str(
664
+ pre_generated_artifact_id
665
+ ), # Phase 6: Track artifact for message streaming
666
+ artifact_type=artifact_type_name, # Phase 6: Artifact type name
667
+ )
668
+ # Use create_task to avoid blocking the streaming loop
669
+ task = asyncio.create_task(ws_manager.broadcast(event))
670
+ ws_broadcast_tasks.add(task)
671
+ task.add_done_callback(ws_broadcast_tasks.discard)
672
+ stream_sequence += 1
673
+ except Exception as e:
674
+ logger.warning(f"Failed to emit streaming event: {e}")
675
+ else:
676
+ logger.debug("No WebSocket manager present for streaming event.")
677
+
678
+ if formatter is not None:
679
+ _refresh_panel()
680
+ continue
681
+
682
+ if isinstance(value, StreamResponse):
683
+ token = getattr(value, "chunk", None)
684
+ signature_field = getattr(value, "signature_field_name", None)
685
+ if signature_field and signature_field != "description":
686
+ # Update payload section - accumulate in "output" buffer
687
+ buffer_key = f"_stream_{signature_field}"
688
+ if token:
689
+ stream_buffers[buffer_key].append(str(token))
690
+ # Show streaming text in payload
691
+ display_data["payload"]["_streaming"] = "".join(
692
+ stream_buffers[buffer_key]
693
+ )
694
+
695
+ # Emit to WebSocket (non-blocking to prevent deadlock)
696
+ if ws_manager:
697
+ logger.info(
698
+ f"[STREAMING] Emitting StreamResponse token='{token}', sequence={stream_sequence}"
699
+ )
700
+ try:
701
+ event = StreamingOutputEvent(
702
+ correlation_id=str(ctx.correlation_id)
703
+ if ctx and ctx.correlation_id
704
+ else "",
705
+ agent_name=agent.name,
706
+ run_id=ctx.task_id if ctx else "",
707
+ output_type="llm_token",
708
+ content=str(token),
709
+ sequence=stream_sequence,
710
+ is_final=False,
711
+ artifact_id=str(
712
+ pre_generated_artifact_id
713
+ ), # Phase 6: Track artifact for message streaming
714
+ artifact_type=artifact_type_name, # Phase 6: Artifact type name
715
+ )
716
+ # Use create_task to avoid blocking the streaming loop
717
+ task = asyncio.create_task(ws_manager.broadcast(event))
718
+ ws_broadcast_tasks.add(task)
719
+ task.add_done_callback(ws_broadcast_tasks.discard)
720
+ stream_sequence += 1
721
+ except Exception as e:
722
+ logger.warning(f"Failed to emit streaming event: {e}")
723
+
724
+ if formatter is not None:
725
+ _refresh_panel()
726
+ continue
727
+
728
+ if isinstance(value, ModelResponseStream):
729
+ chunk = value
730
+ token = chunk.choices[0].delta.content or ""
731
+ signature_field = getattr(value, "signature_field_name", None)
732
+
733
+ if signature_field and signature_field != "description":
734
+ # Update payload section - accumulate in buffer
735
+ buffer_key = f"_stream_{signature_field}"
736
+ if token:
737
+ stream_buffers[buffer_key].append(str(token))
738
+ # Show streaming text in payload
739
+ display_data["payload"]["_streaming"] = "".join(
740
+ stream_buffers[buffer_key]
741
+ )
742
+ elif token:
743
+ stream_buffers[status_field].append(str(token))
744
+ display_data["status"] = "".join(stream_buffers[status_field])
745
+
746
+ # Emit to WebSocket (non-blocking to prevent deadlock)
747
+ if ws_manager and token:
748
+ try:
749
+ event = StreamingOutputEvent(
750
+ correlation_id=str(ctx.correlation_id)
751
+ if ctx and ctx.correlation_id
752
+ else "",
753
+ agent_name=agent.name,
754
+ run_id=ctx.task_id if ctx else "",
755
+ output_type="llm_token",
756
+ content=str(token),
757
+ sequence=stream_sequence,
758
+ is_final=False,
759
+ artifact_id=str(
760
+ pre_generated_artifact_id
761
+ ), # Phase 6: Track artifact for message streaming
762
+ artifact_type=display_data[
763
+ "type"
764
+ ], # Phase 6: Artifact type name from display_data
765
+ )
766
+ # Use create_task to avoid blocking the streaming loop
767
+ task = asyncio.create_task(ws_manager.broadcast(event))
768
+ ws_broadcast_tasks.add(task)
769
+ task.add_done_callback(ws_broadcast_tasks.discard)
770
+ stream_sequence += 1
771
+ except Exception as e:
772
+ logger.warning(f"Failed to emit streaming event: {e}")
773
+
774
+ if formatter is not None:
775
+ _refresh_panel()
776
+ continue
777
+
778
+ if isinstance(value, dspy_mod.Prediction):
779
+ final_result = value
780
+
781
+ # Emit final streaming event (non-blocking to prevent deadlock)
782
+ if ws_manager:
783
+ try:
784
+ event = StreamingOutputEvent(
785
+ correlation_id=str(ctx.correlation_id)
786
+ if ctx and ctx.correlation_id
787
+ else "",
788
+ agent_name=agent.name,
789
+ run_id=ctx.task_id if ctx else "",
790
+ output_type="log",
791
+ content="\nAmount of output tokens: " + str(stream_sequence),
792
+ sequence=stream_sequence,
793
+ is_final=True, # Mark as final
794
+ artifact_id=str(
795
+ pre_generated_artifact_id
796
+ ), # Phase 6: Track artifact for message streaming
797
+ artifact_type=display_data["type"], # Phase 6: Artifact type name
798
+ )
799
+ # Use create_task to avoid blocking the streaming loop
800
+ task = asyncio.create_task(ws_manager.broadcast(event))
801
+ ws_broadcast_tasks.add(task)
802
+ task.add_done_callback(ws_broadcast_tasks.discard)
803
+ event = StreamingOutputEvent(
804
+ correlation_id=str(ctx.correlation_id)
805
+ if ctx and ctx.correlation_id
806
+ else "",
807
+ agent_name=agent.name,
808
+ run_id=ctx.task_id if ctx else "",
809
+ output_type="log",
810
+ content="--- End of output ---",
811
+ sequence=stream_sequence,
812
+ is_final=True, # Mark as final
813
+ artifact_id=str(
814
+ pre_generated_artifact_id
815
+ ), # Phase 6: Track artifact for message streaming
816
+ artifact_type=display_data["type"], # Phase 6: Artifact type name
817
+ )
818
+ # Use create_task to avoid blocking the streaming loop
819
+ task = asyncio.create_task(ws_manager.broadcast(event))
820
+ ws_broadcast_tasks.add(task)
821
+ task.add_done_callback(ws_broadcast_tasks.discard)
822
+ except Exception as e:
823
+ logger.warning(f"Failed to emit final streaming event: {e}")
824
+
825
+ if formatter is not None:
826
+ # Update payload section with final values
827
+ payload_data = OrderedDict()
828
+ for field_name in signature_order:
829
+ if field_name != "description" and hasattr(final_result, field_name):
830
+ field_value = getattr(final_result, field_name)
831
+ # If the field is a BaseModel, unwrap it to dict
832
+ if isinstance(field_value, BaseModel):
833
+ payload_data.update(field_value.model_dump())
834
+ else:
835
+ payload_data[field_name] = field_value
836
+
837
+ # Update all fields with actual values
838
+ display_data["payload"].clear()
839
+ display_data["payload"].update(payload_data)
840
+
841
+ # Update timestamp
842
+ from datetime import datetime, timezone
843
+
844
+ display_data["created_at"] = datetime.now(timezone.utc).isoformat()
845
+
846
+ # Remove status field from display
847
+ display_data.pop("status", None)
848
+ _refresh_panel()
849
+
850
+ if final_result is None:
851
+ raise RuntimeError("Streaming did not yield a final prediction.")
852
+
853
+ # Return both the result and the display data for final ID update
854
+ return final_result, (formatter, display_data, theme_dict, styles, agent_label)
855
+
856
+ def _prepare_stream_formatter(
857
+ self, agent: Any
858
+ ) -> tuple[Any, dict[str, Any], dict[str, Any], str]:
859
+ """Build formatter + theme metadata for streaming tables."""
860
+ import pathlib
861
+
862
+ from flock.logging.formatters.themed_formatter import (
863
+ ThemedAgentResultFormatter,
864
+ create_pygments_syntax_theme,
865
+ get_default_styles,
866
+ load_syntax_theme_from_file,
867
+ load_theme_from_file,
868
+ )
869
+
870
+ themes_dir = pathlib.Path(__file__).resolve().parents[1] / "themes"
871
+ theme_filename = self.theme
872
+ if not theme_filename.endswith(".toml"):
873
+ theme_filename = f"{theme_filename}.toml"
874
+ theme_path = themes_dir / theme_filename
875
+
876
+ try:
877
+ theme_dict = load_theme_from_file(theme_path)
878
+ except Exception:
879
+ fallback_path = themes_dir / "afterglow.toml"
880
+ theme_dict = load_theme_from_file(fallback_path)
881
+ theme_path = fallback_path
882
+
883
+ from flock.logging.formatters.themes import OutputTheme
884
+
885
+ formatter = ThemedAgentResultFormatter(theme=OutputTheme.afterglow)
886
+ styles = get_default_styles(theme_dict)
887
+ formatter.styles = styles
888
+
889
+ try:
890
+ syntax_theme = load_syntax_theme_from_file(theme_path)
891
+ formatter.syntax_style = create_pygments_syntax_theme(syntax_theme)
892
+ except Exception:
893
+ formatter.syntax_style = None
894
+
895
+ model_label = self.model or ""
896
+ agent_label = agent.name if not model_label else f"{agent.name} - {model_label}"
897
+
898
+ return formatter, theme_dict, styles, agent_label
899
+
900
+ def _print_final_stream_display(
901
+ self,
902
+ stream_display_data: tuple[Any, OrderedDict, dict, dict, str],
903
+ artifact_id: str,
904
+ artifact: Artifact,
905
+ ) -> None:
906
+ """Print the final streaming display with the real artifact ID."""
907
+ from rich.console import Console
908
+
909
+ formatter, display_data, theme_dict, styles, agent_label = stream_display_data
910
+
911
+ # Update display_data with the real artifact information
912
+ display_data["id"] = artifact_id
913
+ display_data["created_at"] = artifact.created_at.isoformat()
914
+
915
+ # Update all artifact metadata
916
+ display_data["correlation_id"] = (
917
+ str(artifact.correlation_id) if artifact.correlation_id else None
918
+ )
919
+ display_data["partition_key"] = artifact.partition_key
920
+ display_data["tags"] = "set()" if not artifact.tags else f"set({list(artifact.tags)})"
921
+
922
+ # Print the final panel
923
+ console = Console()
924
+ final_panel = formatter.format_result(display_data, agent_label, theme_dict, styles)
925
+ console.print(final_panel)
926
+
927
+
928
+ __all__ = ["DSPyEngine"]
929
+
930
+
931
+ # Apply the Rich Live patch when this module is imported
932
+ _apply_live_patch_on_import()