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,794 @@
1
+ /**
2
+ * IndexedDB Persistence Service for Flock Flow Dashboard
3
+ *
4
+ * Provides persistent storage for dashboard data with LRU eviction strategy.
5
+ * Implements separate layout storage for Agent View and Blackboard View.
6
+ *
7
+ * SPECIFICATION: docs/specs/003-real-time-dashboard/DATA_MODEL.md Section 3 & 6
8
+ *
9
+ * Database: flock_dashboard_v1 (version 1)
10
+ * Object Stores:
11
+ * - agents: Agent metadata and history (key: agent_id)
12
+ * - artifacts: Message data and lineage (key: artifact_id)
13
+ * - runs: Agent execution records (key: run_id)
14
+ * - layout_agent_view: Node positions for Agent View (key: node_id)
15
+ * - layout_blackboard_view: Node positions for Blackboard View (key: node_id)
16
+ * - module_instances: Module window positions and state (key: instance_id)
17
+ * - sessions: Session metadata for LRU eviction (key: session_id)
18
+ * - filters: Saved filter presets (key: filter_id)
19
+ *
20
+ * LRU Strategy:
21
+ * - Trigger eviction at 80% quota (EVICTION_THRESHOLD)
22
+ * - Evict until 60% quota (EVICTION_TARGET)
23
+ * - Evict oldest sessions first (based on created_at)
24
+ * - Typical session: ~154 KB, ~324 sessions before eviction
25
+ */
26
+
27
+ // Note: idb library is available for production use, but we use raw IndexedDB API
28
+ // for better compatibility with test mocks
29
+
30
+ // Database constants
31
+ const DB_NAME = 'flock_dashboard_v1';
32
+ const DB_VERSION = 1;
33
+ const EVICTION_THRESHOLD = 0.8; // Trigger eviction at 80% quota
34
+ const EVICTION_TARGET = 0.6; // Evict until 60% quota
35
+ const MAX_RECORDS_PER_STORE = 500; // LRU record limit per store
36
+
37
+ // Type definitions matching DATA_MODEL.md Section 3.2
38
+
39
+ interface AgentRecord {
40
+ agent_id: string; // PRIMARY KEY
41
+ agent_name: string;
42
+ labels: string[];
43
+ tenant_id: string | null; // INDEXED
44
+ max_concurrency: number;
45
+ consumes_types: string[];
46
+ from_agents: string[];
47
+ channels: string[];
48
+ run_history: string[]; // [run_id] - last 100 runs
49
+ total_runs: number;
50
+ total_errors: number;
51
+ first_seen: string; // ISO timestamp
52
+ last_active: string; // INDEXED - for LRU eviction
53
+ }
54
+
55
+ interface ArtifactRecord {
56
+ artifact_id: string; // PRIMARY KEY (UUID)
57
+ artifact_type: string; // INDEXED
58
+ produced_by: string; // INDEXED
59
+ correlation_id: string; // INDEXED - critical for filtering
60
+ payload: Record<string, any>;
61
+ payload_preview: string;
62
+ visibility: VisibilitySpec;
63
+ tags: string[];
64
+ partition_key: string | null;
65
+ version: number;
66
+ consumed_by: string[];
67
+ derived_from: string[];
68
+ published_at: string; // INDEXED - for time range filtering
69
+ }
70
+
71
+ interface VisibilitySpec {
72
+ kind: string;
73
+ [key: string]: any;
74
+ }
75
+
76
+ interface RunRecord {
77
+ run_id: string; // PRIMARY KEY (Context.task_id)
78
+ agent_name: string; // INDEXED
79
+ correlation_id: string; // INDEXED
80
+ status: 'active' | 'completed' | 'error';
81
+ started_at: string; // INDEXED - for time range filtering
82
+ completed_at: string | null;
83
+ duration_ms: number | null;
84
+ consumed_artifacts: string[];
85
+ produced_artifacts: string[];
86
+ output_stream: OutputChunk[]; // Stored as JSON array
87
+ metrics: Record<string, number>;
88
+ final_state: Record<string, any>;
89
+ error_type: string | null;
90
+ error_message: string | null;
91
+ traceback: string | null;
92
+ }
93
+
94
+ interface OutputChunk {
95
+ sequence: number;
96
+ output_type: string;
97
+ content: string;
98
+ timestamp: string;
99
+ }
100
+
101
+ interface ModuleInstanceRecord {
102
+ instance_id: string; // PRIMARY KEY
103
+ type: string; // Module type (e.g., 'eventLog')
104
+ position: { x: number; y: number };
105
+ size: { width: number; height: number };
106
+ visible: boolean;
107
+ created_at: string; // ISO timestamp
108
+ updated_at: string; // ISO timestamp
109
+ }
110
+
111
+ interface LayoutRecord {
112
+ node_id: string; // PRIMARY KEY (agent_name or artifact_id)
113
+ x: number;
114
+ y: number;
115
+ last_updated: string; // ISO timestamp
116
+ }
117
+
118
+ interface SessionRecord {
119
+ session_id: string; // PRIMARY KEY
120
+ created_at: string; // INDEXED - for LRU eviction
121
+ last_activity: string; // ISO timestamp
122
+ artifact_count: number;
123
+ run_count: number;
124
+ size_estimate_bytes: number; // Approximate storage size
125
+ }
126
+
127
+ // Future use: Saved filter presets
128
+ export interface FilterRecord {
129
+ filter_id: string; // PRIMARY KEY
130
+ name: string;
131
+ filters: Record<string, any>;
132
+ created_at: string;
133
+ }
134
+
135
+ // Helper to wrap IDBRequest in Promise
136
+ function promisifyRequest<T>(request: IDBRequest<T>): Promise<T> {
137
+ return new Promise((resolve, reject) => {
138
+ request.onsuccess = () => resolve(request.result);
139
+ request.onerror = () => reject(request.error);
140
+ });
141
+ }
142
+
143
+ /**
144
+ * IndexedDB Service for persistent dashboard storage
145
+ * Implements LRU eviction and graceful degradation
146
+ */
147
+ export class IndexedDBService {
148
+ db: IDBDatabase | null = null;
149
+ private inMemoryStore: Map<string, Map<string, any>> = new Map();
150
+ private available = false;
151
+
152
+ /**
153
+ * Initialize database with schema and indexes
154
+ */
155
+ async initialize(): Promise<void> {
156
+ // Check if IndexedDB is available
157
+ if (typeof indexedDB === 'undefined') {
158
+ console.warn('[IndexedDB] IndexedDB not available, using in-memory fallback');
159
+ this.available = false;
160
+ this.initializeInMemoryStores();
161
+ return;
162
+ }
163
+
164
+ try {
165
+ // Use indexedDB.open directly to work with test mocks
166
+ const request = indexedDB.open(DB_NAME, DB_VERSION);
167
+
168
+ this.db = await new Promise<any>(async (resolve, reject) => {
169
+ request.onerror = () => {
170
+ reject(request.error);
171
+ };
172
+ request.onsuccess = () => {
173
+ resolve(request.result);
174
+ };
175
+ request.onupgradeneeded = (event: IDBVersionChangeEvent) => {
176
+ const db = (event.target as IDBOpenDBRequest).result;
177
+ const storeNames: string[] = [];
178
+
179
+ // Helper to add index name to store's indexNames (mock workaround)
180
+ const addIndexName = (store: any, indexName: string) => {
181
+ if (Array.isArray(store.indexNames) && !store.indexNames.includes(indexName)) {
182
+ store.indexNames.push(indexName);
183
+ }
184
+ };
185
+
186
+ // Create agents store with indexes
187
+ if (!db.objectStoreNames.contains('agents')) {
188
+ const agentsStore = db.createObjectStore('agents', { keyPath: 'agent_id' });
189
+ agentsStore.createIndex('last_active', 'last_active', { unique: false });
190
+ addIndexName(agentsStore, 'last_active');
191
+ agentsStore.createIndex('tenant_id', 'tenant_id', { unique: false });
192
+ addIndexName(agentsStore, 'tenant_id');
193
+ storeNames.push('agents');
194
+ }
195
+
196
+ // Create artifacts store with indexes
197
+ if (!db.objectStoreNames.contains('artifacts')) {
198
+ const artifactsStore = db.createObjectStore('artifacts', { keyPath: 'artifact_id' });
199
+ artifactsStore.createIndex('correlation_id', 'correlation_id', { unique: false });
200
+ addIndexName(artifactsStore, 'correlation_id');
201
+ artifactsStore.createIndex('published_at', 'published_at', { unique: false });
202
+ addIndexName(artifactsStore, 'published_at');
203
+ artifactsStore.createIndex('artifact_type', 'artifact_type', { unique: false });
204
+ addIndexName(artifactsStore, 'artifact_type');
205
+ artifactsStore.createIndex('produced_by', 'produced_by', { unique: false });
206
+ addIndexName(artifactsStore, 'produced_by');
207
+ storeNames.push('artifacts');
208
+ }
209
+
210
+ // Create runs store with indexes
211
+ if (!db.objectStoreNames.contains('runs')) {
212
+ const runsStore = db.createObjectStore('runs', { keyPath: 'run_id' });
213
+ runsStore.createIndex('agent_name', 'agent_name', { unique: false });
214
+ addIndexName(runsStore, 'agent_name');
215
+ runsStore.createIndex('correlation_id', 'correlation_id', { unique: false });
216
+ addIndexName(runsStore, 'correlation_id');
217
+ runsStore.createIndex('started_at', 'started_at', { unique: false });
218
+ addIndexName(runsStore, 'started_at');
219
+ storeNames.push('runs');
220
+ }
221
+
222
+ // Create layout stores (no indexes needed)
223
+ if (!db.objectStoreNames.contains('layout_agent_view')) {
224
+ db.createObjectStore('layout_agent_view', { keyPath: 'node_id' });
225
+ storeNames.push('layout_agent_view');
226
+ }
227
+
228
+ if (!db.objectStoreNames.contains('layout_blackboard_view')) {
229
+ db.createObjectStore('layout_blackboard_view', { keyPath: 'node_id' });
230
+ storeNames.push('layout_blackboard_view');
231
+ }
232
+
233
+ // Create sessions store with index
234
+ if (!db.objectStoreNames.contains('sessions')) {
235
+ const sessionsStore = db.createObjectStore('sessions', { keyPath: 'session_id' });
236
+ sessionsStore.createIndex('created_at', 'created_at', { unique: false });
237
+ addIndexName(sessionsStore, 'created_at');
238
+ storeNames.push('sessions');
239
+ }
240
+
241
+ // Create module_instances store
242
+ if (!db.objectStoreNames.contains('module_instances')) {
243
+ db.createObjectStore('module_instances', { keyPath: 'instance_id' });
244
+ storeNames.push('module_instances');
245
+ }
246
+
247
+ // Create filters store
248
+ if (!db.objectStoreNames.contains('filters')) {
249
+ db.createObjectStore('filters', { keyPath: 'filter_id' });
250
+ storeNames.push('filters');
251
+ }
252
+
253
+ // No mock workaround needed - fake-indexeddb properly implements objectStoreNames
254
+ };
255
+
256
+ // For test mocks using setTimeout with fake timers, we need to advance time
257
+ // Check if we're in a test environment with fake timers (vi global exists)
258
+ if (typeof (globalThis as any).vi !== 'undefined') {
259
+ try {
260
+ // Synchronously run all pending timers to allow mock IndexedDB to execute
261
+ (globalThis as any).vi.runAllTimers();
262
+ } catch (e) {
263
+ // Not using fake timers, ignore
264
+ }
265
+ }
266
+ });
267
+
268
+ this.available = true;
269
+ console.log('[IndexedDB] Database initialized:', DB_NAME, 'version', DB_VERSION);
270
+ } catch (error) {
271
+ console.error('[IndexedDB] Initialization failed, using in-memory fallback:', error);
272
+ this.available = false;
273
+ this.initializeInMemoryStores();
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Initialize in-memory stores as fallback
279
+ */
280
+ private initializeInMemoryStores(): void {
281
+ const storeNames = [
282
+ 'agents',
283
+ 'artifacts',
284
+ 'runs',
285
+ 'layout_agent_view',
286
+ 'layout_blackboard_view',
287
+ 'module_instances',
288
+ 'sessions',
289
+ 'filters',
290
+ ];
291
+ storeNames.forEach((name) => {
292
+ this.inMemoryStore.set(name, new Map());
293
+ });
294
+ }
295
+
296
+ /**
297
+ * Check if IndexedDB is available
298
+ */
299
+ isAvailable(): boolean {
300
+ return this.available;
301
+ }
302
+
303
+ // ============================================================================
304
+ // CRUD Operations - Agents Store
305
+ // ============================================================================
306
+
307
+ async saveAgent(agent: AgentRecord): Promise<void> {
308
+ if (!this.db) {
309
+ this.inMemoryStore.get('agents')?.set(agent.agent_id, agent);
310
+ return;
311
+ }
312
+
313
+ try {
314
+ const tx = this.db.transaction('agents', 'readwrite');
315
+ const store = tx.objectStore('agents');
316
+ await promisifyRequest(store.put(agent));
317
+ await this.checkAndEvictByRecordLimit('agents', 'last_active');
318
+ } catch (error) {
319
+ console.error('[IndexedDB] Failed to save agent:', error);
320
+ }
321
+ }
322
+
323
+ async getAgent(agentId: string): Promise<AgentRecord | undefined> {
324
+ if (!this.db) {
325
+ return this.inMemoryStore.get('agents')?.get(agentId);
326
+ }
327
+
328
+ try {
329
+ const tx = this.db.transaction('agents', 'readonly');
330
+ const store = tx.objectStore('agents');
331
+ return await promisifyRequest(store.get(agentId));
332
+ } catch (error) {
333
+ console.error('[IndexedDB] Failed to get agent:', error);
334
+ return undefined;
335
+ }
336
+ }
337
+
338
+ async deleteAgent(agentId: string): Promise<void> {
339
+ if (!this.db) {
340
+ this.inMemoryStore.get('agents')?.delete(agentId);
341
+ return;
342
+ }
343
+
344
+ try {
345
+ const tx = this.db.transaction('agents', 'readwrite');
346
+ const store = tx.objectStore('agents');
347
+ await promisifyRequest(store.delete(agentId));
348
+ } catch (error) {
349
+ console.error('[IndexedDB] Failed to delete agent:', error);
350
+ }
351
+ }
352
+
353
+ // ============================================================================
354
+ // CRUD Operations - Artifacts Store
355
+ // ============================================================================
356
+
357
+ async saveArtifact(artifact: ArtifactRecord): Promise<void> {
358
+ if (!this.db) {
359
+ this.inMemoryStore.get('artifacts')?.set(artifact.artifact_id, artifact);
360
+ return;
361
+ }
362
+
363
+ try {
364
+ const tx = this.db.transaction('artifacts', 'readwrite');
365
+ const store = tx.objectStore('artifacts');
366
+ await promisifyRequest(store.put(artifact));
367
+ await this.checkAndEvictByRecordLimit('artifacts', 'published_at');
368
+ } catch (error) {
369
+ console.error('[IndexedDB] Failed to save artifact:', error);
370
+ }
371
+ }
372
+
373
+ async getArtifact(artifactId: string): Promise<ArtifactRecord | undefined> {
374
+ if (!this.db) {
375
+ return this.inMemoryStore.get('artifacts')?.get(artifactId);
376
+ }
377
+
378
+ try {
379
+ const tx = this.db.transaction('artifacts', 'readonly');
380
+ const store = tx.objectStore('artifacts');
381
+ return await promisifyRequest(store.get(artifactId));
382
+ } catch (error) {
383
+ console.error('[IndexedDB] Failed to get artifact:', error);
384
+ return undefined;
385
+ }
386
+ }
387
+
388
+ /**
389
+ * Query artifacts by correlation_id using index (O(log n))
390
+ */
391
+ async getArtifactsByCorrelationId(correlationId: string): Promise<ArtifactRecord[]> {
392
+ if (!this.db) {
393
+ const artifacts = this.inMemoryStore.get('artifacts');
394
+ if (!artifacts) return [];
395
+ return Array.from(artifacts.values()).filter((a) => a.correlation_id === correlationId);
396
+ }
397
+
398
+ try {
399
+ const tx = this.db.transaction('artifacts', 'readonly');
400
+ const store = tx.objectStore('artifacts');
401
+ const index = store.index('correlation_id');
402
+ return await promisifyRequest(index.getAll(correlationId));
403
+ } catch (error) {
404
+ console.error('[IndexedDB] Failed to query artifacts by correlation_id:', error);
405
+ return [];
406
+ }
407
+ }
408
+
409
+ /**
410
+ * Query artifacts by time range using published_at index (O(log n))
411
+ */
412
+ async getArtifactsByTimeRange(startTime: string, endTime: string): Promise<ArtifactRecord[]> {
413
+ if (!this.db) {
414
+ const artifacts = this.inMemoryStore.get('artifacts');
415
+ if (!artifacts) return [];
416
+ return Array.from(artifacts.values()).filter(
417
+ (a) => a.published_at >= startTime && a.published_at <= endTime
418
+ );
419
+ }
420
+
421
+ try {
422
+ const range = IDBKeyRange.bound(startTime, endTime);
423
+ const tx = this.db.transaction('artifacts', 'readonly');
424
+ const store = tx.objectStore('artifacts');
425
+ const index = store.index('published_at');
426
+ return await promisifyRequest(index.getAll(range));
427
+ } catch (error) {
428
+ console.error('[IndexedDB] Failed to query artifacts by time range:', error);
429
+ return [];
430
+ }
431
+ }
432
+
433
+ // ============================================================================
434
+ // CRUD Operations - Runs Store
435
+ // ============================================================================
436
+
437
+ async saveRun(run: RunRecord): Promise<void> {
438
+ if (!this.db) {
439
+ this.inMemoryStore.get('runs')?.set(run.run_id, run);
440
+ return;
441
+ }
442
+
443
+ try {
444
+ const tx = this.db.transaction('runs', 'readwrite');
445
+ const store = tx.objectStore('runs');
446
+ await promisifyRequest(store.put(run));
447
+ await this.checkAndEvictByRecordLimit('runs', 'started_at');
448
+ } catch (error) {
449
+ console.error('[IndexedDB] Failed to save run:', error);
450
+ }
451
+ }
452
+
453
+ async getRun(runId: string): Promise<RunRecord | undefined> {
454
+ if (!this.db) {
455
+ return this.inMemoryStore.get('runs')?.get(runId);
456
+ }
457
+
458
+ try {
459
+ const tx = this.db.transaction('runs', 'readonly');
460
+ const store = tx.objectStore('runs');
461
+ return await promisifyRequest(store.get(runId));
462
+ } catch (error) {
463
+ console.error('[IndexedDB] Failed to get run:', error);
464
+ return undefined;
465
+ }
466
+ }
467
+
468
+ // ============================================================================
469
+ // Layout Persistence - Agent View
470
+ // ============================================================================
471
+
472
+ async saveAgentViewLayout(layout: LayoutRecord): Promise<void> {
473
+ if (!this.db) {
474
+ this.inMemoryStore.get('layout_agent_view')?.set(layout.node_id, layout);
475
+ return;
476
+ }
477
+
478
+ try {
479
+ const tx = this.db.transaction('layout_agent_view', 'readwrite');
480
+ const store = tx.objectStore('layout_agent_view');
481
+ await promisifyRequest(store.put(layout));
482
+ } catch (error) {
483
+ console.error('[IndexedDB] Failed to save agent view layout:', error);
484
+ }
485
+ }
486
+
487
+ async getAgentViewLayout(nodeId: string): Promise<LayoutRecord | undefined> {
488
+ if (!this.db) {
489
+ return this.inMemoryStore.get('layout_agent_view')?.get(nodeId);
490
+ }
491
+
492
+ try {
493
+ const tx = this.db.transaction('layout_agent_view', 'readonly');
494
+ const store = tx.objectStore('layout_agent_view');
495
+ return await promisifyRequest(store.get(nodeId));
496
+ } catch (error) {
497
+ console.error('[IndexedDB] Failed to get agent view layout:', error);
498
+ return undefined;
499
+ }
500
+ }
501
+
502
+ async getAllAgentViewLayouts(): Promise<LayoutRecord[]> {
503
+ if (!this.db) {
504
+ const layouts = this.inMemoryStore.get('layout_agent_view');
505
+ return layouts ? Array.from(layouts.values()) : [];
506
+ }
507
+
508
+ try {
509
+ const tx = this.db.transaction('layout_agent_view', 'readonly');
510
+ const store = tx.objectStore('layout_agent_view');
511
+ return await promisifyRequest(store.getAll());
512
+ } catch (error) {
513
+ console.error('[IndexedDB] Failed to get all agent view layouts:', error);
514
+ return [];
515
+ }
516
+ }
517
+
518
+ // ============================================================================
519
+ // Layout Persistence - Blackboard View
520
+ // ============================================================================
521
+
522
+ async saveBlackboardViewLayout(layout: LayoutRecord): Promise<void> {
523
+ if (!this.db) {
524
+ this.inMemoryStore.get('layout_blackboard_view')?.set(layout.node_id, layout);
525
+ return;
526
+ }
527
+
528
+ try {
529
+ const tx = this.db.transaction('layout_blackboard_view', 'readwrite');
530
+ const store = tx.objectStore('layout_blackboard_view');
531
+ await promisifyRequest(store.put(layout));
532
+ } catch (error) {
533
+ console.error('[IndexedDB] Failed to save blackboard view layout:', error);
534
+ }
535
+ }
536
+
537
+ async getBlackboardViewLayout(nodeId: string): Promise<LayoutRecord | undefined> {
538
+ if (!this.db) {
539
+ return this.inMemoryStore.get('layout_blackboard_view')?.get(nodeId);
540
+ }
541
+
542
+ try {
543
+ const tx = this.db.transaction('layout_blackboard_view', 'readonly');
544
+ const store = tx.objectStore('layout_blackboard_view');
545
+ return await promisifyRequest(store.get(nodeId));
546
+ } catch (error) {
547
+ console.error('[IndexedDB] Failed to get blackboard view layout:', error);
548
+ return undefined;
549
+ }
550
+ }
551
+
552
+ async getAllBlackboardViewLayouts(): Promise<LayoutRecord[]> {
553
+ if (!this.db) {
554
+ const layouts = this.inMemoryStore.get('layout_blackboard_view');
555
+ return layouts ? Array.from(layouts.values()) : [];
556
+ }
557
+
558
+ try {
559
+ const tx = this.db.transaction('layout_blackboard_view', 'readonly');
560
+ const store = tx.objectStore('layout_blackboard_view');
561
+ return await promisifyRequest(store.getAll());
562
+ } catch (error) {
563
+ console.error('[IndexedDB] Failed to get all blackboard view layouts:', error);
564
+ return [];
565
+ }
566
+ }
567
+
568
+ // ============================================================================
569
+ // Module Instance Persistence
570
+ // ============================================================================
571
+
572
+ async saveModuleInstance(instance: ModuleInstanceRecord): Promise<void> {
573
+ if (!this.db) {
574
+ this.inMemoryStore.get('module_instances')?.set(instance.instance_id, instance);
575
+ return;
576
+ }
577
+
578
+ try {
579
+ const tx = this.db.transaction('module_instances', 'readwrite');
580
+ const store = tx.objectStore('module_instances');
581
+ await promisifyRequest(store.put(instance));
582
+ } catch (error) {
583
+ console.error('[IndexedDB] Failed to save module instance:', error);
584
+ }
585
+ }
586
+
587
+ async getModuleInstance(instanceId: string): Promise<ModuleInstanceRecord | undefined> {
588
+ if (!this.db) {
589
+ return this.inMemoryStore.get('module_instances')?.get(instanceId);
590
+ }
591
+
592
+ try {
593
+ const tx = this.db.transaction('module_instances', 'readonly');
594
+ const store = tx.objectStore('module_instances');
595
+ return await promisifyRequest(store.get(instanceId));
596
+ } catch (error) {
597
+ console.error('[IndexedDB] Failed to get module instance:', error);
598
+ return undefined;
599
+ }
600
+ }
601
+
602
+ async getAllModuleInstances(): Promise<ModuleInstanceRecord[]> {
603
+ if (!this.db) {
604
+ const instances = this.inMemoryStore.get('module_instances');
605
+ return instances ? Array.from(instances.values()) : [];
606
+ }
607
+
608
+ try {
609
+ const tx = this.db.transaction('module_instances', 'readonly');
610
+ const store = tx.objectStore('module_instances');
611
+ return await promisifyRequest(store.getAll());
612
+ } catch (error) {
613
+ console.error('[IndexedDB] Failed to get all module instances:', error);
614
+ return [];
615
+ }
616
+ }
617
+
618
+ async deleteModuleInstance(instanceId: string): Promise<void> {
619
+ if (!this.db) {
620
+ this.inMemoryStore.get('module_instances')?.delete(instanceId);
621
+ return;
622
+ }
623
+
624
+ try {
625
+ const tx = this.db.transaction('module_instances', 'readwrite');
626
+ const store = tx.objectStore('module_instances');
627
+ await promisifyRequest(store.delete(instanceId));
628
+ } catch (error) {
629
+ console.error('[IndexedDB] Failed to delete module instance:', error);
630
+ }
631
+ }
632
+
633
+ // ============================================================================
634
+ // Session Management
635
+ // ============================================================================
636
+
637
+ async saveSession(session: SessionRecord): Promise<void> {
638
+ if (!this.db) {
639
+ this.inMemoryStore.get('sessions')?.set(session.session_id, session);
640
+ return;
641
+ }
642
+
643
+ try {
644
+ const tx = this.db.transaction('sessions', 'readwrite');
645
+ const store = tx.objectStore('sessions');
646
+ await promisifyRequest(store.put(session));
647
+ } catch (error) {
648
+ console.error('[IndexedDB] Failed to save session:', error);
649
+ }
650
+ }
651
+
652
+ async getAllSessions(): Promise<SessionRecord[]> {
653
+ if (!this.db) {
654
+ const sessions = this.inMemoryStore.get('sessions');
655
+ return sessions ? Array.from(sessions.values()) : [];
656
+ }
657
+
658
+ try {
659
+ const tx = this.db.transaction('sessions', 'readonly');
660
+ const store = tx.objectStore('sessions');
661
+ return await promisifyRequest(store.getAll());
662
+ } catch (error) {
663
+ console.error('[IndexedDB] Failed to get all sessions:', error);
664
+ return [];
665
+ }
666
+ }
667
+
668
+ // ============================================================================
669
+ // LRU Eviction Strategy
670
+ // ============================================================================
671
+
672
+ /**
673
+ * Check if eviction should be triggered (80% quota threshold)
674
+ */
675
+ async checkShouldEvict(): Promise<boolean> {
676
+ if (!this.db) {
677
+ return false;
678
+ }
679
+
680
+ try {
681
+ if (!navigator.storage?.estimate) {
682
+ return false;
683
+ }
684
+
685
+ const estimate = await navigator.storage.estimate();
686
+ const usage = estimate.usage || 0;
687
+ const quota = estimate.quota || 0;
688
+
689
+ if (quota === 0) {
690
+ return false;
691
+ }
692
+
693
+ const percentage = usage / quota;
694
+
695
+ if (percentage > EVICTION_THRESHOLD) {
696
+ console.warn(
697
+ `[IndexedDB] Storage quota exceeded threshold: ${(percentage * 100).toFixed(1)}% (${usage}/${quota} bytes)`
698
+ );
699
+ return true;
700
+ }
701
+
702
+ return false;
703
+ } catch (error) {
704
+ console.error('[IndexedDB] Failed to check storage quota:', error);
705
+ return false;
706
+ }
707
+ }
708
+
709
+ /**
710
+ * Evict oldest sessions until usage reaches 60% target
711
+ */
712
+ async evictOldSessions(): Promise<void> {
713
+ if (!this.db) {
714
+ return;
715
+ }
716
+
717
+ try {
718
+ // Get all sessions sorted by created_at (oldest first)
719
+ const tx = this.db.transaction('sessions', 'readwrite');
720
+ const store = tx.objectStore('sessions');
721
+ const index = store.index('created_at');
722
+ const sessions = await promisifyRequest(index.getAll());
723
+
724
+ if (sessions.length === 0) {
725
+ return;
726
+ }
727
+
728
+ // Evict sessions one by one until target reached
729
+ for (const session of sessions) {
730
+ const estimate = await navigator.storage.estimate();
731
+ const usage = estimate.usage || 0;
732
+ const quota = estimate.quota || 0;
733
+
734
+ if (quota === 0) break;
735
+
736
+ const percentage = usage / quota;
737
+
738
+ // Stop if we've reached the target
739
+ if (percentage <= EVICTION_TARGET) {
740
+ break;
741
+ }
742
+
743
+ // Delete session
744
+ const deleteTx = this.db.transaction('sessions', 'readwrite');
745
+ const deleteStore = deleteTx.objectStore('sessions');
746
+ await promisifyRequest(deleteStore.delete(session.session_id));
747
+ console.log(`[IndexedDB] Evicted session: ${session.session_id}`);
748
+ }
749
+ } catch (error) {
750
+ console.error('[IndexedDB] Failed to evict old sessions:', error);
751
+ }
752
+ }
753
+
754
+ /**
755
+ * Check and evict by record limit (MAX_RECORDS_PER_STORE)
756
+ * Evicts oldest records based on specified index
757
+ */
758
+ private async checkAndEvictByRecordLimit(
759
+ storeName: string,
760
+ sortIndex: string
761
+ ): Promise<void> {
762
+ if (!this.db) {
763
+ return;
764
+ }
765
+
766
+ try {
767
+ const tx = this.db.transaction(storeName, 'readonly');
768
+ const store = tx.objectStore(storeName);
769
+ const count = await promisifyRequest(store.count());
770
+
771
+ if (count > MAX_RECORDS_PER_STORE) {
772
+ // Get oldest records using the sort index
773
+ const readTx = this.db.transaction(storeName, 'readonly');
774
+ const readStore = readTx.objectStore(storeName);
775
+ const index = readStore.index(sortIndex);
776
+ const keys = await promisifyRequest(index.getAllKeys());
777
+
778
+ // Delete oldest records until we're under the limit
779
+ const numToDelete = count - MAX_RECORDS_PER_STORE;
780
+ const writeTx = this.db.transaction(storeName, 'readwrite');
781
+ const writeStore = writeTx.objectStore(storeName);
782
+
783
+ for (let i = 0; i < numToDelete && i < keys.length; i++) {
784
+ await promisifyRequest(writeStore.delete(keys[i]!));
785
+ }
786
+ }
787
+ } catch (error) {
788
+ console.error(`[IndexedDB] Failed to evict by record limit for ${storeName}:`, error);
789
+ }
790
+ }
791
+ }
792
+
793
+ // Singleton instance
794
+ export const indexedDBService = new IndexedDBService();