flowent 0.0.6 → 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 (398) hide show
  1. package/README.md +1 -4
  2. package/backend/README.md +1 -4
  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 +217 -3094
  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__/mcp_service.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 -2508
  86. package/backend/src/flowent/image_assets.py +0 -356
  87. package/backend/src/flowent/mcp_service.py +0 -1918
  88. package/backend/src/flowent/model_metadata.py +0 -102
  89. package/backend/src/flowent/models/__init__.py +0 -125
  90. package/backend/src/flowent/models/__pycache__/__init__.cpython-313.pyc +0 -0
  91. package/backend/src/flowent/models/__pycache__/agent.cpython-313.pyc +0 -0
  92. package/backend/src/flowent/models/__pycache__/base.cpython-313.pyc +0 -0
  93. package/backend/src/flowent/models/__pycache__/blueprint.cpython-313.pyc +0 -0
  94. package/backend/src/flowent/models/__pycache__/content.cpython-313.pyc +0 -0
  95. package/backend/src/flowent/models/__pycache__/delta.cpython-313.pyc +0 -0
  96. package/backend/src/flowent/models/__pycache__/event.cpython-313.pyc +0 -0
  97. package/backend/src/flowent/models/__pycache__/graph.cpython-313.pyc +0 -0
  98. package/backend/src/flowent/models/__pycache__/history.cpython-313.pyc +0 -0
  99. package/backend/src/flowent/models/__pycache__/llm.cpython-313.pyc +0 -0
  100. package/backend/src/flowent/models/__pycache__/message.cpython-313.pyc +0 -0
  101. package/backend/src/flowent/models/__pycache__/tab.cpython-313.pyc +0 -0
  102. package/backend/src/flowent/models/__pycache__/todo.cpython-313.pyc +0 -0
  103. package/backend/src/flowent/models/agent.py +0 -34
  104. package/backend/src/flowent/models/base.py +0 -24
  105. package/backend/src/flowent/models/blueprint.py +0 -176
  106. package/backend/src/flowent/models/content.py +0 -164
  107. package/backend/src/flowent/models/delta.py +0 -44
  108. package/backend/src/flowent/models/event.py +0 -51
  109. package/backend/src/flowent/models/graph.py +0 -472
  110. package/backend/src/flowent/models/history.py +0 -272
  111. package/backend/src/flowent/models/llm.py +0 -62
  112. package/backend/src/flowent/models/message.py +0 -33
  113. package/backend/src/flowent/models/tab.py +0 -85
  114. package/backend/src/flowent/models/todo.py +0 -10
  115. package/backend/src/flowent/network.py +0 -146
  116. package/backend/src/flowent/observability_service.py +0 -218
  117. package/backend/src/flowent/prompts/__init__.py +0 -67
  118. package/backend/src/flowent/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  119. package/backend/src/flowent/prompts/__pycache__/common.cpython-313.pyc +0 -0
  120. package/backend/src/flowent/prompts/__pycache__/steward.cpython-313.pyc +0 -0
  121. package/backend/src/flowent/prompts/common.py +0 -250
  122. package/backend/src/flowent/prompts/steward.py +0 -64
  123. package/backend/src/flowent/providers/__init__.py +0 -23
  124. package/backend/src/flowent/providers/__pycache__/__init__.cpython-313.pyc +0 -0
  125. package/backend/src/flowent/providers/__pycache__/anthropic.cpython-313.pyc +0 -0
  126. package/backend/src/flowent/providers/__pycache__/base_url.cpython-313.pyc +0 -0
  127. package/backend/src/flowent/providers/__pycache__/configuration.cpython-313.pyc +0 -0
  128. package/backend/src/flowent/providers/__pycache__/content.cpython-313.pyc +0 -0
  129. package/backend/src/flowent/providers/__pycache__/errors.cpython-313.pyc +0 -0
  130. package/backend/src/flowent/providers/__pycache__/gateway.cpython-313.pyc +0 -0
  131. package/backend/src/flowent/providers/__pycache__/headers.cpython-313.pyc +0 -0
  132. package/backend/src/flowent/providers/__pycache__/management.cpython-313.pyc +0 -0
  133. package/backend/src/flowent/providers/__pycache__/openai.cpython-313.pyc +0 -0
  134. package/backend/src/flowent/providers/__pycache__/openai_responses.cpython-313.pyc +0 -0
  135. package/backend/src/flowent/providers/__pycache__/registry.cpython-313.pyc +0 -0
  136. package/backend/src/flowent/providers/__pycache__/sse.cpython-313.pyc +0 -0
  137. package/backend/src/flowent/providers/__pycache__/thinking.cpython-313.pyc +0 -0
  138. package/backend/src/flowent/providers/anthropic.py +0 -468
  139. package/backend/src/flowent/providers/base_url.py +0 -60
  140. package/backend/src/flowent/providers/configuration.py +0 -189
  141. package/backend/src/flowent/providers/content.py +0 -122
  142. package/backend/src/flowent/providers/errors.py +0 -223
  143. package/backend/src/flowent/providers/gateway.py +0 -169
  144. package/backend/src/flowent/providers/gemini.py +0 -447
  145. package/backend/src/flowent/providers/headers.py +0 -20
  146. package/backend/src/flowent/providers/management.py +0 -96
  147. package/backend/src/flowent/providers/ollama.py +0 -293
  148. package/backend/src/flowent/providers/openai.py +0 -422
  149. package/backend/src/flowent/providers/openai_responses.py +0 -655
  150. package/backend/src/flowent/providers/registry.py +0 -144
  151. package/backend/src/flowent/providers/sse.py +0 -31
  152. package/backend/src/flowent/providers/thinking.py +0 -79
  153. package/backend/src/flowent/registry.py +0 -73
  154. package/backend/src/flowent/role_management.py +0 -267
  155. package/backend/src/flowent/routes/__init__.py +0 -28
  156. package/backend/src/flowent/routes/__pycache__/__init__.cpython-313.pyc +0 -0
  157. package/backend/src/flowent/routes/__pycache__/access.cpython-313.pyc +0 -0
  158. package/backend/src/flowent/routes/__pycache__/assistant.cpython-313.pyc +0 -0
  159. package/backend/src/flowent/routes/__pycache__/image_assets.cpython-313.pyc +0 -0
  160. package/backend/src/flowent/routes/__pycache__/mcp.cpython-313.pyc +0 -0
  161. package/backend/src/flowent/routes/__pycache__/meta.cpython-313.pyc +0 -0
  162. package/backend/src/flowent/routes/__pycache__/nodes.cpython-313.pyc +0 -0
  163. package/backend/src/flowent/routes/__pycache__/prompts.cpython-313.pyc +0 -0
  164. package/backend/src/flowent/routes/__pycache__/providers_route.cpython-313.pyc +0 -0
  165. package/backend/src/flowent/routes/__pycache__/roles.cpython-313.pyc +0 -0
  166. package/backend/src/flowent/routes/__pycache__/settings.cpython-313.pyc +0 -0
  167. package/backend/src/flowent/routes/__pycache__/tabs.cpython-313.pyc +0 -0
  168. package/backend/src/flowent/routes/__pycache__/ws.cpython-313.pyc +0 -0
  169. package/backend/src/flowent/routes/access.py +0 -48
  170. package/backend/src/flowent/routes/assistant.py +0 -155
  171. package/backend/src/flowent/routes/image_assets.py +0 -33
  172. package/backend/src/flowent/routes/mcp.py +0 -125
  173. package/backend/src/flowent/routes/meta.py +0 -28
  174. package/backend/src/flowent/routes/nodes.py +0 -413
  175. package/backend/src/flowent/routes/prompts.py +0 -46
  176. package/backend/src/flowent/routes/providers_route.py +0 -365
  177. package/backend/src/flowent/routes/roles.py +0 -207
  178. package/backend/src/flowent/routes/settings.py +0 -328
  179. package/backend/src/flowent/routes/tabs.py +0 -310
  180. package/backend/src/flowent/routes/ws.py +0 -33
  181. package/backend/src/flowent/runtime.py +0 -165
  182. package/backend/src/flowent/security.py +0 -57
  183. package/backend/src/flowent/settings.py +0 -2518
  184. package/backend/src/flowent/settings_management.py +0 -298
  185. package/backend/src/flowent/state_db.py +0 -120
  186. package/backend/src/flowent/static/assets/AssistantPage-VBohhz4d.js +0 -1
  187. package/backend/src/flowent/static/assets/ChannelsPage-CIydPZA_.js +0 -1
  188. package/backend/src/flowent/static/assets/McpPage-CHPm2TPY.js +0 -7
  189. package/backend/src/flowent/static/assets/PageScaffold-DteOA8V7.js +0 -1
  190. package/backend/src/flowent/static/assets/PromptsPage-CSmJ3sZg.js +0 -1
  191. package/backend/src/flowent/static/assets/ProvidersPage-sl2jeG4e.js +0 -3
  192. package/backend/src/flowent/static/assets/RolesPage-DCe7W6Km.js +0 -1
  193. package/backend/src/flowent/static/assets/SettingsPage-Bix9e63E.js +0 -3
  194. package/backend/src/flowent/static/assets/ToolsPage-favNkj5C.js +0 -1
  195. package/backend/src/flowent/static/assets/WorkspaceCommandDialog-DRS6wiD6.js +0 -1
  196. package/backend/src/flowent/static/assets/WorkspacePage-KuaDjt_D.js +0 -3
  197. package/backend/src/flowent/static/assets/WorkspacePanels-BZxBw8M5.js +0 -1
  198. package/backend/src/flowent/static/assets/alert-dialog-DIBUCmqM.js +0 -1
  199. package/backend/src/flowent/static/assets/datetime-eJqd0V2S.js +0 -1
  200. package/backend/src/flowent/static/assets/dialog-BOvHIBrg.js +0 -1
  201. package/backend/src/flowent/static/assets/elk-worker.min-C9JGDOE-.js +0 -6312
  202. package/backend/src/flowent/static/assets/graph-vendor-CHpVij2M.css +0 -1
  203. package/backend/src/flowent/static/assets/graph-vendor-DRq_-6fV.js +0 -7
  204. package/backend/src/flowent/static/assets/index-Biio-CoI.js +0 -10
  205. package/backend/src/flowent/static/assets/index-CmQvO7sl.css +0 -1
  206. package/backend/src/flowent/static/assets/layout.worker-jMHqAFbP.js +0 -24
  207. package/backend/src/flowent/static/assets/markdown-vendor-C9RtvaJh.js +0 -29
  208. package/backend/src/flowent/static/assets/modelParams-DcEhGnu0.js +0 -1
  209. package/backend/src/flowent/static/assets/react-vendor-mEs_JJxa.js +0 -9
  210. package/backend/src/flowent/static/assets/roles-BbIEIMeG.js +0 -1
  211. package/backend/src/flowent/static/assets/rolldown-runtime-BYbx6iT9.js +0 -1
  212. package/backend/src/flowent/static/assets/select-D9SwnlXF.js +0 -1
  213. package/backend/src/flowent/static/assets/surface-Bzr1FRG4.js +0 -1
  214. package/backend/src/flowent/static/assets/triState-DgLlKdRR.js +0 -1
  215. package/backend/src/flowent/static/assets/ui-vendor-UazN8rcv.js +0 -51
  216. package/backend/src/flowent/static/favicon.svg +0 -4
  217. package/backend/src/flowent/tools/__init__.py +0 -275
  218. package/backend/src/flowent/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  219. package/backend/src/flowent/tools/__pycache__/connect.cpython-313.pyc +0 -0
  220. package/backend/src/flowent/tools/__pycache__/contacts.cpython-313.pyc +0 -0
  221. package/backend/src/flowent/tools/__pycache__/create_agent.cpython-313.pyc +0 -0
  222. package/backend/src/flowent/tools/__pycache__/create_tab.cpython-313.pyc +0 -0
  223. package/backend/src/flowent/tools/__pycache__/delete_tab.cpython-313.pyc +0 -0
  224. package/backend/src/flowent/tools/__pycache__/edit.cpython-313.pyc +0 -0
  225. package/backend/src/flowent/tools/__pycache__/exec.cpython-313.pyc +0 -0
  226. package/backend/src/flowent/tools/__pycache__/fetch.cpython-313.pyc +0 -0
  227. package/backend/src/flowent/tools/__pycache__/idle.cpython-313.pyc +0 -0
  228. package/backend/src/flowent/tools/__pycache__/list_roles.cpython-313.pyc +0 -0
  229. package/backend/src/flowent/tools/__pycache__/list_tabs.cpython-313.pyc +0 -0
  230. package/backend/src/flowent/tools/__pycache__/list_tools.cpython-313.pyc +0 -0
  231. package/backend/src/flowent/tools/__pycache__/manage_prompts.cpython-313.pyc +0 -0
  232. package/backend/src/flowent/tools/__pycache__/manage_providers.cpython-313.pyc +0 -0
  233. package/backend/src/flowent/tools/__pycache__/manage_roles.cpython-313.pyc +0 -0
  234. package/backend/src/flowent/tools/__pycache__/manage_settings.cpython-313.pyc +0 -0
  235. package/backend/src/flowent/tools/__pycache__/mcp.cpython-313.pyc +0 -0
  236. package/backend/src/flowent/tools/__pycache__/read.cpython-313.pyc +0 -0
  237. package/backend/src/flowent/tools/__pycache__/send.cpython-313.pyc +0 -0
  238. package/backend/src/flowent/tools/__pycache__/set_permissions.cpython-313.pyc +0 -0
  239. package/backend/src/flowent/tools/__pycache__/sleep.cpython-313.pyc +0 -0
  240. package/backend/src/flowent/tools/__pycache__/todo.cpython-313.pyc +0 -0
  241. package/backend/src/flowent/tools/connect.py +0 -100
  242. package/backend/src/flowent/tools/contacts.py +0 -22
  243. package/backend/src/flowent/tools/create_agent.py +0 -191
  244. package/backend/src/flowent/tools/create_tab.py +0 -61
  245. package/backend/src/flowent/tools/delete_tab.py +0 -39
  246. package/backend/src/flowent/tools/edit.py +0 -142
  247. package/backend/src/flowent/tools/exec.py +0 -118
  248. package/backend/src/flowent/tools/fetch.py +0 -85
  249. package/backend/src/flowent/tools/idle.py +0 -27
  250. package/backend/src/flowent/tools/list_roles.py +0 -75
  251. package/backend/src/flowent/tools/list_tabs.py +0 -100
  252. package/backend/src/flowent/tools/list_tools.py +0 -28
  253. package/backend/src/flowent/tools/manage_prompts.py +0 -102
  254. package/backend/src/flowent/tools/manage_providers.py +0 -220
  255. package/backend/src/flowent/tools/manage_roles.py +0 -275
  256. package/backend/src/flowent/tools/manage_settings.py +0 -364
  257. package/backend/src/flowent/tools/mcp.py +0 -199
  258. package/backend/src/flowent/tools/read.py +0 -152
  259. package/backend/src/flowent/tools/send.py +0 -68
  260. package/backend/src/flowent/tools/set_permissions.py +0 -99
  261. package/backend/src/flowent/tools/sleep.py +0 -41
  262. package/backend/src/flowent/tools/todo.py +0 -51
  263. package/backend/src/flowent/workspace_store.py +0 -479
  264. package/backend/tests/__init__.py +0 -0
  265. package/backend/tests/__pycache__/__init__.cpython-313.pyc +0 -0
  266. package/backend/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  267. package/backend/tests/conftest.py +0 -6
  268. package/backend/tests/integration/api/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  269. package/backend/tests/integration/api/__pycache__/test_access_api.cpython-313-pytest-9.0.3.pyc +0 -0
  270. package/backend/tests/integration/api/__pycache__/test_assistant_api.cpython-313-pytest-9.0.3.pyc +0 -0
  271. package/backend/tests/integration/api/__pycache__/test_frontend_mounting.cpython-313-pytest-9.0.3.pyc +0 -0
  272. package/backend/tests/integration/api/__pycache__/test_mcp_api.cpython-313-pytest-9.0.3.pyc +0 -0
  273. package/backend/tests/integration/api/__pycache__/test_meta_api.cpython-313-pytest-9.0.3.pyc +0 -0
  274. package/backend/tests/integration/api/__pycache__/test_nodes_api.cpython-313-pytest-9.0.3.pyc +0 -0
  275. package/backend/tests/integration/api/__pycache__/test_prompts_api.cpython-313-pytest-9.0.3.pyc +0 -0
  276. package/backend/tests/integration/api/__pycache__/test_roles_api.cpython-313-pytest-9.0.3.pyc +0 -0
  277. package/backend/tests/integration/api/__pycache__/test_tabs_api.cpython-313-pytest-9.0.3.pyc +0 -0
  278. package/backend/tests/integration/api/conftest.py +0 -29
  279. package/backend/tests/integration/api/test_access_api.py +0 -182
  280. package/backend/tests/integration/api/test_assistant_api.py +0 -354
  281. package/backend/tests/integration/api/test_frontend_mounting.py +0 -61
  282. package/backend/tests/integration/api/test_mcp_api.py +0 -116
  283. package/backend/tests/integration/api/test_meta_api.py +0 -33
  284. package/backend/tests/integration/api/test_nodes_api.py +0 -722
  285. package/backend/tests/integration/api/test_prompts_api.py +0 -47
  286. package/backend/tests/integration/api/test_roles_api.py +0 -228
  287. package/backend/tests/integration/api/test_tabs_api.py +0 -802
  288. package/backend/tests/unit/__pycache__/test_access.cpython-313-pytest-9.0.3.pyc +0 -0
  289. package/backend/tests/unit/__pycache__/test_cli.cpython-313-pytest-9.0.3.pyc +0 -0
  290. package/backend/tests/unit/__pycache__/test_graph_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  291. package/backend/tests/unit/__pycache__/test_network.cpython-313-pytest-9.0.3.pyc +0 -0
  292. package/backend/tests/unit/__pycache__/test_state_sqlite_storage.cpython-313-pytest-9.0.3.pyc +0 -0
  293. package/backend/tests/unit/__pycache__/test_workspace_store.cpython-313-pytest-9.0.3.pyc +0 -0
  294. package/backend/tests/unit/agent/__pycache__/test_agent_public_api.cpython-313-pytest-9.0.3.pyc +0 -0
  295. package/backend/tests/unit/agent/__pycache__/test_agent_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  296. package/backend/tests/unit/agent/test_agent_public_api.py +0 -837
  297. package/backend/tests/unit/agent/test_agent_runtime.py +0 -2942
  298. package/backend/tests/unit/channels/__pycache__/test_telegram_channel.cpython-313-pytest-9.0.3.pyc +0 -0
  299. package/backend/tests/unit/channels/test_telegram_channel.py +0 -552
  300. package/backend/tests/unit/logging/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
  301. package/backend/tests/unit/logging/test_logging.py +0 -132
  302. package/backend/tests/unit/prompts/__pycache__/test_prompts.cpython-313-pytest-9.0.3.pyc +0 -0
  303. package/backend/tests/unit/prompts/test_prompts.py +0 -570
  304. package/backend/tests/unit/providers/__pycache__/test_anthropic_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  305. package/backend/tests/unit/providers/__pycache__/test_errors.cpython-313-pytest-9.0.3.pyc +0 -0
  306. package/backend/tests/unit/providers/__pycache__/test_extract_delta_parts.cpython-313-pytest-9.0.3.pyc +0 -0
  307. package/backend/tests/unit/providers/__pycache__/test_openai_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  308. package/backend/tests/unit/providers/__pycache__/test_openai_responses.cpython-313-pytest-9.0.3.pyc +0 -0
  309. package/backend/tests/unit/providers/__pycache__/test_provider_gateway.cpython-313-pytest-9.0.3.pyc +0 -0
  310. package/backend/tests/unit/providers/__pycache__/test_think_tag_parser.cpython-313-pytest-9.0.3.pyc +0 -0
  311. package/backend/tests/unit/providers/test_anthropic_provider.py +0 -185
  312. package/backend/tests/unit/providers/test_errors.py +0 -68
  313. package/backend/tests/unit/providers/test_extract_delta_parts.py +0 -22
  314. package/backend/tests/unit/providers/test_openai_provider.py +0 -139
  315. package/backend/tests/unit/providers/test_openai_responses.py +0 -402
  316. package/backend/tests/unit/providers/test_provider_gateway.py +0 -359
  317. package/backend/tests/unit/providers/test_think_tag_parser.py +0 -36
  318. package/backend/tests/unit/routes/__pycache__/test_prompts_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  319. package/backend/tests/unit/routes/__pycache__/test_providers_route.cpython-313-pytest-9.0.3.pyc +0 -0
  320. package/backend/tests/unit/routes/__pycache__/test_roles_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  321. package/backend/tests/unit/routes/__pycache__/test_settings_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  322. package/backend/tests/unit/routes/test_prompts_routes.py +0 -104
  323. package/backend/tests/unit/routes/test_providers_route.py +0 -370
  324. package/backend/tests/unit/routes/test_roles_routes.py +0 -535
  325. package/backend/tests/unit/routes/test_settings_routes.py +0 -1142
  326. package/backend/tests/unit/runtime/__pycache__/test_bootstrap_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  327. package/backend/tests/unit/runtime/test_bootstrap_runtime.py +0 -1002
  328. package/backend/tests/unit/sandbox/__pycache__/test_sandbox_tools.cpython-313-pytest-9.0.3.pyc +0 -0
  329. package/backend/tests/unit/sandbox/test_sandbox_tools.py +0 -78
  330. package/backend/tests/unit/security/__pycache__/test_security.cpython-313-pytest-9.0.3.pyc +0 -0
  331. package/backend/tests/unit/security/test_security.py +0 -124
  332. package/backend/tests/unit/settings/__pycache__/test_settings_roles.cpython-313-pytest-9.0.3.pyc +0 -0
  333. package/backend/tests/unit/settings/test_settings_roles.py +0 -751
  334. package/backend/tests/unit/test_access.py +0 -45
  335. package/backend/tests/unit/test_cli.py +0 -124
  336. package/backend/tests/unit/test_graph_runtime.py +0 -72
  337. package/backend/tests/unit/test_network.py +0 -51
  338. package/backend/tests/unit/test_state_sqlite_storage.py +0 -159
  339. package/backend/tests/unit/test_workspace_store.py +0 -231
  340. package/backend/tests/unit/tools/__pycache__/test_connect_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  341. package/backend/tests/unit/tools/__pycache__/test_create_agent_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  342. package/backend/tests/unit/tools/__pycache__/test_delete_tab_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  343. package/backend/tests/unit/tools/__pycache__/test_edit_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  344. package/backend/tests/unit/tools/__pycache__/test_exec_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  345. package/backend/tests/unit/tools/__pycache__/test_fetch_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  346. package/backend/tests/unit/tools/__pycache__/test_manage_prompts_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  347. package/backend/tests/unit/tools/__pycache__/test_manage_providers_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  348. package/backend/tests/unit/tools/__pycache__/test_manage_roles_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  349. package/backend/tests/unit/tools/__pycache__/test_manage_settings_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  350. package/backend/tests/unit/tools/__pycache__/test_read_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  351. package/backend/tests/unit/tools/__pycache__/test_set_permissions_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  352. package/backend/tests/unit/tools/__pycache__/test_todo_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  353. package/backend/tests/unit/tools/__pycache__/test_tool_registry.cpython-313-pytest-9.0.3.pyc +0 -0
  354. package/backend/tests/unit/tools/test_connect_tool.py +0 -228
  355. package/backend/tests/unit/tools/test_create_agent_tool.py +0 -436
  356. package/backend/tests/unit/tools/test_delete_tab_tool.py +0 -116
  357. package/backend/tests/unit/tools/test_edit_tool.py +0 -115
  358. package/backend/tests/unit/tools/test_exec_tool.py +0 -81
  359. package/backend/tests/unit/tools/test_fetch_tool.py +0 -65
  360. package/backend/tests/unit/tools/test_manage_prompts_tool.py +0 -117
  361. package/backend/tests/unit/tools/test_manage_providers_tool.py +0 -460
  362. package/backend/tests/unit/tools/test_manage_roles_tool.py +0 -411
  363. package/backend/tests/unit/tools/test_manage_settings_tool.py +0 -611
  364. package/backend/tests/unit/tools/test_read_tool.py +0 -33
  365. package/backend/tests/unit/tools/test_set_permissions_tool.py +0 -595
  366. package/backend/tests/unit/tools/test_todo_tool.py +0 -37
  367. package/backend/tests/unit/tools/test_tool_registry.py +0 -194
  368. package/dist/frontend/assets/AssistantPage-VBohhz4d.js +0 -1
  369. package/dist/frontend/assets/ChannelsPage-CIydPZA_.js +0 -1
  370. package/dist/frontend/assets/McpPage-CHPm2TPY.js +0 -7
  371. package/dist/frontend/assets/PageScaffold-DteOA8V7.js +0 -1
  372. package/dist/frontend/assets/PromptsPage-CSmJ3sZg.js +0 -1
  373. package/dist/frontend/assets/ProvidersPage-sl2jeG4e.js +0 -3
  374. package/dist/frontend/assets/RolesPage-DCe7W6Km.js +0 -1
  375. package/dist/frontend/assets/SettingsPage-Bix9e63E.js +0 -3
  376. package/dist/frontend/assets/ToolsPage-favNkj5C.js +0 -1
  377. package/dist/frontend/assets/WorkspaceCommandDialog-DRS6wiD6.js +0 -1
  378. package/dist/frontend/assets/WorkspacePage-KuaDjt_D.js +0 -3
  379. package/dist/frontend/assets/WorkspacePanels-BZxBw8M5.js +0 -1
  380. package/dist/frontend/assets/alert-dialog-DIBUCmqM.js +0 -1
  381. package/dist/frontend/assets/datetime-eJqd0V2S.js +0 -1
  382. package/dist/frontend/assets/dialog-BOvHIBrg.js +0 -1
  383. package/dist/frontend/assets/elk-worker.min-C9JGDOE-.js +0 -6312
  384. package/dist/frontend/assets/graph-vendor-CHpVij2M.css +0 -1
  385. package/dist/frontend/assets/graph-vendor-DRq_-6fV.js +0 -7
  386. package/dist/frontend/assets/index-Biio-CoI.js +0 -10
  387. package/dist/frontend/assets/index-CmQvO7sl.css +0 -1
  388. package/dist/frontend/assets/layout.worker-jMHqAFbP.js +0 -24
  389. package/dist/frontend/assets/markdown-vendor-C9RtvaJh.js +0 -29
  390. package/dist/frontend/assets/modelParams-DcEhGnu0.js +0 -1
  391. package/dist/frontend/assets/react-vendor-mEs_JJxa.js +0 -9
  392. package/dist/frontend/assets/roles-BbIEIMeG.js +0 -1
  393. package/dist/frontend/assets/rolldown-runtime-BYbx6iT9.js +0 -1
  394. package/dist/frontend/assets/select-D9SwnlXF.js +0 -1
  395. package/dist/frontend/assets/surface-Bzr1FRG4.js +0 -1
  396. package/dist/frontend/assets/triState-DgLlKdRR.js +0 -1
  397. package/dist/frontend/assets/ui-vendor-UazN8rcv.js +0 -51
  398. package/dist/frontend/favicon.svg +0 -4
