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
@@ -71,6 +71,10 @@ class ManageSettingsTool(Tool):
71
71
  "type": ["boolean", "null"],
72
72
  "description": "Explicit output_image override for the active system model",
73
73
  },
74
+ "structured_output": {
75
+ "type": ["boolean", "null"],
76
+ "description": "Explicit structured_output override for the active system model",
77
+ },
74
78
  "max_retries": {
75
79
  "type": "integer",
76
80
  "description": "Maximum retries for transient LLM call failures when retry_policy is limited",
@@ -139,6 +143,7 @@ class ManageSettingsTool(Tool):
139
143
  build_model_retry_initial_delay_seconds,
140
144
  build_model_retry_max_delay_seconds,
141
145
  build_model_retry_policy,
146
+ build_model_structured_output,
142
147
  build_model_timeout_ms,
143
148
  get_settings,
144
149
  save_settings,
@@ -161,6 +166,7 @@ class ManageSettingsTool(Tool):
161
166
  context_window_tokens = args.get("context_window_tokens")
162
167
  input_image = args.get("input_image")
163
168
  output_image = args.get("output_image")
169
+ structured_output = args.get("structured_output")
164
170
  auto_compact_token_limit = args.get("auto_compact_token_limit")
165
171
  model_params = args.get("model_params")
166
172
  timestamp_format = args.get("timestamp_format")
@@ -237,6 +243,14 @@ class ManageSettingsTool(Tool):
237
243
  build_model_output_image(output_image, field_name="output_image")
238
244
  except ValueError as exc:
239
245
  return json.dumps({"error": str(exc)})
246
+ if "structured_output" in args:
247
+ try:
248
+ build_model_structured_output(
249
+ structured_output,
250
+ field_name="structured_output",
251
+ )
252
+ except ValueError as exc:
253
+ return json.dumps({"error": str(exc)})
240
254
  if "context_window_tokens" in args:
241
255
  try:
242
256
  build_model_context_window_tokens(
@@ -293,6 +307,9 @@ class ManageSettingsTool(Tool):
293
307
  ),
294
308
  input_image=input_image if "input_image" in args else MISSING,
295
309
  output_image=output_image if "output_image" in args else MISSING,
310
+ structured_output=(
311
+ structured_output if "structured_output" in args else MISSING
312
+ ),
296
313
  max_retries=max_retries if max_retries is not None else MISSING,
297
314
  retry_policy=retry_policy if retry_policy is not None else MISSING,
298
315
  timeout_ms=timeout_ms if timeout_ms is not None else MISSING,
@@ -331,6 +348,7 @@ class ManageSettingsTool(Tool):
331
348
  retry_backoff_cap_retries_field_name="retry_backoff_cap_retries",
332
349
  input_image_field_name="input_image",
333
350
  output_image_field_name="output_image",
351
+ structured_output_field_name="structured_output",
334
352
  context_window_tokens_field_name="context_window_tokens",
335
353
  auto_compact_token_limit_field_name="auto_compact_token_limit",
336
354
  )
@@ -10,7 +10,7 @@ if TYPE_CHECKING:
10
10
 
11
11
  class SendTool(Tool):
12
12
  name = "send"
13
- description = "Send a formal message to one current contact."
13
+ description = "Send to one current contact or output-port path."
14
14
  parameters: ClassVar[dict[str, Any]] = {
15
15
  "type": "object",
16
16
  "properties": {
@@ -18,9 +18,20 @@ class SendTool(Tool):
18
18
  "type": "string",
19
19
  "description": "One target id, name, or unique short id from contacts.",
20
20
  },
21
+ "from_output_port_key": {
22
+ "type": "string",
23
+ "description": "Source output port key for workflow path sends.",
24
+ },
25
+ "to_input_port_key": {
26
+ "type": "string",
27
+ "description": "Target input port key for workflow path sends.",
28
+ },
29
+ "value": {
30
+ "description": "Typed value for workflow path sends. Use ordered parts for parts ports, a string for string ports, or an object for json ports.",
31
+ },
21
32
  "parts": {
22
33
  "type": "array",
23
- "description": "Ordered message parts to send.",
34
+ "description": "Ordered message parts for Assistant and Leader entry contacts.",
24
35
  "items": {
25
36
  "type": "object",
26
37
  "properties": {
@@ -36,7 +47,7 @@ class SendTool(Tool):
36
47
  },
37
48
  },
38
49
  },
39
- "required": ["target", "parts"],
50
+ "required": ["target"],
40
51
  "additionalProperties": False,
41
52
  }
