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,182 +0,0 @@
1
- import pytest
2
- from fastapi.testclient import TestClient
3
- from starlette.websockets import WebSocketDisconnect
4
-
5
- from flowent.main import create_app
6
-
7
-
8
- def _create_client(monkeypatch, tmp_path, *, configured: bool) -> TestClient:
9
- import flowent.settings as settings_module
10
- from flowent.access import set_access_code
11
-
12
- settings_file = tmp_path / "settings.json"
13
- monkeypatch.setattr(settings_module, "_SETTINGS_FILE", settings_file)
14
- monkeypatch.setattr(settings_module, "_cached_settings", None)
15
-
16
- settings = settings_module.Settings()
17
- if configured:
18
- set_access_code(settings, "TEST-ACCESS-CODE")
19
- settings_module.save_settings(settings)
20
- monkeypatch.setattr(settings_module, "_cached_settings", None)
21
-
22
- return TestClient(create_app())
23
-
24
-
25
- def test_protected_api_requires_admin_session(monkeypatch, tmp_path):
26
- with _create_client(monkeypatch, tmp_path, configured=True) as client:
27
- access_state = client.get("/api/access/state")
28
- protected_response = client.get("/api/settings/bootstrap")
29
-
30
- assert access_state.status_code == 200
31
- assert access_state.json() == {
32
- "authenticated": False,
33
- "configured": True,
34
- "bootstrap_generated": False,
35
- "requires_restart": False,
36
- }
37
- assert protected_response.status_code == 401
38
- assert protected_response.json() == {"detail": "Access denied"}
39
-
40
-
41
- def test_access_login_and_logout_flow(monkeypatch, tmp_path):
42
- with _create_client(monkeypatch, tmp_path, configured=True) as client:
43
- login_response = client.post(
44
- "/api/access/login",
45
- json={"code": "TEST-ACCESS-CODE"},
46
- )
47
- protected_response = client.get("/api/settings/bootstrap")
48
- logout_response = client.post("/api/access/logout")
49
- denied_response = client.get("/api/settings/bootstrap")
50
-
51
- assert login_response.status_code == 200
52
- assert login_response.json()["authenticated"] is True
53
- assert protected_response.status_code == 200
54
- assert logout_response.status_code == 200
55
- assert logout_response.json()["authenticated"] is False
56
- assert denied_response.status_code == 401
57
-
58
-
59
- def test_admin_session_survives_backend_restart(monkeypatch, tmp_path):
60
- import flowent.settings as settings_module
61
-
62
- with _create_client(monkeypatch, tmp_path, configured=True) as client:
63
- login_response = client.post(
64
- "/api/access/login",
65
- json={"code": "TEST-ACCESS-CODE"},
66
- )
67
- session_cookie = client.cookies.get("flowent_admin_session")
68
-
69
- assert login_response.status_code == 200
70
- assert session_cookie
71
- persisted_settings, _ = settings_module._read_settings_file()
72
- assert persisted_settings.access.code == "TEST-ACCESS-CODE"
73
- assert persisted_settings.access.session_signing_secret
74
-
75
- with _create_client(monkeypatch, tmp_path, configured=True) as restarted_client:
76
- restarted_client.cookies.set("flowent_admin_session", session_cookie)
77
- access_state = restarted_client.get("/api/access/state")
78
- protected_response = restarted_client.get("/api/settings/bootstrap")
79
-
80
- assert access_state.status_code == 200
81
- assert access_state.json()["authenticated"] is True
82
- assert protected_response.status_code == 200
83
-
84
-
85
- def test_access_code_rotation_invalidates_existing_admin_session(
86
- monkeypatch,
87
- tmp_path,
88
- ):
89
- import flowent.settings as settings_module
90
- from flowent.access import set_access_code
91
-
92
- with _create_client(monkeypatch, tmp_path, configured=True) as client:
93
- login_response = client.post(
94
- "/api/access/login",
95
- json={"code": "TEST-ACCESS-CODE"},
96
- )
97
- session_cookie = client.cookies.get("flowent_admin_session")
98
-
99
- assert login_response.status_code == 200
100
- assert session_cookie
101
-
102
- rotated_settings = settings_module.get_settings()
103
- set_access_code(rotated_settings, "NEW-ACCESS-CODE")
104
- settings_module.save_settings(rotated_settings)
105
- monkeypatch.setattr(settings_module, "_cached_settings", None)
106
-
107
- with TestClient(create_app()) as restarted_client:
108
- restarted_client.cookies.set("flowent_admin_session", session_cookie)
109
- access_state = restarted_client.get("/api/access/state")
110
- protected_response = restarted_client.get("/api/settings/bootstrap")
111
-
112
- assert access_state.status_code == 200
113
- assert access_state.json()["authenticated"] is False
114
- assert protected_response.status_code == 401
115
- assert protected_response.json() == {"detail": "Access denied"}
116
-
117
-
118
- def test_websocket_requires_admin_session(monkeypatch, tmp_path):
119
- with _create_client(monkeypatch, tmp_path, configured=True) as client:
120
- with pytest.raises(WebSocketDisconnect), client.websocket_connect("/ws/events"):
121
- pass
122
-
123
- login_response = client.post(
124
- "/api/access/login",
125
- json={"code": "TEST-ACCESS-CODE"},
126
- )
127
-
128
- with client.websocket_connect("/ws/events"):
129
- pass
130
-
131
- assert login_response.status_code == 200
132
-
133
-
134
- def test_access_state_reports_bootstrap_generated_code(monkeypatch, tmp_path):
135
- with _create_client(monkeypatch, tmp_path, configured=False) as client:
136
- access_state = client.get("/api/access/state")
137
-
138
- assert access_state.status_code == 200
139
- assert access_state.json() == {
140
- "authenticated": False,
141
- "configured": True,
142
- "bootstrap_generated": True,
143
- "requires_restart": False,
144
- }
145
-
146
-
147
- def test_legacy_hashed_only_access_rotates_to_persisted_code_at_startup(
148
- monkeypatch,
149
- tmp_path,
150
- ):
151
- import flowent.settings as settings_module
152
- from flowent.access import set_access_code, verify_access_code
153
-
154
- settings_file = tmp_path / "settings.json"
155
- monkeypatch.setattr(settings_module, "_SETTINGS_FILE", settings_file)
156
- monkeypatch.setattr(settings_module, "_cached_settings", None)
157
-
158
- settings = settings_module.Settings()
159
- set_access_code(settings, "OLD-ACCESS-CODE")
160
- settings.access.code = ""
161
- settings_module.save_settings(settings)
162
- monkeypatch.setattr(settings_module, "_cached_settings", None)
163
-
164
- with TestClient(create_app()) as client:
165
- access_state = client.get("/api/access/state")
166
-
167
- assert access_state.status_code == 200
168
- assert access_state.json() == {
169
- "authenticated": False,
170
- "configured": True,
171
- "bootstrap_generated": True,
172
- "requires_restart": False,
173
- }
174
-
175
- persisted_settings = settings_module.load_settings()
176
- assert persisted_settings.access.code
177
- assert persisted_settings.access.code != "OLD-ACCESS-CODE"
178
- assert verify_access_code(
179
- persisted_settings.access,
180
- persisted_settings.access.code,
181
- )
182
- assert not verify_access_code(persisted_settings.access, "OLD-ACCESS-CODE")
@@ -1,422 +0,0 @@
1
- import base64
2
- import threading
3
- from uuid import UUID
4
-
5
- from flowent.models import (
6
- AgentState,
7
- AssistantText,
8
- ErrorEntry,
9
- ImagePart,
10
- LLMResponse,
11
- ReceivedMessage,
12
- TextPart,
13
- )
14
- from flowent.providers.errors import LLMProviderError
15
- from flowent.registry import registry
16
-
17
- _ONE_PIXEL_PNG = base64.b64decode(
18
- "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aF9sAAAAASUVORK5CYII="
19
- )
20
-
21
-
22
- def _get_assistant_id(client) -> str:
23
- response = client.get("/api/assistant")
24
-
25
- assert response.status_code == 200
26
- assistant_id = response.json()["id"]
27
- UUID(assistant_id)
28
- return assistant_id
29
-
30
-
31
- def test_help_command_returns_visible_command_feedback(client):
32
- assistant_id = _get_assistant_id(client)
33
-
34
- response = client.post("/api/assistant/message", json={"content": "/help"})
35
-
36
- assert response.status_code == 200
37
- assert response.json() == {
38
- "status": "command_executed",
39
- "command_name": "/help",
40
- }
41
-
42
- detail = client.get(f"/api/nodes/{assistant_id}").json()
43
-
44
- assert any(
45
- entry["type"] == "CommandResultEntry"
46
- and entry["command_name"] == "/help"
47
- and "/compact" in entry["content"]
48
- for entry in detail["history"]
49
- )
50
- assert not any(
51
- entry["type"] == "ReceivedMessage" and entry.get("content") == "/help"
52
- for entry in detail["history"]
53
- )
54
-
55
-
56
- def test_clear_command_clears_history_back_to_empty_state(client):
57
- assistant_id = _get_assistant_id(client)
58
- assistant = registry.get(assistant_id)
59
- assert assistant is not None
60
- assistant.history.append(ReceivedMessage(content="Old message", from_id="human"))
61
- assistant.history.append(AssistantText(content="Old reply"))
62
-
63
- response = client.post("/api/assistant/message", json={"content": "/clear"})
64
-
65
- assert response.status_code == 200
66
- assert response.json() == {
67
- "status": "command_executed",
68
- "command_name": "/clear",
69
- }
70
-
71
- detail = client.get(f"/api/nodes/{assistant_id}").json()
72
-
73
- assert not any(
74
- entry["type"] in {"ReceivedMessage", "AssistantText"}
75
- and entry.get("content") in {"Old message", "Old reply"}
76
- for entry in detail["history"]
77
- )
78
- assert not any(
79
- entry["type"] == "CommandResultEntry" and entry["command_name"] == "/clear"
80
- for entry in detail["history"]
81
- )
82
-
83
-
84
- def test_assistant_accepts_new_message_after_error(monkeypatch, client):
85
- assistant_id = _get_assistant_id(client)
86
- assistant = registry.get(assistant_id)
87
- assert assistant is not None
88
- chat_started = threading.Event()
89
- release_chat = threading.Event()
90
-
91
- def fake_chat(**kwargs):
92
- chat_started.set()
93
- release_chat.wait(timeout=2)
94
- raise LLMProviderError(
95
- "LLM API error\nDetail: still invalid",
96
- transient=False,
97
- )
98
-
99
- monkeypatch.setattr("flowent.agent.gateway.chat", fake_chat)
100
- assistant.history.append(ErrorEntry(content="LLM API error\nDetail: Invalid key"))
101
- assistant.set_state(AgentState.ERROR, "LLM API error")
102
-
103
- try:
104
- response = client.post(
105
- "/api/assistant/message",
106
- json={"content": "Continue with a safer plan"},
107
- )
108
-
109
- assert response.status_code == 200
110
- message_id = response.json()["message_id"]
111
- assert isinstance(message_id, str)
112
- assert chat_started.wait(timeout=1)
113
-
114
- detail = client.get(f"/api/nodes/{assistant_id}").json()
115
- history = detail["history"]
116
- assert any(
117
- entry["type"] == "ErrorEntry"
118
- and entry["content"] == "LLM API error\nDetail: Invalid key"
119
- for entry in history
120
- )
121
- assert any(
122
- entry["type"] == "ReceivedMessage"
123
- and entry["from_id"] == "human"
124
- and entry["message_id"] == message_id
125
- and entry["content"] == "Continue with a safer plan"
126
- for entry in history
127
- )
128
- assert registry.get(assistant_id).state == AgentState.RUNNING
129
- finally:
130
- release_chat.set()
131
-
132
-
133
- def test_assistant_rejects_message_when_terminated(client):
134
- assistant_id = _get_assistant_id(client)
135
- assistant = registry.get(assistant_id)
136
- assert assistant is not None
137
- assistant.set_state(AgentState.TERMINATED, "done")
138
-
139
- response = client.post(
140
- "/api/assistant/message",
141
- json={"content": "Continue"},
142
- )
143
-
144
- assert response.status_code == 409
145
- assert response.json()["detail"] == "Assistant is no longer available"
146
-
147
-
148
- def test_compact_command_replaces_history_with_summary(monkeypatch, client):
149
- assistant_id = _get_assistant_id(client)
150
- assistant = registry.get(assistant_id)
151
- assert assistant is not None
152
- assistant.history.extend(
153
- [
154
- ReceivedMessage(content="Need a concise recap", from_id="human"),
155
- AssistantText(content="I will summarize the open work."),
156
- ]
157
- )
158
-
159
- monkeypatch.setattr(
160
- "flowent.agent.gateway.chat",
161
- lambda *args, **kwargs: LLMResponse(
162
- content=(
163
- "## Current Goal\nShip the slash commands.\n\n"
164
- "## Active Task Boundary\nKeep the fix limited to Assistant chat.\n\n"
165
- "## Key Constraints\nDo not lose persisted context.\n\n"
166
- "## Confirmed Decisions\nUse built-in commands only.\n\n"
167
- "## Open Questions\nNone.\n\n"
168
- "## Next Actions\nWire the UI and tests."
169
- )
170
- ),
171
- )
172
-
173
- response = client.post(
174
- "/api/assistant/message",
175
- json={"content": "/compact slash command rollout"},
176
- )
177
-
178
- assert response.status_code == 200
179
- assert response.json() == {
180
- "status": "command_executed",
181
- "command_name": "/compact",
182
- }
183
-
184
- detail = client.get(f"/api/nodes/{assistant_id}").json()
185
-
186
- assert any(
187
- entry["type"] == "ReceivedMessage"
188
- and entry.get("content") == "Need a concise recap"
189
- for entry in detail["history"]
190
- )
191
- assert any(
192
- entry["type"] == "AssistantText"
193
- and entry.get("content") == "I will summarize the open work."
194
- for entry in detail["history"]
195
- )
196
- assert any(
197
- entry["type"] == "CommandResultEntry"
198
- and entry["command_name"] == "/compact"
199
- and entry.get("include_in_context") is False
200
- and "Compacted this chat for future replies." in entry["content"]
201
- and "Focus: slash command rollout" in entry["content"]
202
- and "Ship the slash commands." not in entry["content"]
203
- for entry in detail["history"]
204
- )
205
-
206
-
207
- def test_retry_message_rewrites_tail_and_reuses_image_parts(monkeypatch, client):
208
- assistant_id = _get_assistant_id(client)
209
- assistant = registry.get(assistant_id)
210
- assert assistant is not None
211
- queued_messages = []
212
-
213
- upload_response = client.post(
214
- "/api/image-assets",
215
- files={"file": ("pixel.png", _ONE_PIXEL_PNG, "image/png")},
216
- )
217
- assert upload_response.status_code == 200
218
- asset_id = upload_response.json()["id"]
219
-
220
- assistant.history.extend(
221
- [
222
- ReceivedMessage(
223
- content="Keep this turn",
224
- from_id="human",
225
- message_id="msg-1",
226
- ),
227
- AssistantText(content="Keep this reply"),
228
- ReceivedMessage(
229
- from_id="human",
230
- parts=[
231
- TextPart(text="Retry this image request"),
232
- ImagePart(
233
- asset_id=asset_id,
234
- mime_type="image/png",
235
- width=1,
236
- height=1,
237
- ),
238
- ],
239
- message_id="msg-2",
240
- ),
241
- AssistantText(content="Discard this reply"),
242
- ]
243
- )
244
-
245
- monkeypatch.setattr(assistant, "supports_input_image", lambda: True)
246
- monkeypatch.setattr(
247
- assistant,
248
- "enqueue_message",
249
- lambda message: queued_messages.append(message),
250
- )
251
-
252
- response = client.post("/api/assistant/messages/msg-2/retry")
253
-
254
- assert response.status_code == 200
255
- payload = response.json()
256
- assert payload["status"] == "retried"
257
- assert payload["message_id"] != "msg-2"
258
-
259
- detail = client.get(f"/api/nodes/{assistant_id}").json()
260
-
261
- assert any(
262
- entry["type"] == "ReceivedMessage"
263
- and entry.get("message_id") == "msg-1"
264
- and entry.get("content") == "Keep this turn"
265
- for entry in detail["history"]
266
- )
267
- assert not any(
268
- entry["type"] == "ReceivedMessage" and entry.get("message_id") == "msg-2"
269
- for entry in detail["history"]
270
- )
271
- assert not any(
272
- entry["type"] == "AssistantText"
273
- and entry.get("content") == "Discard this reply"
274
- for entry in detail["history"]
275
- )
276
- assert any(
277
- entry["type"] == "ReceivedMessage"
278
- and entry.get("message_id") == payload["message_id"]
279
- and entry.get("content") == "Retry this image request[image]"
280
- for entry in detail["history"]
281
- )
282
- assert len(queued_messages) == 1
283
- assert queued_messages[0].message_id == payload["message_id"]
284
- assert queued_messages[0].parts[0].text == "Retry this image request"
285
- assert queued_messages[0].parts[1].asset_id == asset_id
286
-
287
-
288
- def test_retry_message_returns_404_for_missing_anchor(client):
289
- response = client.post("/api/assistant/messages/missing/retry")
290
-
291
- assert response.status_code == 404
292
- assert "was not found" in response.json()["detail"]
293
-
294
-
295
- def test_retry_message_returns_409_when_image_input_is_unsupported(monkeypatch, client):
296
- assistant_id = _get_assistant_id(client)
297
- assistant = registry.get(assistant_id)
298
- assert assistant is not None
299
-
300
- upload_response = client.post(
301
- "/api/image-assets",
302
- files={"file": ("pixel.png", _ONE_PIXEL_PNG, "image/png")},
303
- )
304
- assert upload_response.status_code == 200
305
- asset_id = upload_response.json()["id"]
306
-
307
- assistant.history.append(
308
- ReceivedMessage(
309
- from_id="human",
310
- parts=[
311
- ImagePart(asset_id=asset_id, mime_type="image/png", width=1, height=1)
312
- ],
313
- message_id="msg-image",
314
- )
315
- )
316
- monkeypatch.setattr(assistant, "supports_input_image", lambda: False)
317
-
318
- response = client.post("/api/assistant/messages/msg-image/retry")
319
-
320
- assert response.status_code == 409
321
- assert response.json()["detail"] == (
322
- "Assistant current model does not support `input_image`."
323
- )
324
-
325
-
326
- def test_upload_image_asset_returns_metadata_and_serves_bytes(client):
327
- response = client.post(
328
- "/api/image-assets",
329
- files={"file": ("pixel.png", _ONE_PIXEL_PNG, "image/png")},
330
- )
331
-
332
- assert response.status_code == 200
333
- payload = response.json()
334
- assert payload["mime_type"] == "image/png"
335
- assert payload["width"] == 1
336
- assert payload["height"] == 1
337
-
338
- image_response = client.get(f"/api/image-assets/{payload['id']}")
339
-
340
- assert image_response.status_code == 200
341
- assert image_response.headers["content-type"].startswith("image/png")
342
- assert image_response.content == _ONE_PIXEL_PNG
343
-
344
-
345
- def test_image_message_bypasses_assistant_commands(monkeypatch, client):
346
- assistant_id = _get_assistant_id(client)
347
- assistant = registry.get(assistant_id)
348
- assert assistant is not None
349
- queued_messages = []
350
-
351
- upload_response = client.post(
352
- "/api/image-assets",
353
- files={"file": ("pixel.png", _ONE_PIXEL_PNG, "image/png")},
354
- )
355
- assert upload_response.status_code == 200
356
- asset_id = upload_response.json()["id"]
357
-
358
- monkeypatch.setattr(assistant, "supports_input_image", lambda: True)
359
- monkeypatch.setattr(
360
- assistant, "enqueue_message", lambda message: queued_messages.append(message)
361
- )
362
-
363
- response = client.post(
364
- "/api/assistant/message",
365
- json={
366
- "parts": [
367
- {"type": "text", "text": "/help"},
368
- {
369
- "type": "image",
370
- "asset_id": asset_id,
371
- "mime_type": "image/png",
372
- "width": 1,
373
- "height": 1,
374
- "alt": "Pixel",
375
- },
376
- ]
377
- },
378
- )
379
-
380
- assert response.status_code == 200
381
- assert response.json()["status"] == "sent"
382
- assert len(queued_messages) == 1
383
- assert queued_messages[0].parts[0].text == "/help"
384
- assert queued_messages[0].parts[1].asset_id == asset_id
385
-
386
-
387
- def test_image_message_is_rejected_when_assistant_lacks_input_image_support(
388
- monkeypatch, client
389
- ):
390
- assistant_id = _get_assistant_id(client)
391
- assistant = registry.get(assistant_id)
392
- assert assistant is not None
393
-
394
- upload_response = client.post(
395
- "/api/image-assets",
396
- files={"file": ("pixel.png", _ONE_PIXEL_PNG, "image/png")},
397
- )
398
- assert upload_response.status_code == 200
399
- asset_id = upload_response.json()["id"]
400
-
401
- monkeypatch.setattr(assistant, "supports_input_image", lambda: False)
402
-
403
- response = client.post(
404
- "/api/assistant/message",
405
- json={
406
- "parts": [
407
- {
408
- "type": "image",
409
- "asset_id": asset_id,
410
- "mime_type": "image/png",
411
- "width": 1,
412
- "height": 1,
413
- }
414
- ]
415
- },
416
- )
417
-
418
- assert response.status_code == 409
419
- assert (
420
- response.json()["detail"]
421
- == "Assistant current model does not support `input_image`."
422
- )
@@ -1,61 +0,0 @@
1
- import json
2
-
3
- from fastapi.testclient import TestClient
4
-
5
- from flowent.main import create_app
6
-
7
-
8
- def _create_client(monkeypatch, tmp_path, *, serve_frontend: bool) -> TestClient:
9
- import flowent.settings as settings_module
10
-
11
- settings_file = tmp_path / "settings.json"
12
- static_dir = tmp_path / "static"
13
- assets_dir = static_dir / "assets"
14
-
15
- assets_dir.mkdir(parents=True)
16
- (static_dir / "index.html").write_text(
17
- "<!doctype html><html><body>Flowent</body></html>",
18
- encoding="utf-8",
19
- )
20
- (assets_dir / "app.js").write_text("console.log('flowent')", encoding="utf-8")
21
- settings_file.write_text(
22
- json.dumps(
23
- {
24
- "event_log": {"timestamp_format": "absolute"},
25
- "model": {"active_provider_id": "", "active_model": ""},
26
- "custom_prompt": "",
27
- "custom_post_prompt": "",
28
- "providers": [],
29
- "roles": [],
30
- }
31
- ),
32
- encoding="utf-8",
33
- )
34
-
35
- monkeypatch.setattr(settings_module, "_SETTINGS_FILE", settings_file)
36
- monkeypatch.setattr(settings_module, "_cached_settings", None)
37
- monkeypatch.setenv("FLOWENT_STATIC_DIR", str(static_dir))
38
-
39
- return TestClient(create_app(serve_frontend=serve_frontend))
40
-
41
-
42
- def test_production_app_serves_frontend_index(monkeypatch, tmp_path):
43
- with _create_client(monkeypatch, tmp_path, serve_frontend=True) as client:
44
- response = client.get("/")
45
- nested_response = client.get("/workspace")
46
- asset_response = client.get("/assets/app.js")
47
-
48
- assert response.status_code == 200
49
- assert response.headers["content-type"].startswith("text/html")
50
- assert nested_response.status_code == 200
51
- assert nested_response.headers["content-type"].startswith("text/html")
52
- assert asset_response.status_code == 200
53
-
54
-
55
- def test_dev_app_does_not_serve_frontend_routes(monkeypatch, tmp_path):
56
- with _create_client(monkeypatch, tmp_path, serve_frontend=False) as client:
57
- root_response = client.get("/")
58
- page_response = client.get("/workspace")
59
-
60
- assert root_response.status_code == 404
61
- assert page_response.status_code == 404
@@ -1,32 +0,0 @@
1
- from fastapi.testclient import TestClient
2
-
3
-
4
- def test_tools_api_shows_agent_visible_management_tools(client: TestClient):
5
- response = client.get("/api/tools")
6
-
7
- assert response.status_code == 200
8
- tool_names = {tool["name"] for tool in response.json()["tools"]}
9
- assert "manage_providers" in tool_names
10
- assert "manage_roles" in tool_names
11
- assert "manage_settings" in tool_names
12
- assert "manage_prompts" in tool_names
13
- assert "set_permissions" in tool_names
14
- assert "sleep" in tool_names
15
- assert "delete_workflow" in tool_names
16
- assert "create_agent" in tool_names
17
- assert "connect" in tool_names
18
-
19
-
20
- def test_settings_bootstrap_returns_settings_related_resources(client: TestClient):
21
- response = client.get("/api/settings/bootstrap")
22
-
23
- assert response.status_code == 200
24
- payload = response.json()
25
- assert payload["version"]
26
- assert "settings" in payload
27
- assert "providers" in payload
28
- assert "roles" in payload
29
- role_names = {role["name"] for role in payload["roles"]}
30
- assert "Steward" in role_names
31
- assert "Worker" in role_names
32
- assert "Designer" in role_names