flowent 0.0.1 → 0.0.4

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 (493) hide show
  1. package/README.md +19 -8
  2. package/backend/.python-version +1 -0
  3. package/backend/pyproject.toml +57 -0
  4. package/backend/src/flowent/__init__.py +3 -0
  5. package/backend/src/flowent/__pycache__/__init__.cpython-313.pyc +0 -0
  6. package/backend/src/flowent/__pycache__/_version.cpython-313.pyc +0 -0
  7. package/backend/src/flowent/__pycache__/access.cpython-313.pyc +0 -0
  8. package/backend/src/flowent/__pycache__/agent.cpython-313.pyc +0 -0
  9. package/backend/src/flowent/__pycache__/assistant_commands.cpython-313.pyc +0 -0
  10. package/backend/src/flowent/__pycache__/cli.cpython-313.pyc +0 -0
  11. package/backend/src/flowent/__pycache__/config.cpython-313.pyc +0 -0
  12. package/backend/src/flowent/__pycache__/events.cpython-313.pyc +0 -0
  13. package/backend/src/flowent/__pycache__/graph_runtime.cpython-313.pyc +0 -0
  14. package/backend/src/flowent/__pycache__/graph_service.cpython-313.pyc +0 -0
  15. package/backend/src/flowent/__pycache__/image_assets.cpython-313.pyc +0 -0
  16. package/backend/src/flowent/__pycache__/logging.cpython-313.pyc +0 -0
  17. package/backend/src/flowent/__pycache__/main.cpython-313.pyc +0 -0
  18. package/backend/src/flowent/__pycache__/mcp_service.cpython-313.pyc +0 -0
  19. package/backend/src/flowent/__pycache__/model_metadata.cpython-313.pyc +0 -0
  20. package/backend/src/flowent/__pycache__/network.cpython-313.pyc +0 -0
  21. package/backend/src/flowent/__pycache__/registry.cpython-313.pyc +0 -0
  22. package/backend/src/flowent/__pycache__/role_management.cpython-313.pyc +0 -0
  23. package/backend/src/flowent/__pycache__/runtime.cpython-313.pyc +0 -0
  24. package/backend/src/flowent/__pycache__/sandbox.cpython-313.pyc +0 -0
  25. package/backend/src/flowent/__pycache__/security.cpython-313.pyc +0 -0
  26. package/backend/src/flowent/__pycache__/settings.cpython-313.pyc +0 -0
  27. package/backend/src/flowent/__pycache__/settings_management.cpython-313.pyc +0 -0
  28. package/backend/src/flowent/__pycache__/state_db.cpython-313.pyc +0 -0
  29. package/backend/src/flowent/__pycache__/stats_service.cpython-313.pyc +0 -0
  30. package/backend/src/flowent/__pycache__/workspace_store.cpython-313.pyc +0 -0
  31. package/backend/src/flowent/_version.py +7 -0
  32. package/backend/src/flowent/access.py +247 -0
  33. package/backend/src/flowent/agent.py +2808 -0
  34. package/backend/src/flowent/assistant_commands.py +106 -0
  35. package/backend/src/flowent/channels/__init__.py +3 -0
  36. package/backend/src/flowent/channels/__pycache__/__init__.cpython-313.pyc +0 -0
  37. package/backend/src/flowent/channels/__pycache__/telegram.cpython-313.pyc +0 -0
  38. package/backend/src/flowent/channels/telegram.py +615 -0
  39. package/backend/src/flowent/cli.py +85 -0
  40. package/backend/src/flowent/config.py +14 -0
  41. package/backend/src/flowent/dev.py +3 -0
  42. package/backend/src/flowent/events.py +157 -0
  43. package/backend/src/flowent/graph_runtime.py +60 -0
  44. package/backend/src/flowent/graph_service.py +1346 -0
  45. package/backend/src/flowent/image_assets.py +356 -0
  46. package/backend/src/flowent/logging.py +155 -0
  47. package/backend/src/flowent/main.py +124 -0
  48. package/backend/src/flowent/mcp_service.py +1904 -0
  49. package/backend/src/flowent/model_metadata.py +98 -0
  50. package/backend/src/flowent/models/__init__.py +121 -0
  51. package/backend/src/flowent/models/__pycache__/__init__.cpython-313.pyc +0 -0
  52. package/backend/src/flowent/models/__pycache__/agent.cpython-313.pyc +0 -0
  53. package/backend/src/flowent/models/__pycache__/base.cpython-313.pyc +0 -0
  54. package/backend/src/flowent/models/__pycache__/blueprint.cpython-313.pyc +0 -0
  55. package/backend/src/flowent/models/__pycache__/content.cpython-313.pyc +0 -0
  56. package/backend/src/flowent/models/__pycache__/delta.cpython-313.pyc +0 -0
  57. package/backend/src/flowent/models/__pycache__/event.cpython-313.pyc +0 -0
  58. package/backend/src/flowent/models/__pycache__/graph.cpython-313.pyc +0 -0
  59. package/backend/src/flowent/models/__pycache__/history.cpython-313.pyc +0 -0
  60. package/backend/src/flowent/models/__pycache__/llm.cpython-313.pyc +0 -0
  61. package/backend/src/flowent/models/__pycache__/message.cpython-313.pyc +0 -0
  62. package/backend/src/flowent/models/__pycache__/tab.cpython-313.pyc +0 -0
  63. package/backend/src/flowent/models/__pycache__/todo.cpython-313.pyc +0 -0
  64. package/backend/src/flowent/models/agent.py +33 -0
  65. package/backend/src/flowent/models/base.py +24 -0
  66. package/backend/src/flowent/models/blueprint.py +176 -0
  67. package/backend/src/flowent/models/content.py +164 -0
  68. package/backend/src/flowent/models/delta.py +44 -0
  69. package/backend/src/flowent/models/event.py +51 -0
  70. package/backend/src/flowent/models/graph.py +437 -0
  71. package/backend/src/flowent/models/history.py +214 -0
  72. package/backend/src/flowent/models/llm.py +61 -0
  73. package/backend/src/flowent/models/message.py +27 -0
  74. package/backend/src/flowent/models/tab.py +48 -0
  75. package/backend/src/flowent/models/todo.py +10 -0
  76. package/backend/src/flowent/network.py +146 -0
  77. package/backend/src/flowent/prompts/__init__.py +67 -0
  78. package/backend/src/flowent/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  79. package/backend/src/flowent/prompts/__pycache__/common.cpython-313.pyc +0 -0
  80. package/backend/src/flowent/prompts/__pycache__/steward.cpython-313.pyc +0 -0
  81. package/backend/src/flowent/prompts/common.py +250 -0
  82. package/backend/src/flowent/prompts/steward.py +64 -0
  83. package/backend/src/flowent/providers/__init__.py +23 -0
  84. package/backend/src/flowent/providers/__pycache__/__init__.cpython-313.pyc +0 -0
  85. package/backend/src/flowent/providers/__pycache__/anthropic.cpython-313.pyc +0 -0
  86. package/backend/src/flowent/providers/__pycache__/base_url.cpython-313.pyc +0 -0
  87. package/backend/src/flowent/providers/__pycache__/configuration.cpython-313.pyc +0 -0
  88. package/backend/src/flowent/providers/__pycache__/content.cpython-313.pyc +0 -0
  89. package/backend/src/flowent/providers/__pycache__/errors.cpython-313.pyc +0 -0
  90. package/backend/src/flowent/providers/__pycache__/gateway.cpython-313.pyc +0 -0
  91. package/backend/src/flowent/providers/__pycache__/headers.cpython-313.pyc +0 -0
  92. package/backend/src/flowent/providers/__pycache__/management.cpython-313.pyc +0 -0
  93. package/backend/src/flowent/providers/__pycache__/openai.cpython-313.pyc +0 -0
  94. package/backend/src/flowent/providers/__pycache__/openai_responses.cpython-313.pyc +0 -0
  95. package/backend/src/flowent/providers/__pycache__/registry.cpython-313.pyc +0 -0
  96. package/backend/src/flowent/providers/__pycache__/sse.cpython-313.pyc +0 -0
  97. package/backend/src/flowent/providers/__pycache__/thinking.cpython-313.pyc +0 -0
  98. package/backend/src/flowent/providers/anthropic.py +468 -0
  99. package/backend/src/flowent/providers/base_url.py +60 -0
  100. package/backend/src/flowent/providers/configuration.py +182 -0
  101. package/backend/src/flowent/providers/content.py +122 -0
  102. package/backend/src/flowent/providers/errors.py +223 -0
  103. package/backend/src/flowent/providers/gateway.py +169 -0
  104. package/backend/src/flowent/providers/gemini.py +447 -0
  105. package/backend/src/flowent/providers/headers.py +20 -0
  106. package/backend/src/flowent/providers/management.py +96 -0
  107. package/backend/src/flowent/providers/ollama.py +293 -0
  108. package/backend/src/flowent/providers/openai.py +422 -0
  109. package/backend/src/flowent/providers/openai_responses.py +655 -0
  110. package/backend/src/flowent/providers/registry.py +144 -0
  111. package/backend/src/flowent/providers/sse.py +31 -0
  112. package/backend/src/flowent/providers/thinking.py +79 -0
  113. package/backend/src/flowent/registry.py +73 -0
  114. package/backend/src/flowent/role_management.py +255 -0
  115. package/backend/src/flowent/routes/__init__.py +30 -0
  116. package/backend/src/flowent/routes/__pycache__/__init__.cpython-313.pyc +0 -0
  117. package/backend/src/flowent/routes/__pycache__/access.cpython-313.pyc +0 -0
  118. package/backend/src/flowent/routes/__pycache__/assistant.cpython-313.pyc +0 -0
  119. package/backend/src/flowent/routes/__pycache__/image_assets.cpython-313.pyc +0 -0
  120. package/backend/src/flowent/routes/__pycache__/mcp.cpython-313.pyc +0 -0
  121. package/backend/src/flowent/routes/__pycache__/meta.cpython-313.pyc +0 -0
  122. package/backend/src/flowent/routes/__pycache__/nodes.cpython-313.pyc +0 -0
  123. package/backend/src/flowent/routes/__pycache__/prompts.cpython-313.pyc +0 -0
  124. package/backend/src/flowent/routes/__pycache__/providers_route.cpython-313.pyc +0 -0
  125. package/backend/src/flowent/routes/__pycache__/roles.cpython-313.pyc +0 -0
  126. package/backend/src/flowent/routes/__pycache__/settings.cpython-313.pyc +0 -0
  127. package/backend/src/flowent/routes/__pycache__/stats.cpython-313.pyc +0 -0
  128. package/backend/src/flowent/routes/__pycache__/tabs.cpython-313.pyc +0 -0
  129. package/backend/src/flowent/routes/__pycache__/ws.cpython-313.pyc +0 -0
  130. package/backend/src/flowent/routes/access.py +48 -0
  131. package/backend/src/flowent/routes/assistant.py +155 -0
  132. package/backend/src/flowent/routes/image_assets.py +33 -0
  133. package/backend/src/flowent/routes/mcp.py +125 -0
  134. package/backend/src/flowent/routes/meta.py +28 -0
  135. package/backend/src/flowent/routes/nodes.py +365 -0
  136. package/backend/src/flowent/routes/prompts.py +46 -0
  137. package/backend/src/flowent/routes/providers_route.py +364 -0
  138. package/backend/src/flowent/routes/roles.py +207 -0
  139. package/backend/src/flowent/routes/settings.py +324 -0
  140. package/backend/src/flowent/routes/stats.py +229 -0
  141. package/backend/src/flowent/routes/tabs.py +292 -0
  142. package/backend/src/flowent/routes/ws.py +33 -0
  143. package/backend/src/flowent/runtime.py +188 -0
  144. package/backend/src/flowent/sandbox.py +45 -0
  145. package/backend/src/flowent/security.py +42 -0
  146. package/backend/src/flowent/settings.py +2467 -0
  147. package/backend/src/flowent/settings_management.py +286 -0
  148. package/backend/src/flowent/state_db.py +120 -0
  149. package/backend/src/flowent/static/assets/AssistantPage-B3Xc08AS.js +1 -0
  150. package/backend/src/flowent/static/assets/ChannelsPage-ByLd28xk.js +1 -0
  151. package/backend/src/flowent/static/assets/HomePage-C0hAx9_l.js +3 -0
  152. package/backend/src/flowent/static/assets/McpPage-DkrYLvBv.js +7 -0
  153. package/backend/src/flowent/static/assets/PageScaffold-D4jO9ooX.js +1 -0
  154. package/backend/src/flowent/static/assets/PromptsPage-DWA7rRJd.js +1 -0
  155. package/backend/src/flowent/static/assets/ProvidersPage-PUWT8seJ.js +3 -0
  156. package/backend/src/flowent/static/assets/RolesPage-CqcclGRw.js +1 -0
  157. package/backend/src/flowent/static/assets/SettingsPage-8tS2cJgX.js +3 -0
  158. package/backend/src/flowent/static/assets/StatsPage-BX9khYzu.js +1 -0
  159. package/backend/src/flowent/static/assets/ToolsPage-9Tl9FdeD.js +1 -0
  160. package/backend/src/flowent/static/assets/WorkspaceCommandDialog-CCXxjDL8.js +1 -0
  161. package/backend/src/flowent/static/assets/WorkspacePanels-aMdJ7ZH7.js +1 -0
  162. package/backend/src/flowent/static/assets/alert-dialog-kFYVQ7oX.js +1 -0
  163. package/backend/src/flowent/static/assets/badge-74-3jsCg.js +1 -0
  164. package/backend/src/flowent/static/assets/constants-XUzFf6i1.js +1 -0
  165. package/backend/src/flowent/static/assets/datetime-m6_O_Ci9.js +1 -0
  166. package/backend/src/flowent/static/assets/dialog-BeGSweF6.js +1 -0
  167. package/backend/src/flowent/static/assets/elk-worker.min-C9JGDOE-.js +6312 -0
  168. package/backend/src/flowent/static/assets/graph-vendor-CHpVij2M.css +1 -0
  169. package/backend/src/flowent/static/assets/graph-vendor-DRq_-6fV.js +7 -0
  170. package/backend/src/flowent/static/assets/index-BHC1Vhy8.css +1 -0
  171. package/backend/src/flowent/static/assets/index-CL1ALZ3r.js +10 -0
  172. package/backend/src/flowent/static/assets/layout.worker-jMHqAFbP.js +24 -0
  173. package/backend/src/flowent/static/assets/markdown-vendor-DVdy_w12.js +29 -0
  174. package/backend/src/flowent/static/assets/modelParams-CaHd0903.js +1 -0
  175. package/backend/src/flowent/static/assets/react-vendor-mEs_JJxa.js +9 -0
  176. package/backend/src/flowent/static/assets/roles-2OLDeTc5.js +1 -0
  177. package/backend/src/flowent/static/assets/rolldown-runtime-BYbx6iT9.js +1 -0
  178. package/backend/src/flowent/static/assets/select-DL_LPeDj.js +1 -0
  179. package/backend/src/flowent/static/assets/shared-CMxbpLeQ.js +1 -0
  180. package/backend/src/flowent/static/assets/triState-DEr3NkXV.js +1 -0
  181. package/backend/src/flowent/static/assets/ui-vendor-Dg9NNnWX.js +51 -0
  182. package/backend/src/flowent/static/index.html +36 -0
  183. package/backend/src/flowent/stats_service.py +218 -0
  184. package/backend/src/flowent/tools/__init__.py +201 -0
  185. package/backend/src/flowent/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  186. package/backend/src/flowent/tools/__pycache__/connect.cpython-313.pyc +0 -0
  187. package/backend/src/flowent/tools/__pycache__/contacts.cpython-313.pyc +0 -0
  188. package/backend/src/flowent/tools/__pycache__/create_agent.cpython-313.pyc +0 -0
  189. package/backend/src/flowent/tools/__pycache__/create_tab.cpython-313.pyc +0 -0
  190. package/backend/src/flowent/tools/__pycache__/delete_tab.cpython-313.pyc +0 -0
  191. package/backend/src/flowent/tools/__pycache__/edit.cpython-313.pyc +0 -0
  192. package/backend/src/flowent/tools/__pycache__/exec.cpython-313.pyc +0 -0
  193. package/backend/src/flowent/tools/__pycache__/fetch.cpython-313.pyc +0 -0
  194. package/backend/src/flowent/tools/__pycache__/idle.cpython-313.pyc +0 -0
  195. package/backend/src/flowent/tools/__pycache__/list_roles.cpython-313.pyc +0 -0
  196. package/backend/src/flowent/tools/__pycache__/list_tabs.cpython-313.pyc +0 -0
  197. package/backend/src/flowent/tools/__pycache__/list_tools.cpython-313.pyc +0 -0
  198. package/backend/src/flowent/tools/__pycache__/manage_prompts.cpython-313.pyc +0 -0
  199. package/backend/src/flowent/tools/__pycache__/manage_providers.cpython-313.pyc +0 -0
  200. package/backend/src/flowent/tools/__pycache__/manage_roles.cpython-313.pyc +0 -0
  201. package/backend/src/flowent/tools/__pycache__/manage_settings.cpython-313.pyc +0 -0
  202. package/backend/src/flowent/tools/__pycache__/mcp.cpython-313.pyc +0 -0
  203. package/backend/src/flowent/tools/__pycache__/read.cpython-313.pyc +0 -0
  204. package/backend/src/flowent/tools/__pycache__/send.cpython-313.pyc +0 -0
  205. package/backend/src/flowent/tools/__pycache__/set_permissions.cpython-313.pyc +0 -0
  206. package/backend/src/flowent/tools/__pycache__/sleep.cpython-313.pyc +0 -0
  207. package/backend/src/flowent/tools/__pycache__/todo.cpython-313.pyc +0 -0
  208. package/backend/src/flowent/tools/connect.py +156 -0
  209. package/backend/src/flowent/tools/contacts.py +22 -0
  210. package/backend/src/flowent/tools/create_agent.py +270 -0
  211. package/backend/src/flowent/tools/create_tab.py +59 -0
  212. package/backend/src/flowent/tools/delete_tab.py +39 -0
  213. package/backend/src/flowent/tools/edit.py +142 -0
  214. package/backend/src/flowent/tools/exec.py +117 -0
  215. package/backend/src/flowent/tools/fetch.py +85 -0
  216. package/backend/src/flowent/tools/idle.py +27 -0
  217. package/backend/src/flowent/tools/list_roles.py +50 -0
  218. package/backend/src/flowent/tools/list_tabs.py +96 -0
  219. package/backend/src/flowent/tools/list_tools.py +24 -0
  220. package/backend/src/flowent/tools/manage_prompts.py +102 -0
  221. package/backend/src/flowent/tools/manage_providers.py +220 -0
  222. package/backend/src/flowent/tools/manage_roles.py +275 -0
  223. package/backend/src/flowent/tools/manage_settings.py +346 -0
  224. package/backend/src/flowent/tools/mcp.py +199 -0
  225. package/backend/src/flowent/tools/read.py +152 -0
  226. package/backend/src/flowent/tools/send.py +50 -0
  227. package/backend/src/flowent/tools/set_permissions.py +84 -0
  228. package/backend/src/flowent/tools/sleep.py +41 -0
  229. package/backend/src/flowent/tools/todo.py +51 -0
  230. package/backend/src/flowent/workspace_store.py +479 -0
  231. package/backend/tests/__init__.py +0 -0
  232. package/backend/tests/__pycache__/__init__.cpython-313.pyc +0 -0
  233. package/backend/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  234. package/backend/tests/conftest.py +6 -0
  235. package/backend/tests/integration/api/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  236. package/backend/tests/integration/api/__pycache__/test_access_api.cpython-313-pytest-9.0.3.pyc +0 -0
  237. package/backend/tests/integration/api/__pycache__/test_assistant_api.cpython-313-pytest-9.0.3.pyc +0 -0
  238. package/backend/tests/integration/api/__pycache__/test_frontend_mounting.cpython-313-pytest-9.0.3.pyc +0 -0
  239. package/backend/tests/integration/api/__pycache__/test_mcp_api.cpython-313-pytest-9.0.3.pyc +0 -0
  240. package/backend/tests/integration/api/__pycache__/test_meta_api.cpython-313-pytest-9.0.3.pyc +0 -0
  241. package/backend/tests/integration/api/__pycache__/test_nodes_api.cpython-313-pytest-9.0.3.pyc +0 -0
  242. package/backend/tests/integration/api/__pycache__/test_prompts_api.cpython-313-pytest-9.0.3.pyc +0 -0
  243. package/backend/tests/integration/api/__pycache__/test_roles_api.cpython-313-pytest-9.0.3.pyc +0 -0
  244. package/backend/tests/integration/api/__pycache__/test_tabs_api.cpython-313-pytest-9.0.3.pyc +0 -0
  245. package/backend/tests/integration/api/conftest.py +29 -0
  246. package/backend/tests/integration/api/test_access_api.py +182 -0
  247. package/backend/tests/integration/api/test_assistant_api.py +354 -0
  248. package/backend/tests/integration/api/test_frontend_mounting.py +61 -0
  249. package/backend/tests/integration/api/test_mcp_api.py +116 -0
  250. package/backend/tests/integration/api/test_meta_api.py +33 -0
  251. package/backend/tests/integration/api/test_nodes_api.py +486 -0
  252. package/backend/tests/integration/api/test_prompts_api.py +47 -0
  253. package/backend/tests/integration/api/test_roles_api.py +227 -0
  254. package/backend/tests/integration/api/test_tabs_api.py +501 -0
  255. package/backend/tests/unit/__pycache__/test_access.cpython-313-pytest-9.0.3.pyc +0 -0
  256. package/backend/tests/unit/__pycache__/test_cli.cpython-313-pytest-9.0.3.pyc +0 -0
  257. package/backend/tests/unit/__pycache__/test_graph_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  258. package/backend/tests/unit/__pycache__/test_network.cpython-313-pytest-9.0.3.pyc +0 -0
  259. package/backend/tests/unit/__pycache__/test_state_sqlite_storage.cpython-313-pytest-9.0.3.pyc +0 -0
  260. package/backend/tests/unit/__pycache__/test_workspace_store.cpython-313-pytest-9.0.3.pyc +0 -0
  261. package/backend/tests/unit/agent/__pycache__/test_agent_public_api.cpython-313-pytest-9.0.3.pyc +0 -0
  262. package/backend/tests/unit/agent/__pycache__/test_agent_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  263. package/backend/tests/unit/agent/test_agent_public_api.py +746 -0
  264. package/backend/tests/unit/agent/test_agent_runtime.py +2726 -0
  265. package/backend/tests/unit/channels/__pycache__/test_telegram_channel.cpython-313-pytest-9.0.3.pyc +0 -0
  266. package/backend/tests/unit/channels/test_telegram_channel.py +552 -0
  267. package/backend/tests/unit/logging/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
  268. package/backend/tests/unit/logging/test_logging.py +132 -0
  269. package/backend/tests/unit/prompts/__pycache__/test_prompts.cpython-313-pytest-9.0.3.pyc +0 -0
  270. package/backend/tests/unit/prompts/test_prompts.py +569 -0
  271. package/backend/tests/unit/providers/__pycache__/test_anthropic_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  272. package/backend/tests/unit/providers/__pycache__/test_errors.cpython-313-pytest-9.0.3.pyc +0 -0
  273. package/backend/tests/unit/providers/__pycache__/test_extract_delta_parts.cpython-313-pytest-9.0.3.pyc +0 -0
  274. package/backend/tests/unit/providers/__pycache__/test_openai_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  275. package/backend/tests/unit/providers/__pycache__/test_openai_responses.cpython-313-pytest-9.0.3.pyc +0 -0
  276. package/backend/tests/unit/providers/__pycache__/test_provider_gateway.cpython-313-pytest-9.0.3.pyc +0 -0
  277. package/backend/tests/unit/providers/__pycache__/test_think_tag_parser.cpython-313-pytest-9.0.3.pyc +0 -0
  278. package/backend/tests/unit/providers/test_anthropic_provider.py +185 -0
  279. package/backend/tests/unit/providers/test_errors.py +68 -0
  280. package/backend/tests/unit/providers/test_extract_delta_parts.py +22 -0
  281. package/backend/tests/unit/providers/test_openai_provider.py +139 -0
  282. package/backend/tests/unit/providers/test_openai_responses.py +402 -0
  283. package/backend/tests/unit/providers/test_provider_gateway.py +359 -0
  284. package/backend/tests/unit/providers/test_think_tag_parser.py +36 -0
  285. package/backend/tests/unit/routes/__pycache__/test_prompts_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  286. package/backend/tests/unit/routes/__pycache__/test_providers_route.cpython-313-pytest-9.0.3.pyc +0 -0
  287. package/backend/tests/unit/routes/__pycache__/test_roles_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  288. package/backend/tests/unit/routes/__pycache__/test_settings_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  289. package/backend/tests/unit/routes/__pycache__/test_stats_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  290. package/backend/tests/unit/routes/test_prompts_routes.py +104 -0
  291. package/backend/tests/unit/routes/test_providers_route.py +368 -0
  292. package/backend/tests/unit/routes/test_roles_routes.py +426 -0
  293. package/backend/tests/unit/routes/test_settings_routes.py +1138 -0
  294. package/backend/tests/unit/routes/test_stats_routes.py +149 -0
  295. package/backend/tests/unit/runtime/__pycache__/test_bootstrap_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  296. package/backend/tests/unit/runtime/test_bootstrap_runtime.py +1012 -0
  297. package/backend/tests/unit/sandbox/__pycache__/test_sandbox_tools.cpython-313-pytest-9.0.3.pyc +0 -0
  298. package/backend/tests/unit/sandbox/test_sandbox_tools.py +78 -0
  299. package/backend/tests/unit/security/__pycache__/test_security.cpython-313-pytest-9.0.3.pyc +0 -0
  300. package/backend/tests/unit/security/test_security.py +110 -0
  301. package/backend/tests/unit/settings/__pycache__/test_settings_roles.cpython-313-pytest-9.0.3.pyc +0 -0
  302. package/backend/tests/unit/settings/test_settings_roles.py +711 -0
  303. package/backend/tests/unit/test_access.py +45 -0
  304. package/backend/tests/unit/test_cli.py +124 -0
  305. package/backend/tests/unit/test_graph_runtime.py +72 -0
  306. package/backend/tests/unit/test_network.py +51 -0
  307. package/backend/tests/unit/test_state_sqlite_storage.py +93 -0
  308. package/backend/tests/unit/test_workspace_store.py +231 -0
  309. package/backend/tests/unit/tools/__pycache__/test_connect_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  310. package/backend/tests/unit/tools/__pycache__/test_create_agent_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  311. package/backend/tests/unit/tools/__pycache__/test_delete_tab_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  312. package/backend/tests/unit/tools/__pycache__/test_edit_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  313. package/backend/tests/unit/tools/__pycache__/test_exec_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  314. package/backend/tests/unit/tools/__pycache__/test_fetch_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  315. package/backend/tests/unit/tools/__pycache__/test_manage_prompts_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  316. package/backend/tests/unit/tools/__pycache__/test_manage_providers_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  317. package/backend/tests/unit/tools/__pycache__/test_manage_roles_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  318. package/backend/tests/unit/tools/__pycache__/test_manage_settings_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  319. package/backend/tests/unit/tools/__pycache__/test_read_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  320. package/backend/tests/unit/tools/__pycache__/test_set_permissions_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  321. package/backend/tests/unit/tools/__pycache__/test_todo_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  322. package/backend/tests/unit/tools/__pycache__/test_tool_registry.cpython-313-pytest-9.0.3.pyc +0 -0
  323. package/backend/tests/unit/tools/test_connect_tool.py +229 -0
  324. package/backend/tests/unit/tools/test_create_agent_tool.py +524 -0
  325. package/backend/tests/unit/tools/test_delete_tab_tool.py +83 -0
  326. package/backend/tests/unit/tools/test_edit_tool.py +115 -0
  327. package/backend/tests/unit/tools/test_exec_tool.py +81 -0
  328. package/backend/tests/unit/tools/test_fetch_tool.py +65 -0
  329. package/backend/tests/unit/tools/test_manage_prompts_tool.py +117 -0
  330. package/backend/tests/unit/tools/test_manage_providers_tool.py +458 -0
  331. package/backend/tests/unit/tools/test_manage_roles_tool.py +411 -0
  332. package/backend/tests/unit/tools/test_manage_settings_tool.py +608 -0
  333. package/backend/tests/unit/tools/test_read_tool.py +33 -0
  334. package/backend/tests/unit/tools/test_set_permissions_tool.py +391 -0
  335. package/backend/tests/unit/tools/test_todo_tool.py +37 -0
  336. package/backend/tests/unit/tools/test_tool_registry.py +91 -0
  337. package/backend/uv.lock +1144 -0
  338. package/bin/flowent.mjs +62 -36
  339. package/dist/frontend/assets/AssistantPage-B3Xc08AS.js +1 -0
  340. package/dist/frontend/assets/ChannelsPage-ByLd28xk.js +1 -0
  341. package/dist/frontend/assets/HomePage-C0hAx9_l.js +3 -0
  342. package/dist/frontend/assets/McpPage-DkrYLvBv.js +7 -0
  343. package/dist/frontend/assets/PageScaffold-D4jO9ooX.js +1 -0
  344. package/dist/frontend/assets/PromptsPage-DWA7rRJd.js +1 -0
  345. package/dist/frontend/assets/ProvidersPage-PUWT8seJ.js +3 -0
  346. package/dist/frontend/assets/RolesPage-CqcclGRw.js +1 -0
  347. package/dist/frontend/assets/SettingsPage-8tS2cJgX.js +3 -0
  348. package/dist/frontend/assets/StatsPage-BX9khYzu.js +1 -0
  349. package/dist/frontend/assets/ToolsPage-9Tl9FdeD.js +1 -0
  350. package/dist/frontend/assets/WorkspaceCommandDialog-CCXxjDL8.js +1 -0
  351. package/dist/frontend/assets/WorkspacePanels-aMdJ7ZH7.js +1 -0
  352. package/dist/frontend/assets/alert-dialog-kFYVQ7oX.js +1 -0
  353. package/dist/frontend/assets/badge-74-3jsCg.js +1 -0
  354. package/dist/frontend/assets/constants-XUzFf6i1.js +1 -0
  355. package/dist/frontend/assets/datetime-m6_O_Ci9.js +1 -0
  356. package/dist/frontend/assets/dialog-BeGSweF6.js +1 -0
  357. package/dist/frontend/assets/elk-worker.min-C9JGDOE-.js +6312 -0
  358. package/dist/frontend/assets/graph-vendor-CHpVij2M.css +1 -0
  359. package/dist/frontend/assets/graph-vendor-DRq_-6fV.js +7 -0
  360. package/dist/frontend/assets/index-BHC1Vhy8.css +1 -0
  361. package/dist/frontend/assets/index-CL1ALZ3r.js +10 -0
  362. package/dist/frontend/assets/layout.worker-jMHqAFbP.js +24 -0
  363. package/dist/frontend/assets/markdown-vendor-DVdy_w12.js +29 -0
  364. package/dist/frontend/assets/modelParams-CaHd0903.js +1 -0
  365. package/dist/frontend/assets/react-vendor-mEs_JJxa.js +9 -0
  366. package/dist/frontend/assets/roles-2OLDeTc5.js +1 -0
  367. package/dist/frontend/assets/rolldown-runtime-BYbx6iT9.js +1 -0
  368. package/dist/frontend/assets/select-DL_LPeDj.js +1 -0
  369. package/dist/frontend/assets/shared-CMxbpLeQ.js +1 -0
  370. package/dist/frontend/assets/triState-DEr3NkXV.js +1 -0
  371. package/dist/frontend/assets/ui-vendor-Dg9NNnWX.js +51 -0
  372. package/dist/frontend/index.html +36 -0
  373. package/package.json +27 -41
  374. package/dist/.next/BUILD_ID +0 -1
  375. package/dist/.next/app-path-routes-manifest.json +0 -6
  376. package/dist/.next/build-manifest.json +0 -20
  377. package/dist/.next/package.json +0 -1
  378. package/dist/.next/prerender-manifest.json +0 -114
  379. package/dist/.next/required-server-files.json +0 -333
  380. package/dist/.next/routes-manifest.json +0 -69
  381. package/dist/.next/server/app/_global-error/page/app-paths-manifest.json +0 -3
  382. package/dist/.next/server/app/_global-error/page/build-manifest.json +0 -16
  383. package/dist/.next/server/app/_global-error/page/next-font-manifest.json +0 -6
  384. package/dist/.next/server/app/_global-error/page/react-loadable-manifest.json +0 -1
  385. package/dist/.next/server/app/_global-error/page/server-reference-manifest.json +0 -4
  386. package/dist/.next/server/app/_global-error/page.js +0 -9
  387. package/dist/.next/server/app/_global-error/page.js.map +0 -5
  388. package/dist/.next/server/app/_global-error/page.js.nft.json +0 -1
  389. package/dist/.next/server/app/_global-error/page_client-reference-manifest.js +0 -3
  390. package/dist/.next/server/app/_global-error.html +0 -1
  391. package/dist/.next/server/app/_global-error.meta +0 -15
  392. package/dist/.next/server/app/_global-error.rsc +0 -14
  393. package/dist/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +0 -5
  394. package/dist/.next/server/app/_global-error.segments/_full.segment.rsc +0 -14
  395. package/dist/.next/server/app/_global-error.segments/_head.segment.rsc +0 -5
  396. package/dist/.next/server/app/_global-error.segments/_index.segment.rsc +0 -5
  397. package/dist/.next/server/app/_global-error.segments/_tree.segment.rsc +0 -1
  398. package/dist/.next/server/app/_not-found/page/app-paths-manifest.json +0 -3
  399. package/dist/.next/server/app/_not-found/page/build-manifest.json +0 -16
  400. package/dist/.next/server/app/_not-found/page/next-font-manifest.json +0 -10
  401. package/dist/.next/server/app/_not-found/page/react-loadable-manifest.json +0 -1
  402. package/dist/.next/server/app/_not-found/page/server-reference-manifest.json +0 -4
  403. package/dist/.next/server/app/_not-found/page.js +0 -13
  404. package/dist/.next/server/app/_not-found/page.js.map +0 -5
  405. package/dist/.next/server/app/_not-found/page.js.nft.json +0 -1
  406. package/dist/.next/server/app/_not-found/page_client-reference-manifest.js +0 -3
  407. package/dist/.next/server/app/_not-found.html +0 -1
  408. package/dist/.next/server/app/_not-found.meta +0 -16
  409. package/dist/.next/server/app/_not-found.rsc +0 -16
  410. package/dist/.next/server/app/_not-found.segments/_full.segment.rsc +0 -16
  411. package/dist/.next/server/app/_not-found.segments/_head.segment.rsc +0 -6
  412. package/dist/.next/server/app/_not-found.segments/_index.segment.rsc +0 -5
  413. package/dist/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +0 -5
  414. package/dist/.next/server/app/_not-found.segments/_not-found.segment.rsc +0 -5
  415. package/dist/.next/server/app/_not-found.segments/_tree.segment.rsc +0 -2
  416. package/dist/.next/server/app/icon.svg/route/app-paths-manifest.json +0 -3
  417. package/dist/.next/server/app/icon.svg/route/build-manifest.json +0 -9
  418. package/dist/.next/server/app/icon.svg/route.js +0 -6
  419. package/dist/.next/server/app/icon.svg/route.js.map +0 -5
  420. package/dist/.next/server/app/icon.svg/route.js.nft.json +0 -1
  421. package/dist/.next/server/app/icon.svg.meta +0 -1
  422. package/dist/.next/server/app/index.html +0 -1
  423. package/dist/.next/server/app/index.meta +0 -14
  424. package/dist/.next/server/app/index.rsc +0 -15
  425. package/dist/.next/server/app/index.segments/__PAGE__.segment.rsc +0 -5
  426. package/dist/.next/server/app/index.segments/_full.segment.rsc +0 -15
  427. package/dist/.next/server/app/index.segments/_head.segment.rsc +0 -6
  428. package/dist/.next/server/app/index.segments/_index.segment.rsc +0 -5
  429. package/dist/.next/server/app/index.segments/_tree.segment.rsc +0 -3
  430. package/dist/.next/server/app/page/app-paths-manifest.json +0 -3
  431. package/dist/.next/server/app/page/build-manifest.json +0 -16
  432. package/dist/.next/server/app/page/next-font-manifest.json +0 -10
  433. package/dist/.next/server/app/page/react-loadable-manifest.json +0 -1
  434. package/dist/.next/server/app/page/server-reference-manifest.json +0 -4
  435. package/dist/.next/server/app/page.js +0 -14
  436. package/dist/.next/server/app/page.js.map +0 -5
  437. package/dist/.next/server/app/page.js.nft.json +0 -1
  438. package/dist/.next/server/app/page_client-reference-manifest.js +0 -3
  439. package/dist/.next/server/app-paths-manifest.json +0 -6
  440. package/dist/.next/server/chunks/[externals]_next_dist_0arv.vj._.js +0 -3
  441. package/dist/.next/server/chunks/[root-of-the-server]__0vcj1q1._.js +0 -13
  442. package/dist/.next/server/chunks/[turbopack]_runtime.js +0 -903
  443. package/dist/.next/server/chunks/_next-internal_server_app_icon_svg_route_actions_0-0ehc~.js +0 -3
  444. package/dist/.next/server/chunks/ssr/05w9_next_dist_0ihu0u9._.js +0 -6
  445. package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_12u3mib._.js +0 -3
  446. package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_builtin_forbidden_04fbe_..js +0 -3
  447. package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_builtin_global-error_0brpl_..js +0 -3
  448. package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_builtin_unauthorized_0~2g66g.js +0 -3
  449. package/dist/.next/server/chunks/ssr/05w9_next_dist_esm_build_templates_app-page_0~cyr1_.js +0 -4
  450. package/dist/.next/server/chunks/ssr/05w9_next_dist_esm_build_templates_app-page_1105emf.js +0 -4
  451. package/dist/.next/server/chunks/ssr/05w9_next_dist_esm_build_templates_app-page_11uhyqv.js +0 -4
  452. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0.t9_75._.js +0 -33
  453. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0c0ud_z._.js +0 -3
  454. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0f9_8d4._.js +0 -3
  455. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0l5ko41._.js +0 -19
  456. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0mn6z7i._.js +0 -3
  457. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0npxxst._.js +0 -33
  458. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0qjhaca._.js +0 -3
  459. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0rwgw3s._.js +0 -3
  460. package/dist/.next/server/chunks/ssr/[turbopack]_runtime.js +0 -903
  461. package/dist/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_0k77kol.js +0 -3
  462. package/dist/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_0eq97pa.js +0 -3
  463. package/dist/.next/server/chunks/ssr/_next-internal_server_app_page_actions_09-gtaw.js +0 -3
  464. package/dist/.next/server/chunks/ssr/node_modules__pnpm_056~6.6._.js +0 -3
  465. package/dist/.next/server/chunks/ssr/node_modules__pnpm_0~j0k.e._.js +0 -33
  466. package/dist/.next/server/functions-config-manifest.json +0 -4
  467. package/dist/.next/server/middleware-build-manifest.js +0 -20
  468. package/dist/.next/server/middleware-manifest.json +0 -6
  469. package/dist/.next/server/next-font-manifest.js +0 -1
  470. package/dist/.next/server/next-font-manifest.json +0 -13
  471. package/dist/.next/server/pages/404.html +0 -1
  472. package/dist/.next/server/pages/500.html +0 -1
  473. package/dist/.next/server/pages-manifest.json +0 -4
  474. package/dist/.next/server/prefetch-hints.json +0 -1
  475. package/dist/.next/server/server-reference-manifest.js +0 -1
  476. package/dist/.next/server/server-reference-manifest.json +0 -5
  477. package/dist/.next/static/SMWpxFVvkpYFxY7uuFvGB/_buildManifest.js +0 -11
  478. package/dist/.next/static/SMWpxFVvkpYFxY7uuFvGB/_clientMiddlewareManifest.js +0 -1
  479. package/dist/.next/static/SMWpxFVvkpYFxY7uuFvGB/_ssgManifest.js +0 -1
  480. package/dist/.next/static/chunks/01qk2~bgf76vu.js +0 -1
  481. package/dist/.next/static/chunks/03~yq9q893hmn.js +0 -1
  482. package/dist/.next/static/chunks/080queev.r2uy.js +0 -31
  483. package/dist/.next/static/chunks/0v3lyuj75aq50.js +0 -1
  484. package/dist/.next/static/chunks/10b~xdx5c-i7s.js +0 -5
  485. package/dist/.next/static/chunks/14gla2ascffgv.css +0 -2
  486. package/dist/.next/static/chunks/turbopack-0m-970~qvs7sc.js +0 -1
  487. package/dist/.next/static/media/7178b3e590c64307-s.11.cyxs5p-0z~.woff2 +0 -0
  488. package/dist/.next/static/media/8a480f0b521d4e75-s.06d3mdzz5bre_.woff2 +0 -0
  489. package/dist/.next/static/media/caa3a2e1cccd8315-s.p.16t1db8_9y2o~.woff2 +0 -0
  490. package/dist/package.json +0 -88
  491. package/dist/server.js +0 -38
  492. /package/{dist/.next/server/app/icon.svg.body → backend/src/flowent/static/favicon.svg} +0 -0
  493. /package/dist/{.next/static/media/icon.0.r~afrtrocz9.svg → frontend/favicon.svg} +0 -0
