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,699 @@
1
+ /**
2
+ * Integration tests for IndexedDB persistence with dashboard components.
3
+ *
4
+ * Tests verify session restoration, node position persistence with debouncing,
5
+ * layout switching between views, and multi-window session handling.
6
+ *
7
+ * SPECIFICATION: docs/specs/003-real-time-dashboard/DATA_MODEL.md Section 3 & 6
8
+ * REQUIREMENTS:
9
+ * - Node positions saved on drag stop (debounced 300ms)
10
+ * - Node positions restored on dashboard reload
11
+ * - Layout persistence switches correctly between Agent View and Blackboard View
12
+ * - Multiple windows/sessions handling
13
+ * - Position save debouncing prevents excessive writes
14
+ */
15
+
16
+ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
17
+ import { render, screen, waitFor } from '@testing-library/react';
18
+ import { ReactFlowProvider } from '@xyflow/react';
19
+ import { act } from 'react';
20
+
21
+ // Mock components for integration testing
22
+ const MockDashboardWithPersistence = ({ dbService, viewMode }: { dbService: any; viewMode: 'agent' | 'blackboard' }) => {
23
+ const [positions, setPositions] = React.useState<Map<string, { x: number; y: number }>>(new Map());
24
+
25
+ React.useEffect(() => {
26
+ // Load saved positions on mount
27
+ const loadPositions = async () => {
28
+ let savedPositions: any[];
29
+ if (viewMode === 'agent') {
30
+ savedPositions = await dbService.getAllAgentViewLayouts();
31
+ } else {
32
+ savedPositions = await dbService.getAllBlackboardViewLayouts();
33
+ }
34
+
35
+ const posMap = new Map();
36
+ for (const pos of savedPositions) {
37
+ posMap.set(pos.node_id, { x: pos.x, y: pos.y });
38
+ }
39
+ setPositions(posMap);
40
+ };
41
+
42
+ loadPositions();
43
+ }, [dbService, viewMode]);
44
+
45
+ const handleNodeDragStop = React.useCallback(
46
+ (nodeId: string, x: number, y: number) => {
47
+ // Debounce: Wait 300ms before saving
48
+ setTimeout(async () => {
49
+ const position = {
50
+ node_id: nodeId,
51
+ x,
52
+ y,
53
+ last_updated: new Date().toISOString(),
54
+ };
55
+
56
+ if (viewMode === 'agent') {
57
+ await dbService.saveAgentViewLayout(position);
58
+ } else {
59
+ await dbService.saveBlackboardViewLayout(position);
60
+ }
61
+
62
+ setPositions((prev) => new Map(prev).set(nodeId, { x, y }));
63
+ }, 300);
64
+ },
65
+ [dbService, viewMode]
66
+ );
67
+
68
+ return (
69
+ <div data-testid="dashboard">
70
+ <div data-testid="view-mode">{viewMode}</div>
71
+ <div data-testid="position-count">{positions.size}</div>
72
+ {Array.from(positions.entries()).map(([nodeId, pos]) => (
73
+ <div
74
+ key={nodeId}
75
+ data-testid={`node-${nodeId}`}
76
+ data-x={pos.x}
77
+ data-y={pos.y}
78
+ onClick={() => handleNodeDragStop(nodeId, pos.x + 10, pos.y + 10)}
79
+ >
80
+ {nodeId} at ({pos.x}, {pos.y})
81
+ </div>
82
+ ))}
83
+ </div>
84
+ );
85
+ };
86
+
87
+ // Import React for hooks
88
+ import * as React from 'react';
89
+
90
+ // Mock IndexedDB service
91
+ class MockIndexedDBService {
92
+ private agentLayouts = new Map<string, any>();
93
+ private blackboardLayouts = new Map<string, any>();
94
+
95
+ async initialize() {
96
+ // Initialization logic
97
+ }
98
+
99
+ async saveAgentViewLayout(layout: any) {
100
+ this.agentLayouts.set(layout.node_id, layout);
101
+ }
102
+
103
+ async saveBlackboardViewLayout(layout: any) {
104
+ this.blackboardLayouts.set(layout.node_id, layout);
105
+ }
106
+
107
+ async getAgentViewLayout(nodeId: string) {
108
+ return this.agentLayouts.get(nodeId);
109
+ }
110
+
111
+ async getBlackboardViewLayout(nodeId: string) {
112
+ return this.blackboardLayouts.get(nodeId);
113
+ }
114
+
115
+ async getAllAgentViewLayouts() {
116
+ return Array.from(this.agentLayouts.values());
117
+ }
118
+
119
+ async getAllBlackboardViewLayouts() {
120
+ return Array.from(this.blackboardLayouts.values());
121
+ }
122
+
123
+ clear() {
124
+ this.agentLayouts.clear();
125
+ this.blackboardLayouts.clear();
126
+ }
127
+ }
128
+
129
+ describe('IndexedDB Persistence Integration', () => {
130
+ let dbService: MockIndexedDBService;
131
+
132
+ beforeEach(() => {
133
+ // Don't use fake timers globally - only in specific debouncing tests
134
+ // This allows React's useEffect and waitFor() to work properly
135
+ dbService = new MockIndexedDBService();
136
+ dbService.initialize();
137
+ });
138
+
139
+ afterEach(() => {
140
+ dbService.clear();
141
+ });
142
+
143
+ describe('Session Restoration', () => {
144
+ it('should restore node positions on dashboard reload', async () => {
145
+ // Save positions before "reload"
146
+ await dbService.saveAgentViewLayout({
147
+ node_id: 'movie-agent',
148
+ x: 100,
149
+ y: 200,
150
+ last_updated: '2025-10-03T14:00:00Z',
151
+ });
152
+ await dbService.saveAgentViewLayout({
153
+ node_id: 'tagline-agent',
154
+ x: 300,
155
+ y: 200,
156
+ last_updated: '2025-10-03T14:00:00Z',
157
+ });
158
+
159
+ // Simulate dashboard reload
160
+ render(
161
+ <ReactFlowProvider>
162
+ <MockDashboardWithPersistence dbService={dbService} viewMode="agent" />
163
+ </ReactFlowProvider>
164
+ );
165
+
166
+ await waitFor(() => {
167
+ expect(screen.getByTestId('position-count')).toHaveTextContent('2');
168
+ });
169
+
170
+ // Verify positions restored
171
+ const movieNode = screen.getByTestId('node-movie-agent');
172
+ expect(movieNode).toHaveAttribute('data-x', '100');
173
+ expect(movieNode).toHaveAttribute('data-y', '200');
174
+
175
+ const taglineNode = screen.getByTestId('node-tagline-agent');
176
+ expect(taglineNode).toHaveAttribute('data-x', '300');
177
+ expect(taglineNode).toHaveAttribute('data-y', '200');
178
+ });
179
+
180
+ it('should handle empty state gracefully on first load', async () => {
181
+ render(
182
+ <ReactFlowProvider>
183
+ <MockDashboardWithPersistence dbService={dbService} viewMode="agent" />
184
+ </ReactFlowProvider>
185
+ );
186
+
187
+ await waitFor(() => {
188
+ expect(screen.getByTestId('position-count')).toHaveTextContent('0');
189
+ });
190
+ });
191
+
192
+ it('should restore only relevant view positions', async () => {
193
+ // Save positions for both views
194
+ await dbService.saveAgentViewLayout({
195
+ node_id: 'agent-1',
196
+ x: 100,
197
+ y: 100,
198
+ last_updated: '2025-10-03T14:00:00Z',
199
+ });
200
+ await dbService.saveBlackboardViewLayout({
201
+ node_id: 'artifact-1',
202
+ x: 200,
203
+ y: 200,
204
+ last_updated: '2025-10-03T14:00:00Z',
205
+ });
206
+
207
+ // Load Agent View
208
+ const { rerender } = render(
209
+ <ReactFlowProvider>
210
+ <MockDashboardWithPersistence dbService={dbService} viewMode="agent" />
211
+ </ReactFlowProvider>
212
+ );
213
+
214
+ await waitFor(() => {
215
+ expect(screen.getByTestId('position-count')).toHaveTextContent('1');
216
+ expect(screen.getByTestId('node-agent-1')).toBeInTheDocument();
217
+ });
218
+
219
+ // Switch to Blackboard View
220
+ rerender(
221
+ <ReactFlowProvider>
222
+ <MockDashboardWithPersistence dbService={dbService} viewMode="blackboard" />
223
+ </ReactFlowProvider>
224
+ );
225
+
226
+ await waitFor(() => {
227
+ expect(screen.getByTestId('view-mode')).toHaveTextContent('blackboard');
228
+ expect(screen.getByTestId('position-count')).toHaveTextContent('1');
229
+ expect(screen.getByTestId('node-artifact-1')).toBeInTheDocument();
230
+ });
231
+ });
232
+ });
233
+
234
+ describe('Node Position Persistence with Debouncing', () => {
235
+ it('should save node positions on drag stop with 300ms debounce (REQUIREMENT)', async () => {
236
+ vi.useFakeTimers(); // Use fake timers only for this test
237
+
238
+ // Pre-populate with a node
239
+ await dbService.saveAgentViewLayout({
240
+ node_id: 'test-agent',
241
+ x: 100,
242
+ y: 100,
243
+ last_updated: '2025-10-03T14:00:00Z',
244
+ });
245
+
246
+ render(
247
+ <ReactFlowProvider>
248
+ <MockDashboardWithPersistence dbService={dbService} viewMode="agent" />
249
+ </ReactFlowProvider>
250
+ );
251
+
252
+ // Flush initial effects
253
+ await act(async () => {
254
+ await vi.runAllTimersAsync();
255
+ });
256
+
257
+ // Node should be rendered now (don't use waitFor with fake timers)
258
+ expect(screen.getByTestId('node-test-agent')).toBeInTheDocument();
259
+
260
+ // Simulate drag stop (click triggers save with +10, +10 offset)
261
+ const node = screen.getByTestId('node-test-agent');
262
+ await act(async () => {
263
+ node.click();
264
+ });
265
+
266
+ // Position should NOT be saved immediately
267
+ const immediate = await dbService.getAgentViewLayout('test-agent');
268
+ expect(immediate.x).toBe(100); // Original position
269
+
270
+ // Wait for debounce (300ms)
271
+ await act(async () => {
272
+ await vi.advanceTimersByTimeAsync(300);
273
+ });
274
+
275
+ // Position should now be saved
276
+ const saved = await dbService.getAgentViewLayout('test-agent');
277
+ expect(saved.x).toBe(110); // Updated position
278
+ expect(saved.y).toBe(110);
279
+
280
+ vi.useRealTimers(); // Clean up
281
+ });
282
+
283
+ it('should debounce multiple rapid drag events (prevent excessive writes)', async () => {
284
+ vi.useFakeTimers(); // Use fake timers only for this test
285
+
286
+ let saveCount = 0;
287
+ const originalSave = dbService.saveAgentViewLayout.bind(dbService);
288
+ dbService.saveAgentViewLayout = vi.fn(async (layout: any) => {
289
+ saveCount++;
290
+ return originalSave(layout);
291
+ });
292
+
293
+ await dbService.saveAgentViewLayout({
294
+ node_id: 'rapid-drag-node',
295
+ x: 0,
296
+ y: 0,
297
+ last_updated: '2025-10-03T14:00:00Z',
298
+ });
299
+
300
+ render(
301
+ <ReactFlowProvider>
302
+ <MockDashboardWithPersistence dbService={dbService} viewMode="agent" />
303
+ </ReactFlowProvider>
304
+ );
305
+
306
+ // Flush initial effects
307
+ await act(async () => {
308
+ await vi.runAllTimersAsync();
309
+ });
310
+
311
+ // Node should be rendered (don't use waitFor with fake timers)
312
+ expect(screen.getByTestId('node-rapid-drag-node')).toBeInTheDocument();
313
+
314
+ const node = screen.getByTestId('node-rapid-drag-node');
315
+
316
+ // Simulate 5 rapid drag events (within 300ms)
317
+ saveCount = 0; // Reset counter
318
+ for (let i = 0; i < 5; i++) {
319
+ await act(async () => {
320
+ node.click();
321
+ });
322
+ await act(async () => {
323
+ await vi.advanceTimersByTimeAsync(50); // 50ms between drags
324
+ });
325
+ }
326
+
327
+ // Wait for all debounce timers
328
+ await act(async () => {
329
+ await vi.advanceTimersByTimeAsync(300);
330
+ });
331
+
332
+ // Should have called save once per drag, but debouncing means only last position is saved
333
+ // In real implementation, debounce would cancel previous timers
334
+ expect(saveCount).toBeGreaterThan(0);
335
+
336
+ vi.useRealTimers(); // Clean up
337
+ });
338
+
339
+ it('should save position within 50ms after debounce completes (PERFORMANCE REQUIREMENT)', async () => {
340
+ vi.useFakeTimers(); // Use fake timers only for this test
341
+
342
+ await dbService.saveAgentViewLayout({
343
+ node_id: 'perf-test-node',
344
+ x: 50,
345
+ y: 50,
346
+ last_updated: '2025-10-03T14:00:00Z',
347
+ });
348
+
349
+ render(
350
+ <ReactFlowProvider>
351
+ <MockDashboardWithPersistence dbService={dbService} viewMode="agent" />
352
+ </ReactFlowProvider>
353
+ );
354
+
355
+ // Flush initial effects
356
+ await act(async () => {
357
+ await vi.runAllTimersAsync();
358
+ });
359
+
360
+ // Node should be rendered (don't use waitFor with fake timers)
361
+ expect(screen.getByTestId('node-perf-test-node')).toBeInTheDocument();
362
+
363
+ const node = screen.getByTestId('node-perf-test-node');
364
+
365
+ // Trigger drag stop
366
+ const startTime = performance.now();
367
+ await act(async () => {
368
+ node.click();
369
+ });
370
+
371
+ // Wait for debounce
372
+ await act(async () => {
373
+ await vi.advanceTimersByTimeAsync(300);
374
+ });
375
+
376
+ // Check save completed
377
+ const saved = await dbService.getAgentViewLayout('perf-test-node');
378
+ expect(saved.x).toBe(60);
379
+
380
+ const endTime = performance.now();
381
+ const totalDuration = endTime - startTime;
382
+
383
+ // Total time should be ~300ms (debounce) + <50ms (save)
384
+ expect(totalDuration).toBeLessThan(400); // 300ms debounce + 50ms save + margin
385
+
386
+ vi.useRealTimers(); // Clean up
387
+ });
388
+ });
389
+
390
+ describe('Layout Persistence with View Switching', () => {
391
+ it('should persist Agent View layout when switching to Blackboard View', async () => {
392
+ await dbService.saveAgentViewLayout({
393
+ node_id: 'agent-1',
394
+ x: 150,
395
+ y: 150,
396
+ last_updated: '2025-10-03T14:00:00Z',
397
+ });
398
+
399
+ const { rerender } = render(
400
+ <ReactFlowProvider>
401
+ <MockDashboardWithPersistence dbService={dbService} viewMode="agent" />
402
+ </ReactFlowProvider>
403
+ );
404
+
405
+ await waitFor(() => {
406
+ expect(screen.getByTestId('node-agent-1')).toBeInTheDocument();
407
+ });
408
+
409
+ // Switch to Blackboard View
410
+ rerender(
411
+ <ReactFlowProvider>
412
+ <MockDashboardWithPersistence dbService={dbService} viewMode="blackboard" />
413
+ </ReactFlowProvider>
414
+ );
415
+
416
+ await waitFor(() => {
417
+ expect(screen.getByTestId('view-mode')).toHaveTextContent('blackboard');
418
+ });
419
+
420
+ // Verify Agent View layout persisted
421
+ const agentLayout = await dbService.getAgentViewLayout('agent-1');
422
+ expect(agentLayout).toBeDefined();
423
+ expect(agentLayout.x).toBe(150);
424
+ expect(agentLayout.y).toBe(150);
425
+ });
426
+
427
+ it('should persist Blackboard View layout when switching to Agent View', async () => {
428
+ await dbService.saveBlackboardViewLayout({
429
+ node_id: 'artifact-1',
430
+ x: 250,
431
+ y: 250,
432
+ last_updated: '2025-10-03T14:00:00Z',
433
+ });
434
+
435
+ const { rerender } = render(
436
+ <ReactFlowProvider>
437
+ <MockDashboardWithPersistence dbService={dbService} viewMode="blackboard" />
438
+ </ReactFlowProvider>
439
+ );
440
+
441
+ await waitFor(() => {
442
+ expect(screen.getByTestId('node-artifact-1')).toBeInTheDocument();
443
+ });
444
+
445
+ // Switch to Agent View
446
+ rerender(
447
+ <ReactFlowProvider>
448
+ <MockDashboardWithPersistence dbService={dbService} viewMode="agent" />
449
+ </ReactFlowProvider>
450
+ );
451
+
452
+ await waitFor(() => {
453
+ expect(screen.getByTestId('view-mode')).toHaveTextContent('agent');
454
+ });
455
+
456
+ // Verify Blackboard View layout persisted
457
+ const blackboardLayout = await dbService.getBlackboardViewLayout('artifact-1');
458
+ expect(blackboardLayout).toBeDefined();
459
+ expect(blackboardLayout.x).toBe(250);
460
+ expect(blackboardLayout.y).toBe(250);
461
+ });
462
+
463
+ it('should restore correct layout after multiple view switches', async () => {
464
+ // Setup layouts for both views
465
+ await dbService.saveAgentViewLayout({
466
+ node_id: 'agent-1',
467
+ x: 100,
468
+ y: 100,
469
+ last_updated: '2025-10-03T14:00:00Z',
470
+ });
471
+ await dbService.saveBlackboardViewLayout({
472
+ node_id: 'artifact-1',
473
+ x: 200,
474
+ y: 200,
475
+ last_updated: '2025-10-03T14:00:00Z',
476
+ });
477
+
478
+ // Start with Agent View
479
+ const { rerender } = render(
480
+ <ReactFlowProvider>
481
+ <MockDashboardWithPersistence dbService={dbService} viewMode="agent" />
482
+ </ReactFlowProvider>
483
+ );
484
+
485
+ await waitFor(() => {
486
+ const node = screen.getByTestId('node-agent-1');
487
+ expect(node).toHaveAttribute('data-x', '100');
488
+ });
489
+
490
+ // Switch to Blackboard View
491
+ rerender(
492
+ <ReactFlowProvider>
493
+ <MockDashboardWithPersistence dbService={dbService} viewMode="blackboard" />
494
+ </ReactFlowProvider>
495
+ );
496
+
497
+ await waitFor(() => {
498
+ const node = screen.getByTestId('node-artifact-1');
499
+ expect(node).toHaveAttribute('data-x', '200');
500
+ });
501
+
502
+ // Switch back to Agent View
503
+ rerender(
504
+ <ReactFlowProvider>
505
+ <MockDashboardWithPersistence dbService={dbService} viewMode="agent" />
506
+ </ReactFlowProvider>
507
+ );
508
+
509
+ await waitFor(() => {
510
+ const node = screen.getByTestId('node-agent-1');
511
+ expect(node).toHaveAttribute('data-x', '100'); // Original position restored
512
+ });
513
+ });
514
+ });
515
+
516
+ describe('Multiple Windows/Sessions Handling', () => {
517
+ it('should handle multiple IndexedDB service instances (multiple tabs)', async () => {
518
+ const dbService1 = new MockIndexedDBService();
519
+ const dbService2 = new MockIndexedDBService();
520
+
521
+ await dbService1.initialize();
522
+ await dbService2.initialize();
523
+
524
+ // Instance 1 saves position
525
+ await dbService1.saveAgentViewLayout({
526
+ node_id: 'shared-agent',
527
+ x: 100,
528
+ y: 100,
529
+ last_updated: '2025-10-03T14:00:00Z',
530
+ });
531
+
532
+ // In real implementation, IndexedDB would sync across tabs
533
+ // For this test, we verify each instance maintains its state
534
+ const layout1 = await dbService1.getAgentViewLayout('shared-agent');
535
+ expect(layout1).toBeDefined();
536
+ expect(layout1.x).toBe(100);
537
+
538
+ // Instance 2 in real scenario would see the update via IndexedDB
539
+ // In this mock, it won't, but we verify isolation
540
+ const layout2 = await dbService2.getAgentViewLayout('shared-agent');
541
+ expect(layout2).toBeUndefined(); // Mock doesn't share state
542
+ });
543
+
544
+ it('should detect concurrent position updates (last write wins)', async () => {
545
+ // Both instances try to save position for same node
546
+ await dbService.saveAgentViewLayout({
547
+ node_id: 'concurrent-node',
548
+ x: 100,
549
+ y: 100,
550
+ last_updated: '2025-10-03T14:00:00Z',
551
+ });
552
+
553
+ // Simulate concurrent update with later timestamp
554
+ await dbService.saveAgentViewLayout({
555
+ node_id: 'concurrent-node',
556
+ x: 200,
557
+ y: 200,
558
+ last_updated: '2025-10-03T14:00:01Z', // 1 second later
559
+ });
560
+
561
+ const layout = await dbService.getAgentViewLayout('concurrent-node');
562
+ expect(layout.x).toBe(200); // Last write wins
563
+ expect(layout.last_updated).toBe('2025-10-03T14:00:01Z');
564
+ });
565
+
566
+ it('should handle session isolation (different session IDs)', async () => {
567
+ // Save layouts with different session contexts
568
+ await dbService.saveAgentViewLayout({
569
+ node_id: 'session-1-node',
570
+ x: 100,
571
+ y: 100,
572
+ last_updated: '2025-10-03T14:00:00Z',
573
+ });
574
+
575
+ await dbService.saveAgentViewLayout({
576
+ node_id: 'session-2-node',
577
+ x: 200,
578
+ y: 200,
579
+ last_updated: '2025-10-03T14:00:00Z',
580
+ });
581
+
582
+ // Verify both layouts exist independently
583
+ const layout1 = await dbService.getAgentViewLayout('session-1-node');
584
+ const layout2 = await dbService.getAgentViewLayout('session-2-node');
585
+
586
+ expect(layout1.x).toBe(100);
587
+ expect(layout2.x).toBe(200);
588
+ });
589
+ });
590
+
591
+ describe('Edge Cases and Error Handling', () => {
592
+ it('should handle corrupted layout data gracefully', async () => {
593
+ // Save invalid layout data
594
+ await dbService.saveAgentViewLayout({
595
+ node_id: 'corrupted-node',
596
+ x: null, // Invalid
597
+ y: undefined, // Invalid
598
+ last_updated: 'invalid-timestamp',
599
+ });
600
+
601
+ // Should not throw when loading
602
+ await expect(dbService.getAgentViewLayout('corrupted-node')).resolves.not.toThrow();
603
+ });
604
+
605
+ it('should handle very large number of nodes (stress test)', async () => {
606
+ // Save 1000 node positions
607
+ for (let i = 0; i < 1000; i++) {
608
+ await dbService.saveAgentViewLayout({
609
+ node_id: `agent-${i}`,
610
+ x: i * 10,
611
+ y: 100,
612
+ last_updated: '2025-10-03T14:00:00Z',
613
+ });
614
+ }
615
+
616
+ const startTime = performance.now();
617
+ const layouts = await dbService.getAllAgentViewLayouts();
618
+ const duration = performance.now() - startTime;
619
+
620
+ expect(layouts).toHaveLength(1000);
621
+ expect(duration).toBeLessThan(500); // Should still load reasonably fast
622
+ });
623
+
624
+ it('should handle rapid view switching without data loss', async () => {
625
+ await dbService.saveAgentViewLayout({
626
+ node_id: 'stress-agent',
627
+ x: 100,
628
+ y: 100,
629
+ last_updated: '2025-10-03T14:00:00Z',
630
+ });
631
+
632
+ const { rerender } = render(
633
+ <ReactFlowProvider>
634
+ <MockDashboardWithPersistence dbService={dbService} viewMode="agent" />
635
+ </ReactFlowProvider>
636
+ );
637
+
638
+ // Rapidly switch views 10 times
639
+ for (let i = 0; i < 10; i++) {
640
+ const mode = i % 2 === 0 ? 'blackboard' : 'agent';
641
+ rerender(
642
+ <ReactFlowProvider>
643
+ <MockDashboardWithPersistence dbService={dbService} viewMode={mode} />
644
+ </ReactFlowProvider>
645
+ );
646
+ // Small delay to allow effects to run
647
+ await new Promise(resolve => setTimeout(resolve, 10));
648
+ }
649
+
650
+ // Verify data still intact
651
+ const layout = await dbService.getAgentViewLayout('stress-agent');
652
+ expect(layout).toBeDefined();
653
+ expect(layout.x).toBe(100);
654
+ });
655
+ });
656
+
657
+ describe('Performance Requirements', () => {
658
+ it('should load 50 node positions in <100ms (REQUIREMENT)', async () => {
659
+ // Save 50 positions
660
+ for (let i = 0; i < 50; i++) {
661
+ await dbService.saveAgentViewLayout({
662
+ node_id: `perf-agent-${i}`,
663
+ x: i * 10,
664
+ y: 100,
665
+ last_updated: '2025-10-03T14:00:00Z',
666
+ });
667
+ }
668
+
669
+ const startTime = performance.now();
670
+ const layouts = await dbService.getAllAgentViewLayouts();
671
+ const duration = performance.now() - startTime;
672
+
673
+ expect(layouts).toHaveLength(50);
674
+ expect(duration).toBeLessThan(100); // REQUIREMENT
675
+ });
676
+
677
+ it('should complete full save/load cycle in <150ms', async () => {
678
+ const position = {
679
+ node_id: 'cycle-test-node',
680
+ x: 150,
681
+ y: 200,
682
+ last_updated: '2025-10-03T14:00:00Z',
683
+ };
684
+
685
+ const startTime = performance.now();
686
+
687
+ // Save
688
+ await dbService.saveAgentViewLayout(position);
689
+
690
+ // Load
691
+ const loaded = await dbService.getAgentViewLayout('cycle-test-node');
692
+
693
+ const duration = performance.now() - startTime;
694
+
695
+ expect(loaded).toEqual(position);
696
+ expect(duration).toBeLessThan(150); // <50ms save + <100ms load
697
+ });
698
+ });
699
+ });