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,116 @@
1
+ from fastapi.testclient import TestClient
2
+
3
+
4
+ def test_mcp_api_returns_only_external_server_state(client: TestClient, monkeypatch):
5
+ monkeypatch.setattr(
6
+ "flowent.routes.mcp.mcp_service.list_server_payloads",
7
+ lambda: [
8
+ {
9
+ "config": {
10
+ "name": "filesystem",
11
+ "transport": "stdio",
12
+ "enabled": True,
13
+ "required": False,
14
+ "startup_timeout_sec": 10,
15
+ "tool_timeout_sec": 30,
16
+ "enabled_tools": [],
17
+ "disabled_tools": [],
18
+ "scopes": [],
19
+ "oauth_resource": "",
20
+ "launcher": "",
21
+ "command": "npx",
22
+ "args": ["-y", "demo-mcp"],
23
+ "env": {},
24
+ "env_vars": [],
25
+ "cwd": "",
26
+ "url": "",
27
+ "bearer_token_env_var": "",
28
+ "http_headers": {},
29
+ "env_http_headers": [],
30
+ },
31
+ "snapshot": {
32
+ "server_name": "filesystem",
33
+ "transport": "stdio",
34
+ "status": "connected",
35
+ "auth_status": "unsupported",
36
+ "last_auth_result": None,
37
+ "last_refresh_at": 1710000000,
38
+ "last_refresh_result": "success",
39
+ "last_error": None,
40
+ "tools": [],
41
+ "resources": [],
42
+ "resource_templates": [],
43
+ "prompts": [],
44
+ "capability_counts": {
45
+ "tools": 0,
46
+ "resources": 0,
47
+ "resource_templates": 0,
48
+ "prompts": 0,
49
+ },
50
+ },
51
+ "visibility": {
52
+ "scope": "global",
53
+ "active": True,
54
+ },
55
+ "activity": [],
56
+ }
57
+ ],
58
+ )
59
+
60
+ response = client.get("/api/mcp")
61
+
62
+ assert response.status_code == 200
63
+ assert response.json() == {
64
+ "servers": [
65
+ {
66
+ "config": {
67
+ "name": "filesystem",
68
+ "transport": "stdio",
69
+ "enabled": True,
70
+ "required": False,
71
+ "startup_timeout_sec": 10,
72
+ "tool_timeout_sec": 30,
73
+ "enabled_tools": [],
74
+ "disabled_tools": [],
75
+ "scopes": [],
76
+ "oauth_resource": "",
77
+ "launcher": "",
78
+ "command": "npx",
79
+ "args": ["-y", "demo-mcp"],
80
+ "env": {},
81
+ "env_vars": [],
82
+ "cwd": "",
83
+ "url": "",
84
+ "bearer_token_env_var": "",
85
+ "http_headers": {},
86
+ "env_http_headers": [],
87
+ },
88
+ "snapshot": {
89
+ "server_name": "filesystem",
90
+ "transport": "stdio",
91
+ "status": "connected",
92
+ "auth_status": "unsupported",
93
+ "last_auth_result": None,
94
+ "last_refresh_at": 1710000000,
95
+ "last_refresh_result": "success",
96
+ "last_error": None,
97
+ "tools": [],
98
+ "resources": [],
99
+ "resource_templates": [],
100
+ "prompts": [],
101
+ "capability_counts": {
102
+ "tools": 0,
103
+ "resources": 0,
104
+ "resource_templates": 0,
105
+ "prompts": 0,
106
+ },
107
+ },
108
+ "visibility": {
109
+ "scope": "global",
110
+ "active": True,
111
+ },
112
+ "activity": [],
113
+ }
114
+ ],
115
+ }
116
+ assert "flowent_server" not in response.json()
@@ -0,0 +1,33 @@
1
+ from fastapi.testclient import TestClient
2
+
3
+
4
+ def test_tools_api_shows_agent_visible_management_tools(client: TestClient):
5
+ response = client.get("/api/tools")
6
+
7
+ assert response.status_code == 200
8
+ tool_names = {tool["name"] for tool in response.json()["tools"]}
9
+ assert "create_root" not in tool_names
10
+ assert "manage_providers" in tool_names
11
+ assert "manage_roles" in tool_names
12
+ assert "manage_settings" in tool_names
13
+ assert "manage_prompts" in tool_names
14
+ assert "set_permissions" in tool_names
15
+ assert "sleep" in tool_names
16
+ assert "delete_workflow" in tool_names
17
+ assert "create_agent" in tool_names
18
+ assert "connect" in tool_names
19
+
20
+
21
+ def test_settings_bootstrap_returns_settings_related_resources(client: TestClient):
22
+ response = client.get("/api/settings/bootstrap")
23
+
24
+ assert response.status_code == 200
25
+ payload = response.json()
26
+ assert payload["version"]
27
+ assert "settings" in payload
28
+ assert "providers" in payload
29
+ assert "roles" in payload
30
+ role_names = {role["name"] for role in payload["roles"]}
31
+ assert "Steward" in role_names
32
+ assert "Worker" in role_names
33
+ assert "Designer" in role_names
@@ -0,0 +1,486 @@
1
+ import os
2
+ import time
3
+ from uuid import UUID
4
+
5
+ from fastapi.testclient import TestClient
6
+
7
+ from flowent.models import (
8
+ AgentState,
9
+ AssistantText,
10
+ ImagePart,
11
+ ReceivedMessage,
12
+ TextPart,
13
+ )
14
+ from flowent.registry import registry
15
+ from flowent.routes.nodes import router as nodes_router
16
+ from flowent.settings import STEWARD_ROLE_INCLUDED_TOOLS
17
+ from flowent.tools import MINIMUM_TOOLS
18
+
19
+ _ONE_PIXEL_PNG = bytes.fromhex(
20
+ "89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c489"
21
+ "0000000d49444154789c6360000002000154a24f5d0000000049454e44ae426082"
22
+ )
23
+
24
+
25
+ def _get_assistant_id(client: TestClient) -> str:
26
+ response = client.get("/api/assistant")
27
+
28
+ assert response.status_code == 200
29
+ assistant_id = response.json()["id"]
30
+ UUID(assistant_id)
31
+ return assistant_id
32
+
33
+
34
+ def test_health_check(client: TestClient):
35
+ response = client.get("/health")
36
+
37
+ assert response.status_code == 200
38
+ assert response.json() == {"status": "healthy"}
39
+
40
+
41
+ def test_list_agents(client: TestClient):
42
+ response = client.get("/api/nodes")
43
+
44
+ assert response.status_code == 200
45
+ data = response.json()
46
+ assert "nodes" in data
47
+ assert isinstance(data["nodes"], list)
48
+
49
+
50
+ def test_get_agent_not_found(client: TestClient):
51
+ response = client.get("/api/nodes/non-existent-id")
52
+
53
+ assert response.status_code == 404
54
+ assert "Node not found" in response.json()["detail"]
55
+
56
+
57
+ def test_get_node_detail_includes_runtime_config(client: TestClient):
58
+ assistant_id = _get_assistant_id(client)
59
+ response = client.get(f"/api/nodes/{assistant_id}")
60
+
61
+ assert response.status_code == 200
62
+ data = response.json()
63
+ assert data["id"] == assistant_id
64
+ assert isinstance(data["contacts"], list)
65
+ assert isinstance(data["history"], list)
66
+ assert isinstance(data["tools"], list)
67
+ assert isinstance(data["write_dirs"], list)
68
+ assert isinstance(data["allow_network"], bool)
69
+
70
+
71
+ def test_worker_and_leader_are_stable_contacts_without_explicit_edge(
72
+ client: TestClient,
73
+ ):
74
+ tab = client.post(
75
+ "/api/workflows",
76
+ json={"title": "Execution"},
77
+ ).json()
78
+ worker = client.post(
79
+ f"/api/workflows/{tab['id']}/nodes",
80
+ json={"role_name": "Worker", "name": "Worker"},
81
+ ).json()
82
+
83
+ detail_without_edge = client.get(f"/api/nodes/{worker['id']}")
84
+
85
+ assert detail_without_edge.status_code == 200
86
+ assert detail_without_edge.json()["contacts"] == [tab["leader_id"]]
87
+ leader_without_edge = client.get(f"/api/nodes/{tab['leader_id']}")
88
+ assert leader_without_edge.status_code == 200
89
+ assert worker["id"] in leader_without_edge.json()["contacts"]
90
+
91
+ edge_response = client.post(
92
+ f"/api/workflows/{tab['id']}/edges",
93
+ json={
94
+ "from_node_id": tab["leader_id"],
95
+ "to_node_id": worker["id"],
96
+ },
97
+ )
98
+
99
+ assert edge_response.status_code == 400
100
+ assert edge_response.json()["detail"] == (
101
+ "Workflow Leader does not participate in Workflow Graph edges"
102
+ )
103
+
104
+
105
+ def test_direct_node_message_api_is_not_available(client: TestClient):
106
+ assert not any(
107
+ getattr(route, "path", None) == "/api/nodes/{node_id}/message"
108
+ for route in nodes_router.routes
109
+ )
110
+
111
+
112
+ def test_only_assistant_node_exists_at_startup(client: TestClient):
113
+ list_response = client.get("/api/nodes")
114
+
115
+ assert list_response.status_code == 200
116
+ nodes = list_response.json()["nodes"]
117
+ assert len(nodes) == 1
118
+ UUID(nodes[0]["id"])
119
+ assert nodes[0]["node_type"] == "assistant"
120
+ assert nodes[0]["name"] == "Assistant"
121
+ assert nodes[0]["role_name"] == "Steward"
122
+
123
+
124
+ def test_get_assistant_detail_includes_tools_and_permissions(client: TestClient):
125
+ assistant_id = _get_assistant_id(client)
126
+ response = client.get(f"/api/nodes/{assistant_id}")
127
+
128
+ assert response.status_code == 200
129
+ data = response.json()
130
+ assert data["id"] == assistant_id
131
+ assert set(data["tools"]) == set(MINIMUM_TOOLS) | set(STEWARD_ROLE_INCLUDED_TOOLS)
132
+ assert data["write_dirs"] == [os.getcwd()]
133
+ assert data["allow_network"] is True
134
+
135
+
136
+ def test_get_node_detail_includes_state_entries_in_history(client: TestClient):
137
+ assistant_id = _get_assistant_id(client)
138
+ assistant = registry.get(assistant_id)
139
+ assert assistant is not None
140
+ assistant.set_state(AgentState.RUNNING, "processing")
141
+
142
+ response = client.get(f"/api/nodes/{assistant_id}")
143
+
144
+ assert response.status_code == 200
145
+ history = response.json()["history"]
146
+ assert any(
147
+ entry["type"] == "StateEntry" and entry["state"] == "running"
148
+ for entry in history
149
+ )
150
+
151
+
152
+ def test_assistant_cannot_be_terminated_via_nodes_api(client: TestClient):
153
+ assistant_id = _get_assistant_id(client)
154
+ response = client.post(f"/api/nodes/{assistant_id}/terminate")
155
+
156
+ assert response.status_code == 400
157
+ assert response.json() == {"detail": "Cannot terminate assistant"}
158
+
159
+
160
+ def test_tab_leader_cannot_be_terminated_directly(client: TestClient):
161
+ created_tab = client.post(
162
+ "/api/workflows",
163
+ json={"title": "Execution"},
164
+ ).json()
165
+
166
+ response = client.post(f"/api/nodes/{created_tab['leader_id']}/terminate")
167
+
168
+ assert response.status_code == 400
169
+ assert response.json() == {"detail": "Cannot terminate a workflow Leader directly"}
170
+
171
+
172
+ def test_assistant_retry_is_not_available_via_nodes_api(client: TestClient):
173
+ assistant_id = _get_assistant_id(client)
174
+
175
+ response = client.post(f"/api/nodes/{assistant_id}/messages/msg-1/retry")
176
+
177
+ assert response.status_code == 400
178
+ assert response.json() == {
179
+ "detail": "Use /api/assistant/messages/{message_id}/retry for Assistant retry"
180
+ }
181
+
182
+
183
+ def test_assistant_can_be_interrupted_via_nodes_api_when_running(client: TestClient):
184
+ assistant_id = _get_assistant_id(client)
185
+ assistant = registry.get(assistant_id)
186
+ assert assistant is not None
187
+ assistant.set_state(AgentState.RUNNING, "processing")
188
+
189
+ response = client.post(f"/api/nodes/{assistant_id}/interrupt")
190
+
191
+ assert response.status_code == 200
192
+ assert response.json() == {"status": "interrupting"}
193
+ assert assistant._interrupt_requested.is_set()
194
+
195
+
196
+ def test_assistant_can_be_interrupted_via_nodes_api_when_sleeping(client: TestClient):
197
+ assistant_id = _get_assistant_id(client)
198
+ assistant = registry.get(assistant_id)
199
+ assert assistant is not None
200
+ assistant.set_state(AgentState.SLEEPING, "waiting for reply")
201
+
202
+ response = client.post(f"/api/nodes/{assistant_id}/interrupt")
203
+
204
+ assert response.status_code == 200
205
+ assert response.json() == {"status": "interrupting"}
206
+ assert assistant._interrupt_requested.is_set()
207
+
208
+
209
+ def test_interrupt_ignores_idle_node(client: TestClient):
210
+ assistant_id = _get_assistant_id(client)
211
+ response = client.post(f"/api/nodes/{assistant_id}/interrupt")
212
+
213
+ assert response.status_code == 200
214
+ assert response.json() == {"status": "ignored"}
215
+
216
+
217
+ def test_assistant_chat_can_be_cleared_via_nodes_api(client: TestClient):
218
+ assistant_id = _get_assistant_id(client)
219
+ assistant = registry.get(assistant_id)
220
+ assert assistant is not None
221
+ assistant.history.append(ReceivedMessage(content="Old message", from_id="human"))
222
+ assistant.history.append(AssistantText(content="Old reply"))
223
+
224
+ response = client.post(f"/api/nodes/{assistant_id}/clear-chat")
225
+
226
+ assert response.status_code == 200
227
+ assert response.json() == {"status": "cleared"}
228
+
229
+ detail = client.get(f"/api/nodes/{assistant_id}").json()
230
+ assert not any(
231
+ entry["type"] in {"ReceivedMessage", "AssistantText"}
232
+ for entry in detail["history"]
233
+ )
234
+
235
+
236
+ def test_human_input_can_be_sent_directly_to_workflow_leader(client: TestClient):
237
+ tab = client.post(
238
+ "/api/workflows",
239
+ json={"title": "Execution"},
240
+ ).json()
241
+
242
+ response = client.post(
243
+ f"/api/nodes/{tab['leader_id']}/messages",
244
+ json={"content": "/help investigate the failure"},
245
+ )
246
+
247
+ assert response.status_code == 200
248
+ message_id = response.json()["message_id"]
249
+ assert isinstance(message_id, str)
250
+
251
+ history = []
252
+ for _ in range(20):
253
+ detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
254
+ history = detail["history"]
255
+ if any(
256
+ entry["type"] == "ReceivedMessage"
257
+ and entry["from_id"] == "human"
258
+ and entry["message_id"] == message_id
259
+ and entry["content"] == "/help investigate the failure"
260
+ for entry in history
261
+ ):
262
+ break
263
+ time.sleep(0.01)
264
+
265
+ assert any(
266
+ entry["type"] == "ReceivedMessage"
267
+ and entry["from_id"] == "human"
268
+ and entry["message_id"] == message_id
269
+ and entry["content"] == "/help investigate the failure"
270
+ for entry in history
271
+ )
272
+
273
+
274
+ def test_leader_retry_rewrites_tail_and_reuses_image_parts(monkeypatch, client):
275
+ assistant_id = _get_assistant_id(client)
276
+ tab = client.post(
277
+ "/api/workflows",
278
+ json={"title": "Execution"},
279
+ ).json()
280
+ leader = registry.get(tab["leader_id"])
281
+ assert leader is not None
282
+ queued_messages = []
283
+
284
+ upload_response = client.post(
285
+ "/api/image-assets",
286
+ files={"file": ("pixel.png", _ONE_PIXEL_PNG, "image/png")},
287
+ )
288
+ assert upload_response.status_code == 200
289
+ asset_id = upload_response.json()["id"]
290
+
291
+ leader.history.extend(
292
+ [
293
+ ReceivedMessage(
294
+ content="Initial brief",
295
+ from_id=assistant_id,
296
+ message_id="brief-1",
297
+ ),
298
+ AssistantText(content="Leader summary"),
299
+ ReceivedMessage(
300
+ from_id="human",
301
+ parts=[
302
+ TextPart(text="Retry this leader request"),
303
+ ImagePart(
304
+ asset_id=asset_id,
305
+ mime_type="image/png",
306
+ width=1,
307
+ height=1,
308
+ ),
309
+ ],
310
+ message_id="msg-2",
311
+ ),
312
+ AssistantText(content="Discard this leader reply"),
313
+ ]
314
+ )
315
+
316
+ monkeypatch.setattr(leader, "supports_input_image", lambda: True)
317
+ monkeypatch.setattr(
318
+ leader,
319
+ "enqueue_message",
320
+ lambda message: queued_messages.append(message),
321
+ )
322
+
323
+ response = client.post(f"/api/nodes/{tab['leader_id']}/messages/msg-2/retry")
324
+
325
+ assert response.status_code == 200
326
+ payload = response.json()
327
+ assert payload["status"] == "retried"
328
+ assert payload["message_id"] != "msg-2"
329
+
330
+ detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
331
+
332
+ assert any(
333
+ entry["type"] == "ReceivedMessage"
334
+ and entry.get("message_id") == "brief-1"
335
+ and entry.get("from_id") == assistant_id
336
+ for entry in detail["history"]
337
+ )
338
+ assert not any(
339
+ entry["type"] == "ReceivedMessage" and entry.get("message_id") == "msg-2"
340
+ for entry in detail["history"]
341
+ )
342
+ assert not any(
343
+ entry["type"] == "AssistantText"
344
+ and entry.get("content") == "Discard this leader reply"
345
+ for entry in detail["history"]
346
+ )
347
+ assert any(
348
+ entry["type"] == "ReceivedMessage"
349
+ and entry.get("message_id") == payload["message_id"]
350
+ and entry.get("content") == "Retry this leader request[image]"
351
+ for entry in detail["history"]
352
+ )
353
+ assert len(queued_messages) == 1
354
+ assert queued_messages[0].message_id == payload["message_id"]
355
+ assert queued_messages[0].parts[0].text == "Retry this leader request"
356
+ assert queued_messages[0].parts[1].asset_id == asset_id
357
+
358
+
359
+ def test_leader_retry_rejects_non_human_anchor(client: TestClient):
360
+ assistant_id = _get_assistant_id(client)
361
+ tab = client.post(
362
+ "/api/workflows",
363
+ json={"title": "Execution"},
364
+ ).json()
365
+ leader = registry.get(tab["leader_id"])
366
+ assert leader is not None
367
+ leader.history.append(
368
+ ReceivedMessage(
369
+ content="Initial brief",
370
+ from_id=assistant_id,
371
+ message_id="brief-1",
372
+ )
373
+ )
374
+
375
+ response = client.post(f"/api/nodes/{tab['leader_id']}/messages/brief-1/retry")
376
+
377
+ assert response.status_code == 404
378
+ assert response.json()["detail"] == "Leader human message `brief-1` was not found."
379
+
380
+
381
+ def test_regular_worker_retry_is_not_available_via_nodes_api(client: TestClient):
382
+ tab = client.post(
383
+ "/api/workflows",
384
+ json={"title": "Execution"},
385
+ ).json()
386
+ worker = client.post(
387
+ f"/api/workflows/{tab['id']}/nodes",
388
+ json={"role_name": "Worker", "name": "Worker"},
389
+ ).json()
390
+
391
+ response = client.post(f"/api/nodes/{worker['id']}/messages/msg-1/retry")
392
+
393
+ assert response.status_code == 400
394
+ assert response.json() == {
395
+ "detail": "Only a Workflow Leader can retry chat history"
396
+ }
397
+
398
+
399
+ def test_human_input_cannot_target_regular_worker(client: TestClient):
400
+ tab = client.post(
401
+ "/api/workflows",
402
+ json={"title": "Execution"},
403
+ ).json()
404
+ worker = client.post(
405
+ f"/api/workflows/{tab['id']}/nodes",
406
+ json={"role_name": "Worker", "name": "Worker"},
407
+ ).json()
408
+
409
+ response = client.post(
410
+ f"/api/nodes/{worker['id']}/messages",
411
+ json={"content": "Do the work"},
412
+ )
413
+
414
+ assert response.status_code == 400
415
+ assert response.json()["detail"] == (
416
+ "Human input can only target Assistant or a Workflow Leader"
417
+ )
418
+
419
+
420
+ def test_browser_cannot_spoof_non_human_sender_for_node_messages(client: TestClient):
421
+ tab = client.post(
422
+ "/api/workflows",
423
+ json={"title": "Execution"},
424
+ ).json()
425
+ worker = client.post(
426
+ f"/api/workflows/{tab['id']}/nodes",
427
+ json={"role_name": "Worker", "name": "Worker"},
428
+ ).json()
429
+
430
+ response = client.post(
431
+ f"/api/nodes/{worker['id']}/messages",
432
+ json={"content": "Do the work", "from_id": "assistant"},
433
+ )
434
+
435
+ assert response.status_code == 400
436
+ assert response.json()["detail"] == (
437
+ "Web UI node messages must originate from `human`"
438
+ )
439
+
440
+
441
+ def test_leader_message_supports_structured_parts_and_image_validation(
442
+ monkeypatch, client: TestClient
443
+ ):
444
+ tab = client.post(
445
+ "/api/workflows",
446
+ json={"title": "Execution"},
447
+ ).json()
448
+ leader = registry.get(tab["leader_id"])
449
+ assert leader is not None
450
+
451
+ upload_response = client.post(
452
+ "/api/image-assets",
453
+ files={"file": ("pixel.png", _ONE_PIXEL_PNG, "image/png")},
454
+ )
455
+ assert upload_response.status_code == 200
456
+ asset_id = upload_response.json()["id"]
457
+
458
+ monkeypatch.setattr(leader, "supports_input_image", lambda: True)
459
+
460
+ response = client.post(
461
+ f"/api/nodes/{tab['leader_id']}/messages",
462
+ json={
463
+ "parts": [
464
+ {"type": "text", "text": "Inspect this screenshot"},
465
+ {
466
+ "type": "image",
467
+ "asset_id": asset_id,
468
+ "mime_type": "image/png",
469
+ "width": 1,
470
+ "height": 1,
471
+ "alt": "Pixel",
472
+ },
473
+ ]
474
+ },
475
+ )
476
+
477
+ assert response.status_code == 200
478
+ detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
479
+ entry = next(
480
+ history_entry
481
+ for history_entry in detail["history"]
482
+ if history_entry["type"] == "ReceivedMessage"
483
+ and history_entry["message_id"] == response.json()["message_id"]
484
+ )
485
+ assert entry["parts"][0]["text"] == "Inspect this screenshot"
486
+ assert entry["parts"][1]["asset_id"] == asset_id
@@ -0,0 +1,47 @@
1
+ from fastapi.testclient import TestClient
2
+
3
+ from flowent.settings import Settings
4
+
5
+
6
+ def test_prompts_api_reads_and_updates_custom_prompt(
7
+ client: TestClient,
8
+ monkeypatch,
9
+ ):
10
+ settings = Settings(
11
+ custom_prompt="Initial global prompt.",
12
+ custom_post_prompt="Initial runtime prompt.",
13
+ )
14
+ saved: list[tuple[str, str]] = []
15
+
16
+ monkeypatch.setattr("flowent.routes.prompts.get_settings", lambda: settings)
17
+ monkeypatch.setattr(
18
+ "flowent.routes.prompts.save_settings",
19
+ lambda current: saved.append(
20
+ (current.custom_prompt, current.custom_post_prompt)
21
+ ),
22
+ )
23
+
24
+ get_response = client.get("/api/prompts")
25
+
26
+ assert get_response.status_code == 200
27
+ assert get_response.json() == {
28
+ "custom_prompt": "Initial global prompt.",
29
+ "custom_post_prompt": "Initial runtime prompt.",
30
+ }
31
+
32
+ put_response = client.put(
33
+ "/api/prompts",
34
+ json={
35
+ "custom_prompt": "Updated global prompt.",
36
+ "custom_post_prompt": "Updated runtime prompt.",
37
+ },
38
+ )
39
+
40
+ assert put_response.status_code == 200
41
+ assert put_response.json() == {
42
+ "custom_prompt": "Updated global prompt.",
43
+ "custom_post_prompt": "Updated runtime prompt.",
44
+ }
45
+ assert settings.custom_prompt == "Updated global prompt."
46
+ assert settings.custom_post_prompt == "Updated runtime prompt."
47
+ assert saved == [("Updated global prompt.", "Updated runtime prompt.")]