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,587 @@
1
+ /**
2
+ * End-to-End Tests for Critical Dashboard Scenarios (Frontend)
3
+ *
4
+ * Tests the 4 critical scenarios from SDD_COMPLETION.md (lines 444-493):
5
+ * 1. End-to-End Agent Execution Visualization (WebSocket → stores → React Flow rendering)
6
+ * 2. WebSocket Reconnection After Backend Restart (client-side resilience)
7
+ * 3. Correlation ID Filtering (autocomplete → filter → graph updates)
8
+ * 4. IndexedDB LRU Eviction (storage quota management with custom mocking)
9
+ *
10
+ * SPECIFICATION: docs/specs/003-real-time-dashboard/SDD_COMPLETION.md Section: Critical Test Scenarios
11
+ *
12
+ * These tests validate the complete frontend flow from WebSocket events through
13
+ * Zustand stores to React Flow graph visualization.
14
+ */
15
+
16
+ import 'fake-indexeddb/auto'; // Polyfills global IndexedDB for tests
17
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
18
+ import { waitFor, act } from '@testing-library/react';
19
+ import { WebSocketClient } from '../../services/websocket';
20
+ import { useGraphStore } from '../../store/graphStore';
21
+ import { useFilterStore } from '../../store/filterStore';
22
+ import { useWSStore } from '../../store/wsStore';
23
+ import { indexedDBService } from '../../services/indexeddb';
24
+ import { CorrelationIdMetadata } from '../../types/filters';
25
+
26
+ // ============================================================================
27
+ // Mock WebSocket Client
28
+ // ============================================================================
29
+
30
+ class MockWebSocket {
31
+ url: string;
32
+ readyState: number;
33
+ onopen: ((event: Event) => void) | null = null;
34
+ onmessage: ((event: MessageEvent) => void) | null = null;
35
+ onerror: ((event: Event) => void) | null = null;
36
+ onclose: ((event: CloseEvent) => void) | null = null;
37
+
38
+ static CONNECTING = 0;
39
+ static OPEN = 1;
40
+ static CLOSING = 2;
41
+ static CLOSED = 3;
42
+
43
+ constructor(url: string) {
44
+ this.url = url;
45
+ this.readyState = MockWebSocket.CONNECTING;
46
+
47
+ // Auto-connect after construction (unless disabled for testing)
48
+ if (MockWebSocket.autoConnect) {
49
+ setTimeout(() => {
50
+ this.readyState = MockWebSocket.OPEN;
51
+ this.onopen?.(new Event('open'));
52
+ }, 0);
53
+ } else {
54
+ // If auto-connect is disabled, simulate connection failure
55
+ setTimeout(() => {
56
+ this.readyState = MockWebSocket.CLOSED;
57
+ // Only fire close event (not error) to avoid setting error status
58
+ // This allows the client to keep retrying with exponential backoff
59
+ this.onclose?.(new CloseEvent('close', { code: 1006, reason: 'Connection failed' }));
60
+ }, 0);
61
+ }
62
+ }
63
+
64
+ send(_data: string) {
65
+ if (this.readyState !== MockWebSocket.OPEN) {
66
+ throw new Error('WebSocket is not open');
67
+ }
68
+ }
69
+
70
+ close() {
71
+ this.readyState = MockWebSocket.CLOSED;
72
+ this.onclose?.(new CloseEvent('close'));
73
+ }
74
+
75
+ // Test helper: simulate receiving message
76
+ simulateMessage(data: any) {
77
+ if (this.readyState === MockWebSocket.OPEN) {
78
+ this.onmessage?.(new MessageEvent('message', { data: JSON.stringify(data) }));
79
+ }
80
+ }
81
+
82
+ // Test helper: simulate connection error
83
+ simulateError() {
84
+ this.onerror?.(new Event('error'));
85
+ }
86
+
87
+ // Test helper: simulate disconnection
88
+ simulateDisconnect() {
89
+ this.readyState = MockWebSocket.CLOSED;
90
+ this.onclose?.(new CloseEvent('close'));
91
+ }
92
+
93
+ // Test helper: simulate reconnection
94
+ simulateReconnect() {
95
+ this.readyState = MockWebSocket.OPEN;
96
+ this.onopen?.(new Event('open'));
97
+ }
98
+
99
+ // Test helper: prevent auto-connection (for testing reconnection retries)
100
+ preventAutoConnect() {
101
+ (this.constructor as any).autoConnect = false;
102
+ }
103
+
104
+ static autoConnect = true;
105
+ }
106
+
107
+ // ============================================================================
108
+ // Mock Storage Quota API for LRU Testing
109
+ // ============================================================================
110
+
111
+ class MockStorageManager {
112
+ private mockUsage: number = 0;
113
+ private mockQuota: number = 50 * 1024 * 1024; // 50MB default quota
114
+ private usageSequence: number[] = [];
115
+ private sequenceIndex: number = 0;
116
+
117
+ async estimate(): Promise<{ usage: number; quota: number }> {
118
+ // Use fixed sequence if configured, otherwise return current usage
119
+ let currentUsage = this.mockUsage;
120
+ if (this.usageSequence.length > 0) {
121
+ currentUsage = this.usageSequence[Math.min(this.sequenceIndex, this.usageSequence.length - 1)]!;
122
+ this.sequenceIndex++;
123
+ }
124
+
125
+ return {
126
+ usage: currentUsage,
127
+ quota: this.mockQuota,
128
+ };
129
+ }
130
+
131
+ // Test helpers
132
+ setUsage(bytes: number) {
133
+ this.mockUsage = bytes;
134
+ this.sequenceIndex = 0;
135
+ }
136
+
137
+ setQuota(bytes: number) {
138
+ this.mockQuota = bytes;
139
+ }
140
+
141
+ // Set a sequence of usage values to return (simulates deletion reducing usage)
142
+ setUsageSequence(sequence: number[]) {
143
+ this.usageSequence = sequence;
144
+ this.sequenceIndex = 0;
145
+ }
146
+
147
+ getUsagePercentage(): number {
148
+ return this.mockQuota > 0 ? this.mockUsage / this.mockQuota : 0;
149
+ }
150
+ }
151
+
152
+ // ============================================================================
153
+ // Test Setup and Fixtures
154
+ // ============================================================================
155
+
156
+ describe('Critical E2E Scenarios (Frontend)', () => {
157
+ let mockWs: any;
158
+ let mockStorageManager: MockStorageManager;
159
+ let wsClient: WebSocketClient;
160
+
161
+ beforeEach(() => {
162
+ // Reset all stores
163
+ const graphStore = useGraphStore.getState();
164
+ graphStore.agents.clear();
165
+ graphStore.messages.clear();
166
+ graphStore.events = [];
167
+ graphStore.runs.clear();
168
+ graphStore.consumptions.clear();
169
+
170
+ const filterStore = useFilterStore.getState();
171
+ filterStore.clearFilters();
172
+ filterStore.updateAvailableCorrelationIds([]);
173
+
174
+ const wsStore = useWSStore.getState();
175
+ wsStore.setStatus('disconnected');
176
+ wsStore.setError(null);
177
+ wsStore.resetAttempts();
178
+
179
+ // Setup mock WebSocket
180
+ (globalThis as any).WebSocket = MockWebSocket;
181
+ MockWebSocket.autoConnect = true; // Reset auto-connect flag
182
+
183
+ // Setup mock storage manager
184
+ mockStorageManager = new MockStorageManager();
185
+ if (!navigator.storage) {
186
+ (navigator as any).storage = {};
187
+ }
188
+ navigator.storage.estimate = mockStorageManager.estimate.bind(mockStorageManager);
189
+ });
190
+
191
+ afterEach(() => {
192
+ // Cleanup
193
+ if (wsClient) {
194
+ wsClient.disconnect();
195
+ }
196
+ vi.clearAllTimers();
197
+ });
198
+
199
+ // ==========================================================================
200
+ // Scenario 1: End-to-End Agent Execution Visualization
201
+ // ==========================================================================
202
+
203
+ describe('Scenario 1: End-to-End Agent Execution Visualization', () => {
204
+ it('should render complete agent execution flow from WebSocket events', async () => {
205
+ /**
206
+ * GIVEN: WebSocket client connected
207
+ * WHEN: Receive agent_activated → message_published → agent_completed sequence
208
+ * THEN: Graph nodes and edges are created correctly
209
+ * AND: Agent status transitions are tracked
210
+ */
211
+
212
+ // Setup: Create WebSocket client
213
+ wsClient = new WebSocketClient('ws://localhost:8000/ws');
214
+ wsClient.connect();
215
+
216
+ await waitFor(() => expect(wsClient.isConnected()).toBe(true));
217
+ mockWs = wsClient.ws as any;
218
+
219
+ // Step 1: Agent activated (raw format for test compatibility)
220
+ await act(async () => {
221
+ mockWs.simulateMessage({
222
+ agent_id: 'test_agent',
223
+ agent_name: 'test_agent',
224
+ run_id: 'run_123',
225
+ consumed_types: ['Input'],
226
+ consumed_artifacts: ['input-1'],
227
+ correlation_id: 'test-correlation-1',
228
+ });
229
+ });
230
+
231
+ await waitFor(() => {
232
+ const agents = useGraphStore.getState().agents;
233
+ expect(agents.has('test_agent')).toBe(true);
234
+ });
235
+
236
+ // Verify agent state
237
+ const agent = useGraphStore.getState().agents.get('test_agent');
238
+ expect(agent?.status).toBe('running');
239
+ expect(agent?.recvCount).toBe(1);
240
+
241
+ // Step 2: Message published (raw format for test compatibility)
242
+ await act(async () => {
243
+ mockWs.simulateMessage({
244
+ artifact_id: 'output-1',
245
+ artifact_type: 'Output',
246
+ produced_by: 'test_agent',
247
+ payload: { result: 'success' },
248
+ correlation_id: 'test-correlation-1',
249
+ });
250
+ });
251
+
252
+ await waitFor(() => {
253
+ const messages = useGraphStore.getState().messages;
254
+ expect(messages.has('output-1')).toBe(true);
255
+ });
256
+
257
+ // Verify message state
258
+ const message = useGraphStore.getState().messages.get('output-1');
259
+ expect(message?.type).toBe('Output');
260
+ expect(message?.producedBy).toBe('test_agent');
261
+
262
+ // Step 3: Agent completed (raw format for test compatibility)
263
+ await act(async () => {
264
+ mockWs.simulateMessage({
265
+ agent_name: 'test_agent',
266
+ run_id: 'run_123',
267
+ duration_ms: 150,
268
+ artifacts_produced: ['output-1'],
269
+ });
270
+ });
271
+
272
+ await waitFor(() => {
273
+ const agent = useGraphStore.getState().agents.get('test_agent');
274
+ expect(agent?.status).toBe('idle');
275
+ });
276
+
277
+ // Verify final state
278
+ const finalState = useGraphStore.getState();
279
+ expect(finalState.agents.size).toBe(1);
280
+ expect(finalState.messages.size).toBe(1);
281
+ expect(finalState.runs.size).toBeGreaterThan(0);
282
+ });
283
+ });
284
+
285
+ // ==========================================================================
286
+ // Scenario 2: WebSocket Reconnection After Backend Restart
287
+ // ==========================================================================
288
+
289
+ describe('Scenario 2: WebSocket Reconnection After Backend Restart', () => {
290
+ it('should handle connection loss and automatic reconnection with exponential backoff', { timeout: 20000 }, async () => {
291
+ /**
292
+ * GIVEN: Active WebSocket connection
293
+ * WHEN: Connection is lost (backend restart simulation)
294
+ * THEN: Client attempts reconnection with exponential backoff (1s, 2s, 4s, 8s)
295
+ * AND: Successfully reconnects when backend is available
296
+ * AND: Reconnect attempts counter is reset to 0
297
+ *
298
+ * APPROACH: Use MockWebSocket with controlled auto-connect behavior (no fake timers).
299
+ * Wait for real time to pass to validate exponential backoff intervals.
300
+ */
301
+
302
+ // Step 1: Connect WebSocket client (using MockWebSocket)
303
+ wsClient = new WebSocketClient('ws://localhost:8000/ws');
304
+ wsClient.connect();
305
+
306
+ await waitFor(() => expect(wsClient.isConnected()).toBe(true), { timeout: 5000 });
307
+ mockWs = wsClient.ws as any;
308
+
309
+ // Step 2: Simulate connection loss
310
+ await act(async () => {
311
+ // Disable auto-connect so reconnection attempts fail
312
+ MockWebSocket.autoConnect = false;
313
+ // Simulate abnormal disconnect (code != 1000 triggers reconnection)
314
+ mockWs.readyState = MockWebSocket.CLOSED;
315
+ mockWs.onclose?.(new CloseEvent('close', { code: 1006, reason: 'Server shutdown' }));
316
+ });
317
+
318
+ // Wait for reconnection status
319
+ await waitFor(() => {
320
+ expect(wsClient.isConnected()).toBe(false);
321
+ expect(useWSStore.getState().status).toBe('reconnecting');
322
+ }, { timeout: 2000 });
323
+
324
+ // Record initial attempt count
325
+ const initialAttempts = useWSStore.getState().reconnectAttempts;
326
+ expect(initialAttempts).toBeGreaterThan(0);
327
+
328
+ // Step 3: Wait for multiple reconnection attempts with exponential backoff
329
+ // Backoff: 1s, 2s, 4s, 8s
330
+ // Total time for 4 attempts: ~15s
331
+ // We'll wait 8s to capture 3-4 attempts (1s + 2s + 4s = 7s + margin)
332
+ await new Promise((resolve) => setTimeout(resolve, 8000));
333
+
334
+ // Verify reconnection attempts increased
335
+ const attemptsAfterBackoff = useWSStore.getState().reconnectAttempts;
336
+ expect(attemptsAfterBackoff).toBeGreaterThanOrEqual(3);
337
+ expect(useWSStore.getState().status).toBe('reconnecting');
338
+
339
+ // Step 4: Re-enable auto-connect to allow successful reconnection
340
+ await act(async () => {
341
+ MockWebSocket.autoConnect = true;
342
+ });
343
+
344
+ // Wait for next reconnection attempt to succeed (max 8s for next backoff)
345
+ await waitFor(() => {
346
+ expect(wsClient.isConnected()).toBe(true);
347
+ expect(useWSStore.getState().status).toBe('connected');
348
+ }, { timeout: 10000 });
349
+
350
+ // Step 5: Verify reconnect counter is reset
351
+ const finalStore = useWSStore.getState();
352
+ expect(finalStore.reconnectAttempts).toBe(0);
353
+
354
+ // Cleanup: Reset auto-connect for other tests
355
+ MockWebSocket.autoConnect = true;
356
+ });
357
+ });
358
+
359
+ // ==========================================================================
360
+ // Scenario 3: Correlation ID Filtering
361
+ // ==========================================================================
362
+
363
+ describe('Scenario 3: Correlation ID Filtering', () => {
364
+ it('should filter graph nodes and edges by selected correlation ID', { timeout: 10000 }, async () => {
365
+ /**
366
+ * GIVEN: Multiple artifacts with different correlation IDs
367
+ * WHEN: User selects a correlation ID filter
368
+ * THEN: Only artifacts/agents with matching correlation ID are visible
369
+ * AND: Graph edges are updated to reflect filtered view
370
+ */
371
+
372
+ // Setup: Connect WebSocket
373
+ wsClient = new WebSocketClient('ws://localhost:8000/ws');
374
+ wsClient.connect();
375
+ await waitFor(() => expect(wsClient.isConnected()).toBe(true));
376
+ mockWs = wsClient.ws as any;
377
+
378
+ // Setup: Receive events with 3 different correlation IDs
379
+ const correlationIds = ['abc-123-xxx', 'abc-456-yyy', 'def-789-zzz'];
380
+
381
+ for (let i = 0; i < correlationIds.length; i++) {
382
+ await act(async () => {
383
+ mockWs.simulateMessage({
384
+ artifact_id: `artifact-${i}`,
385
+ artifact_type: 'TestOutput',
386
+ produced_by: 'test_agent',
387
+ payload: { index: i },
388
+ correlation_id: correlationIds[i],
389
+ timestamp: new Date(Date.now() + i * 1000).toISOString(),
390
+ });
391
+ });
392
+ }
393
+
394
+ // Wait for all messages to be added to the store
395
+ await waitFor(() => {
396
+ const messages = useGraphStore.getState().messages;
397
+ expect(messages.size).toBe(3);
398
+ }, { timeout: 5000 });
399
+
400
+ // Update available correlation IDs
401
+ const metadata: CorrelationIdMetadata[] = correlationIds.map((id, index) => ({
402
+ correlation_id: id,
403
+ first_seen: Date.now() + index * 1000,
404
+ artifact_count: 1,
405
+ run_count: 1,
406
+ }));
407
+ useFilterStore.getState().updateAvailableCorrelationIds(metadata);
408
+
409
+ // Generate graph with all events (use fresh state)
410
+ await act(async () => {
411
+ useGraphStore.getState().generateBlackboardViewGraph();
412
+ });
413
+
414
+ // Wait for nodes to be generated
415
+ await waitFor(() => {
416
+ const nodes = useGraphStore.getState().nodes;
417
+ const visibleNodes = nodes.filter((n) => !n.hidden);
418
+ expect(visibleNodes.length).toBe(3);
419
+ }, { timeout: 5000 });
420
+
421
+ // Apply correlation ID filter (use fresh state)
422
+ await act(async () => {
423
+ useFilterStore.getState().setCorrelationId('abc-123-xxx');
424
+ useGraphStore.getState().applyFilters();
425
+ });
426
+
427
+ // Verify: Only 1 node visible (the one with matching correlation ID)
428
+ await waitFor(() => {
429
+ const filteredNodes = useGraphStore.getState().nodes.filter((n) => !n.hidden);
430
+ expect(filteredNodes.length).toBe(1);
431
+ expect(filteredNodes[0]?.id).toBe('artifact-0');
432
+ }, { timeout: 5000 });
433
+
434
+ // Verify: Edges connected to hidden nodes are also hidden
435
+ const visibleEdges = useGraphStore.getState().edges.filter((e) => !e.hidden);
436
+ // Should be 0 since we don't have transformation edges without runs
437
+ expect(visibleEdges.length).toBe(0);
438
+ });
439
+ });
440
+
441
+ // ==========================================================================
442
+ // Scenario 4: IndexedDB LRU Eviction
443
+ // ==========================================================================
444
+
445
+ describe('Scenario 4: IndexedDB LRU Eviction', () => {
446
+ beforeEach(async () => {
447
+ // Clean up IndexedDB between tests
448
+ if (indexedDBService.db) {
449
+ indexedDBService.db.close();
450
+ indexedDBService.db = null;
451
+ }
452
+ // Delete the database to ensure fresh state
453
+ if (typeof indexedDB !== 'undefined') {
454
+ const deleteRequest = indexedDB.deleteDatabase('flock_dashboard_v1');
455
+ await new Promise<void>((resolve, reject) => {
456
+ deleteRequest.onsuccess = () => resolve();
457
+ deleteRequest.onerror = () => reject(deleteRequest.error);
458
+ deleteRequest.onblocked = () => resolve(); // Continue even if blocked
459
+ });
460
+ }
461
+ });
462
+
463
+ it('should evict oldest sessions when storage quota exceeds 80% threshold', async () => {
464
+ /**
465
+ * GIVEN: Storage usage at 84% (above 80% threshold)
466
+ * WHEN: LRU eviction is triggered
467
+ * THEN: Oldest sessions are evicted until usage drops to 60% target
468
+ * AND: Most recent sessions are preserved
469
+ * AND: Current session data is preserved
470
+ */
471
+
472
+ const quota = 50 * 1024 * 1024; // 50MB
473
+ mockStorageManager.setQuota(quota);
474
+
475
+ // Setup: Configure usage sequence to simulate eviction progress
476
+ // Start at 84% (42MB), after deleting 2 sessions reach 60% (30MB)
477
+ const initialUsage = quota * 0.84; // 42MB
478
+ const afterDelete1 = quota * 0.72; // 36MB (after deleting 1 session)
479
+ const afterDelete2 = quota * 0.60; // 30MB (after deleting 2 sessions - target reached)
480
+
481
+ mockStorageManager.setUsageSequence([
482
+ initialUsage, // First loop iteration: 84% > 60%, delete session-0
483
+ afterDelete1, // Second loop iteration: 72% > 60%, delete session-1
484
+ afterDelete2, // Third loop iteration: 60% <= 60%, BREAK (should not delete)
485
+ afterDelete2, // Any additional calls stay at 60%
486
+ afterDelete2,
487
+ afterDelete2,
488
+ afterDelete2,
489
+ afterDelete2,
490
+ ]);
491
+
492
+ // Initialize IndexedDB service for testing
493
+ await indexedDBService.initialize();
494
+
495
+ // Create 5 sessions with different timestamps (oldest first)
496
+ for (let i = 0; i < 5; i++) {
497
+ const sessionId = `session-${i}`;
498
+ const timestamp = new Date(Date.now() - (5 - i) * 60000).toISOString(); // Each 1 minute apart
499
+
500
+ // Store in IndexedDB
501
+ await indexedDBService.saveSession({
502
+ session_id: sessionId,
503
+ created_at: timestamp,
504
+ last_activity: timestamp,
505
+ artifact_count: 0,
506
+ run_count: 0,
507
+ size_estimate_bytes: 6 * 1024 * 1024, // 6MB per session
508
+ });
509
+ }
510
+
511
+ // Verify sessions were saved
512
+ const savedSessions = await indexedDBService.getAllSessions();
513
+ console.log(`[Test] Saved ${savedSessions.length} sessions before eviction`);
514
+ expect(savedSessions.length).toBe(5); // Verify all 5 sessions were saved
515
+
516
+ // Trigger eviction
517
+ await act(async () => {
518
+ await indexedDBService.evictOldSessions();
519
+ });
520
+
521
+ // Verify: Old sessions were evicted
522
+ const remainingSessions = await indexedDBService.getAllSessions();
523
+ console.log(`[Test] ${remainingSessions.length} sessions remaining after eviction`);
524
+
525
+ // Should have evicted 2 oldest sessions (session-0, session-1), keeping 3 (session-2, session-3, session-4)
526
+ expect(remainingSessions.length).toBe(3);
527
+
528
+ // Verify: Most recent sessions are preserved
529
+ const remainingIds = remainingSessions.map((s: any) => s.session_id);
530
+
531
+ // Most recent session should be preserved
532
+ expect(remainingIds).toContain('session-4');
533
+ expect(remainingIds).toContain('session-3');
534
+ expect(remainingIds).toContain('session-2');
535
+
536
+ // Oldest sessions should be gone
537
+ expect(remainingIds).not.toContain('session-0');
538
+ expect(remainingIds).not.toContain('session-1');
539
+
540
+ console.log(`[LRU] Evicted ${5 - remainingSessions.length} oldest sessions`);
541
+ console.log(`[LRU] Preserved sessions: ${remainingIds.join(', ')}`);
542
+ });
543
+
544
+ it('should preserve current session and most recent 10 sessions during eviction', async () => {
545
+ const quota = 50 * 1024 * 1024;
546
+ mockStorageManager.setQuota(quota);
547
+
548
+ const initialUsage = quota * 0.85; // 42.5MB
549
+
550
+ // Configure usage sequence: Start at 85%, delete 5 sessions to reach 60%
551
+ const usageSequence = [
552
+ initialUsage, // First iteration: 85% > 60%, delete session-0
553
+ quota * 0.80, // Second iteration: 80% > 60%, delete session-1
554
+ quota * 0.75, // Third iteration: 75% > 60%, delete session-2
555
+ quota * 0.70, // Fourth iteration: 70% > 60%, delete session-3
556
+ quota * 0.65, // Fifth iteration: 65% > 60%, delete session-4
557
+ quota * 0.60, // Sixth iteration: 60% <= 60%, BREAK
558
+ quota * 0.60, // Stay at target
559
+ ];
560
+ mockStorageManager.setUsageSequence(usageSequence);
561
+
562
+ // Initialize IndexedDB service for testing
563
+ await indexedDBService.initialize();
564
+
565
+ // Create 15 sessions
566
+ for (let i = 0; i < 15; i++) {
567
+ const timestamp = new Date(Date.now() - (15 - i) * 60000).toISOString();
568
+ await indexedDBService.saveSession({
569
+ session_id: `session-${i}`,
570
+ created_at: timestamp,
571
+ last_activity: timestamp,
572
+ artifact_count: 0,
573
+ run_count: 0,
574
+ size_estimate_bytes: 3 * 1024 * 1024, // 3MB per session
575
+ });
576
+ }
577
+
578
+ // Evict
579
+ await indexedDBService.evictOldSessions();
580
+
581
+ const sessions = await indexedDBService.getAllSessions();
582
+
583
+ // Should have deleted 5 oldest sessions, keeping 10 most recent
584
+ expect(sessions.length).toBe(10);
585
+ });
586
+ });
587
+ });