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