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,688 +0,0 @@
1
- from copy import deepcopy
2
- from typing import Any
3
-
4
- from fastapi.testclient import TestClient
5
-
6
-
7
- def _create_agent_node(
8
- client: TestClient,
9
- *,
10
- tab_id: str,
11
- name: str,
12
- role_name: str = "Worker",
13
- ) -> dict[str, Any]:
14
- response = client.post(
15
- f"/api/workflows/{tab_id}/nodes",
16
- json={"role_name": role_name, "name": name},
17
- )
18
- assert response.status_code == 200
19
- return response.json()
20
-
21
-
22
- def _create_graph_node(
23
- client: TestClient,
24
- *,
25
- tab_id: str,
26
- node_type: str,
27
- name: str,
28
- config: dict[str, object] | None = None,
29
- ) -> dict[str, Any]:
30
- response = client.post(
31
- f"/api/workflows/{tab_id}/nodes",
32
- json={
33
- "node_type": node_type,
34
- "name": name,
35
- "config": config or {},
36
- },
37
- )
38
- assert response.status_code == 200
39
- return response.json()
40
-
41
-
42
- def test_list_tabs_is_empty_at_startup(client: TestClient):
43
- response = client.get("/api/workflows")
44
-
45
- assert response.status_code == 200
46
- assert response.json() == {"workflows": []}
47
-
48
-
49
- def test_create_tab_node_and_edge_round_trip(client: TestClient):
50
- create_tab_response = client.post(
51
- "/api/workflows",
52
- json={
53
- "title": "Review Task",
54
- "allow_network": True,
55
- "write_dirs": ["/tmp"],
56
- },
57
- )
58
-
59
- assert create_tab_response.status_code == 200
60
- tab = create_tab_response.json()
61
- tab_id = tab["id"]
62
- assert tab["title"] == "Review Task"
63
- assert "goal" not in tab
64
- assert tab["node_count"] == 0
65
- assert tab["edge_count"] == 0
66
- assert tab["activation_state"] == "inactive"
67
- assert tab["allow_network"] is True
68
- assert tab["write_dirs"] == ["/tmp"]
69
- assert tab["definition"] == {"version": 1, "nodes": [], "edges": []}
70
- assert isinstance(tab["leader_id"], str)
71
-
72
- reader = _create_agent_node(client, tab_id=tab_id, name="Reader")
73
- writer = _create_agent_node(client, tab_id=tab_id, name="Writer")
74
-
75
- assert reader["workflow_id"] == tab_id
76
- assert writer["workflow_id"] == tab_id
77
- assert reader["node_type"] == "agent"
78
- assert writer["node_type"] == "agent"
79
- assert reader["config"]["role_name"] == "Worker"
80
- assert writer["config"]["role_name"] == "Worker"
81
- assert "write_dirs" not in reader["config"]
82
- assert "allow_network" not in reader["config"]
83
-
84
- removed_permission_response = client.post(
85
- f"/api/workflows/{tab_id}/nodes",
86
- json={
87
- "role_name": "Worker",
88
- "write_dirs": ["/tmp"],
89
- },
90
- )
91
- assert removed_permission_response.status_code == 422
92
-
93
- edge_response = client.post(
94
- f"/api/workflows/{tab_id}/edges",
95
- json={
96
- "from_node_id": reader["id"],
97
- "from_port_key": "out",
98
- "to_node_id": writer["id"],
99
- "to_port_key": "in",
100
- },
101
- )
102
-
103
- assert edge_response.status_code == 200
104
- edge = edge_response.json()
105
- assert edge["tab_id"] == tab_id
106
- assert edge["from_node_id"] == reader["id"]
107
- assert edge["from_port_key"] == "out"
108
- assert edge["to_node_id"] == writer["id"]
109
- assert edge["to_port_key"] == "in"
110
-
111
- tab_detail_response = client.get(f"/api/workflows/{tab_id}")
112
- assert tab_detail_response.status_code == 200
113
- tab_detail = tab_detail_response.json()
114
- assert tab_detail["workflow"]["id"] == tab_id
115
- assert "goal" not in tab_detail["workflow"]
116
- assert tab_detail["workflow"]["allow_network"] is True
117
- assert tab_detail["workflow"]["write_dirs"] == ["/tmp"]
118
- assert tab_detail["workflow"]["node_count"] == 2
119
- assert tab_detail["workflow"]["edge_count"] == 1
120
- assert {node["name"] for node in tab_detail["nodes"]} == {"Reader", "Writer"}
121
- assert tab_detail["edges"] == [edge]
122
- assert len(tab_detail["workflow"]["definition"]["nodes"]) == 2
123
- assert tab_detail["workflow"]["definition"]["edges"] == [edge]
124
-
125
- nodes_response = client.get("/api/nodes")
126
- assert nodes_response.status_code == 200
127
- nodes = nodes_response.json()["nodes"]
128
- reader_node = next(node for node in nodes if node["id"] == reader["id"])
129
- writer_node = next(node for node in nodes if node["id"] == writer["id"])
130
- assert reader_node["workflow_id"] == tab_id
131
- assert writer_node["workflow_id"] == tab_id
132
- assert reader_node["connections"] == [writer["id"]]
133
- assert writer_node["connections"] == [reader["id"]]
134
-
135
-
136
- def test_delete_tab_cleans_up_nodes_and_edges(client: TestClient):
137
- create_tab_response = client.post(
138
- "/api/workflows",
139
- json={"title": "Disposable"},
140
- )
141
-
142
- assert create_tab_response.status_code == 200
143
- created_tab = create_tab_response.json()
144
- tab_id = created_tab["id"]
145
- leader_id = created_tab["leader_id"]
146
-
147
- left = _create_agent_node(client, tab_id=tab_id, name="Left")
148
- right = _create_agent_node(client, tab_id=tab_id, name="Right")
149
-
150
- edge_response = client.post(
151
- f"/api/workflows/{tab_id}/edges",
152
- json={"from_node_id": left["id"], "to_node_id": right["id"]},
153
- )
154
- assert edge_response.status_code == 200
155
- edge_id = edge_response.json()["id"]
156
-
157
- delete_response = client.delete(f"/api/workflows/{tab_id}")
158
-
159
- assert delete_response.status_code == 200
160
- assert delete_response.json()["id"] == tab_id
161
- assert set(delete_response.json()["removed_node_ids"]) == {
162
- leader_id,
163
- left["id"],
164
- right["id"],
165
- }
166
- assert delete_response.json()["removed_edge_ids"] == [edge_id]
167
-
168
- tab_detail_response = client.get(f"/api/workflows/{tab_id}")
169
- assert tab_detail_response.status_code == 404
170
-
171
- nodes_response = client.get("/api/nodes")
172
- assert nodes_response.status_code == 200
173
- node_ids = {node["id"] for node in nodes_response.json()["nodes"]}
174
- assert left["id"] not in node_ids
175
- assert right["id"] not in node_ids
176
-
177
-
178
- def test_delete_tab_edge_requires_exact_direction_and_removes_only_target_edge(
179
- client: TestClient,
180
- ):
181
- tab = client.post(
182
- "/api/workflows",
183
- json={"title": "Edge Delete"},
184
- ).json()
185
- tab_id = tab["id"]
186
-
187
- left = _create_agent_node(client, tab_id=tab_id, name="Left")
188
- middle = _create_agent_node(client, tab_id=tab_id, name="Middle")
189
- right = _create_agent_node(client, tab_id=tab_id, name="Right")
190
-
191
- left_to_middle = client.post(
192
- f"/api/workflows/{tab_id}/edges",
193
- json={"from_node_id": left["id"], "to_node_id": middle["id"]},
194
- )
195
- middle_to_right = client.post(
196
- f"/api/workflows/{tab_id}/edges",
197
- json={"from_node_id": middle["id"], "to_node_id": right["id"]},
198
- )
199
-
200
- assert left_to_middle.status_code == 200
201
- assert middle_to_right.status_code == 200
202
-
203
- reverse_delete_response = client.delete(
204
- f"/api/workflows/{tab_id}/edges",
205
- params={
206
- "from_node_id": middle["id"],
207
- "to_node_id": left["id"],
208
- },
209
- )
210
- assert reverse_delete_response.status_code == 404
211
- assert reverse_delete_response.json()["detail"] == "Edge not found"
212
-
213
- delete_response = client.delete(
214
- f"/api/workflows/{tab_id}/edges",
215
- params={
216
- "from_node_id": left["id"],
217
- "to_node_id": middle["id"],
218
- },
219
- )
220
-
221
- assert delete_response.status_code == 200
222
- assert delete_response.json()["from_node_id"] == left["id"]
223
- assert delete_response.json()["to_node_id"] == middle["id"]
224
-
225
- detail = client.get(f"/api/workflows/{tab_id}").json()
226
- remaining_edges = {
227
- (edge["from_node_id"], edge["to_node_id"]) for edge in detail["edges"]
228
- }
229
- assert remaining_edges == {(middle["id"], right["id"])}
230
-
231
-
232
- def test_delete_tab_node_removes_node_and_all_incident_edges(client: TestClient):
233
- tab = client.post(
234
- "/api/workflows",
235
- json={"title": "Node Delete"},
236
- ).json()
237
- tab_id = tab["id"]
238
-
239
- left = _create_agent_node(client, tab_id=tab_id, name="Left")
240
- middle = _create_agent_node(client, tab_id=tab_id, name="Middle")
241
- right = _create_agent_node(client, tab_id=tab_id, name="Right")
242
-
243
- assert (
244
- client.post(
245
- f"/api/workflows/{tab_id}/edges",
246
- json={"from_node_id": left["id"], "to_node_id": middle["id"]},
247
- ).status_code
248
- == 200
249
- )
250
- assert (
251
- client.post(
252
- f"/api/workflows/{tab_id}/edges",
253
- json={"from_node_id": middle["id"], "to_node_id": right["id"]},
254
- ).status_code
255
- == 200
256
- )
257
-
258
- delete_response = client.delete(f"/api/workflows/{tab_id}/nodes/{middle['id']}")
259
-
260
- assert delete_response.status_code == 200
261
- assert delete_response.json()["id"] == middle["id"]
262
-
263
- detail = client.get(f"/api/workflows/{tab_id}").json()
264
- remaining_nodes = {node["id"] for node in detail["nodes"]}
265
- assert middle["id"] not in remaining_nodes
266
- assert left["id"] in remaining_nodes
267
- assert right["id"] in remaining_nodes
268
- assert detail["edges"] == []
269
-
270
-
271
- def test_tab_edge_creation_enforces_directed_ports_and_single_input(
272
- client: TestClient,
273
- ):
274
- tab = client.post(
275
- "/api/workflows",
276
- json={"title": "Edge Validation"},
277
- ).json()
278
- tab_id = tab["id"]
279
- worker = _create_agent_node(client, tab_id=tab_id, name="Worker")
280
- reviewer = _create_agent_node(client, tab_id=tab_id, name="Reviewer")
281
- observer = _create_agent_node(client, tab_id=tab_id, name="Observer")
282
-
283
- self_loop_response = client.post(
284
- f"/api/workflows/{tab_id}/edges",
285
- json={"from_node_id": worker["id"], "to_node_id": worker["id"]},
286
- )
287
- assert self_loop_response.status_code == 400
288
- assert self_loop_response.json()["detail"] == "Self-loop edges are not allowed"
289
-
290
- first_edge_response = client.post(
291
- f"/api/workflows/{tab_id}/edges",
292
- json={"from_node_id": worker["id"], "to_node_id": reviewer["id"]},
293
- )
294
- assert first_edge_response.status_code == 200
295
-
296
- duplicate_edge_response = client.post(
297
- f"/api/workflows/{tab_id}/edges",
298
- json={"from_node_id": worker["id"], "to_node_id": reviewer["id"]},
299
- )
300
- assert duplicate_edge_response.status_code == 400
301
- assert duplicate_edge_response.json()["detail"] == "Duplicate edges are not allowed"
302
-
303
- reverse_edge_response = client.post(
304
- f"/api/workflows/{tab_id}/edges",
305
- json={"from_node_id": reviewer["id"], "to_node_id": worker["id"]},
306
- )
307
- assert reverse_edge_response.status_code == 200
308
-
309
- conflicting_input_response = client.post(
310
- f"/api/workflows/{tab_id}/edges",
311
- json={"from_node_id": observer["id"], "to_node_id": reviewer["id"]},
312
- )
313
- assert conflicting_input_response.status_code == 400
314
- assert (
315
- conflicting_input_response.json()["detail"]
316
- == "Input port 'in' already has an incoming edge"
317
- )
318
-
319
-
320
- def test_update_tab_definition_updates_metadata_and_positions(client: TestClient):
321
- tab = client.post(
322
- "/api/workflows",
323
- json={"title": "JSON Editor"},
324
- ).json()
325
- tab_id = tab["id"]
326
-
327
- agent_node = _create_agent_node(client, tab_id=tab_id, name="Draft Reviewer")
328
- code_node = _create_graph_node(
329
- client,
330
- tab_id=tab_id,
331
- node_type="code",
332
- name="Formatter",
333
- )
334
-
335
- current = client.get(f"/api/workflows/{tab_id}").json()["workflow"]["definition"]
336
- definition = deepcopy(current)
337
- for node in definition["nodes"]:
338
- if node["id"] == agent_node["id"]:
339
- node["config"]["name"] = "Final Reviewer"
340
- if node["id"] == code_node["id"]:
341
- node["config"]["name"] = "Formatter"
342
- node["config"]["language"] = "python"
343
- definition["view"] = {
344
- "positions": {
345
- agent_node["id"]: {"x": 60, "y": 80},
346
- code_node["id"]: {"x": 260, "y": 80},
347
- }
348
- }
349
- definition["edges"] = [
350
- {
351
- "id": "edge-control",
352
- "from_node_id": agent_node["id"],
353
- "from_port_key": "out",
354
- "to_node_id": code_node["id"],
355
- "to_port_key": "in",
356
- }
357
- ]
358
-
359
- update_response = client.put(
360
- f"/api/workflows/{tab_id}/definition",
361
- json={"definition": definition},
362
- )
363
-
364
- assert update_response.status_code == 200
365
- updated = update_response.json()
366
- assert updated["definition"]["view"]["positions"][agent_node["id"]] == {
367
- "x": 60.0,
368
- "y": 80.0,
369
- }
370
- assert "kind" not in updated["definition"]["edges"][0]
371
-
372
- detail = client.get(f"/api/workflows/{tab_id}").json()
373
- reviewer_detail = next(
374
- node for node in detail["nodes"] if node["id"] == agent_node["id"]
375
- )
376
- formatter_detail = next(
377
- node for node in detail["nodes"] if node["id"] == code_node["id"]
378
- )
379
- assert reviewer_detail["name"] == "Final Reviewer"
380
- assert reviewer_detail["position"] == {"x": 60.0, "y": 80.0}
381
- assert formatter_detail["config"]["language"] == "python"
382
- assert formatter_detail["position"] == {"x": 260.0, "y": 80.0}
383
-
384
- runtime_node = client.get(f"/api/nodes/{agent_node['id']}")
385
- assert runtime_node.status_code == 200
386
- assert runtime_node.json()["name"] == "Final Reviewer"
387
-
388
-
389
- def test_update_tab_definition_rejects_agent_set_changes(client: TestClient):
390
- tab = client.post(
391
- "/api/workflows",
392
- json={"title": "Guard Rails"},
393
- ).json()
394
- tab_id = tab["id"]
395
-
396
- agent_node = _create_agent_node(client, tab_id=tab_id, name="Existing Worker")
397
- definition = deepcopy(
398
- client.get(f"/api/workflows/{tab_id}").json()["workflow"]["definition"]
399
- )
400
- definition["nodes"].append(
401
- {
402
- "id": "new-agent",
403
- "type": "agent",
404
- "config": {"role_name": "Worker", "name": "Injected Worker"},
405
- "inputs": [
406
- {
407
- "key": "in",
408
- "direction": "in",
409
- "type": "parts",
410
- "required": False,
411
- "multiple": False,
412
- }
413
- ],
414
- "outputs": [
415
- {
416
- "key": "out",
417
- "direction": "out",
418
- "type": "parts",
419
- "required": False,
420
- "multiple": True,
421
- }
422
- ],
423
- }
424
- )
425
-
426
- response = client.put(
427
- f"/api/workflows/{tab_id}/definition",
428
- json={"definition": definition},
429
- )
430
-
431
- assert response.status_code == 400
432
- assert response.json()["detail"] == (
433
- "Agent nodes must be created or deleted through workflow node APIs"
434
- )
435
- untouched = client.get(f"/api/nodes/{agent_node['id']}")
436
- assert untouched.status_code == 200
437
-
438
-
439
- def test_activate_empty_workflow_fails_with_validation_errors(client: TestClient):
440
- tab = client.post("/api/workflows", json={"title": "Empty"}).json()
441
-
442
- response = client.post(f"/api/workflows/{tab['id']}/activate")
443
-
444
- assert response.status_code == 400
445
- errors = response.json()["detail"]["errors"]
446
- assert any(
447
- error["message"] == "Add at least one node before activating this workflow"
448
- for error in errors
449
- )
450
- detail = client.get(f"/api/workflows/{tab['id']}").json()
451
- assert detail["workflow"]["activation_state"] == "inactive"
452
-
453
-
454
- def test_activate_agent_only_workflow_succeeds_without_trigger(client: TestClient):
455
- tab = client.post("/api/workflows", json={"title": "Collaborative"}).json()
456
- worker = _create_agent_node(client, tab_id=tab["id"], name="Worker")
457
-
458
- response = client.post(f"/api/workflows/{tab['id']}/activate")
459
-
460
- assert response.status_code == 200
461
- assert response.json()["activation_state"] == "active"
462
- detail = client.get(f"/api/workflows/{tab['id']}").json()
463
- assert detail["workflow"]["activation_state"] == "active"
464
- assert detail["nodes"][0]["id"] == worker["id"]
465
-
466
-
467
- def test_activate_valid_manual_trigger_graph_succeeds(client: TestClient):
468
- tab = client.post("/api/workflows", json={"title": "Manual"}).json()
469
- trigger = _create_graph_node(
470
- client,
471
- tab_id=tab["id"],
472
- node_type="trigger",
473
- name="Manual start",
474
- config={"kind": "manual", "output_type": "string", "message": "Run"},
475
- )
476
-
477
- response = client.post(f"/api/workflows/{tab['id']}/activate")
478
-
479
- assert response.status_code == 200
480
- assert response.json()["activation_state"] == "active"
481
- detail = client.get(f"/api/workflows/{tab['id']}").json()
482
- assert detail["workflow"]["activation_state"] == "active"
483
- assert detail["nodes"][0]["id"] == trigger["id"]
484
-
485
-
486
- def test_active_workflow_locks_semantic_edits_but_allows_view_updates(
487
- client: TestClient,
488
- ):
489
- tab = client.post("/api/workflows", json={"title": "Locked"}).json()
490
- trigger = _create_graph_node(
491
- client,
492
- tab_id=tab["id"],
493
- node_type="trigger",
494
- name="Manual start",
495
- config={"kind": "manual", "output_type": "string", "message": "Run"},
496
- )
497
- assert client.post(f"/api/workflows/{tab['id']}/activate").status_code == 200
498
-
499
- create_response = client.post(
500
- f"/api/workflows/{tab['id']}/nodes",
501
- json={"node_type": "trigger", "config": {"kind": "manual"}},
502
- )
503
- assert create_response.status_code == 400
504
- assert "active" in create_response.json()["detail"]
505
-
506
- definition = deepcopy(
507
- client.get(f"/api/workflows/{tab['id']}").json()["workflow"]["definition"]
508
- )
509
- definition["nodes"][0]["config"]["message"] = "Changed"
510
- semantic_response = client.put(
511
- f"/api/workflows/{tab['id']}/definition",
512
- json={"definition": definition},
513
- )
514
- assert semantic_response.status_code == 400
515
- assert "active" in semantic_response.json()["detail"]
516
-
517
- view_definition = deepcopy(
518
- client.get(f"/api/workflows/{tab['id']}").json()["workflow"]["definition"]
519
- )
520
- view_definition["view"] = {"positions": {trigger["id"]: {"x": 12, "y": 24}}}
521
- view_response = client.put(
522
- f"/api/workflows/{tab['id']}/definition",
523
- json={"definition": view_definition},
524
- )
525
- assert view_response.status_code == 200
526
- assert view_response.json()["activation_state"] == "active"
527
- assert view_response.json()["definition"]["view"]["positions"][trigger["id"]] == {
528
- "x": 12.0,
529
- "y": 24.0,
530
- }
531
-
532
-
533
- def test_deactivate_interrupts_running_workflow_nodes(
534
- monkeypatch,
535
- client: TestClient,
536
- ):
537
- from flowent.models import AgentState
538
- from flowent.registry import registry
539
-
540
- tab = client.post("/api/workflows", json={"title": "Deactivate"}).json()
541
- trigger = _create_graph_node(
542
- client,
543
- tab_id=tab["id"],
544
- node_type="trigger",
545
- name="Manual start",
546
- config={
547
- "kind": "manual",
548
- "output_type": "parts",
549
- "message": [{"type": "text", "text": "Run"}],
550
- },
551
- )
552
- worker = _create_agent_node(client, tab_id=tab["id"], name="Worker")
553
- assert (
554
- client.post(
555
- f"/api/workflows/{tab['id']}/edges",
556
- json={"from_node_id": trigger["id"], "to_node_id": worker["id"]},
557
- ).status_code
558
- == 200
559
- )
560
- assert client.post(f"/api/workflows/{tab['id']}/activate").status_code == 200
561
- live_worker = registry.get(worker["id"])
562
- assert live_worker is not None
563
- live_worker.set_state(AgentState.RUNNING, "test")
564
- interrupted: list[str] = []
565
-
566
- def fake_request_interrupt() -> bool:
567
- interrupted.append(live_worker.uuid)
568
- live_worker.set_state(AgentState.IDLE, "interrupted")
569
- return True
570
-
571
- monkeypatch.setattr(live_worker, "request_interrupt", fake_request_interrupt)
572
-
573
- response = client.post(f"/api/workflows/{tab['id']}/deactivate")
574
-
575
- assert response.status_code == 200
576
- assert response.json()["activation_state"] == "inactive"
577
- assert interrupted == [worker["id"]]
578
- assert live_worker.state == AgentState.IDLE
579
-
580
-
581
- def test_llm_node_and_typed_port_validation(client: TestClient):
582
- from flowent.models import GraphEdge
583
- from flowent.workspace_store import workspace_store
584
-
585
- provider = client.post(
586
- "/api/providers",
587
- json={
588
- "name": "Primary",
589
- "type": "openai_compatible",
590
- "base_url": "https://api.example.com",
591
- "models": [{"model": "gpt-5", "structured_output": True}],
592
- },
593
- ).json()
594
- tab = client.post("/api/workflows", json={"title": "Typed"}).json()
595
- trigger = _create_graph_node(
596
- client,
597
- tab_id=tab["id"],
598
- node_type="trigger",
599
- name="Text trigger",
600
- config={"kind": "manual", "output_type": "string", "message": "Run"},
601
- )
602
- llm = _create_graph_node(
603
- client,
604
- tab_id=tab["id"],
605
- node_type="llm",
606
- name="JSON reader",
607
- config={
608
- "model": {"provider_id": provider["id"], "model": "gpt-5"},
609
- "system_prompt": "Read input.",
610
- "temperature": 0,
611
- "max_output_tokens": 100,
612
- "stop_sequences": [],
613
- "response_format": {"kind": "text"},
614
- "input_type": "json",
615
- "output_type": "string",
616
- },
617
- )
618
- stored_tab = workspace_store.get_tab(tab["id"])
619
- assert stored_tab is not None
620
- stored_tab.definition.edges.append(
621
- GraphEdge(
622
- id="invalid-edge",
623
- tab_id=tab["id"],
624
- from_node_id=trigger["id"],
625
- from_port_key="out",
626
- to_node_id=llm["id"],
627
- to_port_key="in",
628
- )
629
- )
630
- workspace_store.upsert_tab(stored_tab)
631
-
632
- response = client.post(f"/api/workflows/{tab['id']}/activate")
633
-
634
- assert response.status_code == 400
635
- messages = [error["message"] for error in response.json()["detail"]["errors"]]
636
- assert any("port type mismatch" in message for message in messages)
637
-
638
-
639
- def test_structured_output_false_blocks_json_schema_llm(client: TestClient):
640
- provider = client.post(
641
- "/api/providers",
642
- json={
643
- "name": "Primary",
644
- "type": "openai_compatible",
645
- "base_url": "https://api.example.com",
646
- "models": [{"model": "gpt-5", "structured_output": False}],
647
- },
648
- ).json()
649
- tab = client.post("/api/workflows", json={"title": "Structured"}).json()
650
- trigger = _create_graph_node(
651
- client,
652
- tab_id=tab["id"],
653
- node_type="trigger",
654
- name="JSON trigger",
655
- config={"kind": "manual", "output_type": "json", "message": {"task": "Run"}},
656
- )
657
- llm = _create_graph_node(
658
- client,
659
- tab_id=tab["id"],
660
- node_type="llm",
661
- name="JSON writer",
662
- config={
663
- "model": {"provider_id": provider["id"], "model": "gpt-5"},
664
- "system_prompt": "Return JSON.",
665
- "temperature": 0,
666
- "max_output_tokens": 100,
667
- "stop_sequences": [],
668
- "response_format": {
669
- "kind": "json_schema",
670
- "schema": {"type": "object"},
671
- },
672
- "input_type": "json",
673
- "output_type": "json",
674
- },
675
- )
676
- assert (
677
- client.post(
678
- f"/api/workflows/{tab['id']}/edges",
679
- json={"from_node_id": trigger["id"], "to_node_id": llm["id"]},
680
- ).status_code
681
- == 200
682
- )
683
-
684
- response = client.post(f"/api/workflows/{tab['id']}/activate")
685
-
686
- assert response.status_code == 400
687
- messages = [error["message"] for error in response.json()["detail"]["errors"]]
688
- assert "llm model does not support structured_output" in messages