flock-core 0.5.0b28__py3-none-any.whl → 0.5.0b51__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (469) hide show
  1. flock/__init__.py +12 -217
  2. flock/agent.py +678 -0
  3. flock/api/themes.py +71 -0
  4. flock/artifacts.py +79 -0
  5. flock/cli.py +75 -0
  6. flock/components.py +173 -0
  7. flock/dashboard/__init__.py +28 -0
  8. flock/dashboard/collector.py +283 -0
  9. flock/dashboard/events.py +182 -0
  10. flock/dashboard/launcher.py +230 -0
  11. flock/dashboard/service.py +537 -0
  12. flock/dashboard/websocket.py +235 -0
  13. flock/engines/__init__.py +6 -0
  14. flock/engines/dspy_engine.py +856 -0
  15. flock/examples.py +128 -0
  16. flock/frontend/README.md +678 -0
  17. flock/frontend/docs/DESIGN_SYSTEM.md +1980 -0
  18. flock/frontend/index.html +12 -0
  19. flock/frontend/package-lock.json +4347 -0
  20. flock/frontend/package.json +48 -0
  21. flock/frontend/src/App.tsx +79 -0
  22. flock/frontend/src/__tests__/e2e/critical-scenarios.test.tsx +587 -0
  23. flock/frontend/src/__tests__/integration/filtering-e2e.test.tsx +387 -0
  24. flock/frontend/src/__tests__/integration/graph-rendering.test.tsx +640 -0
  25. flock/frontend/src/__tests__/integration/indexeddb-persistence.test.tsx +699 -0
  26. flock/frontend/src/components/common/BuildInfo.tsx +39 -0
  27. flock/frontend/src/components/common/EmptyState.module.css +115 -0
  28. flock/frontend/src/components/common/EmptyState.tsx +128 -0
  29. flock/frontend/src/components/common/ErrorBoundary.module.css +169 -0
  30. flock/frontend/src/components/common/ErrorBoundary.tsx +118 -0
  31. flock/frontend/src/components/common/KeyboardShortcutsDialog.css +251 -0
  32. flock/frontend/src/components/common/KeyboardShortcutsDialog.tsx +151 -0
  33. flock/frontend/src/components/common/LoadingSpinner.module.css +97 -0
  34. flock/frontend/src/components/common/LoadingSpinner.tsx +29 -0
  35. flock/frontend/src/components/controls/PublishControl.css +547 -0
  36. flock/frontend/src/components/controls/PublishControl.test.tsx +543 -0
  37. flock/frontend/src/components/controls/PublishControl.tsx +432 -0
  38. flock/frontend/src/components/details/DetailWindowContainer.tsx +62 -0
  39. flock/frontend/src/components/details/LiveOutputTab.test.tsx +792 -0
  40. flock/frontend/src/components/details/LiveOutputTab.tsx +220 -0
  41. flock/frontend/src/components/details/MessageHistoryTab.tsx +299 -0
  42. flock/frontend/src/components/details/NodeDetailWindow.test.tsx +501 -0
  43. flock/frontend/src/components/details/NodeDetailWindow.tsx +218 -0
  44. flock/frontend/src/components/details/RunStatusTab.tsx +307 -0
  45. flock/frontend/src/components/details/tabs.test.tsx +1015 -0
  46. flock/frontend/src/components/filters/CorrelationIDFilter.module.css +102 -0
  47. flock/frontend/src/components/filters/CorrelationIDFilter.test.tsx +197 -0
  48. flock/frontend/src/components/filters/CorrelationIDFilter.tsx +121 -0
  49. flock/frontend/src/components/filters/FilterBar.module.css +29 -0
  50. flock/frontend/src/components/filters/FilterBar.test.tsx +133 -0
  51. flock/frontend/src/components/filters/FilterBar.tsx +33 -0
  52. flock/frontend/src/components/filters/FilterPills.module.css +79 -0
  53. flock/frontend/src/components/filters/FilterPills.test.tsx +173 -0
  54. flock/frontend/src/components/filters/FilterPills.tsx +67 -0
  55. flock/frontend/src/components/filters/TimeRangeFilter.module.css +91 -0
  56. flock/frontend/src/components/filters/TimeRangeFilter.test.tsx +154 -0
  57. flock/frontend/src/components/filters/TimeRangeFilter.tsx +105 -0
  58. flock/frontend/src/components/graph/AgentNode.test.tsx +75 -0
  59. flock/frontend/src/components/graph/AgentNode.tsx +322 -0
  60. flock/frontend/src/components/graph/GraphCanvas.tsx +406 -0
  61. flock/frontend/src/components/graph/MessageFlowEdge.tsx +128 -0
  62. flock/frontend/src/components/graph/MessageNode.test.tsx +62 -0
  63. flock/frontend/src/components/graph/MessageNode.tsx +116 -0
  64. flock/frontend/src/components/graph/MiniMap.tsx +47 -0
  65. flock/frontend/src/components/graph/TransformEdge.tsx +123 -0
  66. flock/frontend/src/components/layout/DashboardLayout.css +407 -0
  67. flock/frontend/src/components/layout/DashboardLayout.tsx +300 -0
  68. flock/frontend/src/components/layout/Header.module.css +88 -0
  69. flock/frontend/src/components/layout/Header.tsx +52 -0
  70. flock/frontend/src/components/modules/EventLogModule.test.tsx +401 -0
  71. flock/frontend/src/components/modules/EventLogModule.tsx +396 -0
  72. flock/frontend/src/components/modules/EventLogModuleWrapper.tsx +17 -0
  73. flock/frontend/src/components/modules/ModuleRegistry.test.ts +333 -0
  74. flock/frontend/src/components/modules/ModuleRegistry.ts +85 -0
  75. flock/frontend/src/components/modules/ModuleWindow.tsx +155 -0
  76. flock/frontend/src/components/modules/registerModules.ts +20 -0
  77. flock/frontend/src/components/settings/AdvancedSettings.tsx +175 -0
  78. flock/frontend/src/components/settings/AppearanceSettings.tsx +185 -0
  79. flock/frontend/src/components/settings/GraphSettings.tsx +110 -0
  80. flock/frontend/src/components/settings/SettingsPanel.css +327 -0
  81. flock/frontend/src/components/settings/SettingsPanel.tsx +131 -0
  82. flock/frontend/src/components/settings/ThemeSelector.tsx +298 -0
  83. flock/frontend/src/hooks/useKeyboardShortcuts.ts +148 -0
  84. flock/frontend/src/hooks/useModulePersistence.test.ts +442 -0
  85. flock/frontend/src/hooks/useModulePersistence.ts +154 -0
  86. flock/frontend/src/hooks/useModules.ts +139 -0
  87. flock/frontend/src/hooks/usePersistence.ts +139 -0
  88. flock/frontend/src/main.tsx +13 -0
  89. flock/frontend/src/services/api.ts +213 -0
  90. flock/frontend/src/services/indexeddb.test.ts +793 -0
  91. flock/frontend/src/services/indexeddb.ts +794 -0
  92. flock/frontend/src/services/layout.test.ts +437 -0
  93. flock/frontend/src/services/layout.ts +146 -0
  94. flock/frontend/src/services/themeApplicator.ts +140 -0
  95. flock/frontend/src/services/themeService.ts +77 -0
  96. flock/frontend/src/services/websocket.test.ts +595 -0
  97. flock/frontend/src/services/websocket.ts +685 -0
  98. flock/frontend/src/store/filterStore.test.ts +242 -0
  99. flock/frontend/src/store/filterStore.ts +103 -0
  100. flock/frontend/src/store/graphStore.test.ts +186 -0
  101. flock/frontend/src/store/graphStore.ts +414 -0
  102. flock/frontend/src/store/moduleStore.test.ts +253 -0
  103. flock/frontend/src/store/moduleStore.ts +57 -0
  104. flock/frontend/src/store/settingsStore.ts +188 -0
  105. flock/frontend/src/store/streamStore.ts +68 -0
  106. flock/frontend/src/store/uiStore.test.ts +54 -0
  107. flock/frontend/src/store/uiStore.ts +110 -0
  108. flock/frontend/src/store/wsStore.ts +34 -0
  109. flock/frontend/src/styles/index.css +15 -0
  110. flock/frontend/src/styles/scrollbar.css +47 -0
  111. flock/frontend/src/styles/variables.css +488 -0
  112. flock/frontend/src/test/setup.ts +1 -0
  113. flock/frontend/src/types/filters.ts +14 -0
  114. flock/frontend/src/types/graph.ts +55 -0
  115. flock/frontend/src/types/modules.ts +7 -0
  116. flock/frontend/src/types/theme.ts +55 -0
  117. flock/frontend/src/utils/mockData.ts +85 -0
  118. flock/frontend/src/utils/performance.ts +16 -0
  119. flock/frontend/src/utils/transforms.test.ts +860 -0
  120. flock/frontend/src/utils/transforms.ts +323 -0
  121. flock/frontend/src/vite-env.d.ts +17 -0
  122. flock/frontend/tsconfig.json +27 -0
  123. flock/frontend/tsconfig.node.json +11 -0
  124. flock/frontend/vite.config.ts +25 -0
  125. flock/frontend/vitest.config.ts +11 -0
  126. flock/{core/util → helper}/cli_helper.py +4 -3
  127. flock/{core/logging → logging}/__init__.py +2 -3
  128. flock/{core/logging → logging}/formatters/enum_builder.py +3 -4
  129. flock/{core/logging → logging}/formatters/theme_builder.py +19 -44
  130. flock/{core/logging → logging}/formatters/themed_formatter.py +69 -115
  131. flock/{core/logging → logging}/logging.py +77 -61
  132. flock/{core/logging → logging}/telemetry.py +20 -26
  133. flock/{core/logging → logging}/telemetry_exporter/base_exporter.py +2 -2
  134. flock/{core/logging → logging}/telemetry_exporter/file_exporter.py +6 -9
  135. flock/{core/logging → logging}/telemetry_exporter/sqlite_exporter.py +2 -3
  136. flock/{core/logging → logging}/trace_and_logged.py +20 -24
  137. flock/mcp/__init__.py +91 -0
  138. flock/{core/mcp/mcp_client.py → mcp/client.py} +103 -154
  139. flock/{core/mcp/mcp_config.py → mcp/config.py} +62 -117
  140. flock/mcp/manager.py +255 -0
  141. flock/mcp/servers/sse/__init__.py +1 -1
  142. flock/mcp/servers/sse/flock_sse_server.py +11 -53
  143. flock/mcp/servers/stdio/__init__.py +1 -1
  144. flock/mcp/servers/stdio/flock_stdio_server.py +8 -48
  145. flock/mcp/servers/streamable_http/flock_streamable_http_server.py +17 -62
  146. flock/mcp/servers/websockets/flock_websocket_server.py +7 -40
  147. flock/{core/mcp/flock_mcp_tool.py → mcp/tool.py} +16 -26
  148. flock/mcp/types/__init__.py +42 -0
  149. flock/{core/mcp → mcp}/types/callbacks.py +9 -15
  150. flock/{core/mcp → mcp}/types/factories.py +7 -6
  151. flock/{core/mcp → mcp}/types/handlers.py +13 -18
  152. flock/{core/mcp → mcp}/types/types.py +70 -74
  153. flock/{core/mcp → mcp}/util/helpers.py +1 -1
  154. flock/orchestrator.py +645 -0
  155. flock/registry.py +148 -0
  156. flock/runtime.py +262 -0
  157. flock/service.py +140 -0
  158. flock/store.py +69 -0
  159. flock/subscription.py +111 -0
  160. flock/themes/andromeda.toml +1 -1
  161. flock/themes/apple-system-colors.toml +1 -1
  162. flock/themes/arcoiris.toml +1 -1
  163. flock/themes/atomonelight.toml +1 -1
  164. flock/themes/ayu copy.toml +1 -1
  165. flock/themes/ayu-light.toml +1 -1
  166. flock/themes/belafonte-day.toml +1 -1
  167. flock/themes/belafonte-night.toml +1 -1
  168. flock/themes/blulocodark.toml +1 -1
  169. flock/themes/breeze.toml +1 -1
  170. flock/themes/broadcast.toml +1 -1
  171. flock/themes/brogrammer.toml +1 -1
  172. flock/themes/builtin-dark.toml +1 -1
  173. flock/themes/builtin-pastel-dark.toml +1 -1
  174. flock/themes/catppuccin-latte.toml +1 -1
  175. flock/themes/catppuccin-macchiato.toml +1 -1
  176. flock/themes/catppuccin-mocha.toml +1 -1
  177. flock/themes/cga.toml +1 -1
  178. flock/themes/chalk.toml +1 -1
  179. flock/themes/ciapre.toml +1 -1
  180. flock/themes/coffee-theme.toml +1 -1
  181. flock/themes/cyberpunkscarletprotocol.toml +1 -1
  182. flock/themes/dark+.toml +1 -1
  183. flock/themes/darkermatrix.toml +1 -1
  184. flock/themes/darkside.toml +1 -1
  185. flock/themes/desert.toml +1 -1
  186. flock/themes/django.toml +1 -1
  187. flock/themes/djangosmooth.toml +1 -1
  188. flock/themes/doomone.toml +1 -1
  189. flock/themes/dotgov.toml +1 -1
  190. flock/themes/dracula+.toml +1 -1
  191. flock/themes/duckbones.toml +1 -1
  192. flock/themes/encom.toml +1 -1
  193. flock/themes/espresso.toml +1 -1
  194. flock/themes/everblush.toml +1 -1
  195. flock/themes/fairyfloss.toml +1 -1
  196. flock/themes/fideloper.toml +1 -1
  197. flock/themes/fishtank.toml +1 -1
  198. flock/themes/flexoki-light.toml +1 -1
  199. flock/themes/floraverse.toml +1 -1
  200. flock/themes/framer.toml +1 -1
  201. flock/themes/galizur.toml +1 -1
  202. flock/themes/github.toml +1 -1
  203. flock/themes/grass.toml +1 -1
  204. flock/themes/grey-green.toml +1 -1
  205. flock/themes/gruvboxlight.toml +1 -1
  206. flock/themes/guezwhoz.toml +1 -1
  207. flock/themes/harper.toml +1 -1
  208. flock/themes/hax0r-blue.toml +1 -1
  209. flock/themes/hopscotch.256.toml +1 -1
  210. flock/themes/ic-green-ppl.toml +1 -1
  211. flock/themes/iceberg-dark.toml +1 -1
  212. flock/themes/japanesque.toml +1 -1
  213. flock/themes/jubi.toml +1 -1
  214. flock/themes/kibble.toml +1 -1
  215. flock/themes/kolorit.toml +1 -1
  216. flock/themes/kurokula.toml +1 -1
  217. flock/themes/materialdesigncolors.toml +1 -1
  218. flock/themes/matrix.toml +1 -1
  219. flock/themes/mellifluous.toml +1 -1
  220. flock/themes/midnight-in-mojave.toml +1 -1
  221. flock/themes/monokai-remastered.toml +1 -1
  222. flock/themes/monokai-soda.toml +1 -1
  223. flock/themes/neon.toml +1 -1
  224. flock/themes/neopolitan.toml +1 -1
  225. flock/themes/nord-light.toml +1 -1
  226. flock/themes/ocean.toml +1 -1
  227. flock/themes/onehalfdark.toml +1 -1
  228. flock/themes/onehalflight.toml +1 -1
  229. flock/themes/palenighthc.toml +1 -1
  230. flock/themes/paulmillr.toml +1 -1
  231. flock/themes/pencildark.toml +1 -1
  232. flock/themes/pnevma.toml +1 -1
  233. flock/themes/purple-rain.toml +1 -1
  234. flock/themes/purplepeter.toml +1 -1
  235. flock/themes/raycast-dark.toml +1 -1
  236. flock/themes/red-sands.toml +1 -1
  237. flock/themes/relaxed.toml +1 -1
  238. flock/themes/retro.toml +1 -1
  239. flock/themes/rose-pine.toml +1 -1
  240. flock/themes/royal.toml +1 -1
  241. flock/themes/ryuuko.toml +1 -1
  242. flock/themes/sakura.toml +1 -1
  243. flock/themes/scarlet-protocol.toml +1 -1
  244. flock/themes/seoulbones-dark.toml +1 -1
  245. flock/themes/shades-of-purple.toml +1 -1
  246. flock/themes/smyck.toml +1 -1
  247. flock/themes/softserver.toml +1 -1
  248. flock/themes/solarized-darcula.toml +1 -1
  249. flock/themes/square.toml +1 -1
  250. flock/themes/sugarplum.toml +1 -1
  251. flock/themes/thayer-bright.toml +1 -1
  252. flock/themes/tokyonight.toml +1 -1
  253. flock/themes/tomorrow.toml +1 -1
  254. flock/themes/ubuntu.toml +1 -1
  255. flock/themes/ultradark.toml +1 -1
  256. flock/themes/ultraviolent.toml +1 -1
  257. flock/themes/unikitty.toml +1 -1
  258. flock/themes/urple.toml +1 -1
  259. flock/themes/vesper.toml +1 -1
  260. flock/themes/vimbones.toml +1 -1
  261. flock/themes/wildcherry.toml +1 -1
  262. flock/themes/wilmersdorf.toml +1 -1
  263. flock/themes/wryan.toml +1 -1
  264. flock/themes/xcodedarkhc.toml +1 -1
  265. flock/themes/xcodelight.toml +1 -1
  266. flock/themes/zenbones-light.toml +1 -1
  267. flock/themes/zenwritten-dark.toml +1 -1
  268. flock/utilities.py +301 -0
  269. flock/{components/utility → utility}/output_utility_component.py +68 -53
  270. flock/visibility.py +107 -0
  271. flock_core-0.5.0b51.dist-info/METADATA +747 -0
  272. flock_core-0.5.0b51.dist-info/RECORD +508 -0
  273. flock_core-0.5.0b51.dist-info/entry_points.txt +2 -0
  274. {flock_core-0.5.0b28.dist-info → flock_core-0.5.0b51.dist-info}/licenses/LICENSE +1 -1
  275. flock/adapter/__init__.py +0 -14
  276. flock/adapter/azure_adapter.py +0 -68
  277. flock/adapter/chroma_adapter.py +0 -73
  278. flock/adapter/faiss_adapter.py +0 -97
  279. flock/adapter/pinecone_adapter.py +0 -51
  280. flock/adapter/vector_base.py +0 -47
  281. flock/cli/assets/release_notes.md +0 -140
  282. flock/cli/config.py +0 -8
  283. flock/cli/constants.py +0 -36
  284. flock/cli/create_agent.py +0 -1
  285. flock/cli/create_flock.py +0 -280
  286. flock/cli/execute_flock.py +0 -620
  287. flock/cli/load_agent.py +0 -1
  288. flock/cli/load_examples.py +0 -1
  289. flock/cli/load_flock.py +0 -192
  290. flock/cli/load_release_notes.py +0 -20
  291. flock/cli/loaded_flock_cli.py +0 -254
  292. flock/cli/manage_agents.py +0 -459
  293. flock/cli/registry_management.py +0 -889
  294. flock/cli/runner.py +0 -41
  295. flock/cli/settings.py +0 -857
  296. flock/cli/utils.py +0 -135
  297. flock/cli/view_results.py +0 -29
  298. flock/cli/yaml_editor.py +0 -396
  299. flock/components/__init__.py +0 -30
  300. flock/components/evaluation/__init__.py +0 -9
  301. flock/components/evaluation/declarative_evaluation_component.py +0 -606
  302. flock/components/routing/__init__.py +0 -15
  303. flock/components/routing/conditional_routing_component.py +0 -494
  304. flock/components/routing/default_routing_component.py +0 -103
  305. flock/components/routing/llm_routing_component.py +0 -206
  306. flock/components/utility/__init__.py +0 -22
  307. flock/components/utility/example_utility_component.py +0 -250
  308. flock/components/utility/feedback_utility_component.py +0 -206
  309. flock/components/utility/memory_utility_component.py +0 -550
  310. flock/components/utility/metrics_utility_component.py +0 -700
  311. flock/config.py +0 -61
  312. flock/core/__init__.py +0 -110
  313. flock/core/agent/__init__.py +0 -16
  314. flock/core/agent/default_agent.py +0 -216
  315. flock/core/agent/flock_agent_components.py +0 -104
  316. flock/core/agent/flock_agent_execution.py +0 -101
  317. flock/core/agent/flock_agent_integration.py +0 -260
  318. flock/core/agent/flock_agent_lifecycle.py +0 -186
  319. flock/core/agent/flock_agent_serialization.py +0 -381
  320. flock/core/api/__init__.py +0 -10
  321. flock/core/api/custom_endpoint.py +0 -45
  322. flock/core/api/endpoints.py +0 -254
  323. flock/core/api/main.py +0 -162
  324. flock/core/api/models.py +0 -97
  325. flock/core/api/run_store.py +0 -224
  326. flock/core/api/runner.py +0 -44
  327. flock/core/api/service.py +0 -214
  328. flock/core/component/__init__.py +0 -15
  329. flock/core/component/agent_component_base.py +0 -309
  330. flock/core/component/evaluation_component.py +0 -62
  331. flock/core/component/routing_component.py +0 -74
  332. flock/core/component/utility_component.py +0 -69
  333. flock/core/config/flock_agent_config.py +0 -58
  334. flock/core/config/scheduled_agent_config.py +0 -40
  335. flock/core/context/context.py +0 -213
  336. flock/core/context/context_manager.py +0 -37
  337. flock/core/context/context_vars.py +0 -10
  338. flock/core/evaluation/utils.py +0 -396
  339. flock/core/execution/batch_executor.py +0 -369
  340. flock/core/execution/evaluation_executor.py +0 -438
  341. flock/core/execution/local_executor.py +0 -31
  342. flock/core/execution/opik_executor.py +0 -103
  343. flock/core/execution/temporal_executor.py +0 -164
  344. flock/core/flock.py +0 -634
  345. flock/core/flock_agent.py +0 -336
  346. flock/core/flock_factory.py +0 -613
  347. flock/core/flock_scheduler.py +0 -166
  348. flock/core/flock_server_manager.py +0 -136
  349. flock/core/interpreter/python_interpreter.py +0 -689
  350. flock/core/mcp/__init__.py +0 -1
  351. flock/core/mcp/flock_mcp_server.py +0 -680
  352. flock/core/mcp/mcp_client_manager.py +0 -201
  353. flock/core/mcp/types/__init__.py +0 -1
  354. flock/core/mixin/dspy_integration.py +0 -403
  355. flock/core/mixin/prompt_parser.py +0 -125
  356. flock/core/orchestration/__init__.py +0 -15
  357. flock/core/orchestration/flock_batch_processor.py +0 -94
  358. flock/core/orchestration/flock_evaluator.py +0 -113
  359. flock/core/orchestration/flock_execution.py +0 -295
  360. flock/core/orchestration/flock_initialization.py +0 -149
  361. flock/core/orchestration/flock_server_manager.py +0 -67
  362. flock/core/orchestration/flock_web_server.py +0 -117
  363. flock/core/registry/__init__.py +0 -45
  364. flock/core/registry/agent_registry.py +0 -69
  365. flock/core/registry/callable_registry.py +0 -139
  366. flock/core/registry/component_discovery.py +0 -142
  367. flock/core/registry/component_registry.py +0 -64
  368. flock/core/registry/config_mapping.py +0 -64
  369. flock/core/registry/decorators.py +0 -137
  370. flock/core/registry/registry_hub.py +0 -205
  371. flock/core/registry/server_registry.py +0 -57
  372. flock/core/registry/type_registry.py +0 -86
  373. flock/core/serialization/__init__.py +0 -13
  374. flock/core/serialization/callable_registry.py +0 -52
  375. flock/core/serialization/flock_serializer.py +0 -832
  376. flock/core/serialization/json_encoder.py +0 -41
  377. flock/core/serialization/secure_serializer.py +0 -175
  378. flock/core/serialization/serializable.py +0 -342
  379. flock/core/serialization/serialization_utils.py +0 -412
  380. flock/core/util/file_path_utils.py +0 -223
  381. flock/core/util/hydrator.py +0 -309
  382. flock/core/util/input_resolver.py +0 -164
  383. flock/core/util/loader.py +0 -59
  384. flock/core/util/splitter.py +0 -219
  385. flock/di.py +0 -27
  386. flock/platform/docker_tools.py +0 -49
  387. flock/platform/jaeger_install.py +0 -86
  388. flock/webapp/__init__.py +0 -1
  389. flock/webapp/app/__init__.py +0 -0
  390. flock/webapp/app/api/__init__.py +0 -0
  391. flock/webapp/app/api/agent_management.py +0 -241
  392. flock/webapp/app/api/execution.py +0 -709
  393. flock/webapp/app/api/flock_management.py +0 -129
  394. flock/webapp/app/api/registry_viewer.py +0 -30
  395. flock/webapp/app/chat.py +0 -665
  396. flock/webapp/app/config.py +0 -104
  397. flock/webapp/app/dependencies.py +0 -117
  398. flock/webapp/app/main.py +0 -1070
  399. flock/webapp/app/middleware.py +0 -113
  400. flock/webapp/app/models_ui.py +0 -7
  401. flock/webapp/app/services/__init__.py +0 -0
  402. flock/webapp/app/services/feedback_file_service.py +0 -363
  403. flock/webapp/app/services/flock_service.py +0 -337
  404. flock/webapp/app/services/sharing_models.py +0 -81
  405. flock/webapp/app/services/sharing_store.py +0 -762
  406. flock/webapp/app/templates/theme_mapper.html +0 -326
  407. flock/webapp/app/theme_mapper.py +0 -812
  408. flock/webapp/app/utils.py +0 -85
  409. flock/webapp/run.py +0 -215
  410. flock/webapp/static/css/chat.css +0 -301
  411. flock/webapp/static/css/components.css +0 -167
  412. flock/webapp/static/css/header.css +0 -39
  413. flock/webapp/static/css/layout.css +0 -46
  414. flock/webapp/static/css/sidebar.css +0 -127
  415. flock/webapp/static/css/two-pane.css +0 -48
  416. flock/webapp/templates/base.html +0 -200
  417. flock/webapp/templates/chat.html +0 -152
  418. flock/webapp/templates/chat_settings.html +0 -19
  419. flock/webapp/templates/flock_editor.html +0 -16
  420. flock/webapp/templates/index.html +0 -12
  421. flock/webapp/templates/partials/_agent_detail_form.html +0 -93
  422. flock/webapp/templates/partials/_agent_list.html +0 -18
  423. flock/webapp/templates/partials/_agent_manager_view.html +0 -51
  424. flock/webapp/templates/partials/_agent_tools_checklist.html +0 -14
  425. flock/webapp/templates/partials/_chat_container.html +0 -15
  426. flock/webapp/templates/partials/_chat_messages.html +0 -57
  427. flock/webapp/templates/partials/_chat_settings_form.html +0 -85
  428. flock/webapp/templates/partials/_create_flock_form.html +0 -50
  429. flock/webapp/templates/partials/_dashboard_flock_detail.html +0 -17
  430. flock/webapp/templates/partials/_dashboard_flock_file_list.html +0 -16
  431. flock/webapp/templates/partials/_dashboard_flock_properties_preview.html +0 -28
  432. flock/webapp/templates/partials/_dashboard_upload_flock_form.html +0 -16
  433. flock/webapp/templates/partials/_dynamic_input_form_content.html +0 -22
  434. flock/webapp/templates/partials/_env_vars_table.html +0 -23
  435. flock/webapp/templates/partials/_execution_form.html +0 -118
  436. flock/webapp/templates/partials/_execution_view_container.html +0 -28
  437. flock/webapp/templates/partials/_flock_file_list.html +0 -23
  438. flock/webapp/templates/partials/_flock_properties_form.html +0 -52
  439. flock/webapp/templates/partials/_flock_upload_form.html +0 -16
  440. flock/webapp/templates/partials/_header_flock_status.html +0 -5
  441. flock/webapp/templates/partials/_load_manager_view.html +0 -49
  442. flock/webapp/templates/partials/_registry_table.html +0 -25
  443. flock/webapp/templates/partials/_registry_viewer_content.html +0 -70
  444. flock/webapp/templates/partials/_results_display.html +0 -78
  445. flock/webapp/templates/partials/_settings_env_content.html +0 -9
  446. flock/webapp/templates/partials/_settings_theme_content.html +0 -14
  447. flock/webapp/templates/partials/_settings_view.html +0 -36
  448. flock/webapp/templates/partials/_share_chat_link_snippet.html +0 -11
  449. flock/webapp/templates/partials/_share_link_snippet.html +0 -35
  450. flock/webapp/templates/partials/_sidebar.html +0 -74
  451. flock/webapp/templates/partials/_streaming_results_container.html +0 -195
  452. flock/webapp/templates/partials/_structured_data_view.html +0 -40
  453. flock/webapp/templates/partials/_theme_preview.html +0 -36
  454. flock/webapp/templates/registry_viewer.html +0 -84
  455. flock/webapp/templates/shared_run_page.html +0 -140
  456. flock/workflow/__init__.py +0 -0
  457. flock/workflow/activities.py +0 -196
  458. flock/workflow/agent_activities.py +0 -24
  459. flock/workflow/agent_execution_activity.py +0 -202
  460. flock/workflow/flock_workflow.py +0 -214
  461. flock/workflow/temporal_config.py +0 -96
  462. flock/workflow/temporal_setup.py +0 -68
  463. flock_core-0.5.0b28.dist-info/METADATA +0 -274
  464. flock_core-0.5.0b28.dist-info/RECORD +0 -561
  465. flock_core-0.5.0b28.dist-info/entry_points.txt +0 -2
  466. /flock/{core/logging → logging}/formatters/themes.py +0 -0
  467. /flock/{core/logging → logging}/span_middleware/baggage_span_processor.py +0 -0
  468. /flock/{core/mcp → mcp}/util/__init__.py +0 -0
  469. {flock_core-0.5.0b28.dist-info → flock_core-0.5.0b51.dist-info}/WHEEL +0 -0
