flowent 0.0.7 → 0.0.11

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 (387) hide show
  1. package/README.md +0 -3
  2. package/backend/README.md +0 -3
  3. package/backend/pyproject.toml +2 -8
  4. package/backend/src/flowent/__init__.py +6 -2
  5. package/backend/src/flowent/__pycache__/__init__.cpython-313.pyc +0 -0
  6. package/backend/src/flowent/__pycache__/agent.cpython-313.pyc +0 -0
  7. package/backend/src/flowent/__pycache__/context.cpython-313.pyc +0 -0
  8. package/backend/src/flowent/__pycache__/llm.cpython-313.pyc +0 -0
  9. package/backend/src/flowent/__pycache__/logging.cpython-313.pyc +0 -0
  10. package/backend/src/flowent/__pycache__/main.cpython-313.pyc +0 -0
  11. package/backend/src/flowent/__pycache__/patch.cpython-313.pyc +0 -0
  12. package/backend/src/flowent/__pycache__/paths.cpython-313.pyc +0 -0
  13. package/backend/src/flowent/__pycache__/sandbox.cpython-313.pyc +0 -0
  14. package/backend/src/flowent/__pycache__/storage.cpython-313.pyc +0 -0
  15. package/backend/src/flowent/__pycache__/tools.cpython-313.pyc +0 -0
  16. package/backend/src/flowent/agent.py +213 -3173
  17. package/backend/src/flowent/cli.py +19 -24
  18. package/backend/src/flowent/context.py +127 -0
  19. package/backend/src/flowent/llm.py +256 -0
  20. package/backend/src/flowent/logging.py +170 -129
  21. package/backend/src/flowent/main.py +321 -70
  22. package/backend/src/flowent/patch.py +182 -0
  23. package/backend/src/flowent/paths.py +11 -0
  24. package/backend/src/flowent/sandbox.py +214 -40
  25. package/backend/src/flowent/static/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
  26. package/backend/src/flowent/static/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
  27. package/backend/src/flowent/static/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
  28. package/backend/src/flowent/static/assets/index-C76K95ty.js +81 -0
  29. package/backend/src/flowent/static/assets/index-iUMNKvlU.css +2 -0
  30. package/backend/src/flowent/static/flowent.png +0 -0
  31. package/backend/src/flowent/static/index.html +5 -25
  32. package/backend/src/flowent/storage.py +302 -0
  33. package/backend/src/flowent/tools.py +376 -0
  34. package/backend/tests/__pycache__/test_agent_tools.cpython-313-pytest-9.0.3.pyc +0 -0
  35. package/backend/tests/__pycache__/test_health.cpython-313-pytest-9.0.3.pyc +0 -0
  36. package/backend/tests/__pycache__/test_llm_providers.cpython-313-pytest-9.0.3.pyc +0 -0
  37. package/backend/tests/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
  38. package/backend/tests/__pycache__/test_persistence.cpython-313-pytest-9.0.3.pyc +0 -0
  39. package/backend/tests/__pycache__/test_workspace_chat.cpython-313-pytest-9.0.3.pyc +0 -0
  40. package/backend/tests/test_agent_tools.py +477 -0
  41. package/backend/tests/test_health.py +12 -0
  42. package/backend/tests/test_llm_providers.py +113 -0
  43. package/backend/tests/test_logging.py +182 -0
  44. package/backend/tests/test_persistence.py +125 -0
  45. package/backend/tests/test_workspace_chat.py +578 -0
  46. package/backend/uv.lock +803 -99
  47. package/dist/frontend/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
  48. package/dist/frontend/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
  49. package/dist/frontend/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
  50. package/dist/frontend/assets/index-C76K95ty.js +81 -0
  51. package/dist/frontend/assets/index-iUMNKvlU.css +2 -0
  52. package/dist/frontend/flowent.png +0 -0
  53. package/dist/frontend/index.html +5 -25
  54. package/package.json +1 -2
  55. package/backend/src/flowent/__pycache__/_version.cpython-313.pyc +0 -0
  56. package/backend/src/flowent/__pycache__/access.cpython-313.pyc +0 -0
  57. package/backend/src/flowent/__pycache__/assistant_commands.cpython-313.pyc +0 -0
  58. package/backend/src/flowent/__pycache__/cli.cpython-313.pyc +0 -0
  59. package/backend/src/flowent/__pycache__/config.cpython-313.pyc +0 -0
  60. package/backend/src/flowent/__pycache__/events.cpython-313.pyc +0 -0
  61. package/backend/src/flowent/__pycache__/graph_runtime.cpython-313.pyc +0 -0
  62. package/backend/src/flowent/__pycache__/graph_service.cpython-313.pyc +0 -0
  63. package/backend/src/flowent/__pycache__/image_assets.cpython-313.pyc +0 -0
  64. package/backend/src/flowent/__pycache__/model_metadata.cpython-313.pyc +0 -0
  65. package/backend/src/flowent/__pycache__/network.cpython-313.pyc +0 -0
  66. package/backend/src/flowent/__pycache__/observability_service.cpython-313.pyc +0 -0
  67. package/backend/src/flowent/__pycache__/registry.cpython-313.pyc +0 -0
  68. package/backend/src/flowent/__pycache__/role_management.cpython-313.pyc +0 -0
  69. package/backend/src/flowent/__pycache__/runtime.cpython-313.pyc +0 -0
  70. package/backend/src/flowent/__pycache__/security.cpython-313.pyc +0 -0
  71. package/backend/src/flowent/__pycache__/settings.cpython-313.pyc +0 -0
  72. package/backend/src/flowent/__pycache__/settings_management.cpython-313.pyc +0 -0
  73. package/backend/src/flowent/__pycache__/state_db.cpython-313.pyc +0 -0
  74. package/backend/src/flowent/__pycache__/workspace_store.cpython-313.pyc +0 -0
  75. package/backend/src/flowent/access.py +0 -247
  76. package/backend/src/flowent/assistant_commands.py +0 -115
  77. package/backend/src/flowent/channels/__init__.py +0 -3
  78. package/backend/src/flowent/channels/__pycache__/__init__.cpython-313.pyc +0 -0
  79. package/backend/src/flowent/channels/__pycache__/telegram.cpython-313.pyc +0 -0
  80. package/backend/src/flowent/channels/telegram.py +0 -615
  81. package/backend/src/flowent/config.py +0 -14
  82. package/backend/src/flowent/dev.py +0 -3
  83. package/backend/src/flowent/events.py +0 -157
  84. package/backend/src/flowent/graph_runtime.py +0 -60
  85. package/backend/src/flowent/graph_service.py +0 -2401
  86. package/backend/src/flowent/image_assets.py +0 -356
  87. package/backend/src/flowent/model_metadata.py +0 -102
  88. package/backend/src/flowent/models/__init__.py +0 -125
  89. package/backend/src/flowent/models/__pycache__/__init__.cpython-313.pyc +0 -0
  90. package/backend/src/flowent/models/__pycache__/agent.cpython-313.pyc +0 -0
  91. package/backend/src/flowent/models/__pycache__/base.cpython-313.pyc +0 -0
  92. package/backend/src/flowent/models/__pycache__/blueprint.cpython-313.pyc +0 -0
  93. package/backend/src/flowent/models/__pycache__/content.cpython-313.pyc +0 -0
  94. package/backend/src/flowent/models/__pycache__/delta.cpython-313.pyc +0 -0
  95. package/backend/src/flowent/models/__pycache__/event.cpython-313.pyc +0 -0
  96. package/backend/src/flowent/models/__pycache__/graph.cpython-313.pyc +0 -0
  97. package/backend/src/flowent/models/__pycache__/history.cpython-313.pyc +0 -0
  98. package/backend/src/flowent/models/__pycache__/llm.cpython-313.pyc +0 -0
  99. package/backend/src/flowent/models/__pycache__/message.cpython-313.pyc +0 -0
  100. package/backend/src/flowent/models/__pycache__/tab.cpython-313.pyc +0 -0
  101. package/backend/src/flowent/models/__pycache__/todo.cpython-313.pyc +0 -0
  102. package/backend/src/flowent/models/agent.py +0 -34
  103. package/backend/src/flowent/models/base.py +0 -24
  104. package/backend/src/flowent/models/blueprint.py +0 -176
  105. package/backend/src/flowent/models/content.py +0 -164
  106. package/backend/src/flowent/models/delta.py +0 -44
  107. package/backend/src/flowent/models/event.py +0 -51
  108. package/backend/src/flowent/models/graph.py +0 -472
  109. package/backend/src/flowent/models/history.py +0 -272
  110. package/backend/src/flowent/models/llm.py +0 -62
  111. package/backend/src/flowent/models/message.py +0 -33
  112. package/backend/src/flowent/models/tab.py +0 -85
  113. package/backend/src/flowent/models/todo.py +0 -10
  114. package/backend/src/flowent/network.py +0 -146
  115. package/backend/src/flowent/observability_service.py +0 -218
  116. package/backend/src/flowent/prompts/__init__.py +0 -67
  117. package/backend/src/flowent/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  118. package/backend/src/flowent/prompts/__pycache__/common.cpython-313.pyc +0 -0
  119. package/backend/src/flowent/prompts/__pycache__/steward.cpython-313.pyc +0 -0
  120. package/backend/src/flowent/prompts/common.py +0 -250
  121. package/backend/src/flowent/prompts/steward.py +0 -64
  122. package/backend/src/flowent/providers/__init__.py +0 -23
  123. package/backend/src/flowent/providers/__pycache__/__init__.cpython-313.pyc +0 -0
  124. package/backend/src/flowent/providers/__pycache__/anthropic.cpython-313.pyc +0 -0
  125. package/backend/src/flowent/providers/__pycache__/base_url.cpython-313.pyc +0 -0
  126. package/backend/src/flowent/providers/__pycache__/configuration.cpython-313.pyc +0 -0
  127. package/backend/src/flowent/providers/__pycache__/content.cpython-313.pyc +0 -0
  128. package/backend/src/flowent/providers/__pycache__/errors.cpython-313.pyc +0 -0
  129. package/backend/src/flowent/providers/__pycache__/gateway.cpython-313.pyc +0 -0
  130. package/backend/src/flowent/providers/__pycache__/headers.cpython-313.pyc +0 -0
  131. package/backend/src/flowent/providers/__pycache__/management.cpython-313.pyc +0 -0
  132. package/backend/src/flowent/providers/__pycache__/openai.cpython-313.pyc +0 -0
  133. package/backend/src/flowent/providers/__pycache__/openai_responses.cpython-313.pyc +0 -0
  134. package/backend/src/flowent/providers/__pycache__/registry.cpython-313.pyc +0 -0
  135. package/backend/src/flowent/providers/__pycache__/sse.cpython-313.pyc +0 -0
  136. package/backend/src/flowent/providers/__pycache__/thinking.cpython-313.pyc +0 -0
  137. package/backend/src/flowent/providers/anthropic.py +0 -468
  138. package/backend/src/flowent/providers/base_url.py +0 -60
  139. package/backend/src/flowent/providers/configuration.py +0 -189
  140. package/backend/src/flowent/providers/content.py +0 -122
  141. package/backend/src/flowent/providers/errors.py +0 -223
  142. package/backend/src/flowent/providers/gateway.py +0 -169
  143. package/backend/src/flowent/providers/gemini.py +0 -447
  144. package/backend/src/flowent/providers/headers.py +0 -20
  145. package/backend/src/flowent/providers/management.py +0 -96
  146. package/backend/src/flowent/providers/ollama.py +0 -293
  147. package/backend/src/flowent/providers/openai.py +0 -422
  148. package/backend/src/flowent/providers/openai_responses.py +0 -655
  149. package/backend/src/flowent/providers/registry.py +0 -144
  150. package/backend/src/flowent/providers/sse.py +0 -31
  151. package/backend/src/flowent/providers/thinking.py +0 -79
  152. package/backend/src/flowent/registry.py +0 -73
  153. package/backend/src/flowent/role_management.py +0 -270
  154. package/backend/src/flowent/routes/__init__.py +0 -26
  155. package/backend/src/flowent/routes/__pycache__/__init__.cpython-313.pyc +0 -0
  156. package/backend/src/flowent/routes/__pycache__/access.cpython-313.pyc +0 -0
  157. package/backend/src/flowent/routes/__pycache__/assistant.cpython-313.pyc +0 -0
  158. package/backend/src/flowent/routes/__pycache__/image_assets.cpython-313.pyc +0 -0
  159. package/backend/src/flowent/routes/__pycache__/meta.cpython-313.pyc +0 -0
  160. package/backend/src/flowent/routes/__pycache__/nodes.cpython-313.pyc +0 -0
  161. package/backend/src/flowent/routes/__pycache__/prompts.cpython-313.pyc +0 -0
  162. package/backend/src/flowent/routes/__pycache__/providers_route.cpython-313.pyc +0 -0
  163. package/backend/src/flowent/routes/__pycache__/roles.cpython-313.pyc +0 -0
  164. package/backend/src/flowent/routes/__pycache__/settings.cpython-313.pyc +0 -0
  165. package/backend/src/flowent/routes/__pycache__/tabs.cpython-313.pyc +0 -0
  166. package/backend/src/flowent/routes/__pycache__/ws.cpython-313.pyc +0 -0
  167. package/backend/src/flowent/routes/access.py +0 -48
  168. package/backend/src/flowent/routes/assistant.py +0 -158
  169. package/backend/src/flowent/routes/image_assets.py +0 -33
  170. package/backend/src/flowent/routes/meta.py +0 -28
  171. package/backend/src/flowent/routes/nodes.py +0 -423
  172. package/backend/src/flowent/routes/prompts.py +0 -46
  173. package/backend/src/flowent/routes/providers_route.py +0 -365
  174. package/backend/src/flowent/routes/roles.py +0 -207
  175. package/backend/src/flowent/routes/settings.py +0 -379
  176. package/backend/src/flowent/routes/tabs.py +0 -298
  177. package/backend/src/flowent/routes/ws.py +0 -33
  178. package/backend/src/flowent/runtime.py +0 -160
  179. package/backend/src/flowent/security.py +0 -37
  180. package/backend/src/flowent/settings.py +0 -2112
  181. package/backend/src/flowent/settings_management.py +0 -394
  182. package/backend/src/flowent/state_db.py +0 -108
  183. package/backend/src/flowent/static/assets/AssistantPage-BW7XAd9I.js +0 -1
  184. package/backend/src/flowent/static/assets/ChannelsPage-tCJHgt6m.js +0 -1
  185. package/backend/src/flowent/static/assets/PageScaffold-f6g2l7XN.js +0 -1
  186. package/backend/src/flowent/static/assets/PromptsPage-C3Sxn2D7.js +0 -1
  187. package/backend/src/flowent/static/assets/ProvidersPage-BfmdXmNt.js +0 -3
  188. package/backend/src/flowent/static/assets/RolesPage-DET8wO4r.js +0 -1
  189. package/backend/src/flowent/static/assets/SettingsPage-D-g3deMm.js +0 -3
  190. package/backend/src/flowent/static/assets/ToolsPage-CDmtE2g4.js +0 -1
  191. package/backend/src/flowent/static/assets/WorkspacePage-AZsJ0sD0.js +0 -3
  192. package/backend/src/flowent/static/assets/WorkspacePanels-CteCjolX.js +0 -1
  193. package/backend/src/flowent/static/assets/alert-dialog-Duorp_S-.js +0 -1
  194. package/backend/src/flowent/static/assets/dialog-C3ixjGjN.js +0 -1
  195. package/backend/src/flowent/static/assets/elk-worker.min-C9JGDOE-.js +0 -6312
  196. package/backend/src/flowent/static/assets/graph-vendor-CHpVij2M.css +0 -1
  197. package/backend/src/flowent/static/assets/graph-vendor-DRq_-6fV.js +0 -7
  198. package/backend/src/flowent/static/assets/index--o_0fv0N.css +0 -1
  199. package/backend/src/flowent/static/assets/index-C9HuekJm.js +0 -10
  200. package/backend/src/flowent/static/assets/layout.worker-jMHqAFbP.js +0 -24
  201. package/backend/src/flowent/static/assets/markdown-vendor-C9RtvaJh.js +0 -29
  202. package/backend/src/flowent/static/assets/modelParams-DmnF2hwR.js +0 -1
  203. package/backend/src/flowent/static/assets/providerTypes-DT3Ahwl_.js +0 -1
  204. package/backend/src/flowent/static/assets/react-vendor-mEs_JJxa.js +0 -9
  205. package/backend/src/flowent/static/assets/roles-CuRT_chR.js +0 -1
  206. package/backend/src/flowent/static/assets/rolldown-runtime-BYbx6iT9.js +0 -1
  207. package/backend/src/flowent/static/assets/select-DCfeNu-F.js +0 -1
  208. package/backend/src/flowent/static/assets/surface-pWwG5ogx.js +0 -1
  209. package/backend/src/flowent/static/assets/ui-vendor-C5pJa8N7.js +0 -51
  210. package/backend/src/flowent/static/assets/useAppRoute-FgSHBKhV.js +0 -1
  211. package/backend/src/flowent/static/favicon.svg +0 -4
  212. package/backend/src/flowent/tools/__init__.py +0 -176
  213. package/backend/src/flowent/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  214. package/backend/src/flowent/tools/__pycache__/connect.cpython-313.pyc +0 -0
  215. package/backend/src/flowent/tools/__pycache__/contacts.cpython-313.pyc +0 -0
  216. package/backend/src/flowent/tools/__pycache__/create_agent.cpython-313.pyc +0 -0
  217. package/backend/src/flowent/tools/__pycache__/create_tab.cpython-313.pyc +0 -0
  218. package/backend/src/flowent/tools/__pycache__/delete_tab.cpython-313.pyc +0 -0
  219. package/backend/src/flowent/tools/__pycache__/edit.cpython-313.pyc +0 -0
  220. package/backend/src/flowent/tools/__pycache__/exec.cpython-313.pyc +0 -0
  221. package/backend/src/flowent/tools/__pycache__/fetch.cpython-313.pyc +0 -0
  222. package/backend/src/flowent/tools/__pycache__/idle.cpython-313.pyc +0 -0
  223. package/backend/src/flowent/tools/__pycache__/list_roles.cpython-313.pyc +0 -0
  224. package/backend/src/flowent/tools/__pycache__/list_tabs.cpython-313.pyc +0 -0
  225. package/backend/src/flowent/tools/__pycache__/list_tools.cpython-313.pyc +0 -0
  226. package/backend/src/flowent/tools/__pycache__/manage_prompts.cpython-313.pyc +0 -0
  227. package/backend/src/flowent/tools/__pycache__/manage_providers.cpython-313.pyc +0 -0
  228. package/backend/src/flowent/tools/__pycache__/manage_roles.cpython-313.pyc +0 -0
  229. package/backend/src/flowent/tools/__pycache__/manage_settings.cpython-313.pyc +0 -0
  230. package/backend/src/flowent/tools/__pycache__/read.cpython-313.pyc +0 -0
  231. package/backend/src/flowent/tools/__pycache__/send.cpython-313.pyc +0 -0
  232. package/backend/src/flowent/tools/__pycache__/set_permissions.cpython-313.pyc +0 -0
  233. package/backend/src/flowent/tools/__pycache__/sleep.cpython-313.pyc +0 -0
  234. package/backend/src/flowent/tools/__pycache__/todo.cpython-313.pyc +0 -0
  235. package/backend/src/flowent/tools/connect.py +0 -100
  236. package/backend/src/flowent/tools/contacts.py +0 -22
  237. package/backend/src/flowent/tools/create_agent.py +0 -191
  238. package/backend/src/flowent/tools/create_tab.py +0 -61
  239. package/backend/src/flowent/tools/delete_tab.py +0 -39
  240. package/backend/src/flowent/tools/edit.py +0 -142
  241. package/backend/src/flowent/tools/exec.py +0 -118
  242. package/backend/src/flowent/tools/fetch.py +0 -85
  243. package/backend/src/flowent/tools/idle.py +0 -27
  244. package/backend/src/flowent/tools/list_roles.py +0 -68
  245. package/backend/src/flowent/tools/list_tabs.py +0 -100
  246. package/backend/src/flowent/tools/list_tools.py +0 -28
  247. package/backend/src/flowent/tools/manage_prompts.py +0 -102
  248. package/backend/src/flowent/tools/manage_providers.py +0 -220
  249. package/backend/src/flowent/tools/manage_roles.py +0 -275
  250. package/backend/src/flowent/tools/manage_settings.py +0 -326
  251. package/backend/src/flowent/tools/read.py +0 -152
  252. package/backend/src/flowent/tools/send.py +0 -68
  253. package/backend/src/flowent/tools/set_permissions.py +0 -99
  254. package/backend/src/flowent/tools/sleep.py +0 -41
  255. package/backend/src/flowent/tools/todo.py +0 -51
  256. package/backend/src/flowent/workspace_store.py +0 -479
  257. package/backend/tests/__init__.py +0 -0
  258. package/backend/tests/__pycache__/__init__.cpython-313.pyc +0 -0
  259. package/backend/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  260. package/backend/tests/conftest.py +0 -6
  261. package/backend/tests/integration/api/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  262. package/backend/tests/integration/api/__pycache__/test_access_api.cpython-313-pytest-9.0.3.pyc +0 -0
  263. package/backend/tests/integration/api/__pycache__/test_assistant_api.cpython-313-pytest-9.0.3.pyc +0 -0
  264. package/backend/tests/integration/api/__pycache__/test_frontend_mounting.cpython-313-pytest-9.0.3.pyc +0 -0
  265. package/backend/tests/integration/api/__pycache__/test_meta_api.cpython-313-pytest-9.0.3.pyc +0 -0
  266. package/backend/tests/integration/api/__pycache__/test_nodes_api.cpython-313-pytest-9.0.3.pyc +0 -0
  267. package/backend/tests/integration/api/__pycache__/test_prompts_api.cpython-313-pytest-9.0.3.pyc +0 -0
  268. package/backend/tests/integration/api/__pycache__/test_roles_api.cpython-313-pytest-9.0.3.pyc +0 -0
  269. package/backend/tests/integration/api/__pycache__/test_tabs_api.cpython-313-pytest-9.0.3.pyc +0 -0
  270. package/backend/tests/integration/api/conftest.py +0 -29
  271. package/backend/tests/integration/api/test_access_api.py +0 -182
  272. package/backend/tests/integration/api/test_assistant_api.py +0 -422
  273. package/backend/tests/integration/api/test_frontend_mounting.py +0 -61
  274. package/backend/tests/integration/api/test_meta_api.py +0 -32
  275. package/backend/tests/integration/api/test_nodes_api.py +0 -787
  276. package/backend/tests/integration/api/test_prompts_api.py +0 -47
  277. package/backend/tests/integration/api/test_roles_api.py +0 -228
  278. package/backend/tests/integration/api/test_tabs_api.py +0 -688
  279. package/backend/tests/unit/__pycache__/test_access.cpython-313-pytest-9.0.3.pyc +0 -0
  280. package/backend/tests/unit/__pycache__/test_cli.cpython-313-pytest-9.0.3.pyc +0 -0
  281. package/backend/tests/unit/__pycache__/test_graph_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  282. package/backend/tests/unit/__pycache__/test_network.cpython-313-pytest-9.0.3.pyc +0 -0
  283. package/backend/tests/unit/__pycache__/test_state_sqlite_storage.cpython-313-pytest-9.0.3.pyc +0 -0
  284. package/backend/tests/unit/__pycache__/test_workspace_store.cpython-313-pytest-9.0.3.pyc +0 -0
  285. package/backend/tests/unit/agent/__pycache__/test_agent_public_api.cpython-313-pytest-9.0.3.pyc +0 -0
  286. package/backend/tests/unit/agent/__pycache__/test_agent_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  287. package/backend/tests/unit/agent/test_agent_public_api.py +0 -822
  288. package/backend/tests/unit/agent/test_agent_runtime.py +0 -3088
  289. package/backend/tests/unit/channels/__pycache__/test_telegram_channel.cpython-313-pytest-9.0.3.pyc +0 -0
  290. package/backend/tests/unit/channels/test_telegram_channel.py +0 -552
  291. package/backend/tests/unit/logging/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
  292. package/backend/tests/unit/logging/test_logging.py +0 -132
  293. package/backend/tests/unit/prompts/__pycache__/test_prompts.cpython-313-pytest-9.0.3.pyc +0 -0
  294. package/backend/tests/unit/prompts/test_prompts.py +0 -570
  295. package/backend/tests/unit/providers/__pycache__/test_anthropic_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  296. package/backend/tests/unit/providers/__pycache__/test_errors.cpython-313-pytest-9.0.3.pyc +0 -0
  297. package/backend/tests/unit/providers/__pycache__/test_extract_delta_parts.cpython-313-pytest-9.0.3.pyc +0 -0
  298. package/backend/tests/unit/providers/__pycache__/test_openai_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  299. package/backend/tests/unit/providers/__pycache__/test_openai_responses.cpython-313-pytest-9.0.3.pyc +0 -0
  300. package/backend/tests/unit/providers/__pycache__/test_provider_gateway.cpython-313-pytest-9.0.3.pyc +0 -0
  301. package/backend/tests/unit/providers/__pycache__/test_think_tag_parser.cpython-313-pytest-9.0.3.pyc +0 -0
  302. package/backend/tests/unit/providers/test_anthropic_provider.py +0 -185
  303. package/backend/tests/unit/providers/test_errors.py +0 -68
  304. package/backend/tests/unit/providers/test_extract_delta_parts.py +0 -22
  305. package/backend/tests/unit/providers/test_openai_provider.py +0 -139
  306. package/backend/tests/unit/providers/test_openai_responses.py +0 -402
  307. package/backend/tests/unit/providers/test_provider_gateway.py +0 -359
  308. package/backend/tests/unit/providers/test_think_tag_parser.py +0 -36
  309. package/backend/tests/unit/routes/__pycache__/test_prompts_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  310. package/backend/tests/unit/routes/__pycache__/test_providers_route.cpython-313-pytest-9.0.3.pyc +0 -0
  311. package/backend/tests/unit/routes/__pycache__/test_roles_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  312. package/backend/tests/unit/routes/__pycache__/test_settings_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  313. package/backend/tests/unit/routes/test_prompts_routes.py +0 -82
  314. package/backend/tests/unit/routes/test_providers_route.py +0 -370
  315. package/backend/tests/unit/routes/test_roles_routes.py +0 -539
  316. package/backend/tests/unit/routes/test_settings_routes.py +0 -1123
  317. package/backend/tests/unit/runtime/__pycache__/test_bootstrap_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  318. package/backend/tests/unit/runtime/test_bootstrap_runtime.py +0 -1002
  319. package/backend/tests/unit/sandbox/__pycache__/test_sandbox_tools.cpython-313-pytest-9.0.3.pyc +0 -0
  320. package/backend/tests/unit/sandbox/test_sandbox_tools.py +0 -78
  321. package/backend/tests/unit/security/__pycache__/test_security.cpython-313-pytest-9.0.3.pyc +0 -0
  322. package/backend/tests/unit/security/test_security.py +0 -124
  323. package/backend/tests/unit/settings/__pycache__/test_settings_roles.cpython-313-pytest-9.0.3.pyc +0 -0
  324. package/backend/tests/unit/settings/test_settings_roles.py +0 -703
  325. package/backend/tests/unit/test_access.py +0 -45
  326. package/backend/tests/unit/test_cli.py +0 -102
  327. package/backend/tests/unit/test_graph_runtime.py +0 -72
  328. package/backend/tests/unit/test_network.py +0 -51
  329. package/backend/tests/unit/test_state_sqlite_storage.py +0 -87
  330. package/backend/tests/unit/test_workspace_store.py +0 -228
  331. package/backend/tests/unit/tools/__pycache__/test_connect_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  332. package/backend/tests/unit/tools/__pycache__/test_create_agent_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  333. package/backend/tests/unit/tools/__pycache__/test_delete_tab_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  334. package/backend/tests/unit/tools/__pycache__/test_edit_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  335. package/backend/tests/unit/tools/__pycache__/test_exec_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  336. package/backend/tests/unit/tools/__pycache__/test_fetch_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  337. package/backend/tests/unit/tools/__pycache__/test_manage_prompts_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  338. package/backend/tests/unit/tools/__pycache__/test_manage_providers_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  339. package/backend/tests/unit/tools/__pycache__/test_manage_roles_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  340. package/backend/tests/unit/tools/__pycache__/test_manage_settings_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  341. package/backend/tests/unit/tools/__pycache__/test_read_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  342. package/backend/tests/unit/tools/__pycache__/test_set_permissions_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  343. package/backend/tests/unit/tools/__pycache__/test_todo_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  344. package/backend/tests/unit/tools/__pycache__/test_tool_registry.cpython-313-pytest-9.0.3.pyc +0 -0
  345. package/backend/tests/unit/tools/test_connect_tool.py +0 -228
  346. package/backend/tests/unit/tools/test_create_agent_tool.py +0 -404
  347. package/backend/tests/unit/tools/test_delete_tab_tool.py +0 -116
  348. package/backend/tests/unit/tools/test_edit_tool.py +0 -115
  349. package/backend/tests/unit/tools/test_exec_tool.py +0 -81
  350. package/backend/tests/unit/tools/test_fetch_tool.py +0 -65
  351. package/backend/tests/unit/tools/test_manage_prompts_tool.py +0 -92
  352. package/backend/tests/unit/tools/test_manage_providers_tool.py +0 -460
  353. package/backend/tests/unit/tools/test_manage_roles_tool.py +0 -411
  354. package/backend/tests/unit/tools/test_manage_settings_tool.py +0 -611
  355. package/backend/tests/unit/tools/test_read_tool.py +0 -33
  356. package/backend/tests/unit/tools/test_set_permissions_tool.py +0 -595
  357. package/backend/tests/unit/tools/test_todo_tool.py +0 -37
  358. package/backend/tests/unit/tools/test_tool_registry.py +0 -199
  359. package/dist/frontend/assets/AssistantPage-BW7XAd9I.js +0 -1
  360. package/dist/frontend/assets/ChannelsPage-tCJHgt6m.js +0 -1
  361. package/dist/frontend/assets/PageScaffold-f6g2l7XN.js +0 -1
  362. package/dist/frontend/assets/PromptsPage-C3Sxn2D7.js +0 -1
  363. package/dist/frontend/assets/ProvidersPage-BfmdXmNt.js +0 -3
  364. package/dist/frontend/assets/RolesPage-DET8wO4r.js +0 -1
  365. package/dist/frontend/assets/SettingsPage-D-g3deMm.js +0 -3
  366. package/dist/frontend/assets/ToolsPage-CDmtE2g4.js +0 -1
  367. package/dist/frontend/assets/WorkspacePage-AZsJ0sD0.js +0 -3
  368. package/dist/frontend/assets/WorkspacePanels-CteCjolX.js +0 -1
  369. package/dist/frontend/assets/alert-dialog-Duorp_S-.js +0 -1
  370. package/dist/frontend/assets/dialog-C3ixjGjN.js +0 -1
  371. package/dist/frontend/assets/elk-worker.min-C9JGDOE-.js +0 -6312
  372. package/dist/frontend/assets/graph-vendor-CHpVij2M.css +0 -1
  373. package/dist/frontend/assets/graph-vendor-DRq_-6fV.js +0 -7
  374. package/dist/frontend/assets/index--o_0fv0N.css +0 -1
  375. package/dist/frontend/assets/index-C9HuekJm.js +0 -10
  376. package/dist/frontend/assets/layout.worker-jMHqAFbP.js +0 -24
  377. package/dist/frontend/assets/markdown-vendor-C9RtvaJh.js +0 -29
  378. package/dist/frontend/assets/modelParams-DmnF2hwR.js +0 -1
  379. package/dist/frontend/assets/providerTypes-DT3Ahwl_.js +0 -1
  380. package/dist/frontend/assets/react-vendor-mEs_JJxa.js +0 -9
  381. package/dist/frontend/assets/roles-CuRT_chR.js +0 -1
  382. package/dist/frontend/assets/rolldown-runtime-BYbx6iT9.js +0 -1
  383. package/dist/frontend/assets/select-DCfeNu-F.js +0 -1
  384. package/dist/frontend/assets/surface-pWwG5ogx.js +0 -1
  385. package/dist/frontend/assets/ui-vendor-C5pJa8N7.js +0 -51
  386. package/dist/frontend/assets/useAppRoute-FgSHBKhV.js +0 -1
  387. package/dist/frontend/favicon.svg +0 -4
