flowent 0.0.4 → 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 (311) hide show
  1. package/README.md +1 -1
  2. package/backend/README.md +74 -0
  3. package/backend/pyproject.toml +2 -1
  4. package/backend/src/flowent/__pycache__/__init__.cpython-313.pyc +0 -0
  5. package/backend/src/flowent/__pycache__/_version.cpython-313.pyc +0 -0
  6. package/backend/src/flowent/__pycache__/access.cpython-313.pyc +0 -0
  7. package/backend/src/flowent/__pycache__/agent.cpython-313.pyc +0 -0
  8. package/backend/src/flowent/__pycache__/assistant_commands.cpython-313.pyc +0 -0
  9. package/backend/src/flowent/__pycache__/cli.cpython-313.pyc +0 -0
  10. package/backend/src/flowent/__pycache__/config.cpython-313.pyc +0 -0
  11. package/backend/src/flowent/__pycache__/events.cpython-313.pyc +0 -0
  12. package/backend/src/flowent/__pycache__/graph_runtime.cpython-313.pyc +0 -0
  13. package/backend/src/flowent/__pycache__/graph_service.cpython-313.pyc +0 -0
  14. package/backend/src/flowent/__pycache__/image_assets.cpython-313.pyc +0 -0
  15. package/backend/src/flowent/__pycache__/logging.cpython-313.pyc +0 -0
  16. package/backend/src/flowent/__pycache__/main.cpython-313.pyc +0 -0
  17. package/backend/src/flowent/__pycache__/mcp_service.cpython-313.pyc +0 -0
  18. package/backend/src/flowent/__pycache__/model_metadata.cpython-313.pyc +0 -0
  19. package/backend/src/flowent/__pycache__/network.cpython-313.pyc +0 -0
  20. package/backend/src/flowent/__pycache__/{stats_service.cpython-313.pyc → observability_service.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__/workspace_store.cpython-313.pyc +0 -0
  30. package/backend/src/flowent/agent.py +364 -52
  31. package/backend/src/flowent/assistant_commands.py +31 -22
  32. package/backend/src/flowent/channels/__pycache__/__init__.cpython-313.pyc +0 -0
  33. package/backend/src/flowent/channels/__pycache__/telegram.cpython-313.pyc +0 -0
  34. package/backend/src/flowent/channels/telegram.py +4 -4
  35. package/backend/src/flowent/graph_service.py +1307 -145
  36. package/backend/src/flowent/mcp_service.py +21 -7
  37. package/backend/src/flowent/model_metadata.py +4 -0
  38. package/backend/src/flowent/models/__init__.py +6 -2
  39. package/backend/src/flowent/models/__pycache__/__init__.cpython-313.pyc +0 -0
  40. package/backend/src/flowent/models/__pycache__/agent.cpython-313.pyc +0 -0
  41. package/backend/src/flowent/models/__pycache__/base.cpython-313.pyc +0 -0
  42. package/backend/src/flowent/models/__pycache__/blueprint.cpython-313.pyc +0 -0
  43. package/backend/src/flowent/models/__pycache__/content.cpython-313.pyc +0 -0
  44. package/backend/src/flowent/models/__pycache__/delta.cpython-313.pyc +0 -0
  45. package/backend/src/flowent/models/__pycache__/event.cpython-313.pyc +0 -0
  46. package/backend/src/flowent/models/__pycache__/graph.cpython-313.pyc +0 -0
  47. package/backend/src/flowent/models/__pycache__/history.cpython-313.pyc +0 -0
  48. package/backend/src/flowent/models/__pycache__/llm.cpython-313.pyc +0 -0
  49. package/backend/src/flowent/models/__pycache__/message.cpython-313.pyc +0 -0
  50. package/backend/src/flowent/models/__pycache__/tab.cpython-313.pyc +0 -0
  51. package/backend/src/flowent/models/__pycache__/todo.cpython-313.pyc +0 -0
  52. package/backend/src/flowent/models/agent.py +1 -0
  53. package/backend/src/flowent/models/graph.py +44 -9
  54. package/backend/src/flowent/models/history.py +73 -15
  55. package/backend/src/flowent/models/llm.py +1 -0
  56. package/backend/src/flowent/models/message.py +6 -0
  57. package/backend/src/flowent/models/tab.py +38 -1
  58. package/backend/src/flowent/{stats_service.py → observability_service.py} +4 -4
  59. package/backend/src/flowent/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  60. package/backend/src/flowent/prompts/__pycache__/common.cpython-313.pyc +0 -0
  61. package/backend/src/flowent/prompts/__pycache__/steward.cpython-313.pyc +0 -0
  62. package/backend/src/flowent/prompts/common.py +2 -2
  63. package/backend/src/flowent/prompts/steward.py +2 -2
  64. package/backend/src/flowent/providers/__pycache__/__init__.cpython-313.pyc +0 -0
  65. package/backend/src/flowent/providers/__pycache__/anthropic.cpython-313.pyc +0 -0
  66. package/backend/src/flowent/providers/__pycache__/base_url.cpython-313.pyc +0 -0
  67. package/backend/src/flowent/providers/__pycache__/configuration.cpython-313.pyc +0 -0
  68. package/backend/src/flowent/providers/__pycache__/content.cpython-313.pyc +0 -0
  69. package/backend/src/flowent/providers/__pycache__/errors.cpython-313.pyc +0 -0
  70. package/backend/src/flowent/providers/__pycache__/gateway.cpython-313.pyc +0 -0
  71. package/backend/src/flowent/providers/__pycache__/headers.cpython-313.pyc +0 -0
  72. package/backend/src/flowent/providers/__pycache__/management.cpython-313.pyc +0 -0
  73. package/backend/src/flowent/providers/__pycache__/openai.cpython-313.pyc +0 -0
  74. package/backend/src/flowent/providers/__pycache__/openai_responses.cpython-313.pyc +0 -0
  75. package/backend/src/flowent/providers/__pycache__/registry.cpython-313.pyc +0 -0
  76. package/backend/src/flowent/providers/__pycache__/sse.cpython-313.pyc +0 -0
  77. package/backend/src/flowent/providers/__pycache__/thinking.cpython-313.pyc +0 -0
  78. package/backend/src/flowent/providers/configuration.py +7 -0
  79. package/backend/src/flowent/role_management.py +12 -0
  80. package/backend/src/flowent/routes/__init__.py +0 -2
  81. package/backend/src/flowent/routes/__pycache__/__init__.cpython-313.pyc +0 -0
  82. package/backend/src/flowent/routes/__pycache__/access.cpython-313.pyc +0 -0
  83. package/backend/src/flowent/routes/__pycache__/assistant.cpython-313.pyc +0 -0
  84. package/backend/src/flowent/routes/__pycache__/image_assets.cpython-313.pyc +0 -0
  85. package/backend/src/flowent/routes/__pycache__/mcp.cpython-313.pyc +0 -0
  86. package/backend/src/flowent/routes/__pycache__/meta.cpython-313.pyc +0 -0
  87. package/backend/src/flowent/routes/__pycache__/nodes.cpython-313.pyc +0 -0
  88. package/backend/src/flowent/routes/__pycache__/prompts.cpython-313.pyc +0 -0
  89. package/backend/src/flowent/routes/__pycache__/providers_route.cpython-313.pyc +0 -0
  90. package/backend/src/flowent/routes/__pycache__/roles.cpython-313.pyc +0 -0
  91. package/backend/src/flowent/routes/__pycache__/settings.cpython-313.pyc +0 -0
  92. package/backend/src/flowent/routes/__pycache__/tabs.cpython-313.pyc +0 -0
  93. package/backend/src/flowent/routes/__pycache__/ws.cpython-313.pyc +0 -0
  94. package/backend/src/flowent/routes/assistant.py +4 -4
  95. package/backend/src/flowent/routes/nodes.py +54 -6
  96. package/backend/src/flowent/routes/providers_route.py +1 -0
  97. package/backend/src/flowent/routes/roles.py +1 -1
  98. package/backend/src/flowent/routes/settings.py +4 -0
  99. package/backend/src/flowent/routes/tabs.py +29 -11
  100. package/backend/src/flowent/runtime.py +7 -30
  101. package/backend/src/flowent/security.py +23 -8
  102. package/backend/src/flowent/settings.py +56 -5
  103. package/backend/src/flowent/settings_management.py +12 -0
  104. package/backend/src/flowent/static/assets/AssistantPage-VBohhz4d.js +1 -0
  105. package/backend/src/flowent/static/assets/ChannelsPage-CIydPZA_.js +1 -0
  106. package/backend/src/flowent/static/assets/McpPage-CHPm2TPY.js +7 -0
  107. package/backend/src/flowent/static/assets/PageScaffold-DteOA8V7.js +1 -0
  108. package/backend/src/flowent/static/assets/PromptsPage-CSmJ3sZg.js +1 -0
  109. package/backend/src/flowent/static/assets/ProvidersPage-sl2jeG4e.js +3 -0
  110. package/backend/src/flowent/static/assets/RolesPage-DCe7W6Km.js +1 -0
  111. package/backend/src/flowent/static/assets/SettingsPage-Bix9e63E.js +3 -0
  112. package/backend/src/flowent/static/assets/ToolsPage-favNkj5C.js +1 -0
  113. package/backend/src/flowent/static/assets/WorkspaceCommandDialog-DRS6wiD6.js +1 -0
  114. package/backend/src/flowent/static/assets/WorkspacePage-KuaDjt_D.js +3 -0
  115. package/backend/src/flowent/static/assets/WorkspacePanels-BZxBw8M5.js +1 -0
  116. package/backend/src/flowent/static/assets/alert-dialog-DIBUCmqM.js +1 -0
  117. package/backend/src/flowent/static/assets/{dialog-BeGSweF6.js → dialog-BOvHIBrg.js} +1 -1
  118. package/backend/src/flowent/static/assets/index-Biio-CoI.js +10 -0
  119. package/backend/src/flowent/static/assets/index-CmQvO7sl.css +1 -0
  120. package/backend/src/flowent/static/assets/modelParams-DcEhGnu0.js +1 -0
  121. package/backend/src/flowent/static/assets/roles-BbIEIMeG.js +1 -0
  122. package/backend/src/flowent/static/assets/select-D9SwnlXF.js +1 -0
  123. package/backend/src/flowent/static/assets/surface-Bzr1FRG4.js +1 -0
  124. package/backend/src/flowent/static/assets/{ui-vendor-Dg9NNnWX.js → ui-vendor-UazN8rcv.js} +15 -15
  125. package/backend/src/flowent/static/index.html +3 -4
  126. package/backend/src/flowent/tools/__init__.py +76 -2
  127. package/backend/src/flowent/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  128. package/backend/src/flowent/tools/__pycache__/connect.cpython-313.pyc +0 -0
  129. package/backend/src/flowent/tools/__pycache__/contacts.cpython-313.pyc +0 -0
  130. package/backend/src/flowent/tools/__pycache__/create_agent.cpython-313.pyc +0 -0
  131. package/backend/src/flowent/tools/__pycache__/create_tab.cpython-313.pyc +0 -0
  132. package/backend/src/flowent/tools/__pycache__/delete_tab.cpython-313.pyc +0 -0
  133. package/backend/src/flowent/tools/__pycache__/edit.cpython-313.pyc +0 -0
  134. package/backend/src/flowent/tools/__pycache__/exec.cpython-313.pyc +0 -0
  135. package/backend/src/flowent/tools/__pycache__/fetch.cpython-313.pyc +0 -0
  136. package/backend/src/flowent/tools/__pycache__/idle.cpython-313.pyc +0 -0
  137. package/backend/src/flowent/tools/__pycache__/list_roles.cpython-313.pyc +0 -0
  138. package/backend/src/flowent/tools/__pycache__/list_tabs.cpython-313.pyc +0 -0
  139. package/backend/src/flowent/tools/__pycache__/list_tools.cpython-313.pyc +0 -0
  140. package/backend/src/flowent/tools/__pycache__/manage_prompts.cpython-313.pyc +0 -0
  141. package/backend/src/flowent/tools/__pycache__/manage_providers.cpython-313.pyc +0 -0
  142. package/backend/src/flowent/tools/__pycache__/manage_roles.cpython-313.pyc +0 -0
  143. package/backend/src/flowent/tools/__pycache__/manage_settings.cpython-313.pyc +0 -0
  144. package/backend/src/flowent/tools/__pycache__/mcp.cpython-313.pyc +0 -0
  145. package/backend/src/flowent/tools/__pycache__/read.cpython-313.pyc +0 -0
  146. package/backend/src/flowent/tools/__pycache__/send.cpython-313.pyc +0 -0
  147. package/backend/src/flowent/tools/__pycache__/set_permissions.cpython-313.pyc +0 -0
  148. package/backend/src/flowent/tools/__pycache__/sleep.cpython-313.pyc +0 -0
  149. package/backend/src/flowent/tools/__pycache__/todo.cpython-313.pyc +0 -0
  150. package/backend/src/flowent/tools/connect.py +10 -66
  151. package/backend/src/flowent/tools/contacts.py +1 -1
  152. package/backend/src/flowent/tools/create_agent.py +9 -88
  153. package/backend/src/flowent/tools/create_tab.py +7 -5
  154. package/backend/src/flowent/tools/exec.py +3 -2
  155. package/backend/src/flowent/tools/list_roles.py +29 -4
  156. package/backend/src/flowent/tools/list_tabs.py +4 -0
  157. package/backend/src/flowent/tools/list_tools.py +5 -1
  158. package/backend/src/flowent/tools/manage_settings.py +18 -0
  159. package/backend/src/flowent/tools/send.py +21 -3
  160. package/backend/src/flowent/tools/set_permissions.py +21 -6
  161. package/backend/tests/__pycache__/__init__.cpython-313.pyc +0 -0
  162. package/backend/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  163. package/backend/tests/integration/api/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  164. package/backend/tests/integration/api/__pycache__/test_access_api.cpython-313-pytest-9.0.3.pyc +0 -0
  165. package/backend/tests/integration/api/__pycache__/test_assistant_api.cpython-313-pytest-9.0.3.pyc +0 -0
  166. package/backend/tests/integration/api/__pycache__/test_frontend_mounting.cpython-313-pytest-9.0.3.pyc +0 -0
  167. package/backend/tests/integration/api/__pycache__/test_mcp_api.cpython-313-pytest-9.0.3.pyc +0 -0
  168. package/backend/tests/integration/api/__pycache__/test_meta_api.cpython-313-pytest-9.0.3.pyc +0 -0
  169. package/backend/tests/integration/api/__pycache__/test_nodes_api.cpython-313-pytest-9.0.3.pyc +0 -0
  170. package/backend/tests/integration/api/__pycache__/test_prompts_api.cpython-313-pytest-9.0.3.pyc +0 -0
  171. package/backend/tests/integration/api/__pycache__/test_roles_api.cpython-313-pytest-9.0.3.pyc +0 -0
  172. package/backend/tests/integration/api/__pycache__/test_tabs_api.cpython-313-pytest-9.0.3.pyc +0 -0
  173. package/backend/tests/integration/api/test_assistant_api.py +1 -1
  174. package/backend/tests/integration/api/test_nodes_api.py +257 -21
  175. package/backend/tests/integration/api/test_roles_api.py +3 -2
  176. package/backend/tests/integration/api/test_tabs_api.py +312 -11
  177. package/backend/tests/unit/__pycache__/test_access.cpython-313-pytest-9.0.3.pyc +0 -0
  178. package/backend/tests/unit/__pycache__/test_cli.cpython-313-pytest-9.0.3.pyc +0 -0
  179. package/backend/tests/unit/__pycache__/test_graph_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  180. package/backend/tests/unit/__pycache__/test_network.cpython-313-pytest-9.0.3.pyc +0 -0
  181. package/backend/tests/unit/__pycache__/test_state_sqlite_storage.cpython-313-pytest-9.0.3.pyc +0 -0
  182. package/backend/tests/unit/__pycache__/test_workspace_store.cpython-313-pytest-9.0.3.pyc +0 -0
  183. package/backend/tests/unit/agent/__pycache__/test_agent_public_api.cpython-313-pytest-9.0.3.pyc +0 -0
  184. package/backend/tests/unit/agent/__pycache__/test_agent_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  185. package/backend/tests/unit/agent/test_agent_public_api.py +162 -71
  186. package/backend/tests/unit/agent/test_agent_runtime.py +285 -69
  187. package/backend/tests/unit/channels/__pycache__/test_telegram_channel.cpython-313-pytest-9.0.3.pyc +0 -0
  188. package/backend/tests/unit/logging/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
  189. package/backend/tests/unit/prompts/__pycache__/test_prompts.cpython-313-pytest-9.0.3.pyc +0 -0
  190. package/backend/tests/unit/prompts/test_prompts.py +3 -2
  191. package/backend/tests/unit/providers/__pycache__/test_anthropic_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  192. package/backend/tests/unit/providers/__pycache__/test_errors.cpython-313-pytest-9.0.3.pyc +0 -0
  193. package/backend/tests/unit/providers/__pycache__/test_extract_delta_parts.cpython-313-pytest-9.0.3.pyc +0 -0
  194. package/backend/tests/unit/providers/__pycache__/test_openai_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  195. package/backend/tests/unit/providers/__pycache__/test_openai_responses.cpython-313-pytest-9.0.3.pyc +0 -0
  196. package/backend/tests/unit/providers/__pycache__/test_provider_gateway.cpython-313-pytest-9.0.3.pyc +0 -0
  197. package/backend/tests/unit/providers/__pycache__/test_think_tag_parser.cpython-313-pytest-9.0.3.pyc +0 -0
  198. package/backend/tests/unit/routes/__pycache__/test_prompts_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  199. package/backend/tests/unit/routes/__pycache__/test_providers_route.cpython-313-pytest-9.0.3.pyc +0 -0
  200. package/backend/tests/unit/routes/__pycache__/test_roles_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  201. package/backend/tests/unit/routes/__pycache__/test_settings_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  202. package/backend/tests/unit/routes/test_providers_route.py +2 -0
  203. package/backend/tests/unit/routes/test_roles_routes.py +109 -0
  204. package/backend/tests/unit/routes/test_settings_routes.py +4 -0
  205. package/backend/tests/unit/runtime/__pycache__/test_bootstrap_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  206. package/backend/tests/unit/runtime/test_bootstrap_runtime.py +8 -18
  207. package/backend/tests/unit/sandbox/__pycache__/test_sandbox_tools.cpython-313-pytest-9.0.3.pyc +0 -0
  208. package/backend/tests/unit/security/__pycache__/test_security.cpython-313-pytest-9.0.3.pyc +0 -0
  209. package/backend/tests/unit/security/test_security.py +16 -2
  210. package/backend/tests/unit/settings/__pycache__/test_settings_roles.cpython-313-pytest-9.0.3.pyc +0 -0
  211. package/backend/tests/unit/settings/test_settings_roles.py +40 -0
  212. package/backend/tests/unit/test_state_sqlite_storage.py +67 -1
  213. package/backend/tests/unit/tools/__pycache__/test_connect_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  214. package/backend/tests/unit/tools/__pycache__/test_create_agent_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  215. package/backend/tests/unit/tools/__pycache__/test_delete_tab_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  216. package/backend/tests/unit/tools/__pycache__/test_edit_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  217. package/backend/tests/unit/tools/__pycache__/test_exec_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  218. package/backend/tests/unit/tools/__pycache__/test_fetch_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  219. package/backend/tests/unit/tools/__pycache__/test_manage_prompts_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  220. package/backend/tests/unit/tools/__pycache__/test_manage_providers_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  221. package/backend/tests/unit/tools/__pycache__/test_manage_roles_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  222. package/backend/tests/unit/tools/__pycache__/test_manage_settings_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  223. package/backend/tests/unit/tools/__pycache__/test_read_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  224. package/backend/tests/unit/tools/__pycache__/test_set_permissions_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  225. package/backend/tests/unit/tools/__pycache__/test_todo_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  226. package/backend/tests/unit/tools/__pycache__/test_tool_registry.cpython-313-pytest-9.0.3.pyc +0 -0
  227. package/backend/tests/unit/tools/test_connect_tool.py +2 -3
  228. package/backend/tests/unit/tools/test_create_agent_tool.py +9 -97
  229. package/backend/tests/unit/tools/test_delete_tab_tool.py +33 -0
  230. package/backend/tests/unit/tools/test_manage_providers_tool.py +2 -0
  231. package/backend/tests/unit/tools/test_manage_settings_tool.py +3 -0
  232. package/backend/tests/unit/tools/test_set_permissions_tool.py +216 -12
  233. package/backend/tests/unit/tools/test_tool_registry.py +103 -0
  234. package/backend/uv.lock +1 -1
  235. package/dist/frontend/assets/AssistantPage-VBohhz4d.js +1 -0
  236. package/dist/frontend/assets/ChannelsPage-CIydPZA_.js +1 -0
  237. package/dist/frontend/assets/McpPage-CHPm2TPY.js +7 -0
  238. package/dist/frontend/assets/PageScaffold-DteOA8V7.js +1 -0
  239. package/dist/frontend/assets/PromptsPage-CSmJ3sZg.js +1 -0
  240. package/dist/frontend/assets/ProvidersPage-sl2jeG4e.js +3 -0
  241. package/dist/frontend/assets/RolesPage-DCe7W6Km.js +1 -0
  242. package/dist/frontend/assets/SettingsPage-Bix9e63E.js +3 -0
  243. package/dist/frontend/assets/ToolsPage-favNkj5C.js +1 -0
  244. package/dist/frontend/assets/WorkspaceCommandDialog-DRS6wiD6.js +1 -0
  245. package/dist/frontend/assets/WorkspacePage-KuaDjt_D.js +3 -0
  246. package/dist/frontend/assets/WorkspacePanels-BZxBw8M5.js +1 -0
  247. package/dist/frontend/assets/alert-dialog-DIBUCmqM.js +1 -0
  248. package/dist/frontend/assets/{dialog-BeGSweF6.js → dialog-BOvHIBrg.js} +1 -1
  249. package/dist/frontend/assets/index-Biio-CoI.js +10 -0
  250. package/dist/frontend/assets/index-CmQvO7sl.css +1 -0
  251. package/dist/frontend/assets/modelParams-DcEhGnu0.js +1 -0
  252. package/dist/frontend/assets/roles-BbIEIMeG.js +1 -0
  253. package/dist/frontend/assets/select-D9SwnlXF.js +1 -0
  254. package/dist/frontend/assets/surface-Bzr1FRG4.js +1 -0
  255. package/dist/frontend/assets/{ui-vendor-Dg9NNnWX.js → ui-vendor-UazN8rcv.js} +15 -15
  256. package/dist/frontend/index.html +3 -4
  257. package/package.json +3 -3
  258. package/backend/src/flowent/routes/__pycache__/stats.cpython-313.pyc +0 -0
  259. package/backend/src/flowent/routes/stats.py +0 -229
  260. package/backend/src/flowent/static/assets/AssistantPage-B3Xc08AS.js +0 -1
  261. package/backend/src/flowent/static/assets/ChannelsPage-ByLd28xk.js +0 -1
  262. package/backend/src/flowent/static/assets/HomePage-C0hAx9_l.js +0 -3
  263. package/backend/src/flowent/static/assets/McpPage-DkrYLvBv.js +0 -7
  264. package/backend/src/flowent/static/assets/PageScaffold-D4jO9ooX.js +0 -1
  265. package/backend/src/flowent/static/assets/PromptsPage-DWA7rRJd.js +0 -1
  266. package/backend/src/flowent/static/assets/ProvidersPage-PUWT8seJ.js +0 -3
  267. package/backend/src/flowent/static/assets/RolesPage-CqcclGRw.js +0 -1
  268. package/backend/src/flowent/static/assets/SettingsPage-8tS2cJgX.js +0 -3
  269. package/backend/src/flowent/static/assets/StatsPage-BX9khYzu.js +0 -1
  270. package/backend/src/flowent/static/assets/ToolsPage-9Tl9FdeD.js +0 -1
  271. package/backend/src/flowent/static/assets/WorkspaceCommandDialog-CCXxjDL8.js +0 -1
  272. package/backend/src/flowent/static/assets/WorkspacePanels-aMdJ7ZH7.js +0 -1
  273. package/backend/src/flowent/static/assets/alert-dialog-kFYVQ7oX.js +0 -1
  274. package/backend/src/flowent/static/assets/badge-74-3jsCg.js +0 -1
  275. package/backend/src/flowent/static/assets/constants-XUzFf6i1.js +0 -1
  276. package/backend/src/flowent/static/assets/index-BHC1Vhy8.css +0 -1
  277. package/backend/src/flowent/static/assets/index-CL1ALZ3r.js +0 -10
  278. package/backend/src/flowent/static/assets/modelParams-CaHd0903.js +0 -1
  279. package/backend/src/flowent/static/assets/roles-2OLDeTc5.js +0 -1
  280. package/backend/src/flowent/static/assets/select-DL_LPeDj.js +0 -1
  281. package/backend/src/flowent/static/assets/shared-CMxbpLeQ.js +0 -1
  282. package/backend/tests/unit/routes/__pycache__/test_stats_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  283. package/backend/tests/unit/routes/test_stats_routes.py +0 -149
  284. package/dist/frontend/assets/AssistantPage-B3Xc08AS.js +0 -1
  285. package/dist/frontend/assets/ChannelsPage-ByLd28xk.js +0 -1
  286. package/dist/frontend/assets/HomePage-C0hAx9_l.js +0 -3
  287. package/dist/frontend/assets/McpPage-DkrYLvBv.js +0 -7
  288. package/dist/frontend/assets/PageScaffold-D4jO9ooX.js +0 -1
  289. package/dist/frontend/assets/PromptsPage-DWA7rRJd.js +0 -1
  290. package/dist/frontend/assets/ProvidersPage-PUWT8seJ.js +0 -3
  291. package/dist/frontend/assets/RolesPage-CqcclGRw.js +0 -1
  292. package/dist/frontend/assets/SettingsPage-8tS2cJgX.js +0 -3
  293. package/dist/frontend/assets/StatsPage-BX9khYzu.js +0 -1
  294. package/dist/frontend/assets/ToolsPage-9Tl9FdeD.js +0 -1
  295. package/dist/frontend/assets/WorkspaceCommandDialog-CCXxjDL8.js +0 -1
  296. package/dist/frontend/assets/WorkspacePanels-aMdJ7ZH7.js +0 -1
  297. package/dist/frontend/assets/alert-dialog-kFYVQ7oX.js +0 -1
  298. package/dist/frontend/assets/badge-74-3jsCg.js +0 -1
  299. package/dist/frontend/assets/constants-XUzFf6i1.js +0 -1
  300. package/dist/frontend/assets/index-BHC1Vhy8.css +0 -1
  301. package/dist/frontend/assets/index-CL1ALZ3r.js +0 -10
  302. package/dist/frontend/assets/modelParams-CaHd0903.js +0 -1
  303. package/dist/frontend/assets/roles-2OLDeTc5.js +0 -1
  304. package/dist/frontend/assets/select-DL_LPeDj.js +0 -1
  305. package/dist/frontend/assets/shared-CMxbpLeQ.js +0 -1
  306. /package/backend/src/flowent/static/assets/{datetime-m6_O_Ci9.js → datetime-eJqd0V2S.js} +0 -0
  307. /package/backend/src/flowent/static/assets/{markdown-vendor-DVdy_w12.js → markdown-vendor-C9RtvaJh.js} +0 -0
  308. /package/backend/src/flowent/static/assets/{triState-DEr3NkXV.js → triState-DgLlKdRR.js} +0 -0
  309. /package/dist/frontend/assets/{datetime-m6_O_Ci9.js → datetime-eJqd0V2S.js} +0 -0
  310. /package/dist/frontend/assets/{markdown-vendor-DVdy_w12.js → markdown-vendor-C9RtvaJh.js} +0 -0
  311. /package/dist/frontend/assets/{triState-DEr3NkXV.js → triState-DgLlKdRR.js} +0 -0
@@ -4,7 +4,7 @@ import pytest
4
4
 
5
5
  from flowent.agent import Agent
6
6
  from flowent.graph_service import create_tab
7
- from flowent.models import AgentState, GraphNodeRecord, NodeConfig, NodeType
7
+ from flowent.models import NodeConfig, NodeType
8
8
  from flowent.registry import registry
9
9
  from flowent.settings import CONDUCTOR_ROLE_NAME, RoleConfig, Settings
10
10
  from flowent.tools.create_agent import CreateAgentTool
@@ -309,14 +309,12 @@ def test_create_agent_rejects_reserved_conductor_role(monkeypatch):
309
309
  }
