flowent 0.0.1 → 0.0.5

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 (484) hide show
  1. package/README.md +20 -9
  2. package/backend/.python-version +1 -0
  3. package/backend/README.md +74 -0
  4. package/backend/pyproject.toml +58 -0
  5. package/backend/src/flowent/__init__.py +3 -0
  6. package/backend/src/flowent/__pycache__/__init__.cpython-313.pyc +0 -0
  7. package/backend/src/flowent/__pycache__/_version.cpython-313.pyc +0 -0
  8. package/backend/src/flowent/__pycache__/access.cpython-313.pyc +0 -0
  9. package/backend/src/flowent/__pycache__/agent.cpython-313.pyc +0 -0
  10. package/backend/src/flowent/__pycache__/assistant_commands.cpython-313.pyc +0 -0
  11. package/backend/src/flowent/__pycache__/cli.cpython-313.pyc +0 -0
  12. package/backend/src/flowent/__pycache__/config.cpython-313.pyc +0 -0
  13. package/backend/src/flowent/__pycache__/events.cpython-313.pyc +0 -0
  14. package/backend/src/flowent/__pycache__/graph_runtime.cpython-313.pyc +0 -0
  15. package/backend/src/flowent/__pycache__/graph_service.cpython-313.pyc +0 -0
  16. package/backend/src/flowent/__pycache__/image_assets.cpython-313.pyc +0 -0
  17. package/backend/src/flowent/__pycache__/logging.cpython-313.pyc +0 -0
  18. package/backend/src/flowent/__pycache__/main.cpython-313.pyc +0 -0
  19. package/backend/src/flowent/__pycache__/mcp_service.cpython-313.pyc +0 -0
  20. package/backend/src/flowent/__pycache__/model_metadata.cpython-313.pyc +0 -0
  21. package/backend/src/flowent/__pycache__/network.cpython-313.pyc +0 -0
  22. package/backend/src/flowent/__pycache__/observability_service.cpython-313.pyc +0 -0
  23. package/backend/src/flowent/__pycache__/registry.cpython-313.pyc +0 -0
  24. package/backend/src/flowent/__pycache__/role_management.cpython-313.pyc +0 -0
  25. package/backend/src/flowent/__pycache__/runtime.cpython-313.pyc +0 -0
  26. package/backend/src/flowent/__pycache__/sandbox.cpython-313.pyc +0 -0
  27. package/backend/src/flowent/__pycache__/security.cpython-313.pyc +0 -0
  28. package/backend/src/flowent/__pycache__/settings.cpython-313.pyc +0 -0
  29. package/backend/src/flowent/__pycache__/settings_management.cpython-313.pyc +0 -0
  30. package/backend/src/flowent/__pycache__/state_db.cpython-313.pyc +0 -0
  31. package/backend/src/flowent/__pycache__/workspace_store.cpython-313.pyc +0 -0
  32. package/backend/src/flowent/_version.py +7 -0
  33. package/backend/src/flowent/access.py +247 -0
  34. package/backend/src/flowent/agent.py +3120 -0
  35. package/backend/src/flowent/assistant_commands.py +115 -0
  36. package/backend/src/flowent/channels/__init__.py +3 -0
  37. package/backend/src/flowent/channels/__pycache__/__init__.cpython-313.pyc +0 -0
  38. package/backend/src/flowent/channels/__pycache__/telegram.cpython-313.pyc +0 -0
  39. package/backend/src/flowent/channels/telegram.py +615 -0
  40. package/backend/src/flowent/cli.py +85 -0
  41. package/backend/src/flowent/config.py +14 -0
  42. package/backend/src/flowent/dev.py +3 -0
  43. package/backend/src/flowent/events.py +157 -0
  44. package/backend/src/flowent/graph_runtime.py +60 -0
  45. package/backend/src/flowent/graph_service.py +2508 -0
  46. package/backend/src/flowent/image_assets.py +356 -0
  47. package/backend/src/flowent/logging.py +155 -0
  48. package/backend/src/flowent/main.py +124 -0
  49. package/backend/src/flowent/mcp_service.py +1918 -0
  50. package/backend/src/flowent/model_metadata.py +102 -0
  51. package/backend/src/flowent/models/__init__.py +125 -0
  52. package/backend/src/flowent/models/__pycache__/__init__.cpython-313.pyc +0 -0
  53. package/backend/src/flowent/models/__pycache__/agent.cpython-313.pyc +0 -0
  54. package/backend/src/flowent/models/__pycache__/base.cpython-313.pyc +0 -0
  55. package/backend/src/flowent/models/__pycache__/blueprint.cpython-313.pyc +0 -0
  56. package/backend/src/flowent/models/__pycache__/content.cpython-313.pyc +0 -0
  57. package/backend/src/flowent/models/__pycache__/delta.cpython-313.pyc +0 -0
  58. package/backend/src/flowent/models/__pycache__/event.cpython-313.pyc +0 -0
  59. package/backend/src/flowent/models/__pycache__/graph.cpython-313.pyc +0 -0
  60. package/backend/src/flowent/models/__pycache__/history.cpython-313.pyc +0 -0
  61. package/backend/src/flowent/models/__pycache__/llm.cpython-313.pyc +0 -0
  62. package/backend/src/flowent/models/__pycache__/message.cpython-313.pyc +0 -0
  63. package/backend/src/flowent/models/__pycache__/tab.cpython-313.pyc +0 -0
  64. package/backend/src/flowent/models/__pycache__/todo.cpython-313.pyc +0 -0
  65. package/backend/src/flowent/models/agent.py +34 -0
  66. package/backend/src/flowent/models/base.py +24 -0
  67. package/backend/src/flowent/models/blueprint.py +176 -0
  68. package/backend/src/flowent/models/content.py +164 -0
  69. package/backend/src/flowent/models/delta.py +44 -0
  70. package/backend/src/flowent/models/event.py +51 -0
  71. package/backend/src/flowent/models/graph.py +472 -0
  72. package/backend/src/flowent/models/history.py +272 -0
  73. package/backend/src/flowent/models/llm.py +62 -0
  74. package/backend/src/flowent/models/message.py +33 -0
  75. package/backend/src/flowent/models/tab.py +85 -0
  76. package/backend/src/flowent/models/todo.py +10 -0
  77. package/backend/src/flowent/network.py +146 -0
  78. package/backend/src/flowent/observability_service.py +218 -0
  79. package/backend/src/flowent/prompts/__init__.py +67 -0
  80. package/backend/src/flowent/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  81. package/backend/src/flowent/prompts/__pycache__/common.cpython-313.pyc +0 -0
  82. package/backend/src/flowent/prompts/__pycache__/steward.cpython-313.pyc +0 -0
  83. package/backend/src/flowent/prompts/common.py +250 -0
  84. package/backend/src/flowent/prompts/steward.py +64 -0
  85. package/backend/src/flowent/providers/__init__.py +23 -0
  86. package/backend/src/flowent/providers/__pycache__/__init__.cpython-313.pyc +0 -0
  87. package/backend/src/flowent/providers/__pycache__/anthropic.cpython-313.pyc +0 -0
  88. package/backend/src/flowent/providers/__pycache__/base_url.cpython-313.pyc +0 -0
  89. package/backend/src/flowent/providers/__pycache__/configuration.cpython-313.pyc +0 -0
  90. package/backend/src/flowent/providers/__pycache__/content.cpython-313.pyc +0 -0
  91. package/backend/src/flowent/providers/__pycache__/errors.cpython-313.pyc +0 -0
  92. package/backend/src/flowent/providers/__pycache__/gateway.cpython-313.pyc +0 -0
  93. package/backend/src/flowent/providers/__pycache__/headers.cpython-313.pyc +0 -0
  94. package/backend/src/flowent/providers/__pycache__/management.cpython-313.pyc +0 -0
  95. package/backend/src/flowent/providers/__pycache__/openai.cpython-313.pyc +0 -0
  96. package/backend/src/flowent/providers/__pycache__/openai_responses.cpython-313.pyc +0 -0
  97. package/backend/src/flowent/providers/__pycache__/registry.cpython-313.pyc +0 -0
  98. package/backend/src/flowent/providers/__pycache__/sse.cpython-313.pyc +0 -0
  99. package/backend/src/flowent/providers/__pycache__/thinking.cpython-313.pyc +0 -0
  100. package/backend/src/flowent/providers/anthropic.py +468 -0
  101. package/backend/src/flowent/providers/base_url.py +60 -0
  102. package/backend/src/flowent/providers/configuration.py +189 -0
  103. package/backend/src/flowent/providers/content.py +122 -0
  104. package/backend/src/flowent/providers/errors.py +223 -0
  105. package/backend/src/flowent/providers/gateway.py +169 -0
  106. package/backend/src/flowent/providers/gemini.py +447 -0
  107. package/backend/src/flowent/providers/headers.py +20 -0
  108. package/backend/src/flowent/providers/management.py +96 -0
  109. package/backend/src/flowent/providers/ollama.py +293 -0
  110. package/backend/src/flowent/providers/openai.py +422 -0
  111. package/backend/src/flowent/providers/openai_responses.py +655 -0
  112. package/backend/src/flowent/providers/registry.py +144 -0
  113. package/backend/src/flowent/providers/sse.py +31 -0
  114. package/backend/src/flowent/providers/thinking.py +79 -0
  115. package/backend/src/flowent/registry.py +73 -0
  116. package/backend/src/flowent/role_management.py +267 -0
  117. package/backend/src/flowent/routes/__init__.py +28 -0
  118. package/backend/src/flowent/routes/__pycache__/__init__.cpython-313.pyc +0 -0
  119. package/backend/src/flowent/routes/__pycache__/access.cpython-313.pyc +0 -0
  120. package/backend/src/flowent/routes/__pycache__/assistant.cpython-313.pyc +0 -0
  121. package/backend/src/flowent/routes/__pycache__/image_assets.cpython-313.pyc +0 -0
  122. package/backend/src/flowent/routes/__pycache__/mcp.cpython-313.pyc +0 -0
  123. package/backend/src/flowent/routes/__pycache__/meta.cpython-313.pyc +0 -0
  124. package/backend/src/flowent/routes/__pycache__/nodes.cpython-313.pyc +0 -0
  125. package/backend/src/flowent/routes/__pycache__/prompts.cpython-313.pyc +0 -0
  126. package/backend/src/flowent/routes/__pycache__/providers_route.cpython-313.pyc +0 -0
  127. package/backend/src/flowent/routes/__pycache__/roles.cpython-313.pyc +0 -0
  128. package/backend/src/flowent/routes/__pycache__/settings.cpython-313.pyc +0 -0
  129. package/backend/src/flowent/routes/__pycache__/tabs.cpython-313.pyc +0 -0
  130. package/backend/src/flowent/routes/__pycache__/ws.cpython-313.pyc +0 -0
  131. package/backend/src/flowent/routes/access.py +48 -0
  132. package/backend/src/flowent/routes/assistant.py +155 -0
  133. package/backend/src/flowent/routes/image_assets.py +33 -0
  134. package/backend/src/flowent/routes/mcp.py +125 -0
  135. package/backend/src/flowent/routes/meta.py +28 -0
  136. package/backend/src/flowent/routes/nodes.py +413 -0
  137. package/backend/src/flowent/routes/prompts.py +46 -0
  138. package/backend/src/flowent/routes/providers_route.py +365 -0
  139. package/backend/src/flowent/routes/roles.py +207 -0
  140. package/backend/src/flowent/routes/settings.py +328 -0
  141. package/backend/src/flowent/routes/tabs.py +310 -0
  142. package/backend/src/flowent/routes/ws.py +33 -0
  143. package/backend/src/flowent/runtime.py +165 -0
  144. package/backend/src/flowent/sandbox.py +45 -0
  145. package/backend/src/flowent/security.py +57 -0
  146. package/backend/src/flowent/settings.py +2518 -0
  147. package/backend/src/flowent/settings_management.py +298 -0
  148. package/backend/src/flowent/state_db.py +120 -0
  149. package/backend/src/flowent/static/assets/AssistantPage-VBohhz4d.js +1 -0
  150. package/backend/src/flowent/static/assets/ChannelsPage-CIydPZA_.js +1 -0
  151. package/backend/src/flowent/static/assets/McpPage-CHPm2TPY.js +7 -0
  152. package/backend/src/flowent/static/assets/PageScaffold-DteOA8V7.js +1 -0
  153. package/backend/src/flowent/static/assets/PromptsPage-CSmJ3sZg.js +1 -0
  154. package/backend/src/flowent/static/assets/ProvidersPage-sl2jeG4e.js +3 -0
  155. package/backend/src/flowent/static/assets/RolesPage-DCe7W6Km.js +1 -0
  156. package/backend/src/flowent/static/assets/SettingsPage-Bix9e63E.js +3 -0
  157. package/backend/src/flowent/static/assets/ToolsPage-favNkj5C.js +1 -0
  158. package/backend/src/flowent/static/assets/WorkspaceCommandDialog-DRS6wiD6.js +1 -0
  159. package/backend/src/flowent/static/assets/WorkspacePage-KuaDjt_D.js +3 -0
  160. package/backend/src/flowent/static/assets/WorkspacePanels-BZxBw8M5.js +1 -0
  161. package/backend/src/flowent/static/assets/alert-dialog-DIBUCmqM.js +1 -0
  162. package/backend/src/flowent/static/assets/datetime-eJqd0V2S.js +1 -0
  163. package/backend/src/flowent/static/assets/dialog-BOvHIBrg.js +1 -0
  164. package/backend/src/flowent/static/assets/elk-worker.min-C9JGDOE-.js +6312 -0
  165. package/backend/src/flowent/static/assets/graph-vendor-CHpVij2M.css +1 -0
  166. package/backend/src/flowent/static/assets/graph-vendor-DRq_-6fV.js +7 -0
  167. package/backend/src/flowent/static/assets/index-Biio-CoI.js +10 -0
  168. package/backend/src/flowent/static/assets/index-CmQvO7sl.css +1 -0
  169. package/backend/src/flowent/static/assets/layout.worker-jMHqAFbP.js +24 -0
  170. package/backend/src/flowent/static/assets/markdown-vendor-C9RtvaJh.js +29 -0
  171. package/backend/src/flowent/static/assets/modelParams-DcEhGnu0.js +1 -0
  172. package/backend/src/flowent/static/assets/react-vendor-mEs_JJxa.js +9 -0
  173. package/backend/src/flowent/static/assets/roles-BbIEIMeG.js +1 -0
  174. package/backend/src/flowent/static/assets/rolldown-runtime-BYbx6iT9.js +1 -0
  175. package/backend/src/flowent/static/assets/select-D9SwnlXF.js +1 -0
  176. package/backend/src/flowent/static/assets/surface-Bzr1FRG4.js +1 -0
  177. package/backend/src/flowent/static/assets/triState-DgLlKdRR.js +1 -0
  178. package/backend/src/flowent/static/assets/ui-vendor-UazN8rcv.js +51 -0
  179. package/backend/src/flowent/static/index.html +35 -0
  180. package/backend/src/flowent/tools/__init__.py +275 -0
  181. package/backend/src/flowent/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  182. package/backend/src/flowent/tools/__pycache__/connect.cpython-313.pyc +0 -0
  183. package/backend/src/flowent/tools/__pycache__/contacts.cpython-313.pyc +0 -0
  184. package/backend/src/flowent/tools/__pycache__/create_agent.cpython-313.pyc +0 -0
  185. package/backend/src/flowent/tools/__pycache__/create_tab.cpython-313.pyc +0 -0
  186. package/backend/src/flowent/tools/__pycache__/delete_tab.cpython-313.pyc +0 -0
  187. package/backend/src/flowent/tools/__pycache__/edit.cpython-313.pyc +0 -0
  188. package/backend/src/flowent/tools/__pycache__/exec.cpython-313.pyc +0 -0
  189. package/backend/src/flowent/tools/__pycache__/fetch.cpython-313.pyc +0 -0
  190. package/backend/src/flowent/tools/__pycache__/idle.cpython-313.pyc +0 -0
  191. package/backend/src/flowent/tools/__pycache__/list_roles.cpython-313.pyc +0 -0
  192. package/backend/src/flowent/tools/__pycache__/list_tabs.cpython-313.pyc +0 -0
  193. package/backend/src/flowent/tools/__pycache__/list_tools.cpython-313.pyc +0 -0
  194. package/backend/src/flowent/tools/__pycache__/manage_prompts.cpython-313.pyc +0 -0
  195. package/backend/src/flowent/tools/__pycache__/manage_providers.cpython-313.pyc +0 -0
  196. package/backend/src/flowent/tools/__pycache__/manage_roles.cpython-313.pyc +0 -0
  197. package/backend/src/flowent/tools/__pycache__/manage_settings.cpython-313.pyc +0 -0
  198. package/backend/src/flowent/tools/__pycache__/mcp.cpython-313.pyc +0 -0
  199. package/backend/src/flowent/tools/__pycache__/read.cpython-313.pyc +0 -0
  200. package/backend/src/flowent/tools/__pycache__/send.cpython-313.pyc +0 -0
  201. package/backend/src/flowent/tools/__pycache__/set_permissions.cpython-313.pyc +0 -0
  202. package/backend/src/flowent/tools/__pycache__/sleep.cpython-313.pyc +0 -0
  203. package/backend/src/flowent/tools/__pycache__/todo.cpython-313.pyc +0 -0
  204. package/backend/src/flowent/tools/connect.py +100 -0
  205. package/backend/src/flowent/tools/contacts.py +22 -0
  206. package/backend/src/flowent/tools/create_agent.py +191 -0
  207. package/backend/src/flowent/tools/create_tab.py +61 -0
  208. package/backend/src/flowent/tools/delete_tab.py +39 -0
  209. package/backend/src/flowent/tools/edit.py +142 -0
  210. package/backend/src/flowent/tools/exec.py +118 -0
  211. package/backend/src/flowent/tools/fetch.py +85 -0
  212. package/backend/src/flowent/tools/idle.py +27 -0
  213. package/backend/src/flowent/tools/list_roles.py +75 -0
  214. package/backend/src/flowent/tools/list_tabs.py +100 -0
  215. package/backend/src/flowent/tools/list_tools.py +28 -0
  216. package/backend/src/flowent/tools/manage_prompts.py +102 -0
  217. package/backend/src/flowent/tools/manage_providers.py +220 -0
  218. package/backend/src/flowent/tools/manage_roles.py +275 -0
  219. package/backend/src/flowent/tools/manage_settings.py +364 -0
  220. package/backend/src/flowent/tools/mcp.py +199 -0
  221. package/backend/src/flowent/tools/read.py +152 -0
  222. package/backend/src/flowent/tools/send.py +68 -0
  223. package/backend/src/flowent/tools/set_permissions.py +99 -0
  224. package/backend/src/flowent/tools/sleep.py +41 -0
  225. package/backend/src/flowent/tools/todo.py +51 -0
  226. package/backend/src/flowent/workspace_store.py +479 -0
  227. package/backend/tests/__init__.py +0 -0
  228. package/backend/tests/__pycache__/__init__.cpython-313.pyc +0 -0
  229. package/backend/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  230. package/backend/tests/conftest.py +6 -0
  231. package/backend/tests/integration/api/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  232. package/backend/tests/integration/api/__pycache__/test_access_api.cpython-313-pytest-9.0.3.pyc +0 -0
  233. package/backend/tests/integration/api/__pycache__/test_assistant_api.cpython-313-pytest-9.0.3.pyc +0 -0
  234. package/backend/tests/integration/api/__pycache__/test_frontend_mounting.cpython-313-pytest-9.0.3.pyc +0 -0
  235. package/backend/tests/integration/api/__pycache__/test_mcp_api.cpython-313-pytest-9.0.3.pyc +0 -0
  236. package/backend/tests/integration/api/__pycache__/test_meta_api.cpython-313-pytest-9.0.3.pyc +0 -0
  237. package/backend/tests/integration/api/__pycache__/test_nodes_api.cpython-313-pytest-9.0.3.pyc +0 -0
  238. package/backend/tests/integration/api/__pycache__/test_prompts_api.cpython-313-pytest-9.0.3.pyc +0 -0
  239. package/backend/tests/integration/api/__pycache__/test_roles_api.cpython-313-pytest-9.0.3.pyc +0 -0
  240. package/backend/tests/integration/api/__pycache__/test_tabs_api.cpython-313-pytest-9.0.3.pyc +0 -0
  241. package/backend/tests/integration/api/conftest.py +29 -0
  242. package/backend/tests/integration/api/test_access_api.py +182 -0
  243. package/backend/tests/integration/api/test_assistant_api.py +354 -0
  244. package/backend/tests/integration/api/test_frontend_mounting.py +61 -0
  245. package/backend/tests/integration/api/test_mcp_api.py +116 -0
  246. package/backend/tests/integration/api/test_meta_api.py +33 -0
  247. package/backend/tests/integration/api/test_nodes_api.py +722 -0
  248. package/backend/tests/integration/api/test_prompts_api.py +47 -0
  249. package/backend/tests/integration/api/test_roles_api.py +228 -0
  250. package/backend/tests/integration/api/test_tabs_api.py +802 -0
  251. package/backend/tests/unit/__pycache__/test_access.cpython-313-pytest-9.0.3.pyc +0 -0
  252. package/backend/tests/unit/__pycache__/test_cli.cpython-313-pytest-9.0.3.pyc +0 -0
  253. package/backend/tests/unit/__pycache__/test_graph_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  254. package/backend/tests/unit/__pycache__/test_network.cpython-313-pytest-9.0.3.pyc +0 -0
  255. package/backend/tests/unit/__pycache__/test_state_sqlite_storage.cpython-313-pytest-9.0.3.pyc +0 -0
  256. package/backend/tests/unit/__pycache__/test_workspace_store.cpython-313-pytest-9.0.3.pyc +0 -0
  257. package/backend/tests/unit/agent/__pycache__/test_agent_public_api.cpython-313-pytest-9.0.3.pyc +0 -0
  258. package/backend/tests/unit/agent/__pycache__/test_agent_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  259. package/backend/tests/unit/agent/test_agent_public_api.py +837 -0
  260. package/backend/tests/unit/agent/test_agent_runtime.py +2942 -0
  261. package/backend/tests/unit/channels/__pycache__/test_telegram_channel.cpython-313-pytest-9.0.3.pyc +0 -0
  262. package/backend/tests/unit/channels/test_telegram_channel.py +552 -0
  263. package/backend/tests/unit/logging/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
  264. package/backend/tests/unit/logging/test_logging.py +132 -0
  265. package/backend/tests/unit/prompts/__pycache__/test_prompts.cpython-313-pytest-9.0.3.pyc +0 -0
  266. package/backend/tests/unit/prompts/test_prompts.py +570 -0
  267. package/backend/tests/unit/providers/__pycache__/test_anthropic_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  268. package/backend/tests/unit/providers/__pycache__/test_errors.cpython-313-pytest-9.0.3.pyc +0 -0
  269. package/backend/tests/unit/providers/__pycache__/test_extract_delta_parts.cpython-313-pytest-9.0.3.pyc +0 -0
  270. package/backend/tests/unit/providers/__pycache__/test_openai_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  271. package/backend/tests/unit/providers/__pycache__/test_openai_responses.cpython-313-pytest-9.0.3.pyc +0 -0
  272. package/backend/tests/unit/providers/__pycache__/test_provider_gateway.cpython-313-pytest-9.0.3.pyc +0 -0
  273. package/backend/tests/unit/providers/__pycache__/test_think_tag_parser.cpython-313-pytest-9.0.3.pyc +0 -0
  274. package/backend/tests/unit/providers/test_anthropic_provider.py +185 -0
  275. package/backend/tests/unit/providers/test_errors.py +68 -0
  276. package/backend/tests/unit/providers/test_extract_delta_parts.py +22 -0
  277. package/backend/tests/unit/providers/test_openai_provider.py +139 -0
  278. package/backend/tests/unit/providers/test_openai_responses.py +402 -0
  279. package/backend/tests/unit/providers/test_provider_gateway.py +359 -0
  280. package/backend/tests/unit/providers/test_think_tag_parser.py +36 -0
  281. package/backend/tests/unit/routes/__pycache__/test_prompts_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  282. package/backend/tests/unit/routes/__pycache__/test_providers_route.cpython-313-pytest-9.0.3.pyc +0 -0
  283. package/backend/tests/unit/routes/__pycache__/test_roles_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  284. package/backend/tests/unit/routes/__pycache__/test_settings_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  285. package/backend/tests/unit/routes/test_prompts_routes.py +104 -0
  286. package/backend/tests/unit/routes/test_providers_route.py +370 -0
  287. package/backend/tests/unit/routes/test_roles_routes.py +535 -0
  288. package/backend/tests/unit/routes/test_settings_routes.py +1142 -0
  289. package/backend/tests/unit/runtime/__pycache__/test_bootstrap_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  290. package/backend/tests/unit/runtime/test_bootstrap_runtime.py +1002 -0
  291. package/backend/tests/unit/sandbox/__pycache__/test_sandbox_tools.cpython-313-pytest-9.0.3.pyc +0 -0
  292. package/backend/tests/unit/sandbox/test_sandbox_tools.py +78 -0
  293. package/backend/tests/unit/security/__pycache__/test_security.cpython-313-pytest-9.0.3.pyc +0 -0
  294. package/backend/tests/unit/security/test_security.py +124 -0
  295. package/backend/tests/unit/settings/__pycache__/test_settings_roles.cpython-313-pytest-9.0.3.pyc +0 -0
  296. package/backend/tests/unit/settings/test_settings_roles.py +751 -0
  297. package/backend/tests/unit/test_access.py +45 -0
  298. package/backend/tests/unit/test_cli.py +124 -0
  299. package/backend/tests/unit/test_graph_runtime.py +72 -0
  300. package/backend/tests/unit/test_network.py +51 -0
  301. package/backend/tests/unit/test_state_sqlite_storage.py +159 -0
  302. package/backend/tests/unit/test_workspace_store.py +231 -0
  303. package/backend/tests/unit/tools/__pycache__/test_connect_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  304. package/backend/tests/unit/tools/__pycache__/test_create_agent_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  305. package/backend/tests/unit/tools/__pycache__/test_delete_tab_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  306. package/backend/tests/unit/tools/__pycache__/test_edit_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  307. package/backend/tests/unit/tools/__pycache__/test_exec_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  308. package/backend/tests/unit/tools/__pycache__/test_fetch_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  309. package/backend/tests/unit/tools/__pycache__/test_manage_prompts_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  310. package/backend/tests/unit/tools/__pycache__/test_manage_providers_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  311. package/backend/tests/unit/tools/__pycache__/test_manage_roles_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  312. package/backend/tests/unit/tools/__pycache__/test_manage_settings_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  313. package/backend/tests/unit/tools/__pycache__/test_read_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  314. package/backend/tests/unit/tools/__pycache__/test_set_permissions_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  315. package/backend/tests/unit/tools/__pycache__/test_todo_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  316. package/backend/tests/unit/tools/__pycache__/test_tool_registry.cpython-313-pytest-9.0.3.pyc +0 -0
  317. package/backend/tests/unit/tools/test_connect_tool.py +228 -0
  318. package/backend/tests/unit/tools/test_create_agent_tool.py +436 -0
  319. package/backend/tests/unit/tools/test_delete_tab_tool.py +116 -0
  320. package/backend/tests/unit/tools/test_edit_tool.py +115 -0
  321. package/backend/tests/unit/tools/test_exec_tool.py +81 -0
  322. package/backend/tests/unit/tools/test_fetch_tool.py +65 -0
  323. package/backend/tests/unit/tools/test_manage_prompts_tool.py +117 -0
  324. package/backend/tests/unit/tools/test_manage_providers_tool.py +460 -0
  325. package/backend/tests/unit/tools/test_manage_roles_tool.py +411 -0
  326. package/backend/tests/unit/tools/test_manage_settings_tool.py +611 -0
  327. package/backend/tests/unit/tools/test_read_tool.py +33 -0
  328. package/backend/tests/unit/tools/test_set_permissions_tool.py +595 -0
  329. package/backend/tests/unit/tools/test_todo_tool.py +37 -0
  330. package/backend/tests/unit/tools/test_tool_registry.py +194 -0
  331. package/backend/uv.lock +1144 -0
  332. package/bin/flowent.mjs +62 -36
  333. package/dist/frontend/assets/AssistantPage-VBohhz4d.js +1 -0
  334. package/dist/frontend/assets/ChannelsPage-CIydPZA_.js +1 -0
  335. package/dist/frontend/assets/McpPage-CHPm2TPY.js +7 -0
  336. package/dist/frontend/assets/PageScaffold-DteOA8V7.js +1 -0
  337. package/dist/frontend/assets/PromptsPage-CSmJ3sZg.js +1 -0
  338. package/dist/frontend/assets/ProvidersPage-sl2jeG4e.js +3 -0
  339. package/dist/frontend/assets/RolesPage-DCe7W6Km.js +1 -0
  340. package/dist/frontend/assets/SettingsPage-Bix9e63E.js +3 -0
  341. package/dist/frontend/assets/ToolsPage-favNkj5C.js +1 -0
  342. package/dist/frontend/assets/WorkspaceCommandDialog-DRS6wiD6.js +1 -0
  343. package/dist/frontend/assets/WorkspacePage-KuaDjt_D.js +3 -0
  344. package/dist/frontend/assets/WorkspacePanels-BZxBw8M5.js +1 -0
  345. package/dist/frontend/assets/alert-dialog-DIBUCmqM.js +1 -0
  346. package/dist/frontend/assets/datetime-eJqd0V2S.js +1 -0
  347. package/dist/frontend/assets/dialog-BOvHIBrg.js +1 -0
  348. package/dist/frontend/assets/elk-worker.min-C9JGDOE-.js +6312 -0
  349. package/dist/frontend/assets/graph-vendor-CHpVij2M.css +1 -0
  350. package/dist/frontend/assets/graph-vendor-DRq_-6fV.js +7 -0
  351. package/dist/frontend/assets/index-Biio-CoI.js +10 -0
  352. package/dist/frontend/assets/index-CmQvO7sl.css +1 -0
  353. package/dist/frontend/assets/layout.worker-jMHqAFbP.js +24 -0
  354. package/dist/frontend/assets/markdown-vendor-C9RtvaJh.js +29 -0
  355. package/dist/frontend/assets/modelParams-DcEhGnu0.js +1 -0
  356. package/dist/frontend/assets/react-vendor-mEs_JJxa.js +9 -0
  357. package/dist/frontend/assets/roles-BbIEIMeG.js +1 -0
  358. package/dist/frontend/assets/rolldown-runtime-BYbx6iT9.js +1 -0
  359. package/dist/frontend/assets/select-D9SwnlXF.js +1 -0
  360. package/dist/frontend/assets/surface-Bzr1FRG4.js +1 -0
  361. package/dist/frontend/assets/triState-DgLlKdRR.js +1 -0
  362. package/dist/frontend/assets/ui-vendor-UazN8rcv.js +51 -0
  363. package/dist/frontend/index.html +35 -0
  364. package/package.json +27 -41
  365. package/dist/.next/BUILD_ID +0 -1
  366. package/dist/.next/app-path-routes-manifest.json +0 -6
  367. package/dist/.next/build-manifest.json +0 -20
  368. package/dist/.next/package.json +0 -1
  369. package/dist/.next/prerender-manifest.json +0 -114
  370. package/dist/.next/required-server-files.json +0 -333
  371. package/dist/.next/routes-manifest.json +0 -69
  372. package/dist/.next/server/app/_global-error/page/app-paths-manifest.json +0 -3
  373. package/dist/.next/server/app/_global-error/page/build-manifest.json +0 -16
  374. package/dist/.next/server/app/_global-error/page/next-font-manifest.json +0 -6
  375. package/dist/.next/server/app/_global-error/page/react-loadable-manifest.json +0 -1
  376. package/dist/.next/server/app/_global-error/page/server-reference-manifest.json +0 -4
  377. package/dist/.next/server/app/_global-error/page.js +0 -9
  378. package/dist/.next/server/app/_global-error/page.js.map +0 -5
  379. package/dist/.next/server/app/_global-error/page.js.nft.json +0 -1
  380. package/dist/.next/server/app/_global-error/page_client-reference-manifest.js +0 -3
  381. package/dist/.next/server/app/_global-error.html +0 -1
  382. package/dist/.next/server/app/_global-error.meta +0 -15
  383. package/dist/.next/server/app/_global-error.rsc +0 -14
  384. package/dist/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +0 -5
  385. package/dist/.next/server/app/_global-error.segments/_full.segment.rsc +0 -14
  386. package/dist/.next/server/app/_global-error.segments/_head.segment.rsc +0 -5
  387. package/dist/.next/server/app/_global-error.segments/_index.segment.rsc +0 -5
  388. package/dist/.next/server/app/_global-error.segments/_tree.segment.rsc +0 -1
  389. package/dist/.next/server/app/_not-found/page/app-paths-manifest.json +0 -3
  390. package/dist/.next/server/app/_not-found/page/build-manifest.json +0 -16
  391. package/dist/.next/server/app/_not-found/page/next-font-manifest.json +0 -10
  392. package/dist/.next/server/app/_not-found/page/react-loadable-manifest.json +0 -1
  393. package/dist/.next/server/app/_not-found/page/server-reference-manifest.json +0 -4
  394. package/dist/.next/server/app/_not-found/page.js +0 -13
  395. package/dist/.next/server/app/_not-found/page.js.map +0 -5
  396. package/dist/.next/server/app/_not-found/page.js.nft.json +0 -1
  397. package/dist/.next/server/app/_not-found/page_client-reference-manifest.js +0 -3
  398. package/dist/.next/server/app/_not-found.html +0 -1
  399. package/dist/.next/server/app/_not-found.meta +0 -16
  400. package/dist/.next/server/app/_not-found.rsc +0 -16
  401. package/dist/.next/server/app/_not-found.segments/_full.segment.rsc +0 -16
  402. package/dist/.next/server/app/_not-found.segments/_head.segment.rsc +0 -6
  403. package/dist/.next/server/app/_not-found.segments/_index.segment.rsc +0 -5
  404. package/dist/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +0 -5
  405. package/dist/.next/server/app/_not-found.segments/_not-found.segment.rsc +0 -5
  406. package/dist/.next/server/app/_not-found.segments/_tree.segment.rsc +0 -2
  407. package/dist/.next/server/app/icon.svg/route/app-paths-manifest.json +0 -3
  408. package/dist/.next/server/app/icon.svg/route/build-manifest.json +0 -9
  409. package/dist/.next/server/app/icon.svg/route.js +0 -6
  410. package/dist/.next/server/app/icon.svg/route.js.map +0 -5
  411. package/dist/.next/server/app/icon.svg/route.js.nft.json +0 -1
  412. package/dist/.next/server/app/icon.svg.meta +0 -1
  413. package/dist/.next/server/app/index.html +0 -1
  414. package/dist/.next/server/app/index.meta +0 -14
  415. package/dist/.next/server/app/index.rsc +0 -15
  416. package/dist/.next/server/app/index.segments/__PAGE__.segment.rsc +0 -5
  417. package/dist/.next/server/app/index.segments/_full.segment.rsc +0 -15
  418. package/dist/.next/server/app/index.segments/_head.segment.rsc +0 -6
  419. package/dist/.next/server/app/index.segments/_index.segment.rsc +0 -5
  420. package/dist/.next/server/app/index.segments/_tree.segment.rsc +0 -3
  421. package/dist/.next/server/app/page/app-paths-manifest.json +0 -3
  422. package/dist/.next/server/app/page/build-manifest.json +0 -16
  423. package/dist/.next/server/app/page/next-font-manifest.json +0 -10
  424. package/dist/.next/server/app/page/react-loadable-manifest.json +0 -1
  425. package/dist/.next/server/app/page/server-reference-manifest.json +0 -4
  426. package/dist/.next/server/app/page.js +0 -14
  427. package/dist/.next/server/app/page.js.map +0 -5
  428. package/dist/.next/server/app/page.js.nft.json +0 -1
  429. package/dist/.next/server/app/page_client-reference-manifest.js +0 -3
  430. package/dist/.next/server/app-paths-manifest.json +0 -6
  431. package/dist/.next/server/chunks/[externals]_next_dist_0arv.vj._.js +0 -3
  432. package/dist/.next/server/chunks/[root-of-the-server]__0vcj1q1._.js +0 -13
  433. package/dist/.next/server/chunks/[turbopack]_runtime.js +0 -903
  434. package/dist/.next/server/chunks/_next-internal_server_app_icon_svg_route_actions_0-0ehc~.js +0 -3
  435. package/dist/.next/server/chunks/ssr/05w9_next_dist_0ihu0u9._.js +0 -6
  436. package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_12u3mib._.js +0 -3
  437. package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_builtin_forbidden_04fbe_..js +0 -3
  438. package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_builtin_global-error_0brpl_..js +0 -3
  439. package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_builtin_unauthorized_0~2g66g.js +0 -3
  440. package/dist/.next/server/chunks/ssr/05w9_next_dist_esm_build_templates_app-page_0~cyr1_.js +0 -4
  441. package/dist/.next/server/chunks/ssr/05w9_next_dist_esm_build_templates_app-page_1105emf.js +0 -4
  442. package/dist/.next/server/chunks/ssr/05w9_next_dist_esm_build_templates_app-page_11uhyqv.js +0 -4
  443. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0.t9_75._.js +0 -33
  444. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0c0ud_z._.js +0 -3
  445. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0f9_8d4._.js +0 -3
  446. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0l5ko41._.js +0 -19
  447. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0mn6z7i._.js +0 -3
  448. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0npxxst._.js +0 -33
  449. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0qjhaca._.js +0 -3
  450. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0rwgw3s._.js +0 -3
  451. package/dist/.next/server/chunks/ssr/[turbopack]_runtime.js +0 -903
  452. package/dist/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_0k77kol.js +0 -3
  453. package/dist/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_0eq97pa.js +0 -3
  454. package/dist/.next/server/chunks/ssr/_next-internal_server_app_page_actions_09-gtaw.js +0 -3
  455. package/dist/.next/server/chunks/ssr/node_modules__pnpm_056~6.6._.js +0 -3
  456. package/dist/.next/server/chunks/ssr/node_modules__pnpm_0~j0k.e._.js +0 -33
  457. package/dist/.next/server/functions-config-manifest.json +0 -4
  458. package/dist/.next/server/middleware-build-manifest.js +0 -20
  459. package/dist/.next/server/middleware-manifest.json +0 -6
  460. package/dist/.next/server/next-font-manifest.js +0 -1
  461. package/dist/.next/server/next-font-manifest.json +0 -13
  462. package/dist/.next/server/pages/404.html +0 -1
  463. package/dist/.next/server/pages/500.html +0 -1
  464. package/dist/.next/server/pages-manifest.json +0 -4
  465. package/dist/.next/server/prefetch-hints.json +0 -1
  466. package/dist/.next/server/server-reference-manifest.js +0 -1
  467. package/dist/.next/server/server-reference-manifest.json +0 -5
  468. package/dist/.next/static/SMWpxFVvkpYFxY7uuFvGB/_buildManifest.js +0 -11
  469. package/dist/.next/static/SMWpxFVvkpYFxY7uuFvGB/_clientMiddlewareManifest.js +0 -1
  470. package/dist/.next/static/SMWpxFVvkpYFxY7uuFvGB/_ssgManifest.js +0 -1
  471. package/dist/.next/static/chunks/01qk2~bgf76vu.js +0 -1
  472. package/dist/.next/static/chunks/03~yq9q893hmn.js +0 -1
  473. package/dist/.next/static/chunks/080queev.r2uy.js +0 -31
  474. package/dist/.next/static/chunks/0v3lyuj75aq50.js +0 -1
  475. package/dist/.next/static/chunks/10b~xdx5c-i7s.js +0 -5
  476. package/dist/.next/static/chunks/14gla2ascffgv.css +0 -2
  477. package/dist/.next/static/chunks/turbopack-0m-970~qvs7sc.js +0 -1
  478. package/dist/.next/static/media/7178b3e590c64307-s.11.cyxs5p-0z~.woff2 +0 -0
  479. package/dist/.next/static/media/8a480f0b521d4e75-s.06d3mdzz5bre_.woff2 +0 -0
  480. package/dist/.next/static/media/caa3a2e1cccd8315-s.p.16t1db8_9y2o~.woff2 +0 -0
  481. package/dist/package.json +0 -88
  482. package/dist/server.js +0 -38
  483. /package/{dist/.next/server/app/icon.svg.body → backend/src/flowent/static/favicon.svg} +0 -0
  484. /package/dist/{.next/static/media/icon.0.r~afrtrocz9.svg → frontend/favicon.svg} +0 -0
