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

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

Potentially problematic release.


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

Files changed (501) hide show
  1. flock/__init__.py +12 -217
  2. flock/agent.py +1079 -0
  3. flock/api/themes.py +71 -0
  4. flock/artifacts.py +86 -0
  5. flock/cli.py +147 -0
  6. flock/components.py +189 -0
  7. flock/dashboard/__init__.py +30 -0
  8. flock/dashboard/collector.py +559 -0
  9. flock/dashboard/events.py +188 -0
  10. flock/dashboard/graph_builder.py +563 -0
  11. flock/dashboard/launcher.py +235 -0
  12. flock/dashboard/models/graph.py +156 -0
  13. flock/dashboard/service.py +991 -0
  14. flock/dashboard/static_v2/assets/index-DFRnI_mt.js +111 -0
  15. flock/dashboard/static_v2/assets/index-fPLNdmp1.css +1 -0
  16. flock/dashboard/static_v2/index.html +13 -0
  17. flock/dashboard/websocket.py +246 -0
  18. flock/engines/__init__.py +6 -0
  19. flock/engines/dspy_engine.py +932 -0
  20. flock/examples.py +131 -0
  21. flock/frontend/README.md +778 -0
  22. flock/frontend/docs/DESIGN_SYSTEM.md +1980 -0
  23. flock/frontend/index.html +12 -0
  24. flock/frontend/package-lock.json +4337 -0
  25. flock/frontend/package.json +48 -0
  26. flock/frontend/src/App.tsx +139 -0
  27. flock/frontend/src/__tests__/integration/graph-snapshot.test.tsx +647 -0
  28. flock/frontend/src/__tests__/integration/indexeddb-persistence.test.tsx +699 -0
  29. flock/frontend/src/components/common/BuildInfo.tsx +39 -0
  30. flock/frontend/src/components/common/EmptyState.module.css +115 -0
  31. flock/frontend/src/components/common/EmptyState.tsx +128 -0
  32. flock/frontend/src/components/common/ErrorBoundary.module.css +169 -0
  33. flock/frontend/src/components/common/ErrorBoundary.tsx +118 -0
  34. flock/frontend/src/components/common/KeyboardShortcutsDialog.css +251 -0
  35. flock/frontend/src/components/common/KeyboardShortcutsDialog.tsx +151 -0
  36. flock/frontend/src/components/common/LoadingSpinner.module.css +97 -0
  37. flock/frontend/src/components/common/LoadingSpinner.tsx +29 -0
  38. flock/frontend/src/components/controls/PublishControl.css +547 -0
  39. flock/frontend/src/components/controls/PublishControl.test.tsx +543 -0
  40. flock/frontend/src/components/controls/PublishControl.tsx +432 -0
  41. flock/frontend/src/components/details/DetailWindowContainer.tsx +58 -0
  42. flock/frontend/src/components/details/LiveOutputTab.test.tsx +792 -0
  43. flock/frontend/src/components/details/LiveOutputTab.tsx +220 -0
  44. flock/frontend/src/components/details/MessageDetailWindow.tsx +439 -0
  45. flock/frontend/src/components/details/MessageHistoryTab.tsx +374 -0
  46. flock/frontend/src/components/details/NodeDetailWindow.test.tsx +501 -0
  47. flock/frontend/src/components/details/NodeDetailWindow.tsx +218 -0
  48. flock/frontend/src/components/details/RunStatusTab.tsx +348 -0
  49. flock/frontend/src/components/details/tabs.test.tsx +1015 -0
  50. flock/frontend/src/components/filters/ArtifactTypeFilter.tsx +21 -0
  51. flock/frontend/src/components/filters/CorrelationIDFilter.module.css +102 -0
  52. flock/frontend/src/components/filters/CorrelationIDFilter.test.tsx +197 -0
  53. flock/frontend/src/components/filters/CorrelationIDFilter.tsx +121 -0
  54. flock/frontend/src/components/filters/FilterFlyout.module.css +104 -0
  55. flock/frontend/src/components/filters/FilterFlyout.tsx +80 -0
  56. flock/frontend/src/components/filters/FilterPills.module.css +220 -0
  57. flock/frontend/src/components/filters/FilterPills.test.tsx +189 -0
  58. flock/frontend/src/components/filters/FilterPills.tsx +143 -0
  59. flock/frontend/src/components/filters/ProducerFilter.tsx +21 -0
  60. flock/frontend/src/components/filters/SavedFiltersControl.module.css +60 -0
  61. flock/frontend/src/components/filters/SavedFiltersControl.test.tsx +158 -0
  62. flock/frontend/src/components/filters/SavedFiltersControl.tsx +159 -0
  63. flock/frontend/src/components/filters/TagFilter.tsx +21 -0
  64. flock/frontend/src/components/filters/TimeRangeFilter.module.css +115 -0
  65. flock/frontend/src/components/filters/TimeRangeFilter.test.tsx +154 -0
  66. flock/frontend/src/components/filters/TimeRangeFilter.tsx +110 -0
  67. flock/frontend/src/components/filters/VisibilityFilter.tsx +21 -0
  68. flock/frontend/src/components/graph/AgentNode.test.tsx +77 -0
  69. flock/frontend/src/components/graph/AgentNode.tsx +324 -0
  70. flock/frontend/src/components/graph/GraphCanvas.tsx +613 -0
  71. flock/frontend/src/components/graph/MessageFlowEdge.tsx +128 -0
  72. flock/frontend/src/components/graph/MessageNode.test.tsx +64 -0
  73. flock/frontend/src/components/graph/MessageNode.tsx +129 -0
  74. flock/frontend/src/components/graph/MiniMap.tsx +47 -0
  75. flock/frontend/src/components/graph/TransformEdge.tsx +123 -0
  76. flock/frontend/src/components/layout/DashboardLayout.css +420 -0
  77. flock/frontend/src/components/layout/DashboardLayout.tsx +287 -0
  78. flock/frontend/src/components/layout/Header.module.css +88 -0
  79. flock/frontend/src/components/layout/Header.tsx +52 -0
  80. flock/frontend/src/components/modules/HistoricalArtifactsModule.module.css +288 -0
  81. flock/frontend/src/components/modules/HistoricalArtifactsModule.tsx +450 -0
  82. flock/frontend/src/components/modules/HistoricalArtifactsModuleWrapper.tsx +13 -0
  83. flock/frontend/src/components/modules/JsonAttributeRenderer.tsx +140 -0
  84. flock/frontend/src/components/modules/ModuleRegistry.test.ts +333 -0
  85. flock/frontend/src/components/modules/ModuleRegistry.ts +93 -0
  86. flock/frontend/src/components/modules/ModuleWindow.tsx +223 -0
  87. flock/frontend/src/components/modules/TraceModuleJaeger.tsx +1971 -0
  88. flock/frontend/src/components/modules/TraceModuleJaegerWrapper.tsx +13 -0
  89. flock/frontend/src/components/modules/registerModules.ts +29 -0
  90. flock/frontend/src/components/settings/AdvancedSettings.tsx +175 -0
  91. flock/frontend/src/components/settings/AppearanceSettings.tsx +185 -0
  92. flock/frontend/src/components/settings/GraphSettings.tsx +110 -0
  93. flock/frontend/src/components/settings/MultiSelect.tsx +235 -0
  94. flock/frontend/src/components/settings/SettingsPanel.css +327 -0
  95. flock/frontend/src/components/settings/SettingsPanel.tsx +131 -0
  96. flock/frontend/src/components/settings/ThemeSelector.tsx +298 -0
  97. flock/frontend/src/components/settings/TracingSettings.tsx +404 -0
  98. flock/frontend/src/hooks/useKeyboardShortcuts.ts +148 -0
  99. flock/frontend/src/hooks/useModulePersistence.test.ts +442 -0
  100. flock/frontend/src/hooks/useModulePersistence.ts +154 -0
  101. flock/frontend/src/hooks/useModules.ts +157 -0
  102. flock/frontend/src/hooks/usePersistence.ts +141 -0
  103. flock/frontend/src/main.tsx +13 -0
  104. flock/frontend/src/services/api.ts +337 -0
  105. flock/frontend/src/services/graphService.test.ts +330 -0
  106. flock/frontend/src/services/graphService.ts +75 -0
  107. flock/frontend/src/services/indexeddb.test.ts +793 -0
  108. flock/frontend/src/services/indexeddb.ts +848 -0
  109. flock/frontend/src/services/layout.test.ts +437 -0
  110. flock/frontend/src/services/layout.ts +357 -0
  111. flock/frontend/src/services/themeApplicator.ts +140 -0
  112. flock/frontend/src/services/themeService.ts +77 -0
  113. flock/frontend/src/services/websocket.ts +650 -0
  114. flock/frontend/src/store/filterStore.test.ts +250 -0
  115. flock/frontend/src/store/filterStore.ts +272 -0
  116. flock/frontend/src/store/graphStore.test.ts +570 -0
  117. flock/frontend/src/store/graphStore.ts +462 -0
  118. flock/frontend/src/store/moduleStore.test.ts +253 -0
  119. flock/frontend/src/store/moduleStore.ts +75 -0
  120. flock/frontend/src/store/settingsStore.ts +188 -0
  121. flock/frontend/src/store/streamStore.ts +68 -0
  122. flock/frontend/src/store/uiStore.test.ts +54 -0
  123. flock/frontend/src/store/uiStore.ts +122 -0
  124. flock/frontend/src/store/wsStore.ts +34 -0
  125. flock/frontend/src/styles/index.css +15 -0
  126. flock/frontend/src/styles/scrollbar.css +47 -0
  127. flock/frontend/src/styles/variables.css +488 -0
  128. flock/frontend/src/test/setup.ts +1 -0
  129. flock/frontend/src/types/filters.ts +47 -0
  130. flock/frontend/src/types/graph.ts +95 -0
  131. flock/frontend/src/types/modules.ts +10 -0
  132. flock/frontend/src/types/theme.ts +55 -0
  133. flock/frontend/src/utils/artifacts.ts +24 -0
  134. flock/frontend/src/utils/mockData.ts +98 -0
  135. flock/frontend/src/utils/performance.ts +16 -0
  136. flock/frontend/src/vite-env.d.ts +17 -0
  137. flock/frontend/tsconfig.json +27 -0
  138. flock/frontend/tsconfig.node.json +11 -0
  139. flock/frontend/vite.config.ts +25 -0
  140. flock/frontend/vitest.config.ts +11 -0
  141. flock/{core/util → helper}/cli_helper.py +9 -5
  142. flock/{core/logging → logging}/__init__.py +2 -3
  143. flock/logging/auto_trace.py +159 -0
  144. flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
  145. flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
  146. flock/{core/logging → logging}/formatters/themed_formatter.py +69 -107
  147. flock/{core/logging → logging}/logging.py +78 -61
  148. flock/{core/logging → logging}/telemetry.py +66 -26
  149. flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
  150. flock/logging/telemetry_exporter/duckdb_exporter.py +216 -0
  151. flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +13 -10
  152. flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
  153. flock/logging/trace_and_logged.py +304 -0
  154. flock/mcp/__init__.py +91 -0
  155. flock/{core/mcp/mcp_client.py → mcp/client.py} +131 -158
  156. flock/{core/mcp/mcp_config.py → mcp/config.py} +86 -132
  157. flock/mcp/manager.py +286 -0
  158. flock/mcp/servers/sse/__init__.py +1 -1
  159. flock/mcp/servers/sse/flock_sse_server.py +16 -58
  160. flock/mcp/servers/stdio/__init__.py +1 -1
  161. flock/mcp/servers/stdio/flock_stdio_server.py +13 -53
  162. flock/mcp/servers/streamable_http/flock_streamable_http_server.py +22 -67
  163. flock/mcp/servers/websockets/flock_websocket_server.py +12 -45
  164. flock/{core/mcp/flock_mcp_tool_base.py → mcp/tool.py} +24 -78
  165. flock/mcp/types/__init__.py +42 -0
  166. flock/{core/mcp → mcp}/types/callbacks.py +12 -15
  167. flock/{core/mcp → mcp}/types/factories.py +7 -6
  168. flock/{core/mcp → mcp}/types/handlers.py +13 -18
  169. flock/{core/mcp → mcp}/types/types.py +70 -74
  170. flock/{core/mcp → mcp}/util/helpers.py +3 -3
  171. flock/orchestrator.py +970 -0
  172. flock/registry.py +148 -0
  173. flock/runtime.py +262 -0
  174. flock/service.py +277 -0
  175. flock/store.py +1214 -0
  176. flock/subscription.py +111 -0
  177. flock/themes/andromeda.toml +1 -1
  178. flock/themes/apple-system-colors.toml +1 -1
  179. flock/themes/arcoiris.toml +1 -1
  180. flock/themes/atomonelight.toml +1 -1
  181. flock/themes/ayu copy.toml +1 -1
  182. flock/themes/ayu-light.toml +1 -1
  183. flock/themes/belafonte-day.toml +1 -1
  184. flock/themes/belafonte-night.toml +1 -1
  185. flock/themes/blulocodark.toml +1 -1
  186. flock/themes/breeze.toml +1 -1
  187. flock/themes/broadcast.toml +1 -1
  188. flock/themes/brogrammer.toml +1 -1
  189. flock/themes/builtin-dark.toml +1 -1
  190. flock/themes/builtin-pastel-dark.toml +1 -1
  191. flock/themes/catppuccin-latte.toml +1 -1
  192. flock/themes/catppuccin-macchiato.toml +1 -1
  193. flock/themes/catppuccin-mocha.toml +1 -1
  194. flock/themes/cga.toml +1 -1
  195. flock/themes/chalk.toml +1 -1
  196. flock/themes/ciapre.toml +1 -1
  197. flock/themes/coffee-theme.toml +1 -1
  198. flock/themes/cyberpunkscarletprotocol.toml +1 -1
  199. flock/themes/dark+.toml +1 -1
  200. flock/themes/darkermatrix.toml +1 -1
  201. flock/themes/darkmatrix.toml +2 -2
  202. flock/themes/darkside.toml +1 -1
  203. flock/themes/deep.toml +2 -2
  204. flock/themes/desert.toml +1 -1
  205. flock/themes/django.toml +1 -1
  206. flock/themes/djangosmooth.toml +1 -1
  207. flock/themes/doomone.toml +1 -1
  208. flock/themes/dotgov.toml +1 -1
  209. flock/themes/dracula+.toml +1 -1
  210. flock/themes/duckbones.toml +1 -1
  211. flock/themes/encom.toml +1 -1
  212. flock/themes/espresso.toml +1 -1
  213. flock/themes/everblush.toml +1 -1
  214. flock/themes/fairyfloss.toml +1 -1
  215. flock/themes/fideloper.toml +1 -1
  216. flock/themes/fishtank.toml +1 -1
  217. flock/themes/flexoki-light.toml +1 -1
  218. flock/themes/floraverse.toml +1 -1
  219. flock/themes/framer.toml +1 -1
  220. flock/themes/galizur.toml +1 -1
  221. flock/themes/github.toml +1 -1
  222. flock/themes/grass.toml +1 -1
  223. flock/themes/grey-green.toml +1 -1
  224. flock/themes/gruvboxlight.toml +1 -1
  225. flock/themes/guezwhoz.toml +1 -1
  226. flock/themes/harper.toml +1 -1
  227. flock/themes/hax0r-blue.toml +1 -1
  228. flock/themes/hopscotch.256.toml +1 -1
  229. flock/themes/ic-green-ppl.toml +1 -1
  230. flock/themes/iceberg-dark.toml +1 -1
  231. flock/themes/japanesque.toml +1 -1
  232. flock/themes/jubi.toml +1 -1
  233. flock/themes/kibble.toml +1 -1
  234. flock/themes/kolorit.toml +1 -1
  235. flock/themes/kurokula.toml +1 -1
  236. flock/themes/materialdesigncolors.toml +1 -1
  237. flock/themes/matrix.toml +1 -1
  238. flock/themes/mellifluous.toml +1 -1
  239. flock/themes/midnight-in-mojave.toml +1 -1
  240. flock/themes/monokai-remastered.toml +1 -1
  241. flock/themes/monokai-soda.toml +1 -1
  242. flock/themes/neon.toml +1 -1
  243. flock/themes/neopolitan.toml +5 -5
  244. flock/themes/nord-light.toml +1 -1
  245. flock/themes/ocean.toml +1 -1
  246. flock/themes/onehalfdark.toml +1 -1
  247. flock/themes/onehalflight.toml +1 -1
  248. flock/themes/palenighthc.toml +1 -1
  249. flock/themes/paulmillr.toml +1 -1
  250. flock/themes/pencildark.toml +1 -1
  251. flock/themes/pnevma.toml +1 -1
  252. flock/themes/purple-rain.toml +1 -1
  253. flock/themes/purplepeter.toml +1 -1
  254. flock/themes/raycast-dark.toml +1 -1
  255. flock/themes/red-sands.toml +1 -1
  256. flock/themes/relaxed.toml +1 -1
  257. flock/themes/retro.toml +1 -1
  258. flock/themes/rose-pine.toml +1 -1
  259. flock/themes/royal.toml +1 -1
  260. flock/themes/ryuuko.toml +1 -1
  261. flock/themes/sakura.toml +1 -1
  262. flock/themes/scarlet-protocol.toml +1 -1
  263. flock/themes/seoulbones-dark.toml +1 -1
  264. flock/themes/shades-of-purple.toml +1 -1
  265. flock/themes/smyck.toml +1 -1
  266. flock/themes/softserver.toml +1 -1
  267. flock/themes/solarized-darcula.toml +1 -1
  268. flock/themes/square.toml +1 -1
  269. flock/themes/sugarplum.toml +1 -1
  270. flock/themes/thayer-bright.toml +1 -1
  271. flock/themes/tokyonight.toml +1 -1
  272. flock/themes/tomorrow.toml +1 -1
  273. flock/themes/ubuntu.toml +1 -1
  274. flock/themes/ultradark.toml +1 -1
  275. flock/themes/ultraviolent.toml +1 -1
  276. flock/themes/unikitty.toml +1 -1
  277. flock/themes/urple.toml +1 -1
  278. flock/themes/vesper.toml +1 -1
  279. flock/themes/vimbones.toml +1 -1
  280. flock/themes/wildcherry.toml +1 -1
  281. flock/themes/wilmersdorf.toml +1 -1
  282. flock/themes/wryan.toml +1 -1
  283. flock/themes/xcodedarkhc.toml +1 -1
  284. flock/themes/xcodelight.toml +1 -1
  285. flock/themes/zenbones-light.toml +1 -1
  286. flock/themes/zenwritten-dark.toml +1 -1
  287. flock/utilities.py +301 -0
  288. flock/utility/output_utility_component.py +226 -0
  289. flock/visibility.py +107 -0
  290. flock_core-0.5.0.dist-info/METADATA +964 -0
  291. flock_core-0.5.0.dist-info/RECORD +525 -0
  292. flock_core-0.5.0.dist-info/entry_points.txt +2 -0
  293. {flock_core-0.4.543.dist-info → flock_core-0.5.0.dist-info}/licenses/LICENSE +1 -1
  294. flock/adapter/__init__.py +0 -14
  295. flock/adapter/azure_adapter.py +0 -68
  296. flock/adapter/chroma_adapter.py +0 -73
  297. flock/adapter/faiss_adapter.py +0 -97
  298. flock/adapter/pinecone_adapter.py +0 -51
  299. flock/adapter/vector_base.py +0 -47
  300. flock/cli/assets/release_notes.md +0 -140
  301. flock/cli/config.py +0 -8
  302. flock/cli/constants.py +0 -36
  303. flock/cli/create_agent.py +0 -1
  304. flock/cli/create_flock.py +0 -280
  305. flock/cli/execute_flock.py +0 -620
  306. flock/cli/load_agent.py +0 -1
  307. flock/cli/load_examples.py +0 -1
  308. flock/cli/load_flock.py +0 -192
  309. flock/cli/load_release_notes.py +0 -20
  310. flock/cli/loaded_flock_cli.py +0 -254
  311. flock/cli/manage_agents.py +0 -459
  312. flock/cli/registry_management.py +0 -889
  313. flock/cli/runner.py +0 -41
  314. flock/cli/settings.py +0 -857
  315. flock/cli/utils.py +0 -135
  316. flock/cli/view_results.py +0 -29
  317. flock/cli/yaml_editor.py +0 -396
  318. flock/config.py +0 -56
  319. flock/core/__init__.py +0 -44
  320. flock/core/api/__init__.py +0 -10
  321. flock/core/api/custom_endpoint.py +0 -45
  322. flock/core/api/endpoints.py +0 -262
  323. flock/core/api/main.py +0 -162
  324. flock/core/api/models.py +0 -101
  325. flock/core/api/run_store.py +0 -224
  326. flock/core/api/runner.py +0 -44
  327. flock/core/api/service.py +0 -214
  328. flock/core/config/flock_agent_config.py +0 -11
  329. flock/core/config/scheduled_agent_config.py +0 -40
  330. flock/core/context/context.py +0 -214
  331. flock/core/context/context_manager.py +0 -40
  332. flock/core/context/context_vars.py +0 -11
  333. flock/core/evaluation/utils.py +0 -395
  334. flock/core/execution/batch_executor.py +0 -369
  335. flock/core/execution/evaluation_executor.py +0 -438
  336. flock/core/execution/local_executor.py +0 -31
  337. flock/core/execution/opik_executor.py +0 -103
  338. flock/core/execution/temporal_executor.py +0 -166
  339. flock/core/flock.py +0 -1003
  340. flock/core/flock_agent.py +0 -1258
  341. flock/core/flock_evaluator.py +0 -60
  342. flock/core/flock_factory.py +0 -513
  343. flock/core/flock_module.py +0 -207
  344. flock/core/flock_registry.py +0 -702
  345. flock/core/flock_router.py +0 -83
  346. flock/core/flock_scheduler.py +0 -166
  347. flock/core/flock_server_manager.py +0 -136
  348. flock/core/interpreter/python_interpreter.py +0 -689
  349. flock/core/logging/live_capture.py +0 -137
  350. flock/core/logging/trace_and_logged.py +0 -59
  351. flock/core/mcp/__init__.py +0 -1
  352. flock/core/mcp/flock_mcp_server.py +0 -640
  353. flock/core/mcp/mcp_client_manager.py +0 -201
  354. flock/core/mcp/types/__init__.py +0 -1
  355. flock/core/mixin/dspy_integration.py +0 -445
  356. flock/core/mixin/prompt_parser.py +0 -125
  357. flock/core/serialization/__init__.py +0 -13
  358. flock/core/serialization/callable_registry.py +0 -52
  359. flock/core/serialization/flock_serializer.py +0 -854
  360. flock/core/serialization/json_encoder.py +0 -41
  361. flock/core/serialization/secure_serializer.py +0 -175
  362. flock/core/serialization/serializable.py +0 -342
  363. flock/core/serialization/serialization_utils.py +0 -409
  364. flock/core/util/file_path_utils.py +0 -223
  365. flock/core/util/hydrator.py +0 -309
  366. flock/core/util/input_resolver.py +0 -141
  367. flock/core/util/loader.py +0 -59
  368. flock/core/util/splitter.py +0 -219
  369. flock/di.py +0 -41
  370. flock/evaluators/__init__.py +0 -1
  371. flock/evaluators/declarative/__init__.py +0 -1
  372. flock/evaluators/declarative/declarative_evaluator.py +0 -217
  373. flock/evaluators/memory/memory_evaluator.py +0 -90
  374. flock/evaluators/test/test_case_evaluator.py +0 -38
  375. flock/evaluators/zep/zep_evaluator.py +0 -59
  376. flock/modules/__init__.py +0 -1
  377. flock/modules/assertion/__init__.py +0 -1
  378. flock/modules/assertion/assertion_module.py +0 -286
  379. flock/modules/callback/__init__.py +0 -1
  380. flock/modules/callback/callback_module.py +0 -91
  381. flock/modules/enterprise_memory/README.md +0 -99
  382. flock/modules/enterprise_memory/enterprise_memory_module.py +0 -526
  383. flock/modules/mem0/__init__.py +0 -1
  384. flock/modules/mem0/mem0_module.py +0 -126
  385. flock/modules/mem0_async/__init__.py +0 -1
  386. flock/modules/mem0_async/async_mem0_module.py +0 -126
  387. flock/modules/memory/__init__.py +0 -1
  388. flock/modules/memory/memory_module.py +0 -429
  389. flock/modules/memory/memory_parser.py +0 -125
  390. flock/modules/memory/memory_storage.py +0 -736
  391. flock/modules/output/__init__.py +0 -1
  392. flock/modules/output/output_module.py +0 -196
  393. flock/modules/performance/__init__.py +0 -1
  394. flock/modules/performance/metrics_module.py +0 -678
  395. flock/modules/zep/__init__.py +0 -1
  396. flock/modules/zep/zep_module.py +0 -192
  397. flock/platform/docker_tools.py +0 -49
  398. flock/platform/jaeger_install.py +0 -86
  399. flock/routers/__init__.py +0 -1
  400. flock/routers/agent/__init__.py +0 -1
  401. flock/routers/agent/agent_router.py +0 -236
  402. flock/routers/agent/handoff_agent.py +0 -58
  403. flock/routers/conditional/conditional_router.py +0 -486
  404. flock/routers/default/__init__.py +0 -1
  405. flock/routers/default/default_router.py +0 -80
  406. flock/routers/feedback/feedback_router.py +0 -114
  407. flock/routers/list_generator/list_generator_router.py +0 -166
  408. flock/routers/llm/__init__.py +0 -1
  409. flock/routers/llm/llm_router.py +0 -365
  410. flock/tools/__init__.py +0 -0
  411. flock/tools/azure_tools.py +0 -781
  412. flock/tools/code_tools.py +0 -167
  413. flock/tools/file_tools.py +0 -149
  414. flock/tools/github_tools.py +0 -157
  415. flock/tools/markdown_tools.py +0 -205
  416. flock/tools/system_tools.py +0 -9
  417. flock/tools/text_tools.py +0 -810
  418. flock/tools/web_tools.py +0 -92
  419. flock/tools/zendesk_tools.py +0 -501
  420. flock/webapp/__init__.py +0 -1
  421. flock/webapp/app/__init__.py +0 -0
  422. flock/webapp/app/api/__init__.py +0 -0
  423. flock/webapp/app/api/agent_management.py +0 -237
  424. flock/webapp/app/api/execution.py +0 -503
  425. flock/webapp/app/api/flock_management.py +0 -125
  426. flock/webapp/app/api/registry_viewer.py +0 -29
  427. flock/webapp/app/chat.py +0 -662
  428. flock/webapp/app/config.py +0 -104
  429. flock/webapp/app/dependencies.py +0 -117
  430. flock/webapp/app/main.py +0 -1086
  431. flock/webapp/app/middleware.py +0 -113
  432. flock/webapp/app/models_ui.py +0 -7
  433. flock/webapp/app/services/__init__.py +0 -0
  434. flock/webapp/app/services/feedback_file_service.py +0 -363
  435. flock/webapp/app/services/flock_service.py +0 -345
  436. flock/webapp/app/services/sharing_models.py +0 -81
  437. flock/webapp/app/services/sharing_store.py +0 -597
  438. flock/webapp/app/templates/theme_mapper.html +0 -326
  439. flock/webapp/app/theme_mapper.py +0 -811
  440. flock/webapp/app/utils.py +0 -85
  441. flock/webapp/run.py +0 -219
  442. flock/webapp/static/css/chat.css +0 -301
  443. flock/webapp/static/css/components.css +0 -167
  444. flock/webapp/static/css/header.css +0 -39
  445. flock/webapp/static/css/layout.css +0 -281
  446. flock/webapp/static/css/sidebar.css +0 -127
  447. flock/webapp/static/css/two-pane.css +0 -48
  448. flock/webapp/templates/base.html +0 -389
  449. flock/webapp/templates/chat.html +0 -152
  450. flock/webapp/templates/chat_settings.html +0 -19
  451. flock/webapp/templates/flock_editor.html +0 -16
  452. flock/webapp/templates/index.html +0 -12
  453. flock/webapp/templates/partials/_agent_detail_form.html +0 -93
  454. flock/webapp/templates/partials/_agent_list.html +0 -18
  455. flock/webapp/templates/partials/_agent_manager_view.html +0 -51
  456. flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
  457. flock/webapp/templates/partials/_chat_container.html +0 -15
  458. flock/webapp/templates/partials/_chat_messages.html +0 -57
  459. flock/webapp/templates/partials/_chat_settings_form.html +0 -85
  460. flock/webapp/templates/partials/_create_flock_form.html +0 -50
  461. flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
  462. flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
  463. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
  464. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
  465. flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
  466. flock/webapp/templates/partials/_env_vars_table.html +0 -23
  467. flock/webapp/templates/partials/_execution_form.html +0 -127
  468. flock/webapp/templates/partials/_execution_view_container.html +0 -28
  469. flock/webapp/templates/partials/_flock_file_list.html +0 -23
  470. flock/webapp/templates/partials/_flock_properties_form.html +0 -52
  471. flock/webapp/templates/partials/_flock_upload_form.html +0 -16
  472. flock/webapp/templates/partials/_header_flock_status.html +0 -5
  473. flock/webapp/templates/partials/_live_logs.html +0 -13
  474. flock/webapp/templates/partials/_load_manager_view.html +0 -49
  475. flock/webapp/templates/partials/_registry_table.html +0 -25
  476. flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
  477. flock/webapp/templates/partials/_results_display.html +0 -78
  478. flock/webapp/templates/partials/_settings_env_content.html +0 -9
  479. flock/webapp/templates/partials/_settings_theme_content.html +0 -14
  480. flock/webapp/templates/partials/_settings_view.html +0 -36
  481. flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
  482. flock/webapp/templates/partials/_share_link_snippet.html +0 -35
  483. flock/webapp/templates/partials/_sidebar.html +0 -74
  484. flock/webapp/templates/partials/_structured_data_view.html +0 -40
  485. flock/webapp/templates/partials/_theme_preview.html +0 -36
  486. flock/webapp/templates/registry_viewer.html +0 -84
  487. flock/webapp/templates/shared_run_page.html +0 -140
  488. flock/workflow/__init__.py +0 -0
  489. flock/workflow/activities.py +0 -237
  490. flock/workflow/agent_activities.py +0 -24
  491. flock/workflow/agent_execution_activity.py +0 -240
  492. flock/workflow/flock_workflow.py +0 -225
  493. flock/workflow/temporal_config.py +0 -96
  494. flock/workflow/temporal_setup.py +0 -60
  495. flock_core-0.4.543.dist-info/METADATA +0 -676
  496. flock_core-0.4.543.dist-info/RECORD +0 -572
  497. flock_core-0.4.543.dist-info/entry_points.txt +0 -2
  498. /flock/{core/logging → logging}/formatters/themes.py +0 -0
  499. /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
  500. /flock/{core/mcp → mcp}/util/__init__.py +0 -0
  501. {flock_core-0.4.543.dist-info → flock_core-0.5.0.dist-info}/WHEEL +0 -0