310
310
 
311
311
 
312
- def test_create_agent_respects_write_dir_and_network_boundaries(monkeypatch, tmp_path):
312
+ def test_create_agent_rejects_node_level_permissions(monkeypatch, tmp_path):
313
313
  monkeypatch.setattr(
314
314
  "flowent.settings.get_settings",
315
315
  lambda: Settings(roles=[RoleConfig(name="Worker", system_prompt="Do work.")]),
316
316
  )
317
317
 
318
- allowed_dir = tmp_path / "allowed"
319
- allowed_dir.mkdir()
320
318
  disallowed_dir = tmp_path / "disallowed"
321
319
  disallowed_dir.mkdir()
322
320
  tab = create_tab(title="Task")
@@ -327,8 +325,6 @@ def test_create_agent_respects_write_dir_and_network_boundaries(monkeypatch, tmp
327
325
  role_name="Conductor",
328
326
  tab_id=tab.id,
329
327
  tools=["create_agent"],
330
- write_dirs=[str(allowed_dir)],
331
- allow_network=False,
332
328
  ),
333
329
  uuid=tab.leader_id,
334
330
  )
@@ -353,90 +349,16 @@ def test_create_agent_respects_write_dir_and_network_boundaries(monkeypatch, tmp
353
349
  )
354
350
 