@@ -1,802 +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_rejects_removed_mcp_servers_field(client: TestClient):
50
- response = client.post(
51
- "/api/workflows",
52
- json={"title": "Review Task", "mcp_servers": ["filesystem"]},
53
- )
54
-
55
- assert response.status_code == 422
56
-
57
-
58
- def test_create_tab_rejects_removed_goal_field(client: TestClient):
59
- response = client.post(
60
- "/api/workflows",
61
- json={"title": "Review Task", "goal": "Inspect changed files"},
62
- )
63
-
64
- assert response.status_code == 422
65
-
66
-
67
- def test_create_tab_node_and_edge_round_trip(client: TestClient):
68
- create_tab_response = client.post(
69
- "/api/workflows",
70
- json={
71
- "title": "Review Task",
72
- "allow_network": True,
73
- "write_dirs": ["/tmp"],
74
- },
75
- )
76
-
77
- assert create_tab_response.status_code == 200
78
- tab = create_tab_response.json()
79
- tab_id = tab["id"]
80
- assert tab["title"] == "Review Task"
81
- assert "goal" not in tab
82
- assert tab["node_count"] == 0
83
- assert tab["edge_count"] == 0
84
- assert tab["activation_state"] == "inactive"
85
- assert tab["allow_network"] is True
86
- assert tab["write_dirs"] == ["/tmp"]
87
- assert tab["definition"] == {"version": 1, "nodes": [], "edges": []}
88
- assert isinstance(tab["leader_id"], str)
89
-
90
- reader = _create_agent_node(client, tab_id=tab_id, name="Reader")
91
- writer = _create_agent_node(client, tab_id=tab_id, name="Writer")
92
-
93
- assert reader["workflow_id"] == tab_id
94
- assert writer["workflow_id"] == tab_id
95
- assert reader["node_type"] == "agent"
96
- assert writer["node_type"] == "agent"
97
- assert reader["config"]["role_name"] == "Worker"
98
- assert writer["config"]["role_name"] == "Worker"
99
- assert "write_dirs" not in reader["config"]
100
- assert "allow_network" not in reader["config"]
101
-
102
- removed_permission_response = client.post(
103
- f"/api/workflows/{tab_id}/nodes",
104
- json={
105
- "role_name": "Worker",
106
- "write_dirs": ["/tmp"],
107
- },
108
- )
109
- assert removed_permission_response.status_code == 422
110
-
111
- edge_response = client.post(
112
- f"/api/workflows/{tab_id}/edges",
113
- json={
114
- "from_node_id": reader["id"],
115
- "from_port_key": "out",
116
- "to_node_id": writer["id"],
117
- "to_port_key": "in",
118
- },
119
- )
120
-
121
- assert edge_response.status_code == 200
122
- edge = edge_response.json()
123
- assert edge["tab_id"] == tab_id
124
- assert edge["from_node_id"] == reader["id"]
125
- assert edge["from_port_key"] == "out"
126
- assert edge["to_node_id"] == writer["id"]
127
- assert edge["to_port_key"] == "in"
128
-
129
- tab_detail_response = client.get(f"/api/workflows/{tab_id}")
130
- assert tab_detail_response.status_code == 200
131
- tab_detail = tab_detail_response.json()
132
- assert tab_detail["workflow"]["id"] == tab_id
133
- assert "goal" not in tab_detail["workflow"]
134
- assert tab_detail["workflow"]["allow_network"] is True
135
- assert tab_detail["workflow"]["write_dirs"] == ["/tmp"]
136
- assert tab_detail["workflow"]["node_count"] == 2
137
- assert tab_detail["workflow"]["edge_count"] == 1
138
- assert {node["name"] for node in tab_detail["nodes"]} == {"Reader", "Writer"}
139
- assert tab_detail["edges"] == [edge]
140
- assert len(tab_detail["workflow"]["definition"]["nodes"]) == 2
141
- assert tab_detail["workflow"]["definition"]["edges"] == [edge]
142
-
143
- nodes_response = client.get("/api/nodes")
144
- assert nodes_response.status_code == 200
145
- nodes = nodes_response.json()["nodes"]
146
- reader_node = next(node for node in nodes if node["id"] == reader["id"])
147
- writer_node = next(node for node in nodes if node["id"] == writer["id"])
148
- assert reader_node["workflow_id"] == tab_id
149
- assert writer_node["workflow_id"] == tab_id
150
- assert reader_node["connections"] == [writer["id"]]
151
- assert writer_node["connections"] == [reader["id"]]
152
-
153
-
154
- def test_delete_tab_cleans_up_nodes_and_edges(client: TestClient):
155
- create_tab_response = client.post(
156
- "/api/workflows",
157
- json={"title": "Disposable"},
158
- )
159
-
160
- assert create_tab_response.status_code == 200
161
- created_tab = create_tab_response.json()
162
- tab_id = created_tab["id"]
163
- leader_id = created_tab["leader_id"]
164
-
165
- left = _create_agent_node(client, tab_id=tab_id, name="Left")
166
- right = _create_agent_node(client, tab_id=tab_id, name="Right")
167
-
168
- edge_response = client.post(
169
- f"/api/workflows/{tab_id}/edges",
170
- json={"from_node_id": left["id"], "to_node_id": right["id"]},
171
- )
172
- assert edge_response.status_code == 200
173
- edge_id = edge_response.json()["id"]
174
-
175
- delete_response = client.delete(f"/api/workflows/{tab_id}")
176
-
177
- assert delete_response.status_code == 200
178
- assert delete_response.json()["id"] == tab_id
179
- assert set(delete_response.json()["removed_node_ids"]) == {
180
- leader_id,
181
- left["id"],
182
- right["id"],
183
- }
184
- assert delete_response.json()["removed_edge_ids"] == [edge_id]
185
-
186
- tab_detail_response = client.get(f"/api/workflows/{tab_id}")
187
- assert tab_detail_response.status_code == 404
188
-
189
- nodes_response = client.get("/api/nodes")
190
- assert nodes_response.status_code == 200
191
- node_ids = {node["id"] for node in nodes_response.json()["nodes"]}
192
- assert left["id"] not in node_ids
193
- assert right["id"] not in node_ids
194
-
195
-
196
- def test_delete_tab_edge_requires_exact_direction_and_removes_only_target_edge(
197
- client: TestClient,
198
- ):
199
- tab = client.post(
200
- "/api/workflows",
201
- json={"title": "Edge Delete"},
202
- ).json()
203
- tab_id = tab["id"]
204
-
205
- left = _create_agent_node(client, tab_id=tab_id, name="Left")
206
- middle = _create_agent_node(client, tab_id=tab_id, name="Middle")
207
- right = _create_agent_node(client, tab_id=tab_id, name="Right")
208
-
209
- left_to_middle = client.post(
210
- f"/api/workflows/{tab_id}/edges",
211
- json={"from_node_id": left["id"], "to_node_id": middle["id"]},
212
- )
213
- middle_to_right = client.post(
214
- f"/api/workflows/{tab_id}/edges",
215
- json={"from_node_id": middle["id"], "to_node_id": right["id"]},
216
- )
217
-
218
- assert left_to_middle.status_code == 200
219
- assert middle_to_right.status_code == 200
220
-
221
- reverse_delete_response = client.delete(
222
- f"/api/workflows/{tab_id}/edges",
223
- params={
224
- "from_node_id": middle["id"],
225
- "to_node_id": left["id"],
226
- },
227
- )
228
- assert reverse_delete_response.status_code == 404
229
- assert reverse_delete_response.json()["detail"] == "Edge not found"
230
-
231
- delete_response = client.delete(
232
- f"/api/workflows/{tab_id}/edges",
233
- params={
234
- "from_node_id": left["id"],
235
- "to_node_id": middle["id"],
236
- },
237
- )
238
-
239
- assert delete_response.status_code == 200
240
- assert delete_response.json()["from_node_id"] == left["id"]
241
- assert delete_response.json()["to_node_id"] == middle["id"]
242
-
243
- detail = client.get(f"/api/workflows/{tab_id}").json()
244
- remaining_edges = {
245
- (edge["from_node_id"], edge["to_node_id"]) for edge in detail["edges"]
246
- }
247
- assert remaining_edges == {(middle["id"], right["id"])}
248
-
249
-
250
- def test_delete_tab_node_removes_node_and_all_incident_edges(client: TestClient):
251
- tab = client.post(
252
- "/api/workflows",
253
- json={"title": "Node Delete"},
254
- ).json()
255
- tab_id = tab["id"]
256
-
257
- left = _create_agent_node(client, tab_id=tab_id, name="Left")
258
- middle = _create_agent_node(client, tab_id=tab_id, name="Middle")
259
- right = _create_agent_node(client, tab_id=tab_id, name="Right")
260
-
261
- assert (
262
- client.post(
263
- f"/api/workflows/{tab_id}/edges",
264
- json={"from_node_id": left["id"], "to_node_id": middle["id"]},
265
- ).status_code
266
- == 200
267
- )
268
- assert (
269
- client.post(
270
- f"/api/workflows/{tab_id}/edges",
271
- json={"from_node_id": middle["id"], "to_node_id": right["id"]},
272
- ).status_code
273
- == 200
274
- )
275
-
276
- delete_response = client.delete(f"/api/workflows/{tab_id}/nodes/{middle['id']}")
277
-
278
- assert delete_response.status_code == 200
279
- assert delete_response.json()["id"] == middle["id"]
280
-
281
- detail = client.get(f"/api/workflows/{tab_id}").json()
282
- remaining_nodes = {node["id"] for node in detail["nodes"]}
283
- assert middle["id"] not in remaining_nodes
284
- assert left["id"] in remaining_nodes
285
- assert right["id"] in remaining_nodes
286
- assert detail["edges"] == []
287
-
288
-
289
- def test_tab_edge_creation_enforces_directed_ports_and_single_input(
290
- client: TestClient,
291
- ):
292
- tab = client.post(
293
- "/api/workflows",
294
- json={"title": "Edge Validation"},
295
- ).json()
296
- tab_id = tab["id"]
297
- worker = _create_agent_node(client, tab_id=tab_id, name="Worker")
298
- reviewer = _create_agent_node(client, tab_id=tab_id, name="Reviewer")
299
- observer = _create_agent_node(client, tab_id=tab_id, name="Observer")
300
-
301
- self_loop_response = client.post(
302
- f"/api/workflows/{tab_id}/edges",
303
- json={"from_node_id": worker["id"], "to_node_id": worker["id"]},
304
- )
305
- assert self_loop_response.status_code == 400
306
- assert self_loop_response.json()["detail"] == "Self-loop edges are not allowed"
307
-
308
- first_edge_response = client.post(
309
- f"/api/workflows/{tab_id}/edges",
310
- json={"from_node_id": worker["id"], "to_node_id": reviewer["id"]},
311
- )
312
- assert first_edge_response.status_code == 200
313
-
314
- duplicate_edge_response = client.post(
315
- f"/api/workflows/{tab_id}/edges",
316
- json={"from_node_id": worker["id"], "to_node_id": reviewer["id"]},
317
- )
318
- assert duplicate_edge_response.status_code == 400
319
- assert duplicate_edge_response.json()["detail"] == "Duplicate edges are not allowed"
320
-
321
- reverse_edge_response = client.post(
322
- f"/api/workflows/{tab_id}/edges",
323
- json={"from_node_id": reviewer["id"], "to_node_id": worker["id"]},
324
- )
325
- assert reverse_edge_response.status_code == 200
326
-
327
- conflicting_input_response = client.post(
328
- f"/api/workflows/{tab_id}/edges",
329
- json={"from_node_id": observer["id"], "to_node_id": reviewer["id"]},
330
- )
331
- assert conflicting_input_response.status_code == 400
332
- assert (
333
- conflicting_input_response.json()["detail"]
334
- == "Input port 'in' already has an incoming edge"
335
- )
336
-
337
-
338
- def test_duplicate_tab_copies_definition_and_runtime_agents(client: TestClient):
339
- source_tab = client.post(
340
- "/api/workflows",
341
- json={
342
- "title": "Original Workflow",
343
- "allow_network": True,
344
- "write_dirs": ["/tmp"],
345
- },
346
- ).json()
347
- source_tab_id = source_tab["id"]
348
-
349
- reviewer = _create_agent_node(client, tab_id=source_tab_id, name="Reviewer")
350
- formatter = _create_graph_node(
351
- client,
352
- tab_id=source_tab_id,
353
- node_type="code",
354
- name="Formatter",
355
- config={"language": "python"},
356
- )
357
- source_detail = client.get(f"/api/workflows/{source_tab_id}").json()
358
- source_definition = deepcopy(source_detail["workflow"]["definition"])
359
- source_definition["view"] = {
360
- "positions": {
361
- reviewer["id"]: {"x": 20, "y": 40},
362
- formatter["id"]: {"x": 180, "y": 40},
363
- }
364
- }
365
- source_definition["edges"] = [
366
- {
367
- "id": "edge-review",
368
- "from_node_id": reviewer["id"],
369
- "from_port_key": "out",
370
- "to_node_id": formatter["id"],
371
- "to_port_key": "in",
372
- }
373
- ]
374
- update_response = client.put(
375
- f"/api/workflows/{source_tab_id}/definition",
376
- json={"definition": source_definition},
377
- )
378
- assert update_response.status_code == 200
379
-
380
- duplicate_response = client.post(f"/api/workflows/{source_tab_id}/duplicate")
381
-
382
- assert duplicate_response.status_code == 200
383
- duplicated_tab = duplicate_response.json()
384
- assert duplicated_tab["title"] == "Original Workflow Copy"
385
- assert "goal" not in duplicated_tab
386
- assert duplicated_tab["node_count"] == 2
387
- assert duplicated_tab["edge_count"] == 1
388
- assert duplicated_tab["id"] != source_tab_id
389
- assert duplicated_tab["leader_id"] != source_tab["leader_id"]
390
- assert duplicated_tab["activation_state"] == "inactive"
391
- assert duplicated_tab["allow_network"] is True
392
- assert duplicated_tab["write_dirs"] == ["/tmp"]
393
-
394
- duplicated_detail = client.get(f"/api/workflows/{duplicated_tab['id']}").json()
395
- assert "goal" not in duplicated_detail["workflow"]
396
- assert duplicated_detail["workflow"]["allow_network"] is True
397
- assert duplicated_detail["workflow"]["write_dirs"] == ["/tmp"]
398
- assert {node["name"] for node in duplicated_detail["nodes"]} == {
399
- "Reviewer",
400
- "Formatter",
401
- }
402
- duplicated_node_ids = {node["id"] for node in duplicated_detail["nodes"]}
403
- source_node_ids = {node["id"] for node in source_detail["nodes"]}
404
- assert duplicated_node_ids.isdisjoint(source_node_ids)
405
- assert duplicated_detail["edges"][0]["from_node_id"] in duplicated_node_ids
406
- assert duplicated_detail["edges"][0]["to_node_id"] in duplicated_node_ids
407
- assert duplicated_detail["workflow"]["definition"]["view"]["positions"]
408
-
409
-
410
- def test_update_tab_definition_updates_metadata_and_positions(client: TestClient):
411
- tab = client.post(
412
- "/api/workflows",
413
- json={"title": "JSON Editor"},
414
- ).json()
415
- tab_id = tab["id"]
416
-
417
- agent_node = _create_agent_node(client, tab_id=tab_id, name="Draft Reviewer")
418
- code_node = _create_graph_node(
419
- client,
420
- tab_id=tab_id,
421
- node_type="code",
422
- name="Formatter",
423
- )
424
-
425
- current = client.get(f"/api/workflows/{tab_id}").json()["workflow"]["definition"]
426
- definition = deepcopy(current)
427
- for node in definition["nodes"]:
428
- if node["id"] == agent_node["id"]:
429
- node["config"]["name"] = "Final Reviewer"
430
- if node["id"] == code_node["id"]:
431
- node["config"]["name"] = "Formatter"
432
- node["config"]["language"] = "python"
433
- definition["view"] = {
434
- "positions": {
435
- agent_node["id"]: {"x": 60, "y": 80},
436
- code_node["id"]: {"x": 260, "y": 80},
437
- }
438
- }
439
- definition["edges"] = [
440
- {
441
- "id": "edge-control",
442
- "from_node_id": agent_node["id"],
443
- "from_port_key": "out",
444
- "to_node_id": code_node["id"],
445
- "to_port_key": "in",
446
- }
447
- ]
448
-
449
- update_response = client.put(
450
- f"/api/workflows/{tab_id}/definition",
451
- json={"definition": definition},
452
- )
453
-
454
- assert update_response.status_code == 200
455
- updated = update_response.json()
456
- assert updated["definition"]["view"]["positions"][agent_node["id"]] == {
457
- "x": 60.0,
458
- "y": 80.0,
459
- }
460
- assert "kind" not in updated["definition"]["edges"][0]
461
-
462
- detail = client.get(f"/api/workflows/{tab_id}").json()
463
- reviewer_detail = next(
464
- node for node in detail["nodes"] if node["id"] == agent_node["id"]
465
- )
466
- formatter_detail = next(
467
- node for node in detail["nodes"] if node["id"] == code_node["id"]
468
- )
469
- assert reviewer_detail["name"] == "Final Reviewer"
470
- assert reviewer_detail["position"] == {"x": 60.0, "y": 80.0}
471
- assert formatter_detail["config"]["language"] == "python"
472
- assert formatter_detail["position"] == {"x": 260.0, "y": 80.0}
473
-
474
- runtime_node = client.get(f"/api/nodes/{agent_node['id']}")
475
- assert runtime_node.status_code == 200
476
- assert runtime_node.json()["name"] == "Final Reviewer"
477
-
478
-
479
- def test_update_tab_definition_rejects_agent_set_changes(client: TestClient):
480
- tab = client.post(
481
- "/api/workflows",
482
- json={"title": "Guard Rails"},
483
- ).json()
484
- tab_id = tab["id"]
485
-
486
- agent_node = _create_agent_node(client, tab_id=tab_id, name="Existing Worker")
487
- definition = deepcopy(
488
- client.get(f"/api/workflows/{tab_id}").json()["workflow"]["definition"]
489
- )
490
- definition["nodes"].append(
491
- {
492
- "id": "new-agent",
493
- "type": "agent",
494
- "config": {"role_name": "Worker", "name": "Injected Worker"},
495
- "inputs": [
496
- {
497
- "key": "in",
498
- "direction": "in",
499
- "type": "parts",
500
- "required": False,
501
- "multiple": False,
502
- }
503
- ],
504
- "outputs": [
505
- {
506
- "key": "out",
507
- "direction": "out",
508
- "type": "parts",
509
- "required": False,
510
- "multiple": True,
511
- }
512
- ],
513
- }
514
- )
515
-
516
- response = client.put(
517
- f"/api/workflows/{tab_id}/definition",
518
- json={"definition": definition},
519
- )
520
-
521
- assert response.status_code == 400
522
- assert response.json()["detail"] == (
523
- "Agent nodes must be created or deleted through workflow node APIs"
524
- )
525
- untouched = client.get(f"/api/nodes/{agent_node['id']}")
526
- assert untouched.status_code == 200
527
-
528
-
529
- def test_activate_empty_workflow_fails_with_validation_errors(client: TestClient):
530
- tab = client.post("/api/workflows", json={"title": "Empty"}).json()
531
-
532
- response = client.post(f"/api/workflows/{tab['id']}/activate")
533
-
534
- assert response.status_code == 400
535
- errors = response.json()["detail"]["errors"]
536
- assert any(
537
- error["message"] == "Add at least one node before activating this workflow"
538
- for error in errors
539
- )
540
- detail = client.get(f"/api/workflows/{tab['id']}").json()
541
- assert detail["workflow"]["activation_state"] == "inactive"
542
-
543
-
544
- def test_activate_agent_only_workflow_succeeds_without_trigger(client: TestClient):
545
- tab = client.post("/api/workflows", json={"title": "Collaborative"}).json()
546
- worker = _create_agent_node(client, tab_id=tab["id"], name="Worker")
547
-
548
- response = client.post(f"/api/workflows/{tab['id']}/activate")
549
-
550
- assert response.status_code == 200
551
- assert response.json()["activation_state"] == "active"
552
- detail = client.get(f"/api/workflows/{tab['id']}").json()
553
- assert detail["workflow"]["activation_state"] == "active"
554
- assert detail["nodes"][0]["id"] == worker["id"]
555
-
556
-
557
- def test_activate_legacy_agent_only_workflow_succeeds_without_trigger(
558
- client: TestClient,
559
- ):
560
- tab = client.post("/api/workflows", json={"title": "Legacy Collaborative"}).json()
561
- worker = _create_agent_node(client, tab_id=tab["id"], name="Worker")
562
- definition = deepcopy(
563
- client.get(f"/api/workflows/{tab['id']}").json()["workflow"]["definition"]
564
- )
565
- definition["nodes"][0]["inputs"][0]["required"] = True
566
- update_response = client.put(
567
- f"/api/workflows/{tab['id']}/definition",
568
- json={"definition": definition},
569
- )
570
- assert update_response.status_code == 200
571
-
572
- response = client.post(f"/api/workflows/{tab['id']}/activate")
573
-
574
- assert response.status_code == 200
575
- assert response.json()["activation_state"] == "active"
576
- detail = client.get(f"/api/workflows/{tab['id']}").json()
577
- assert detail["workflow"]["activation_state"] == "active"
578
- assert detail["nodes"][0]["id"] == worker["id"]
579
-
580
-
581
- def test_activate_valid_manual_trigger_graph_succeeds(client: TestClient):
582
- tab = client.post("/api/workflows", json={"title": "Manual"}).json()
583
- trigger = _create_graph_node(
584
- client,
585
- tab_id=tab["id"],
586
- node_type="trigger",
587
- name="Manual start",
588
- config={"kind": "manual", "output_type": "string", "message": "Run"},
589
- )
590
-
591
- response = client.post(f"/api/workflows/{tab['id']}/activate")
592
-
593
- assert response.status_code == 200
594
- assert response.json()["activation_state"] == "active"
595
- detail = client.get(f"/api/workflows/{tab['id']}").json()
596
- assert detail["workflow"]["activation_state"] == "active"
597
- assert detail["nodes"][0]["id"] == trigger["id"]
598
-
599
-
600
- def test_active_workflow_locks_semantic_edits_but_allows_view_updates(
601
- client: TestClient,
602
- ):
603
- tab = client.post("/api/workflows", json={"title": "Locked"}).json()
604
- trigger = _create_graph_node(
605
- client,
606
- tab_id=tab["id"],
607
- node_type="trigger",
608
- name="Manual start",
609
- config={"kind": "manual", "output_type": "string", "message": "Run"},
610
- )
611
- assert client.post(f"/api/workflows/{tab['id']}/activate").status_code == 200
612
-
613
- create_response = client.post(
614
- f"/api/workflows/{tab['id']}/nodes",
615
- json={"node_type": "trigger", "config": {"kind": "manual"}},
616
- )
617
- assert create_response.status_code == 400
618
- assert "active" in create_response.json()["detail"]
619
-
620
- definition = deepcopy(
621
- client.get(f"/api/workflows/{tab['id']}").json()["workflow"]["definition"]
622
- )
623
- definition["nodes"][0]["config"]["message"] = "Changed"
624
- semantic_response = client.put(
625
- f"/api/workflows/{tab['id']}/definition",
626
- json={"definition": definition},
627
- )
628
- assert semantic_response.status_code == 400
629
- assert "active" in semantic_response.json()["detail"]
630
-
631
- view_definition = deepcopy(
632
- client.get(f"/api/workflows/{tab['id']}").json()["workflow"]["definition"]
633
- )
634
- view_definition["view"] = {"positions": {trigger["id"]: {"x": 12, "y": 24}}}
635
- view_response = client.put(
636
- f"/api/workflows/{tab['id']}/definition",
637
- json={"definition": view_definition},
638
- )
639
- assert view_response.status_code == 200
640
- assert view_response.json()["activation_state"] == "active"
641
- assert view_response.json()["definition"]["view"]["positions"][trigger["id"]] == {
642
- "x": 12.0,
643
- "y": 24.0,
644
- }
645
-
646
-
647
- def test_deactivate_interrupts_running_workflow_nodes(
648
- monkeypatch,
649
- client: TestClient,
650
- ):
651
- from flowent.models import AgentState
652
- from flowent.registry import registry
653
-
654
- tab = client.post("/api/workflows", json={"title": "Deactivate"}).json()
655
- trigger = _create_graph_node(
656
- client,
657
- tab_id=tab["id"],
658
- node_type="trigger",
659
- name="Manual start",
660
- config={
661
- "kind": "manual",
662
- "output_type": "parts",
663
- "message": [{"type": "text", "text": "Run"}],
664
- },
665
- )
666
- worker = _create_agent_node(client, tab_id=tab["id"], name="Worker")
667
- assert (
668
- client.post(
669
- f"/api/workflows/{tab['id']}/edges",
670
- json={"from_node_id": trigger["id"], "to_node_id": worker["id"]},
671
- ).status_code
672
- == 200
673
- )
674
- assert client.post(f"/api/workflows/{tab['id']}/activate").status_code == 200
675
- live_worker = registry.get(worker["id"])
676
- assert live_worker is not None
677
- live_worker.set_state(AgentState.RUNNING, "test")
678
- interrupted: list[str] = []
679
-
680
- def fake_request_interrupt() -> bool:
681
- interrupted.append(live_worker.uuid)
682
- live_worker.set_state(AgentState.IDLE, "interrupted")
683
- return True
684
-
685
- monkeypatch.setattr(live_worker, "request_interrupt", fake_request_interrupt)
686
-
687
- response = client.post(f"/api/workflows/{tab['id']}/deactivate")
688
-
689
- assert response.status_code == 200
690
- assert response.json()["activation_state"] == "inactive"
691
- assert interrupted == [worker["id"]]
692
- assert live_worker.state == AgentState.IDLE
693
-
694
-
695
- def test_llm_node_and_typed_port_validation(client: TestClient):
696
- from flowent.models import GraphEdge
697
- from flowent.workspace_store import workspace_store
698
-
699
- provider = client.post(
700
- "/api/providers",
701
- json={
702
- "name": "Primary",
703
- "type": "openai_compatible",
704
- "base_url": "https://api.example.com",
705
- "models": [{"model": "gpt-5", "structured_output": True}],
706
- },
707
- ).json()
708
- tab = client.post("/api/workflows", json={"title": "Typed"}).json()
709
- trigger = _create_graph_node(
710
- client,
711
- tab_id=tab["id"],
712
- node_type="trigger",
713
- name="Text trigger",
714
- config={"kind": "manual", "output_type": "string", "message": "Run"},
715
- )
716
- llm = _create_graph_node(
717
- client,
718
- tab_id=tab["id"],
719
- node_type="llm",
720
- name="JSON reader",
721
- config={
722
- "model": {"provider_id": provider["id"], "model": "gpt-5"},
723
- "system_prompt": "Read input.",
724
- "temperature": 0,
725
- "max_output_tokens": 100,
726
- "stop_sequences": [],
727
- "response_format": {"kind": "text"},
728
- "input_type": "json",
729
- "output_type": "string",
730
- },
731
- )
732
- stored_tab = workspace_store.get_tab(tab["id"])
733
- assert stored_tab is not None
734
- stored_tab.definition.edges.append(
735
- GraphEdge(
736
- id="invalid-edge",
737
- tab_id=tab["id"],
738
- from_node_id=trigger["id"],
739
- from_port_key="out",
740
- to_node_id=llm["id"],
741
- to_port_key="in",
742
- )
743
- )
744
- workspace_store.upsert_tab(stored_tab)
745
-
746
- response = client.post(f"/api/workflows/{tab['id']}/activate")
747
-
748
- assert response.status_code == 400
749
- messages = [error["message"] for error in response.json()["detail"]["errors"]]
750
- assert any("port type mismatch" in message for message in messages)
751
-
752
-
753
- def test_structured_output_false_blocks_json_schema_llm(client: TestClient):
754
- provider = client.post(
755
- "/api/providers",
756
- json={
757
- "name": "Primary",
758
- "type": "openai_compatible",
759
- "base_url": "https://api.example.com",
760
- "models": [{"model": "gpt-5", "structured_output": False}],
761
- },
762
- ).json()
763
- tab = client.post("/api/workflows", json={"title": "Structured"}).json()
764
- trigger = _create_graph_node(
765
- client,
766
- tab_id=tab["id"],
767
- node_type="trigger",
768
- name="JSON trigger",
769
- config={"kind": "manual", "output_type": "json", "message": {"task": "Run"}},
770
- )
771
- llm = _create_graph_node(
772
- client,
773
- tab_id=tab["id"],
774
- node_type="llm",
775
- name="JSON writer",
776
- config={
777
- "model": {"provider_id": provider["id"], "model": "gpt-5"},
778
- "system_prompt": "Return JSON.",
779
- "temperature": 0,
780
- "max_output_tokens": 100,
781
- "stop_sequences": [],
782
- "response_format": {
783
- "kind": "json_schema",
784
- "schema": {"type": "object"},
785
- },
786
- "input_type": "json",
787
- "output_type": "json",
788
- },
789
- )
790
- assert (
791
- client.post(
792
- f"/api/workflows/{tab['id']}/edges",
793
- json={"from_node_id": trigger["id"], "to_node_id": llm["id"]},
794
- ).status_code
795
- == 200
796
- )
797
-
798
- response = client.post(f"/api/workflows/{tab['id']}/activate")
799
-
800
- assert response.status_code == 400
801
- messages = [error["message"] for error in response.json()["detail"]["errors"]]
802
- assert "llm model does not support structured_output" in messages