flowent 0.0.1 → 0.0.4

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.
Files changed (493) hide show
  1. package/README.md +19 -8
  2. package/backend/.python-version +1 -0
  3. package/backend/pyproject.toml +57 -0
  4. package/backend/src/flowent/__init__.py +3 -0
  5. package/backend/src/flowent/__pycache__/__init__.cpython-313.pyc +0 -0
  6. package/backend/src/flowent/__pycache__/_version.cpython-313.pyc +0 -0
  7. package/backend/src/flowent/__pycache__/access.cpython-313.pyc +0 -0
  8. package/backend/src/flowent/__pycache__/agent.cpython-313.pyc +0 -0
  9. package/backend/src/flowent/__pycache__/assistant_commands.cpython-313.pyc +0 -0
  10. package/backend/src/flowent/__pycache__/cli.cpython-313.pyc +0 -0
  11. package/backend/src/flowent/__pycache__/config.cpython-313.pyc +0 -0
  12. package/backend/src/flowent/__pycache__/events.cpython-313.pyc +0 -0
  13. package/backend/src/flowent/__pycache__/graph_runtime.cpython-313.pyc +0 -0
  14. package/backend/src/flowent/__pycache__/graph_service.cpython-313.pyc +0 -0
  15. package/backend/src/flowent/__pycache__/image_assets.cpython-313.pyc +0 -0
  16. package/backend/src/flowent/__pycache__/logging.cpython-313.pyc +0 -0
  17. package/backend/src/flowent/__pycache__/main.cpython-313.pyc +0 -0
  18. package/backend/src/flowent/__pycache__/mcp_service.cpython-313.pyc +0 -0
  19. package/backend/src/flowent/__pycache__/model_metadata.cpython-313.pyc +0 -0
  20. package/backend/src/flowent/__pycache__/network.cpython-313.pyc +0 -0
  21. package/backend/src/flowent/__pycache__/registry.cpython-313.pyc +0 -0
  22. package/backend/src/flowent/__pycache__/role_management.cpython-313.pyc +0 -0
  23. package/backend/src/flowent/__pycache__/runtime.cpython-313.pyc +0 -0
  24. package/backend/src/flowent/__pycache__/sandbox.cpython-313.pyc +0 -0
  25. package/backend/src/flowent/__pycache__/security.cpython-313.pyc +0 -0
  26. package/backend/src/flowent/__pycache__/settings.cpython-313.pyc +0 -0
  27. package/backend/src/flowent/__pycache__/settings_management.cpython-313.pyc +0 -0
  28. package/backend/src/flowent/__pycache__/state_db.cpython-313.pyc +0 -0
  29. package/backend/src/flowent/__pycache__/stats_service.cpython-313.pyc +0 -0
  30. package/backend/src/flowent/__pycache__/workspace_store.cpython-313.pyc +0 -0
  31. package/backend/src/flowent/_version.py +7 -0
  32. package/backend/src/flowent/access.py +247 -0
  33. package/backend/src/flowent/agent.py +2808 -0
  34. package/backend/src/flowent/assistant_commands.py +106 -0
  35. package/backend/src/flowent/channels/__init__.py +3 -0
  36. package/backend/src/flowent/channels/__pycache__/__init__.cpython-313.pyc +0 -0
  37. package/backend/src/flowent/channels/__pycache__/telegram.cpython-313.pyc +0 -0
  38. package/backend/src/flowent/channels/telegram.py +615 -0
  39. package/backend/src/flowent/cli.py +85 -0
  40. package/backend/src/flowent/config.py +14 -0
  41. package/backend/src/flowent/dev.py +3 -0
  42. package/backend/src/flowent/events.py +157 -0
  43. package/backend/src/flowent/graph_runtime.py +60 -0
  44. package/backend/src/flowent/graph_service.py +1346 -0
  45. package/backend/src/flowent/image_assets.py +356 -0
  46. package/backend/src/flowent/logging.py +155 -0
  47. package/backend/src/flowent/main.py +124 -0
  48. package/backend/src/flowent/mcp_service.py +1904 -0
  49. package/backend/src/flowent/model_metadata.py +98 -0
  50. package/backend/src/flowent/models/__init__.py +121 -0
  51. package/backend/src/flowent/models/__pycache__/__init__.cpython-313.pyc +0 -0
  52. package/backend/src/flowent/models/__pycache__/agent.cpython-313.pyc +0 -0
  53. package/backend/src/flowent/models/__pycache__/base.cpython-313.pyc +0 -0
  54. package/backend/src/flowent/models/__pycache__/blueprint.cpython-313.pyc +0 -0
  55. package/backend/src/flowent/models/__pycache__/content.cpython-313.pyc +0 -0
  56. package/backend/src/flowent/models/__pycache__/delta.cpython-313.pyc +0 -0
  57. package/backend/src/flowent/models/__pycache__/event.cpython-313.pyc +0 -0
  58. package/backend/src/flowent/models/__pycache__/graph.cpython-313.pyc +0 -0
  59. package/backend/src/flowent/models/__pycache__/history.cpython-313.pyc +0 -0
  60. package/backend/src/flowent/models/__pycache__/llm.cpython-313.pyc +0 -0
  61. package/backend/src/flowent/models/__pycache__/message.cpython-313.pyc +0 -0
  62. package/backend/src/flowent/models/__pycache__/tab.cpython-313.pyc +0 -0
  63. package/backend/src/flowent/models/__pycache__/todo.cpython-313.pyc +0 -0
  64. package/backend/src/flowent/models/agent.py +33 -0
  65. package/backend/src/flowent/models/base.py +24 -0
  66. package/backend/src/flowent/models/blueprint.py +176 -0
  67. package/backend/src/flowent/models/content.py +164 -0
  68. package/backend/src/flowent/models/delta.py +44 -0
  69. package/backend/src/flowent/models/event.py +51 -0
  70. package/backend/src/flowent/models/graph.py +437 -0
  71. package/backend/src/flowent/models/history.py +214 -0
  72. package/backend/src/flowent/models/llm.py +61 -0
  73. package/backend/src/flowent/models/message.py +27 -0
  74. package/backend/src/flowent/models/tab.py +48 -0
  75. package/backend/src/flowent/models/todo.py +10 -0
  76. package/backend/src/flowent/network.py +146 -0
  77. package/backend/src/flowent/prompts/__init__.py +67 -0
  78. package/backend/src/flowent/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  79. package/backend/src/flowent/prompts/__pycache__/common.cpython-313.pyc +0 -0
  80. package/backend/src/flowent/prompts/__pycache__/steward.cpython-313.pyc +0 -0
  81. package/backend/src/flowent/prompts/common.py +250 -0
  82. package/backend/src/flowent/prompts/steward.py +64 -0
  83. package/backend/src/flowent/providers/__init__.py +23 -0
  84. package/backend/src/flowent/providers/__pycache__/__init__.cpython-313.pyc +0 -0
  85. package/backend/src/flowent/providers/__pycache__/anthropic.cpython-313.pyc +0 -0
  86. package/backend/src/flowent/providers/__pycache__/base_url.cpython-313.pyc +0 -0
  87. package/backend/src/flowent/providers/__pycache__/configuration.cpython-313.pyc +0 -0
  88. package/backend/src/flowent/providers/__pycache__/content.cpython-313.pyc +0 -0
  89. package/backend/src/flowent/providers/__pycache__/errors.cpython-313.pyc +0 -0
  90. package/backend/src/flowent/providers/__pycache__/gateway.cpython-313.pyc +0 -0
  91. package/backend/src/flowent/providers/__pycache__/headers.cpython-313.pyc +0 -0
  92. package/backend/src/flowent/providers/__pycache__/management.cpython-313.pyc +0 -0
  93. package/backend/src/flowent/providers/__pycache__/openai.cpython-313.pyc +0 -0
  94. package/backend/src/flowent/providers/__pycache__/openai_responses.cpython-313.pyc +0 -0
  95. package/backend/src/flowent/providers/__pycache__/registry.cpython-313.pyc +0 -0
  96. package/backend/src/flowent/providers/__pycache__/sse.cpython-313.pyc +0 -0
  97. package/backend/src/flowent/providers/__pycache__/thinking.cpython-313.pyc +0 -0
  98. package/backend/src/flowent/providers/anthropic.py +468 -0
  99. package/backend/src/flowent/providers/base_url.py +60 -0
  100. package/backend/src/flowent/providers/configuration.py +182 -0
  101. package/backend/src/flowent/providers/content.py +122 -0
  102. package/backend/src/flowent/providers/errors.py +223 -0
  103. package/backend/src/flowent/providers/gateway.py +169 -0
  104. package/backend/src/flowent/providers/gemini.py +447 -0
  105. package/backend/src/flowent/providers/headers.py +20 -0
  106. package/backend/src/flowent/providers/management.py +96 -0
  107. package/backend/src/flowent/providers/ollama.py +293 -0
  108. package/backend/src/flowent/providers/openai.py +422 -0
  109. package/backend/src/flowent/providers/openai_responses.py +655 -0
  110. package/backend/src/flowent/providers/registry.py +144 -0
  111. package/backend/src/flowent/providers/sse.py +31 -0
  112. package/backend/src/flowent/providers/thinking.py +79 -0
  113. package/backend/src/flowent/registry.py +73 -0
  114. package/backend/src/flowent/role_management.py +255 -0
  115. package/backend/src/flowent/routes/__init__.py +30 -0
  116. package/backend/src/flowent/routes/__pycache__/__init__.cpython-313.pyc +0 -0
  117. package/backend/src/flowent/routes/__pycache__/access.cpython-313.pyc +0 -0
  118. package/backend/src/flowent/routes/__pycache__/assistant.cpython-313.pyc +0 -0
  119. package/backend/src/flowent/routes/__pycache__/image_assets.cpython-313.pyc +0 -0
  120. package/backend/src/flowent/routes/__pycache__/mcp.cpython-313.pyc +0 -0
  121. package/backend/src/flowent/routes/__pycache__/meta.cpython-313.pyc +0 -0
  122. package/backend/src/flowent/routes/__pycache__/nodes.cpython-313.pyc +0 -0
  123. package/backend/src/flowent/routes/__pycache__/prompts.cpython-313.pyc +0 -0
  124. package/backend/src/flowent/routes/__pycache__/providers_route.cpython-313.pyc +0 -0
  125. package/backend/src/flowent/routes/__pycache__/roles.cpython-313.pyc +0 -0
  126. package/backend/src/flowent/routes/__pycache__/settings.cpython-313.pyc +0 -0
  127. package/backend/src/flowent/routes/__pycache__/stats.cpython-313.pyc +0 -0
  128. package/backend/src/flowent/routes/__pycache__/tabs.cpython-313.pyc +0 -0
  129. package/backend/src/flowent/routes/__pycache__/ws.cpython-313.pyc +0 -0
  130. package/backend/src/flowent/routes/access.py +48 -0
  131. package/backend/src/flowent/routes/assistant.py +155 -0
  132. package/backend/src/flowent/routes/image_assets.py +33 -0
  133. package/backend/src/flowent/routes/mcp.py +125 -0
  134. package/backend/src/flowent/routes/meta.py +28 -0
  135. package/backend/src/flowent/routes/nodes.py +365 -0
  136. package/backend/src/flowent/routes/prompts.py +46 -0
  137. package/backend/src/flowent/routes/providers_route.py +364 -0
  138. package/backend/src/flowent/routes/roles.py +207 -0
  139. package/backend/src/flowent/routes/settings.py +324 -0
  140. package/backend/src/flowent/routes/stats.py +229 -0
  141. package/backend/src/flowent/routes/tabs.py +292 -0
  142. package/backend/src/flowent/routes/ws.py +33 -0
  143. package/backend/src/flowent/runtime.py +188 -0
  144. package/backend/src/flowent/sandbox.py +45 -0
  145. package/backend/src/flowent/security.py +42 -0
  146. package/backend/src/flowent/settings.py +2467 -0
  147. package/backend/src/flowent/settings_management.py +286 -0
  148. package/backend/src/flowent/state_db.py +120 -0
  149. package/backend/src/flowent/static/assets/AssistantPage-B3Xc08AS.js +1 -0
  150. package/backend/src/flowent/static/assets/ChannelsPage-ByLd28xk.js +1 -0
  151. package/backend/src/flowent/static/assets/HomePage-C0hAx9_l.js +3 -0
  152. package/backend/src/flowent/static/assets/McpPage-DkrYLvBv.js +7 -0
  153. package/backend/src/flowent/static/assets/PageScaffold-D4jO9ooX.js +1 -0
  154. package/backend/src/flowent/static/assets/PromptsPage-DWA7rRJd.js +1 -0
  155. package/backend/src/flowent/static/assets/ProvidersPage-PUWT8seJ.js +3 -0
  156. package/backend/src/flowent/static/assets/RolesPage-CqcclGRw.js +1 -0
  157. package/backend/src/flowent/static/assets/SettingsPage-8tS2cJgX.js +3 -0
  158. package/backend/src/flowent/static/assets/StatsPage-BX9khYzu.js +1 -0
  159. package/backend/src/flowent/static/assets/ToolsPage-9Tl9FdeD.js +1 -0
  160. package/backend/src/flowent/static/assets/WorkspaceCommandDialog-CCXxjDL8.js +1 -0
  161. package/backend/src/flowent/static/assets/WorkspacePanels-aMdJ7ZH7.js +1 -0
  162. package/backend/src/flowent/static/assets/alert-dialog-kFYVQ7oX.js +1 -0
  163. package/backend/src/flowent/static/assets/badge-74-3jsCg.js +1 -0
  164. package/backend/src/flowent/static/assets/constants-XUzFf6i1.js +1 -0
  165. package/backend/src/flowent/static/assets/datetime-m6_O_Ci9.js +1 -0
  166. package/backend/src/flowent/static/assets/dialog-BeGSweF6.js +1 -0
  167. package/backend/src/flowent/static/assets/elk-worker.min-C9JGDOE-.js +6312 -0
  168. package/backend/src/flowent/static/assets/graph-vendor-CHpVij2M.css +1 -0
  169. package/backend/src/flowent/static/assets/graph-vendor-DRq_-6fV.js +7 -0
  170. package/backend/src/flowent/static/assets/index-BHC1Vhy8.css +1 -0
  171. package/backend/src/flowent/static/assets/index-CL1ALZ3r.js +10 -0
  172. package/backend/src/flowent/static/assets/layout.worker-jMHqAFbP.js +24 -0
  173. package/backend/src/flowent/static/assets/markdown-vendor-DVdy_w12.js +29 -0
  174. package/backend/src/flowent/static/assets/modelParams-CaHd0903.js +1 -0
  175. package/backend/src/flowent/static/assets/react-vendor-mEs_JJxa.js +9 -0
  176. package/backend/src/flowent/static/assets/roles-2OLDeTc5.js +1 -0
  177. package/backend/src/flowent/static/assets/rolldown-runtime-BYbx6iT9.js +1 -0
  178. package/backend/src/flowent/static/assets/select-DL_LPeDj.js +1 -0
  179. package/backend/src/flowent/static/assets/shared-CMxbpLeQ.js +1 -0
  180. package/backend/src/flowent/static/assets/triState-DEr3NkXV.js +1 -0
  181. package/backend/src/flowent/static/assets/ui-vendor-Dg9NNnWX.js +51 -0
  182. package/backend/src/flowent/static/index.html +36 -0
  183. package/backend/src/flowent/stats_service.py +218 -0
  184. package/backend/src/flowent/tools/__init__.py +201 -0
  185. package/backend/src/flowent/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  186. package/backend/src/flowent/tools/__pycache__/connect.cpython-313.pyc +0 -0
  187. package/backend/src/flowent/tools/__pycache__/contacts.cpython-313.pyc +0 -0
  188. package/backend/src/flowent/tools/__pycache__/create_agent.cpython-313.pyc +0 -0
  189. package/backend/src/flowent/tools/__pycache__/create_tab.cpython-313.pyc +0 -0
  190. package/backend/src/flowent/tools/__pycache__/delete_tab.cpython-313.pyc +0 -0
  191. package/backend/src/flowent/tools/__pycache__/edit.cpython-313.pyc +0 -0
  192. package/backend/src/flowent/tools/__pycache__/exec.cpython-313.pyc +0 -0
  193. package/backend/src/flowent/tools/__pycache__/fetch.cpython-313.pyc +0 -0
  194. package/backend/src/flowent/tools/__pycache__/idle.cpython-313.pyc +0 -0
  195. package/backend/src/flowent/tools/__pycache__/list_roles.cpython-313.pyc +0 -0
  196. package/backend/src/flowent/tools/__pycache__/list_tabs.cpython-313.pyc +0 -0
  197. package/backend/src/flowent/tools/__pycache__/list_tools.cpython-313.pyc +0 -0
  198. package/backend/src/flowent/tools/__pycache__/manage_prompts.cpython-313.pyc +0 -0
  199. package/backend/src/flowent/tools/__pycache__/manage_providers.cpython-313.pyc +0 -0
  200. package/backend/src/flowent/tools/__pycache__/manage_roles.cpython-313.pyc +0 -0
  201. package/backend/src/flowent/tools/__pycache__/manage_settings.cpython-313.pyc +0 -0
  202. package/backend/src/flowent/tools/__pycache__/mcp.cpython-313.pyc +0 -0
  203. package/backend/src/flowent/tools/__pycache__/read.cpython-313.pyc +0 -0
  204. package/backend/src/flowent/tools/__pycache__/send.cpython-313.pyc +0 -0
  205. package/backend/src/flowent/tools/__pycache__/set_permissions.cpython-313.pyc +0 -0
  206. package/backend/src/flowent/tools/__pycache__/sleep.cpython-313.pyc +0 -0
  207. package/backend/src/flowent/tools/__pycache__/todo.cpython-313.pyc +0 -0
  208. package/backend/src/flowent/tools/connect.py +156 -0
  209. package/backend/src/flowent/tools/contacts.py +22 -0
  210. package/backend/src/flowent/tools/create_agent.py +270 -0
  211. package/backend/src/flowent/tools/create_tab.py +59 -0
  212. package/backend/src/flowent/tools/delete_tab.py +39 -0
  213. package/backend/src/flowent/tools/edit.py +142 -0
  214. package/backend/src/flowent/tools/exec.py +117 -0
  215. package/backend/src/flowent/tools/fetch.py +85 -0
  216. package/backend/src/flowent/tools/idle.py +27 -0
  217. package/backend/src/flowent/tools/list_roles.py +50 -0
  218. package/backend/src/flowent/tools/list_tabs.py +96 -0
  219. package/backend/src/flowent/tools/list_tools.py +24 -0
  220. package/backend/src/flowent/tools/manage_prompts.py +102 -0
  221. package/backend/src/flowent/tools/manage_providers.py +220 -0
  222. package/backend/src/flowent/tools/manage_roles.py +275 -0
  223. package/backend/src/flowent/tools/manage_settings.py +346 -0
  224. package/backend/src/flowent/tools/mcp.py +199 -0
  225. package/backend/src/flowent/tools/read.py +152 -0
  226. package/backend/src/flowent/tools/send.py +50 -0
  227. package/backend/src/flowent/tools/set_permissions.py +84 -0
  228. package/backend/src/flowent/tools/sleep.py +41 -0
  229. package/backend/src/flowent/tools/todo.py +51 -0
  230. package/backend/src/flowent/workspace_store.py +479 -0
  231. package/backend/tests/__init__.py +0 -0
  232. package/backend/tests/__pycache__/__init__.cpython-313.pyc +0 -0
  233. package/backend/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  234. package/backend/tests/conftest.py +6 -0
  235. package/backend/tests/integration/api/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  236. package/backend/tests/integration/api/__pycache__/test_access_api.cpython-313-pytest-9.0.3.pyc +0 -0
  237. package/backend/tests/integration/api/__pycache__/test_assistant_api.cpython-313-pytest-9.0.3.pyc +0 -0
  238. package/backend/tests/integration/api/__pycache__/test_frontend_mounting.cpython-313-pytest-9.0.3.pyc +0 -0
  239. package/backend/tests/integration/api/__pycache__/test_mcp_api.cpython-313-pytest-9.0.3.pyc +0 -0
  240. package/backend/tests/integration/api/__pycache__/test_meta_api.cpython-313-pytest-9.0.3.pyc +0 -0
  241. package/backend/tests/integration/api/__pycache__/test_nodes_api.cpython-313-pytest-9.0.3.pyc +0 -0
  242. package/backend/tests/integration/api/__pycache__/test_prompts_api.cpython-313-pytest-9.0.3.pyc +0 -0
  243. package/backend/tests/integration/api/__pycache__/test_roles_api.cpython-313-pytest-9.0.3.pyc +0 -0
  244. package/backend/tests/integration/api/__pycache__/test_tabs_api.cpython-313-pytest-9.0.3.pyc +0 -0
  245. package/backend/tests/integration/api/conftest.py +29 -0
  246. package/backend/tests/integration/api/test_access_api.py +182 -0
  247. package/backend/tests/integration/api/test_assistant_api.py +354 -0
  248. package/backend/tests/integration/api/test_frontend_mounting.py +61 -0
  249. package/backend/tests/integration/api/test_mcp_api.py +116 -0
  250. package/backend/tests/integration/api/test_meta_api.py +33 -0
  251. package/backend/tests/integration/api/test_nodes_api.py +486 -0
  252. package/backend/tests/integration/api/test_prompts_api.py +47 -0
  253. package/backend/tests/integration/api/test_roles_api.py +227 -0
  254. package/backend/tests/integration/api/test_tabs_api.py +501 -0
  255. package/backend/tests/unit/__pycache__/test_access.cpython-313-pytest-9.0.3.pyc +0 -0
  256. package/backend/tests/unit/__pycache__/test_cli.cpython-313-pytest-9.0.3.pyc +0 -0
  257. package/backend/tests/unit/__pycache__/test_graph_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  258. package/backend/tests/unit/__pycache__/test_network.cpython-313-pytest-9.0.3.pyc +0 -0
  259. package/backend/tests/unit/__pycache__/test_state_sqlite_storage.cpython-313-pytest-9.0.3.pyc +0 -0
  260. package/backend/tests/unit/__pycache__/test_workspace_store.cpython-313-pytest-9.0.3.pyc +0 -0
  261. package/backend/tests/unit/agent/__pycache__/test_agent_public_api.cpython-313-pytest-9.0.3.pyc +0 -0
  262. package/backend/tests/unit/agent/__pycache__/test_agent_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  263. package/backend/tests/unit/agent/test_agent_public_api.py +746 -0
  264. package/backend/tests/unit/agent/test_agent_runtime.py +2726 -0
  265. package/backend/tests/unit/channels/__pycache__/test_telegram_channel.cpython-313-pytest-9.0.3.pyc +0 -0
  266. package/backend/tests/unit/channels/test_telegram_channel.py +552 -0
  267. package/backend/tests/unit/logging/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
  268. package/backend/tests/unit/logging/test_logging.py +132 -0
  269. package/backend/tests/unit/prompts/__pycache__/test_prompts.cpython-313-pytest-9.0.3.pyc +0 -0
  270. package/backend/tests/unit/prompts/test_prompts.py +569 -0
  271. package/backend/tests/unit/providers/__pycache__/test_anthropic_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  272. package/backend/tests/unit/providers/__pycache__/test_errors.cpython-313-pytest-9.0.3.pyc +0 -0
  273. package/backend/tests/unit/providers/__pycache__/test_extract_delta_parts.cpython-313-pytest-9.0.3.pyc +0 -0
  274. package/backend/tests/unit/providers/__pycache__/test_openai_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  275. package/backend/tests/unit/providers/__pycache__/test_openai_responses.cpython-313-pytest-9.0.3.pyc +0 -0
  276. package/backend/tests/unit/providers/__pycache__/test_provider_gateway.cpython-313-pytest-9.0.3.pyc +0 -0
  277. package/backend/tests/unit/providers/__pycache__/test_think_tag_parser.cpython-313-pytest-9.0.3.pyc +0 -0
  278. package/backend/tests/unit/providers/test_anthropic_provider.py +185 -0
  279. package/backend/tests/unit/providers/test_errors.py +68 -0
  280. package/backend/tests/unit/providers/test_extract_delta_parts.py +22 -0
  281. package/backend/tests/unit/providers/test_openai_provider.py +139 -0
  282. package/backend/tests/unit/providers/test_openai_responses.py +402 -0
  283. package/backend/tests/unit/providers/test_provider_gateway.py +359 -0
  284. package/backend/tests/unit/providers/test_think_tag_parser.py +36 -0
  285. package/backend/tests/unit/routes/__pycache__/test_prompts_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  286. package/backend/tests/unit/routes/__pycache__/test_providers_route.cpython-313-pytest-9.0.3.pyc +0 -0
  287. package/backend/tests/unit/routes/__pycache__/test_roles_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  288. package/backend/tests/unit/routes/__pycache__/test_settings_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  289. package/backend/tests/unit/routes/__pycache__/test_stats_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  290. package/backend/tests/unit/routes/test_prompts_routes.py +104 -0
  291. package/backend/tests/unit/routes/test_providers_route.py +368 -0
  292. package/backend/tests/unit/routes/test_roles_routes.py +426 -0
  293. package/backend/tests/unit/routes/test_settings_routes.py +1138 -0
  294. package/backend/tests/unit/routes/test_stats_routes.py +149 -0
  295. package/backend/tests/unit/runtime/__pycache__/test_bootstrap_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  296. package/backend/tests/unit/runtime/test_bootstrap_runtime.py +1012 -0
  297. package/backend/tests/unit/sandbox/__pycache__/test_sandbox_tools.cpython-313-pytest-9.0.3.pyc +0 -0
  298. package/backend/tests/unit/sandbox/test_sandbox_tools.py +78 -0
  299. package/backend/tests/unit/security/__pycache__/test_security.cpython-313-pytest-9.0.3.pyc +0 -0
  300. package/backend/tests/unit/security/test_security.py +110 -0
  301. package/backend/tests/unit/settings/__pycache__/test_settings_roles.cpython-313-pytest-9.0.3.pyc +0 -0
  302. package/backend/tests/unit/settings/test_settings_roles.py +711 -0
  303. package/backend/tests/unit/test_access.py +45 -0
  304. package/backend/tests/unit/test_cli.py +124 -0
  305. package/backend/tests/unit/test_graph_runtime.py +72 -0
  306. package/backend/tests/unit/test_network.py +51 -0
  307. package/backend/tests/unit/test_state_sqlite_storage.py +93 -0
  308. package/backend/tests/unit/test_workspace_store.py +231 -0
  309. package/backend/tests/unit/tools/__pycache__/test_connect_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  310. package/backend/tests/unit/tools/__pycache__/test_create_agent_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  311. package/backend/tests/unit/tools/__pycache__/test_delete_tab_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  312. package/backend/tests/unit/tools/__pycache__/test_edit_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  313. package/backend/tests/unit/tools/__pycache__/test_exec_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  314. package/backend/tests/unit/tools/__pycache__/test_fetch_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  315. package/backend/tests/unit/tools/__pycache__/test_manage_prompts_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  316. package/backend/tests/unit/tools/__pycache__/test_manage_providers_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  317. package/backend/tests/unit/tools/__pycache__/test_manage_roles_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  318. package/backend/tests/unit/tools/__pycache__/test_manage_settings_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  319. package/backend/tests/unit/tools/__pycache__/test_read_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  320. package/backend/tests/unit/tools/__pycache__/test_set_permissions_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  321. package/backend/tests/unit/tools/__pycache__/test_todo_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  322. package/backend/tests/unit/tools/__pycache__/test_tool_registry.cpython-313-pytest-9.0.3.pyc +0 -0
  323. package/backend/tests/unit/tools/test_connect_tool.py +229 -0
  324. package/backend/tests/unit/tools/test_create_agent_tool.py +524 -0
  325. package/backend/tests/unit/tools/test_delete_tab_tool.py +83 -0
  326. package/backend/tests/unit/tools/test_edit_tool.py +115 -0
  327. package/backend/tests/unit/tools/test_exec_tool.py +81 -0
  328. package/backend/tests/unit/tools/test_fetch_tool.py +65 -0
  329. package/backend/tests/unit/tools/test_manage_prompts_tool.py +117 -0
  330. package/backend/tests/unit/tools/test_manage_providers_tool.py +458 -0
  331. package/backend/tests/unit/tools/test_manage_roles_tool.py +411 -0
  332. package/backend/tests/unit/tools/test_manage_settings_tool.py +608 -0
  333. package/backend/tests/unit/tools/test_read_tool.py +33 -0
  334. package/backend/tests/unit/tools/test_set_permissions_tool.py +391 -0
  335. package/backend/tests/unit/tools/test_todo_tool.py +37 -0
  336. package/backend/tests/unit/tools/test_tool_registry.py +91 -0
  337. package/backend/uv.lock +1144 -0
  338. package/bin/flowent.mjs +62 -36
  339. package/dist/frontend/assets/AssistantPage-B3Xc08AS.js +1 -0
  340. package/dist/frontend/assets/ChannelsPage-ByLd28xk.js +1 -0
  341. package/dist/frontend/assets/HomePage-C0hAx9_l.js +3 -0
  342. package/dist/frontend/assets/McpPage-DkrYLvBv.js +7 -0
  343. package/dist/frontend/assets/PageScaffold-D4jO9ooX.js +1 -0
  344. package/dist/frontend/assets/PromptsPage-DWA7rRJd.js +1 -0
  345. package/dist/frontend/assets/ProvidersPage-PUWT8seJ.js +3 -0
  346. package/dist/frontend/assets/RolesPage-CqcclGRw.js +1 -0
  347. package/dist/frontend/assets/SettingsPage-8tS2cJgX.js +3 -0
  348. package/dist/frontend/assets/StatsPage-BX9khYzu.js +1 -0
  349. package/dist/frontend/assets/ToolsPage-9Tl9FdeD.js +1 -0
  350. package/dist/frontend/assets/WorkspaceCommandDialog-CCXxjDL8.js +1 -0
  351. package/dist/frontend/assets/WorkspacePanels-aMdJ7ZH7.js +1 -0
  352. package/dist/frontend/assets/alert-dialog-kFYVQ7oX.js +1 -0
  353. package/dist/frontend/assets/badge-74-3jsCg.js +1 -0
  354. package/dist/frontend/assets/constants-XUzFf6i1.js +1 -0
  355. package/dist/frontend/assets/datetime-m6_O_Ci9.js +1 -0
  356. package/dist/frontend/assets/dialog-BeGSweF6.js +1 -0
  357. package/dist/frontend/assets/elk-worker.min-C9JGDOE-.js +6312 -0
  358. package/dist/frontend/assets/graph-vendor-CHpVij2M.css +1 -0
  359. package/dist/frontend/assets/graph-vendor-DRq_-6fV.js +7 -0
  360. package/dist/frontend/assets/index-BHC1Vhy8.css +1 -0
  361. package/dist/frontend/assets/index-CL1ALZ3r.js +10 -0
  362. package/dist/frontend/assets/layout.worker-jMHqAFbP.js +24 -0
  363. package/dist/frontend/assets/markdown-vendor-DVdy_w12.js +29 -0
  364. package/dist/frontend/assets/modelParams-CaHd0903.js +1 -0
  365. package/dist/frontend/assets/react-vendor-mEs_JJxa.js +9 -0
  366. package/dist/frontend/assets/roles-2OLDeTc5.js +1 -0
  367. package/dist/frontend/assets/rolldown-runtime-BYbx6iT9.js +1 -0
  368. package/dist/frontend/assets/select-DL_LPeDj.js +1 -0
  369. package/dist/frontend/assets/shared-CMxbpLeQ.js +1 -0
  370. package/dist/frontend/assets/triState-DEr3NkXV.js +1 -0
  371. package/dist/frontend/assets/ui-vendor-Dg9NNnWX.js +51 -0
  372. package/dist/frontend/index.html +36 -0
  373. package/package.json +27 -41
  374. package/dist/.next/BUILD_ID +0 -1
  375. package/dist/.next/app-path-routes-manifest.json +0 -6
  376. package/dist/.next/build-manifest.json +0 -20
  377. package/dist/.next/package.json +0 -1
  378. package/dist/.next/prerender-manifest.json +0 -114
  379. package/dist/.next/required-server-files.json +0 -333
  380. package/dist/.next/routes-manifest.json +0 -69
  381. package/dist/.next/server/app/_global-error/page/app-paths-manifest.json +0 -3
  382. package/dist/.next/server/app/_global-error/page/build-manifest.json +0 -16
  383. package/dist/.next/server/app/_global-error/page/next-font-manifest.json +0 -6
  384. package/dist/.next/server/app/_global-error/page/react-loadable-manifest.json +0 -1
  385. package/dist/.next/server/app/_global-error/page/server-reference-manifest.json +0 -4
  386. package/dist/.next/server/app/_global-error/page.js +0 -9
  387. package/dist/.next/server/app/_global-error/page.js.map +0 -5
  388. package/dist/.next/server/app/_global-error/page.js.nft.json +0 -1
  389. package/dist/.next/server/app/_global-error/page_client-reference-manifest.js +0 -3
  390. package/dist/.next/server/app/_global-error.html +0 -1
  391. package/dist/.next/server/app/_global-error.meta +0 -15
  392. package/dist/.next/server/app/_global-error.rsc +0 -14
  393. package/dist/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +0 -5
  394. package/dist/.next/server/app/_global-error.segments/_full.segment.rsc +0 -14
  395. package/dist/.next/server/app/_global-error.segments/_head.segment.rsc +0 -5
  396. package/dist/.next/server/app/_global-error.segments/_index.segment.rsc +0 -5
  397. package/dist/.next/server/app/_global-error.segments/_tree.segment.rsc +0 -1
  398. package/dist/.next/server/app/_not-found/page/app-paths-manifest.json +0 -3
  399. package/dist/.next/server/app/_not-found/page/build-manifest.json +0 -16
  400. package/dist/.next/server/app/_not-found/page/next-font-manifest.json +0 -10
  401. package/dist/.next/server/app/_not-found/page/react-loadable-manifest.json +0 -1
  402. package/dist/.next/server/app/_not-found/page/server-reference-manifest.json +0 -4
  403. package/dist/.next/server/app/_not-found/page.js +0 -13
  404. package/dist/.next/server/app/_not-found/page.js.map +0 -5
  405. package/dist/.next/server/app/_not-found/page.js.nft.json +0 -1
  406. package/dist/.next/server/app/_not-found/page_client-reference-manifest.js +0 -3
  407. package/dist/.next/server/app/_not-found.html +0 -1
  408. package/dist/.next/server/app/_not-found.meta +0 -16
  409. package/dist/.next/server/app/_not-found.rsc +0 -16
  410. package/dist/.next/server/app/_not-found.segments/_full.segment.rsc +0 -16
  411. package/dist/.next/server/app/_not-found.segments/_head.segment.rsc +0 -6
  412. package/dist/.next/server/app/_not-found.segments/_index.segment.rsc +0 -5
  413. package/dist/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +0 -5
  414. package/dist/.next/server/app/_not-found.segments/_not-found.segment.rsc +0 -5
  415. package/dist/.next/server/app/_not-found.segments/_tree.segment.rsc +0 -2
  416. package/dist/.next/server/app/icon.svg/route/app-paths-manifest.json +0 -3
  417. package/dist/.next/server/app/icon.svg/route/build-manifest.json +0 -9
  418. package/dist/.next/server/app/icon.svg/route.js +0 -6
  419. package/dist/.next/server/app/icon.svg/route.js.map +0 -5
  420. package/dist/.next/server/app/icon.svg/route.js.nft.json +0 -1
  421. package/dist/.next/server/app/icon.svg.meta +0 -1
  422. package/dist/.next/server/app/index.html +0 -1
  423. package/dist/.next/server/app/index.meta +0 -14
  424. package/dist/.next/server/app/index.rsc +0 -15
  425. package/dist/.next/server/app/index.segments/__PAGE__.segment.rsc +0 -5
  426. package/dist/.next/server/app/index.segments/_full.segment.rsc +0 -15
  427. package/dist/.next/server/app/index.segments/_head.segment.rsc +0 -6
  428. package/dist/.next/server/app/index.segments/_index.segment.rsc +0 -5
  429. package/dist/.next/server/app/index.segments/_tree.segment.rsc +0 -3
  430. package/dist/.next/server/app/page/app-paths-manifest.json +0 -3
  431. package/dist/.next/server/app/page/build-manifest.json +0 -16
  432. package/dist/.next/server/app/page/next-font-manifest.json +0 -10
  433. package/dist/.next/server/app/page/react-loadable-manifest.json +0 -1
  434. package/dist/.next/server/app/page/server-reference-manifest.json +0 -4
  435. package/dist/.next/server/app/page.js +0 -14
  436. package/dist/.next/server/app/page.js.map +0 -5
  437. package/dist/.next/server/app/page.js.nft.json +0 -1
  438. package/dist/.next/server/app/page_client-reference-manifest.js +0 -3
  439. package/dist/.next/server/app-paths-manifest.json +0 -6
  440. package/dist/.next/server/chunks/[externals]_next_dist_0arv.vj._.js +0 -3
  441. package/dist/.next/server/chunks/[root-of-the-server]__0vcj1q1._.js +0 -13
  442. package/dist/.next/server/chunks/[turbopack]_runtime.js +0 -903
  443. package/dist/.next/server/chunks/_next-internal_server_app_icon_svg_route_actions_0-0ehc~.js +0 -3
  444. package/dist/.next/server/chunks/ssr/05w9_next_dist_0ihu0u9._.js +0 -6
  445. package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_12u3mib._.js +0 -3
  446. package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_builtin_forbidden_04fbe_..js +0 -3
  447. package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_builtin_global-error_0brpl_..js +0 -3
  448. package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_builtin_unauthorized_0~2g66g.js +0 -3
  449. package/dist/.next/server/chunks/ssr/05w9_next_dist_esm_build_templates_app-page_0~cyr1_.js +0 -4
  450. package/dist/.next/server/chunks/ssr/05w9_next_dist_esm_build_templates_app-page_1105emf.js +0 -4
  451. package/dist/.next/server/chunks/ssr/05w9_next_dist_esm_build_templates_app-page_11uhyqv.js +0 -4
  452. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0.t9_75._.js +0 -33
  453. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0c0ud_z._.js +0 -3
  454. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0f9_8d4._.js +0 -3
  455. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0l5ko41._.js +0 -19
  456. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0mn6z7i._.js +0 -3
  457. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0npxxst._.js +0 -33
  458. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0qjhaca._.js +0 -3
  459. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0rwgw3s._.js +0 -3
  460. package/dist/.next/server/chunks/ssr/[turbopack]_runtime.js +0 -903
  461. package/dist/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_0k77kol.js +0 -3
  462. package/dist/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_0eq97pa.js +0 -3
  463. package/dist/.next/server/chunks/ssr/_next-internal_server_app_page_actions_09-gtaw.js +0 -3
  464. package/dist/.next/server/chunks/ssr/node_modules__pnpm_056~6.6._.js +0 -3
  465. package/dist/.next/server/chunks/ssr/node_modules__pnpm_0~j0k.e._.js +0 -33
  466. package/dist/.next/server/functions-config-manifest.json +0 -4
  467. package/dist/.next/server/middleware-build-manifest.js +0 -20
  468. package/dist/.next/server/middleware-manifest.json +0 -6
  469. package/dist/.next/server/next-font-manifest.js +0 -1
  470. package/dist/.next/server/next-font-manifest.json +0 -13
  471. package/dist/.next/server/pages/404.html +0 -1
  472. package/dist/.next/server/pages/500.html +0 -1
  473. package/dist/.next/server/pages-manifest.json +0 -4
  474. package/dist/.next/server/prefetch-hints.json +0 -1
  475. package/dist/.next/server/server-reference-manifest.js +0 -1
  476. package/dist/.next/server/server-reference-manifest.json +0 -5
  477. package/dist/.next/static/SMWpxFVvkpYFxY7uuFvGB/_buildManifest.js +0 -11
  478. package/dist/.next/static/SMWpxFVvkpYFxY7uuFvGB/_clientMiddlewareManifest.js +0 -1
  479. package/dist/.next/static/SMWpxFVvkpYFxY7uuFvGB/_ssgManifest.js +0 -1
  480. package/dist/.next/static/chunks/01qk2~bgf76vu.js +0 -1
  481. package/dist/.next/static/chunks/03~yq9q893hmn.js +0 -1
  482. package/dist/.next/static/chunks/080queev.r2uy.js +0 -31
  483. package/dist/.next/static/chunks/0v3lyuj75aq50.js +0 -1
  484. package/dist/.next/static/chunks/10b~xdx5c-i7s.js +0 -5
  485. package/dist/.next/static/chunks/14gla2ascffgv.css +0 -2
  486. package/dist/.next/static/chunks/turbopack-0m-970~qvs7sc.js +0 -1
  487. package/dist/.next/static/media/7178b3e590c64307-s.11.cyxs5p-0z~.woff2 +0 -0
  488. package/dist/.next/static/media/8a480f0b521d4e75-s.06d3mdzz5bre_.woff2 +0 -0
  489. package/dist/.next/static/media/caa3a2e1cccd8315-s.p.16t1db8_9y2o~.woff2 +0 -0
  490. package/dist/package.json +0 -88
  491. package/dist/server.js +0 -38
  492. /package/{dist/.next/server/app/icon.svg.body → backend/src/flowent/static/favicon.svg} +0 -0
  493. /package/dist/{.next/static/media/icon.0.r~afrtrocz9.svg → frontend/favicon.svg} +0 -0
