flowent 0.0.1 → 0.0.5

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 (484) hide show
  1. package/README.md +20 -9
  2. package/backend/.python-version +1 -0
  3. package/backend/README.md +74 -0
  4. package/backend/pyproject.toml +58 -0
  5. package/backend/src/flowent/__init__.py +3 -0
  6. package/backend/src/flowent/__pycache__/__init__.cpython-313.pyc +0 -0
  7. package/backend/src/flowent/__pycache__/_version.cpython-313.pyc +0 -0
  8. package/backend/src/flowent/__pycache__/access.cpython-313.pyc +0 -0
  9. package/backend/src/flowent/__pycache__/agent.cpython-313.pyc +0 -0
  10. package/backend/src/flowent/__pycache__/assistant_commands.cpython-313.pyc +0 -0
  11. package/backend/src/flowent/__pycache__/cli.cpython-313.pyc +0 -0
  12. package/backend/src/flowent/__pycache__/config.cpython-313.pyc +0 -0
  13. package/backend/src/flowent/__pycache__/events.cpython-313.pyc +0 -0
  14. package/backend/src/flowent/__pycache__/graph_runtime.cpython-313.pyc +0 -0
  15. package/backend/src/flowent/__pycache__/graph_service.cpython-313.pyc +0 -0
  16. package/backend/src/flowent/__pycache__/image_assets.cpython-313.pyc +0 -0
  17. package/backend/src/flowent/__pycache__/logging.cpython-313.pyc +0 -0
  18. package/backend/src/flowent/__pycache__/main.cpython-313.pyc +0 -0
  19. package/backend/src/flowent/__pycache__/mcp_service.cpython-313.pyc +0 -0
  20. package/backend/src/flowent/__pycache__/model_metadata.cpython-313.pyc +0 -0
  21. package/backend/src/flowent/__pycache__/network.cpython-313.pyc +0 -0
  22. package/backend/src/flowent/__pycache__/observability_service.cpython-313.pyc +0 -0
  23. package/backend/src/flowent/__pycache__/registry.cpython-313.pyc +0 -0
  24. package/backend/src/flowent/__pycache__/role_management.cpython-313.pyc +0 -0
  25. package/backend/src/flowent/__pycache__/runtime.cpython-313.pyc +0 -0
  26. package/backend/src/flowent/__pycache__/sandbox.cpython-313.pyc +0 -0
  27. package/backend/src/flowent/__pycache__/security.cpython-313.pyc +0 -0
  28. package/backend/src/flowent/__pycache__/settings.cpython-313.pyc +0 -0
  29. package/backend/src/flowent/__pycache__/settings_management.cpython-313.pyc +0 -0
  30. package/backend/src/flowent/__pycache__/state_db.cpython-313.pyc +0 -0
  31. package/backend/src/flowent/__pycache__/workspace_store.cpython-313.pyc +0 -0
  32. package/backend/src/flowent/_version.py +7 -0
  33. package/backend/src/flowent/access.py +247 -0
  34. package/backend/src/flowent/agent.py +3120 -0
  35. package/backend/src/flowent/assistant_commands.py +115 -0
  36. package/backend/src/flowent/channels/__init__.py +3 -0
  37. package/backend/src/flowent/channels/__pycache__/__init__.cpython-313.pyc +0 -0
  38. package/backend/src/flowent/channels/__pycache__/telegram.cpython-313.pyc +0 -0
  39. package/backend/src/flowent/channels/telegram.py +615 -0
  40. package/backend/src/flowent/cli.py +85 -0
  41. package/backend/src/flowent/config.py +14 -0
  42. package/backend/src/flowent/dev.py +3 -0
  43. package/backend/src/flowent/events.py +157 -0
  44. package/backend/src/flowent/graph_runtime.py +60 -0
  45. package/backend/src/flowent/graph_service.py +2508 -0
  46. package/backend/src/flowent/image_assets.py +356 -0
  47. package/backend/src/flowent/logging.py +155 -0
  48. package/backend/src/flowent/main.py +124 -0
  49. package/backend/src/flowent/mcp_service.py +1918 -0
  50. package/backend/src/flowent/model_metadata.py +102 -0
  51. package/backend/src/flowent/models/__init__.py +125 -0
  52. package/backend/src/flowent/models/__pycache__/__init__.cpython-313.pyc +0 -0
  53. package/backend/src/flowent/models/__pycache__/agent.cpython-313.pyc +0 -0
  54. package/backend/src/flowent/models/__pycache__/base.cpython-313.pyc +0 -0
  55. package/backend/src/flowent/models/__pycache__/blueprint.cpython-313.pyc +0 -0
  56. package/backend/src/flowent/models/__pycache__/content.cpython-313.pyc +0 -0
  57. package/backend/src/flowent/models/__pycache__/delta.cpython-313.pyc +0 -0
  58. package/backend/src/flowent/models/__pycache__/event.cpython-313.pyc +0 -0
  59. package/backend/src/flowent/models/__pycache__/graph.cpython-313.pyc +0 -0
  60. package/backend/src/flowent/models/__pycache__/history.cpython-313.pyc +0 -0
  61. package/backend/src/flowent/models/__pycache__/llm.cpython-313.pyc +0 -0
  62. package/backend/src/flowent/models/__pycache__/message.cpython-313.pyc +0 -0
  63. package/backend/src/flowent/models/__pycache__/tab.cpython-313.pyc +0 -0
  64. package/backend/src/flowent/models/__pycache__/todo.cpython-313.pyc +0 -0
  65. package/backend/src/flowent/models/agent.py +34 -0
  66. package/backend/src/flowent/models/base.py +24 -0
  67. package/backend/src/flowent/models/blueprint.py +176 -0
  68. package/backend/src/flowent/models/content.py +164 -0
  69. package/backend/src/flowent/models/delta.py +44 -0
  70. package/backend/src/flowent/models/event.py +51 -0
  71. package/backend/src/flowent/models/graph.py +472 -0
  72. package/backend/src/flowent/models/history.py +272 -0
  73. package/backend/src/flowent/models/llm.py +62 -0
  74. package/backend/src/flowent/models/message.py +33 -0
  75. package/backend/src/flowent/models/tab.py +85 -0
  76. package/backend/src/flowent/models/todo.py +10 -0
  77. package/backend/src/flowent/network.py +146 -0
  78. package/backend/src/flowent/observability_service.py +218 -0
  79. package/backend/src/flowent/prompts/__init__.py +67 -0
  80. package/backend/src/flowent/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  81. package/backend/src/flowent/prompts/__pycache__/common.cpython-313.pyc +0 -0
  82. package/backend/src/flowent/prompts/__pycache__/steward.cpython-313.pyc +0 -0
  83. package/backend/src/flowent/prompts/common.py +250 -0
  84. package/backend/src/flowent/prompts/steward.py +64 -0
  85. package/backend/src/flowent/providers/__init__.py +23 -0
  86. package/backend/src/flowent/providers/__pycache__/__init__.cpython-313.pyc +0 -0
  87. package/backend/src/flowent/providers/__pycache__/anthropic.cpython-313.pyc +0 -0
  88. package/backend/src/flowent/providers/__pycache__/base_url.cpython-313.pyc +0 -0
  89. package/backend/src/flowent/providers/__pycache__/configuration.cpython-313.pyc +0 -0
  90. package/backend/src/flowent/providers/__pycache__/content.cpython-313.pyc +0 -0
  91. package/backend/src/flowent/providers/__pycache__/errors.cpython-313.pyc +0 -0
  92. package/backend/src/flowent/providers/__pycache__/gateway.cpython-313.pyc +0 -0
  93. package/backend/src/flowent/providers/__pycache__/headers.cpython-313.pyc +0 -0
  94. package/backend/src/flowent/providers/__pycache__/management.cpython-313.pyc +0 -0
  95. package/backend/src/flowent/providers/__pycache__/openai.cpython-313.pyc +0 -0
  96. package/backend/src/flowent/providers/__pycache__/openai_responses.cpython-313.pyc +0 -0
  97. package/backend/src/flowent/providers/__pycache__/registry.cpython-313.pyc +0 -0
  98. package/backend/src/flowent/providers/__pycache__/sse.cpython-313.pyc +0 -0
  99. package/backend/src/flowent/providers/__pycache__/thinking.cpython-313.pyc +0 -0
  100. package/backend/src/flowent/providers/anthropic.py +468 -0
  101. package/backend/src/flowent/providers/base_url.py +60 -0
  102. package/backend/src/flowent/providers/configuration.py +189 -0
  103. package/backend/src/flowent/providers/content.py +122 -0
  104. package/backend/src/flowent/providers/errors.py +223 -0
  105. package/backend/src/flowent/providers/gateway.py +169 -0
  106. package/backend/src/flowent/providers/gemini.py +447 -0
  107. package/backend/src/flowent/providers/headers.py +20 -0
  108. package/backend/src/flowent/providers/management.py +96 -0
  109. package/backend/src/flowent/providers/ollama.py +293 -0
  110. package/backend/src/flowent/providers/openai.py +422 -0
  111. package/backend/src/flowent/providers/openai_responses.py +655 -0
  112. package/backend/src/flowent/providers/registry.py +144 -0
  113. package/backend/src/flowent/providers/sse.py +31 -0
  114. package/backend/src/flowent/providers/thinking.py +79 -0
  115. package/backend/src/flowent/registry.py +73 -0
  116. package/backend/src/flowent/role_management.py +267 -0
  117. package/backend/src/flowent/routes/__init__.py +28 -0
  118. package/backend/src/flowent/routes/__pycache__/__init__.cpython-313.pyc +0 -0
  119. package/backend/src/flowent/routes/__pycache__/access.cpython-313.pyc +0 -0
  120. package/backend/src/flowent/routes/__pycache__/assistant.cpython-313.pyc +0 -0
  121. package/backend/src/flowent/routes/__pycache__/image_assets.cpython-313.pyc +0 -0
  122. package/backend/src/flowent/routes/__pycache__/mcp.cpython-313.pyc +0 -0
  123. package/backend/src/flowent/routes/__pycache__/meta.cpython-313.pyc +0 -0
  124. package/backend/src/flowent/routes/__pycache__/nodes.cpython-313.pyc +0 -0
  125. package/backend/src/flowent/routes/__pycache__/prompts.cpython-313.pyc +0 -0
  126. package/backend/src/flowent/routes/__pycache__/providers_route.cpython-313.pyc +0 -0
  127. package/backend/src/flowent/routes/__pycache__/roles.cpython-313.pyc +0 -0
  128. package/backend/src/flowent/routes/__pycache__/settings.cpython-313.pyc +0 -0
  129. package/backend/src/flowent/routes/__pycache__/tabs.cpython-313.pyc +0 -0
  130. package/backend/src/flowent/routes/__pycache__/ws.cpython-313.pyc +0 -0
  131. package/backend/src/flowent/routes/access.py +48 -0
  132. package/backend/src/flowent/routes/assistant.py +155 -0
  133. package/backend/src/flowent/routes/image_assets.py +33 -0
  134. package/backend/src/flowent/routes/mcp.py +125 -0
  135. package/backend/src/flowent/routes/meta.py +28 -0
  136. package/backend/src/flowent/routes/nodes.py +413 -0
  137. package/backend/src/flowent/routes/prompts.py +46 -0
  138. package/backend/src/flowent/routes/providers_route.py +365 -0
  139. package/backend/src/flowent/routes/roles.py +207 -0
  140. package/backend/src/flowent/routes/settings.py +328 -0
  141. package/backend/src/flowent/routes/tabs.py +310 -0
  142. package/backend/src/flowent/routes/ws.py +33 -0
  143. package/backend/src/flowent/runtime.py +165 -0
  144. package/backend/src/flowent/sandbox.py +45 -0
  145. package/backend/src/flowent/security.py +57 -0
  146. package/backend/src/flowent/settings.py +2518 -0
  147. package/backend/src/flowent/settings_management.py +298 -0
  148. package/backend/src/flowent/state_db.py +120 -0
  149. package/backend/src/flowent/static/assets/AssistantPage-VBohhz4d.js +1 -0
  150. package/backend/src/flowent/static/assets/ChannelsPage-CIydPZA_.js +1 -0
  151. package/backend/src/flowent/static/assets/McpPage-CHPm2TPY.js +7 -0
  152. package/backend/src/flowent/static/assets/PageScaffold-DteOA8V7.js +1 -0
  153. package/backend/src/flowent/static/assets/PromptsPage-CSmJ3sZg.js +1 -0
  154. package/backend/src/flowent/static/assets/ProvidersPage-sl2jeG4e.js +3 -0
  155. package/backend/src/flowent/static/assets/RolesPage-DCe7W6Km.js +1 -0
  156. package/backend/src/flowent/static/assets/SettingsPage-Bix9e63E.js +3 -0
  157. package/backend/src/flowent/static/assets/ToolsPage-favNkj5C.js +1 -0
  158. package/backend/src/flowent/static/assets/WorkspaceCommandDialog-DRS6wiD6.js +1 -0
  159. package/backend/src/flowent/static/assets/WorkspacePage-KuaDjt_D.js +3 -0
  160. package/backend/src/flowent/static/assets/WorkspacePanels-BZxBw8M5.js +1 -0
  161. package/backend/src/flowent/static/assets/alert-dialog-DIBUCmqM.js +1 -0
  162. package/backend/src/flowent/static/assets/datetime-eJqd0V2S.js +1 -0
  163. package/backend/src/flowent/static/assets/dialog-BOvHIBrg.js +1 -0
  164. package/backend/src/flowent/static/assets/elk-worker.min-C9JGDOE-.js +6312 -0
  165. package/backend/src/flowent/static/assets/graph-vendor-CHpVij2M.css +1 -0
  166. package/backend/src/flowent/static/assets/graph-vendor-DRq_-6fV.js +7 -0
  167. package/backend/src/flowent/static/assets/index-Biio-CoI.js +10 -0
  168. package/backend/src/flowent/static/assets/index-CmQvO7sl.css +1 -0
  169. package/backend/src/flowent/static/assets/layout.worker-jMHqAFbP.js +24 -0
  170. package/backend/src/flowent/static/assets/markdown-vendor-C9RtvaJh.js +29 -0
  171. package/backend/src/flowent/static/assets/modelParams-DcEhGnu0.js +1 -0
  172. package/backend/src/flowent/static/assets/react-vendor-mEs_JJxa.js +9 -0
  173. package/backend/src/flowent/static/assets/roles-BbIEIMeG.js +1 -0
  174. package/backend/src/flowent/static/assets/rolldown-runtime-BYbx6iT9.js +1 -0
  175. package/backend/src/flowent/static/assets/select-D9SwnlXF.js +1 -0
  176. package/backend/src/flowent/static/assets/surface-Bzr1FRG4.js +1 -0
  177. package/backend/src/flowent/static/assets/triState-DgLlKdRR.js +1 -0
  178. package/backend/src/flowent/static/assets/ui-vendor-UazN8rcv.js +51 -0
  179. package/backend/src/flowent/static/index.html +35 -0
  180. package/backend/src/flowent/tools/__init__.py +275 -0
  181. package/backend/src/flowent/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  182. package/backend/src/flowent/tools/__pycache__/connect.cpython-313.pyc +0 -0
  183. package/backend/src/flowent/tools/__pycache__/contacts.cpython-313.pyc +0 -0
  184. package/backend/src/flowent/tools/__pycache__/create_agent.cpython-313.pyc +0 -0
  185. package/backend/src/flowent/tools/__pycache__/create_tab.cpython-313.pyc +0 -0
  186. package/backend/src/flowent/tools/__pycache__/delete_tab.cpython-313.pyc +0 -0
  187. package/backend/src/flowent/tools/__pycache__/edit.cpython-313.pyc +0 -0
  188. package/backend/src/flowent/tools/__pycache__/exec.cpython-313.pyc +0 -0
  189. package/backend/src/flowent/tools/__pycache__/fetch.cpython-313.pyc +0 -0
  190. package/backend/src/flowent/tools/__pycache__/idle.cpython-313.pyc +0 -0
  191. package/backend/src/flowent/tools/__pycache__/list_roles.cpython-313.pyc +0 -0
  192. package/backend/src/flowent/tools/__pycache__/list_tabs.cpython-313.pyc +0 -0
  193. package/backend/src/flowent/tools/__pycache__/list_tools.cpython-313.pyc +0 -0
  194. package/backend/src/flowent/tools/__pycache__/manage_prompts.cpython-313.pyc +0 -0
  195. package/backend/src/flowent/tools/__pycache__/manage_providers.cpython-313.pyc +0 -0
  196. package/backend/src/flowent/tools/__pycache__/manage_roles.cpython-313.pyc +0 -0
  197. package/backend/src/flowent/tools/__pycache__/manage_settings.cpython-313.pyc +0 -0
  198. package/backend/src/flowent/tools/__pycache__/mcp.cpython-313.pyc +0 -0
  199. package/backend/src/flowent/tools/__pycache__/read.cpython-313.pyc +0 -0
  200. package/backend/src/flowent/tools/__pycache__/send.cpython-313.pyc +0 -0
  201. package/backend/src/flowent/tools/__pycache__/set_permissions.cpython-313.pyc +0 -0
  202. package/backend/src/flowent/tools/__pycache__/sleep.cpython-313.pyc +0 -0
  203. package/backend/src/flowent/tools/__pycache__/todo.cpython-313.pyc +0 -0
  204. package/backend/src/flowent/tools/connect.py +100 -0
  205. package/backend/src/flowent/tools/contacts.py +22 -0
  206. package/backend/src/flowent/tools/create_agent.py +191 -0
  207. package/backend/src/flowent/tools/create_tab.py +61 -0
  208. package/backend/src/flowent/tools/delete_tab.py +39 -0
  209. package/backend/src/flowent/tools/edit.py +142 -0
  210. package/backend/src/flowent/tools/exec.py +118 -0
  211. package/backend/src/flowent/tools/fetch.py +85 -0
  212. package/backend/src/flowent/tools/idle.py +27 -0
  213. package/backend/src/flowent/tools/list_roles.py +75 -0
  214. package/backend/src/flowent/tools/list_tabs.py +100 -0
  215. package/backend/src/flowent/tools/list_tools.py +28 -0
  216. package/backend/src/flowent/tools/manage_prompts.py +102 -0
  217. package/backend/src/flowent/tools/manage_providers.py +220 -0
  218. package/backend/src/flowent/tools/manage_roles.py +275 -0
  219. package/backend/src/flowent/tools/manage_settings.py +364 -0
  220. package/backend/src/flowent/tools/mcp.py +199 -0
  221. package/backend/src/flowent/tools/read.py +152 -0
  222. package/backend/src/flowent/tools/send.py +68 -0
  223. package/backend/src/flowent/tools/set_permissions.py +99 -0
  224. package/backend/src/flowent/tools/sleep.py +41 -0
  225. package/backend/src/flowent/tools/todo.py +51 -0
  226. package/backend/src/flowent/workspace_store.py +479 -0
  227. package/backend/tests/__init__.py +0 -0
  228. package/backend/tests/__pycache__/__init__.cpython-313.pyc +0 -0
  229. package/backend/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  230. package/backend/tests/conftest.py +6 -0
  231. package/backend/tests/integration/api/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  232. package/backend/tests/integration/api/__pycache__/test_access_api.cpython-313-pytest-9.0.3.pyc +0 -0
  233. package/backend/tests/integration/api/__pycache__/test_assistant_api.cpython-313-pytest-9.0.3.pyc +0 -0
  234. package/backend/tests/integration/api/__pycache__/test_frontend_mounting.cpython-313-pytest-9.0.3.pyc +0 -0
  235. package/backend/tests/integration/api/__pycache__/test_mcp_api.cpython-313-pytest-9.0.3.pyc +0 -0
  236. package/backend/tests/integration/api/__pycache__/test_meta_api.cpython-313-pytest-9.0.3.pyc +0 -0
  237. package/backend/tests/integration/api/__pycache__/test_nodes_api.cpython-313-pytest-9.0.3.pyc +0 -0
  238. package/backend/tests/integration/api/__pycache__/test_prompts_api.cpython-313-pytest-9.0.3.pyc +0 -0
  239. package/backend/tests/integration/api/__pycache__/test_roles_api.cpython-313-pytest-9.0.3.pyc +0 -0
  240. package/backend/tests/integration/api/__pycache__/test_tabs_api.cpython-313-pytest-9.0.3.pyc +0 -0
  241. package/backend/tests/integration/api/conftest.py +29 -0
  242. package/backend/tests/integration/api/test_access_api.py +182 -0
  243. package/backend/tests/integration/api/test_assistant_api.py +354 -0
  244. package/backend/tests/integration/api/test_frontend_mounting.py +61 -0
  245. package/backend/tests/integration/api/test_mcp_api.py +116 -0
  246. package/backend/tests/integration/api/test_meta_api.py +33 -0
  247. package/backend/tests/integration/api/test_nodes_api.py +722 -0
  248. package/backend/tests/integration/api/test_prompts_api.py +47 -0
  249. package/backend/tests/integration/api/test_roles_api.py +228 -0
  250. package/backend/tests/integration/api/test_tabs_api.py +802 -0
  251. package/backend/tests/unit/__pycache__/test_access.cpython-313-pytest-9.0.3.pyc +0 -0
  252. package/backend/tests/unit/__pycache__/test_cli.cpython-313-pytest-9.0.3.pyc +0 -0
  253. package/backend/tests/unit/__pycache__/test_graph_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  254. package/backend/tests/unit/__pycache__/test_network.cpython-313-pytest-9.0.3.pyc +0 -0
  255. package/backend/tests/unit/__pycache__/test_state_sqlite_storage.cpython-313-pytest-9.0.3.pyc +0 -0
  256. package/backend/tests/unit/__pycache__/test_workspace_store.cpython-313-pytest-9.0.3.pyc +0 -0
  257. package/backend/tests/unit/agent/__pycache__/test_agent_public_api.cpython-313-pytest-9.0.3.pyc +0 -0
  258. package/backend/tests/unit/agent/__pycache__/test_agent_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  259. package/backend/tests/unit/agent/test_agent_public_api.py +837 -0
  260. package/backend/tests/unit/agent/test_agent_runtime.py +2942 -0
  261. package/backend/tests/unit/channels/__pycache__/test_telegram_channel.cpython-313-pytest-9.0.3.pyc +0 -0
  262. package/backend/tests/unit/channels/test_telegram_channel.py +552 -0
  263. package/backend/tests/unit/logging/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
  264. package/backend/tests/unit/logging/test_logging.py +132 -0
  265. package/backend/tests/unit/prompts/__pycache__/test_prompts.cpython-313-pytest-9.0.3.pyc +0 -0
  266. package/backend/tests/unit/prompts/test_prompts.py +570 -0
  267. package/backend/tests/unit/providers/__pycache__/test_anthropic_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  268. package/backend/tests/unit/providers/__pycache__/test_errors.cpython-313-pytest-9.0.3.pyc +0 -0
  269. package/backend/tests/unit/providers/__pycache__/test_extract_delta_parts.cpython-313-pytest-9.0.3.pyc +0 -0
  270. package/backend/tests/unit/providers/__pycache__/test_openai_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  271. package/backend/tests/unit/providers/__pycache__/test_openai_responses.cpython-313-pytest-9.0.3.pyc +0 -0
  272. package/backend/tests/unit/providers/__pycache__/test_provider_gateway.cpython-313-pytest-9.0.3.pyc +0 -0
  273. package/backend/tests/unit/providers/__pycache__/test_think_tag_parser.cpython-313-pytest-9.0.3.pyc +0 -0
  274. package/backend/tests/unit/providers/test_anthropic_provider.py +185 -0
  275. package/backend/tests/unit/providers/test_errors.py +68 -0
  276. package/backend/tests/unit/providers/test_extract_delta_parts.py +22 -0
  277. package/backend/tests/unit/providers/test_openai_provider.py +139 -0
  278. package/backend/tests/unit/providers/test_openai_responses.py +402 -0
  279. package/backend/tests/unit/providers/test_provider_gateway.py +359 -0
  280. package/backend/tests/unit/providers/test_think_tag_parser.py +36 -0
  281. package/backend/tests/unit/routes/__pycache__/test_prompts_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  282. package/backend/tests/unit/routes/__pycache__/test_providers_route.cpython-313-pytest-9.0.3.pyc +0 -0
  283. package/backend/tests/unit/routes/__pycache__/test_roles_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  284. package/backend/tests/unit/routes/__pycache__/test_settings_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  285. package/backend/tests/unit/routes/test_prompts_routes.py +104 -0
  286. package/backend/tests/unit/routes/test_providers_route.py +370 -0
  287. package/backend/tests/unit/routes/test_roles_routes.py +535 -0
  288. package/backend/tests/unit/routes/test_settings_routes.py +1142 -0
  289. package/backend/tests/unit/runtime/__pycache__/test_bootstrap_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  290. package/backend/tests/unit/runtime/test_bootstrap_runtime.py +1002 -0
  291. package/backend/tests/unit/sandbox/__pycache__/test_sandbox_tools.cpython-313-pytest-9.0.3.pyc +0 -0
  292. package/backend/tests/unit/sandbox/test_sandbox_tools.py +78 -0
  293. package/backend/tests/unit/security/__pycache__/test_security.cpython-313-pytest-9.0.3.pyc +0 -0
  294. package/backend/tests/unit/security/test_security.py +124 -0
  295. package/backend/tests/unit/settings/__pycache__/test_settings_roles.cpython-313-pytest-9.0.3.pyc +0 -0
  296. package/backend/tests/unit/settings/test_settings_roles.py +751 -0
  297. package/backend/tests/unit/test_access.py +45 -0
  298. package/backend/tests/unit/test_cli.py +124 -0
  299. package/backend/tests/unit/test_graph_runtime.py +72 -0
  300. package/backend/tests/unit/test_network.py +51 -0
  301. package/backend/tests/unit/test_state_sqlite_storage.py +159 -0
  302. package/backend/tests/unit/test_workspace_store.py +231 -0
  303. package/backend/tests/unit/tools/__pycache__/test_connect_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  304. package/backend/tests/unit/tools/__pycache__/test_create_agent_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  305. package/backend/tests/unit/tools/__pycache__/test_delete_tab_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  306. package/backend/tests/unit/tools/__pycache__/test_edit_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  307. package/backend/tests/unit/tools/__pycache__/test_exec_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  308. package/backend/tests/unit/tools/__pycache__/test_fetch_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  309. package/backend/tests/unit/tools/__pycache__/test_manage_prompts_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  310. package/backend/tests/unit/tools/__pycache__/test_manage_providers_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  311. package/backend/tests/unit/tools/__pycache__/test_manage_roles_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  312. package/backend/tests/unit/tools/__pycache__/test_manage_settings_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  313. package/backend/tests/unit/tools/__pycache__/test_read_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  314. package/backend/tests/unit/tools/__pycache__/test_set_permissions_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  315. package/backend/tests/unit/tools/__pycache__/test_todo_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  316. package/backend/tests/unit/tools/__pycache__/test_tool_registry.cpython-313-pytest-9.0.3.pyc +0 -0
  317. package/backend/tests/unit/tools/test_connect_tool.py +228 -0
  318. package/backend/tests/unit/tools/test_create_agent_tool.py +436 -0
  319. package/backend/tests/unit/tools/test_delete_tab_tool.py +116 -0
  320. package/backend/tests/unit/tools/test_edit_tool.py +115 -0
  321. package/backend/tests/unit/tools/test_exec_tool.py +81 -0
  322. package/backend/tests/unit/tools/test_fetch_tool.py +65 -0
  323. package/backend/tests/unit/tools/test_manage_prompts_tool.py +117 -0
  324. package/backend/tests/unit/tools/test_manage_providers_tool.py +460 -0
  325. package/backend/tests/unit/tools/test_manage_roles_tool.py +411 -0
  326. package/backend/tests/unit/tools/test_manage_settings_tool.py +611 -0
  327. package/backend/tests/unit/tools/test_read_tool.py +33 -0
  328. package/backend/tests/unit/tools/test_set_permissions_tool.py +595 -0
  329. package/backend/tests/unit/tools/test_todo_tool.py +37 -0
  330. package/backend/tests/unit/tools/test_tool_registry.py +194 -0
  331. package/backend/uv.lock +1144 -0
  332. package/bin/flowent.mjs +62 -36
  333. package/dist/frontend/assets/AssistantPage-VBohhz4d.js +1 -0
  334. package/dist/frontend/assets/ChannelsPage-CIydPZA_.js +1 -0
  335. package/dist/frontend/assets/McpPage-CHPm2TPY.js +7 -0
  336. package/dist/frontend/assets/PageScaffold-DteOA8V7.js +1 -0
  337. package/dist/frontend/assets/PromptsPage-CSmJ3sZg.js +1 -0
  338. package/dist/frontend/assets/ProvidersPage-sl2jeG4e.js +3 -0
  339. package/dist/frontend/assets/RolesPage-DCe7W6Km.js +1 -0
  340. package/dist/frontend/assets/SettingsPage-Bix9e63E.js +3 -0
  341. package/dist/frontend/assets/ToolsPage-favNkj5C.js +1 -0
  342. package/dist/frontend/assets/WorkspaceCommandDialog-DRS6wiD6.js +1 -0
  343. package/dist/frontend/assets/WorkspacePage-KuaDjt_D.js +3 -0
  344. package/dist/frontend/assets/WorkspacePanels-BZxBw8M5.js +1 -0
  345. package/dist/frontend/assets/alert-dialog-DIBUCmqM.js +1 -0
  346. package/dist/frontend/assets/datetime-eJqd0V2S.js +1 -0
  347. package/dist/frontend/assets/dialog-BOvHIBrg.js +1 -0
  348. package/dist/frontend/assets/elk-worker.min-C9JGDOE-.js +6312 -0
  349. package/dist/frontend/assets/graph-vendor-CHpVij2M.css +1 -0
  350. package/dist/frontend/assets/graph-vendor-DRq_-6fV.js +7 -0
  351. package/dist/frontend/assets/index-Biio-CoI.js +10 -0
  352. package/dist/frontend/assets/index-CmQvO7sl.css +1 -0
  353. package/dist/frontend/assets/layout.worker-jMHqAFbP.js +24 -0
  354. package/dist/frontend/assets/markdown-vendor-C9RtvaJh.js +29 -0
  355. package/dist/frontend/assets/modelParams-DcEhGnu0.js +1 -0
  356. package/dist/frontend/assets/react-vendor-mEs_JJxa.js +9 -0
  357. package/dist/frontend/assets/roles-BbIEIMeG.js +1 -0
  358. package/dist/frontend/assets/rolldown-runtime-BYbx6iT9.js +1 -0
  359. package/dist/frontend/assets/select-D9SwnlXF.js +1 -0
  360. package/dist/frontend/assets/surface-Bzr1FRG4.js +1 -0
  361. package/dist/frontend/assets/triState-DgLlKdRR.js +1 -0
  362. package/dist/frontend/assets/ui-vendor-UazN8rcv.js +51 -0
  363. package/dist/frontend/index.html +35 -0
  364. package/package.json +27 -41
  365. package/dist/.next/BUILD_ID +0 -1
  366. package/dist/.next/app-path-routes-manifest.json +0 -6
  367. package/dist/.next/build-manifest.json +0 -20
  368. package/dist/.next/package.json +0 -1
  369. package/dist/.next/prerender-manifest.json +0 -114
  370. package/dist/.next/required-server-files.json +0 -333
  371. package/dist/.next/routes-manifest.json +0 -69
  372. package/dist/.next/server/app/_global-error/page/app-paths-manifest.json +0 -3
  373. package/dist/.next/server/app/_global-error/page/build-manifest.json +0 -16
  374. package/dist/.next/server/app/_global-error/page/next-font-manifest.json +0 -6
  375. package/dist/.next/server/app/_global-error/page/react-loadable-manifest.json +0 -1
  376. package/dist/.next/server/app/_global-error/page/server-reference-manifest.json +0 -4
  377. package/dist/.next/server/app/_global-error/page.js +0 -9
  378. package/dist/.next/server/app/_global-error/page.js.map +0 -5
  379. package/dist/.next/server/app/_global-error/page.js.nft.json +0 -1
  380. package/dist/.next/server/app/_global-error/page_client-reference-manifest.js +0 -3
  381. package/dist/.next/server/app/_global-error.html +0 -1
  382. package/dist/.next/server/app/_global-error.meta +0 -15
  383. package/dist/.next/server/app/_global-error.rsc +0 -14
  384. package/dist/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +0 -5
  385. package/dist/.next/server/app/_global-error.segments/_full.segment.rsc +0 -14
  386. package/dist/.next/server/app/_global-error.segments/_head.segment.rsc +0 -5
  387. package/dist/.next/server/app/_global-error.segments/_index.segment.rsc +0 -5
  388. package/dist/.next/server/app/_global-error.segments/_tree.segment.rsc +0 -1
  389. package/dist/.next/server/app/_not-found/page/app-paths-manifest.json +0 -3
  390. package/dist/.next/server/app/_not-found/page/build-manifest.json +0 -16
  391. package/dist/.next/server/app/_not-found/page/next-font-manifest.json +0 -10
  392. package/dist/.next/server/app/_not-found/page/react-loadable-manifest.json +0 -1
  393. package/dist/.next/server/app/_not-found/page/server-reference-manifest.json +0 -4
  394. package/dist/.next/server/app/_not-found/page.js +0 -13
  395. package/dist/.next/server/app/_not-found/page.js.map +0 -5
  396. package/dist/.next/server/app/_not-found/page.js.nft.json +0 -1
  397. package/dist/.next/server/app/_not-found/page_client-reference-manifest.js +0 -3
  398. package/dist/.next/server/app/_not-found.html +0 -1
  399. package/dist/.next/server/app/_not-found.meta +0 -16
  400. package/dist/.next/server/app/_not-found.rsc +0 -16
  401. package/dist/.next/server/app/_not-found.segments/_full.segment.rsc +0 -16
  402. package/dist/.next/server/app/_not-found.segments/_head.segment.rsc +0 -6
  403. package/dist/.next/server/app/_not-found.segments/_index.segment.rsc +0 -5
  404. package/dist/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +0 -5
  405. package/dist/.next/server/app/_not-found.segments/_not-found.segment.rsc +0 -5
  406. package/dist/.next/server/app/_not-found.segments/_tree.segment.rsc +0 -2
  407. package/dist/.next/server/app/icon.svg/route/app-paths-manifest.json +0 -3
  408. package/dist/.next/server/app/icon.svg/route/build-manifest.json +0 -9
  409. package/dist/.next/server/app/icon.svg/route.js +0 -6
  410. package/dist/.next/server/app/icon.svg/route.js.map +0 -5
  411. package/dist/.next/server/app/icon.svg/route.js.nft.json +0 -1
  412. package/dist/.next/server/app/icon.svg.meta +0 -1
  413. package/dist/.next/server/app/index.html +0 -1
  414. package/dist/.next/server/app/index.meta +0 -14
  415. package/dist/.next/server/app/index.rsc +0 -15
  416. package/dist/.next/server/app/index.segments/__PAGE__.segment.rsc +0 -5
  417. package/dist/.next/server/app/index.segments/_full.segment.rsc +0 -15
  418. package/dist/.next/server/app/index.segments/_head.segment.rsc +0 -6
  419. package/dist/.next/server/app/index.segments/_index.segment.rsc +0 -5
  420. package/dist/.next/server/app/index.segments/_tree.segment.rsc +0 -3
  421. package/dist/.next/server/app/page/app-paths-manifest.json +0 -3
  422. package/dist/.next/server/app/page/build-manifest.json +0 -16
  423. package/dist/.next/server/app/page/next-font-manifest.json +0 -10
  424. package/dist/.next/server/app/page/react-loadable-manifest.json +0 -1
  425. package/dist/.next/server/app/page/server-reference-manifest.json +0 -4
  426. package/dist/.next/server/app/page.js +0 -14
  427. package/dist/.next/server/app/page.js.map +0 -5
  428. package/dist/.next/server/app/page.js.nft.json +0 -1
  429. package/dist/.next/server/app/page_client-reference-manifest.js +0 -3
  430. package/dist/.next/server/app-paths-manifest.json +0 -6
  431. package/dist/.next/server/chunks/[externals]_next_dist_0arv.vj._.js +0 -3
  432. package/dist/.next/server/chunks/[root-of-the-server]__0vcj1q1._.js +0 -13
  433. package/dist/.next/server/chunks/[turbopack]_runtime.js +0 -903
  434. package/dist/.next/server/chunks/_next-internal_server_app_icon_svg_route_actions_0-0ehc~.js +0 -3
  435. package/dist/.next/server/chunks/ssr/05w9_next_dist_0ihu0u9._.js +0 -6
  436. package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_12u3mib._.js +0 -3
  437. package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_builtin_forbidden_04fbe_..js +0 -3
  438. package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_builtin_global-error_0brpl_..js +0 -3
  439. package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_builtin_unauthorized_0~2g66g.js +0 -3
  440. package/dist/.next/server/chunks/ssr/05w9_next_dist_esm_build_templates_app-page_0~cyr1_.js +0 -4
  441. package/dist/.next/server/chunks/ssr/05w9_next_dist_esm_build_templates_app-page_1105emf.js +0 -4
  442. package/dist/.next/server/chunks/ssr/05w9_next_dist_esm_build_templates_app-page_11uhyqv.js +0 -4
  443. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0.t9_75._.js +0 -33
  444. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0c0ud_z._.js +0 -3
  445. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0f9_8d4._.js +0 -3
  446. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0l5ko41._.js +0 -19
  447. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0mn6z7i._.js +0 -3
  448. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0npxxst._.js +0 -33
  449. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0qjhaca._.js +0 -3
  450. package/dist/.next/server/chunks/ssr/[root-of-the-server]__0rwgw3s._.js +0 -3
  451. package/dist/.next/server/chunks/ssr/[turbopack]_runtime.js +0 -903
  452. package/dist/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_0k77kol.js +0 -3
  453. package/dist/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_0eq97pa.js +0 -3
  454. package/dist/.next/server/chunks/ssr/_next-internal_server_app_page_actions_09-gtaw.js +0 -3
  455. package/dist/.next/server/chunks/ssr/node_modules__pnpm_056~6.6._.js +0 -3
  456. package/dist/.next/server/chunks/ssr/node_modules__pnpm_0~j0k.e._.js +0 -33
  457. package/dist/.next/server/functions-config-manifest.json +0 -4
  458. package/dist/.next/server/middleware-build-manifest.js +0 -20
  459. package/dist/.next/server/middleware-manifest.json +0 -6
  460. package/dist/.next/server/next-font-manifest.js +0 -1
  461. package/dist/.next/server/next-font-manifest.json +0 -13
  462. package/dist/.next/server/pages/404.html +0 -1
  463. package/dist/.next/server/pages/500.html +0 -1
  464. package/dist/.next/server/pages-manifest.json +0 -4
  465. package/dist/.next/server/prefetch-hints.json +0 -1
  466. package/dist/.next/server/server-reference-manifest.js +0 -1
  467. package/dist/.next/server/server-reference-manifest.json +0 -5
  468. package/dist/.next/static/SMWpxFVvkpYFxY7uuFvGB/_buildManifest.js +0 -11
  469. package/dist/.next/static/SMWpxFVvkpYFxY7uuFvGB/_clientMiddlewareManifest.js +0 -1
  470. package/dist/.next/static/SMWpxFVvkpYFxY7uuFvGB/_ssgManifest.js +0 -1
  471. package/dist/.next/static/chunks/01qk2~bgf76vu.js +0 -1
  472. package/dist/.next/static/chunks/03~yq9q893hmn.js +0 -1
  473. package/dist/.next/static/chunks/080queev.r2uy.js +0 -31
  474. package/dist/.next/static/chunks/0v3lyuj75aq50.js +0 -1
  475. package/dist/.next/static/chunks/10b~xdx5c-i7s.js +0 -5
  476. package/dist/.next/static/chunks/14gla2ascffgv.css +0 -2
  477. package/dist/.next/static/chunks/turbopack-0m-970~qvs7sc.js +0 -1
  478. package/dist/.next/static/media/7178b3e590c64307-s.11.cyxs5p-0z~.woff2 +0 -0
  479. package/dist/.next/static/media/8a480f0b521d4e75-s.06d3mdzz5bre_.woff2 +0 -0
  480. package/dist/.next/static/media/caa3a2e1cccd8315-s.p.16t1db8_9y2o~.woff2 +0 -0
  481. package/dist/package.json +0 -88
  482. package/dist/server.js +0 -38
  483. /package/{dist/.next/server/app/icon.svg.body → backend/src/flowent/static/favicon.svg} +0 -0
  484. /package/dist/{.next/static/media/icon.0.r~afrtrocz9.svg → frontend/favicon.svg} +0 -0