flock/webapp/app/main.py DELETED
@@ -1,1086 +0,0 @@
1
- # src/flock/webapp/app/main.py
2
- import asyncio
3
- import json
4
- import os # Added import
5
- import shutil
6
-
7
- # Added for share link creation
8
- import uuid
9
- from contextlib import asynccontextmanager
10
- from datetime import datetime
11
- from pathlib import Path
12
- from typing import Any
13
-
14
- import markdown2 # Import markdown2
15
- from fastapi import (
16
- Depends,
17
- FastAPI,
18
- File,
19
- Form,
20
- HTTPException,
21
- Query,
22
- Request,
23
- UploadFile,
24
- )
25
- from fastapi.responses import HTMLResponse, RedirectResponse
26
- from fastapi.staticfiles import StaticFiles
27
- from fastapi.templating import Jinja2Templates
28
- from pydantic import BaseModel
29
-
30
- from flock.core.api.endpoints import create_api_router
31
- from flock.core.api.run_store import RunStore
32
-
33
- # Import core Flock components and API related modules
34
- from flock.core.flock import Flock # For type hinting
35
- from flock.core.flock_scheduler import FlockScheduler
36
- from flock.core.logging.live_capture import get_live_log_store
37
- from flock.core.logging.logging import get_logger # For logging
38
- from flock.core.util.splitter import parse_schema
39
-
40
- # Import UI-specific routers
41
- from flock.webapp.app.api import (
42
- agent_management,
43
- execution,
44
- flock_management,
45
- registry_viewer,
46
- )
47
- from flock.webapp.app.config import (
48
- DEFAULT_THEME_NAME,
49
- FLOCK_FILES_DIR,
50
- THEMES_DIR,
51
- get_current_theme_name,
52
- )
53
-
54
- # Import dependency management and config
55
- from flock.webapp.app.dependencies import (
56
- get_pending_custom_endpoints_and_clear,
57
- get_shared_link_store,
58
- set_global_flock_services,
59
- set_global_shared_link_store,
60
- )
61
-
62
- # Import service functions (which now expect app_state)
63
- from flock.webapp.app.middleware import ProxyHeadersMiddleware
64
- from flock.webapp.app.services.flock_service import (
65
- clear_current_flock_service,
66
- create_new_flock_service,
67
- get_available_flock_files,
68
- get_flock_preview_service,
69
- load_flock_from_file_service,
70
- # Note: get_current_flock_instance/filename are removed from service,
71
- # as main.py will use request.app.state for this.
72
- )
73
-
74
- # Added for share link creation
75
- from flock.webapp.app.services.sharing_models import SharedLinkConfig
76
- from flock.webapp.app.services.sharing_store import (
77
- SharedLinkStoreInterface,
78
- create_shared_link_store,
79
- )
80
- from flock.webapp.app.theme_mapper import alacritty_to_pico
81
-
82
- logger = get_logger("webapp.main")
83
-
84
-
85
- try:
86
- from flock.core.logging.formatters.themed_formatter import (
87
- load_theme_from_file,
88
- )
89
- THEME_LOADER_AVAILABLE = True
90
- except ImportError:
91
- logger.warning("Could not import flock.core theme loading utilities.")
92
- THEME_LOADER_AVAILABLE = False
93
-
94
- # --- .env helpers (copied from original main.py for self-containment) ---
95
- ENV_FILE_PATH = Path(".env") #Path(os.getenv("FLOCK_WEB_ENV_FILE", Path.home() / ".flock" / ".env"))
96
- #ENV_FILE_PATH.parent.mkdir(parents=True, exist_ok=True)
97
- SHOW_SECRETS_KEY = "SHOW_SECRETS"
98
-
99
- def load_env_file_web() -> dict[str, str]:
100
- env_vars: dict[str, str] = {}
101
- if not ENV_FILE_PATH.exists(): return env_vars
102
- with open(ENV_FILE_PATH) as f: lines = f.readlines()
103
- for line in lines:
104
- line = line.strip()
105
- if not line: env_vars[""] = ""; continue
106
- if line.startswith("#"): env_vars[line] = ""; continue
107
- if "=" in line: k, v = line.split("=", 1); env_vars[k] = v
108
- else: env_vars[line] = ""
109
- return env_vars
110
-
111
- def save_env_file_web(env_vars: dict[str, str]):
112
- try:
113
- with open(ENV_FILE_PATH, "w") as f:
114
- for k, v in env_vars.items():
115
- if k.startswith("#"): f.write(f"{k}\n")
116
- elif not k: f.write("\n")
117
- else: f.write(f"{k}={v}\n")
118
- except Exception as e: logger.error(f"[Settings] Failed to save .env: {e}")
119
-
120
- def is_sensitive_web(key: str) -> bool:
121
- patterns = ["key", "token", "secret", "password", "api", "pat"]; low = key.lower()
122
- return any(p in low for p in patterns)
123
-
124
- def mask_sensitive_value_web(value: str) -> str:
125
- if not value: return value
126
- if len(value) <= 4: return "••••"
127
- return value[:2] + "•" * (len(value) - 4) + value[-2:]
128
-
129
- def get_show_secrets_setting_web(env_vars: dict[str, str]) -> bool:
130
- return env_vars.get(SHOW_SECRETS_KEY, "false").lower() == "true"
131
-
132
- def set_show_secrets_setting_web(show: bool):
133
- env_vars = load_env_file_web()
134
- env_vars[SHOW_SECRETS_KEY] = str(show)
135
- save_env_file_web(env_vars)
136
- # --- End .env helpers ---
137
-
138
-
139
- @asynccontextmanager
140
- async def lifespan(app: FastAPI):
141
- logger.info("FastAPI application starting up...")
142
- # Flock instance and RunStore are expected to be set on app.state
143
- # by `start_unified_server` in `webapp/run.py` *before* uvicorn starts the app.
144
- # The call to `set_global_flock_services` also happens there. # Initialize and set the SharedLinkStore
145
- try:
146
- logger.info("Initializing SharedLinkStore using factory...")
147
- shared_link_store = create_shared_link_store()
148
- await shared_link_store.initialize() # Create tables if they don't exist
149
- set_global_shared_link_store(shared_link_store)
150
- logger.info("SharedLinkStore initialized and set globally.")
151
- except Exception as e:
152
- logger.error(f"Failed to initialize SharedLinkStore: {e}", exc_info=True)# Configure chat features with clear precedence:
153
- # 1. Value set by start_unified_server (programmatic)
154
- # 2. Environment variables (standalone mode)
155
- programmatic_chat_enabled = getattr(app.state, "chat_enabled", None)
156
- env_start_mode = os.environ.get("FLOCK_START_MODE")
157
- env_chat_enabled = os.environ.get("FLOCK_CHAT_ENABLED", "false").lower() == "true"
158
-
159
- if programmatic_chat_enabled is not None:
160
- # Programmatic setting takes precedence (from start_unified_server)
161
- should_enable_chat_routes = programmatic_chat_enabled
162
- logger.info(f"Using programmatic chat_enabled setting: {should_enable_chat_routes}")
163
- elif env_start_mode == "chat":
164
- should_enable_chat_routes = True
165
- app.state.initial_redirect_to_chat = True
166
- app.state.chat_enabled = True
167
- logger.info("FLOCK_START_MODE='chat'. Enabling chat routes and setting redirect.")
168
- elif env_chat_enabled:
169
- should_enable_chat_routes = True
170
- app.state.chat_enabled = True
171
- logger.info("FLOCK_CHAT_ENABLED='true'. Enabling chat routes.")
172
- else:
173
- should_enable_chat_routes = False
174
- app.state.chat_enabled = False
175
- logger.info("Chat routes disabled (no programmatic or environment setting).")
176
-
177
- if should_enable_chat_routes:
178
- try:
179
- from flock.webapp.app.chat import router as chat_router
180
- app.include_router(chat_router, tags=["Chat"])
181
- logger.info("Chat routes included in the application.")
182
- except Exception as e:
183
- logger.error(f"Failed to include chat routes during lifespan startup: {e}", exc_info=True) # If in standalone chat mode, strip non-essential UI routes
184
- if env_start_mode == "chat":
185
- from fastapi.routing import APIRoute
186
- logger.info("FLOCK_START_MODE='chat'. Stripping non-chat UI routes.")
187
-
188
- # Define tags for routes to KEEP.
189
- # "Chat" for primary chat functionality.
190
- # "Chat Sharing" for shared chat links & pages.
191
- # API tags might be needed if chat agents make internal API calls or for general health/docs.
192
- # Public static files (/static/...) are typically handled by app.mount and not in app.router.routes directly this way.
193
- allowed_tags_for_chat_mode = {
194
- "Chat",
195
- "Chat Sharing",
196
- "Flock API Core", # Keep core API for potential underlying needs
197
- "Flock API Custom Endpoints" # Keep custom API endpoints
198
- }
199
-
200
- def _route_is_allowed_in_chat_mode(route: APIRoute) -> bool:
201
- # Keep documentation (e.g. /docs, /openapi.json - usually no tags or specific tags)
202
- # and non-API utility routes (often no tags).
203
- if not hasattr(route, "tags") or not route.tags:
204
- # Check common doc paths explicitly as they might not have tags or might have default tags
205
- if route.path in ["/docs", "/openapi.json", "/redoc"]:
206
- return True
207
- # Allow other untagged routes for now, assuming they are essential (e.g. static mounts if they appeared here)
208
- # This might need refinement if untagged UI routes exist.
209
- return True
210
- return any(tag in allowed_tags_for_chat_mode for tag in route.tags)
211
-
212
- original_route_count = len(app.router.routes)
213
- app.router.routes = [r for r in app.router.routes if _route_is_allowed_in_chat_mode(r)]
214
- num_removed = original_route_count - len(app.router.routes)
215
- logger.info(f"Stripped {num_removed} routes for chat-only mode. {len(app.router.routes)} routes remaining.")
216
-
217
- if num_removed > 0 and hasattr(app, "openapi_schema"):
218
- app.openapi_schema = None # Clear cached OpenAPI schema to regenerate
219
- logger.info("Cleared OpenAPI schema cache due to route removal.")
220
-
221
- # Add custom routes if any were passed during server startup
222
- # These are retrieved from the dependency module where `start_unified_server` stored them.
223
- pending_endpoints = get_pending_custom_endpoints_and_clear()
224
- if pending_endpoints:
225
- flock_instance_from_state: Flock | None = getattr(app.state, "flock_instance", None)
226
- if flock_instance_from_state:
227
- from flock.core.api.main import (
228
- FlockAPI, # Local import for this specific task
229
- )
230
- # Create a temporary FlockAPI service object just for adding routes
231
- temp_flock_api_service = FlockAPI(
232
- flock_instance_from_state,
233
- custom_endpoints=pending_endpoints
234
- )
235
- temp_flock_api_service.add_custom_routes_to_app(app)
236
- logger.info(f"Lifespan: Added {len(pending_endpoints)} custom API routes to main app.")
237
- else:
238
- logger.warning("Lifespan: Pending custom endpoints found, but no Flock instance in app.state. Cannot add custom routes.")
239
-
240
- # --- Add Scheduler Startup Logic ---
241
- flock_instance_from_state: Flock | None = getattr(app.state, "flock_instance", None)
242
- if flock_instance_from_state:
243
- # Create and start the scheduler
244
- scheduler = FlockScheduler(flock_instance_from_state)
245
- app.state.flock_scheduler = scheduler # Store for access during shutdown
246
-
247
- scheduler_loop_task = await scheduler.start() # Start returns the task
248
- if scheduler_loop_task:
249
- app.state.flock_scheduler_task = scheduler_loop_task # Store the task
250
- logger.info("FlockScheduler background task started.")
251
- else:
252
- app.state.flock_scheduler_task = None
253
- logger.info("FlockScheduler initialized, but no scheduled agents found or loop not started.")
254
- else:
255
- app.state.flock_scheduler = None
256
- app.state.flock_scheduler_task = None
257
- logger.warning("No Flock instance found in app.state; FlockScheduler not started.")
258
- # --- End Scheduler Startup Logic ---
259
-
260
- yield
261
- logger.info("FastAPI application shutting down...")
262
-
263
- # --- Add Scheduler Shutdown Logic ---
264
- logger.info("FastAPI application initiating shutdown...")
265
- scheduler_to_stop: FlockScheduler | None = getattr(app.state, "flock_scheduler", None)
266
- scheduler_task_to_await: asyncio.Task | None = getattr(app.state, "flock_scheduler_task", None)
267
-
268
- if scheduler_to_stop:
269
- logger.info("Attempting to stop FlockScheduler...")
270
- await scheduler_to_stop.stop() # Signal the scheduler loop to stop
271
-
272
- if scheduler_task_to_await and not scheduler_task_to_await.done():
273
- logger.info("Waiting for FlockScheduler task to complete...")
274
- try:
275
- await asyncio.wait_for(scheduler_task_to_await, timeout=10.0) # Wait for graceful exit
276
- logger.info("FlockScheduler task completed gracefully.")
277
- except asyncio.TimeoutError:
278
- logger.warning("FlockScheduler task did not complete in time, cancelling.")
279
- scheduler_task_to_await.cancel()
280
- try:
281
- await scheduler_task_to_await # Await cancellation
282
- except asyncio.CancelledError:
283
- logger.info("FlockScheduler task cancelled.")
284
- except Exception as e:
285
- logger.error(f"Error during FlockScheduler task finalization: {e}", exc_info=True)
286
- elif scheduler_task_to_await and scheduler_task_to_await.done():
287
- logger.info("FlockScheduler task was already done.")
288
- else:
289
- logger.info("FlockScheduler instance found, but no running task was stored to await.")
290
- else:
291
- logger.info("No active FlockScheduler found to stop.")
292
-
293
- logger.info("FastAPI application finished shutdown sequence.")
294
- # --- End Scheduler Shutdown Logic ---
295
-
296
- app = FastAPI(title="Flock Web UI & API", lifespan=lifespan, docs_url="/docs",
297
- openapi_url="/openapi.json", root_path=os.getenv("FLOCK_ROOT_PATH", ""))
298
-
299
- # Add middleware for handling proxy headers (HTTPS detection)
300
- # You can force HTTPS by setting FLOCK_FORCE_HTTPS=true
301
- force_https = os.getenv("FLOCK_FORCE_HTTPS", "false").lower() == "true"
302
- app.add_middleware(ProxyHeadersMiddleware, force_https=force_https)
303
- logger.info(f"FastAPI booting complete with proxy headers middleware (force_https={force_https}).")
304
-
305
- BASE_DIR = Path(__file__).resolve().parent.parent
306
- app.mount("/static", StaticFiles(directory=str(BASE_DIR / "static")), name="static")
307
- templates = Jinja2Templates(directory=str(BASE_DIR / "templates"))
308
-
309
- # Add markdown2 filter to Jinja2 environment
310
- def markdown_filter(text):
311
- return markdown2.markdown(text, extras=["tables", "fenced-code-blocks"])
312
-
313
- templates.env.filters['markdown'] = markdown_filter
314
-
315
- core_api_router = create_api_router()
316
- app.include_router(core_api_router, prefix="/api", tags=["Flock API Core"])
317
- app.include_router(flock_management.router, prefix="/ui/api/flock", tags=["UI Flock Management"])
318
- app.include_router(agent_management.router, prefix="/ui/api/flock", tags=["UI Agent Management"])
319
- app.include_router(execution.router, prefix="/ui/api/flock", tags=["UI Execution"])
320
- app.include_router(registry_viewer.router, prefix="/ui/api/registry", tags=["UI Registry"])
321
-
322
- # --- Share Link API Models and Endpoint ---
323
- class CreateShareLinkRequest(BaseModel):
324
- agent_name: str
325
-
326
- class CreateShareLinkResponse(BaseModel):
327
- share_url: str
328
-
329
- @app.post("/api/v1/share/link", response_model=CreateShareLinkResponse, tags=["UI Sharing"])
330
- async def create_share_link(
331
- request: Request,
332
- request_data: CreateShareLinkRequest,
333
- store: SharedLinkStoreInterface = Depends(get_shared_link_store)
334
- ):
335
- """Creates a new shareable link for an agent."""
336
- share_id = uuid.uuid4().hex
337
- agent_name = request_data.agent_name
338
-
339
- if not agent_name: # Basic validation
340
- raise HTTPException(status_code=400, detail="Agent name cannot be empty.")
341
-
342
- current_flock_instance: Flock | None = getattr(request.app.state, "flock_instance", None)
343
- current_flock_filename: str | None = getattr(request.app.state, "flock_filename", None)
344
-
345
- if not current_flock_instance or not current_flock_filename:
346
- logger.error("Cannot create share link: No Flock is currently loaded in the application state.")
347
- raise HTTPException(status_code=400, detail="No Flock loaded. Cannot create share link.")
348
-
349
- if agent_name not in current_flock_instance.agents:
350
- logger.error(f"Agent '{agent_name}' not found in currently loaded Flock '{current_flock_instance.name}'.")
351
- raise HTTPException(status_code=404, detail=f"Agent '{agent_name}' not found in current Flock.")
352
-
353
- try:
354
- flock_file_path = FLOCK_FILES_DIR / current_flock_filename
355
- if not flock_file_path.is_file():
356
- logger.warning(f"Flock file {current_flock_filename} not found at {flock_file_path} for sharing. Using in-memory definition.")
357
- flock_definition_str = current_flock_instance.to_yaml()
358
- else:
359
- flock_definition_str = flock_file_path.read_text()
360
- except Exception as e:
361
- logger.error(f"Failed to get flock definition for sharing: {e}", exc_info=True)
362
- raise HTTPException(status_code=500, detail="Could not retrieve Flock definition for sharing.")
363
-
364
- config = SharedLinkConfig(
365
- share_id=share_id,
366
- agent_name=agent_name,
367
- flock_definition=flock_definition_str
368
- )
369
- try:
370
- await store.save_config(config)
371
- share_url = f"/ui/shared-run/{share_id}" # Relative URL for client-side navigation
372
- logger.info(f"Created share link for agent '{agent_name}' in Flock '{current_flock_instance.name}' with ID '{share_id}'. URL: {share_url}")
373
- return CreateShareLinkResponse(share_url=share_url)
374
- except Exception as e:
375
- logger.error(f"Failed to create share link for agent '{agent_name}': {e}", exc_info=True)
376
- raise HTTPException(status_code=500, detail=f"Failed to create share link: {e!s}")
377
-
378
- # --- End Share Link API ---
379
-
380
- # --- HTMX Endpoint for Generating Share Link Snippet ---
381
- @app.post("/ui/htmx/share/generate-link", response_class=HTMLResponse, tags=["UI Sharing HTMX"])
382
- async def htmx_generate_share_link(
383
- request: Request,
384
- start_agent_name: str | None = Form(None),
385
- store: SharedLinkStoreInterface = Depends(get_shared_link_store)
386
- ):
387
- if not start_agent_name:
388
- logger.warning("HTMX generate share link: Agent name not provided.")
389
- return templates.TemplateResponse(request, "partials/_share_link_snippet.html",
390
- {"request": request, "error_message": "No agent selected to share."}
391
- )
392
-
393
- current_flock_instance: Flock | None = getattr(request.app.state, "flock_instance", None)
394
- current_flock_filename: str | None = getattr(request.app.state, "flock_filename", None)
395
-
396
- if not current_flock_instance or not current_flock_filename:
397
- logger.error("HTMX: Cannot create share link: No Flock is currently loaded.")
398
- return templates.TemplateResponse(request, "partials/_share_link_snippet.html",
399
- {"request": request, "error_message": "No Flock loaded. Cannot create share link."}
400
- )
401
-
402
- if start_agent_name not in current_flock_instance.agents:
403
- logger.error(f"HTMX: Agent '{start_agent_name}' not found in Flock '{current_flock_instance.name}'.")
404
- return templates.TemplateResponse(request, "partials/_share_link_snippet.html",
405
- {"request": request, "error_message": f"Agent '{start_agent_name}' not found in current Flock."}
406
- )
407
-
408
- try:
409
- flock_file_path = FLOCK_FILES_DIR / current_flock_filename
410
- if not flock_file_path.is_file():
411
- logger.warning(f"HTMX: Flock file {current_flock_filename} not found at {flock_file_path} for sharing. Using in-memory definition.")
412
- flock_definition_str = current_flock_instance.to_yaml()
413
- else:
414
- flock_definition_str = flock_file_path.read_text()
415
- except Exception as e:
416
- logger.error(f"HTMX: Failed to get flock definition for sharing: {e}", exc_info=True)
417
- return templates.TemplateResponse(request, "partials/_share_link_snippet.html",
418
- {"request": request, "error_message": "Could not retrieve Flock definition for sharing."}
419
- )
420
-
421
- share_id = uuid.uuid4().hex
422
- config = SharedLinkConfig(
423
- share_id=share_id,
424
- agent_name=start_agent_name,
425
- flock_definition=flock_definition_str
426
- )
427
-
428
- try:
429
- await store.save_config(config)
430
- base_url = str(request.base_url)
431
- full_share_url = f"{base_url.rstrip('/')}/ui/shared-run/{share_id}"
432
-
433
- logger.info(f"HTMX: Generated share link for agent '{start_agent_name}' in Flock '{current_flock_instance.name}' with ID '{share_id}'. URL: {full_share_url}")
434
- return templates.TemplateResponse(request, "partials/_share_link_snippet.html",
435
- {"request": request, "share_url": full_share_url, "flock_name": current_flock_instance.name, "agent_name": start_agent_name}
436
- )
437
- except Exception as e:
438
- logger.error(f"HTMX: Failed to create share link for agent '{start_agent_name}': {e}", exc_info=True)
439
- return templates.TemplateResponse(request, "partials/_share_link_snippet.html",
440
- {"request": request, "error_message": f"Could not generate link: {e!s}"}
441
- )
442
- # --- End HTMX Endpoint ---
443
-
444
- # --- HTMX Endpoint for Generating SHARED CHAT Link Snippet ---
445
- @app.post("/ui/htmx/share/chat/generate-link", response_class=HTMLResponse, tags=["UI Sharing HTMX"])
446
- async def htmx_generate_share_chat_link(
447
- request: Request,
448
- agent_name: str | None = Form(None), # This is the chat agent
449
- message_key: str | None = Form(None), # Changed default to None
450
- history_key: str | None = Form(None), # Changed default to None
451
- response_key: str | None = Form(None), # Changed default to None
452
- store: SharedLinkStoreInterface = Depends(get_shared_link_store)
453
- ):
454
- if not agent_name:
455
- logger.warning("HTMX generate share chat link: Agent name not provided.")
456
- return templates.TemplateResponse(request, "partials/_share_chat_link_snippet.html", # Will create this template
457
- {"request": request, "error_message": "No agent selected for chat sharing."}
458
- )
459
-
460
- current_flock_instance: Flock | None = getattr(request.app.state, "flock_instance", None)
461
- current_flock_filename: str | None = getattr(request.app.state, "flock_filename", None)
462
-
463
- if not current_flock_instance or not current_flock_filename:
464
- logger.error("HTMX Chat Share: Cannot create share link: No Flock is currently loaded.")
465
- return templates.TemplateResponse(request, "partials/_share_chat_link_snippet.html",
466
- {"request": request, "error_message": "No Flock loaded. Cannot create share link."}
467
- )
468
-
469
- if agent_name not in current_flock_instance.agents:
470
- logger.error(f"HTMX Chat Share: Agent '{agent_name}' not found in Flock '{current_flock_instance.name}'.")
471
- return templates.TemplateResponse(request, "partials/_share_chat_link_snippet.html",
472
- {"request": request, "error_message": f"Agent '{agent_name}' not found in current Flock."}
473
- )
474
-
475
- try:
476
- flock_file_path = FLOCK_FILES_DIR / current_flock_filename
477
- if not flock_file_path.is_file():
478
- logger.warning(f"HTMX Chat Share: Flock file {current_flock_filename} not found at {flock_file_path} for sharing. Using in-memory definition.")
479
- flock_definition_str = current_flock_instance.to_yaml()
480
- else:
481
- flock_definition_str = flock_file_path.read_text()
482
- except Exception as e:
483
- logger.error(f"HTMX Chat Share: Failed to get flock definition for sharing: {e}", exc_info=True)
484
- return templates.TemplateResponse(request, "partials/_share_chat_link_snippet.html",
485
- {"request": request, "error_message": "Could not retrieve Flock definition for sharing."}
486
- )
487
-
488
- share_id = uuid.uuid4().hex
489
-
490
- # Explicitly convert empty strings from form to None for optional keys
491
- actual_message_key = message_key if message_key else None
492
- actual_history_key = history_key if history_key else None
493
- actual_response_key = response_key if response_key else None
494
-
495
- config = SharedLinkConfig(
496
- share_id=share_id,
497
- agent_name=agent_name, # agent_name from form is the chat agent
498
- flock_definition=flock_definition_str,
499
- share_type="chat",
500
- chat_message_key=actual_message_key,
501
- chat_history_key=actual_history_key,
502
- chat_response_key=actual_response_key
503
- )
504
-
505
- try:
506
- await store.save_config(config)
507
- base_url = str(request.base_url)
508
- # Link to the new /chat/shared/{share_id} endpoint
509
- full_share_url = f"{base_url.rstrip('/')}/chat/shared/{share_id}"
510
-
511
- logger.info(f"HTMX: Generated share CHAT link for agent '{agent_name}' in Flock '{current_flock_instance.name}' with ID '{share_id}'. URL: {full_share_url}")
512
- return templates.TemplateResponse(request, "partials/_share_chat_link_snippet.html", # Will create this template
513
- {"request": request, "share_url": full_share_url, "flock_name": current_flock_instance.name, "agent_name": agent_name}
514
- )
515
- except Exception as e:
516
- logger.error(f"HTMX Chat Share: Failed to create share link for agent '{agent_name}': {e}", exc_info=True)
517
- return templates.TemplateResponse(request, "partials/_share_chat_link_snippet.html",
518
- {"request": request, "error_message": f"Could not generate chat link: {e!s}"}
519
- )
520
-
521
- # --- Route for Shared Run Page ---
522
- @app.get("/ui/shared-run/{share_id}", response_class=HTMLResponse, tags=["UI Sharing"])
523
- async def page_shared_run(
524
- request: Request,
525
- share_id: str,
526
- store: SharedLinkStoreInterface = Depends(get_shared_link_store),
527
- ):
528
- logger.info(f"Accessed shared run page with share_id: {share_id}")
529
- shared_config = await store.get_config(share_id)
530
-
531
- if not shared_config:
532
- logger.warning(f"Share ID {share_id} not found.")
533
- return templates.TemplateResponse(request, "error_page.html",
534
- {"request": request, "error_title": "Link Not Found", "error_message": "The shared link does not exist or may have expired."},
535
- status_code=404
536
- )
537
-
538
- agent_name_from_link = shared_config.agent_name
539
- flock_definition_str = shared_config.flock_definition
540
- context: dict[str, Any] = {"request": request, "is_shared_run_page": True, "share_id": share_id}
541
-
542
- try:
543
- from flock.core.flock import Flock as ConcreteFlock
544
- loaded_flock = ConcreteFlock.from_yaml(flock_definition_str)
545
-
546
- # Store the loaded_flock instance in app.state for later retrieval
547
- if not hasattr(request.app.state, 'shared_flocks'):
548
- request.app.state.shared_flocks = {}
549
- request.app.state.shared_flocks[share_id] = loaded_flock
550
- logger.info(f"Shared Run Page: Stored Flock instance for share_id {share_id} in app.state.")
551
-
552
- context["flock"] = loaded_flock
553
- context["selected_agent_name"] = agent_name_from_link # For pre-selection & hidden field
554
- # flock_definition_str is no longer needed in the template for a hidden field if we reuse the instance
555
- # context["flock_definition_str"] = flock_definition_str
556
- logger.info(f"Shared Run Page: Loaded Flock '{loaded_flock.name}' for agent '{agent_name_from_link}'.")
557
-
558
- if agent_name_from_link not in loaded_flock.agents:
559
- context["error_message"] = f"Agent '{agent_name_from_link}' not found in the shared Flock definition."
560
- logger.warning(context["error_message"])
561
- else:
562
- agent = loaded_flock.agents[agent_name_from_link]
563
- input_fields = []
564
- if agent.input and isinstance(agent.input, str):
565
- try:
566
- parsed_spec = parse_schema(agent.input) # parse_schema is imported at top of main.py
567
- for name, type_str, description in parsed_spec:
568
- field_info = {"name": name, "type": type_str.lower(), "description": description or ""}
569
- if "bool" in field_info["type"]: field_info["html_type"] = "checkbox"
570
- elif "int" in field_info["type"] or "float" in field_info["type"]: field_info["html_type"] = "number"
571
- elif "list" in field_info["type"] or "dict" in field_info["type"]:
572
- field_info["html_type"] = "textarea"; field_info["placeholder"] = f"Enter JSON for {field_info['type']}"
573
- else: field_info["html_type"] = "text"
574
- input_fields.append(field_info)
575
- context["input_fields"] = input_fields
576
- except Exception as e_parse:
577
- logger.error(f"Shared Run Page: Error parsing input for '{agent_name_from_link}': {e_parse}", exc_info=True)
578
- context["error_message"] = f"Could not parse inputs for agent '{agent_name_from_link}'."
579
- else:
580
- context["input_fields"] = [] # Agent has no inputs defined
581
-
582
- except Exception as e_load:
583
- logger.error(f"Shared Run Page: Failed to load Flock from definition for share_id {share_id}: {e_load}", exc_info=True)
584
- context["error_message"] = f"Fatal: Could not load the shared Flock configuration: {e_load!s}"
585
- context["flock"] = None
586
- context["selected_agent_name"] = agent_name_from_link # Still pass for potential error display
587
- context["input_fields"] = []
588
- # context["flock_definition_str"] = flock_definition_str # Not needed if not sent to template
589
-
590
- try:
591
- current_theme_name = get_current_theme_name()
592
- context["theme_css"] = generate_theme_css_web(current_theme_name)
593
- context["active_theme_name"] = current_theme_name or DEFAULT_THEME_NAME
594
- except Exception as e_theme:
595
- logger.error(f"Shared Run Page: Error generating theme: {e_theme}", exc_info=True)
596
- context["theme_css"] = ""
597
- context["active_theme_name"] = DEFAULT_THEME_NAME
598
-
599
- # The shared_run_page.html will now be a simple wrapper that includes _execution_form.html
600
- return templates.TemplateResponse(request, "shared_run_page.html", context)
601
-
602
- # --- End Route for Shared Run Page ---
603
-
604
- def generate_theme_css_web(theme_name: str | None) -> str:
605
- if not THEME_LOADER_AVAILABLE or THEMES_DIR is None: return ""
606
-
607
- chosen_theme_name_input = theme_name or get_current_theme_name() or DEFAULT_THEME_NAME
608
-
609
- # Sanitize the input to get only the filename component
610
- sanitized_name_part = Path(chosen_theme_name_input).name
611
- # Ensure we have a stem
612
- theme_stem_candidate = sanitized_name_part
613
- if theme_stem_candidate.endswith(".toml"):
614
- theme_stem_candidate = theme_stem_candidate[:-5]
615
-
616
- effective_theme_filename = f"{theme_stem_candidate}.toml"
617
- _theme_to_load_stem = theme_stem_candidate # This will be the name of the theme we attempt to load
618
-
619
- try:
620
- resolved_themes_dir = THEMES_DIR.resolve(strict=True) # Ensure THEMES_DIR itself is valid
621
- prospective_theme_path = resolved_themes_dir / effective_theme_filename
622
-
623
- # Resolve the prospective path
624
- resolved_theme_path = prospective_theme_path.resolve()
625
-
626
- # Validate:
627
- # 1. Path is still within the resolved THEMES_DIR
628
- # 2. The final filename component of the resolved path matches the intended filename
629
- # (guards against symlinks or normalization changing the name unexpectedly)
630
- # 3. The file exists
631
- if (
632
- str(resolved_theme_path).startswith(str(resolved_themes_dir)) and
633
- resolved_theme_path.name == effective_theme_filename and
634
- resolved_theme_path.is_file() # is_file checks existence too
635
- ):
636
- theme_path = resolved_theme_path
637
- else:
638
- logger.warning(
639
- f"Validation failed or theme '{effective_theme_filename}' not found in '{resolved_themes_dir}'. "
640
- f"Attempted path: '{prospective_theme_path}'. Resolved to: '{resolved_theme_path}'. "
641
- f"Falling back to default theme: {DEFAULT_THEME_NAME}.toml"
642
- )
643
- _theme_to_load_stem = DEFAULT_THEME_NAME
644
- theme_path = resolved_themes_dir / f"{DEFAULT_THEME_NAME}.toml"
645
- if not theme_path.is_file():
646
- logger.error(f"Default theme file '{theme_path}' not found. No theme CSS will be generated.")
647
- return ""
648
- except FileNotFoundError: # THEMES_DIR does not exist
649
- logger.error(f"Themes directory '{THEMES_DIR}' not found. Falling back to default theme.")
650
- _theme_to_load_stem = DEFAULT_THEME_NAME
651
- # Attempt to use a conceptual default path if THEMES_DIR was bogus, though it's unlikely to succeed
652
- theme_path = Path(f"{DEFAULT_THEME_NAME}.toml") # This won't be in THEMES_DIR if THEMES_DIR is bad
653
- if not theme_path.exists(): # Check existence without assuming a base directory
654
- logger.error(f"Default theme file '{DEFAULT_THEME_NAME}.toml' not found at root or THEMES_DIR is inaccessible. No theme CSS.")
655
- return ""
656
- except Exception as e:
657
- logger.error(f"Error during theme path resolution for '{effective_theme_filename}': {e}. Falling back to default.")
658
- _theme_to_load_stem = DEFAULT_THEME_NAME
659
- theme_path = THEMES_DIR / f"{DEFAULT_THEME_NAME}.toml" if THEMES_DIR else Path(f"{DEFAULT_THEME_NAME}.toml")
660
- if not theme_path.exists():
661
- logger.error(f"Default theme file '{theme_path}' not found after error. No theme CSS.")
662
- return ""
663
-
664
- try:
665
- theme_dict = load_theme_from_file(str(theme_path))
666
- logger.debug(f"Successfully loaded theme '{_theme_to_load_stem}' from '{theme_path}'")
667
- except Exception as e:
668
- logger.error(f"Error loading theme file '{theme_path}' (intended: '{_theme_to_load_stem}.toml'): {e}")
669
- return ""
670
-
671
- pico_vars = alacritty_to_pico(theme_dict)
672
- if not pico_vars: return ""
673
- css_rules = [f" {name}: {value};" for name, value in pico_vars.items()]
674
- css_string = ":root {\n" + "\n".join(css_rules) + "\n}"
675
- return css_string
676
-
677
- def get_base_context_web(
678
- request: Request, error: str = None, success: str = None, ui_mode: str = "standalone"
679
- ) -> dict:
680
- flock_instance_from_state: Flock | None = getattr(request.app.state, "flock_instance", None)
681
- current_flock_filename_from_state: str | None = getattr(request.app.state, "flock_filename", None)
682
- theme_name = get_current_theme_name()
683
- theme_css = generate_theme_css_web(theme_name)
684
-
685
- return {
686
- "request": request,
687
- "current_flock": flock_instance_from_state,
688
- "current_filename": current_flock_filename_from_state,
689
- "error_message": error,
690
- "success_message": success,
691
- "ui_mode": ui_mode,
692
- "theme_css": theme_css,
693
- "active_theme_name": theme_name,
694
- "chat_enabled": getattr(request.app.state, "chat_enabled", False), # Reverted to app.state
695
- }
696
-
697
- @app.get("/", response_class=HTMLResponse, tags=["UI Pages"])
698
- async def page_dashboard(
699
- request: Request, error: str = None, success: str = None, ui_mode: str = Query(None)
700
- ):
701
- # Handle initial redirect if flagged during app startup
702
- if getattr(request.app.state, "initial_redirect_to_chat", False):
703
- logger.info("Initial redirect to CHAT page triggered from dashboard (FLOCK_START_MODE='chat').")
704
- # Use url_for to respect the root_path setting
705
- chat_url = str(request.url_for("page_chat"))
706
- return RedirectResponse(url=chat_url, status_code=307)
707
-
708
- effective_ui_mode = ui_mode
709
- flock_is_preloaded = hasattr(request.app.state, "flock_instance") and request.app.state.flock_instance is not None
710
-
711
- if effective_ui_mode is None:
712
- effective_ui_mode = "scoped" if flock_is_preloaded else "standalone"
713
- if effective_ui_mode == "scoped":
714
- # Manually construct URL with root_path to ensure it works with proxy setups
715
- root_path = request.scope.get("root_path", "")
716
- redirect_url = f"{root_path}/?ui_mode=scoped&initial_load=true"
717
- logger.info(f"Dashboard redirect: {redirect_url} (root_path: '{root_path}')")
718
- return RedirectResponse(url=redirect_url, status_code=307)
719
-
720
- if effective_ui_mode == "standalone" and flock_is_preloaded:
721
- clear_current_flock_service(request.app.state) # Pass app.state
722
- logger.info("Switched to standalone mode, cleared preloaded Flock instance from app.state.")
723
-
724
- context = get_base_context_web(request, error, success, effective_ui_mode)
725
- flock_in_state = hasattr(request.app.state, "flock_instance") and request.app.state.flock_instance is not None
726
-
727
- if effective_ui_mode == "scoped":
728
- context["initial_content_url"] = str(request.url_for("htmx_get_execution_view_container")) if flock_in_state else str(request.url_for("htmx_scoped_no_flock_view"))
729
- else:
730
- context["initial_content_url"] = str(request.url_for("htmx_get_load_flock_view"))
731
- return templates.TemplateResponse(request, "base.html", context)
732
-
733
- @app.get("/ui/editor/{section:path}", response_class=HTMLResponse, tags=["UI Pages"])
734
- async def page_editor_section(
735
- request: Request, section: str, success: str = None, error: str = None, ui_mode: str = Query("standalone")
736
- ):
737
- flock_instance_from_state: Flock | None = getattr(request.app.state, "flock_instance", None)
738
- if not flock_instance_from_state:
739
- err_msg = "No flock loaded. Please load or create a flock first."
740
- # Use url_for to respect the root_path setting
741
- redirect_url = str(request.url_for("page_dashboard").include_query_params(error=err_msg))
742
- if ui_mode == "scoped":
743
- redirect_url = str(request.url_for("page_dashboard").include_query_params(error=err_msg, ui_mode="scoped"))
744
- return RedirectResponse(url=redirect_url, status_code=303)
745
-
746
- context = get_base_context_web(request, error, success, ui_mode)
747
- root_path = request.scope.get("root_path", "")
748
- content_map = {
749
- "properties": f"{root_path}/ui/api/flock/htmx/flock-properties-form",
750
- "agents": f"{root_path}/ui/htmx/agent-manager-view",
751
- "execute": f"{root_path}/ui/htmx/execution-view-container"
752
- }
753
- context["initial_content_url"] = content_map.get(section, f"{root_path}/ui/htmx/load-flock-view")
754
- if section not in content_map: context["error_message"] = "Invalid editor section."
755
- return templates.TemplateResponse(request, "base.html", context)
756
-
757
- @app.get("/ui/registry", response_class=HTMLResponse, tags=["UI Pages"])
758
- async def page_registry(request: Request, error: str = None, success: str = None, ui_mode: str = Query("standalone")):
759
- context = get_base_context_web(request, error, success, ui_mode)
760
- root_path = request.scope.get("root_path", "")
761
- context["initial_content_url"] = f"{root_path}/ui/htmx/registry-viewer"
762
- return templates.TemplateResponse(request, "base.html", context)
763
-
764
- @app.get("/ui/create", response_class=HTMLResponse, tags=["UI Pages"])
765
- async def page_create(request: Request, error: str = None, success: str = None, ui_mode: str = Query("standalone")):
766
- clear_current_flock_service(request.app.state) # Pass app.state
767
- context = get_base_context_web(request, error, success, "standalone")
768
- root_path = request.scope.get("root_path", "")
769
- context["initial_content_url"] = f"{root_path}/ui/htmx/create-flock-form"
770
- return templates.TemplateResponse(request, "base.html", context)
771
-
772
- @app.get("/ui/htmx/sidebar", response_class=HTMLResponse, tags=["UI HTMX Partials"])
773
- async def htmx_get_sidebar(request: Request, ui_mode: str = Query("standalone")):
774
- return templates.TemplateResponse(request, "partials/_sidebar.html", get_base_context_web(request, ui_mode=ui_mode))
775
-
776
- @app.get("/ui/htmx/header-flock-status", response_class=HTMLResponse, tags=["UI HTMX Partials"])
777
- async def htmx_get_header_flock_status(request: Request, ui_mode: str = Query("standalone")):
778
- return templates.TemplateResponse(request, "partials/_header_flock_status.html", get_base_context_web(request, ui_mode=ui_mode))
779
-
780
- @app.get("/ui/htmx/load-flock-view", response_class=HTMLResponse, tags=["UI HTMX Partials"])
781
- async def htmx_get_load_flock_view(request: Request, error: str = None, success: str = None, ui_mode: str = Query("standalone")):
782
- return templates.TemplateResponse(request, "partials/_load_manager_view.html", get_base_context_web(request, error, success, ui_mode))
783
-
784
- @app.get("/ui/htmx/dashboard-flock-file-list", response_class=HTMLResponse, tags=["UI HTMX Partials"])
785
- async def htmx_get_dashboard_flock_file_list_partial(request: Request):
786
- return templates.TemplateResponse(request, "partials/_dashboard_flock_file_list.html", {"request": request, "flock_files": get_available_flock_files()})
787
-
788
- @app.get("/ui/htmx/dashboard-default-action-pane", response_class=HTMLResponse, tags=["UI HTMX Partials"])
789
- async def htmx_get_dashboard_default_action_pane(request: Request):
790
- return HTMLResponse("""<article style="text-align:center; margin-top: 2rem; border: none; background: transparent;"><p>Select a Flock from the list to view its details and load it into the editor.</p><hr><p>Or, create a new Flock or upload an existing one using the "Create New Flock" option in the sidebar.</p></article>""")
791
-
792
- @app.get("/ui/htmx/dashboard-flock-properties-preview/{filename}", response_class=HTMLResponse, tags=["UI HTMX Partials"])
793
- async def htmx_get_dashboard_flock_properties_preview(request: Request, filename: str):
794
- preview_flock_data = get_flock_preview_service(filename)
795
- return templates.TemplateResponse(request, "partials/_dashboard_flock_properties_preview.html", {"request": request, "selected_filename": filename, "preview_flock": preview_flock_data})
796
-
797
- @app.get("/ui/htmx/create-flock-form", response_class=HTMLResponse, tags=["UI HTMX Partials"])
798
- async def htmx_get_create_flock_form(request: Request, error: str = None, success: str = None, ui_mode: str = Query("standalone")):
799
- return templates.TemplateResponse(request, "partials/_create_flock_form.html", get_base_context_web(request, error, success, ui_mode))
800
-
801
- @app.get("/ui/htmx/agent-manager-view", response_class=HTMLResponse, tags=["UI HTMX Partials"])
802
- async def htmx_get_agent_manager_view(request: Request):
803
- context = get_base_context_web(request) # This gets flock from app.state
804
- if not context.get("current_flock"): # Check if flock exists in the context
805
- return HTMLResponse("<article class='error'><p>No flock loaded. Cannot manage agents.</p></article>")
806
- # Pass the 'current_flock' from the context to the template as 'flock'
807
- return templates.TemplateResponse(request, "partials/_agent_manager_view.html",
808
- {"request": request, "flock": context.get("current_flock")}
809
- )
810
-
811
- @app.get("/ui/htmx/registry-viewer", response_class=HTMLResponse, tags=["UI HTMX Partials"])
812
- async def htmx_get_registry_viewer(request: Request):
813
- return templates.TemplateResponse(request, "partials/_registry_viewer_content.html", get_base_context_web(request))
814
-
815
- @app.get("/ui/htmx/execution-view-container", response_class=HTMLResponse, tags=["UI HTMX Partials"])
816
- async def htmx_get_execution_view_container(request: Request):
817
- context = get_base_context_web(request)
818
- if not context.get("current_flock"): return HTMLResponse("<article class='error'><p>No Flock loaded. Cannot execute.</p></article>")
819
- return templates.TemplateResponse(request, "partials/_execution_view_container.html", context)
820
-
821
- @app.get("/ui/htmx/scoped-no-flock-view", response_class=HTMLResponse, tags=["UI HTMX Partials"])
822
- async def htmx_scoped_no_flock_view(request: Request):
823
- return HTMLResponse("""<article style="text-align:center; margin-top: 2rem; border: none; background: transparent;"><hgroup><h2>Scoped Flock Mode</h2><h3>No Flock Loaded</h3></hgroup><p>This UI is in a scoped mode, expecting a Flock to be pre-loaded.</p><p>Please ensure the calling application provides a Flock instance.</p></article>""")
824
-
825
- # --- Action Routes (POST requests for UI interactions) ---
826
- @app.post("/ui/load-flock-action/by-name", response_class=HTMLResponse, tags=["UI Actions"])
827
- async def ui_load_flock_by_name_action(request: Request, selected_flock_filename: str = Form(...)):
828
- loaded_flock = load_flock_from_file_service(selected_flock_filename, request.app.state)
829
- response_headers = {}
830
- ui_mode_query = request.query_params.get("ui_mode", "standalone")
831
- if loaded_flock:
832
- success_message_text = f"Flock '{loaded_flock.name}' loaded from '{selected_flock_filename}'."
833
- response_headers["HX-Push-Url"] = "/ui/editor/execute?ui_mode=" + ui_mode_query
834
- response_headers["HX-Trigger"] = json.dumps({"flockLoaded": None, "notify": {"type": "success", "message": success_message_text}})
835
- context = get_base_context_web(request, success=success_message_text, ui_mode=ui_mode_query)
836
- return templates.TemplateResponse(request, "partials/_execution_view_container.html", context, headers=response_headers)
837
- else:
838
- error_message_text = f"Failed to load flock file '{selected_flock_filename}'."
839
- response_headers["HX-Trigger"] = json.dumps({"notify": {"type": "error", "message": error_message_text}})
840
- context = get_base_context_web(request, error=error_message_text, ui_mode=ui_mode_query)
841
- context["error_message_inline"] = error_message_text # For direct display in partial
842
- return templates.TemplateResponse(request, "partials/_load_manager_view.html", context, headers=response_headers)
843
-
844
- @app.post("/ui/load-flock-action/by-upload", response_class=HTMLResponse, tags=["UI Actions"])
845
- async def ui_load_flock_by_upload_action(request: Request, flock_file_upload: UploadFile = File(...)):
846
- error_message_text, filename_to_load, response_headers = None, None, {}
847
- ui_mode_query = request.query_params.get("ui_mode", "standalone")
848
-
849
- if flock_file_upload and flock_file_upload.filename:
850
- if not flock_file_upload.filename.endswith((".yaml", ".yml", ".flock")): error_message_text = "Invalid file type."
851
- else:
852
- upload_path = FLOCK_FILES_DIR / flock_file_upload.filename
853
- try:
854
- with upload_path.open("wb") as buffer: shutil.copyfileobj(flock_file_upload.file, buffer)
855
- filename_to_load = flock_file_upload.filename
856
- except Exception as e: error_message_text = f"Upload failed: {e}"
857
- finally: await flock_file_upload.close()
858
- else: error_message_text = "No file uploaded."
859
-
860
- if filename_to_load and not error_message_text:
861
- loaded_flock = load_flock_from_file_service(filename_to_load, request.app.state)
862
- if loaded_flock:
863
- success_message_text = f"Flock '{loaded_flock.name}' loaded from '{filename_to_load}'."
864
- response_headers["HX-Push-Url"] = f"/ui/editor/execute?ui_mode={ui_mode_query}"
865
- response_headers["HX-Trigger"] = json.dumps({"flockLoaded": None, "flockFileListChanged": None, "notify": {"type": "success", "message": success_message_text}})
866
- context = get_base_context_web(request, success=success_message_text, ui_mode=ui_mode_query)
867
- return templates.TemplateResponse(request, "partials/_execution_view_container.html", context, headers=response_headers)
868
- else: error_message_text = f"Failed to process uploaded '{filename_to_load}'."
869
-
870
- final_error_msg = error_message_text or "Upload failed."
871
- response_headers["HX-Trigger"] = json.dumps({"notify": {"type": "error", "message": final_error_msg}})
872
- context = get_base_context_web(request, error=final_error_msg, ui_mode=ui_mode_query)
873
- return templates.TemplateResponse(request, "partials/_create_flock_form.html", context, headers=response_headers)
874
-
875
- @app.post("/ui/create-flock", response_class=HTMLResponse, tags=["UI Actions"])
876
- async def ui_create_flock_action(request: Request, flock_name: str = Form(...), default_model: str = Form(None), description: str = Form(None)):
877
- ui_mode_query = request.query_params.get("ui_mode", "standalone")
878
- if not flock_name.strip():
879
- context = get_base_context_web(request, error="Flock name cannot be empty.", ui_mode=ui_mode_query)
880
- return templates.TemplateResponse(request, "partials/_create_flock_form.html", context)
881
-
882
- new_flock = create_new_flock_service(flock_name, default_model, description, request.app.state)
883
- success_msg_text = f"New flock '{new_flock.name}' created. Navigating to Execute page. Configure properties and agents as needed."
884
- response_headers = {"HX-Push-Url": f"/ui/editor/execute?ui_mode={ui_mode_query}", "HX-Trigger": json.dumps({"flockLoaded": None, "notify": {"type": "success", "message": success_msg_text}})}
885
- context = get_base_context_web(request, success=success_msg_text, ui_mode=ui_mode_query)
886
- return templates.TemplateResponse(request, "partials/_execution_view_container.html", context, headers=response_headers)
887
-
888
-
889
- # --- Settings Page & Endpoints ---
890
-
891
- @app.get("/ui/htmx/live-logs", response_class=HTMLResponse, tags=["UI HTMX Partials"])
892
- async def htmx_live_cli_logs(request: Request, limit: int = Query(200, ge=50, le=800)):
893
- store = get_live_log_store()
894
- entries = store.get_entries(limit=limit)
895
-
896
- hide_polling_param = request.query_params.get("hide_polling", "1")
897
- hide_polling = str(hide_polling_param).lower() not in {"0", "false", "off", ""}
898
-
899
- if hide_polling:
900
- entries = [entry for entry in entries if "htmx/live-logs" not in str(entry.get("text", ""))]
901
-
902
- logs = []
903
- for entry in entries:
904
- raw_timestamp = float(entry.get("timestamp", 0.0) or 0.0)
905
- time_display = datetime.fromtimestamp(raw_timestamp).strftime("%H:%M:%S")
906
- stream = str(entry.get("stream", "stdout") or "stdout")
907
- css_class = "stderr" if stream == "stderr" else "stdout"
908
- stream_label = "STDERR" if stream == "stderr" else "STDOUT"
909
- text_value = str(entry.get("text", ""))
910
- logs.append(
911
- {
912
- "time_display": time_display,
913
- "stream_label": stream_label,
914
- "css_class": css_class,
915
- "text": text_value,
916
- }
917
- )
918
-
919
- latest_timestamp = entries[-1]["timestamp"] if entries else None
920
- return templates.TemplateResponse(request, "partials/_live_logs.html",
921
- {
922
- "request": request,
923
- "logs": logs,
924
- "latest_timestamp": latest_timestamp,
925
- },
926
- )
927
-
928
- @app.get("/ui/settings", response_class=HTMLResponse, tags=["UI Pages"])
929
- async def page_settings(request: Request, error: str = None, success: str = None, ui_mode: str = Query("standalone")):
930
- context = get_base_context_web(request, error, success, ui_mode)
931
- root_path = request.scope.get("root_path", "")
932
- context["initial_content_url"] = f"{root_path}/ui/htmx/settings-view"
933
- return templates.TemplateResponse(request, "base.html", context)
934
-
935
- def _prepare_env_vars_for_template_web():
936
- env_vars_raw = load_env_file_web(); show_secrets = get_show_secrets_setting_web(env_vars_raw)
937
- env_vars_list = []
938
- for name, value in env_vars_raw.items():
939
- if name.startswith("#") or name == "": continue
940
- display_value = value if (not is_sensitive_web(name) or show_secrets) else mask_sensitive_value_web(value)
941
- env_vars_list.append({"name": name, "value": display_value})
942
- return env_vars_list, show_secrets
943
-
944
- @app.get("/ui/htmx/settings-view", response_class=HTMLResponse, tags=["UI HTMX Partials"])
945
- async def htmx_get_settings_view(request: Request):
946
- env_vars_list, show_secrets = _prepare_env_vars_for_template_web()
947
- theme_name = get_current_theme_name()
948
- themes_available = [p.stem for p in THEMES_DIR.glob("*.toml")] if THEMES_DIR and THEMES_DIR.exists() else []
949
- return templates.TemplateResponse(request, "partials/_settings_view.html", {"request": request, "env_vars": env_vars_list, "show_secrets": show_secrets, "themes": themes_available, "current_theme": theme_name})
950
-
951
- @app.post("/ui/htmx/toggle-show-secrets", response_class=HTMLResponse, tags=["UI Actions"])
952
- async def htmx_toggle_show_secrets(request: Request):
953
- env_vars_raw = load_env_file_web(); current = get_show_secrets_setting_web(env_vars_raw)
954
- set_show_secrets_setting_web(not current)
955
- env_vars_list, show_secrets = _prepare_env_vars_for_template_web()
956
- return templates.TemplateResponse(request, "partials/_env_vars_table.html", {"request": request, "env_vars": env_vars_list, "show_secrets": show_secrets})
957
-
958
- @app.post("/ui/htmx/env-delete", response_class=HTMLResponse, tags=["UI Actions"])
959
- async def htmx_env_delete(request: Request, var_name: str = Form(...)):
960
- env_vars_raw = load_env_file_web()
961
- if var_name in env_vars_raw: del env_vars_raw[var_name]; save_env_file_web(env_vars_raw)
962
- env_vars_list, show_secrets = _prepare_env_vars_for_template_web()
963
- return templates.TemplateResponse(request, "partials/_env_vars_table.html", {"request": request, "env_vars": env_vars_list, "show_secrets": show_secrets})
964
-
965
- @app.post("/ui/htmx/env-edit", response_class=HTMLResponse, tags=["UI Actions"])
966
- async def htmx_env_edit(request: Request, var_name: str = Form(...)):
967
- new_value = request.headers.get("HX-Prompt")
968
- env_vars_list, show_secrets = _prepare_env_vars_for_template_web()
969
- if new_value is not None:
970
- env_vars_raw = load_env_file_web()
971
- env_vars_raw[var_name] = new_value
972
- save_env_file_web(env_vars_raw)
973
- env_vars_list, show_secrets = _prepare_env_vars_for_template_web()
974
- return templates.TemplateResponse(request, "partials/_env_vars_table.html", {"request": request, "env_vars": env_vars_list, "show_secrets": show_secrets})
975
-
976
- @app.get("/ui/htmx/env-add-form", response_class=HTMLResponse, tags=["UI HTMX Partials"])
977
- async def htmx_env_add_form(request: Request):
978
- return HTMLResponse("""<form hx-post='/ui/htmx/env-add' hx-target='#env-vars-container' hx-swap='outerHTML' style='display:flex; gap:0.5rem; margin-bottom:0.5rem;'><input name='var_name' placeholder='NAME' required style='flex:2;'><input name='var_value' placeholder='VALUE' style='flex:3;'><button type='submit'>Add</button></form>""")
979
-
980
- @app.post("/ui/htmx/env-add", response_class=HTMLResponse, tags=["UI Actions"])
981
- async def htmx_env_add(request: Request, var_name: str = Form(...), var_value: str = Form("")):
982
- env_vars_raw = load_env_file_web()
983
- env_vars_raw[var_name] = var_value; save_env_file_web(env_vars_raw)
984
- env_vars_list, show_secrets = _prepare_env_vars_for_template_web()
985
- return templates.TemplateResponse(request, "partials/_env_vars_table.html", {"request": request, "env_vars": env_vars_list, "show_secrets": show_secrets})
986
-
987
- @app.get("/ui/htmx/theme-preview", response_class=HTMLResponse, tags=["UI HTMX Partials"])
988
- async def htmx_theme_preview(request: Request, theme: str = Query(None)):
989
- if not THEME_LOADER_AVAILABLE:
990
- return HTMLResponse("<p>Theme loading functionality is not available.</p>", status_code=500)
991
- if THEMES_DIR is None or not THEMES_DIR.exists():
992
- return HTMLResponse("<p>Themes directory is not configured or does not exist.</p>", status_code=500)
993
-
994
- chosen_theme_name_input = theme or get_current_theme_name() or DEFAULT_THEME_NAME
995
-
996
- # Sanitize the input to get only the filename component
997
- sanitized_name_part = Path(chosen_theme_name_input).name
998
- # Ensure we have a stem
999
- theme_stem_from_input = sanitized_name_part
1000
- if theme_stem_from_input.endswith(".toml"):
1001
- theme_stem_from_input = theme_stem_from_input[:-5]
1002
-
1003
- theme_filename_to_load = f"{theme_stem_from_input}.toml"
1004
- theme_name_for_display = theme_stem_from_input # Use the sanitized stem for display/logging
1005
-
1006
- try:
1007
- resolved_themes_dir = THEMES_DIR.resolve(strict=True)
1008
- theme_path_candidate = resolved_themes_dir / theme_filename_to_load
1009
- resolved_theme_path = theme_path_candidate.resolve()
1010
-
1011
- if not str(resolved_theme_path).startswith(str(resolved_themes_dir)) or \
1012
- resolved_theme_path.name != theme_filename_to_load:
1013
- logger.warning(f"Invalid theme path access attempt for '{theme_name_for_display}'. "
1014
- f"Original input: '{chosen_theme_name_input}', Sanitized filename: '{theme_filename_to_load}', "
1015
- f"Attempted path: '{theme_path_candidate}', Resolved to: '{resolved_theme_path}'")
1016
- return HTMLResponse(f"<p>Invalid theme name or path for '{theme_name_for_display}'.</p>", status_code=400)
1017
-
1018
- if not resolved_theme_path.is_file():
1019
- logger.info(f"Theme preview: Theme file '{theme_filename_to_load}' not found at '{resolved_theme_path}'.")
1020
- return HTMLResponse(f"<p>Theme '{theme_name_for_display}' not found.</p>", status_code=404)
1021
-
1022
- theme_path = resolved_theme_path
1023
- theme_data = load_theme_from_file(str(theme_path))
1024
- logger.debug(f"Successfully loaded theme '{theme_name_for_display}' for preview from '{theme_path}'")
1025
-
1026
- except FileNotFoundError: # For THEMES_DIR.resolve(strict=True)
1027
- logger.error(f"Themes directory '{THEMES_DIR}' not found during preview for '{theme_name_for_display}'.")
1028
- return HTMLResponse("<p>Themes directory not found.</p>", status_code=500)
1029
- except Exception as e:
1030
- logger.error(f"Error loading theme '{theme_name_for_display}' for preview (path: '{theme_path_candidate if 'theme_path_candidate' in locals() else 'unknown'}'): {e}")
1031
- return HTMLResponse(f"<p>Error loading theme '{theme_name_for_display}': {e}</p>", status_code=500)
1032
-
1033
- css_vars = alacritty_to_pico(theme_data)
1034
- if not css_vars:
1035
- return HTMLResponse(f"<p>Could not convert theme '{theme_name_for_display}' to CSS variables.</p>")
1036
-
1037
- css_vars_str = ":root {\n" + "\\n".join([f" {k}: {v};" for k, v in css_vars.items()]) + "\\n}"
1038
- main_colors = [("Background", css_vars.get("--pico-background-color")), ("Text", css_vars.get("--pico-color")), ("Primary", css_vars.get("--pico-primary")), ("Secondary", css_vars.get("--pico-secondary")), ("Muted", css_vars.get("--pico-muted-color"))]
1039
- return templates.TemplateResponse(request, "partials/_theme_preview.html", {"request": request, "theme_name": theme_name_for_display, "css_vars_str": css_vars_str, "main_colors": main_colors})
1040
-
1041
- @app.post("/ui/apply-theme", tags=["UI Actions"])
1042
- async def apply_theme(request: Request, theme: str = Form(...)):
1043
- try:
1044
- from flock.webapp.app.config import set_current_theme_name
1045
- set_current_theme_name(theme)
1046
- headers = {"HX-Refresh": "true"}
1047
- return HTMLResponse("", headers=headers)
1048
- except Exception as e: return HTMLResponse(f"Failed to apply theme: {e}", status_code=500)
1049
-
1050
- @app.get("/ui/htmx/settings/env-vars", response_class=HTMLResponse, tags=["UI HTMX Partials"])
1051
- async def htmx_settings_env_vars(request: Request):
1052
- env_vars_list, show_secrets = _prepare_env_vars_for_template_web()
1053
- return templates.TemplateResponse(request, "partials/_settings_env_content.html", {"request": request, "env_vars": env_vars_list, "show_secrets": show_secrets})
1054
-
1055
- @app.get("/ui/htmx/settings/theme", response_class=HTMLResponse, tags=["UI HTMX Partials"])
1056
- async def htmx_settings_theme(request: Request):
1057
- theme_name = get_current_theme_name()
1058
- themes_available = [p.stem for p in THEMES_DIR.glob("*.toml")] if THEMES_DIR and THEMES_DIR.exists() else []
1059
- return templates.TemplateResponse(request, "partials/_settings_theme_content.html", {"request": request, "themes": themes_available, "current_theme": theme_name})
1060
-
1061
- @app.get("/ui/chat", response_class=HTMLResponse, tags=["UI Pages"])
1062
- async def page_chat(request: Request, ui_mode: str = Query("standalone")):
1063
- context = get_base_context_web(request, ui_mode=ui_mode)
1064
- context["initial_content_url"] = "/ui/htmx/chat-view"
1065
- return templates.TemplateResponse(request, "base.html", context)
1066
-
1067
- @app.get("/ui/htmx/chat-view", response_class=HTMLResponse, tags=["UI HTMX Partials"])
1068
- async def htmx_get_chat_view(request: Request):
1069
- # Render container partial; session handled in chat router
1070
- return templates.TemplateResponse(request, "partials/_chat_container.html", get_base_context_web(request))
1071
-
1072
- if __name__ == "__main__":
1073
- import uvicorn
1074
- # Ensure the dependency injection system is initialized for standalone run
1075
- temp_run_store = RunStore()
1076
- # Create a default/dummy Flock instance for standalone UI testing
1077
- # This allows the UI to function without being started by `Flock.start_api()`
1078
- dev_flock_instance = Flock(name="DevStandaloneFlock", model="test/dummy", show_flock_banner=False)
1079
-
1080
- set_global_flock_services(dev_flock_instance, temp_run_store)
1081
- app.state.flock_instance = dev_flock_instance
1082
- app.state.run_store = temp_run_store
1083
- app.state.flock_filename = "development_standalone.flock.yaml"
1084
-
1085
- logger.info("Running webapp.app.main directly for development with a dummy Flock instance.")
1086
- uvicorn.run(app, host="127.0.0.1", port=8344, reload=True)