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,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