355
351
  assert write_dir_result == {
356
- "error": f"write_dirs boundary exceeded: {disallowed_dir}"
357
- }
358
- assert network_result == {
359
- "error": "allow_network boundary exceeded: parent disallows network access"
360
- }
361
-
362
-
363
- def test_create_agent_also_respects_tab_leader_boundaries(monkeypatch, tmp_path):
364
- monkeypatch.setattr(
365
- "flowent.settings.get_settings",
366
- lambda: Settings(roles=[RoleConfig(name="Worker", system_prompt="Do work.")]),
367
- )
368
-
369
- leader_dir = tmp_path / "leader"
370
- leader_dir.mkdir()
371
- creator_dir = tmp_path / "creator"
372
- creator_dir.mkdir()
373
- creator_child = creator_dir / "child"
374
- creator_child.mkdir()
375
- tab = create_tab(title="Task")
376
-
377
- leader = Agent(
378
- NodeConfig(
379
- node_type=NodeType.AGENT,
380
- role_name="Conductor",
381
- tab_id=tab.id,
382
- tools=[],
383
- write_dirs=[str(leader_dir)],
384
- allow_network=False,
385
- ),
386
- uuid=tab.leader_id,
387
- )
388
- creator = Agent(
389
- NodeConfig(
390
- node_type=NodeType.AGENT,
391
- role_name="Worker",
392
- tab_id=tab.id,
393
- tools=["create_agent"],
394
- write_dirs=[str(creator_dir)],
395
- allow_network=True,
396
- ),
397
- uuid="worker",
398
- )
399
- registry.register(leader)
400
- registry.register(creator)
401
- workspace_store.upsert_node_record(
402
- GraphNodeRecord(
403
- id=leader.uuid,
404
- config=leader.config,
405
- state=AgentState.INITIALIZING,
406
- )
407
- )
408
- workspace_store.upsert_node_record(
409
- GraphNodeRecord(
410
- id=creator.uuid,
411
- config=creator.config,
412
- state=AgentState.INITIALIZING,
352
+ "error": (
353
+ "create_agent uses the current workflow permissions; update the "
354
+ "workflow permissions instead"
413
355
  )
414
- )
415
-
416
- write_dir_result = json.loads(
417
- CreateAgentTool().execute(
418
- creator,
419
- {
420
- "role_name": "Worker",
421
- "write_dirs": [str(creator_child)],
422
- },
423
- )
424
- )
425
- network_result = json.loads(
426
- CreateAgentTool().execute(
427
- creator,
428
- {
429
- "role_name": "Worker",
430
- "allow_network": True,
431
- },
432
- )
433
- )
434
-
435
- assert write_dir_result == {
436
- "error": f"write_dirs boundary exceeded: {creator_child}"
437
356
  }
438
357
  assert network_result == {
439
- "error": "allow_network boundary exceeded: workflow Leader disallows network access"
358
+ "error": (
359
+ "create_agent uses the current workflow permissions; update the "
360
+ "workflow permissions instead"
361
+ )
440
362
  }
441
363
 
442
364
 
@@ -489,16 +411,6 @@ def test_create_agent_tool_schema_exposes_workflow_placement_options():
489
411
  "items": {"type": "string"},
490
412
  "description": "Optional additional tools",
491
413
  },