@@ -1,787 +0,0 @@
1
- import os
2
- import threading
3
- import time
4
- from uuid import UUID
5
-
6
- from fastapi.testclient import TestClient
7
-
8
- from flowent.models import (
9
- AgentState,
10
- AssistantText,
11
- ErrorEntry,
12
- ImagePart,
13
- LLMResponse,
14
- ReceivedMessage,
15
- TextPart,
16
- )
17
- from flowent.providers.errors import LLMProviderError
18
- from flowent.registry import registry
19
- from flowent.settings import STEWARD_ROLE_INCLUDED_TOOLS
20
- from flowent.tools import MINIMUM_TOOLS
21
-
22
- _ONE_PIXEL_PNG = bytes.fromhex(
23
- "89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c489"
24
- "0000000d49444154789c6360000002000154a24f5d0000000049454e44ae426082"
25
- )
26
-
27
-
28
- def _get_assistant_id(client: TestClient) -> str:
29
- response = client.get("/api/assistant")
30
-
31
- assert response.status_code == 200
32
- assistant_id = response.json()["id"]
33
- UUID(assistant_id)
34
- return assistant_id
35
-
36
-
37
- def test_health_check(client: TestClient):
38
- response = client.get("/health")
39
-
40
- assert response.status_code == 200
41
- assert response.json() == {"status": "healthy"}
42
-
43
-
44
- def test_list_agents(client: TestClient):
45
- response = client.get("/api/nodes")
46
-
47
- assert response.status_code == 200
48
- data = response.json()
49
- assert "nodes" in data
50
- assert isinstance(data["nodes"], list)
51
-
52
-
53
- def test_get_agent_not_found(client: TestClient):
54
- response = client.get("/api/nodes/non-existent-id")
55
-
56
- assert response.status_code == 404
57
- assert "Node not found" in response.json()["detail"]
58
-
59
-
60
- def test_get_node_detail_includes_runtime_config(client: TestClient):
61
- assistant_id = _get_assistant_id(client)
62
- response = client.get(f"/api/nodes/{assistant_id}")
63
-
64
- assert response.status_code == 200
65
- data = response.json()
66
- assert data["id"] == assistant_id
67
- assert isinstance(data["contacts"], list)
68
- assert isinstance(data["history"], list)
69
- assert isinstance(data["tools"], list)
70
- assert isinstance(data["write_dirs"], list)
71
- assert isinstance(data["allow_network"], bool)
72
- assert data["workflow_permissions"] == {
73
- "allow_network": data["allow_network"],
74
- "write_dirs": data["write_dirs"],
75
- }
76
-
77
-
78
- def test_worker_contacts_follow_output_paths(
79
- client: TestClient,
80
- ):
81
- tab = client.post(
82
- "/api/workflows",
83
- json={"title": "Execution", "allow_network": True, "write_dirs": ["/tmp"]},
84
- ).json()
85
- worker = client.post(
86
- f"/api/workflows/{tab['id']}/nodes",
87
- json={"role_name": "Worker", "name": "Worker"},
88
- ).json()
89
- reviewer = client.post(
90
- f"/api/workflows/{tab['id']}/nodes",
91
- json={"role_name": "Worker", "name": "Reviewer"},
92
- ).json()
93
-
94
- detail_without_edge = client.get(f"/api/nodes/{worker['id']}")
95
-
96
- assert detail_without_edge.status_code == 200
97
- worker_detail = detail_without_edge.json()
98
- assert worker_detail["contacts"] == []
99
- assert worker_detail["allow_network"] is True
100
- assert worker_detail["write_dirs"] == ["/tmp"]
101
- assert worker_detail["workflow_permissions"] == {
102
- "allow_network": True,
103
- "write_dirs": ["/tmp"],
104
- }
105
- leader_without_edge = client.get(f"/api/nodes/{tab['leader_id']}")
106
- assert leader_without_edge.status_code == 200
107
- assert any(
108
- contact["id"] == worker["id"]
109
- for contact in leader_without_edge.json()["contacts"]
110
- )
111
-
112
- edge_response = client.post(
113
- f"/api/workflows/{tab['id']}/edges",
114
- json={
115
- "from_node_id": worker["id"],
116
- "to_node_id": reviewer["id"],
117
- },
118
- )
119
-
120
- assert edge_response.status_code == 200
121
- edge = edge_response.json()
122
- worker_with_edge = client.get(f"/api/nodes/{worker['id']}").json()
123
- assert worker_with_edge["contacts"] == [
124
- {
125
- "id": reviewer["id"],
126
- "target_id": reviewer["id"],
127
- "node_type": "agent",
128
- "role_name": "Worker",
129
- "name": "Reviewer",
130
- "state": "idle",
131
- "is_leader": False,
132
- "from_output_port_key": "out",
133
- "to_input_port_key": "in",
134
- "port_type": "parts",
135
- "edge_id": edge["id"],
136
- }
137
- ]
138
-
139
-
140
- def test_only_assistant_node_exists_at_startup(client: TestClient):
141
- list_response = client.get("/api/nodes")
142
-
143
- assert list_response.status_code == 200
144
- nodes = list_response.json()["nodes"]
145
- assert len(nodes) == 1
146
- UUID(nodes[0]["id"])
147
- assert nodes[0]["node_type"] == "assistant"
148
- assert nodes[0]["name"] == "Assistant"
149
- assert nodes[0]["role_name"] == "Steward"
150
-
151
-
152
- def test_get_assistant_detail_includes_tools_and_permissions(client: TestClient):
153
- assistant_id = _get_assistant_id(client)
154
- response = client.get(f"/api/nodes/{assistant_id}")
155
-
156
- assert response.status_code == 200
157
- data = response.json()
158
- assert data["id"] == assistant_id
159
- assert set(data["tools"]) == set(MINIMUM_TOOLS) | set(STEWARD_ROLE_INCLUDED_TOOLS)
160
- assert data["write_dirs"] == [os.getcwd()]
161
- assert data["allow_network"] is True
162
- assert data["workflow_permissions"] == {
163
- "allow_network": True,
164
- "write_dirs": [os.getcwd()],
165
- }
166
-
167
-
168
- def test_get_node_detail_keeps_state_out_of_history(client: TestClient):
169
- assistant_id = _get_assistant_id(client)
170
- assistant = registry.get(assistant_id)
171
- assert assistant is not None
172
- assistant.set_state(AgentState.RUNNING, "processing")
173
-
174
- response = client.get(f"/api/nodes/{assistant_id}")
175
-
176
- assert response.status_code == 200
177
- data = response.json()
178
- assert data["state"] == "running"
179
- assert all(entry["type"] != "StateEntry" for entry in data["history"])
180
-
181
-
182
- def test_assistant_cannot_be_terminated_via_nodes_api(client: TestClient):
183
- assistant_id = _get_assistant_id(client)
184
- response = client.post(f"/api/nodes/{assistant_id}/terminate")
185
-
186
- assert response.status_code == 400
187
- assert response.json() == {"detail": "Cannot terminate assistant"}
188
-
189
-
190
- def test_tab_leader_cannot_be_terminated_directly(client: TestClient):
191
- created_tab = client.post(
192
- "/api/workflows",
193
- json={"title": "Execution"},
194
- ).json()
195
-
196
- response = client.post(f"/api/nodes/{created_tab['leader_id']}/terminate")
197
-
198
- assert response.status_code == 400
199
- assert response.json() == {"detail": "Cannot terminate a workflow Leader directly"}
200
-
201
-
202
- def test_assistant_retry_is_not_available_via_nodes_api(client: TestClient):
203
- assistant_id = _get_assistant_id(client)
204
-
205
- response = client.post(f"/api/nodes/{assistant_id}/messages/msg-1/retry")
206
-
207
- assert response.status_code == 400
208
- assert response.json() == {
209
- "detail": "Use /api/assistant/messages/{message_id}/retry for Assistant retry"
210
- }
211
-
212
-
213
- def test_assistant_can_be_interrupted_via_nodes_api_when_running(client: TestClient):
214
- assistant_id = _get_assistant_id(client)
215
- assistant = registry.get(assistant_id)
216
- assert assistant is not None
217
- assistant.set_state(AgentState.RUNNING, "processing")
218
-
219
- response = client.post(f"/api/nodes/{assistant_id}/interrupt")
220
-
221
- assert response.status_code == 200
222
- assert response.json() == {"status": "interrupting"}
223
- assert assistant._interrupt_requested.is_set()
224
-
225
-
226
- def test_assistant_can_be_interrupted_via_nodes_api_when_sleeping(client: TestClient):
227
- assistant_id = _get_assistant_id(client)
228
- assistant = registry.get(assistant_id)
229
- assert assistant is not None
230
- assistant.set_state(AgentState.SLEEPING, "waiting for reply")
231
-
232
- response = client.post(f"/api/nodes/{assistant_id}/interrupt")
233
-
234
- assert response.status_code == 200
235
- assert response.json() == {"status": "interrupting"}
236
- assert assistant._interrupt_requested.is_set()
237
-
238
-
239
- def test_interrupt_ignores_idle_node(client: TestClient):
240
- assistant_id = _get_assistant_id(client)
241
- response = client.post(f"/api/nodes/{assistant_id}/interrupt")
242
-
243
- assert response.status_code == 200
244
- assert response.json() == {"status": "ignored"}
245
-
246
-
247
- def test_assistant_chat_can_be_cleared_via_nodes_api(client: TestClient):
248
- assistant_id = _get_assistant_id(client)
249
- assistant = registry.get(assistant_id)
250
- assert assistant is not None
251
- assistant.history.append(ReceivedMessage(content="Old message", from_id="human"))
252
- assistant.history.append(AssistantText(content="Old reply"))
253
-
254
- response = client.post(f"/api/nodes/{assistant_id}/clear-chat")
255
-
256
- assert response.status_code == 200
257
- assert response.json() == {"status": "cleared"}
258
-
259
- detail = client.get(f"/api/nodes/{assistant_id}").json()
260
- assert not any(
261
- entry["type"] in {"ReceivedMessage", "AssistantText"}
262
- for entry in detail["history"]
263
- )
264
-
265
-
266
- def test_unknown_slash_input_can_be_sent_directly_to_workflow_leader(
267
- client: TestClient,
268
- ):
269
- tab = client.post(
270
- "/api/workflows",
271
- json={"title": "Execution", "allow_network": True},
272
- ).json()
273
-
274
- response = client.post(
275
- f"/api/nodes/{tab['leader_id']}/messages",
276
- json={"content": "/unknown investigate the failure"},
277
- )
278
-
279
- assert response.status_code == 200
280
- message_id = response.json()["message_id"]
281
- assert isinstance(message_id, str)
282
-
283
- history = []
284
- for _ in range(20):
285
- detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
286
- history = detail["history"]
287
- if any(
288
- entry["type"] == "ReceivedMessage"
289
- and entry["from_id"] == "human"
290
- and entry["message_id"] == message_id
291
- and entry["content"] == "/unknown investigate the failure"
292
- for entry in history
293
- ):
294
- break
295
- time.sleep(0.01)
296
-
297
- assert any(
298
- entry["type"] == "ReceivedMessage"
299
- and entry["from_id"] == "human"
300
- and entry["message_id"] == message_id
301
- and entry["content"] == "/unknown investigate the failure"
302
- for entry in history
303
- )
304
-
305
-
306
- def test_leader_accepts_new_message_after_error(monkeypatch, client: TestClient):
307
- tab = client.post(
308
- "/api/workflows",
309
- json={"title": "Execution", "allow_network": True},
310
- ).json()
311
- leader = registry.get(tab["leader_id"])
312
- assert leader is not None
313
- chat_started = threading.Event()
314
- release_chat = threading.Event()
315
-
316
- def fake_chat(**kwargs):
317
- chat_started.set()
318
- release_chat.wait(timeout=2)
319
- raise LLMProviderError(
320
- "LLM API error\nDetail: still invalid",
321
- transient=False,
322
- )
323
-
324
- monkeypatch.setattr("flowent.agent.gateway.chat", fake_chat)
325
- leader.history.append(ErrorEntry(content="LLM API error\nDetail: Invalid key"))
326
- leader.set_state(AgentState.ERROR, "LLM API error")
327
-
328
- try:
329
- response = client.post(
330
- f"/api/nodes/{tab['leader_id']}/messages",
331
- json={"content": "Continue the workflow chat"},
332
- )
333
-
334
- assert response.status_code == 200
335
- message_id = response.json()["message_id"]
336
- assert isinstance(message_id, str)
337
- assert chat_started.wait(timeout=1)
338
-
339
- detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
340
- history = detail["history"]
341
- assert any(
342
- entry["type"] == "ErrorEntry"
343
- and entry["content"] == "LLM API error\nDetail: Invalid key"
344
- for entry in history
345
- )
346
- assert any(
347
- entry["type"] == "ReceivedMessage"
348
- and entry["from_id"] == "human"
349
- and entry["message_id"] == message_id
350
- and entry["content"] == "Continue the workflow chat"
351
- for entry in history
352
- )
353
- assert registry.get(tab["leader_id"]).state == AgentState.RUNNING
354
- finally:
355
- release_chat.set()
356
-
357
-
358
- def test_leader_rejects_message_when_terminated(client: TestClient):
359
- tab = client.post(
360
- "/api/workflows",
361
- json={"title": "Execution"},
362
- ).json()
363
- leader = registry.get(tab["leader_id"])
364
- assert leader is not None
365
- leader.set_state(AgentState.TERMINATED, "done")
366
-
367
- response = client.post(
368
- f"/api/nodes/{tab['leader_id']}/messages",
369
- json={"content": "Continue the workflow chat"},
370
- )
371
-
372
- assert response.status_code == 409
373
- assert response.json()["detail"] == "This workflow chat is no longer available"
374
-
375
-
376
- def test_leader_help_command_returns_visible_command_feedback(client: TestClient):
377
- tab = client.post(
378
- "/api/workflows",
379
- json={"title": "Execution"},
380
- ).json()
381
-
382
- response = client.post(
383
- f"/api/nodes/{tab['leader_id']}/messages",
384
- json={"content": "/help"},
385
- )
386
-
387
- assert response.status_code == 200
388
- assert response.json() == {
389
- "status": "command_executed",
390
- "command_name": "/help",
391
- }
392
-
393
- detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
394
-
395
- assert any(
396
- entry["type"] == "CommandResultEntry"
397
- and entry["command_name"] == "/help"
398
- and "Available commands" in entry["content"]
399
- and "/compact" in entry["content"]
400
- for entry in detail["history"]
401
- )
402
- assert not any(
403
- entry["type"] == "ReceivedMessage" and entry.get("content") == "/help"
404
- for entry in detail["history"]
405
- )
406
-
407
-
408
- def test_leader_clear_command_clears_only_workflow_chat(client: TestClient):
409
- assistant_id = _get_assistant_id(client)
410
- assistant = registry.get(assistant_id)
411
- assert assistant is not None
412
- assistant.history.append(
413
- ReceivedMessage(content="Assistant stays", from_id="human")
414
- )
415
- tab = client.post(
416
- "/api/workflows",
417
- json={"title": "Execution"},
418
- ).json()
419
- worker = client.post(
420
- f"/api/workflows/{tab['id']}/nodes",
421
- json={"role_name": "Worker", "name": "Worker"},
422
- ).json()
423
- leader = registry.get(tab["leader_id"])
424
- assert leader is not None
425
- leader.history.append(ReceivedMessage(content="Old workflow chat", from_id="human"))
426
- leader.history.append(AssistantText(content="Old workflow reply"))
427
-
428
- response = client.post(
429
- f"/api/nodes/{tab['leader_id']}/messages",
430
- json={"content": "/clear"},
431
- )
432
-
433
- assert response.status_code == 200
434
- assert response.json() == {
435
- "status": "command_executed",
436
- "command_name": "/clear",
437
- }
438
-
439
- leader_detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
440
- assistant_detail = client.get(f"/api/nodes/{assistant_id}").json()
441
- workflow = client.get(f"/api/workflows/{tab['id']}").json()
442
-
443
- assert not any(
444
- entry["type"] in {"ReceivedMessage", "AssistantText"}
445
- and entry.get("content") in {"Old workflow chat", "Old workflow reply"}
446
- for entry in leader_detail["history"]
447
- )
448
- assert not any(
449
- entry["type"] == "CommandResultEntry" and entry["command_name"] == "/clear"
450
- for entry in leader_detail["history"]
451
- )
452
- assert any(
453
- entry["type"] == "ReceivedMessage" and entry.get("content") == "Assistant stays"
454
- for entry in assistant_detail["history"]
455
- )
456
- assert any(node["id"] == worker["id"] for node in workflow["nodes"])
457
-
458
-
459
- def test_leader_compact_command_keeps_history_and_adds_system_feedback(
460
- monkeypatch,
461
- client: TestClient,
462
- ):
463
- tab = client.post(
464
- "/api/workflows",
465
- json={"title": "Execution", "allow_network": True},
466
- ).json()
467
- leader = registry.get(tab["leader_id"])
468
- assert leader is not None
469
- leader.history.extend(
470
- [
471
- ReceivedMessage(content="Need a concise recap", from_id="human"),
472
- AssistantText(content="I will summarize the workflow."),
473
- ]
474
- )
475
-
476
- monkeypatch.setattr(
477
- "flowent.agent.gateway.chat",
478
- lambda *args, **kwargs: LLMResponse(
479
- content=(
480
- "## Current Goal\nShip the shared commands.\n\n"
481
- "## Active Task Boundary\nKeep it in the workflow chat.\n\n"
482
- "## Key Constraints\nDo not clear visible history.\n\n"
483
- "## Confirmed Decisions\nUse shared commands.\n\n"
484
- "## Open Questions\nNone.\n\n"
485
- "## Next Actions\nVerify the workflow panel."
486
- )
487
- ),
488
- )
489
-
490
- response = client.post(
491
- f"/api/nodes/{tab['leader_id']}/messages",
492
- json={"content": "/compact workflow command rollout"},
493
- )
494
-
495
- assert response.status_code == 200
496
- assert response.json() == {
497
- "status": "command_executed",
498
- "command_name": "/compact",
499
- }
500
-
501
- detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
502
-
503
- assert any(
504
- entry["type"] == "ReceivedMessage"
505
- and entry.get("content") == "Need a concise recap"
506
- for entry in detail["history"]
507
- )
508
- assert any(
509
- entry["type"] == "AssistantText"
510
- and entry.get("content") == "I will summarize the workflow."
511
- for entry in detail["history"]
512
- )
513
- assert any(
514
- entry["type"] == "CommandResultEntry"
515
- and entry["command_name"] == "/compact"
516
- and entry.get("include_in_context") is False
517
- and "Compacted this chat for future replies." in entry["content"]
518
- and "Focus: workflow command rollout" in entry["content"]
519
- for entry in detail["history"]
520
- )
521
-
522
-
523
- def test_leader_image_message_bypasses_conversation_commands(
524
- client: TestClient,
525
- monkeypatch,
526
- ):
527
- tab = client.post(
528
- "/api/workflows",
529
- json={"title": "Execution"},
530
- ).json()
531
- leader = registry.get(tab["leader_id"])
532
- assert leader is not None
533
- monkeypatch.setattr(leader, "supports_input_image", lambda: True)
534
-
535
- upload_response = client.post(
536
- "/api/image-assets",
537
- files={"file": ("pixel.png", _ONE_PIXEL_PNG, "image/png")},
538
- )
539
- assert upload_response.status_code == 200
540
- asset_id = upload_response.json()["id"]
541
-
542
- response = client.post(
543
- f"/api/nodes/{tab['leader_id']}/messages",
544
- json={
545
- "parts": [
546
- {"type": "text", "text": "/help"},
547
- {
548
- "type": "image",
549
- "asset_id": asset_id,
550
- "mime_type": "image/png",
551
- "width": 1,
552
- "height": 1,
553
- },
554
- ]
555
- },
556
- )
557
-
558
- assert response.status_code == 200
559
- assert response.json()["status"] == "sent"
560
-
561
- detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
562
-
563
- assert any(
564
- entry["type"] == "ReceivedMessage"
565
- and entry["from_id"] == "human"
566
- and entry["content"] == "/help[image]"
567
- for entry in detail["history"]
568
- )
569
- assert not any(
570
- entry["type"] == "CommandResultEntry" and entry["command_name"] == "/help"
571
- for entry in detail["history"]
572
- )
573
-
574
-
575
- def test_leader_retry_rewrites_tail_and_reuses_image_parts(monkeypatch, client):
576
- assistant_id = _get_assistant_id(client)
577
- tab = client.post(
578
- "/api/workflows",
579
- json={"title": "Execution"},
580
- ).json()
581
- leader = registry.get(tab["leader_id"])
582
- assert leader is not None
583
- queued_messages = []
584
-
585
- upload_response = client.post(
586
- "/api/image-assets",
587
- files={"file": ("pixel.png", _ONE_PIXEL_PNG, "image/png")},
588
- )
589
- assert upload_response.status_code == 200
590
- asset_id = upload_response.json()["id"]
591
-
592
- leader.history.extend(
593
- [
594
- ReceivedMessage(
595
- content="Initial brief",
596
- from_id=assistant_id,
597
- message_id="brief-1",
598
- ),
599
- AssistantText(content="Leader summary"),
600
- ReceivedMessage(
601
- from_id="human",
602
- parts=[
603
- TextPart(text="Retry this leader request"),
604
- ImagePart(
605
- asset_id=asset_id,
606
- mime_type="image/png",
607
- width=1,
608
- height=1,
609
- ),
610
- ],
611
- message_id="msg-2",
612
- ),
613
- AssistantText(content="Discard this leader reply"),
614
- ]
615
- )
616
-
617
- monkeypatch.setattr(leader, "supports_input_image", lambda: True)
618
- monkeypatch.setattr(
619
- leader,
620
- "enqueue_message",
621
- lambda message: queued_messages.append(message),
622
- )
623
-
624
- response = client.post(f"/api/nodes/{tab['leader_id']}/messages/msg-2/retry")
625
-
626
- assert response.status_code == 200
627
- payload = response.json()
628
- assert payload["status"] == "retried"
629
- assert payload["message_id"] != "msg-2"
630
-
631
- detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
632
-
633
- assert any(
634
- entry["type"] == "ReceivedMessage"
635
- and entry.get("message_id") == "brief-1"
636
- and entry.get("from_id") == assistant_id
637
- for entry in detail["history"]
638
- )
639
- assert not any(
640
- entry["type"] == "ReceivedMessage" and entry.get("message_id") == "msg-2"
641
- for entry in detail["history"]
642
- )
643
- assert not any(
644
- entry["type"] == "AssistantText"
645
- and entry.get("content") == "Discard this leader reply"
646
- for entry in detail["history"]
647
- )
648
- assert any(
649
- entry["type"] == "ReceivedMessage"
650
- and entry.get("message_id") == payload["message_id"]
651
- and entry.get("content") == "Retry this leader request[image]"
652
- for entry in detail["history"]
653
- )
654
- assert len(queued_messages) == 1
655
- assert queued_messages[0].message_id == payload["message_id"]
656
- assert queued_messages[0].parts[0].text == "Retry this leader request"
657
- assert queued_messages[0].parts[1].asset_id == asset_id
658
-
659
-
660
- def test_leader_retry_rejects_non_human_anchor(client: TestClient):
661
- assistant_id = _get_assistant_id(client)
662
- tab = client.post(
663
- "/api/workflows",
664
- json={"title": "Execution"},
665
- ).json()
666
- leader = registry.get(tab["leader_id"])
667
- assert leader is not None
668
- leader.history.append(
669
- ReceivedMessage(
670
- content="Initial brief",
671
- from_id=assistant_id,
672
- message_id="brief-1",
673
- )
674
- )
675
-
676
- response = client.post(f"/api/nodes/{tab['leader_id']}/messages/brief-1/retry")
677
-
678
- assert response.status_code == 404
679
- assert response.json()["detail"] == "Leader human message `brief-1` was not found."
680
-
681
-
682
- def test_regular_worker_retry_is_not_available_via_nodes_api(client: TestClient):
683
- tab = client.post(
684
- "/api/workflows",
685
- json={"title": "Execution"},
686
- ).json()
687
- worker = client.post(
688
- f"/api/workflows/{tab['id']}/nodes",
689
- json={"role_name": "Worker", "name": "Worker"},
690
- ).json()
691
-
692
- response = client.post(f"/api/nodes/{worker['id']}/messages/msg-1/retry")
693
-
694
- assert response.status_code == 400
695
- assert response.json() == {
696
- "detail": "Only a Workflow Leader can retry chat history"
697
- }
698
-
699
-
700
- def test_human_input_cannot_target_regular_worker(client: TestClient):
701
- tab = client.post(
702
- "/api/workflows",
703
- json={"title": "Execution"},
704
- ).json()
705
- worker = client.post(
706
- f"/api/workflows/{tab['id']}/nodes",
707
- json={"role_name": "Worker", "name": "Worker"},
708
- ).json()
709
-
710
- response = client.post(
711
- f"/api/nodes/{worker['id']}/messages",
712
- json={"content": "Do the work"},
713
- )
714
-
715
- assert response.status_code == 400
716
- assert response.json()["detail"] == (
717
- "Human input can only target Assistant or a Workflow Leader"
718
- )
719
-
720
-
721
- def test_browser_cannot_spoof_non_human_sender_for_node_messages(client: TestClient):
722
- tab = client.post(
723
- "/api/workflows",
724
- json={"title": "Execution"},
725
- ).json()
726
- worker = client.post(
727
- f"/api/workflows/{tab['id']}/nodes",
728
- json={"role_name": "Worker", "name": "Worker"},
729
- ).json()
730
-
731
- response = client.post(
732
- f"/api/nodes/{worker['id']}/messages",
733
- json={"content": "Do the work", "from_id": "assistant"},
734
- )
735
-
736
- assert response.status_code == 400
737
- assert response.json()["detail"] == (
738
- "Web UI node messages must originate from `human`"
739
- )
740
-
741
-
742
- def test_leader_message_supports_structured_parts_and_image_validation(
743
- monkeypatch, client: TestClient
744
- ):
745
- tab = client.post(
746
- "/api/workflows",
747
- json={"title": "Execution"},
748
- ).json()
749
- leader = registry.get(tab["leader_id"])
750
- assert leader is not None
751
-
752
- upload_response = client.post(
753
- "/api/image-assets",
754
- files={"file": ("pixel.png", _ONE_PIXEL_PNG, "image/png")},
755
- )
756
- assert upload_response.status_code == 200
757
- asset_id = upload_response.json()["id"]
758
-
759
- monkeypatch.setattr(leader, "supports_input_image", lambda: True)
760
-
761
- response = client.post(
762
- f"/api/nodes/{tab['leader_id']}/messages",
763
- json={
764
- "parts": [
765
- {"type": "text", "text": "Inspect this screenshot"},
766
- {
767
- "type": "image",
768
- "asset_id": asset_id,
769
- "mime_type": "image/png",
770
- "width": 1,
771
- "height": 1,
772
- "alt": "Pixel",
773
- },
774
- ]
775
- },
776
- )
777
-
778
- assert response.status_code == 200
779
- detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
780
- entry = next(
781
- history_entry
782
- for history_entry in detail["history"]
783
- if history_entry["type"] == "ReceivedMessage"
784
- and history_entry["message_id"] == response.json()["message_id"]
785
- )
786
- assert entry["parts"][0]["text"] == "Inspect this screenshot"
787
- assert entry["parts"][1]["asset_id"] == asset_id