@@ -0,0 +1,36 @@
1
+ <!doctype html>
2
+ <html lang="en" class="dark">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Flowent</title>
8
+ <meta name="description" content="Flowent application" />
9
+ <script>
10
+ (() => {
11
+ try {
12
+ const storedTheme = window.localStorage.getItem("flowent-theme");
13
+ const theme = storedTheme === "light" ? "light" : "dark";
14
+ const root = document.documentElement;
15
+ root.classList.toggle("dark", theme === "dark");
16
+ root.classList.toggle("light", theme === "light");
17
+ root.style.colorScheme = theme;
18
+ } catch {
19
+ document.documentElement.classList.add("dark");
20
+ document.documentElement.style.colorScheme = "dark";
21
+ }
22
+ })();
23
+ </script>
24
+ <script type="module" crossorigin src="/assets/index-CL1ALZ3r.js"></script>
25
+ <link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-BYbx6iT9.js">
26
+ <link rel="modulepreload" crossorigin href="/assets/shared-CMxbpLeQ.js">
27
+ <link rel="modulepreload" crossorigin href="/assets/graph-vendor-DRq_-6fV.js">
28
+ <link rel="modulepreload" crossorigin href="/assets/react-vendor-mEs_JJxa.js">
29
+ <link rel="modulepreload" crossorigin href="/assets/ui-vendor-Dg9NNnWX.js">
30
+ <link rel="stylesheet" crossorigin href="/assets/graph-vendor-CHpVij2M.css">
31
+ <link rel="stylesheet" crossorigin href="/assets/index-BHC1Vhy8.css">
32
+ </head>
33
+ <body class="min-h-dvh bg-background text-foreground">
34
+ <div id="root"></div>
35
+ </body>
36
+ </html>
@@ -0,0 +1,218 @@
1
+ from __future__ import annotations
2
+
3
+ import copy
4
+ import json
5
+ import threading
6
+ import time
7
+ import uuid
8
+ from dataclasses import dataclass
9
+ from typing import Any, Literal
10
+
11
+ from flowent.models import LLMUsage
12
+ from flowent.state_db import open_state_db
13
+
14
+ MAX_STATS_RETENTION_SECONDS = 30 * 24 * 60 * 60
15
+
16
+
17
+ def serialize_usage(usage: LLMUsage | None) -> dict[str, Any] | None:
18
+ if usage is None:
19
+ return None
20
+ return {
21
+ "total_tokens": usage.total_tokens,
22
+ "input_tokens": usage.input_tokens,
23
+ "output_tokens": usage.output_tokens,
24
+ "cached_input_tokens": usage.cached_input_tokens,
25
+ "cache_read_tokens": usage.cache_read_tokens,
26
+ "cache_write_tokens": usage.cache_write_tokens,
27
+ "details": dict(usage.details),
28
+ }
29
+
30
+
31
+ @dataclass(frozen=True)
32
+ class RequestRecordInput:
33
+ node_id: str
34
+ node_label: str
35
+ role_name: str | None
36
+ tab_id: str | None
37
+ tab_title: str | None
38
+ provider_id: str | None
39
+ provider_name: str | None
40
+ provider_type: str | None
41
+ model: str | None
42
+ started_at: float
43
+ ended_at: float
44
+ retry_count: int
45
+ result: Literal["success", "error"]
46
+ normalized_usage: LLMUsage | None = None
47
+ raw_usage: dict[str, Any] | None = None
48
+ error_summary: str | None = None
49
+
50
+
51
+ @dataclass(frozen=True)
52
+ class CompactRecordInput:
53
+ node_id: str
54
+ node_label: str
55
+ role_name: str | None
56
+ tab_id: str | None
57
+ tab_title: str | None
58
+ provider_id: str | None
59
+ provider_name: str | None
60
+ provider_type: str | None
61
+ model: str | None
62
+ trigger_type: Literal["manual", "auto"]
63
+ started_at: float
64
+ ended_at: float
65
+ result: Literal["success", "error"]
66
+ error_summary: str | None = None
67
+
68
+
69
+ class StatsStore:
70
+ def __init__(self) -> None:
71
+ self._lock = threading.Lock()
72
+
73
+ def reset(self) -> None:
74
+ connection = open_state_db(create=False)
75
+ if connection is None:
76
+ return
77
+ try:
78
+ with connection:
79
+ connection.execute("DELETE FROM llm_request_records")
80
+ connection.execute("DELETE FROM compact_records")
81
+ finally:
82
+ connection.close()
83
+
84
+ def _prune_locked(self, connection, now: float) -> None:
85
+ min_timestamp = now - MAX_STATS_RETENTION_SECONDS
86
+ connection.execute(
87
+ "DELETE FROM llm_request_records WHERE ended_at < ?",
88
+ (min_timestamp,),
89
+ )
90
+ connection.execute(
91
+ "DELETE FROM compact_records WHERE ended_at < ?",
92
+ (min_timestamp,),
93
+ )
94
+
95
+ def record_request(self, record: RequestRecordInput) -> None:
96
+ payload = {
97
+ "id": str(uuid.uuid4()),
98
+ "node_id": record.node_id,
99
+ "node_label": record.node_label,
100
+ "role_name": record.role_name,
101
+ "tab_id": record.tab_id,
102
+ "tab_title": record.tab_title,
103
+ "provider_id": record.provider_id,
104
+ "provider_name": record.provider_name,
105
+ "provider_type": record.provider_type,
106
+ "model": record.model,
107
+ "started_at": record.started_at,
108
+ "ended_at": record.ended_at,
109
+ "duration_ms": max(0.0, (record.ended_at - record.started_at) * 1000),
110
+ "retry_count": max(record.retry_count, 0),
111
+ "result": record.result,
112
+ "error_summary": record.error_summary,
113
+ "normalized_usage": serialize_usage(record.normalized_usage),
114
+ "raw_usage": copy.deepcopy(record.raw_usage),
115
+ }
116
+ with self._lock:
117
+ connection = open_state_db(create=True)
118
+ assert connection is not None
119
+ try:
120
+ with connection:
121
+ self._prune_locked(connection, time.time())
122
+ connection.execute(
123
+ """
124
+ INSERT INTO llm_request_records (id, ended_at, payload)
125
+ VALUES (?, ?, ?)
126
+ """,
127
+ (
128
+ payload["id"],
129
+ payload["ended_at"],
130
+ json.dumps(payload, ensure_ascii=False),
131
+ ),
132
+ )
133
+ finally:
134
+ connection.close()
135
+
136
+ def record_compact(self, record: CompactRecordInput) -> None:
137
+ payload = {
138
+ "id": str(uuid.uuid4()),
139
+ "node_id": record.node_id,
140
+ "node_label": record.node_label,
141
+ "role_name": record.role_name,
142
+ "tab_id": record.tab_id,
143
+ "tab_title": record.tab_title,
144
+ "provider_id": record.provider_id,
145
+ "provider_name": record.provider_name,
146
+ "provider_type": record.provider_type,
147
+ "model": record.model,
148
+ "trigger_type": record.trigger_type,
149
+ "started_at": record.started_at,
150
+ "ended_at": record.ended_at,
151
+ "duration_ms": max(0.0, (record.ended_at - record.started_at) * 1000),
152
+ "result": record.result,
153
+ "error_summary": record.error_summary,
154
+ }
155
+ with self._lock:
156
+ connection = open_state_db(create=True)
157
+ assert connection is not None
158
+ try:
159
+ with connection:
160
+ self._prune_locked(connection, time.time())
161
+ connection.execute(
162
+ """
163
+ INSERT INTO compact_records (id, ended_at, payload)
164
+ VALUES (?, ?, ?)
165
+ """,
166
+ (
167
+ payload["id"],
168
+ payload["ended_at"],
169
+ json.dumps(payload, ensure_ascii=False),
170
+ ),
171
+ )
172
+ finally:
173
+ connection.close()
174
+
175
+ def list_requests(self, *, since: float) -> list[dict[str, Any]]:
176
+ with self._lock:
177
+ return self._list_records(
178
+ table_name="llm_request_records",
179
+ since=since,
180
+ )
181
+
182
+ def list_compacts(self, *, since: float) -> list[dict[str, Any]]:
183
+ with self._lock:
184
+ return self._list_records(
185
+ table_name="compact_records",
186
+ since=since,
187
+ )
188
+
189
+ def _list_records(self, *, table_name: str, since: float) -> list[dict[str, Any]]:
190
+ connection = open_state_db(create=False)
191
+ if connection is None:
192
+ return []
193
+ try:
194
+ with connection:
195
+ self._prune_locked(connection, time.time())
196
+ rows = connection.execute(
197
+ f"""
198
+ SELECT payload
199
+ FROM {table_name}
200
+ WHERE ended_at >= ?
201
+ ORDER BY ended_at
202
+ """,
203
+ (since,),
204
+ ).fetchall()
205
+ finally:
206
+ connection.close()
207
+ records: list[dict[str, Any]] = []
208
+ for row in rows:
209
+ payload = row["payload"]
210
+ if not isinstance(payload, str):
211
+ continue
212
+ parsed = json.loads(payload)
213
+ if isinstance(parsed, dict):
214
+ records.append(parsed)
215
+ return records
216
+
217
+
218
+ stats_store = StatsStore()
@@ -0,0 +1,201 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ if TYPE_CHECKING:
7
+ from typing import ClassVar
8
+
9
+ from flowent.agent import Agent
10
+
11
+ MINIMUM_TOOLS = (
12
+ "idle",
13
+ "sleep",
14
+ "todo",
15
+ "contacts",
16
+ "send",
17
+ )
18
+
19
+ MCP_BROWSING_TOOLS = (
20
+ "list_mcp_resources",
21
+ "list_mcp_resource_templates",
22
+ "read_mcp_resource",
23
+ "list_mcp_prompts",
24
+ "get_mcp_prompt",
25
+ )
26
+
27
+
28
+ class Tool(ABC):
29
+ name: str
30
+ description: str
31
+ parameters: ClassVar[dict[str, Any]]
32
+ agent_visible: ClassVar[bool] = True
33
+ llm_visible: ClassVar[bool] = True
34
+
35
+ @abstractmethod
36
+ def execute(
37
+ self, agent: Agent, args: dict[str, Any], **kwargs: Any
38
+ ) -> str | None: ...
39
+
40
+ def to_schema(self) -> dict[str, Any]:
41
+ return {
42
+ "type": "function",
43
+ "function": {
44
+ "name": self.name,
45
+ "description": self.description,
46
+ "parameters": self.parameters,
47
+ },
48
+ }
49
+
50
+
51
+ def re_raise_interrupt(agent: Agent, exc: BaseException) -> None:
52
+ from flowent.agent import InterruptRequestedError
53
+
54
+ if isinstance(exc, InterruptRequestedError) or agent.is_interrupt_requested():
55
+ raise InterruptRequestedError() from exc
56
+
57
+
58
+ class ToolRegistry:
59
+ def __init__(self) -> None:
60
+ self._tools: dict[str, Tool] = {}
61
+
62
+ def register(self, tool: Tool) -> None:
63
+ self._tools[tool.name] = tool
64
+
65
+ def get(self, name: str) -> Tool | None:
66
+ tool = self._tools.get(name)
67
+ if tool is not None:
68
+ return tool
69
+ from flowent.mcp_service import mcp_service
70
+ from flowent.tools.mcp import DynamicMCPTool
71
+
72
+ descriptor = mcp_service.get_dynamic_tool_descriptor(name)
73
+ if descriptor is None:
74
+ return None
75
+ return DynamicMCPTool(descriptor=descriptor)
76
+
77
+ def list_tools(self, *, agent_visible_only: bool = False) -> list[Tool]:
78
+ tools = list(self._tools.values())
79
+ from flowent.mcp_service import mcp_service
80
+ from flowent.tools.mcp import DynamicMCPTool
81
+
82
+ dynamic_tools = [
83
+ DynamicMCPTool(descriptor=descriptor)
84
+ for descriptor in mcp_service.list_discovered_tools()
85
+ ]
86
+ tools.extend(dynamic_tools)
87
+ if not agent_visible_only:
88
+ return tools
89
+ return [tool for tool in tools if tool.agent_visible]
90
+
91
+ def get_tools_for_agent(self, agent: Agent) -> list[Tool]:
92
+ from flowent.mcp_service import mcp_service
93
+ from flowent.settings import find_role, get_settings
94
+ from flowent.tools.mcp import DynamicMCPTool
95
+
96
+ allowed = set(agent.config.tools) | set(MINIMUM_TOOLS)
97
+ visible_tools = [
98
+ t for t in self._tools.values() if t.name in allowed and t.llm_visible
99
+ ]
100
+ role = find_role(get_settings(), agent.config.role_name or "")
101
+ excluded = set(role.excluded_tools) if role is not None else set()
102
+ dynamic_tools = [
103
+ DynamicMCPTool(descriptor=descriptor)
104
+ for descriptor in mcp_service.list_agent_dynamic_tools(agent)
105
+ if descriptor.fully_qualified_id in allowed
106
+ and descriptor.fully_qualified_id not in excluded
107
+ ]
108
+ return [*visible_tools, *dynamic_tools]
109
+
110
+ def get_tools_schema(self, agent: Agent) -> list[dict[str, Any]]:
111
+ return [t.to_schema() for t in self.get_tools_for_agent(agent)]
112
+
113
+
114
+ def build_tool_registry() -> ToolRegistry:
115
+ from flowent.tools.connect import ConnectTool
116
+ from flowent.tools.contacts import ContactsTool
117
+ from flowent.tools.create_agent import CreateAgentTool
118
+ from flowent.tools.create_tab import CreateTabTool
119
+ from flowent.tools.delete_tab import DeleteTabTool
120
+ from flowent.tools.edit import EditTool
121
+ from flowent.tools.exec import ExecTool
122
+ from flowent.tools.fetch import FetchTool
123
+ from flowent.tools.idle import IdleTool
124
+ from flowent.tools.list_roles import ListRolesTool
125
+ from flowent.tools.list_tabs import ListTabsTool
126
+ from flowent.tools.list_tools import ListToolsTool
127
+ from flowent.tools.manage_prompts import ManagePromptsTool
128
+ from flowent.tools.manage_providers import ManageProvidersTool
129
+ from flowent.tools.manage_roles import ManageRolesTool
130
+ from flowent.tools.manage_settings import ManageSettingsTool
131
+ from flowent.tools.mcp import (
132
+ GetMCPPromptTool,
133
+ ListMCPPromptsTool,
134
+ ListMCPResourcesTool,
135
+ ListMCPResourceTemplatesTool,
136
+ ReadMCPResourceTool,
137
+ )
138
+ from flowent.tools.read import ReadTool
139
+ from flowent.tools.send import SendTool
140
+ from flowent.tools.set_permissions import SetPermissionsTool
141
+ from flowent.tools.sleep import SleepTool
142
+ from flowent.tools.todo import TodoTool
143
+
144
+ reg = ToolRegistry()
145
+ for tool_cls in [
146
+ IdleTool,
147
+ SleepTool,
148
+ TodoTool,
149
+ ContactsTool,
150
+ SendTool,
151
+ CreateTabTool,
152
+ DeleteTabTool,
153
+ SetPermissionsTool,
154
+ CreateAgentTool,
155
+ ConnectTool,
156
+ ManageProvidersTool,
157
+ ManageRolesTool,
158
+ ManageSettingsTool,
159
+ ManagePromptsTool,
160
+ ReadTool,
161
+ EditTool,
162
+ ExecTool,
163
+ FetchTool,
164
+ ListRolesTool,
165
+ ListTabsTool,
166
+ ListToolsTool,
167
+ ListMCPResourcesTool,
168
+ ListMCPResourceTemplatesTool,
169
+ ReadMCPResourceTool,
170
+ ListMCPPromptsTool,
171
+ GetMCPPromptTool,
172
+ ]:
173
+ reg.register(tool_cls()) # type: ignore[abstract]
174
+ return reg
175
+
176
+
177
+ def list_agent_visible_tool_descriptors() -> list[dict[str, Any]]:
178
+ registry = build_tool_registry()
179
+ from flowent.mcp_service import mcp_service
180
+
181
+ descriptors: list[dict[str, Any]] = []
182
+ for tool in registry.list_tools(agent_visible_only=True):
183
+ if tool.name.startswith("mcp__"):
184
+ continue
185
+ schema = tool.to_schema()
186
+ function = schema.get("function")
187
+ parameters = (
188
+ function.get("parameters")
189
+ if isinstance(function, dict)
190
+ else {"type": "object", "properties": {}, "required": []}
191
+ )
192
+ descriptors.append(
193
+ {
194
+ "name": tool.name,
195
+ "description": tool.description,
196
+ "parameters": parameters,
197
+ "source": "builtin",
198
+ }
199
+ )
200
+ descriptors.extend(mcp_service.list_discovered_tool_descriptors())
201
+ return descriptors
@@ -0,0 +1,156 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from typing import TYPE_CHECKING, Any, ClassVar
5
+
6
+ from flowent.graph_runtime import resolve_node_ref
7
+ from flowent.models import NodeType
8
+ from flowent.tools import Tool
9
+ from flowent.workspace_store import workspace_store
10
+
11
+ if TYPE_CHECKING:
12
+ from flowent.agent import Agent
13
+
14
+
15
+ def _resolve_workflow_node_ref(
16
+ *,
17
+ tab_id: str,
18
+ node_ref: str,
19
+ ) -> tuple[str, str] | None:
20
+ target = resolve_node_ref(node_ref)
21
+ if target is not None and target.config.tab_id == tab_id:
22
+ return target.uuid, target.config.tab_id
23
+
24
+ tab = workspace_store.get_tab(tab_id)
25
+ if tab is None:
26
+ return None
27
+
28
+ definition_nodes = list(tab.definition.nodes)
29
+ exact_match = next(
30
+ (node for node in definition_nodes if node.id == node_ref),
31
+ None,
32
+ )
33
+ if exact_match is not None:
34
+ return exact_match.id, tab_id
35
+
36
+ named_matches = [
37
+ node
38
+ for node in definition_nodes
39
+ if isinstance(node.config.get("name"), str) and node.config["name"] == node_ref
40
+ ]
41
+ if len(named_matches) == 1:
42
+ return named_matches[0].id, tab_id
43
+
44
+ if 4 <= len(node_ref) < 36:
45
+ prefix_matches = [
46
+ node for node in definition_nodes if node.id.startswith(node_ref)
47
+ ]
48
+ if len(prefix_matches) == 1:
49
+ return prefix_matches[0].id, tab_id
50
+ return None
51
+
52
+
53
+ class ConnectTool(Tool):
54
+ name = "connect"
55
+ description = (
56
+ "Create a directed workflow edge between two nodes in the same workflow."
57
+ )
58
+ parameters: ClassVar[dict[str, Any]] = {
59
+ "type": "object",
60
+ "properties": {
61
+ "from": {
62
+ "type": "string",
63
+ "description": "Source node UUID or name",
64
+ },
65
+ "to": {
66
+ "type": "string",
67
+ "description": "Target node UUID or name",
68
+ },
69
+ "from_port_key": {
70
+ "type": "string",
71
+ "description": "Source output port key",
72
+ "default": "out",
73
+ },
74
+ "to_port_key": {
75
+ "type": "string",
76
+ "description": "Target input port key",
77
+ "default": "in",
78
+ },
79
+ "kind": {
80
+ "type": "string",
81
+ "enum": ["control", "data", "event"],
82
+ "description": "Workflow edge kind",
83
+ "default": "control",
84
+ },
85
+ },
86
+ "required": ["from", "to"],
87
+ }
88
+
89
+ def execute(self, agent: Agent, args: dict[str, Any], **_kwargs: Any) -> str:
90
+ from flowent.graph_service import create_edge, is_tab_leader
91
+
92
+ from_ref = args.get("from")
93
+ to_ref = args.get("to")
94
+ from_port_key = args.get("from_port_key", "out")
95
+ to_port_key = args.get("to_port_key", "in")
96
+ kind = args.get("kind", "control")
97
+
98
+ if not isinstance(from_ref, str) or not from_ref:
99
+ return json.dumps({"error": "from must be a non-empty string"})
100
+ if not isinstance(to_ref, str) or not to_ref:
101
+ return json.dumps({"error": "to must be a non-empty string"})
102
+ if not isinstance(from_port_key, str) or not from_port_key.strip():
103
+ return json.dumps({"error": "from_port_key must be a non-empty string"})
104
+ if not isinstance(to_port_key, str) or not to_port_key.strip():
105
+ return json.dumps({"error": "to_port_key must be a non-empty string"})
106
+ if kind not in {"control", "data", "event"}:
107
+ return json.dumps({"error": "kind must be control, data, or event"})
108
+ if not agent.config.tab_id:
109
+ return json.dumps(
110
+ {"error": "Only a workflow Leader may connect task nodes"}
111
+ )
112
+
113
+ source = _resolve_workflow_node_ref(
114
+ tab_id=agent.config.tab_id,
115
+ node_ref=from_ref,
116
+ )
117
+ target = _resolve_workflow_node_ref(
118
+ tab_id=agent.config.tab_id,
119
+ node_ref=to_ref,
120
+ )
121
+ if source is None:
122
+ return json.dumps({"error": f"Node '{from_ref}' not found"})
123
+ if target is None:
124
+ return json.dumps({"error": f"Node '{to_ref}' not found"})
125
+
126
+ source_id, source_tab_id = source
127
+ target_id, target_tab_id = target
128
+ if source_tab_id != target_tab_id:
129
+ return json.dumps({"error": "Both nodes must belong to the same workflow"})
130
+ if agent.node_type == NodeType.ASSISTANT:
131
+ return json.dumps(
132
+ {"error": "Assistant may not rewire a Workflow Graph directly"}
133
+ )
134
+ if agent.config.tab_id != source_tab_id:
135
+ return json.dumps(
136
+ {
137
+ "error": "A workflow Leader may only connect peers inside its own workflow"
138
+ }
139
+ )
140
+ if not is_tab_leader(node_id=agent.uuid, tab_id=agent.config.tab_id):
141
+ return json.dumps(
142
+ {"error": "Only a workflow Leader may connect task nodes"}
143
+ )
144
+
145
+ edge, error = create_edge(
146
+ tab_id=source_tab_id,
147
+ from_node_id=source_id,
148
+ from_port_key=from_port_key,
149
+ to_node_id=target_id,
150
+ to_port_key=to_port_key,
151
+ kind=kind,
152
+ )
153
+ if error is not None or edge is None:
154
+ return json.dumps({"error": error or "Failed to connect nodes"})
155
+
156
+ return json.dumps(edge.serialize())