492
- "write_dirs": {
493
- "type": "array",
494
- "items": {"type": "string"},
495
- "description": "Optional writable directories",
496
- },
497
- "allow_network": {
498
- "type": "boolean",
499
- "description": "Whether the node can access the network",
500
- "default": False,
501
- },
502
414
  "placement": {
503
415
  "type": "string",
504
416
  "enum": ["standalone", "after", "between"],
@@ -7,7 +7,9 @@ from flowent.graph_service import create_agent_node, create_edge, create_tab
7
7
  from flowent.models import NodeConfig, NodeType
8
8
  from flowent.registry import registry
9
9
  from flowent.settings import RoleConfig, Settings
10
+ from flowent.tools.create_tab import CreateTabTool
10
11
  from flowent.tools.delete_tab import DeleteTabTool
12
+ from flowent.tools.list_tabs import ListTabsTool
11
13
  from flowent.workspace_store import workspace_store
12
14
 
13
15
 
@@ -81,3 +83,34 @@ def test_delete_tab_tool_rejects_non_assistant():
81
83
  result = json.loads(DeleteTabTool().execute(agent, {"workflow_id": "tab-1"}))
82
84
 
83
85
  assert result == {"error": "Only the Assistant may delete workflows"}
86
+
87
+
88
+ def test_create_tab_tool_rejects_non_assistant():
89
+ agent = Agent(
90
+ NodeConfig(
91
+ node_type=NodeType.AGENT,
92
+ role_name="Worker",
93
+ tools=["create_workflow"],
94
+ ),
95
+ uuid="worker",
96
+ )
97
+
98
+ result = json.loads(CreateTabTool().execute(agent, {"title": "Draft"}))
99
+
100
+ assert result == {"error": "Only the Assistant may create workflows"}
101
+ assert workspace_store.list_tabs() == []
102
+
103
+
104
+ def test_list_tabs_tool_rejects_non_assistant():
105
+ agent = Agent(
106
+ NodeConfig(
107
+ node_type=NodeType.AGENT,
108
+ role_name="Worker",
109
+ tools=["list_workflows"],
110
+ ),
111
+ uuid="worker",
112
+ )
113
+
114
+ result = json.loads(ListTabsTool().execute(agent, {}))
115
+
116
+ assert result == {"error": "Only the Assistant may list workflows"}
@@ -444,6 +444,7 @@ def test_manage_providers_list_models_streams_model_ids(monkeypatch):
444
444
  "context_window_tokens": None,
445
445
  "input_image": False,
446
446
  "output_image": False,
447
+ "structured_output": False,
447
448
  },