@@ -0,0 +1,685 @@
1
+ import { useWSStore } from '../store/wsStore';
2
+ import { useGraphStore } from '../store/graphStore';
3
+
4
+ interface WebSocketMessage {
5
+ event_type: 'agent_activated' | 'message_published' | 'streaming_output' | 'agent_completed' | 'agent_error';
6
+ timestamp: string;
7
+ correlation_id: string;
8
+ session_id: string;
9
+ data: any;
10
+ }
11
+
12
+ interface StoreInterface {
13
+ addAgent: (agent: any) => void;
14
+ updateAgent: (id: string, updates: any) => void;
15
+ addMessage: (message: any) => void;
16
+ updateMessage: (id: string, updates: any) => void;
17
+ batchUpdate?: (update: any) => void;
18
+ }
19
+
20
+ export class WebSocketClient {
21
+ ws: WebSocket | null = null;
22
+ private reconnectTimeout: number | null = null;
23
+ private reconnectAttempt = 0;
24
+ private maxReconnectDelay = 30000; // 30 seconds
25
+ private connectionTimeout: number | null = null;
26
+ private connectionTimeoutMs = 10000; // 10 seconds
27
+ private messageBuffer: any[] = [];
28
+ private maxBufferSize = 100;
29
+ private eventHandlers: Map<string, ((data: any) => void)[]> = new Map();
30
+ private url: string;
31
+ private shouldReconnect = true;
32
+ private heartbeatInterval: number | null = null;
33
+ private heartbeatTimeout: number | null = null;
34
+ private connectionStatus: 'connecting' | 'connected' | 'disconnected' | 'disconnecting' | 'error' = 'disconnected';
35
+ private store: StoreInterface;
36
+ private enableHeartbeat: boolean;
37
+
38
+ constructor(url: string, mockStore?: StoreInterface) {
39
+ this.url = url;
40
+ this.store = mockStore || {
41
+ addAgent: (agent: any) => useGraphStore.getState().addAgent(agent),
42
+ updateAgent: (id: string, updates: any) => useGraphStore.getState().updateAgent(id, updates),
43
+ addMessage: (message: any) => useGraphStore.getState().addMessage(message),
44
+ updateMessage: (id: string, updates: any) => useGraphStore.getState().updateMessage(id, updates),
45
+ batchUpdate: (update: any) => useGraphStore.getState().batchUpdate(update),
46
+ };
47
+ // Phase 11 Fix: Disable heartbeat entirely - it causes unnecessary disconnects
48
+ // WebSocket auto-reconnects on real network issues without needing heartbeat
49
+ // The heartbeat was closing connections every 2min when backend didn't respond to pings
50
+ this.enableHeartbeat = false;
51
+ this.setupEventHandlers();
52
+ }
53
+
54
+ private setupEventHandlers(): void {
55
+ // Handler for agent_activated: create/update agent in graph AND create Run
56
+ this.on('agent_activated', (data) => {
57
+ const agents = useGraphStore.getState().agents;
58
+ const messages = useGraphStore.getState().messages;
59
+ const existingAgent = agents.get(data.agent_id);
60
+
61
+ // Count received messages by type
62
+ const receivedByType = { ...(existingAgent?.receivedByType || {}) };
63
+ if (data.consumed_artifacts && data.consumed_artifacts.length > 0) {
64
+ // Look up each consumed artifact and count by type
65
+ data.consumed_artifacts.forEach((artifactId: string) => {
66
+ const message = messages.get(artifactId);
67
+ if (message) {
68
+ receivedByType[message.type] = (receivedByType[message.type] || 0) + 1;
69
+ }
70
+ });
71
+ }
72
+
73
+ // Bug Fix #2: Preserve sentCount/recvCount if agent already exists
74
+ // Otherwise counters get reset to 0 on each activation
75
+ const agent = {
76
+ id: data.agent_id,
77
+ name: data.agent_name,
78
+ status: 'running' as const,
79
+ subscriptions: data.consumed_types || [],
80
+ lastActive: Date.now(),
81
+ sentCount: existingAgent?.sentCount || 0, // Preserve existing count
82
+ recvCount: (existingAgent?.recvCount || 0) + (data.consumed_artifacts?.length || 0), // Add new consumed artifacts
83
+ outputTypes: data.produced_types || [], // Get output types from backend
84
+ receivedByType, // Track per-type received counts
85
+ sentByType: existingAgent?.sentByType || {}, // Preserve sent counts
86
+ };
87
+ this.store.addAgent(agent);
88
+
89
+ // Phase 11 Bug Fix: Record actual consumption to track filtering
90
+ // This enables showing "(3, filtered: 1)" on edges
91
+ if (data.consumed_artifacts && data.consumed_artifacts.length > 0) {
92
+ useGraphStore.getState().recordConsumption(data.consumed_artifacts, data.agent_id);
93
+ }
94
+
95
+ // Create Run object for Blackboard View edges
96
+ // Bug Fix: Use run_id from backend (unique per agent activation) instead of correlation_id
97
+ const run = {
98
+ run_id: data.run_id || `run_${Date.now()}`, // data.run_id is ctx.task_id from backend
99
+ agent_name: data.agent_name,
100
+ correlation_id: data.correlation_id, // Separate field for grouping runs
101
+ status: 'active' as const,
102
+ consumed_artifacts: data.consumed_artifacts || [],
103
+ produced_artifacts: [], // Will be populated on message_published
104
+ started_at: new Date().toISOString(),
105
+ };
106
+ if (this.store.batchUpdate) {
107
+ this.store.batchUpdate({ runs: [run] });
108
+ }
109
+ });
110
+
111
+ // Handler for message_published: update existing streaming message or create new one
112
+ this.on('message_published', (data) => {
113
+ // Finalize or create the message
114
+ const messages = useGraphStore.getState().messages;
115
+ const streamingMessageId = `streaming_${data.produced_by}_${data.correlation_id}`;
116
+ const existingMessage = messages.get(streamingMessageId);
117
+
118
+ if (existingMessage) {
119
+ // Update existing streaming message with final data
120
+ const finalMessage = {
121
+ ...existingMessage,
122
+ id: data.artifact_id, // Replace temp ID with real artifact ID
123
+ type: data.artifact_type,
124
+ payload: data.payload,
125
+ isStreaming: false, // Streaming complete
126
+ streamingText: '', // Clear streaming text
127
+ };
128
+
129
+ // Use store action to properly update (triggers graph regeneration)
130
+ useGraphStore.getState().finalizeStreamingMessage(streamingMessageId, finalMessage);
131
+ } else {
132
+ // No streaming message - create new message directly
133
+ const message = {
134
+ id: data.artifact_id,
135
+ type: data.artifact_type,
136
+ payload: data.payload,
137
+ timestamp: data.timestamp ? new Date(data.timestamp).getTime() : Date.now(),
138
+ correlationId: data.correlation_id || '',
139
+ producedBy: data.produced_by,
140
+ isStreaming: false,
141
+ };
142
+ this.store.addMessage(message);
143
+ }
144
+
145
+ // Update producer agent counters (outputTypes come from agent_activated event now)
146
+ const producer = useGraphStore.getState().agents.get(data.produced_by);
147
+ if (producer) {
148
+ // Track sent count by type
149
+ const sentByType = { ...(producer.sentByType || {}) };
150
+ sentByType[data.artifact_type] = (sentByType[data.artifact_type] || 0) + 1;
151
+
152
+ this.store.updateAgent(data.produced_by, {
153
+ sentCount: (producer.sentCount || 0) + 1,
154
+ lastActive: Date.now(),
155
+ sentByType,
156
+ });
157
+ } else {
158
+ // Producer doesn't exist as a registered agent - create virtual agent
159
+ // This handles orchestrator-published artifacts (e.g., initial Idea from dashboard PublishControl)
160
+ this.store.addAgent({
161
+ id: data.produced_by,
162
+ name: data.produced_by,
163
+ status: 'idle' as const,
164
+ subscriptions: [],
165
+ lastActive: Date.now(),
166
+ sentCount: 1,
167
+ recvCount: 0,
168
+ outputTypes: [data.artifact_type], // Virtual agents get type from their first message
169
+ sentByType: { [data.artifact_type]: 1 },
170
+ receivedByType: {},
171
+ });
172
+ }
173
+
174
+ // Phase 11 Bug Fix: Increment consumers' recv count instead of setting to 1
175
+ if (data.consumers && Array.isArray(data.consumers)) {
176
+ const agents = useGraphStore.getState().agents;
177
+ data.consumers.forEach((consumerId: string) => {
178
+ const consumer = agents.get(consumerId);
179
+ if (consumer) {
180
+ this.store.updateAgent(consumerId, {
181
+ recvCount: (consumer.recvCount || 0) + 1,
182
+ lastActive: Date.now(),
183
+ });
184
+ }
185
+ });
186
+ }
187
+
188
+ // Update Run with produced artifact for Blackboard View edges
189
+ // Bug Fix: Find Run by agent_name + correlation_id since run_id is not in message_published event
190
+ if (data.correlation_id && this.store.batchUpdate) {
191
+ const runs = useGraphStore.getState().runs;
192
+ // Find the active Run for this agent + correlation_id
193
+ const run = Array.from(runs.values()).find(
194
+ r => r.agent_name === data.produced_by &&
195
+ r.correlation_id === data.correlation_id &&
196
+ r.status === 'active'
197
+ );
198
+ if (run) {
199
+ // Add artifact to produced_artifacts if not already present
200
+ if (!run.produced_artifacts.includes(data.artifact_id)) {
201
+ const updatedRun = {
202
+ ...run,
203
+ produced_artifacts: [...run.produced_artifacts, data.artifact_id],
204
+ };
205
+ this.store.batchUpdate({ runs: [updatedRun] });
206
+ }
207
+ }
208
+ }
209
+ });
210
+
211
+ // Handler for streaming_output: update live output (Phase 6)
212
+ this.on('streaming_output', (data) => {
213
+ // Phase 6: Update detail window live output
214
+ console.log('[WebSocket] Streaming output:', data);
215
+ // Update agent to show it's active and track streaming tokens for news ticker
216
+ if (data.agent_name && data.output_type === 'llm_token') {
217
+ const agents = useGraphStore.getState().agents;
218
+ const agent = agents.get(data.agent_name);
219
+ const currentTokens = agent?.streamingTokens || [];
220
+
221
+ // Keep only last 6 tokens (news ticker effect)
222
+ const updatedTokens = [...currentTokens, data.content].slice(-6);
223
+
224
+ this.store.updateAgent(data.agent_name, {
225
+ lastActive: Date.now(),
226
+ streamingTokens: updatedTokens,
227
+ });
228
+ }
229
+
230
+ // Create/update streaming message node for blackboard view
231
+ if (data.output_type === 'llm_token' && data.agent_name && data.correlation_id) {
232
+ const messages = useGraphStore.getState().messages;
233
+ // Use agent_name + correlation_id as temporary ID
234
+ const streamingMessageId = `streaming_${data.agent_name}_${data.correlation_id}`;
235
+ const existingMessage = messages.get(streamingMessageId);
236
+
237
+ if (existingMessage) {
238
+ // Append token to existing streaming message using updateMessage
239
+ // This updates the messages Map without flooding the events array
240
+ this.store.updateMessage(streamingMessageId, {
241
+ streamingText: (existingMessage.streamingText || '') + data.content,
242
+ timestamp: data.timestamp ? new Date(data.timestamp).getTime() : Date.now(),
243
+ });
244
+ } else if (data.sequence === 0 || !existingMessage) {
245
+ // Look up agent's typical output type
246
+ const agents = useGraphStore.getState().agents;
247
+ const agent = agents.get(data.agent_name);
248
+ const outputType = agent?.outputTypes?.[0] || 'output';
249
+
250
+ // Create new streaming message on first token
251
+ const streamingMessage = {
252
+ id: streamingMessageId,
253
+ type: outputType, // Use agent's known output type
254
+ payload: {},
255
+ timestamp: data.timestamp ? new Date(data.timestamp).getTime() : Date.now(),
256
+ correlationId: data.correlation_id || '',
257
+ producedBy: data.agent_name,
258
+ isStreaming: true,
259
+ streamingText: data.content,
260
+ };
261
+ this.store.addMessage(streamingMessage);
262
+ }
263
+ }
264
+
265
+ // Note: The actual output storage is handled by LiveOutputTab's event listener
266
+ // This handler is for store updates only
267
+ });
268
+
269
+ // Handler for agent_completed: update agent status to idle
270
+ this.on('agent_completed', (data) => {
271
+ this.store.updateAgent(data.agent_name, {
272
+ status: 'idle',
273
+ lastActive: Date.now(),
274
+ streamingTokens: [], // Clear news ticker on completion
275
+ });
276
+
277
+ // Update Run status to completed for Blackboard View edges
278
+ // Bug Fix: Use run_id from event data (agent_completed has run_id)
279
+ if (data.run_id && this.store.batchUpdate) {
280
+ const runs = useGraphStore.getState().runs;
281
+ const run = runs.get(data.run_id);
282
+ if (run) {
283
+ const updatedRun = {
284
+ ...run,
285
+ status: 'completed' as const,
286
+ completed_at: new Date().toISOString(),
287
+ duration_ms: data.duration_ms,
288
+ };
289
+ this.store.batchUpdate({ runs: [updatedRun] });
290
+ }
291
+ }
292
+ });
293
+
294
+ // Handler for agent_error: update agent status to error
295
+ this.on('agent_error', (data) => {
296
+ this.store.updateAgent(data.agent_name, {
297
+ status: 'error',
298
+ lastActive: Date.now(),
299
+ });
300
+
301
+ // Update Run status to error
302
+ // Bug Fix: Use run_id from event data (agent_error has run_id)
303
+ if (data.run_id && this.store.batchUpdate) {
304
+ const runs = useGraphStore.getState().runs;
305
+ const run = runs.get(data.run_id);
306
+ if (run) {
307
+ const updatedRun = {
308
+ ...run,
309
+ status: 'error' as const,
310
+ completed_at: new Date().toISOString(),
311
+ error_message: data.error_message || 'Unknown error',
312
+ };
313
+ this.store.batchUpdate({ runs: [updatedRun] });
314
+ }
315
+ }
316
+ });
317
+
318
+ // Handler for ping: respond with pong
319
+ this.on('ping', () => {
320
+ this.send({ type: 'pong', timestamp: Date.now() });
321
+ });
322
+ }
323
+
324
+ connect(): void {
325
+ if (this.ws?.readyState === WebSocket.OPEN || this.ws?.readyState === WebSocket.CONNECTING) {
326
+ return;
327
+ }
328
+
329
+ try {
330
+ this.connectionStatus = 'connecting';
331
+ if (typeof useWSStore !== 'undefined') {
332
+ useWSStore.getState().setStatus('connecting');
333
+ }
334
+
335
+ this.ws = new WebSocket(this.url);
336
+
337
+ // Set connection timeout
338
+ this.connectionTimeout = window.setTimeout(() => {
339
+ console.warn('[WebSocket] Connection timeout');
340
+ if (this.ws && this.ws.readyState !== WebSocket.OPEN) {
341
+ this.ws.close();
342
+ this.connectionStatus = 'error';
343
+ if (typeof useWSStore !== 'undefined') {
344
+ useWSStore.getState().setStatus('disconnected');
345
+ useWSStore.getState().setError('Connection timeout');
346
+ }
347
+ if (this.shouldReconnect) {
348
+ this.reconnect();
349
+ }
350
+ }
351
+ }, this.connectionTimeoutMs);
352
+
353
+ this.ws.onopen = () => {
354
+ console.log('[WebSocket] Connected');
355
+
356
+ // Clear connection timeout
357
+ if (this.connectionTimeout !== null) {
358
+ clearTimeout(this.connectionTimeout);
359
+ this.connectionTimeout = null;
360
+ }
361
+
362
+ this.connectionStatus = 'connected';
363
+ if (typeof useWSStore !== 'undefined') {
364
+ useWSStore.getState().setStatus('connected');
365
+ useWSStore.getState().setError(null);
366
+ useWSStore.getState().resetAttempts();
367
+ }
368
+ this.reconnectAttempt = 0;
369
+ this.flushBuffer();
370
+ if (this.enableHeartbeat) {
371
+ this.startHeartbeat();
372
+ }
373
+ };
374
+
375
+ this.ws.onmessage = (event: MessageEvent) => {
376
+ this.handleMessage(event);
377
+ };
378
+
379
+ this.ws.onerror = (error) => {
380
+ console.error('[WebSocket] Error:', error);
381
+ // Keep connection status as error even after close event
382
+ this.connectionStatus = 'error';
383
+ if (typeof useWSStore !== 'undefined') {
384
+ useWSStore.getState().setError('Connection error');
385
+ useWSStore.getState().setStatus('disconnected');
386
+ }
387
+ };
388
+
389
+ this.ws.onclose = (event) => {
390
+ console.log('[WebSocket] Closed:', event.code, event.reason);
391
+ this.stopHeartbeat();
392
+
393
+ // Don't override error status
394
+ if (this.connectionStatus !== 'error') {
395
+ if (this.shouldReconnect && event.code !== 1000) {
396
+ this.connectionStatus = 'connecting'; // Will be reconnecting
397
+ if (typeof useWSStore !== 'undefined') {
398
+ useWSStore.getState().setStatus('reconnecting');
399
+ }
400
+ this.reconnect();
401
+ } else {
402
+ this.connectionStatus = 'disconnected';
403
+ if (typeof useWSStore !== 'undefined') {
404
+ useWSStore.getState().setStatus('disconnected');
405
+ }
406
+ }
407
+ }
408
+ };
409
+ } catch (error) {
410
+ console.error('[WebSocket] Connection failed:', error);
411
+ this.connectionStatus = 'error';
412
+ if (typeof useWSStore !== 'undefined') {
413
+ useWSStore.getState().setStatus('disconnected');
414
+ useWSStore.getState().setError(error instanceof Error ? error.message : 'Connection failed');
415
+ }
416
+ if (this.shouldReconnect) {
417
+ this.reconnect();
418
+ }
419
+ }
420
+ }
421
+
422
+ private reconnect(): void {
423
+ if (this.reconnectTimeout !== null) {
424
+ return; // Already scheduled
425
+ }
426
+
427
+ // Exponential backoff: 1s, 2s, 4s, 8s, max 30s
428
+ const delay = Math.min(1000 * Math.pow(2, this.reconnectAttempt), this.maxReconnectDelay);
429
+
430
+ if (typeof useWSStore !== 'undefined') {
431
+ useWSStore.getState().incrementAttempts();
432
+ }
433
+ this.reconnectAttempt++;
434
+
435
+ console.log(`[WebSocket] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempt})`);
436
+
437
+ this.reconnectTimeout = window.setTimeout(() => {
438
+ this.reconnectTimeout = null;
439
+ this.connect();
440
+ }, delay);
441
+ }
442
+
443
+ private handleMessage(event: MessageEvent): void {
444
+ try {
445
+ const data = JSON.parse(event.data);
446
+
447
+ // Handle direct type field (for ping/pong)
448
+ if (data.type === 'ping') {
449
+ this.send({ type: 'pong', timestamp: Date.now() });
450
+ return;
451
+ }
452
+
453
+ if (data.type === 'pong') {
454
+ this.resetHeartbeatTimeout();
455
+ return;
456
+ }
457
+
458
+ // Handle WebSocketMessage envelope
459
+ const message: WebSocketMessage = data;
460
+
461
+ // Handle pong as event_type
462
+ if (message.event_type === 'pong' as any) {
463
+ this.resetHeartbeatTimeout();
464
+ return;
465
+ }
466
+
467
+ // Determine if this is an envelope or raw data
468
+ // If it has event_type, it's an envelope; use message.data
469
+ // Otherwise, it's raw data (for tests)
470
+ const eventData = message.event_type ? message.data : data;
471
+
472
+ // Try to detect event type from data
473
+ let eventType = message.event_type;
474
+ if (!eventType) {
475
+ // Infer event type from data structure for test compatibility
476
+ if (data.agent_id && data.consumed_types) {
477
+ eventType = 'agent_activated';
478
+ } else if (data.artifact_id && data.artifact_type) {
479
+ eventType = 'message_published';
480
+ } else if (data.run_id && data.output_type) {
481
+ eventType = 'streaming_output';
482
+ } else if (data.run_id && data.duration_ms !== undefined) {
483
+ eventType = 'agent_completed';
484
+ } else if (data.run_id && data.error_type) {
485
+ eventType = 'agent_error';
486
+ }
487
+ }
488
+
489
+ // Dispatch to registered handlers
490
+ if (eventType) {
491
+ const handlers = this.eventHandlers.get(eventType);
492
+ if (handlers) {
493
+ handlers.forEach((handler) => {
494
+ try {
495
+ handler(eventData);
496
+ } catch (error) {
497
+ console.error(`[WebSocket] Handler error for ${eventType}:`, error);
498
+ }
499
+ });
500
+ }
501
+ }
502
+ } catch (error) {
503
+ console.error('[WebSocket] Failed to parse message:', error);
504
+ }
505
+ }
506
+
507
+ send(message: any): void {
508
+ if (this.ws?.readyState === WebSocket.OPEN) {
509
+ try {
510
+ this.ws.send(JSON.stringify(message));
511
+ } catch (error) {
512
+ console.error('[WebSocket] Send failed:', error);
513
+ this.bufferMessage(message);
514
+ }
515
+ } else {
516
+ this.bufferMessage(message);
517
+ }
518
+ }
519
+
520
+ private bufferMessage(message: any): void {
521
+ if (this.messageBuffer.length >= this.maxBufferSize) {
522
+ this.messageBuffer.shift(); // Remove oldest message
523
+ }
524
+ this.messageBuffer.push(message);
525
+ }
526
+
527
+ private flushBuffer(): void {
528
+ if (this.messageBuffer.length === 0) {
529
+ return;
530
+ }
531
+
532
+ console.log(`[WebSocket] Flushing ${this.messageBuffer.length} buffered messages`);
533
+
534
+ const messages = [...this.messageBuffer];
535
+ this.messageBuffer = [];
536
+
537
+ messages.forEach((message) => {
538
+ // Send directly to avoid re-buffering
539
+ if (this.ws?.readyState === WebSocket.OPEN) {
540
+ try {
541
+ this.ws.send(JSON.stringify(message));
542
+ } catch (error) {
543
+ console.error('[WebSocket] Failed to send buffered message:', error);
544
+ }
545
+ }
546
+ });
547
+ }
548
+
549
+ on(eventType: string, handler: (data: any) => void): void {
550
+ if (!this.eventHandlers.has(eventType)) {
551
+ this.eventHandlers.set(eventType, []);
552
+ }
553
+ this.eventHandlers.get(eventType)!.push(handler);
554
+ }
555
+
556
+ off(eventType: string, handler: (data: any) => void): void {
557
+ const handlers = this.eventHandlers.get(eventType);
558
+ if (handlers) {
559
+ const index = handlers.indexOf(handler);
560
+ if (index > -1) {
561
+ handlers.splice(index, 1);
562
+ }
563
+ }
564
+ }
565
+
566
+ private startHeartbeat(): void {
567
+ this.stopHeartbeat();
568
+
569
+ // Send ping every 2 minutes
570
+ this.heartbeatInterval = window.setInterval(() => {
571
+ if (this.ws?.readyState === WebSocket.OPEN) {
572
+ this.send({ type: 'ping' });
573
+
574
+ // Set timeout for pong response (10 seconds)
575
+ this.heartbeatTimeout = window.setTimeout(() => {
576
+ console.warn('[WebSocket] Heartbeat timeout, closing connection');
577
+ this.ws?.close();
578
+ }, 10000);
579
+ }
580
+ }, 120000);
581
+ }
582
+
583
+ private stopHeartbeat(): void {
584
+ if (this.heartbeatInterval !== null) {
585
+ clearInterval(this.heartbeatInterval);
586
+ this.heartbeatInterval = null;
587
+ }
588
+ if (this.heartbeatTimeout !== null) {
589
+ clearTimeout(this.heartbeatTimeout);
590
+ this.heartbeatTimeout = null;
591
+ }
592
+ }
593
+
594
+ private resetHeartbeatTimeout(): void {
595
+ if (this.heartbeatTimeout !== null) {
596
+ clearTimeout(this.heartbeatTimeout);
597
+ this.heartbeatTimeout = null;
598
+ }
599
+ }
600
+
601
+ disconnect(): void {
602
+ this.shouldReconnect = false;
603
+ this.connectionStatus = 'disconnecting';
604
+
605
+ if (this.reconnectTimeout !== null) {
606
+ clearTimeout(this.reconnectTimeout);
607
+ this.reconnectTimeout = null;
608
+ }
609
+
610
+ if (this.connectionTimeout !== null) {
611
+ clearTimeout(this.connectionTimeout);
612
+ this.connectionTimeout = null;
613
+ }
614
+
615
+ this.stopHeartbeat();
616
+
617
+ if (this.ws) {
618
+ this.ws.close();
619
+ this.ws = null;
620
+ }
621
+
622
+ // Status will be set to 'disconnected' by onclose handler
623
+ // Don't override it here to maintain proper status flow
624
+ }
625
+
626
+ reconnectManually(): void {
627
+ this.shouldReconnect = true;
628
+ this.reconnectAttempt = 0;
629
+ if (typeof useWSStore !== 'undefined') {
630
+ useWSStore.getState().resetAttempts();
631
+ }
632
+ this.connect();
633
+ }
634
+
635
+ // Test helper methods
636
+ isConnected(): boolean {
637
+ return this.ws?.readyState === WebSocket.OPEN && this.connectionStatus !== 'error';
638
+ }
639
+
640
+ getConnectionStatus(): string {
641
+ return this.connectionStatus;
642
+ }
643
+
644
+ getBufferedMessageCount(): number {
645
+ return this.messageBuffer.length;
646
+ }
647
+
648
+ getStatus(): string {
649
+ if (!this.ws) return 'disconnected';
650
+
651
+ switch (this.ws.readyState) {
652
+ case WebSocket.CONNECTING:
653
+ return 'connecting';
654
+ case WebSocket.OPEN:
655
+ return 'connected';
656
+ case WebSocket.CLOSING:
657
+ return 'disconnecting';
658
+ case WebSocket.CLOSED:
659
+ return 'disconnected';
660
+ default:
661
+ return 'disconnected';
662
+ }
663
+ }
664
+ }
665
+
666
+ // Singleton instance
667
+ let wsClient: WebSocketClient | null = null;
668
+
669
+ export const getWebSocketClient = (url?: string): WebSocketClient => {
670
+ if (!wsClient && url) {
671
+ wsClient = new WebSocketClient(url);
672
+ }
673
+ if (!wsClient) {
674
+ throw new Error('WebSocket client not initialized');
675
+ }
676
+ return wsClient;
677
+ };
678
+
679
+ export const initializeWebSocket = (url: string): WebSocketClient => {
680
+ if (wsClient) {
681
+ wsClient.disconnect();
682
+ }
683
+ wsClient = new WebSocketClient(url);
684
+ return wsClient;
685
+ };