@@ -0,0 +1,292 @@
1
+ from __future__ import annotations
2
+
3
+ from fastapi import APIRouter, HTTPException
4
+ from pydantic import BaseModel, ConfigDict
5
+
6
+ from flowent.graph_service import (
7
+ create_agent_node,
8
+ create_edge,
9
+ create_graph_node,
10
+ create_tab,
11
+ delete_agent_node,
12
+ delete_edge,
13
+ delete_tab,
14
+ duplicate_tab,
15
+ list_node_connection_ids,
16
+ list_tab_edges,
17
+ list_workflow_nodes,
18
+ serialize_tab_summary,
19
+ update_tab_definition,
20
+ )
21
+ from flowent.models import AgentState, EdgeKind, WorkflowNodeKind
22
+ from flowent.registry import registry
23
+ from flowent.workspace_store import workspace_store
24
+
25
+ router = APIRouter()
26
+
27
+
28
+ class CreateTabRequest(BaseModel):
29
+ model_config = ConfigDict(extra="forbid")
30
+ title: str
31
+ allow_network: bool = False
32
+ write_dirs: list[str] = []
33
+
34
+
35
+ class CreateTabNodeRequest(BaseModel):
36
+ model_config = ConfigDict(extra="forbid")
37
+ node_type: str = WorkflowNodeKind.AGENT.value
38
+ role_name: str | None = None
39
+ name: str | None = None
40
+ config: dict[str, object] = {}
41
+ tools: list[str] = []
42
+ write_dirs: list[str] = []
43
+ allow_network: bool = False
44
+
45
+
46
+ class CreateTabEdgeRequest(BaseModel):
47
+ model_config = ConfigDict(extra="forbid")
48
+ from_node_id: str
49
+ from_port_key: str = "out"
50
+ to_node_id: str
51
+ to_port_key: str = "in"
52
+ kind: str = EdgeKind.CONTROL.value
53
+
54
+
55
+ class UpdateTabDefinitionRequest(BaseModel):
56
+ model_config = ConfigDict(extra="forbid")
57
+ definition: dict[str, object]
58
+
59
+
60
+ def _serialize_workflow_node(
61
+ *,
62
+ tab_id: str,
63
+ node_id: str,
64
+ ) -> dict[str, object]:
65
+ tab = workspace_store.get_tab(tab_id)
66
+ if tab is None:
67
+ raise HTTPException(status_code=404, detail="Workflow not found")
68
+ definition = tab.definition.get_node(node_id)
69
+ if definition is None:
70
+ raise HTTPException(status_code=404, detail="Node not found")
71
+ record = workspace_store.get_node_record(node_id)
72
+ live_node = registry.get(node_id)
73
+ config = dict(definition.config)
74
+ role_name = config.get("role_name")
75
+ name = config.get("name")
76
+ position = tab.definition.view.positions.get(node_id)
77
+ todos = (
78
+ [todo.serialize() for todo in live_node.get_todos_snapshot()]
79
+ if live_node is not None
80
+ else [todo.serialize() for todo in (record.todos if record is not None else [])]
81
+ )
82
+ state = (
83
+ live_node.state.value
84
+ if live_node is not None
85
+ else (record.state.value if record is not None else AgentState.IDLE.value)
86
+ )
87
+ return {
88
+ "id": definition.id,
89
+ "node_type": definition.type.value,
90
+ "workflow_id": tab_id,
91
+ "role_name": role_name if isinstance(role_name, str) else None,
92
+ "is_leader": False,
93
+ "state": state,
94
+ "connections": list_node_connection_ids(tab_id=tab_id, node_id=definition.id)
95
+ if definition.type == WorkflowNodeKind.AGENT
96
+ else [],
97
+ "name": name if isinstance(name, str) else None,
98
+ "todos": todos if definition.type == WorkflowNodeKind.AGENT else [],
99
+ "position": position.serialize() if position is not None else None,
100
+ "config": config,
101
+ "inputs": [port.serialize() for port in definition.inputs],
102
+ "outputs": [port.serialize() for port in definition.outputs],
103
+ }
104
+
105
+
106
+ @router.get("/api/workflows")
107
+ async def list_workflows() -> dict[str, object]:
108
+ tabs = workspace_store.list_tabs()
109
+ return {"workflows": [serialize_tab_summary(tab) for tab in tabs]}
110
+
111
+
112
+ @router.post("/api/workflows")
113
+ async def create_workflow_route(req: CreateTabRequest) -> dict[str, object]:
114
+ title = req.title.strip()
115
+ if not title:
116
+ raise HTTPException(status_code=400, detail="title must not be empty")
117
+ try:
118
+ tab = create_tab(
119
+ title=title,
120
+ allow_network=req.allow_network,
121
+ write_dirs=req.write_dirs,
122
+ )
123
+ except ValueError as exc:
124
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
125
+ return serialize_tab_summary(tab)
126
+
127
+
128
+ @router.post("/api/workflows/{tab_id}/duplicate")
129
+ async def duplicate_workflow_route(tab_id: str) -> dict[str, object]:
130
+ duplicated, error = duplicate_tab(tab_id=tab_id)
131
+ if error is not None or duplicated is None:
132
+ raise HTTPException(
133
+ status_code=404 if error and error.endswith("not found") else 400,
134
+ detail=error or "Failed to duplicate workflow",
135
+ )
136
+ return serialize_tab_summary(duplicated)
137
+
138
+
139
+ @router.get("/api/workflows/{tab_id}")
140
+ async def get_workflow(tab_id: str) -> dict[str, object]:
141
+ tab = workspace_store.get_tab(tab_id)
142
+ if tab is None:
143
+ raise HTTPException(status_code=404, detail="Workflow not found")
144
+ nodes = [
145
+ _serialize_workflow_node(tab_id=tab_id, node_id=node.id)
146
+ for node in list_workflow_nodes(tab_id)
147
+ ]
148
+ edges = [edge.serialize() for edge in list_tab_edges(tab_id)]
149
+ return {
150
+ "workflow": serialize_tab_summary(tab),
151
+ "nodes": nodes,
152
+ "edges": edges,
153
+ }
154
+
155
+
156
+ @router.put("/api/workflows/{tab_id}/definition")
157
+ async def update_workflow_definition_route(
158
+ tab_id: str,
159
+ req: UpdateTabDefinitionRequest,
160
+ ) -> dict[str, object]:
161
+ updated, error = update_tab_definition(
162
+ tab_id=tab_id,
163
+ definition_payload=req.definition,
164
+ actor_id=tab_id,
165
+ )
166
+ if error is not None or updated is None:
167
+ raise HTTPException(
168
+ status_code=400 if error and not error.endswith("not found") else 404,
169
+ detail=error or "Failed to update workflow definition",
170
+ )
171
+ return serialize_tab_summary(updated)
172
+
173
+
174
+ @router.delete("/api/workflows/{tab_id}")
175
+ async def delete_workflow_route(tab_id: str) -> dict[str, object]:
176
+ deleted, error = delete_tab(tab_id=tab_id)
177
+ if error is not None or deleted is None:
178
+ status_code = 404 if error and error.endswith("not found") else 400
179
+ raise HTTPException(
180
+ status_code=status_code,
181
+ detail=error or "Failed to delete workflow",
182
+ )
183
+ return deleted
184
+
185
+
186
+ @router.post("/api/workflows/{tab_id}/nodes")
187
+ async def create_workflow_node(
188
+ tab_id: str,
189
+ req: CreateTabNodeRequest,
190
+ ) -> dict[str, object]:
191
+ try:
192
+ node_type = WorkflowNodeKind(req.node_type.strip())
193
+ except ValueError as exc:
194
+ raise HTTPException(status_code=400, detail="Invalid node_type") from exc
195
+
196
+ if node_type == WorkflowNodeKind.AGENT:
197
+ if not isinstance(req.role_name, str) or not req.role_name.strip():
198
+ raise HTTPException(status_code=400, detail="role_name is required")
199
+ record, error = create_agent_node(
200
+ role_name=req.role_name,
201
+ tab_id=tab_id,
202
+ name=req.name,
203
+ tools=req.tools,
204
+ write_dirs=req.write_dirs,
205
+ allow_network=req.allow_network,
206
+ )
207
+ if error is not None or record is None:
208
+ raise HTTPException(
209
+ status_code=400, detail=error or "Failed to create node"
210
+ )
211
+ return _serialize_workflow_node(tab_id=tab_id, node_id=record.id)
212
+
213
+ node, error = create_graph_node(
214
+ tab_id=tab_id,
215
+ node_type=node_type,
216
+ config={
217
+ **req.config,
218
+ **(
219
+ {"name": req.name}
220
+ if isinstance(req.name, str) and req.name.strip()
221
+ else {}
222
+ ),
223
+ },
224
+ actor_id=tab_id,
225
+ )
226
+ if error is not None or node is None:
227
+ raise HTTPException(status_code=400, detail=error or "Failed to create node")
228
+ return _serialize_workflow_node(tab_id=tab_id, node_id=node.id)
229
+
230
+
231
+ @router.post("/api/workflows/{tab_id}/edges")
232
+ async def create_workflow_edge(
233
+ tab_id: str,
234
+ req: CreateTabEdgeRequest,
235
+ ) -> dict[str, object]:
236
+ tab = workspace_store.get_tab(tab_id)
237
+ if tab is None:
238
+ raise HTTPException(status_code=404, detail="Workflow not found")
239
+ try:
240
+ edge_kind = EdgeKind(req.kind.strip())
241
+ except ValueError as exc:
242
+ raise HTTPException(status_code=400, detail="Invalid edge kind") from exc
243
+ edge, error = create_edge(
244
+ tab_id=tab_id,
245
+ from_node_id=req.from_node_id,
246
+ from_port_key=req.from_port_key,
247
+ to_node_id=req.to_node_id,
248
+ to_port_key=req.to_port_key,
249
+ kind=edge_kind,
250
+ )
251
+ if error is not None or edge is None:
252
+ raise HTTPException(status_code=400, detail=error or "Failed to create edge")
253
+ return edge.serialize()
254
+
255
+
256
+ @router.delete("/api/workflows/{tab_id}/nodes/{node_id}")
257
+ async def delete_workflow_node(tab_id: str, node_id: str) -> dict[str, object]:
258
+ deleted, error = delete_agent_node(
259
+ tab_id=tab_id,
260
+ node_id=node_id,
261
+ )
262
+ if error is not None or deleted is None:
263
+ status_code = 404 if error and error.endswith("not found") else 400
264
+ raise HTTPException(
265
+ status_code=status_code, detail=error or "Failed to delete node"
266
+ )
267
+ return deleted
268
+
269
+
270
+ @router.delete("/api/workflows/{tab_id}/edges")
271
+ async def delete_workflow_edge(
272
+ tab_id: str,
273
+ edge_id: str | None = None,
274
+ from_node_id: str | None = None,
275
+ to_node_id: str | None = None,
276
+ from_port_key: str | None = None,
277
+ to_port_key: str | None = None,
278
+ ) -> dict[str, object]:
279
+ deleted, error = delete_edge(
280
+ tab_id=tab_id,
281
+ edge_id=edge_id,
282
+ from_node_id=from_node_id,
283
+ to_node_id=to_node_id,
284
+ from_port_key=from_port_key,
285
+ to_port_key=to_port_key,
286
+ )
287
+ if error is not None or deleted is None:
288
+ status_code = 404 if error and error.endswith("not found") else 400
289
+ raise HTTPException(
290
+ status_code=status_code, detail=error or "Failed to delete edge"
291
+ )
292
+ return deleted
@@ -0,0 +1,33 @@
1
+ from __future__ import annotations
2
+
3
+ from fastapi import APIRouter
4
+ from starlette.websockets import WebSocket, WebSocketDisconnect
5
+
6
+ from flowent.access import authorize_websocket
7
+ from flowent.events import event_bus
8
+
9
+ router = APIRouter()
10
+
11
+
12
+ @router.websocket("/ws/events")
13
+ async def ws_events(ws: WebSocket) -> None:
14
+ if not await authorize_websocket(ws):
15
+ return
16
+ await event_bus.connect_display(ws)
17
+ try:
18
+ while True:
19
+ await ws.receive_text()
20
+ except WebSocketDisconnect:
21
+ event_bus.disconnect_display(ws)
22
+
23
+
24
+ @router.websocket("/ws/updates")
25
+ async def ws_updates(ws: WebSocket) -> None:
26
+ if not await authorize_websocket(ws):
27
+ return
28
+ await event_bus.connect_updates(ws)
29
+ try:
30
+ while True:
31
+ await ws.receive_text()
32
+ except WebSocketDisconnect:
33
+ event_bus.disconnect_updates(ws)
@@ -0,0 +1,188 @@
1
+ from __future__ import annotations
2
+
3
+ import threading
4
+ from typing import TYPE_CHECKING
5
+
6
+ from loguru import logger
7
+
8
+ from flowent.registry import registry
9
+
10
+ if TYPE_CHECKING:
11
+ from flowent.channels.telegram import TelegramChannel
12
+
13
+ SYSTEM_NODE_TIMEOUT = 5.0
14
+ _telegram_channel: TelegramChannel | None = None
15
+ _telegram_channel_lock = threading.Lock()
16
+
17
+
18
+ def _stop_telegram_channel() -> None:
19
+ global _telegram_channel
20
+ with _telegram_channel_lock:
21
+ channel = _telegram_channel
22
+ _telegram_channel = None
23
+
24
+ if channel is not None:
25
+ channel.stop()
26
+
27
+
28
+ def restart_telegram_channel() -> None:
29
+ from flowent.channels.telegram import TelegramChannel
30
+ from flowent.settings import get_settings
31
+
32
+ _stop_telegram_channel()
33
+
34
+ settings = get_settings()
35
+ if not settings.telegram.bot_token.strip():
36
+ return
37
+
38
+ channel = TelegramChannel()
39
+ channel.start()
40
+ with _telegram_channel_lock:
41
+ global _telegram_channel
42
+ _telegram_channel = channel
43
+
44
+
45
+ def bootstrap_runtime() -> None:
46
+ from flowent.access import ensure_access_bootstrap
47
+ from flowent.agent import Agent
48
+ from flowent.graph_service import (
49
+ build_assistant_tools,
50
+ ensure_tab_leaders,
51
+ )
52
+ from flowent.mcp_service import mcp_service
53
+ from flowent.models import AgentState, NodeConfig, NodeType, StateEntry
54
+ from flowent.settings import (
55
+ ensure_builtin_roles,
56
+ get_settings,
57
+ save_settings,
58
+ )
59
+ from flowent.workspace_store import workspace_store
60
+
61
+ workspace_store.reset_cache()
62
+ mcp_service.clear_runtime_state()
63
+ settings = get_settings()
64
+ settings_changed = ensure_builtin_roles(settings)
65
+ generated_access_code = ensure_access_bootstrap(settings)
66
+ if settings_changed or generated_access_code is not None:
67
+ save_settings(settings)
68
+ mcp_service.bootstrap()
69
+ assistant_tools = build_assistant_tools(settings=settings)
70
+
71
+ assistant_record = next(
72
+ (
73
+ record
74
+ for record in workspace_store.list_node_records()
75
+ if record.config.node_type == NodeType.ASSISTANT
76
+ and record.state != AgentState.TERMINATED
77
+ ),
78
+ None,
79
+ )
80
+ assistant = Agent(
81
+ NodeConfig(
82
+ node_type=NodeType.ASSISTANT,
83
+ role_name=settings.assistant.role_name,
84
+ name="Assistant",
85
+ tools=assistant_tools,
86
+ write_dirs=list(settings.assistant.write_dirs),
87
+ allow_network=settings.assistant.allow_network,
88
+ ),
89
+ uuid=assistant_record.id if assistant_record is not None else None,
90
+ )
91
+ if assistant_record is not None:
92
+ assistant.history = list(assistant_record.history)
93
+ assistant._set_execution_context(
94
+ summary=assistant_record.execution_context_summary,
95
+ history_cutoff=assistant_record.execution_context_history_cutoff,
96
+ )
97
+ if not any(isinstance(entry, StateEntry) for entry in assistant.history):
98
+ assistant.history.insert(
99
+ 0,
100
+ StateEntry(state=assistant_record.state.value, reason="restored"),
101
+ )
102
+ if assistant_record.state in {
103
+ AgentState.INITIALIZING,
104
+ AgentState.IDLE,
105
+ AgentState.RUNNING,
106
+ AgentState.SLEEPING,
107
+ }:
108
+ assistant.history.append(
109
+ StateEntry(state=AgentState.IDLE.value, reason="restored")
110
+ )
111
+ assistant.todos = list(assistant_record.todos)
112
+ assistant.prime_runtime_state(
113
+ AgentState.ERROR
114
+ if assistant_record.state == AgentState.ERROR
115
+ else AgentState.IDLE
116
+ )
117
+ registry.register(assistant)
118
+ assistant.start()
119
+ logger.info("Assistant started with role {}", settings.assistant.role_name)
120
+
121
+ ensure_tab_leaders()
122
+
123
+ restored_node_ids: set[str] = set()
124
+ for record in workspace_store.list_node_records():
125
+ if record.config.node_type == NodeType.ASSISTANT:
126
+ continue
127
+ if record.state == AgentState.TERMINATED:
128
+ continue
129
+ node = Agent(
130
+ NodeConfig(
131
+ node_type=record.config.node_type,
132
+ role_name=record.config.role_name,
133
+ tab_id=record.config.tab_id,
134
+ name=record.config.name,
135
+ tools=list(record.config.tools),
136
+ write_dirs=list(record.config.write_dirs),
137
+ allow_network=record.config.allow_network,
138
+ ),
139
+ uuid=record.id,
140
+ )
141
+ node.history = list(record.history)
142
+ node._set_execution_context(
143
+ summary=record.execution_context_summary,
144
+ history_cutoff=record.execution_context_history_cutoff,
145
+ )
146
+ if not any(isinstance(entry, StateEntry) for entry in node.history):
147
+ node.history.insert(
148
+ 0,
149
+ StateEntry(state=record.state.value, reason="restored"),
150
+ )
151
+ if record.state in {
152
+ AgentState.INITIALIZING,
153
+ AgentState.RUNNING,
154
+ AgentState.SLEEPING,
155
+ }:
156
+ node.history.append(
157
+ StateEntry(state=AgentState.IDLE.value, reason="restored")
158
+ )
159
+ node.todos = list(record.todos)
160
+ node.prime_runtime_state(
161
+ AgentState.ERROR if record.state == AgentState.ERROR else AgentState.IDLE
162
+ )
163
+ registry.register(node)
164
+ node.start()
165
+ restored_node_ids.add(node.uuid)
166
+
167
+ if settings.telegram.bot_token.strip():
168
+ restart_telegram_channel()
169
+
170
+
171
+ def shutdown_runtime(timeout: float = SYSTEM_NODE_TIMEOUT) -> None:
172
+ from flowent.mcp_service import mcp_service
173
+ from flowent.models import NodeType
174
+
175
+ logger.info("Shutting down runtime")
176
+ _stop_telegram_channel()
177
+ mcp_service.clear_runtime_state()
178
+ persistent_agents = []
179
+ for agent in registry.get_all():
180
+ if agent.node_type == NodeType.ASSISTANT or agent.config.tab_id:
181
+ agent.request_process_exit()
182
+ persistent_agents.append(agent)
183
+ continue
184
+ agent.terminate_and_wait(timeout=timeout)
185
+ for agent in persistent_agents:
186
+ agent.wait_for_termination(timeout=timeout)
187
+ registry.reset()
188
+ logger.info("Runtime shutdown complete")
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+
5
+
6
+ def is_path_writable(path: str | Path, write_dirs: list[str]) -> bool:
7
+ from flowent.settings import resolve_path
8
+
9
+ p = resolve_path(path)
10
+ return any(p.is_relative_to(resolve_path(directory)) for directory in write_dirs)
11
+
12
+
13
+ def build_bwrap_cmd(
14
+ write_dirs: list[str],
15
+ command: str,
16
+ *,
17
+ allow_network: bool = False,
18
+ cwd: str | Path | None = None,
19
+ ) -> list[str]:
20
+ from flowent.settings import resolve_path
21
+
22
+ resolved_cwd = resolve_path(cwd) if cwd is not None else None
23
+ cmd = [
24
+ "bwrap",
25
+ "--ro-bind",
26
+ "/",
27
+ "/",
28
+ "--dev",
29
+ "/dev",
30
+ "--proc",
31
+ "/proc",
32
+ "--tmpfs",
33
+ "/tmp",
34
+ ]
35
+ if resolved_cwd is not None:
36
+ cmd.extend(["--ro-bind", str(resolved_cwd), str(resolved_cwd)])
37
+ for directory in write_dirs:
38
+ resolved_dir = resolve_path(directory)
39
+ cmd.extend(["--bind", str(resolved_dir), str(resolved_dir)])
40
+ if not allow_network:
41
+ cmd.append("--unshare-net")
42
+ if resolved_cwd is not None:
43
+ cmd.extend(["--chdir", str(resolved_cwd)])
44
+ cmd.extend(["--die-with-parent", "--new-session", "--", "bash", "-c", command])
45
+ return cmd
@@ -0,0 +1,42 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from flowent.sandbox import is_path_writable
6
+
7
+ if TYPE_CHECKING:
8
+ from flowent.agent import Agent
9
+
10
+
11
+ def authorize(tool_name: str, agent: Agent, args: dict[str, Any]) -> str | None:
12
+ if tool_name.startswith("mcp__"):
13
+ from flowent.mcp_service import mcp_service
14
+ from flowent.settings import find_mcp_server, get_settings
15
+
16
+ descriptor = mcp_service.get_dynamic_tool_descriptor(tool_name)
17
+ if descriptor is None:
18
+ return f"MCP tool not found: {tool_name}"
19
+ server = find_mcp_server(get_settings(), descriptor.server_name)
20
+ if (
21
+ server is not None
22
+ and server.transport == "streamable_http"
23
+ and not agent.config.allow_network
24
+ ):
25
+ return "Network access is disabled for this agent"
26
+ if descriptor.open_world_hint and not agent.config.allow_network:
27
+ return "Network access is disabled for this agent"
28
+ return None
29
+
30
+ if tool_name == "edit":
31
+ write_dirs = agent.config.write_dirs
32
+ if not write_dirs:
33
+ return "Write access is disabled for this agent"
34
+ path = args.get("path")
35
+ if isinstance(path, str) and not is_path_writable(path, write_dirs):
36
+ return f"Path not in write_dirs: {path}"
37
+ return None
38
+
39
+ if tool_name == "fetch" and not agent.config.allow_network:
40
+ return "Network access is disabled for this agent"
41
+
42
+ return None