flowent 0.0.7 → 0.0.10

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 (386) 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/__pycache__/__init__.cpython-313.pyc +0 -0
  5. package/backend/src/flowent/__pycache__/agent.cpython-313.pyc +0 -0
  6. package/backend/src/flowent/__pycache__/cli.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 +364 -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 +449 -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__/config.cpython-313.pyc +0 -0
  59. package/backend/src/flowent/__pycache__/events.cpython-313.pyc +0 -0
  60. package/backend/src/flowent/__pycache__/graph_runtime.cpython-313.pyc +0 -0
  61. package/backend/src/flowent/__pycache__/graph_service.cpython-313.pyc +0 -0
  62. package/backend/src/flowent/__pycache__/image_assets.cpython-313.pyc +0 -0
  63. package/backend/src/flowent/__pycache__/model_metadata.cpython-313.pyc +0 -0
  64. package/backend/src/flowent/__pycache__/network.cpython-313.pyc +0 -0
  65. package/backend/src/flowent/__pycache__/observability_service.cpython-313.pyc +0 -0
  66. package/backend/src/flowent/__pycache__/registry.cpython-313.pyc +0 -0
  67. package/backend/src/flowent/__pycache__/role_management.cpython-313.pyc +0 -0
  68. package/backend/src/flowent/__pycache__/runtime.cpython-313.pyc +0 -0
  69. package/backend/src/flowent/__pycache__/security.cpython-313.pyc +0 -0
  70. package/backend/src/flowent/__pycache__/settings.cpython-313.pyc +0 -0
  71. package/backend/src/flowent/__pycache__/settings_management.cpython-313.pyc +0 -0
  72. package/backend/src/flowent/__pycache__/state_db.cpython-313.pyc +0 -0
  73. package/backend/src/flowent/__pycache__/workspace_store.cpython-313.pyc +0 -0
  74. package/backend/src/flowent/access.py +0 -247
  75. package/backend/src/flowent/assistant_commands.py +0 -115
  76. package/backend/src/flowent/channels/__init__.py +0 -3
  77. package/backend/src/flowent/channels/__pycache__/__init__.cpython-313.pyc +0 -0
  78. package/backend/src/flowent/channels/__pycache__/telegram.cpython-313.pyc +0 -0
  79. package/backend/src/flowent/channels/telegram.py +0 -615
  80. package/backend/src/flowent/config.py +0 -14
  81. package/backend/src/flowent/dev.py +0 -3
  82. package/backend/src/flowent/events.py +0 -157
  83. package/backend/src/flowent/graph_runtime.py +0 -60
  84. package/backend/src/flowent/graph_service.py +0 -2401
  85. package/backend/src/flowent/image_assets.py +0 -356
  86. package/backend/src/flowent/model_metadata.py +0 -102
  87. package/backend/src/flowent/models/__init__.py +0 -125
  88. package/backend/src/flowent/models/__pycache__/__init__.cpython-313.pyc +0 -0
  89. package/backend/src/flowent/models/__pycache__/agent.cpython-313.pyc +0 -0
  90. package/backend/src/flowent/models/__pycache__/base.cpython-313.pyc +0 -0
  91. package/backend/src/flowent/models/__pycache__/blueprint.cpython-313.pyc +0 -0
  92. package/backend/src/flowent/models/__pycache__/content.cpython-313.pyc +0 -0
  93. package/backend/src/flowent/models/__pycache__/delta.cpython-313.pyc +0 -0
  94. package/backend/src/flowent/models/__pycache__/event.cpython-313.pyc +0 -0
  95. package/backend/src/flowent/models/__pycache__/graph.cpython-313.pyc +0 -0
  96. package/backend/src/flowent/models/__pycache__/history.cpython-313.pyc +0 -0
  97. package/backend/src/flowent/models/__pycache__/llm.cpython-313.pyc +0 -0
  98. package/backend/src/flowent/models/__pycache__/message.cpython-313.pyc +0 -0
  99. package/backend/src/flowent/models/__pycache__/tab.cpython-313.pyc +0 -0
  100. package/backend/src/flowent/models/__pycache__/todo.cpython-313.pyc +0 -0
  101. package/backend/src/flowent/models/agent.py +0 -34
  102. package/backend/src/flowent/models/base.py +0 -24
  103. package/backend/src/flowent/models/blueprint.py +0 -176
  104. package/backend/src/flowent/models/content.py +0 -164
  105. package/backend/src/flowent/models/delta.py +0 -44
  106. package/backend/src/flowent/models/event.py +0 -51
  107. package/backend/src/flowent/models/graph.py +0 -472
  108. package/backend/src/flowent/models/history.py +0 -272
  109. package/backend/src/flowent/models/llm.py +0 -62
  110. package/backend/src/flowent/models/message.py +0 -33
  111. package/backend/src/flowent/models/tab.py +0 -85
  112. package/backend/src/flowent/models/todo.py +0 -10
  113. package/backend/src/flowent/network.py +0 -146
  114. package/backend/src/flowent/observability_service.py +0 -218
  115. package/backend/src/flowent/prompts/__init__.py +0 -67
  116. package/backend/src/flowent/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  117. package/backend/src/flowent/prompts/__pycache__/common.cpython-313.pyc +0 -0
  118. package/backend/src/flowent/prompts/__pycache__/steward.cpython-313.pyc +0 -0
  119. package/backend/src/flowent/prompts/common.py +0 -250
  120. package/backend/src/flowent/prompts/steward.py +0 -64
  121. package/backend/src/flowent/providers/__init__.py +0 -23
  122. package/backend/src/flowent/providers/__pycache__/__init__.cpython-313.pyc +0 -0
  123. package/backend/src/flowent/providers/__pycache__/anthropic.cpython-313.pyc +0 -0
  124. package/backend/src/flowent/providers/__pycache__/base_url.cpython-313.pyc +0 -0
  125. package/backend/src/flowent/providers/__pycache__/configuration.cpython-313.pyc +0 -0
  126. package/backend/src/flowent/providers/__pycache__/content.cpython-313.pyc +0 -0
  127. package/backend/src/flowent/providers/__pycache__/errors.cpython-313.pyc +0 -0
  128. package/backend/src/flowent/providers/__pycache__/gateway.cpython-313.pyc +0 -0
  129. package/backend/src/flowent/providers/__pycache__/headers.cpython-313.pyc +0 -0
  130. package/backend/src/flowent/providers/__pycache__/management.cpython-313.pyc +0 -0
  131. package/backend/src/flowent/providers/__pycache__/openai.cpython-313.pyc +0 -0
  132. package/backend/src/flowent/providers/__pycache__/openai_responses.cpython-313.pyc +0 -0
  133. package/backend/src/flowent/providers/__pycache__/registry.cpython-313.pyc +0 -0
  134. package/backend/src/flowent/providers/__pycache__/sse.cpython-313.pyc +0 -0
  135. package/backend/src/flowent/providers/__pycache__/thinking.cpython-313.pyc +0 -0
  136. package/backend/src/flowent/providers/anthropic.py +0 -468
  137. package/backend/src/flowent/providers/base_url.py +0 -60
  138. package/backend/src/flowent/providers/configuration.py +0 -189
  139. package/backend/src/flowent/providers/content.py +0 -122
  140. package/backend/src/flowent/providers/errors.py +0 -223
  141. package/backend/src/flowent/providers/gateway.py +0 -169
  142. package/backend/src/flowent/providers/gemini.py +0 -447
  143. package/backend/src/flowent/providers/headers.py +0 -20
  144. package/backend/src/flowent/providers/management.py +0 -96
  145. package/backend/src/flowent/providers/ollama.py +0 -293
  146. package/backend/src/flowent/providers/openai.py +0 -422
  147. package/backend/src/flowent/providers/openai_responses.py +0 -655
  148. package/backend/src/flowent/providers/registry.py +0 -144
  149. package/backend/src/flowent/providers/sse.py +0 -31
  150. package/backend/src/flowent/providers/thinking.py +0 -79
  151. package/backend/src/flowent/registry.py +0 -73
  152. package/backend/src/flowent/role_management.py +0 -270
  153. package/backend/src/flowent/routes/__init__.py +0 -26
  154. package/backend/src/flowent/routes/__pycache__/__init__.cpython-313.pyc +0 -0
  155. package/backend/src/flowent/routes/__pycache__/access.cpython-313.pyc +0 -0
  156. package/backend/src/flowent/routes/__pycache__/assistant.cpython-313.pyc +0 -0
  157. package/backend/src/flowent/routes/__pycache__/image_assets.cpython-313.pyc +0 -0
  158. package/backend/src/flowent/routes/__pycache__/meta.cpython-313.pyc +0 -0
  159. package/backend/src/flowent/routes/__pycache__/nodes.cpython-313.pyc +0 -0
  160. package/backend/src/flowent/routes/__pycache__/prompts.cpython-313.pyc +0 -0
  161. package/backend/src/flowent/routes/__pycache__/providers_route.cpython-313.pyc +0 -0
  162. package/backend/src/flowent/routes/__pycache__/roles.cpython-313.pyc +0 -0
  163. package/backend/src/flowent/routes/__pycache__/settings.cpython-313.pyc +0 -0
  164. package/backend/src/flowent/routes/__pycache__/tabs.cpython-313.pyc +0 -0
  165. package/backend/src/flowent/routes/__pycache__/ws.cpython-313.pyc +0 -0
  166. package/backend/src/flowent/routes/access.py +0 -48
  167. package/backend/src/flowent/routes/assistant.py +0 -158
  168. package/backend/src/flowent/routes/image_assets.py +0 -33
  169. package/backend/src/flowent/routes/meta.py +0 -28
  170. package/backend/src/flowent/routes/nodes.py +0 -423
  171. package/backend/src/flowent/routes/prompts.py +0 -46
  172. package/backend/src/flowent/routes/providers_route.py +0 -365
  173. package/backend/src/flowent/routes/roles.py +0 -207
  174. package/backend/src/flowent/routes/settings.py +0 -379
  175. package/backend/src/flowent/routes/tabs.py +0 -298
  176. package/backend/src/flowent/routes/ws.py +0 -33
  177. package/backend/src/flowent/runtime.py +0 -160
  178. package/backend/src/flowent/security.py +0 -37
  179. package/backend/src/flowent/settings.py +0 -2112
  180. package/backend/src/flowent/settings_management.py +0 -394
  181. package/backend/src/flowent/state_db.py +0 -108
  182. package/backend/src/flowent/static/assets/AssistantPage-BW7XAd9I.js +0 -1
  183. package/backend/src/flowent/static/assets/ChannelsPage-tCJHgt6m.js +0 -1
  184. package/backend/src/flowent/static/assets/PageScaffold-f6g2l7XN.js +0 -1
  185. package/backend/src/flowent/static/assets/PromptsPage-C3Sxn2D7.js +0 -1
  186. package/backend/src/flowent/static/assets/ProvidersPage-BfmdXmNt.js +0 -3
  187. package/backend/src/flowent/static/assets/RolesPage-DET8wO4r.js +0 -1
  188. package/backend/src/flowent/static/assets/SettingsPage-D-g3deMm.js +0 -3
  189. package/backend/src/flowent/static/assets/ToolsPage-CDmtE2g4.js +0 -1
  190. package/backend/src/flowent/static/assets/WorkspacePage-AZsJ0sD0.js +0 -3
  191. package/backend/src/flowent/static/assets/WorkspacePanels-CteCjolX.js +0 -1
  192. package/backend/src/flowent/static/assets/alert-dialog-Duorp_S-.js +0 -1
  193. package/backend/src/flowent/static/assets/dialog-C3ixjGjN.js +0 -1
  194. package/backend/src/flowent/static/assets/elk-worker.min-C9JGDOE-.js +0 -6312
  195. package/backend/src/flowent/static/assets/graph-vendor-CHpVij2M.css +0 -1
  196. package/backend/src/flowent/static/assets/graph-vendor-DRq_-6fV.js +0 -7
  197. package/backend/src/flowent/static/assets/index--o_0fv0N.css +0 -1
  198. package/backend/src/flowent/static/assets/index-C9HuekJm.js +0 -10
  199. package/backend/src/flowent/static/assets/layout.worker-jMHqAFbP.js +0 -24
  200. package/backend/src/flowent/static/assets/markdown-vendor-C9RtvaJh.js +0 -29
  201. package/backend/src/flowent/static/assets/modelParams-DmnF2hwR.js +0 -1
  202. package/backend/src/flowent/static/assets/providerTypes-DT3Ahwl_.js +0 -1
  203. package/backend/src/flowent/static/assets/react-vendor-mEs_JJxa.js +0 -9
  204. package/backend/src/flowent/static/assets/roles-CuRT_chR.js +0 -1
  205. package/backend/src/flowent/static/assets/rolldown-runtime-BYbx6iT9.js +0 -1
  206. package/backend/src/flowent/static/assets/select-DCfeNu-F.js +0 -1
  207. package/backend/src/flowent/static/assets/surface-pWwG5ogx.js +0 -1
  208. package/backend/src/flowent/static/assets/ui-vendor-C5pJa8N7.js +0 -51
  209. package/backend/src/flowent/static/assets/useAppRoute-FgSHBKhV.js +0 -1
  210. package/backend/src/flowent/static/favicon.svg +0 -4
  211. package/backend/src/flowent/tools/__init__.py +0 -176
  212. package/backend/src/flowent/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  213. package/backend/src/flowent/tools/__pycache__/connect.cpython-313.pyc +0 -0
  214. package/backend/src/flowent/tools/__pycache__/contacts.cpython-313.pyc +0 -0
  215. package/backend/src/flowent/tools/__pycache__/create_agent.cpython-313.pyc +0 -0
  216. package/backend/src/flowent/tools/__pycache__/create_tab.cpython-313.pyc +0 -0
  217. package/backend/src/flowent/tools/__pycache__/delete_tab.cpython-313.pyc +0 -0
  218. package/backend/src/flowent/tools/__pycache__/edit.cpython-313.pyc +0 -0
  219. package/backend/src/flowent/tools/__pycache__/exec.cpython-313.pyc +0 -0
  220. package/backend/src/flowent/tools/__pycache__/fetch.cpython-313.pyc +0 -0
  221. package/backend/src/flowent/tools/__pycache__/idle.cpython-313.pyc +0 -0
  222. package/backend/src/flowent/tools/__pycache__/list_roles.cpython-313.pyc +0 -0
  223. package/backend/src/flowent/tools/__pycache__/list_tabs.cpython-313.pyc +0 -0
  224. package/backend/src/flowent/tools/__pycache__/list_tools.cpython-313.pyc +0 -0
  225. package/backend/src/flowent/tools/__pycache__/manage_prompts.cpython-313.pyc +0 -0
  226. package/backend/src/flowent/tools/__pycache__/manage_providers.cpython-313.pyc +0 -0
  227. package/backend/src/flowent/tools/__pycache__/manage_roles.cpython-313.pyc +0 -0
  228. package/backend/src/flowent/tools/__pycache__/manage_settings.cpython-313.pyc +0 -0
  229. package/backend/src/flowent/tools/__pycache__/read.cpython-313.pyc +0 -0
  230. package/backend/src/flowent/tools/__pycache__/send.cpython-313.pyc +0 -0
  231. package/backend/src/flowent/tools/__pycache__/set_permissions.cpython-313.pyc +0 -0
  232. package/backend/src/flowent/tools/__pycache__/sleep.cpython-313.pyc +0 -0
  233. package/backend/src/flowent/tools/__pycache__/todo.cpython-313.pyc +0 -0
  234. package/backend/src/flowent/tools/connect.py +0 -100
  235. package/backend/src/flowent/tools/contacts.py +0 -22
  236. package/backend/src/flowent/tools/create_agent.py +0 -191
  237. package/backend/src/flowent/tools/create_tab.py +0 -61
  238. package/backend/src/flowent/tools/delete_tab.py +0 -39
  239. package/backend/src/flowent/tools/edit.py +0 -142
  240. package/backend/src/flowent/tools/exec.py +0 -118
  241. package/backend/src/flowent/tools/fetch.py +0 -85
  242. package/backend/src/flowent/tools/idle.py +0 -27
  243. package/backend/src/flowent/tools/list_roles.py +0 -68
  244. package/backend/src/flowent/tools/list_tabs.py +0 -100
  245. package/backend/src/flowent/tools/list_tools.py +0 -28
  246. package/backend/src/flowent/tools/manage_prompts.py +0 -102
  247. package/backend/src/flowent/tools/manage_providers.py +0 -220
  248. package/backend/src/flowent/tools/manage_roles.py +0 -275
  249. package/backend/src/flowent/tools/manage_settings.py +0 -326
  250. package/backend/src/flowent/tools/read.py +0 -152
  251. package/backend/src/flowent/tools/send.py +0 -68
  252. package/backend/src/flowent/tools/set_permissions.py +0 -99
  253. package/backend/src/flowent/tools/sleep.py +0 -41
  254. package/backend/src/flowent/tools/todo.py +0 -51
  255. package/backend/src/flowent/workspace_store.py +0 -479
  256. package/backend/tests/__init__.py +0 -0
  257. package/backend/tests/__pycache__/__init__.cpython-313.pyc +0 -0
  258. package/backend/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  259. package/backend/tests/conftest.py +0 -6
  260. package/backend/tests/integration/api/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  261. package/backend/tests/integration/api/__pycache__/test_access_api.cpython-313-pytest-9.0.3.pyc +0 -0
  262. package/backend/tests/integration/api/__pycache__/test_assistant_api.cpython-313-pytest-9.0.3.pyc +0 -0
  263. package/backend/tests/integration/api/__pycache__/test_frontend_mounting.cpython-313-pytest-9.0.3.pyc +0 -0
  264. package/backend/tests/integration/api/__pycache__/test_meta_api.cpython-313-pytest-9.0.3.pyc +0 -0
  265. package/backend/tests/integration/api/__pycache__/test_nodes_api.cpython-313-pytest-9.0.3.pyc +0 -0
  266. package/backend/tests/integration/api/__pycache__/test_prompts_api.cpython-313-pytest-9.0.3.pyc +0 -0
  267. package/backend/tests/integration/api/__pycache__/test_roles_api.cpython-313-pytest-9.0.3.pyc +0 -0
  268. package/backend/tests/integration/api/__pycache__/test_tabs_api.cpython-313-pytest-9.0.3.pyc +0 -0
  269. package/backend/tests/integration/api/conftest.py +0 -29
  270. package/backend/tests/integration/api/test_access_api.py +0 -182
  271. package/backend/tests/integration/api/test_assistant_api.py +0 -422
  272. package/backend/tests/integration/api/test_frontend_mounting.py +0 -61
  273. package/backend/tests/integration/api/test_meta_api.py +0 -32
  274. package/backend/tests/integration/api/test_nodes_api.py +0 -787
  275. package/backend/tests/integration/api/test_prompts_api.py +0 -47
  276. package/backend/tests/integration/api/test_roles_api.py +0 -228
  277. package/backend/tests/integration/api/test_tabs_api.py +0 -688
  278. package/backend/tests/unit/__pycache__/test_access.cpython-313-pytest-9.0.3.pyc +0 -0
  279. package/backend/tests/unit/__pycache__/test_cli.cpython-313-pytest-9.0.3.pyc +0 -0
  280. package/backend/tests/unit/__pycache__/test_graph_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  281. package/backend/tests/unit/__pycache__/test_network.cpython-313-pytest-9.0.3.pyc +0 -0
  282. package/backend/tests/unit/__pycache__/test_state_sqlite_storage.cpython-313-pytest-9.0.3.pyc +0 -0
  283. package/backend/tests/unit/__pycache__/test_workspace_store.cpython-313-pytest-9.0.3.pyc +0 -0
  284. package/backend/tests/unit/agent/__pycache__/test_agent_public_api.cpython-313-pytest-9.0.3.pyc +0 -0
  285. package/backend/tests/unit/agent/__pycache__/test_agent_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  286. package/backend/tests/unit/agent/test_agent_public_api.py +0 -822
  287. package/backend/tests/unit/agent/test_agent_runtime.py +0 -3088
  288. package/backend/tests/unit/channels/__pycache__/test_telegram_channel.cpython-313-pytest-9.0.3.pyc +0 -0
  289. package/backend/tests/unit/channels/test_telegram_channel.py +0 -552
  290. package/backend/tests/unit/logging/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
  291. package/backend/tests/unit/logging/test_logging.py +0 -132
  292. package/backend/tests/unit/prompts/__pycache__/test_prompts.cpython-313-pytest-9.0.3.pyc +0 -0
  293. package/backend/tests/unit/prompts/test_prompts.py +0 -570
  294. package/backend/tests/unit/providers/__pycache__/test_anthropic_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  295. package/backend/tests/unit/providers/__pycache__/test_errors.cpython-313-pytest-9.0.3.pyc +0 -0
  296. package/backend/tests/unit/providers/__pycache__/test_extract_delta_parts.cpython-313-pytest-9.0.3.pyc +0 -0
  297. package/backend/tests/unit/providers/__pycache__/test_openai_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  298. package/backend/tests/unit/providers/__pycache__/test_openai_responses.cpython-313-pytest-9.0.3.pyc +0 -0
  299. package/backend/tests/unit/providers/__pycache__/test_provider_gateway.cpython-313-pytest-9.0.3.pyc +0 -0
  300. package/backend/tests/unit/providers/__pycache__/test_think_tag_parser.cpython-313-pytest-9.0.3.pyc +0 -0
  301. package/backend/tests/unit/providers/test_anthropic_provider.py +0 -185
  302. package/backend/tests/unit/providers/test_errors.py +0 -68
  303. package/backend/tests/unit/providers/test_extract_delta_parts.py +0 -22
  304. package/backend/tests/unit/providers/test_openai_provider.py +0 -139
  305. package/backend/tests/unit/providers/test_openai_responses.py +0 -402
  306. package/backend/tests/unit/providers/test_provider_gateway.py +0 -359
  307. package/backend/tests/unit/providers/test_think_tag_parser.py +0 -36
  308. package/backend/tests/unit/routes/__pycache__/test_prompts_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  309. package/backend/tests/unit/routes/__pycache__/test_providers_route.cpython-313-pytest-9.0.3.pyc +0 -0
  310. package/backend/tests/unit/routes/__pycache__/test_roles_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  311. package/backend/tests/unit/routes/__pycache__/test_settings_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  312. package/backend/tests/unit/routes/test_prompts_routes.py +0 -82
  313. package/backend/tests/unit/routes/test_providers_route.py +0 -370
  314. package/backend/tests/unit/routes/test_roles_routes.py +0 -539
  315. package/backend/tests/unit/routes/test_settings_routes.py +0 -1123
  316. package/backend/tests/unit/runtime/__pycache__/test_bootstrap_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  317. package/backend/tests/unit/runtime/test_bootstrap_runtime.py +0 -1002
  318. package/backend/tests/unit/sandbox/__pycache__/test_sandbox_tools.cpython-313-pytest-9.0.3.pyc +0 -0
  319. package/backend/tests/unit/sandbox/test_sandbox_tools.py +0 -78
  320. package/backend/tests/unit/security/__pycache__/test_security.cpython-313-pytest-9.0.3.pyc +0 -0
  321. package/backend/tests/unit/security/test_security.py +0 -124
  322. package/backend/tests/unit/settings/__pycache__/test_settings_roles.cpython-313-pytest-9.0.3.pyc +0 -0
  323. package/backend/tests/unit/settings/test_settings_roles.py +0 -703
  324. package/backend/tests/unit/test_access.py +0 -45
  325. package/backend/tests/unit/test_cli.py +0 -102
  326. package/backend/tests/unit/test_graph_runtime.py +0 -72
  327. package/backend/tests/unit/test_network.py +0 -51
  328. package/backend/tests/unit/test_state_sqlite_storage.py +0 -87
  329. package/backend/tests/unit/test_workspace_store.py +0 -228
  330. package/backend/tests/unit/tools/__pycache__/test_connect_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  331. package/backend/tests/unit/tools/__pycache__/test_create_agent_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  332. package/backend/tests/unit/tools/__pycache__/test_delete_tab_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  333. package/backend/tests/unit/tools/__pycache__/test_edit_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  334. package/backend/tests/unit/tools/__pycache__/test_exec_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  335. package/backend/tests/unit/tools/__pycache__/test_fetch_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  336. package/backend/tests/unit/tools/__pycache__/test_manage_prompts_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  337. package/backend/tests/unit/tools/__pycache__/test_manage_providers_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  338. package/backend/tests/unit/tools/__pycache__/test_manage_roles_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  339. package/backend/tests/unit/tools/__pycache__/test_manage_settings_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  340. package/backend/tests/unit/tools/__pycache__/test_read_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  341. package/backend/tests/unit/tools/__pycache__/test_set_permissions_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  342. package/backend/tests/unit/tools/__pycache__/test_todo_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  343. package/backend/tests/unit/tools/__pycache__/test_tool_registry.cpython-313-pytest-9.0.3.pyc +0 -0
  344. package/backend/tests/unit/tools/test_connect_tool.py +0 -228
  345. package/backend/tests/unit/tools/test_create_agent_tool.py +0 -404
  346. package/backend/tests/unit/tools/test_delete_tab_tool.py +0 -116
  347. package/backend/tests/unit/tools/test_edit_tool.py +0 -115
  348. package/backend/tests/unit/tools/test_exec_tool.py +0 -81
  349. package/backend/tests/unit/tools/test_fetch_tool.py +0 -65
  350. package/backend/tests/unit/tools/test_manage_prompts_tool.py +0 -92
  351. package/backend/tests/unit/tools/test_manage_providers_tool.py +0 -460
  352. package/backend/tests/unit/tools/test_manage_roles_tool.py +0 -411
  353. package/backend/tests/unit/tools/test_manage_settings_tool.py +0 -611
  354. package/backend/tests/unit/tools/test_read_tool.py +0 -33
  355. package/backend/tests/unit/tools/test_set_permissions_tool.py +0 -595
  356. package/backend/tests/unit/tools/test_todo_tool.py +0 -37
  357. package/backend/tests/unit/tools/test_tool_registry.py +0 -199
  358. package/dist/frontend/assets/AssistantPage-BW7XAd9I.js +0 -1
  359. package/dist/frontend/assets/ChannelsPage-tCJHgt6m.js +0 -1
  360. package/dist/frontend/assets/PageScaffold-f6g2l7XN.js +0 -1
  361. package/dist/frontend/assets/PromptsPage-C3Sxn2D7.js +0 -1
  362. package/dist/frontend/assets/ProvidersPage-BfmdXmNt.js +0 -3
  363. package/dist/frontend/assets/RolesPage-DET8wO4r.js +0 -1
  364. package/dist/frontend/assets/SettingsPage-D-g3deMm.js +0 -3
  365. package/dist/frontend/assets/ToolsPage-CDmtE2g4.js +0 -1
  366. package/dist/frontend/assets/WorkspacePage-AZsJ0sD0.js +0 -3
  367. package/dist/frontend/assets/WorkspacePanels-CteCjolX.js +0 -1
  368. package/dist/frontend/assets/alert-dialog-Duorp_S-.js +0 -1
  369. package/dist/frontend/assets/dialog-C3ixjGjN.js +0 -1
  370. package/dist/frontend/assets/elk-worker.min-C9JGDOE-.js +0 -6312
  371. package/dist/frontend/assets/graph-vendor-CHpVij2M.css +0 -1
  372. package/dist/frontend/assets/graph-vendor-DRq_-6fV.js +0 -7
  373. package/dist/frontend/assets/index--o_0fv0N.css +0 -1
  374. package/dist/frontend/assets/index-C9HuekJm.js +0 -10
  375. package/dist/frontend/assets/layout.worker-jMHqAFbP.js +0 -24
  376. package/dist/frontend/assets/markdown-vendor-C9RtvaJh.js +0 -29
  377. package/dist/frontend/assets/modelParams-DmnF2hwR.js +0 -1
  378. package/dist/frontend/assets/providerTypes-DT3Ahwl_.js +0 -1
  379. package/dist/frontend/assets/react-vendor-mEs_JJxa.js +0 -9
  380. package/dist/frontend/assets/roles-CuRT_chR.js +0 -1
  381. package/dist/frontend/assets/rolldown-runtime-BYbx6iT9.js +0 -1
  382. package/dist/frontend/assets/select-DCfeNu-F.js +0 -1
  383. package/dist/frontend/assets/surface-pWwG5ogx.js +0 -1
  384. package/dist/frontend/assets/ui-vendor-C5pJa8N7.js +0 -51
  385. package/dist/frontend/assets/useAppRoute-FgSHBKhV.js +0 -1
  386. 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