@@ -0,0 +1,328 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+ from copy import deepcopy
5
+
6
+ from fastapi import APIRouter, HTTPException
7
+ from loguru import logger
8
+ from pydantic import BaseModel, ConfigDict
9
+
10
+ from flowent.access import initialize_live_access_signature, set_access_code
11
+ from flowent.events import event_bus
12
+ from flowent.settings import (
13
+ TelegramApprovedChat,
14
+ TelegramSettings,
15
+ get_settings,
16
+ save_settings,
17
+ serialize_provider,
18
+ serialize_role,
19
+ serialize_settings,
20
+ serialize_telegram_settings,
21
+ )
22
+ from flowent.settings_management import (
23
+ MISSING,
24
+ apply_resolved_settings_update,
25
+ resolve_settings_update,
26
+ )
27
+
28
+ router = APIRouter()
29
+
30
+
31
+ @router.get("/api/settings/bootstrap")
32
+ async def get_settings_bootstrap() -> dict[str, object]:
33
+ from flowent._version import __version__
34
+
35
+ settings = get_settings()
36
+ return {
37
+ "settings": serialize_settings(settings),
38
+ "providers": [serialize_provider(provider) for provider in settings.providers],
39
+ "roles": [serialize_role(role) for role in settings.roles],
40
+ "version": __version__,
41
+ }
42
+
43
+
44
+ @router.get("/api/settings")
45
+ async def get_settings_api() -> dict[str, object]:
46
+ settings = get_settings()
47
+ return serialize_settings(settings)
48
+
49
+
50
+ class UpdateSettingsRequest(BaseModel):
51
+ model_config = ConfigDict(extra="forbid")
52
+ access: dict[str, object] | None = None
53
+ assistant: dict[str, object] | None = None
54
+ event_log: dict[str, object] | None = None
55
+ leader: dict[str, object] | None = None
56
+ model: dict[str, object] | None = None
57
+ working_dir: str | None = None
58
+
59
+
60
+ class UpdateTelegramSettingsRequest(BaseModel):
61
+ model_config = ConfigDict(extra="forbid")
62
+ bot_token: str | None = None
63
+
64
+
65
+ @router.post("/api/settings")
66
+ async def update_settings(req: UpdateSettingsRequest) -> dict[str, object]:
67
+ from flowent.graph_service import sync_assistant_role, sync_tab_leaders
68
+ from flowent.providers.gateway import gateway
69
+
70
+ source_settings = get_settings()
71
+ current = deepcopy(source_settings)
72
+ next_access_code: str | None = None
73
+ reauth_required = False
74
+
75
+ if req.access is not None:
76
+ raw_new_code = req.access.get("new_code", "")
77
+ raw_confirm_code = req.access.get("confirm_code", "")
78
+ if raw_new_code is not None and not isinstance(raw_new_code, str):
79
+ raise HTTPException(
80
+ status_code=400, detail="access.new_code must be a string"
81
+ )
82
+ if raw_confirm_code is not None and not isinstance(raw_confirm_code, str):
83
+ raise HTTPException(
84
+ status_code=400,
85
+ detail="access.confirm_code must be a string",
86
+ )
87
+ new_code = raw_new_code if isinstance(raw_new_code, str) else ""
88
+ confirm_code = raw_confirm_code if isinstance(raw_confirm_code, str) else ""
89
+ if new_code or confirm_code:
90
+ if not new_code.strip():
91
+ raise HTTPException(
92
+ status_code=400,
93
+ detail="access.new_code must not be empty",
94
+ )
95
+ if confirm_code != new_code:
96
+ raise HTTPException(
97
+ status_code=400,
98
+ detail="access.confirm_code must match access.new_code",
99
+ )
100
+ next_access_code = new_code
101
+
102
+ assistant_role_name: str | None = None
103
+ assistant_allow_network: object = MISSING
104
+ assistant_write_dirs: object = MISSING
105
+ if req.assistant is not None:
106
+ assistant_unknown_fields = sorted(
107
+ set(req.assistant) - {"role_name", "allow_network", "write_dirs"}
108
+ )
109
+ if assistant_unknown_fields:
110
+ raise HTTPException(
111
+ status_code=400,
112
+ detail=(
113
+ "Unknown assistant fields: " + ", ".join(assistant_unknown_fields)
114
+ ),
115
+ )
116
+ if "allow_network" in req.assistant:
117
+ assistant_allow_network = req.assistant.get("allow_network")
118
+ if "write_dirs" in req.assistant:
119
+ assistant_write_dirs = req.assistant.get("write_dirs")
120
+ raw_role_name = req.assistant.get("role_name")
121
+ if isinstance(raw_role_name, str) and raw_role_name.strip():
122
+ assistant_role_name = raw_role_name
123
+
124
+ leader_role_name: str | None = None
125
+ if req.leader is not None:
126
+ raw_role_name = req.leader.get("role_name")
127
+ if isinstance(raw_role_name, str) and raw_role_name.strip():
128
+ leader_role_name = raw_role_name
129
+
130
+ timestamp_format: str | None = None
131
+ if req.event_log is not None:
132
+ raw_timestamp_format = req.event_log.get("timestamp_format")
133
+ if isinstance(raw_timestamp_format, str):
134
+ timestamp_format = raw_timestamp_format
135
+
136
+ active_provider_id: str | None = None
137
+ active_model: str | None = None
138
+ context_window_tokens: object = MISSING
139
+ input_image: object = MISSING
140
+ output_image: object = MISSING
141
+ structured_output: object = MISSING
142
+ timeout_ms: object = MISSING
143
+ retry_policy: object = MISSING
144
+ max_retries: object = MISSING
145
+ retry_initial_delay_seconds: object = MISSING
146
+ retry_max_delay_seconds: object = MISSING
147
+ retry_backoff_cap_retries: object = MISSING
148
+ auto_compact_token_limit: object = MISSING
149
+ model_params: object = MISSING
150
+ if req.model is not None:
151
+ raw_active_provider_id = req.model.get("active_provider_id")
152
+ if isinstance(raw_active_provider_id, str):
153
+ active_provider_id = raw_active_provider_id
154
+ raw_active_model = req.model.get("active_model")
155
+ if isinstance(raw_active_model, str):
156
+ active_model = raw_active_model
157
+ if "context_window_tokens" in req.model:
158
+ context_window_tokens = req.model.get("context_window_tokens")
159
+ if "input_image" in req.model:
160
+ input_image = req.model.get("input_image")
161
+ if "output_image" in req.model:
162
+ output_image = req.model.get("output_image")
163
+ if "structured_output" in req.model:
164
+ structured_output = req.model.get("structured_output")
165
+ if "timeout_ms" in req.model:
166
+ timeout_ms = req.model.get("timeout_ms")
167
+ if "retry_policy" in req.model:
168
+ retry_policy = req.model.get("retry_policy")
169
+ if "max_retries" in req.model:
170
+ max_retries = req.model.get("max_retries")
171
+ if "retry_initial_delay_seconds" in req.model:
172
+ retry_initial_delay_seconds = req.model.get("retry_initial_delay_seconds")
173
+ if "retry_max_delay_seconds" in req.model:
174
+ retry_max_delay_seconds = req.model.get("retry_max_delay_seconds")
175
+ if "retry_backoff_cap_retries" in req.model:
176
+ retry_backoff_cap_retries = req.model.get("retry_backoff_cap_retries")
177
+ if "auto_compact_token_limit" in req.model:
178
+ auto_compact_token_limit = req.model.get("auto_compact_token_limit")
179
+ if "params" in req.model:
180
+ model_params = req.model.get("params")
181
+
182
+ try:
183
+ resolved = resolve_settings_update(
184
+ current,
185
+ working_dir=req.working_dir,
186
+ assistant_role_name=assistant_role_name,
187
+ assistant_allow_network=assistant_allow_network,
188
+ assistant_write_dirs=assistant_write_dirs,
189
+ leader_role_name=leader_role_name,
190
+ active_provider_id=active_provider_id,
191
+ active_model=active_model,
192
+ context_window_tokens=context_window_tokens,
193
+ input_image=input_image,
194
+ output_image=output_image,
195
+ structured_output=structured_output,
196
+ max_retries=max_retries,
197
+ retry_policy=retry_policy,
198
+ timeout_ms=timeout_ms,
199
+ retry_initial_delay_seconds=retry_initial_delay_seconds,
200
+ retry_max_delay_seconds=retry_max_delay_seconds,
201
+ retry_backoff_cap_retries=retry_backoff_cap_retries,
202
+ auto_compact_token_limit=auto_compact_token_limit,
203
+ model_params=model_params,
204
+ timestamp_format=timestamp_format,
205
+ )
206
+ except ValueError as exc:
207
+ raise HTTPException(status_code=400, detail=str(exc)) from exc
208
+
209
+ apply_resolved_settings_update(current, resolved)
210
+
211
+ if next_access_code is not None:
212
+ set_access_code(current, next_access_code)
213
+ reauth_required = True
214
+
215
+ save_settings(current)
216
+ source_settings.__dict__.clear()
217
+ source_settings.__dict__.update(deepcopy(current).__dict__)
218
+ initialize_live_access_signature()
219
+ try:
220
+ sync_assistant_role(reason="assistant settings updated")
221
+ sync_tab_leaders(reason="leader settings updated")
222
+ gateway.invalidate_cache()
223
+ except Exception:
224
+ logger.exception("Settings saved but runtime synchronization failed")
225
+ if reauth_required:
226
+ event_bus.close_all_connections(code=4001, reason="Access code rotated")
227
+ logger.info("Settings updated")
228
+ return {
229
+ "status": "saved",
230
+ "settings": serialize_settings(current),
231
+ "reauth_required": reauth_required,
232
+ }
233
+
234
+
235
+ @router.get("/api/settings/telegram")
236
+ async def get_telegram_settings() -> dict[str, object]:
237
+ settings = get_settings()
238
+ return serialize_telegram_settings(settings.telegram)
239
+
240
+
241
+ @router.patch("/api/settings/telegram")
242
+ async def update_telegram_settings(
243
+ req: UpdateTelegramSettingsRequest,
244
+ ) -> dict[str, object]:
245
+ from flowent.runtime import restart_telegram_channel
246
+
247
+ settings = get_settings()
248
+ previous_token = settings.telegram.bot_token
249
+
250
+ next_token = previous_token
251
+ if req.bot_token is not None:
252
+ next_token = req.bot_token.strip()
253
+
254
+ settings.telegram = TelegramSettings(
255
+ bot_token=next_token,
256
+ pending_chats=list(settings.telegram.pending_chats),
257
+ approved_chats=list(settings.telegram.approved_chats),
258
+ )
259
+ save_settings(settings)
260
+
261
+ if next_token != previous_token:
262
+ restart_telegram_channel()
263
+
264
+ logger.info("Telegram settings updated")
265
+ return {
266
+ "status": "saved",
267
+ "telegram": serialize_telegram_settings(settings.telegram),
268
+ }
269
+
270
+
271
+ @router.post("/api/settings/telegram/approve/{chat_id}")
272
+ async def approve_telegram_chat(chat_id: int) -> dict[str, object]:
273
+ settings = get_settings()
274
+ pending_chat = next(
275
+ (chat for chat in settings.telegram.pending_chats if chat.chat_id == chat_id),
276
+ None,
277
+ )
278
+ if pending_chat is None:
279
+ raise HTTPException(status_code=404, detail="Pending Telegram chat not found")
280
+
281
+ settings.telegram.pending_chats = [
282
+ chat for chat in settings.telegram.pending_chats if chat.chat_id != chat_id
283
+ ]
284
+ if not any(chat.chat_id == chat_id for chat in settings.telegram.approved_chats):
285
+ settings.telegram.approved_chats.append(
286
+ TelegramApprovedChat(
287
+ chat_id=pending_chat.chat_id,
288
+ username=pending_chat.username,
289
+ display_name=pending_chat.display_name,
290
+ approved_at=time.time(),
291
+ )
292
+ )
293
+ save_settings(settings)
294
+ logger.info("Telegram chat approved: {}", chat_id)
295
+ return {
296
+ "status": "approved",
297
+ "telegram": serialize_telegram_settings(settings.telegram),
298
+ }
299
+
300
+
301
+ @router.delete("/api/settings/telegram/pending/{chat_id}")
302
+ async def delete_pending_telegram_chat(chat_id: int) -> dict[str, object]:
303
+ settings = get_settings()
304
+ settings.telegram.pending_chats = [
305
+ chat for chat in settings.telegram.pending_chats if chat.chat_id != chat_id
306
+ ]
307
+ save_settings(settings)
308
+ logger.info("Pending Telegram chat removed: {}", chat_id)
309
+ return {
310
+ "status": "deleted",
311
+ "telegram": serialize_telegram_settings(settings.telegram),
312
+ }
313
+
314
+
315
+ @router.delete("/api/settings/telegram/chat/{chat_id}")
316
+ async def delete_telegram_chat(chat_id: int) -> dict[str, object]:
317
+ settings = get_settings()
318
+ settings.telegram.approved_chats = [
319
+ existing_chat
320
+ for existing_chat in settings.telegram.approved_chats
321
+ if existing_chat.chat_id != chat_id
322
+ ]
323
+ save_settings(settings)
324
+ logger.info("Telegram chat removed: {}", chat_id)
325
+ return {
326
+ "status": "deleted",
327
+ "telegram": serialize_telegram_settings(settings.telegram),
328
+ }
@@ -0,0 +1,310 @@
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
+ activate_tab,
8
+ create_agent_node,
9
+ create_edge,
10
+ create_graph_node,
11
+ create_tab,
12
+ deactivate_tab,
13
+ delete_agent_node,
14
+ delete_edge,
15
+ delete_tab,
16
+ duplicate_tab,
17
+ list_node_connection_ids,
18
+ list_tab_edges,
19
+ list_workflow_nodes,
20
+ serialize_tab_summary,
21
+ update_tab_definition,
22
+ )
23
+ from flowent.models import AgentState, WorkflowNodeKind
24
+ from flowent.registry import registry
25
+ from flowent.workspace_store import workspace_store
26
+
27
+ router = APIRouter()
28
+
29
+
30
+ class CreateTabRequest(BaseModel):
31
+ model_config = ConfigDict(extra="forbid")
32
+ title: str
33
+ allow_network: bool = False
34
+ write_dirs: list[str] = []
35
+
36
+
37
+ class CreateTabNodeRequest(BaseModel):
38
+ model_config = ConfigDict(extra="forbid")
39
+ node_type: str = WorkflowNodeKind.AGENT.value
40
+ role_name: str | None = None
41
+ name: str | None = None
42
+ config: dict[str, object] = {}
43
+ tools: list[str] = []
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 | None = None
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.post("/api/workflows/{tab_id}/activate")
140
+ async def activate_workflow_route(tab_id: str) -> dict[str, object]:
141
+ updated, errors, error = activate_tab(tab_id=tab_id, actor_id=tab_id)
142
+ if errors:
143
+ raise HTTPException(status_code=400, detail={"errors": errors})
144
+ if error is not None or updated is None:
145
+ raise HTTPException(
146
+ status_code=404 if error and error.endswith("not found") else 400,
147
+ detail=error or "Failed to activate workflow",
148
+ )
149
+ return serialize_tab_summary(updated)
150
+
151
+
152
+ @router.post("/api/workflows/{tab_id}/deactivate")
153
+ async def deactivate_workflow_route(tab_id: str) -> dict[str, object]:
154
+ updated, error = deactivate_tab(tab_id=tab_id, actor_id=tab_id)
155
+ if error is not None or updated is None:
156
+ raise HTTPException(
157
+ status_code=404 if error and error.endswith("not found") else 400,
158
+ detail=error or "Failed to deactivate workflow",
159
+ )
160
+ return serialize_tab_summary(updated)
161
+
162
+
163
+ @router.get("/api/workflows/{tab_id}")
164
+ async def get_workflow(tab_id: str) -> dict[str, object]:
165
+ tab = workspace_store.get_tab(tab_id)
166
+ if tab is None:
167
+ raise HTTPException(status_code=404, detail="Workflow not found")
168
+ nodes = [
169
+ _serialize_workflow_node(tab_id=tab_id, node_id=node.id)
170
+ for node in list_workflow_nodes(tab_id)
171
+ ]
172
+ edges = [edge.serialize() for edge in list_tab_edges(tab_id)]
173
+ return {
174
+ "workflow": serialize_tab_summary(tab),
175
+ "nodes": nodes,
176
+ "edges": edges,
177
+ }
178
+
179
+
180
+ @router.put("/api/workflows/{tab_id}/definition")
181
+ async def update_workflow_definition_route(
182
+ tab_id: str,
183
+ req: UpdateTabDefinitionRequest,
184
+ ) -> dict[str, object]:
185
+ updated, error = update_tab_definition(
186
+ tab_id=tab_id,
187
+ definition_payload=req.definition,
188
+ actor_id=tab_id,
189
+ )
190
+ if error is not None or updated is None:
191
+ raise HTTPException(
192
+ status_code=400 if error and not error.endswith("not found") else 404,
193
+ detail=error or "Failed to update workflow definition",
194
+ )
195
+ return serialize_tab_summary(updated)
196
+
197
+
198
+ @router.delete("/api/workflows/{tab_id}")
199
+ async def delete_workflow_route(tab_id: str) -> dict[str, object]:
200
+ deleted, error = delete_tab(tab_id=tab_id)
201
+ if error is not None or deleted is None:
202
+ status_code = 404 if error and error.endswith("not found") else 400
203
+ raise HTTPException(
204
+ status_code=status_code,
205
+ detail=error or "Failed to delete workflow",
206
+ )
207
+ return deleted
208
+
209
+
210
+ @router.post("/api/workflows/{tab_id}/nodes")
211
+ async def create_workflow_node(
212
+ tab_id: str,
213
+ req: CreateTabNodeRequest,
214
+ ) -> dict[str, object]:
215
+ try:
216
+ node_type = WorkflowNodeKind(req.node_type.strip())
217
+ except ValueError as exc:
218
+ raise HTTPException(status_code=400, detail="Invalid node_type") from exc
219
+
220
+ if node_type == WorkflowNodeKind.AGENT:
221
+ if not isinstance(req.role_name, str) or not req.role_name.strip():
222
+ raise HTTPException(status_code=400, detail="role_name is required")
223
+ record, error = create_agent_node(
224
+ role_name=req.role_name,
225
+ tab_id=tab_id,
226
+ name=req.name,
227
+ tools=req.tools,
228
+ )
229
+ if error is not None or record is None:
230
+ raise HTTPException(
231
+ status_code=400, detail=error or "Failed to create node"
232
+ )
233
+ return _serialize_workflow_node(tab_id=tab_id, node_id=record.id)
234
+
235
+ node, error = create_graph_node(
236
+ tab_id=tab_id,
237
+ node_type=node_type,
238
+ config={
239
+ **req.config,
240
+ **(
241
+ {"name": req.name}
242
+ if isinstance(req.name, str) and req.name.strip()
243
+ else {}
244
+ ),
245
+ },
246
+ actor_id=tab_id,
247
+ )
248
+ if error is not None or node is None:
249
+ raise HTTPException(status_code=400, detail=error or "Failed to create node")
250
+ return _serialize_workflow_node(tab_id=tab_id, node_id=node.id)
251
+
252
+
253
+ @router.post("/api/workflows/{tab_id}/edges")
254
+ async def create_workflow_edge(
255
+ tab_id: str,
256
+ req: CreateTabEdgeRequest,
257
+ ) -> dict[str, object]:
258
+ tab = workspace_store.get_tab(tab_id)
259
+ if tab is None:
260
+ raise HTTPException(status_code=404, detail="Workflow not found")
261
+ edge, error = create_edge(
262
+ tab_id=tab_id,
263
+ from_node_id=req.from_node_id,
264
+ from_port_key=req.from_port_key,
265
+ to_node_id=req.to_node_id,
266
+ to_port_key=req.to_port_key,
267
+ kind=req.kind or "",
268
+ )
269
+ if error is not None or edge is None:
270
+ raise HTTPException(status_code=400, detail=error or "Failed to create edge")
271
+ return edge.serialize()
272
+
273
+
274
+ @router.delete("/api/workflows/{tab_id}/nodes/{node_id}")
275
+ async def delete_workflow_node(tab_id: str, node_id: str) -> dict[str, object]:
276
+ deleted, error = delete_agent_node(
277
+ tab_id=tab_id,
278
+ node_id=node_id,
279
+ )
280
+ if error is not None or deleted is None:
281
+ status_code = 404 if error and error.endswith("not found") else 400
282
+ raise HTTPException(
283
+ status_code=status_code, detail=error or "Failed to delete node"
284
+ )
285
+ return deleted
286
+
287
+
288
+ @router.delete("/api/workflows/{tab_id}/edges")
289
+ async def delete_workflow_edge(
290
+ tab_id: str,
291
+ edge_id: str | None = None,
292
+ from_node_id: str | None = None,
293
+ to_node_id: str | None = None,
294
+ from_port_key: str | None = None,
295
+ to_port_key: str | None = None,
296
+ ) -> dict[str, object]:
297
+ deleted, error = delete_edge(
298
+ tab_id=tab_id,
299
+ edge_id=edge_id,
300
+ from_node_id=from_node_id,
301
+ to_node_id=to_node_id,
302
+ from_port_key=from_port_key,
303
+ to_port_key=to_port_key,
304
+ )
305
+ if error is not None or deleted is None:
306
+ status_code = 404 if error and error.endswith("not found") else 400
307
+ raise HTTPException(
308
+ status_code=status_code, detail=error or "Failed to delete edge"
309
+ )
310
+ 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)