42
53
 
@@ -44,6 +55,13 @@ class SendTool(Tool):
44
55
  target = args.get("target")
45
56
  if not isinstance(target, str) or not target.strip():
46
57
  raise ValueError("send.target must be a non-empty string")
58
+ if not agent._is_entry_level_sender():
59
+ return agent.send_port_value(
60
+ target_ref=target.strip(),
61
+ from_output_port_key=args.get("from_output_port_key"),
62
+ to_input_port_key=args.get("to_input_port_key"),
63
+ raw_value=args.get("value", args.get("parts")),
64
+ )
47
65
  return agent.send_message(
48
66
  target_ref=target.strip(),
49
67
  raw_parts=args.get("parts"),
@@ -3,6 +3,7 @@ from __future__ import annotations
3
3
  import json
4
4
  from typing import TYPE_CHECKING, Any, ClassVar
5
5
 
6
+ from flowent.models import NodeType
6
7
  from flowent.tools import Tool
7
8
 
8
9
  if TYPE_CHECKING:
@@ -12,8 +13,8 @@ if TYPE_CHECKING:
12
13
  class SetPermissionsTool(Tool):
13
14
  name = "set_permissions"
14
15
  description = (
15
- "Update a workflow's permission boundary by patching its bound Leader's "
16
- "allow_network and write_dirs."
16
+ "Update a workflow's permission boundary by patching its allow_network "
17
+ "and write_dirs."
17
18
  )
18
19
  parameters: ClassVar[dict[str, Any]] = {
19
20
  "type": "object",
@@ -36,7 +37,10 @@ class SetPermissionsTool(Tool):
36
37
  }
37
38
 
38
39
  def execute(self, agent: Agent, args: dict[str, Any], **_kwargs: Any) -> str:
39
- from flowent.graph_service import set_tab_permissions
40
+ from flowent.graph_service import (
41
+ resolve_effective_permissions_for_agent,
42
+ set_tab_permissions,
43
+ )
40
44
  from flowent.settings import (
41
45
  build_assistant_allow_network,
42
46
  build_assistant_write_dirs,
@@ -48,6 +52,14 @@ class SetPermissionsTool(Tool):
48
52
 
49
53
  if not isinstance(workflow_id, str) or not workflow_id.strip():
50
54
  return json.dumps({"error": "workflow_id must be a non-empty string"})
55
+ normalized_workflow_id = workflow_id.strip()
56
+ if (
57
+ agent.node_type != NodeType.ASSISTANT
58
+ and agent.config.tab_id != normalized_workflow_id
59
+ ):
60
+ return json.dumps(
61
+ {"error": "Workflow permissions can only be changed for this workflow"}
62
+ )
51
63
 
52
64
  allow_network: bool | None = None
53
65
  if "allow_network" in args:
@@ -69,12 +81,15 @@ class SetPermissionsTool(Tool):
69
81
  except ValueError as exc:
70
82
  return json.dumps({"error": str(exc)})
71
83
 
84
+ caller_allow_network, caller_write_dirs = (
85
+ resolve_effective_permissions_for_agent(agent)
86
+ )
72
87
  result, error = set_tab_permissions(
73
- tab_id=workflow_id.strip(),
88
+ tab_id=normalized_workflow_id,
74
89
  allow_network=allow_network,
75
90
  write_dirs=write_dirs,
76
- caller_allow_network=agent.config.allow_network,
77
- caller_write_dirs=list(agent.config.write_dirs),
91
+ caller_allow_network=caller_allow_network,
92
+ caller_write_dirs=caller_write_dirs,
78
93
  actor_id=agent.uuid,
79
94
  )
80
95
  if error is not None or result is None:
@@ -129,7 +129,7 @@ def test_compact_command_replaces_history_with_summary(monkeypatch, client):
129
129
  entry["type"] == "CommandResultEntry"
130
130
  and entry["command_name"] == "/compact"
131
131
  and entry.get("include_in_context") is False
132
- and "Compacted the current Assistant execution context." in entry["content"]
132
+ and "Compacted this chat for future replies." in entry["content"]
133
133
  and "Focus: slash command rollout" in entry["content"]
134
134
  and "Ship the slash commands." not in entry["content"]
135
135
  for entry in detail["history"]
@@ -8,6 +8,7 @@ from flowent.models import (
8
8
  AgentState,
9
9
  AssistantText,
10
10
  ImagePart,
11
+ LLMResponse,
11
12
  ReceivedMessage,
12
13
  TextPart,
13
14
  )
@@ -66,40 +67,72 @@ def test_get_node_detail_includes_runtime_config(client: TestClient):
66
67
  assert isinstance(data["tools"], list)
67
68
  assert isinstance(data["write_dirs"], list)
68
69
  assert isinstance(data["allow_network"], bool)
70
+ assert data["workflow_permissions"] == {
71
+ "allow_network": data["allow_network"],
72
+ "write_dirs": data["write_dirs"],
73
+ }
69
74
 
70
75
 
71
- def test_worker_and_leader_are_stable_contacts_without_explicit_edge(
76
+ def test_worker_contacts_follow_output_paths(
72
77
  client: TestClient,
73
78
  ):
74
79
  tab = client.post(
75
80
  "/api/workflows",
76
- json={"title": "Execution"},
81
+ json={"title": "Execution", "allow_network": True, "write_dirs": ["/tmp"]},
77
82
  ).json()
78
83
  worker = client.post(
79
84
  f"/api/workflows/{tab['id']}/nodes",
80
85
  json={"role_name": "Worker", "name": "Worker"},
81
86
  ).json()
87
+ reviewer = client.post(
88
+ f"/api/workflows/{tab['id']}/nodes",
89
+ json={"role_name": "Worker", "name": "Reviewer"},
90
+ ).json()
82
91
 
83
92
  detail_without_edge = client.get(f"/api/nodes/{worker['id']}")
84
93
 
85
94
  assert detail_without_edge.status_code == 200
86
- assert detail_without_edge.json()["contacts"] == [tab["leader_id"]]
95
+ worker_detail = detail_without_edge.json()
96
+ assert worker_detail["contacts"] == []
97
+ assert worker_detail["allow_network"] is True
98
+ assert worker_detail["write_dirs"] == ["/tmp"]
99
+ assert worker_detail["workflow_permissions"] == {
100
+ "allow_network": True,
101
+ "write_dirs": ["/tmp"],
102
+ }
87
103
  leader_without_edge = client.get(f"/api/nodes/{tab['leader_id']}")
88
104
  assert leader_without_edge.status_code == 200
89
- assert worker["id"] in leader_without_edge.json()["contacts"]
105
+ assert any(
106
+ contact["id"] == worker["id"]
107
+ for contact in leader_without_edge.json()["contacts"]
108
+ )
90
109
 
91
110
  edge_response = client.post(
92
111
  f"/api/workflows/{tab['id']}/edges",
93
112
  json={
94
- "from_node_id": tab["leader_id"],
95
- "to_node_id": worker["id"],
113
+ "from_node_id": worker["id"],
114
+ "to_node_id": reviewer["id"],
96
115
  },
97
116
  )
98
117
 
99
- assert edge_response.status_code == 400
100
- assert edge_response.json()["detail"] == (
101
- "Workflow Leader does not participate in Workflow Graph edges"
102
- )
118
+ assert edge_response.status_code == 200
119
+ edge = edge_response.json()
120
+ worker_with_edge = client.get(f"/api/nodes/{worker['id']}").json()
121
+ assert worker_with_edge["contacts"] == [
122
+ {
123
+ "id": reviewer["id"],
124
+ "target_id": reviewer["id"],
125
+ "node_type": "agent",
126
+ "role_name": "Worker",
127
+ "name": "Reviewer",
128
+ "state": "idle",
129
+ "is_leader": False,
130
+ "from_output_port_key": "out",
131
+ "to_input_port_key": "in",
132
+ "port_type": "parts",
133
+ "edge_id": edge["id"],
134
+ }
135
+ ]
103
136
 
104
137
 
105
138
  def test_direct_node_message_api_is_not_available(client: TestClient):
@@ -131,9 +164,13 @@ def test_get_assistant_detail_includes_tools_and_permissions(client: TestClient)
131
164
  assert set(data["tools"]) == set(MINIMUM_TOOLS) | set(STEWARD_ROLE_INCLUDED_TOOLS)
132
165
  assert data["write_dirs"] == [os.getcwd()]
133
166
  assert data["allow_network"] is True
167
+ assert data["workflow_permissions"] == {
168
+ "allow_network": True,
169
+ "write_dirs": [os.getcwd()],
170
+ }
134
171
 
135
172
 
136
- def test_get_node_detail_includes_state_entries_in_history(client: TestClient):
173
+ def test_get_node_detail_keeps_state_out_of_history(client: TestClient):
137
174
  assistant_id = _get_assistant_id(client)
138
175
  assistant = registry.get(assistant_id)
139
176
  assert assistant is not None
@@ -142,11 +179,9 @@ def test_get_node_detail_includes_state_entries_in_history(client: TestClient):
142
179
  response = client.get(f"/api/nodes/{assistant_id}")
143
180
 
144
181
  assert response.status_code == 200
145
- history = response.json()["history"]
146
- assert any(
147
- entry["type"] == "StateEntry" and entry["state"] == "running"
148
- for entry in history
149
- )
182
+ data = response.json()
183
+ assert data["state"] == "running"
184
+ assert all(entry["type"] != "StateEntry" for entry in data["history"])
150
185
 
151
186
 
152
187
  def test_assistant_cannot_be_terminated_via_nodes_api(client: TestClient):
@@ -233,15 +268,17 @@ def test_assistant_chat_can_be_cleared_via_nodes_api(client: TestClient):
233
268
  )
234
269
 
235
270
 
236
- def test_human_input_can_be_sent_directly_to_workflow_leader(client: TestClient):
271
+ def test_unknown_slash_input_can_be_sent_directly_to_workflow_leader(
272
+ client: TestClient,
273
+ ):
237
274
  tab = client.post(
238
275
  "/api/workflows",
239
- json={"title": "Execution"},
276
+ json={"title": "Execution", "allow_network": True},
240
277
  ).json()
241
278
 
242
279
  response = client.post(
243
280
  f"/api/nodes/{tab['leader_id']}/messages",
244
- json={"content": "/help investigate the failure"},
281
+ json={"content": "/unknown investigate the failure"},
245
282
  )
246
283
 
247
284
  assert response.status_code == 200
@@ -256,7 +293,7 @@ def test_human_input_can_be_sent_directly_to_workflow_leader(client: TestClient)
256
293
  entry["type"] == "ReceivedMessage"
257
294
  and entry["from_id"] == "human"
258
295
  and entry["message_id"] == message_id
259
- and entry["content"] == "/help investigate the failure"
296
+ and entry["content"] == "/unknown investigate the failure"
260
297
  for entry in history
261
298
  ):
262
299
  break
@@ -266,11 +303,210 @@ def test_human_input_can_be_sent_directly_to_workflow_leader(client: TestClient)
266
303
  entry["type"] == "ReceivedMessage"
267
304
  and entry["from_id"] == "human"
268
305
  and entry["message_id"] == message_id
269
- and entry["content"] == "/help investigate the failure"
306
+ and entry["content"] == "/unknown investigate the failure"
270
307
  for entry in history
271
308
  )