@@ -0,0 +1,722 @@
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
+ LLMResponse,
12
+ ReceivedMessage,
13
+ TextPart,
14
+ )
15
+ from flowent.registry import registry
16
+ from flowent.routes.nodes import router as nodes_router
17
+ from flowent.settings import STEWARD_ROLE_INCLUDED_TOOLS
18
+ from flowent.tools import MINIMUM_TOOLS
19
+
20
+ _ONE_PIXEL_PNG = bytes.fromhex(
21
+ "89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c489"
22
+ "0000000d49444154789c6360000002000154a24f5d0000000049454e44ae426082"
23
+ )
24
+
25
+
26
+ def _get_assistant_id(client: TestClient) -> str:
27
+ response = client.get("/api/assistant")
28
+
29
+ assert response.status_code == 200
30
+ assistant_id = response.json()["id"]
31
+ UUID(assistant_id)
32
+ return assistant_id
33
+
34
+
35
+ def test_health_check(client: TestClient):
36
+ response = client.get("/health")
37
+
38
+ assert response.status_code == 200
39
+ assert response.json() == {"status": "healthy"}
40
+
41
+
42
+ def test_list_agents(client: TestClient):
43
+ response = client.get("/api/nodes")
44
+
45
+ assert response.status_code == 200
46
+ data = response.json()
47
+ assert "nodes" in data
48
+ assert isinstance(data["nodes"], list)
49
+
50
+
51
+ def test_get_agent_not_found(client: TestClient):
52
+ response = client.get("/api/nodes/non-existent-id")
53
+
54
+ assert response.status_code == 404
55
+ assert "Node not found" in response.json()["detail"]
56
+
57
+
58
+ def test_get_node_detail_includes_runtime_config(client: TestClient):
59
+ assistant_id = _get_assistant_id(client)
60
+ response = client.get(f"/api/nodes/{assistant_id}")
61
+
62
+ assert response.status_code == 200
63
+ data = response.json()
64
+ assert data["id"] == assistant_id
65
+ assert isinstance(data["contacts"], list)
66
+ assert isinstance(data["history"], list)
67
+ assert isinstance(data["tools"], list)
68
+ assert isinstance(data["write_dirs"], list)
69
+ assert isinstance(data["allow_network"], bool)
70
+ assert data["workflow_permissions"] == {
71
+ "allow_network": data["allow_network"],
72
+ "write_dirs": data["write_dirs"],
73
+ }
74
+
75
+
76
+ def test_worker_contacts_follow_output_paths(
77
+ client: TestClient,
78
+ ):
79
+ tab = client.post(
80
+ "/api/workflows",
81
+ json={"title": "Execution", "allow_network": True, "write_dirs": ["/tmp"]},
82
+ ).json()
83
+ worker = client.post(
84
+ f"/api/workflows/{tab['id']}/nodes",
85
+ json={"role_name": "Worker", "name": "Worker"},
86
+ ).json()
87
+ reviewer = client.post(
88
+ f"/api/workflows/{tab['id']}/nodes",
89
+ json={"role_name": "Worker", "name": "Reviewer"},
90
+ ).json()
91
+
92
+ detail_without_edge = client.get(f"/api/nodes/{worker['id']}")
93
+
94
+ assert detail_without_edge.status_code == 200
95
+ worker_detail = detail_without_edge.json()
96
+ assert worker_detail["contacts"] == []
97
+ assert worker_detail["allow_network"] is True
98
+ assert worker_detail["write_dirs"] == ["/tmp"]
99
+ assert worker_detail["workflow_permissions"] == {
100
+ "allow_network": True,
101
+ "write_dirs": ["/tmp"],
102
+ }
103
+ leader_without_edge = client.get(f"/api/nodes/{tab['leader_id']}")
104
+ assert leader_without_edge.status_code == 200
105
+ assert any(
106
+ contact["id"] == worker["id"]
107
+ for contact in leader_without_edge.json()["contacts"]
108
+ )
109
+
110
+ edge_response = client.post(
111
+ f"/api/workflows/{tab['id']}/edges",
112
+ json={
113
+ "from_node_id": worker["id"],
114
+ "to_node_id": reviewer["id"],
115
+ },
116
+ )
117
+
118
+ assert edge_response.status_code == 200
119
+ edge = edge_response.json()
120
+ worker_with_edge = client.get(f"/api/nodes/{worker['id']}").json()
121
+ assert worker_with_edge["contacts"] == [
122
+ {
123
+ "id": reviewer["id"],
124
+ "target_id": reviewer["id"],
125
+ "node_type": "agent",
126
+ "role_name": "Worker",
127
+ "name": "Reviewer",
128
+ "state": "idle",
129
+ "is_leader": False,
130
+ "from_output_port_key": "out",
131
+ "to_input_port_key": "in",
132
+ "port_type": "parts",
133
+ "edge_id": edge["id"],
134
+ }
135
+ ]
136
+
137
+
138
+ def test_direct_node_message_api_is_not_available(client: TestClient):
139
+ assert not any(
140
+ getattr(route, "path", None) == "/api/nodes/{node_id}/message"
141
+ for route in nodes_router.routes
142
+ )
143
+
144
+
145
+ def test_only_assistant_node_exists_at_startup(client: TestClient):
146
+ list_response = client.get("/api/nodes")
147
+
148
+ assert list_response.status_code == 200
149
+ nodes = list_response.json()["nodes"]
150
+ assert len(nodes) == 1
151
+ UUID(nodes[0]["id"])
152
+ assert nodes[0]["node_type"] == "assistant"
153
+ assert nodes[0]["name"] == "Assistant"
154
+ assert nodes[0]["role_name"] == "Steward"
155
+
156
+
157
+ def test_get_assistant_detail_includes_tools_and_permissions(client: TestClient):
158
+ assistant_id = _get_assistant_id(client)
159
+ response = client.get(f"/api/nodes/{assistant_id}")
160
+
161
+ assert response.status_code == 200
162
+ data = response.json()
163
+ assert data["id"] == assistant_id
164
+ assert set(data["tools"]) == set(MINIMUM_TOOLS) | set(STEWARD_ROLE_INCLUDED_TOOLS)
165
+ assert data["write_dirs"] == [os.getcwd()]
166
+ assert data["allow_network"] is True
167
+ assert data["workflow_permissions"] == {
168
+ "allow_network": True,
169
+ "write_dirs": [os.getcwd()],
170
+ }
171
+
172
+
173
+ def test_get_node_detail_keeps_state_out_of_history(client: TestClient):
174
+ assistant_id = _get_assistant_id(client)
175
+ assistant = registry.get(assistant_id)
176
+ assert assistant is not None
177
+ assistant.set_state(AgentState.RUNNING, "processing")
178
+
179
+ response = client.get(f"/api/nodes/{assistant_id}")
180
+
181
+ assert response.status_code == 200
182
+ data = response.json()
183
+ assert data["state"] == "running"
184
+ assert all(entry["type"] != "StateEntry" for entry in data["history"])
185
+
186
+
187
+ def test_assistant_cannot_be_terminated_via_nodes_api(client: TestClient):
188
+ assistant_id = _get_assistant_id(client)
189
+ response = client.post(f"/api/nodes/{assistant_id}/terminate")
190
+
191
+ assert response.status_code == 400
192
+ assert response.json() == {"detail": "Cannot terminate assistant"}
193
+
194
+
195
+ def test_tab_leader_cannot_be_terminated_directly(client: TestClient):
196
+ created_tab = client.post(
197
+ "/api/workflows",
198
+ json={"title": "Execution"},
199
+ ).json()
200
+
201
+ response = client.post(f"/api/nodes/{created_tab['leader_id']}/terminate")
202
+
203
+ assert response.status_code == 400
204
+ assert response.json() == {"detail": "Cannot terminate a workflow Leader directly"}
205
+
206
+
207
+ def test_assistant_retry_is_not_available_via_nodes_api(client: TestClient):
208
+ assistant_id = _get_assistant_id(client)
209
+
210
+ response = client.post(f"/api/nodes/{assistant_id}/messages/msg-1/retry")
211
+
212
+ assert response.status_code == 400
213
+ assert response.json() == {
214
+ "detail": "Use /api/assistant/messages/{message_id}/retry for Assistant retry"
215
+ }
216
+
217
+
218
+ def test_assistant_can_be_interrupted_via_nodes_api_when_running(client: TestClient):
219
+ assistant_id = _get_assistant_id(client)
220
+ assistant = registry.get(assistant_id)
221
+ assert assistant is not None
222
+ assistant.set_state(AgentState.RUNNING, "processing")
223
+
224
+ response = client.post(f"/api/nodes/{assistant_id}/interrupt")
225
+
226
+ assert response.status_code == 200
227
+ assert response.json() == {"status": "interrupting"}
228
+ assert assistant._interrupt_requested.is_set()
229
+
230
+
231
+ def test_assistant_can_be_interrupted_via_nodes_api_when_sleeping(client: TestClient):
232
+ assistant_id = _get_assistant_id(client)
233
+ assistant = registry.get(assistant_id)
234
+ assert assistant is not None
235
+ assistant.set_state(AgentState.SLEEPING, "waiting for reply")
236
+
237
+ response = client.post(f"/api/nodes/{assistant_id}/interrupt")
238
+
239
+ assert response.status_code == 200
240
+ assert response.json() == {"status": "interrupting"}
241
+ assert assistant._interrupt_requested.is_set()
242
+
243
+
244
+ def test_interrupt_ignores_idle_node(client: TestClient):
245
+ assistant_id = _get_assistant_id(client)
246
+ response = client.post(f"/api/nodes/{assistant_id}/interrupt")
247
+
248
+ assert response.status_code == 200
249
+ assert response.json() == {"status": "ignored"}
250
+
251
+
252
+ def test_assistant_chat_can_be_cleared_via_nodes_api(client: TestClient):
253
+ assistant_id = _get_assistant_id(client)
254
+ assistant = registry.get(assistant_id)
255
+ assert assistant is not None
256
+ assistant.history.append(ReceivedMessage(content="Old message", from_id="human"))
257
+ assistant.history.append(AssistantText(content="Old reply"))
258
+
259
+ response = client.post(f"/api/nodes/{assistant_id}/clear-chat")
260
+
261
+ assert response.status_code == 200
262
+ assert response.json() == {"status": "cleared"}
263
+
264
+ detail = client.get(f"/api/nodes/{assistant_id}").json()
265
+ assert not any(
266
+ entry["type"] in {"ReceivedMessage", "AssistantText"}
267
+ for entry in detail["history"]
268
+ )
269
+
270
+
271
+ def test_unknown_slash_input_can_be_sent_directly_to_workflow_leader(
272
+ client: TestClient,
273
+ ):
274
+ tab = client.post(
275
+ "/api/workflows",
276
+ json={"title": "Execution", "allow_network": True},
277
+ ).json()
278
+
279
+ response = client.post(
280
+ f"/api/nodes/{tab['leader_id']}/messages",
281
+ json={"content": "/unknown investigate the failure"},
282
+ )
283
+
284
+ assert response.status_code == 200
285
+ message_id = response.json()["message_id"]
286
+ assert isinstance(message_id, str)
287
+
288
+ history = []
289
+ for _ in range(20):
290
+ detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
291
+ history = detail["history"]
292
+ if any(
293
+ entry["type"] == "ReceivedMessage"
294
+ and entry["from_id"] == "human"
295
+ and entry["message_id"] == message_id
296
+ and entry["content"] == "/unknown investigate the failure"
297
+ for entry in history
298
+ ):
299
+ break
300
+ time.sleep(0.01)
301
+
302
+ assert any(
303
+ entry["type"] == "ReceivedMessage"
304
+ and entry["from_id"] == "human"
305
+ and entry["message_id"] == message_id
306
+ and entry["content"] == "/unknown investigate the failure"
307
+ for entry in history
308
+ )
309
+
310
+
311
+ def test_leader_help_command_returns_visible_command_feedback(client: TestClient):
312
+ tab = client.post(
313
+ "/api/workflows",
314
+ json={"title": "Execution"},
315
+ ).json()
316
+
317
+ response = client.post(
318
+ f"/api/nodes/{tab['leader_id']}/messages",
319
+ json={"content": "/help"},
320
+ )
321
+
322
+ assert response.status_code == 200
323
+ assert response.json() == {
324
+ "status": "command_executed",
325
+ "command_name": "/help",
326
+ }
327
+
328
+ detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
329
+
330
+ assert any(
331
+ entry["type"] == "CommandResultEntry"
332
+ and entry["command_name"] == "/help"
333
+ and "Available commands" in entry["content"]
334
+ and "/compact" in entry["content"]
335
+ for entry in detail["history"]
336
+ )
337
+ assert not any(
338
+ entry["type"] == "ReceivedMessage" and entry.get("content") == "/help"
339
+ for entry in detail["history"]
340
+ )
341
+
342
+
343
+ def test_leader_clear_command_clears_only_workflow_chat(client: TestClient):
344
+ assistant_id = _get_assistant_id(client)
345
+ assistant = registry.get(assistant_id)
346
+ assert assistant is not None
347
+ assistant.history.append(
348
+ ReceivedMessage(content="Assistant stays", from_id="human")
349
+ )
350
+ tab = client.post(
351
+ "/api/workflows",
352
+ json={"title": "Execution"},
353
+ ).json()
354
+ worker = client.post(
355
+ f"/api/workflows/{tab['id']}/nodes",
356
+ json={"role_name": "Worker", "name": "Worker"},
357
+ ).json()
358
+ leader = registry.get(tab["leader_id"])
359
+ assert leader is not None
360
+ leader.history.append(ReceivedMessage(content="Old workflow chat", from_id="human"))
361
+ leader.history.append(AssistantText(content="Old workflow reply"))
362
+
363
+ response = client.post(
364
+ f"/api/nodes/{tab['leader_id']}/messages",
365
+ json={"content": "/clear"},
366
+ )
367
+
368
+ assert response.status_code == 200
369
+ assert response.json() == {
370
+ "status": "command_executed",
371
+ "command_name": "/clear",
372
+ }
373
+
374
+ leader_detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
375
+ assistant_detail = client.get(f"/api/nodes/{assistant_id}").json()
376
+ workflow = client.get(f"/api/workflows/{tab['id']}").json()
377
+
378
+ assert not any(
379
+ entry["type"] in {"ReceivedMessage", "AssistantText"}
380
+ and entry.get("content") in {"Old workflow chat", "Old workflow reply"}
381
+ for entry in leader_detail["history"]
382
+ )
383
+ assert not any(
384
+ entry["type"] == "CommandResultEntry" and entry["command_name"] == "/clear"
385
+ for entry in leader_detail["history"]
386
+ )
387
+ assert any(
388
+ entry["type"] == "ReceivedMessage" and entry.get("content") == "Assistant stays"
389
+ for entry in assistant_detail["history"]
390
+ )
391
+ assert any(node["id"] == worker["id"] for node in workflow["nodes"])
392
+
393
+
394
+ def test_leader_compact_command_keeps_history_and_adds_system_feedback(
395
+ monkeypatch,
396
+ client: TestClient,
397
+ ):
398
+ tab = client.post(
399
+ "/api/workflows",
400
+ json={"title": "Execution", "allow_network": True},
401
+ ).json()
402
+ leader = registry.get(tab["leader_id"])
403
+ assert leader is not None
404
+ leader.history.extend(
405
+ [
406
+ ReceivedMessage(content="Need a concise recap", from_id="human"),
407
+ AssistantText(content="I will summarize the workflow."),
408
+ ]
409
+ )
410
+
411
+ monkeypatch.setattr(
412
+ "flowent.agent.gateway.chat",
413
+ lambda *args, **kwargs: LLMResponse(
414
+ content=(
415
+ "## Current Goal\nShip the shared commands.\n\n"
416
+ "## Active Task Boundary\nKeep it in the workflow chat.\n\n"
417
+ "## Key Constraints\nDo not clear visible history.\n\n"
418
+ "## Confirmed Decisions\nUse shared commands.\n\n"
419
+ "## Open Questions\nNone.\n\n"
420
+ "## Next Actions\nVerify the workflow panel."
421
+ )
422
+ ),
423
+ )
424
+
425
+ response = client.post(
426
+ f"/api/nodes/{tab['leader_id']}/messages",
427
+ json={"content": "/compact workflow command rollout"},
428
+ )
429
+
430
+ assert response.status_code == 200
431
+ assert response.json() == {
432
+ "status": "command_executed",
433
+ "command_name": "/compact",
434
+ }
435
+
436
+ detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
437
+
438
+ assert any(
439
+ entry["type"] == "ReceivedMessage"
440
+ and entry.get("content") == "Need a concise recap"
441
+ for entry in detail["history"]
442
+ )
443
+ assert any(
444
+ entry["type"] == "AssistantText"
445
+ and entry.get("content") == "I will summarize the workflow."
446
+ for entry in detail["history"]
447
+ )
448
+ assert any(
449
+ entry["type"] == "CommandResultEntry"
450
+ and entry["command_name"] == "/compact"
451
+ and entry.get("include_in_context") is False
452
+ and "Compacted this chat for future replies." in entry["content"]
453
+ and "Focus: workflow command rollout" in entry["content"]
454
+ for entry in detail["history"]
455
+ )
456
+
457
+
458
+ def test_leader_image_message_bypasses_conversation_commands(
459
+ client: TestClient,
460
+ monkeypatch,
461
+ ):
462
+ tab = client.post(
463
+ "/api/workflows",
464
+ json={"title": "Execution"},
465
+ ).json()
466
+ leader = registry.get(tab["leader_id"])
467
+ assert leader is not None
468
+ monkeypatch.setattr(leader, "supports_input_image", lambda: True)
469
+
470
+ upload_response = client.post(
471
+ "/api/image-assets",
472
+ files={"file": ("pixel.png", _ONE_PIXEL_PNG, "image/png")},
473
+ )
474
+ assert upload_response.status_code == 200
475
+ asset_id = upload_response.json()["id"]
476
+
477
+ response = client.post(
478
+ f"/api/nodes/{tab['leader_id']}/messages",
479
+ json={
480
+ "parts": [
481
+ {"type": "text", "text": "/help"},
482
+ {
483
+ "type": "image",
484
+ "asset_id": asset_id,
485
+ "mime_type": "image/png",
486
+ "width": 1,
487
+ "height": 1,
488
+ },
489
+ ]
490
+ },
491
+ )
492
+
493
+ assert response.status_code == 200
494
+ assert response.json()["status"] == "sent"
495
+
496
+ detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
497
+
498
+ assert any(
499
+ entry["type"] == "ReceivedMessage"
500
+ and entry["from_id"] == "human"
501
+ and entry["content"] == "/help[image]"
502
+ for entry in detail["history"]
503
+ )
504
+ assert not any(
505
+ entry["type"] == "CommandResultEntry" and entry["command_name"] == "/help"
506
+ for entry in detail["history"]
507
+ )
508
+
509
+
510
+ def test_leader_retry_rewrites_tail_and_reuses_image_parts(monkeypatch, client):
511
+ assistant_id = _get_assistant_id(client)
512
+ tab = client.post(
513
+ "/api/workflows",
514
+ json={"title": "Execution"},
515
+ ).json()
516
+ leader = registry.get(tab["leader_id"])
517
+ assert leader is not None
518
+ queued_messages = []
519
+
520
+ upload_response = client.post(
521
+ "/api/image-assets",
522
+ files={"file": ("pixel.png", _ONE_PIXEL_PNG, "image/png")},
523
+ )
524
+ assert upload_response.status_code == 200
525
+ asset_id = upload_response.json()["id"]
526
+
527
+ leader.history.extend(
528
+ [
529
+ ReceivedMessage(
530
+ content="Initial brief",
531
+ from_id=assistant_id,
532
+ message_id="brief-1",
533
+ ),
534
+ AssistantText(content="Leader summary"),
535
+ ReceivedMessage(
536
+ from_id="human",
537
+ parts=[
538
+ TextPart(text="Retry this leader request"),
539
+ ImagePart(
540
+ asset_id=asset_id,
541
+ mime_type="image/png",
542
+ width=1,
543
+ height=1,
544
+ ),
545
+ ],
546
+ message_id="msg-2",
547
+ ),
548
+ AssistantText(content="Discard this leader reply"),
549
+ ]
550
+ )
551
+
552
+ monkeypatch.setattr(leader, "supports_input_image", lambda: True)
553
+ monkeypatch.setattr(
554
+ leader,
555
+ "enqueue_message",
556
+ lambda message: queued_messages.append(message),
557
+ )
558
+
559
+ response = client.post(f"/api/nodes/{tab['leader_id']}/messages/msg-2/retry")
560
+
561
+ assert response.status_code == 200
562
+ payload = response.json()
563
+ assert payload["status"] == "retried"
564
+ assert payload["message_id"] != "msg-2"
565
+
566
+ detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
567
+
568
+ assert any(
569
+ entry["type"] == "ReceivedMessage"
570
+ and entry.get("message_id") == "brief-1"
571
+ and entry.get("from_id") == assistant_id
572
+ for entry in detail["history"]
573
+ )
574
+ assert not any(
575
+ entry["type"] == "ReceivedMessage" and entry.get("message_id") == "msg-2"
576
+ for entry in detail["history"]
577
+ )
578
+ assert not any(
579
+ entry["type"] == "AssistantText"
580
+ and entry.get("content") == "Discard this leader reply"
581
+ for entry in detail["history"]
582
+ )
583
+ assert any(
584
+ entry["type"] == "ReceivedMessage"
585
+ and entry.get("message_id") == payload["message_id"]
586
+ and entry.get("content") == "Retry this leader request[image]"
587
+ for entry in detail["history"]
588
+ )
589
+ assert len(queued_messages) == 1
590
+ assert queued_messages[0].message_id == payload["message_id"]
591
+ assert queued_messages[0].parts[0].text == "Retry this leader request"
592
+ assert queued_messages[0].parts[1].asset_id == asset_id
593
+
594
+
595
+ def test_leader_retry_rejects_non_human_anchor(client: TestClient):
596
+ assistant_id = _get_assistant_id(client)
597
+ tab = client.post(
598
+ "/api/workflows",
599
+ json={"title": "Execution"},
600
+ ).json()
601
+ leader = registry.get(tab["leader_id"])
602
+ assert leader is not None
603
+ leader.history.append(
604
+ ReceivedMessage(
605
+ content="Initial brief",
606
+ from_id=assistant_id,
607
+ message_id="brief-1",
608
+ )
609
+ )
610
+
611
+ response = client.post(f"/api/nodes/{tab['leader_id']}/messages/brief-1/retry")
612
+
613
+ assert response.status_code == 404
614
+ assert response.json()["detail"] == "Leader human message `brief-1` was not found."
615
+
616
+
617
+ def test_regular_worker_retry_is_not_available_via_nodes_api(client: TestClient):
618
+ tab = client.post(
619
+ "/api/workflows",
620
+ json={"title": "Execution"},
621
+ ).json()
622
+ worker = client.post(
623
+ f"/api/workflows/{tab['id']}/nodes",
624
+ json={"role_name": "Worker", "name": "Worker"},
625
+ ).json()
626
+
627
+ response = client.post(f"/api/nodes/{worker['id']}/messages/msg-1/retry")
628
+
629
+ assert response.status_code == 400
630
+ assert response.json() == {
631
+ "detail": "Only a Workflow Leader can retry chat history"
632
+ }
633
+
634
+
635
+ def test_human_input_cannot_target_regular_worker(client: TestClient):
636
+ tab = client.post(
637
+ "/api/workflows",
638
+ json={"title": "Execution"},
639
+ ).json()
640
+ worker = client.post(
641
+ f"/api/workflows/{tab['id']}/nodes",
642
+ json={"role_name": "Worker", "name": "Worker"},
643
+ ).json()
644
+
645
+ response = client.post(
646
+ f"/api/nodes/{worker['id']}/messages",
647
+ json={"content": "Do the work"},
648
+ )
649
+
650
+ assert response.status_code == 400
651
+ assert response.json()["detail"] == (
652
+ "Human input can only target Assistant or a Workflow Leader"
653
+ )
654
+
655
+
656
+ def test_browser_cannot_spoof_non_human_sender_for_node_messages(client: TestClient):
657
+ tab = client.post(
658
+ "/api/workflows",
659
+ json={"title": "Execution"},
660
+ ).json()
661
+ worker = client.post(
662
+ f"/api/workflows/{tab['id']}/nodes",
663
+ json={"role_name": "Worker", "name": "Worker"},
664
+ ).json()
665
+
666
+ response = client.post(
667
+ f"/api/nodes/{worker['id']}/messages",
668
+ json={"content": "Do the work", "from_id": "assistant"},
669
+ )
670
+
671
+ assert response.status_code == 400
672
+ assert response.json()["detail"] == (
673
+ "Web UI node messages must originate from `human`"
674
+ )
675
+
676
+
677
+ def test_leader_message_supports_structured_parts_and_image_validation(
678
+ monkeypatch, client: TestClient
679
+ ):
680
+ tab = client.post(
681
+ "/api/workflows",
682
+ json={"title": "Execution"},
683
+ ).json()
684
+ leader = registry.get(tab["leader_id"])
685
+ assert leader is not None
686
+
687
+ upload_response = client.post(
688
+ "/api/image-assets",
689
+ files={"file": ("pixel.png", _ONE_PIXEL_PNG, "image/png")},
690
+ )
691
+ assert upload_response.status_code == 200
692
+ asset_id = upload_response.json()["id"]
693
+
694
+ monkeypatch.setattr(leader, "supports_input_image", lambda: True)
695
+
696
+ response = client.post(
697
+ f"/api/nodes/{tab['leader_id']}/messages",
698
+ json={
699
+ "parts": [
700
+ {"type": "text", "text": "Inspect this screenshot"},
701
+ {
702
+ "type": "image",
703
+ "asset_id": asset_id,
704
+ "mime_type": "image/png",
705
+ "width": 1,
706
+ "height": 1,
707
+ "alt": "Pixel",
708
+ },
709
+ ]
710
+ },
711
+ )
712
+
713
+ assert response.status_code == 200
714
+ detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
715
+ entry = next(
716
+ history_entry
717
+ for history_entry in detail["history"]
718
+ if history_entry["type"] == "ReceivedMessage"
719
+ and history_entry["message_id"] == response.json()["message_id"]
720
+ )
721
+ assert entry["parts"][0]["text"] == "Inspect this screenshot"
722
+ assert entry["parts"][1]["asset_id"] == asset_id