448
449
  {
449
450
  "model": "provider-1-b",
@@ -451,6 +452,7 @@ def test_manage_providers_list_models_streams_model_ids(monkeypatch):
451
452
  "context_window_tokens": None,
452
453
  "input_image": False,
453
454
  "output_image": False,
455
+ "structured_output": False,
454
456
  },
455
457
  ]
456
458
  assert "".join(chunks) == (
@@ -48,6 +48,7 @@ def test_manage_settings_get_returns_current_settings(monkeypatch):
48
48
  "active_model": "gpt-4o",
49
49
  "input_image": None,
50
50
  "output_image": None,
51
+ "structured_output": None,
51
52
  "context_window_tokens": None,
52
53
  "capabilities": None,
53
54
  "resolved_context_window_tokens": None,
@@ -103,6 +104,7 @@ def test_manage_settings_update_changes_active_provider_and_model(monkeypatch):
103
104
  "active_model": "gpt-4.1",
104
105
  "input_image": None,
105
106
  "output_image": None,
107
+ "structured_output": None,
106
108
  "context_window_tokens": None,
107
109
  "capabilities": None,
108
110
  "resolved_context_window_tokens": None,
@@ -448,6 +450,7 @@ def test_manage_settings_update_changes_model_metadata_overrides_and_token_limit
448
450
  assert result["model"]["capabilities"] == {
449
451
  "input_image": True,
450
452
  "output_image": False,
453
+ "structured_output": False,
451
454
  }
452
455
  assert result["model"]["auto_compact_token_limit"] == 48000
453
456
  assert settings.model.context_window_tokens == 64000
@@ -5,6 +5,7 @@ import pytest
5
5
  from flowent.agent import Agent
6
6
  from flowent.models import AgentState, GraphNodeRecord, NodeConfig, NodeType, Tab
7
7
  from flowent.registry import registry
8
+ from flowent.settings import AssistantSettings, Settings
8
9
  from flowent.tools.set_permissions import SetPermissionsTool
9
10
  from flowent.workspace_store import workspace_store
10
11
 
@@ -67,15 +68,40 @@ def _register_live_node(record: GraphNodeRecord) -> Agent:
67
68
  return node
68
69
 
69
70
 
70
- def test_set_permissions_updates_leader_and_clamps_existing_workers(tmp_path):
71
+ def _grant_assistant_permissions(
72
+ monkeypatch,
73
+ *,
74
+ write_dirs: list[str],
75
+ allow_network: bool,
76
+ ) -> None:
77
+ monkeypatch.setattr(
78
+ "flowent.settings.get_settings",
79
+ lambda: Settings(
80
+ assistant=AssistantSettings(
81
+ write_dirs=list(write_dirs),
82
+ allow_network=allow_network,
83
+ )
84
+ ),
85
+ )
86
+
87
+
88
+ def test_set_permissions_updates_workflow_boundary_for_all_nodes(monkeypatch, tmp_path):
71
89
  root_dir = tmp_path / "root"
72
90
  keep_boundary = root_dir / "keep"
73
91
  keep_dir = keep_boundary / "child"
74
92
  drop_dir = root_dir / "drop"
75
93
  keep_dir.mkdir(parents=True)
76
94
  drop_dir.mkdir(parents=True)
95
+ _grant_assistant_permissions(
96
+ monkeypatch,
97
+ write_dirs=[str(root_dir)],
98
+ allow_network=True,
99
+ )
77
100
 
78
101
  tab = Tab(id="tab-1", title="Task", leader_id="leader-1")
102
+ tab.allow_network = True
103
+ tab.write_dirs = [str(root_dir)]
104
+ tab.permissions_initialized = True
79
105
  workspace_store.upsert_tab(tab)
80
106
  leader = _make_record(
81
107
  node_id="leader-1",
@@ -135,21 +161,33 @@ def test_set_permissions_updates_leader_and_clamps_existing_workers(tmp_path):
135
161
  "write_dirs": [str(keep_boundary)],
136
162
  "updated_node_ids": ["leader-1", "worker-keep", "worker-drop"],
137
163
  }
138
- assert leader_live.config.allow_network is False
139
- assert leader_live.config.write_dirs == [str(keep_boundary)]
140
- assert keep_live.config.allow_network is False
164
+ updated_tab = workspace_store.get_tab(tab.id)
165
+ assert updated_tab is not None
166
+ assert updated_tab.allow_network is False
167
+ assert updated_tab.write_dirs == [str(keep_boundary)]
168
+ assert leader_live.config.allow_network is True
169
+ assert leader_live.config.write_dirs == [str(root_dir)]
170
+ assert keep_live.config.allow_network is True
141
171
  assert keep_live.config.write_dirs == [str(keep_dir)]
142
- assert drop_live.config.allow_network is False
143
- assert drop_live.config.write_dirs == []
172
+ assert drop_live.config.allow_network is True
173
+ assert drop_live.config.write_dirs == [str(drop_dir)]
144
174
 
145
175
 
146
- def test_set_permissions_keeps_omitted_fields_unchanged(tmp_path):
176
+ def test_set_permissions_keeps_omitted_fields_unchanged(monkeypatch, tmp_path):
147
177
  root_dir = tmp_path / "root"
148
178
  narrowed_dir = root_dir / "narrowed"
149
179
  root_dir.mkdir()
150
180
  narrowed_dir.mkdir()
181
+ _grant_assistant_permissions(
182
+ monkeypatch,
183
+ write_dirs=[str(root_dir)],
184
+ allow_network=False,
185
+ )
151
186
 
152
187
  tab = Tab(id="tab-1", title="Task", leader_id="leader-1")
188
+ tab.allow_network = True
189
+ tab.write_dirs = [str(root_dir)]
190
+ tab.permissions_initialized = True
153
191
  workspace_store.upsert_tab(tab)
154
192
  leader = _make_record(
155
193
  node_id="leader-1",
@@ -183,17 +221,32 @@ def test_set_permissions_keeps_omitted_fields_unchanged(tmp_path):
183
221
 
184
222
  assert result["allow_network"] is True
185
223
  assert result["write_dirs"] == [str(narrowed_dir)]
224
+ updated_tab = workspace_store.get_tab(tab.id)
225
+ assert updated_tab is not None
226
+ assert updated_tab.allow_network is True
227
+ assert updated_tab.write_dirs == [str(narrowed_dir)]
186
228
  assert leader_live.config.allow_network is True
187
- assert leader_live.config.write_dirs == [str(narrowed_dir)]
229
+ assert leader_live.config.write_dirs == [str(root_dir)]
188
230
 
189
231
 
190
- def test_set_permissions_does_not_auto_broaden_existing_workers(tmp_path):
232
+ def test_set_permissions_broadens_workflow_boundary_without_mutating_workers(
233
+ monkeypatch,
234
+ tmp_path,
235
+ ):
191
236
  root_dir = tmp_path / "root"
192
237
  current_boundary = root_dir / "current"
193
238
  worker_dir = current_boundary / "child"
194
239
  worker_dir.mkdir(parents=True)
240
+ _grant_assistant_permissions(
241
+ monkeypatch,
242
+ write_dirs=[str(root_dir)],
243
+ allow_network=True,
244
+ )
195
245
 
196
246
  tab = Tab(id="tab-1", title="Task", leader_id="leader-1")
247
+ tab.allow_network = False
248
+ tab.write_dirs = [str(current_boundary)]
249
+ tab.permissions_initialized = True
197
250
  workspace_store.upsert_tab(tab)
198
251
  leader = _make_record(
199
252
  node_id="leader-1",
@@ -237,15 +290,30 @@ def test_set_permissions_does_not_auto_broaden_existing_workers(tmp_path):
237
290
 
238
291
  assert result["allow_network"] is True
239
292
  assert result["write_dirs"] == [str(root_dir)]
293
+ updated_tab = workspace_store.get_tab(tab.id)
294
+ assert updated_tab is not None
295
+ assert updated_tab.allow_network is True
296
+ assert updated_tab.write_dirs == [str(root_dir)]
240
297
  assert worker_live.config.allow_network is False
241
298
  assert worker_live.config.write_dirs == [str(worker_dir)]
242
299
 
243
300
 
244
- def test_set_permissions_rejects_allow_network_outside_caller_boundary(tmp_path):
301
+ def test_set_permissions_rejects_allow_network_outside_caller_boundary(
302
+ monkeypatch,
303
+ tmp_path,
304
+ ):
245
305
  root_dir = tmp_path / "root"
246
306
  root_dir.mkdir()
307
+ _grant_assistant_permissions(
308
+ monkeypatch,
309
+ write_dirs=[str(root_dir)],
310
+ allow_network=False,
311
+ )
247
312
 
248
313
  tab = Tab(id="tab-1", title="Task", leader_id="leader-1")
314
+ tab.allow_network = False
315
+ tab.write_dirs = [str(root_dir)]
316
+ tab.permissions_initialized = True
249
317
  workspace_store.upsert_tab(tab)
250
318
  workspace_store.upsert_node_record(
251
319
  _make_record(
@@ -282,13 +350,24 @@ def test_set_permissions_rejects_allow_network_outside_caller_boundary(tmp_path)
282
350
  }
283
351
 
284
352
 
285
- def test_set_permissions_rejects_write_dirs_outside_caller_boundary(tmp_path):
353
+ def test_set_permissions_rejects_write_dirs_outside_caller_boundary(
354
+ monkeypatch,
355
+ tmp_path,
356
+ ):
286
357
  caller_dir = tmp_path / "caller"
287
358
  other_dir = tmp_path / "other"
288
359
  caller_dir.mkdir()
289
360
  other_dir.mkdir()
361
+ _grant_assistant_permissions(
362
+ monkeypatch,
363
+ write_dirs=[str(caller_dir)],
364
+ allow_network=True,
365
+ )
290
366
 
291
367
  tab = Tab(id="tab-1", title="Task", leader_id="leader-1")
368
+ tab.allow_network = False
369
+ tab.write_dirs = [str(caller_dir)]
370
+ tab.permissions_initialized = True
292
371
  workspace_store.upsert_tab(tab)
293
372
  workspace_store.upsert_node_record(
294
373
  _make_record(
@@ -323,13 +402,18 @@ def test_set_permissions_rejects_write_dirs_outside_caller_boundary(tmp_path):
323
402
  assert result == {"error": f"write_dirs boundary exceeded: {other_dir}"}
324
403
 
325
404
 
326
- def test_set_permissions_allows_explicitly_granted_non_assistant_agent(tmp_path):
405
+ def test_set_permissions_allows_explicitly_granted_non_assistant_agent_for_own_workflow(
406
+ tmp_path,
407
+ ):
327
408
  root_dir = tmp_path / "root"
328
409
  narrowed_dir = root_dir / "narrowed"
329
410
  root_dir.mkdir()
330
411
  narrowed_dir.mkdir()
331
412
 
332
413
  tab = Tab(id="tab-1", title="Task", leader_id="leader-1")
414
+ tab.allow_network = True
415
+ tab.write_dirs = [str(root_dir)]
416
+ tab.permissions_initialized = True
333
417
  workspace_store.upsert_tab(tab)
334
418
  workspace_store.upsert_node_record(
335
419
  _make_record(
@@ -367,6 +451,126 @@ def test_set_permissions_allows_explicitly_granted_non_assistant_agent(tmp_path)
367
451
 
368
452
  assert result["tab_id"] == tab.id
369
453
  assert result["write_dirs"] == [str(narrowed_dir)]
454
+ updated_tab = workspace_store.get_tab(tab.id)
455
+ assert updated_tab is not None
456
+ assert updated_tab.write_dirs == [str(narrowed_dir)]
457
+
458
+
459
+ def test_set_permissions_rejects_non_assistant_cross_workflow(tmp_path):
460
+ root_dir = tmp_path / "root"
461
+ root_dir.mkdir()
462
+
463
+ own_tab = Tab(id="tab-1", title="Own", leader_id="leader-1")
464
+ own_tab.allow_network = True
465
+ own_tab.write_dirs = [str(root_dir)]
466
+ own_tab.permissions_initialized = True
467
+ other_tab = Tab(id="tab-2", title="Other", leader_id="leader-2")
468
+ other_tab.allow_network = True
469
+ other_tab.write_dirs = [str(root_dir)]
470
+ other_tab.permissions_initialized = True
471
+ workspace_store.upsert_tab(own_tab)
472
+ workspace_store.upsert_tab(other_tab)
473
+ workspace_store.upsert_node_record(
474
+ _make_record(
475
+ node_id="leader-1",
476
+ tab_id=own_tab.id,
477
+ role_name="Conductor",
478
+ name="Leader",
479
+ write_dirs=[str(root_dir)],
480
+ allow_network=True,
481
+ tools=["set_permissions"],
482
+ )
483
+ )
484
+ workspace_store.upsert_node_record(
485
+ _make_record(
486
+ node_id="leader-2",
487
+ tab_id=other_tab.id,
488
+ role_name="Conductor",
489
+ name="Leader",
490
+ write_dirs=[str(root_dir)],
491
+ allow_network=True,
492
+ )
493
+ )
494
+ leader = Agent(
495
+ NodeConfig(
496
+ node_type=NodeType.AGENT,
497
+ role_name="Conductor",
498
+ tab_id=own_tab.id,
499
+ name="Leader",
500
+ tools=["set_permissions"],
501
+ write_dirs=[str(root_dir)],
502
+ allow_network=True,
503
+ ),
504
+ uuid="leader-1",
505
+ )
506
+
507
+ result = json.loads(
508
+ SetPermissionsTool().execute(
509
+ leader,
510
+ {
511
+ "workflow_id": other_tab.id,
512
+ "write_dirs": [],
513
+ },
514
+ )
515
+ )
516
+
517
+ assert result == {
518
+ "error": "Workflow permissions can only be changed for this workflow"
519
+ }
520
+ updated_other_tab = workspace_store.get_tab(other_tab.id)
521
+ assert updated_other_tab is not None
522
+ assert updated_other_tab.write_dirs == [str(root_dir)]
523
+
524
+
525
+ def test_set_permissions_migrates_legacy_leader_permissions(tmp_path):
526
+ root_dir = tmp_path / "root"
527
+ narrowed_dir = root_dir / "narrowed"
528
+ root_dir.mkdir()
529
+ narrowed_dir.mkdir()
530
+
531
+ tab = Tab(id="tab-1", title="Task", leader_id="leader-1")
532
+ workspace_store.upsert_tab(tab)
533
+ workspace_store.upsert_node_record(
534
+ _make_record(
535
+ node_id="leader-1",
536
+ tab_id=tab.id,
537
+ role_name="Conductor",
538
+ name="Leader",
539
+ write_dirs=[str(root_dir)],
540
+ allow_network=True,
541
+ tools=["set_permissions"],
542
+ )
543
+ )
544
+ leader = Agent(
545
+ NodeConfig(
546
+ node_type=NodeType.AGENT,
547
+ role_name="Conductor",
548
+ tab_id=tab.id,
549
+ name="Leader",
550
+ tools=["set_permissions"],
551
+ write_dirs=[str(root_dir)],
552
+ allow_network=True,
553
+ ),
554
+ uuid="leader-1",
555
+ )
556
+
557
+ result = json.loads(
558
+ SetPermissionsTool().execute(
559
+ leader,
560
+ {
561
+ "workflow_id": tab.id,
562
+ "write_dirs": [str(narrowed_dir)],
563
+ },
564
+ )
565
+ )
566
+
567
+ updated_tab = workspace_store.get_tab(tab.id)
568
+ assert result["allow_network"] is True
569
+ assert result["write_dirs"] == [str(narrowed_dir)]
570
+ assert updated_tab is not None
571
+ assert updated_tab.permissions_initialized is True
572
+ assert updated_tab.allow_network is True
573
+ assert updated_tab.write_dirs == [str(narrowed_dir)]
370
574
 
371
575
 
372
576
  def test_set_permissions_tool_schema_matches_patch_contract():
@@ -3,6 +3,17 @@ from flowent.models import NodeConfig, NodeType
3
3
  from flowent.tools import build_tool_registry
4
4
 
5
5
 
6
+ class _FakeMCPService:
7
+ def __init__(self, descriptors):
8
+ self._descriptors = descriptors
9
+
10
+ def list_agent_dynamic_tools(self, agent):
11
+ return list(self._descriptors)
12
+
13
+ def list_discovered_tools(self):
14
+ return list(self._descriptors)
15
+
16
+
6
17
  def test_empty_tools_list_grants_minimum_tools():
7
18
  agent = Agent(NodeConfig(node_type=NodeType.AGENT, tools=[]))
8
19
 
@@ -78,6 +89,98 @@ def test_tool_registry_grants_workflow_graph_tools_when_explicitly_allowed():
78
89
  ]
79
90
 
80
91
 
92
+ def test_tool_registry_filters_assistant_only_tools_for_workflow_nodes():
93
+ agent = Agent(
94
+ NodeConfig(
95
+ node_type=NodeType.AGENT,
96
+ role_name="Conductor",
97
+ tools=[
98
+ "create_workflow",
99
+ "delete_workflow",
100
+ "set_permissions",
101
+ "create_agent",
102
+ "connect",
103
+ "list_workflows",
104
+ "manage_settings",
105
+ ],
106
+ )
107
+ )
108
+
109
+ tools = build_tool_registry().get_tools_for_agent(agent)
110
+
111
+ assert [tool.name for tool in tools] == [
112
+ "idle",
113
+ "sleep",
114
+ "todo",
115
+ "contacts",
116
+ "send",
117
+ "set_permissions",
118
+ "create_agent",
119
+ "connect",
120
+ ]
121
+
122
+
123
+ def test_tool_registry_filters_assistant_only_mcp_tools_for_workflow_nodes(
124
+ monkeypatch,
125
+ ):
126
+ from flowent.mcp_service import MCPToolDescriptor
127
+
128
+ workflow_tool = MCPToolDescriptor(
129
+ server_name="flowent",
130
+ tool_name="list_workflows",
131
+ fully_qualified_id="mcp__flowent__list_workflows",
132
+ )
133
+ regular_tool = MCPToolDescriptor(
134
+ server_name="flowent",
135
+ tool_name="search_notes",
136
+ fully_qualified_id="mcp__flowent__search_notes",
137
+ )
138
+ fake_service = _FakeMCPService([workflow_tool, regular_tool])
139
+ monkeypatch.setattr("flowent.mcp_service.mcp_service", fake_service)
140
+ agent = Agent(
141
+ NodeConfig(
142
+ node_type=NodeType.AGENT,
143
+ role_name="Worker",
144
+ tools=[
145
+ "mcp__flowent__list_workflows",
146
+ "mcp__flowent__search_notes",
147
+ ],
148
+ )
149
+ )
150
+
151
+ tools = build_tool_registry().get_tools_for_agent(agent)
152
+
153
+ assert "mcp__flowent__list_workflows" not in {tool.name for tool in tools}
154
+ assert "mcp__flowent__search_notes" in {tool.name for tool in tools}
155
+
156
+
157
+ def test_build_tools_for_role_filters_assistant_only_mcp_tools(monkeypatch):
158
+ from flowent.graph_service import build_tools_for_role
159
+ from flowent.settings import RoleConfig, Settings
160
+
161
+ monkeypatch.setattr(
162
+ "flowent.settings.get_settings",
163
+ lambda: Settings(
164
+ roles=[
165
+ RoleConfig(
166
+ name="Worker",
167
+ system_prompt="Do work.",
168
+ included_tools=["mcp__flowent__list_workflows", "read"],
169
+ )
170
+ ]
171
+ ),
172
+ )
173
+
174
+ tools = build_tools_for_role(
175
+ "Worker",
176
+ requested_tools=["mcp__flowent__delete_workflow", "mcp__flowent__search_notes"],
177
+ )
178
+
179
+ assert "mcp__flowent__list_workflows" not in tools
180
+ assert "mcp__flowent__delete_workflow" not in tools
181
+ assert "mcp__flowent__search_notes" in tools
182
+
183
+
81
184
  def test_tool_registry_shows_management_tools_in_agent_visible_list():
82
185
  visible_tool_names = {
83
186
  tool.name for tool in build_tool_registry().list_tools(agent_visible_only=True)
package/backend/uv.lock CHANGED
@@ -286,7 +286,7 @@ wheels = [
286
286
 
287
287
  [[package]]
288
288
  name = "flowent"
289
- version = "0.0.4"
289
+ version = "0.0.5"
290
290
  source = { editable = "." }
291
291
  dependencies = [
292
292
  { name = "curl-cffi" },