272
309
 
273
310
 
311
+ def test_leader_help_command_returns_visible_command_feedback(client: TestClient):
312
+ tab = client.post(
313
+ "/api/workflows",
314
+ json={"title": "Execution"},
315
+ ).json()
316
+
317
+ response = client.post(
318
+ f"/api/nodes/{tab['leader_id']}/messages",
319
+ json={"content": "/help"},
320
+ )
321
+
322
+ assert response.status_code == 200
323
+ assert response.json() == {
324
+ "status": "command_executed",
325
+ "command_name": "/help",
326
+ }
327
+
328
+ detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
329
+
330
+ assert any(
331
+ entry["type"] == "CommandResultEntry"
332
+ and entry["command_name"] == "/help"
333
+ and "Available commands" in entry["content"]
334
+ and "/compact" in entry["content"]
335
+ for entry in detail["history"]
336
+ )
337
+ assert not any(
338
+ entry["type"] == "ReceivedMessage" and entry.get("content") == "/help"
339
+ for entry in detail["history"]
340
+ )
341
+
342
+
343
+ def test_leader_clear_command_clears_only_workflow_chat(client: TestClient):
344
+ assistant_id = _get_assistant_id(client)
345
+ assistant = registry.get(assistant_id)
346
+ assert assistant is not None
347
+ assistant.history.append(
348
+ ReceivedMessage(content="Assistant stays", from_id="human")
349
+ )
350
+ tab = client.post(
351
+ "/api/workflows",
352
+ json={"title": "Execution"},
353
+ ).json()
354
+ worker = client.post(
355
+ f"/api/workflows/{tab['id']}/nodes",
356
+ json={"role_name": "Worker", "name": "Worker"},
357
+ ).json()
358
+ leader = registry.get(tab["leader_id"])
359
+ assert leader is not None
360
+ leader.history.append(ReceivedMessage(content="Old workflow chat", from_id="human"))
361
+ leader.history.append(AssistantText(content="Old workflow reply"))
362
+
363
+ response = client.post(
364
+ f"/api/nodes/{tab['leader_id']}/messages",
365
+ json={"content": "/clear"},
366
+ )
367
+
368
+ assert response.status_code == 200
369
+ assert response.json() == {
370
+ "status": "command_executed",
371
+ "command_name": "/clear",
372
+ }
373
+
374
+ leader_detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
375
+ assistant_detail = client.get(f"/api/nodes/{assistant_id}").json()
376
+ workflow = client.get(f"/api/workflows/{tab['id']}").json()
377
+
378
+ assert not any(
379
+ entry["type"] in {"ReceivedMessage", "AssistantText"}
380
+ and entry.get("content") in {"Old workflow chat", "Old workflow reply"}
381
+ for entry in leader_detail["history"]
382
+ )
383
+ assert not any(
384
+ entry["type"] == "CommandResultEntry" and entry["command_name"] == "/clear"
385
+ for entry in leader_detail["history"]
386
+ )
387
+ assert any(
388
+ entry["type"] == "ReceivedMessage" and entry.get("content") == "Assistant stays"
389
+ for entry in assistant_detail["history"]
390
+ )
391
+ assert any(node["id"] == worker["id"] for node in workflow["nodes"])
392
+
393
+
394
+ def test_leader_compact_command_keeps_history_and_adds_system_feedback(
395
+ monkeypatch,
396
+ client: TestClient,
397
+ ):
398
+ tab = client.post(
399
+ "/api/workflows",
400
+ json={"title": "Execution", "allow_network": True},
401
+ ).json()
402
+ leader = registry.get(tab["leader_id"])
403
+ assert leader is not None
404
+ leader.history.extend(
405
+ [
406
+ ReceivedMessage(content="Need a concise recap", from_id="human"),
407
+ AssistantText(content="I will summarize the workflow."),
408
+ ]
409
+ )
410
+
411
+ monkeypatch.setattr(
412
+ "flowent.agent.gateway.chat",
413
+ lambda *args, **kwargs: LLMResponse(
414
+ content=(
415
+ "## Current Goal\nShip the shared commands.\n\n"
416
+ "## Active Task Boundary\nKeep it in the workflow chat.\n\n"
417
+ "## Key Constraints\nDo not clear visible history.\n\n"
418
+ "## Confirmed Decisions\nUse shared commands.\n\n"
419
+ "## Open Questions\nNone.\n\n"
420
+ "## Next Actions\nVerify the workflow panel."
421
+ )
422
+ ),
423
+ )
424
+
425
+ response = client.post(
426
+ f"/api/nodes/{tab['leader_id']}/messages",
427
+ json={"content": "/compact workflow command rollout"},
428
+ )
429
+
430
+ assert response.status_code == 200
431
+ assert response.json() == {
432
+ "status": "command_executed",
433
+ "command_name": "/compact",
434
+ }
435
+
436
+ detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
437
+
438
+ assert any(
439
+ entry["type"] == "ReceivedMessage"
440
+ and entry.get("content") == "Need a concise recap"
441
+ for entry in detail["history"]
442
+ )
443
+ assert any(
444
+ entry["type"] == "AssistantText"
445
+ and entry.get("content") == "I will summarize the workflow."
446
+ for entry in detail["history"]
447
+ )
448
+ assert any(
449
+ entry["type"] == "CommandResultEntry"
450
+ and entry["command_name"] == "/compact"
451
+ and entry.get("include_in_context") is False
452
+ and "Compacted this chat for future replies." in entry["content"]
453
+ and "Focus: workflow command rollout" in entry["content"]
454
+ for entry in detail["history"]
455
+ )
456
+
457
+
458
+ def test_leader_image_message_bypasses_conversation_commands(
459
+ client: TestClient,
460
+ monkeypatch,
461
+ ):
462
+ tab = client.post(
463
+ "/api/workflows",
464
+ json={"title": "Execution"},
465
+ ).json()
466
+ leader = registry.get(tab["leader_id"])
467
+ assert leader is not None
468
+ monkeypatch.setattr(leader, "supports_input_image", lambda: True)
469
+
470
+ upload_response = client.post(
471
+ "/api/image-assets",
472
+ files={"file": ("pixel.png", _ONE_PIXEL_PNG, "image/png")},
473
+ )
474
+ assert upload_response.status_code == 200
475
+ asset_id = upload_response.json()["id"]
476
+
477
+ response = client.post(
478
+ f"/api/nodes/{tab['leader_id']}/messages",
479
+ json={
480
+ "parts": [
481
+ {"type": "text", "text": "/help"},
482
+ {
483
+ "type": "image",
484
+ "asset_id": asset_id,
485
+ "mime_type": "image/png",
486
+ "width": 1,
487
+ "height": 1,
488
+ },
489
+ ]
490
+ },
491
+ )
492
+
493
+ assert response.status_code == 200
494
+ assert response.json()["status"] == "sent"
495
+
496
+ detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
497
+
498
+ assert any(
499
+ entry["type"] == "ReceivedMessage"
500
+ and entry["from_id"] == "human"
501
+ and entry["content"] == "/help[image]"
502
+ for entry in detail["history"]
503
+ )
504
+ assert not any(
505
+ entry["type"] == "CommandResultEntry" and entry["command_name"] == "/help"
506
+ for entry in detail["history"]
507
+ )
508
+
509
+
274
510
  def test_leader_retry_rewrites_tail_and_reuses_image_parts(monkeypatch, client):
275
511
  assistant_id = _get_assistant_id(client)
276
512
  tab = client.post(
@@ -64,9 +64,10 @@ def test_roles_bootstrap_includes_tools_and_providers(client: TestClient, monkey
64
64
  ]
65
65
  assert {role["name"] for role in payload["roles"]} == {"Steward", "Reviewer"}
66
66
  tool_names = {tool["name"] for tool in payload["tools"]}
67
- assert "delete_workflow" in tool_names
67
+ assert "delete_workflow" not in tool_names
68
+ assert "list_workflows" not in tool_names
68
69
  assert "create_agent" in tool_names
69
- assert "manage_roles" in tool_names
70
+ assert "manage_roles" not in tool_names
70
71
 
71
72
 
72
73
  def test_create_role_rejects_duplicate_name(client: TestClient, monkeypatch):