flowent 0.0.6 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (398) hide show
  1. package/README.md +1 -4
  2. package/backend/README.md +1 -4
  3. package/backend/pyproject.toml +2 -8
  4. package/backend/src/flowent/__pycache__/__init__.cpython-313.pyc +0 -0
  5. package/backend/src/flowent/__pycache__/agent.cpython-313.pyc +0 -0
  6. package/backend/src/flowent/__pycache__/cli.cpython-313.pyc +0 -0
  7. package/backend/src/flowent/__pycache__/context.cpython-313.pyc +0 -0
  8. package/backend/src/flowent/__pycache__/llm.cpython-313.pyc +0 -0
  9. package/backend/src/flowent/__pycache__/logging.cpython-313.pyc +0 -0
  10. package/backend/src/flowent/__pycache__/main.cpython-313.pyc +0 -0
  11. package/backend/src/flowent/__pycache__/patch.cpython-313.pyc +0 -0
  12. package/backend/src/flowent/__pycache__/paths.cpython-313.pyc +0 -0
  13. package/backend/src/flowent/__pycache__/sandbox.cpython-313.pyc +0 -0
  14. package/backend/src/flowent/__pycache__/storage.cpython-313.pyc +0 -0
  15. package/backend/src/flowent/__pycache__/tools.cpython-313.pyc +0 -0
  16. package/backend/src/flowent/agent.py +217 -3094
  17. package/backend/src/flowent/cli.py +19 -24
  18. package/backend/src/flowent/context.py +127 -0
  19. package/backend/src/flowent/llm.py +256 -0
  20. package/backend/src/flowent/logging.py +170 -129
  21. package/backend/src/flowent/main.py +321 -70
  22. package/backend/src/flowent/patch.py +182 -0
  23. package/backend/src/flowent/paths.py +11 -0
  24. package/backend/src/flowent/sandbox.py +214 -40
  25. package/backend/src/flowent/static/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
  26. package/backend/src/flowent/static/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
  27. package/backend/src/flowent/static/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
  28. package/backend/src/flowent/static/assets/index-C76K95ty.js +81 -0
  29. package/backend/src/flowent/static/assets/index-iUMNKvlU.css +2 -0
  30. package/backend/src/flowent/static/flowent.png +0 -0
  31. package/backend/src/flowent/static/index.html +5 -25
  32. package/backend/src/flowent/storage.py +302 -0
  33. package/backend/src/flowent/tools.py +364 -0
  34. package/backend/tests/__pycache__/test_agent_tools.cpython-313-pytest-9.0.3.pyc +0 -0
  35. package/backend/tests/__pycache__/test_health.cpython-313-pytest-9.0.3.pyc +0 -0
  36. package/backend/tests/__pycache__/test_llm_providers.cpython-313-pytest-9.0.3.pyc +0 -0
  37. package/backend/tests/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
  38. package/backend/tests/__pycache__/test_persistence.cpython-313-pytest-9.0.3.pyc +0 -0
  39. package/backend/tests/__pycache__/test_workspace_chat.cpython-313-pytest-9.0.3.pyc +0 -0
  40. package/backend/tests/test_agent_tools.py +449 -0
  41. package/backend/tests/test_health.py +12 -0
  42. package/backend/tests/test_llm_providers.py +113 -0
  43. package/backend/tests/test_logging.py +182 -0
  44. package/backend/tests/test_persistence.py +125 -0
  45. package/backend/tests/test_workspace_chat.py +578 -0
  46. package/backend/uv.lock +803 -99
  47. package/dist/frontend/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
  48. package/dist/frontend/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
  49. package/dist/frontend/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
  50. package/dist/frontend/assets/index-C76K95ty.js +81 -0
  51. package/dist/frontend/assets/index-iUMNKvlU.css +2 -0
  52. package/dist/frontend/flowent.png +0 -0
  53. package/dist/frontend/index.html +5 -25
  54. package/package.json +1 -2
  55. package/backend/src/flowent/__pycache__/_version.cpython-313.pyc +0 -0
  56. package/backend/src/flowent/__pycache__/access.cpython-313.pyc +0 -0
  57. package/backend/src/flowent/__pycache__/assistant_commands.cpython-313.pyc +0 -0
  58. package/backend/src/flowent/__pycache__/config.cpython-313.pyc +0 -0
  59. package/backend/src/flowent/__pycache__/events.cpython-313.pyc +0 -0
  60. package/backend/src/flowent/__pycache__/graph_runtime.cpython-313.pyc +0 -0
  61. package/backend/src/flowent/__pycache__/graph_service.cpython-313.pyc +0 -0
  62. package/backend/src/flowent/__pycache__/image_assets.cpython-313.pyc +0 -0
  63. package/backend/src/flowent/__pycache__/mcp_service.cpython-313.pyc +0 -0
  64. package/backend/src/flowent/__pycache__/model_metadata.cpython-313.pyc +0 -0
  65. package/backend/src/flowent/__pycache__/network.cpython-313.pyc +0 -0
  66. package/backend/src/flowent/__pycache__/observability_service.cpython-313.pyc +0 -0
  67. package/backend/src/flowent/__pycache__/registry.cpython-313.pyc +0 -0
  68. package/backend/src/flowent/__pycache__/role_management.cpython-313.pyc +0 -0
  69. package/backend/src/flowent/__pycache__/runtime.cpython-313.pyc +0 -0
  70. package/backend/src/flowent/__pycache__/security.cpython-313.pyc +0 -0
  71. package/backend/src/flowent/__pycache__/settings.cpython-313.pyc +0 -0
  72. package/backend/src/flowent/__pycache__/settings_management.cpython-313.pyc +0 -0
  73. package/backend/src/flowent/__pycache__/state_db.cpython-313.pyc +0 -0
  74. package/backend/src/flowent/__pycache__/workspace_store.cpython-313.pyc +0 -0
  75. package/backend/src/flowent/access.py +0 -247
  76. package/backend/src/flowent/assistant_commands.py +0 -115
  77. package/backend/src/flowent/channels/__init__.py +0 -3
  78. package/backend/src/flowent/channels/__pycache__/__init__.cpython-313.pyc +0 -0
  79. package/backend/src/flowent/channels/__pycache__/telegram.cpython-313.pyc +0 -0
  80. package/backend/src/flowent/channels/telegram.py +0 -615
  81. package/backend/src/flowent/config.py +0 -14
  82. package/backend/src/flowent/dev.py +0 -3
  83. package/backend/src/flowent/events.py +0 -157
  84. package/backend/src/flowent/graph_runtime.py +0 -60
  85. package/backend/src/flowent/graph_service.py +0 -2508
  86. package/backend/src/flowent/image_assets.py +0 -356
  87. package/backend/src/flowent/mcp_service.py +0 -1918
  88. package/backend/src/flowent/model_metadata.py +0 -102
  89. package/backend/src/flowent/models/__init__.py +0 -125
  90. package/backend/src/flowent/models/__pycache__/__init__.cpython-313.pyc +0 -0
  91. package/backend/src/flowent/models/__pycache__/agent.cpython-313.pyc +0 -0
  92. package/backend/src/flowent/models/__pycache__/base.cpython-313.pyc +0 -0
  93. package/backend/src/flowent/models/__pycache__/blueprint.cpython-313.pyc +0 -0
  94. package/backend/src/flowent/models/__pycache__/content.cpython-313.pyc +0 -0
  95. package/backend/src/flowent/models/__pycache__/delta.cpython-313.pyc +0 -0
  96. package/backend/src/flowent/models/__pycache__/event.cpython-313.pyc +0 -0
  97. package/backend/src/flowent/models/__pycache__/graph.cpython-313.pyc +0 -0
  98. package/backend/src/flowent/models/__pycache__/history.cpython-313.pyc +0 -0
  99. package/backend/src/flowent/models/__pycache__/llm.cpython-313.pyc +0 -0
  100. package/backend/src/flowent/models/__pycache__/message.cpython-313.pyc +0 -0
  101. package/backend/src/flowent/models/__pycache__/tab.cpython-313.pyc +0 -0
  102. package/backend/src/flowent/models/__pycache__/todo.cpython-313.pyc +0 -0
  103. package/backend/src/flowent/models/agent.py +0 -34
  104. package/backend/src/flowent/models/base.py +0 -24
  105. package/backend/src/flowent/models/blueprint.py +0 -176
  106. package/backend/src/flowent/models/content.py +0 -164
  107. package/backend/src/flowent/models/delta.py +0 -44
  108. package/backend/src/flowent/models/event.py +0 -51
  109. package/backend/src/flowent/models/graph.py +0 -472
  110. package/backend/src/flowent/models/history.py +0 -272
  111. package/backend/src/flowent/models/llm.py +0 -62
  112. package/backend/src/flowent/models/message.py +0 -33
  113. package/backend/src/flowent/models/tab.py +0 -85
  114. package/backend/src/flowent/models/todo.py +0 -10
  115. package/backend/src/flowent/network.py +0 -146
  116. package/backend/src/flowent/observability_service.py +0 -218
  117. package/backend/src/flowent/prompts/__init__.py +0 -67
  118. package/backend/src/flowent/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  119. package/backend/src/flowent/prompts/__pycache__/common.cpython-313.pyc +0 -0
  120. package/backend/src/flowent/prompts/__pycache__/steward.cpython-313.pyc +0 -0
  121. package/backend/src/flowent/prompts/common.py +0 -250
  122. package/backend/src/flowent/prompts/steward.py +0 -64
  123. package/backend/src/flowent/providers/__init__.py +0 -23
  124. package/backend/src/flowent/providers/__pycache__/__init__.cpython-313.pyc +0 -0
  125. package/backend/src/flowent/providers/__pycache__/anthropic.cpython-313.pyc +0 -0
  126. package/backend/src/flowent/providers/__pycache__/base_url.cpython-313.pyc +0 -0
  127. package/backend/src/flowent/providers/__pycache__/configuration.cpython-313.pyc +0 -0
  128. package/backend/src/flowent/providers/__pycache__/content.cpython-313.pyc +0 -0
  129. package/backend/src/flowent/providers/__pycache__/errors.cpython-313.pyc +0 -0
  130. package/backend/src/flowent/providers/__pycache__/gateway.cpython-313.pyc +0 -0
  131. package/backend/src/flowent/providers/__pycache__/headers.cpython-313.pyc +0 -0
  132. package/backend/src/flowent/providers/__pycache__/management.cpython-313.pyc +0 -0
  133. package/backend/src/flowent/providers/__pycache__/openai.cpython-313.pyc +0 -0
  134. package/backend/src/flowent/providers/__pycache__/openai_responses.cpython-313.pyc +0 -0
  135. package/backend/src/flowent/providers/__pycache__/registry.cpython-313.pyc +0 -0
  136. package/backend/src/flowent/providers/__pycache__/sse.cpython-313.pyc +0 -0
  137. package/backend/src/flowent/providers/__pycache__/thinking.cpython-313.pyc +0 -0
  138. package/backend/src/flowent/providers/anthropic.py +0 -468
  139. package/backend/src/flowent/providers/base_url.py +0 -60
  140. package/backend/src/flowent/providers/configuration.py +0 -189
  141. package/backend/src/flowent/providers/content.py +0 -122
  142. package/backend/src/flowent/providers/errors.py +0 -223
  143. package/backend/src/flowent/providers/gateway.py +0 -169
  144. package/backend/src/flowent/providers/gemini.py +0 -447
  145. package/backend/src/flowent/providers/headers.py +0 -20
  146. package/backend/src/flowent/providers/management.py +0 -96
  147. package/backend/src/flowent/providers/ollama.py +0 -293
  148. package/backend/src/flowent/providers/openai.py +0 -422
  149. package/backend/src/flowent/providers/openai_responses.py +0 -655
  150. package/backend/src/flowent/providers/registry.py +0 -144
  151. package/backend/src/flowent/providers/sse.py +0 -31
  152. package/backend/src/flowent/providers/thinking.py +0 -79
  153. package/backend/src/flowent/registry.py +0 -73
  154. package/backend/src/flowent/role_management.py +0 -267
  155. package/backend/src/flowent/routes/__init__.py +0 -28
  156. package/backend/src/flowent/routes/__pycache__/__init__.cpython-313.pyc +0 -0
  157. package/backend/src/flowent/routes/__pycache__/access.cpython-313.pyc +0 -0
  158. package/backend/src/flowent/routes/__pycache__/assistant.cpython-313.pyc +0 -0
  159. package/backend/src/flowent/routes/__pycache__/image_assets.cpython-313.pyc +0 -0
  160. package/backend/src/flowent/routes/__pycache__/mcp.cpython-313.pyc +0 -0
  161. package/backend/src/flowent/routes/__pycache__/meta.cpython-313.pyc +0 -0
  162. package/backend/src/flowent/routes/__pycache__/nodes.cpython-313.pyc +0 -0
  163. package/backend/src/flowent/routes/__pycache__/prompts.cpython-313.pyc +0 -0
  164. package/backend/src/flowent/routes/__pycache__/providers_route.cpython-313.pyc +0 -0
  165. package/backend/src/flowent/routes/__pycache__/roles.cpython-313.pyc +0 -0
  166. package/backend/src/flowent/routes/__pycache__/settings.cpython-313.pyc +0 -0
  167. package/backend/src/flowent/routes/__pycache__/tabs.cpython-313.pyc +0 -0
  168. package/backend/src/flowent/routes/__pycache__/ws.cpython-313.pyc +0 -0
  169. package/backend/src/flowent/routes/access.py +0 -48
  170. package/backend/src/flowent/routes/assistant.py +0 -155
  171. package/backend/src/flowent/routes/image_assets.py +0 -33
  172. package/backend/src/flowent/routes/mcp.py +0 -125
  173. package/backend/src/flowent/routes/meta.py +0 -28
  174. package/backend/src/flowent/routes/nodes.py +0 -413
  175. package/backend/src/flowent/routes/prompts.py +0 -46
  176. package/backend/src/flowent/routes/providers_route.py +0 -365
  177. package/backend/src/flowent/routes/roles.py +0 -207
  178. package/backend/src/flowent/routes/settings.py +0 -328
  179. package/backend/src/flowent/routes/tabs.py +0 -310
  180. package/backend/src/flowent/routes/ws.py +0 -33
  181. package/backend/src/flowent/runtime.py +0 -165
  182. package/backend/src/flowent/security.py +0 -57
  183. package/backend/src/flowent/settings.py +0 -2518
  184. package/backend/src/flowent/settings_management.py +0 -298
  185. package/backend/src/flowent/state_db.py +0 -120
  186. package/backend/src/flowent/static/assets/AssistantPage-VBohhz4d.js +0 -1
  187. package/backend/src/flowent/static/assets/ChannelsPage-CIydPZA_.js +0 -1
  188. package/backend/src/flowent/static/assets/McpPage-CHPm2TPY.js +0 -7
  189. package/backend/src/flowent/static/assets/PageScaffold-DteOA8V7.js +0 -1
  190. package/backend/src/flowent/static/assets/PromptsPage-CSmJ3sZg.js +0 -1
  191. package/backend/src/flowent/static/assets/ProvidersPage-sl2jeG4e.js +0 -3
  192. package/backend/src/flowent/static/assets/RolesPage-DCe7W6Km.js +0 -1
  193. package/backend/src/flowent/static/assets/SettingsPage-Bix9e63E.js +0 -3
  194. package/backend/src/flowent/static/assets/ToolsPage-favNkj5C.js +0 -1
  195. package/backend/src/flowent/static/assets/WorkspaceCommandDialog-DRS6wiD6.js +0 -1
  196. package/backend/src/flowent/static/assets/WorkspacePage-KuaDjt_D.js +0 -3
  197. package/backend/src/flowent/static/assets/WorkspacePanels-BZxBw8M5.js +0 -1
  198. package/backend/src/flowent/static/assets/alert-dialog-DIBUCmqM.js +0 -1
  199. package/backend/src/flowent/static/assets/datetime-eJqd0V2S.js +0 -1
  200. package/backend/src/flowent/static/assets/dialog-BOvHIBrg.js +0 -1
  201. package/backend/src/flowent/static/assets/elk-worker.min-C9JGDOE-.js +0 -6312
  202. package/backend/src/flowent/static/assets/graph-vendor-CHpVij2M.css +0 -1
  203. package/backend/src/flowent/static/assets/graph-vendor-DRq_-6fV.js +0 -7
  204. package/backend/src/flowent/static/assets/index-Biio-CoI.js +0 -10
  205. package/backend/src/flowent/static/assets/index-CmQvO7sl.css +0 -1
  206. package/backend/src/flowent/static/assets/layout.worker-jMHqAFbP.js +0 -24
  207. package/backend/src/flowent/static/assets/markdown-vendor-C9RtvaJh.js +0 -29
  208. package/backend/src/flowent/static/assets/modelParams-DcEhGnu0.js +0 -1
  209. package/backend/src/flowent/static/assets/react-vendor-mEs_JJxa.js +0 -9
  210. package/backend/src/flowent/static/assets/roles-BbIEIMeG.js +0 -1
  211. package/backend/src/flowent/static/assets/rolldown-runtime-BYbx6iT9.js +0 -1
  212. package/backend/src/flowent/static/assets/select-D9SwnlXF.js +0 -1
  213. package/backend/src/flowent/static/assets/surface-Bzr1FRG4.js +0 -1
  214. package/backend/src/flowent/static/assets/triState-DgLlKdRR.js +0 -1
  215. package/backend/src/flowent/static/assets/ui-vendor-UazN8rcv.js +0 -51
  216. package/backend/src/flowent/static/favicon.svg +0 -4
  217. package/backend/src/flowent/tools/__init__.py +0 -275
  218. package/backend/src/flowent/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  219. package/backend/src/flowent/tools/__pycache__/connect.cpython-313.pyc +0 -0
  220. package/backend/src/flowent/tools/__pycache__/contacts.cpython-313.pyc +0 -0
  221. package/backend/src/flowent/tools/__pycache__/create_agent.cpython-313.pyc +0 -0
  222. package/backend/src/flowent/tools/__pycache__/create_tab.cpython-313.pyc +0 -0
  223. package/backend/src/flowent/tools/__pycache__/delete_tab.cpython-313.pyc +0 -0
  224. package/backend/src/flowent/tools/__pycache__/edit.cpython-313.pyc +0 -0
  225. package/backend/src/flowent/tools/__pycache__/exec.cpython-313.pyc +0 -0
  226. package/backend/src/flowent/tools/__pycache__/fetch.cpython-313.pyc +0 -0
  227. package/backend/src/flowent/tools/__pycache__/idle.cpython-313.pyc +0 -0
  228. package/backend/src/flowent/tools/__pycache__/list_roles.cpython-313.pyc +0 -0
  229. package/backend/src/flowent/tools/__pycache__/list_tabs.cpython-313.pyc +0 -0
  230. package/backend/src/flowent/tools/__pycache__/list_tools.cpython-313.pyc +0 -0
  231. package/backend/src/flowent/tools/__pycache__/manage_prompts.cpython-313.pyc +0 -0
  232. package/backend/src/flowent/tools/__pycache__/manage_providers.cpython-313.pyc +0 -0
  233. package/backend/src/flowent/tools/__pycache__/manage_roles.cpython-313.pyc +0 -0
  234. package/backend/src/flowent/tools/__pycache__/manage_settings.cpython-313.pyc +0 -0
  235. package/backend/src/flowent/tools/__pycache__/mcp.cpython-313.pyc +0 -0
  236. package/backend/src/flowent/tools/__pycache__/read.cpython-313.pyc +0 -0
  237. package/backend/src/flowent/tools/__pycache__/send.cpython-313.pyc +0 -0
  238. package/backend/src/flowent/tools/__pycache__/set_permissions.cpython-313.pyc +0 -0
  239. package/backend/src/flowent/tools/__pycache__/sleep.cpython-313.pyc +0 -0
  240. package/backend/src/flowent/tools/__pycache__/todo.cpython-313.pyc +0 -0
  241. package/backend/src/flowent/tools/connect.py +0 -100
  242. package/backend/src/flowent/tools/contacts.py +0 -22
  243. package/backend/src/flowent/tools/create_agent.py +0 -191
  244. package/backend/src/flowent/tools/create_tab.py +0 -61
  245. package/backend/src/flowent/tools/delete_tab.py +0 -39
  246. package/backend/src/flowent/tools/edit.py +0 -142
  247. package/backend/src/flowent/tools/exec.py +0 -118
  248. package/backend/src/flowent/tools/fetch.py +0 -85
  249. package/backend/src/flowent/tools/idle.py +0 -27
  250. package/backend/src/flowent/tools/list_roles.py +0 -75
  251. package/backend/src/flowent/tools/list_tabs.py +0 -100
  252. package/backend/src/flowent/tools/list_tools.py +0 -28
  253. package/backend/src/flowent/tools/manage_prompts.py +0 -102
  254. package/backend/src/flowent/tools/manage_providers.py +0 -220
  255. package/backend/src/flowent/tools/manage_roles.py +0 -275
  256. package/backend/src/flowent/tools/manage_settings.py +0 -364
  257. package/backend/src/flowent/tools/mcp.py +0 -199
  258. package/backend/src/flowent/tools/read.py +0 -152
  259. package/backend/src/flowent/tools/send.py +0 -68
  260. package/backend/src/flowent/tools/set_permissions.py +0 -99
  261. package/backend/src/flowent/tools/sleep.py +0 -41
  262. package/backend/src/flowent/tools/todo.py +0 -51
  263. package/backend/src/flowent/workspace_store.py +0 -479
  264. package/backend/tests/__init__.py +0 -0
  265. package/backend/tests/__pycache__/__init__.cpython-313.pyc +0 -0
  266. package/backend/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  267. package/backend/tests/conftest.py +0 -6
  268. package/backend/tests/integration/api/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  269. package/backend/tests/integration/api/__pycache__/test_access_api.cpython-313-pytest-9.0.3.pyc +0 -0
  270. package/backend/tests/integration/api/__pycache__/test_assistant_api.cpython-313-pytest-9.0.3.pyc +0 -0
  271. package/backend/tests/integration/api/__pycache__/test_frontend_mounting.cpython-313-pytest-9.0.3.pyc +0 -0
  272. package/backend/tests/integration/api/__pycache__/test_mcp_api.cpython-313-pytest-9.0.3.pyc +0 -0
  273. package/backend/tests/integration/api/__pycache__/test_meta_api.cpython-313-pytest-9.0.3.pyc +0 -0
  274. package/backend/tests/integration/api/__pycache__/test_nodes_api.cpython-313-pytest-9.0.3.pyc +0 -0
  275. package/backend/tests/integration/api/__pycache__/test_prompts_api.cpython-313-pytest-9.0.3.pyc +0 -0
  276. package/backend/tests/integration/api/__pycache__/test_roles_api.cpython-313-pytest-9.0.3.pyc +0 -0
  277. package/backend/tests/integration/api/__pycache__/test_tabs_api.cpython-313-pytest-9.0.3.pyc +0 -0
  278. package/backend/tests/integration/api/conftest.py +0 -29
  279. package/backend/tests/integration/api/test_access_api.py +0 -182
  280. package/backend/tests/integration/api/test_assistant_api.py +0 -354
  281. package/backend/tests/integration/api/test_frontend_mounting.py +0 -61
  282. package/backend/tests/integration/api/test_mcp_api.py +0 -116
  283. package/backend/tests/integration/api/test_meta_api.py +0 -33
  284. package/backend/tests/integration/api/test_nodes_api.py +0 -722
  285. package/backend/tests/integration/api/test_prompts_api.py +0 -47
  286. package/backend/tests/integration/api/test_roles_api.py +0 -228
  287. package/backend/tests/integration/api/test_tabs_api.py +0 -802
  288. package/backend/tests/unit/__pycache__/test_access.cpython-313-pytest-9.0.3.pyc +0 -0
  289. package/backend/tests/unit/__pycache__/test_cli.cpython-313-pytest-9.0.3.pyc +0 -0
  290. package/backend/tests/unit/__pycache__/test_graph_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  291. package/backend/tests/unit/__pycache__/test_network.cpython-313-pytest-9.0.3.pyc +0 -0
  292. package/backend/tests/unit/__pycache__/test_state_sqlite_storage.cpython-313-pytest-9.0.3.pyc +0 -0
  293. package/backend/tests/unit/__pycache__/test_workspace_store.cpython-313-pytest-9.0.3.pyc +0 -0
  294. package/backend/tests/unit/agent/__pycache__/test_agent_public_api.cpython-313-pytest-9.0.3.pyc +0 -0
  295. package/backend/tests/unit/agent/__pycache__/test_agent_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  296. package/backend/tests/unit/agent/test_agent_public_api.py +0 -837
  297. package/backend/tests/unit/agent/test_agent_runtime.py +0 -2942
  298. package/backend/tests/unit/channels/__pycache__/test_telegram_channel.cpython-313-pytest-9.0.3.pyc +0 -0
  299. package/backend/tests/unit/channels/test_telegram_channel.py +0 -552
  300. package/backend/tests/unit/logging/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
  301. package/backend/tests/unit/logging/test_logging.py +0 -132
  302. package/backend/tests/unit/prompts/__pycache__/test_prompts.cpython-313-pytest-9.0.3.pyc +0 -0
  303. package/backend/tests/unit/prompts/test_prompts.py +0 -570
  304. package/backend/tests/unit/providers/__pycache__/test_anthropic_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  305. package/backend/tests/unit/providers/__pycache__/test_errors.cpython-313-pytest-9.0.3.pyc +0 -0
  306. package/backend/tests/unit/providers/__pycache__/test_extract_delta_parts.cpython-313-pytest-9.0.3.pyc +0 -0
  307. package/backend/tests/unit/providers/__pycache__/test_openai_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  308. package/backend/tests/unit/providers/__pycache__/test_openai_responses.cpython-313-pytest-9.0.3.pyc +0 -0
  309. package/backend/tests/unit/providers/__pycache__/test_provider_gateway.cpython-313-pytest-9.0.3.pyc +0 -0
  310. package/backend/tests/unit/providers/__pycache__/test_think_tag_parser.cpython-313-pytest-9.0.3.pyc +0 -0
  311. package/backend/tests/unit/providers/test_anthropic_provider.py +0 -185
  312. package/backend/tests/unit/providers/test_errors.py +0 -68
  313. package/backend/tests/unit/providers/test_extract_delta_parts.py +0 -22
  314. package/backend/tests/unit/providers/test_openai_provider.py +0 -139
  315. package/backend/tests/unit/providers/test_openai_responses.py +0 -402
  316. package/backend/tests/unit/providers/test_provider_gateway.py +0 -359
  317. package/backend/tests/unit/providers/test_think_tag_parser.py +0 -36
  318. package/backend/tests/unit/routes/__pycache__/test_prompts_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  319. package/backend/tests/unit/routes/__pycache__/test_providers_route.cpython-313-pytest-9.0.3.pyc +0 -0
  320. package/backend/tests/unit/routes/__pycache__/test_roles_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  321. package/backend/tests/unit/routes/__pycache__/test_settings_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  322. package/backend/tests/unit/routes/test_prompts_routes.py +0 -104
  323. package/backend/tests/unit/routes/test_providers_route.py +0 -370
  324. package/backend/tests/unit/routes/test_roles_routes.py +0 -535
  325. package/backend/tests/unit/routes/test_settings_routes.py +0 -1142
  326. package/backend/tests/unit/runtime/__pycache__/test_bootstrap_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  327. package/backend/tests/unit/runtime/test_bootstrap_runtime.py +0 -1002
  328. package/backend/tests/unit/sandbox/__pycache__/test_sandbox_tools.cpython-313-pytest-9.0.3.pyc +0 -0
  329. package/backend/tests/unit/sandbox/test_sandbox_tools.py +0 -78
  330. package/backend/tests/unit/security/__pycache__/test_security.cpython-313-pytest-9.0.3.pyc +0 -0
  331. package/backend/tests/unit/security/test_security.py +0 -124
  332. package/backend/tests/unit/settings/__pycache__/test_settings_roles.cpython-313-pytest-9.0.3.pyc +0 -0
  333. package/backend/tests/unit/settings/test_settings_roles.py +0 -751
  334. package/backend/tests/unit/test_access.py +0 -45
  335. package/backend/tests/unit/test_cli.py +0 -124
  336. package/backend/tests/unit/test_graph_runtime.py +0 -72
  337. package/backend/tests/unit/test_network.py +0 -51
  338. package/backend/tests/unit/test_state_sqlite_storage.py +0 -159
  339. package/backend/tests/unit/test_workspace_store.py +0 -231
  340. package/backend/tests/unit/tools/__pycache__/test_connect_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  341. package/backend/tests/unit/tools/__pycache__/test_create_agent_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  342. package/backend/tests/unit/tools/__pycache__/test_delete_tab_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  343. package/backend/tests/unit/tools/__pycache__/test_edit_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  344. package/backend/tests/unit/tools/__pycache__/test_exec_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  345. package/backend/tests/unit/tools/__pycache__/test_fetch_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  346. package/backend/tests/unit/tools/__pycache__/test_manage_prompts_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  347. package/backend/tests/unit/tools/__pycache__/test_manage_providers_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  348. package/backend/tests/unit/tools/__pycache__/test_manage_roles_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  349. package/backend/tests/unit/tools/__pycache__/test_manage_settings_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  350. package/backend/tests/unit/tools/__pycache__/test_read_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  351. package/backend/tests/unit/tools/__pycache__/test_set_permissions_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  352. package/backend/tests/unit/tools/__pycache__/test_todo_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  353. package/backend/tests/unit/tools/__pycache__/test_tool_registry.cpython-313-pytest-9.0.3.pyc +0 -0
  354. package/backend/tests/unit/tools/test_connect_tool.py +0 -228
  355. package/backend/tests/unit/tools/test_create_agent_tool.py +0 -436
  356. package/backend/tests/unit/tools/test_delete_tab_tool.py +0 -116
  357. package/backend/tests/unit/tools/test_edit_tool.py +0 -115
  358. package/backend/tests/unit/tools/test_exec_tool.py +0 -81
  359. package/backend/tests/unit/tools/test_fetch_tool.py +0 -65
  360. package/backend/tests/unit/tools/test_manage_prompts_tool.py +0 -117
  361. package/backend/tests/unit/tools/test_manage_providers_tool.py +0 -460
  362. package/backend/tests/unit/tools/test_manage_roles_tool.py +0 -411
  363. package/backend/tests/unit/tools/test_manage_settings_tool.py +0 -611
  364. package/backend/tests/unit/tools/test_read_tool.py +0 -33
  365. package/backend/tests/unit/tools/test_set_permissions_tool.py +0 -595
  366. package/backend/tests/unit/tools/test_todo_tool.py +0 -37
  367. package/backend/tests/unit/tools/test_tool_registry.py +0 -194
  368. package/dist/frontend/assets/AssistantPage-VBohhz4d.js +0 -1
  369. package/dist/frontend/assets/ChannelsPage-CIydPZA_.js +0 -1
  370. package/dist/frontend/assets/McpPage-CHPm2TPY.js +0 -7
  371. package/dist/frontend/assets/PageScaffold-DteOA8V7.js +0 -1
  372. package/dist/frontend/assets/PromptsPage-CSmJ3sZg.js +0 -1
  373. package/dist/frontend/assets/ProvidersPage-sl2jeG4e.js +0 -3
  374. package/dist/frontend/assets/RolesPage-DCe7W6Km.js +0 -1
  375. package/dist/frontend/assets/SettingsPage-Bix9e63E.js +0 -3
  376. package/dist/frontend/assets/ToolsPage-favNkj5C.js +0 -1
  377. package/dist/frontend/assets/WorkspaceCommandDialog-DRS6wiD6.js +0 -1
  378. package/dist/frontend/assets/WorkspacePage-KuaDjt_D.js +0 -3
  379. package/dist/frontend/assets/WorkspacePanels-BZxBw8M5.js +0 -1
  380. package/dist/frontend/assets/alert-dialog-DIBUCmqM.js +0 -1
  381. package/dist/frontend/assets/datetime-eJqd0V2S.js +0 -1
  382. package/dist/frontend/assets/dialog-BOvHIBrg.js +0 -1
  383. package/dist/frontend/assets/elk-worker.min-C9JGDOE-.js +0 -6312
  384. package/dist/frontend/assets/graph-vendor-CHpVij2M.css +0 -1
  385. package/dist/frontend/assets/graph-vendor-DRq_-6fV.js +0 -7
  386. package/dist/frontend/assets/index-Biio-CoI.js +0 -10
  387. package/dist/frontend/assets/index-CmQvO7sl.css +0 -1
  388. package/dist/frontend/assets/layout.worker-jMHqAFbP.js +0 -24
  389. package/dist/frontend/assets/markdown-vendor-C9RtvaJh.js +0 -29
  390. package/dist/frontend/assets/modelParams-DcEhGnu0.js +0 -1
  391. package/dist/frontend/assets/react-vendor-mEs_JJxa.js +0 -9
  392. package/dist/frontend/assets/roles-BbIEIMeG.js +0 -1
  393. package/dist/frontend/assets/rolldown-runtime-BYbx6iT9.js +0 -1
  394. package/dist/frontend/assets/select-D9SwnlXF.js +0 -1
  395. package/dist/frontend/assets/surface-Bzr1FRG4.js +0 -1
  396. package/dist/frontend/assets/triState-DgLlKdRR.js +0 -1
  397. package/dist/frontend/assets/ui-vendor-UazN8rcv.js +0 -51
  398. package/dist/frontend/favicon.svg +0 -4
@@ -1,1142 +0,0 @@
1
- import asyncio
2
- from pathlib import Path
3
-
4
- import pytest
5
- from fastapi import HTTPException
6
-
7
- from flowent.access import set_access_code, verify_access_code
8
- from flowent.agent import Agent
9
- from flowent.models import NodeConfig, NodeType, SystemEntry
10
- from flowent.prompts.steward import STEWARD_ROLE_SYSTEM_PROMPT
11
- from flowent.registry import registry
12
- from flowent.routes.settings import (
13
- UpdateSettingsRequest,
14
- UpdateTelegramSettingsRequest,
15
- approve_telegram_chat,
16
- delete_pending_telegram_chat,
17
- delete_telegram_chat,
18
- get_settings_api,
19
- get_settings_bootstrap,
20
- get_telegram_settings,
21
- update_settings,
22
- update_telegram_settings,
23
- )
24
- from flowent.settings import (
25
- ProviderConfig,
26
- RoleConfig,
27
- Settings,
28
- TelegramApprovedChat,
29
- TelegramPendingChat,
30
- TelegramSettings,
31
- build_default_assistant_write_dirs,
32
- )
33
-
34
-
35
- def test_get_settings_returns_assistant_configuration(monkeypatch):
36
- settings = Settings(
37
- roles=[RoleConfig(name="Steward", system_prompt="Default assistant role.")]
38
- )
39
-
40
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
41
-
42
- result = asyncio.run(get_settings_api())
43
-
44
- assert result["assistant"] == {
45
- "role_name": "Steward",
46
- "allow_network": True,
47
- "write_dirs": build_default_assistant_write_dirs(),
48
- }
49
- assert result["leader"] == {"role_name": "Conductor"}
50
-
51
-
52
- def test_get_settings_bootstrap_returns_related_resources(monkeypatch):
53
- settings = Settings(
54
- providers=[
55
- ProviderConfig(
56
- id="provider-1",
57
- name="Primary",
58
- type="openai_compatible",
59
- base_url="https://api.example.com/v1",
60
- api_key="secret",
61
- )
62
- ],
63
- roles=[
64
- RoleConfig(
65
- name="Steward",
66
- description="Default assistant role.",
67
- system_prompt="Default assistant role.",
68
- )
69
- ],
70
- )
71
-
72
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
73
- monkeypatch.setattr("flowent._version.__version__", "1.2.3")
74
-
75
- result = asyncio.run(get_settings_bootstrap())
76
-
77
- assert result == {
78
- "settings": {
79
- "app_data_dir": settings.app_data_dir,
80
- "working_dir": settings.working_dir,
81
- "event_log": {"timestamp_format": "absolute"},
82
- "access": {"configured": False},
83
- "assistant": {
84
- "role_name": "Steward",
85
- "allow_network": True,
86
- "write_dirs": build_default_assistant_write_dirs(),
87
- },
88
- "leader": {"role_name": "Conductor"},
89
- "telegram": {
90
- "bot_token": "",
91
- "pending_chats": [],
92
- "approved_chats": [],
93
- },
94
- "model": {
95
- "active_provider_id": "",
96
- "active_model": "",
97
- "input_image": None,
98
- "output_image": None,
99
- "structured_output": None,
100
- "capabilities": None,
101
- "context_window_tokens": None,
102
- "resolved_context_window_tokens": None,
103
- "timeout_ms": 10000,
104
- "retry_policy": "limited",
105
- "max_retries": 5,
106
- "retry_initial_delay_seconds": 0.5,
107
- "retry_max_delay_seconds": 8.0,
108
- "retry_backoff_cap_retries": 5,
109
- "auto_compact_token_limit": None,
110
- "params": {
111
- "reasoning_effort": None,
112
- "verbosity": None,
113
- "max_output_tokens": None,
114
- "temperature": None,
115
- "top_p": None,
116
- },
117
- },
118
- "custom_prompt": "",
119
- "custom_post_prompt": "",
120
- "providers": [
121
- {
122
- "id": "provider-1",
123
- "name": "Primary",
124
- "type": "openai_compatible",
125
- "base_url": "https://api.example.com/v1",
126
- "api_key": "secret",
127
- "headers": {},
128
- "retry_429_delay_seconds": 0,
129
- "models": [],
130
- }
131
- ],
132
- "roles": [
133
- {
134
- "name": "Steward",
135
- "description": "Default assistant role.",
136
- "system_prompt": "Default assistant role.",
137
- "model": None,
138
- "model_params": None,
139
- "included_tools": [],
140
- "excluded_tools": [],
141
- }
142
- ],
143
- "mcp_servers": [],
144
- },
145
- "providers": [
146
- {
147
- "id": "provider-1",
148
- "name": "Primary",
149
- "type": "openai_compatible",
150
- "base_url": "https://api.example.com/v1",
151
- "api_key": "secret",
152
- "headers": {},
153
- "retry_429_delay_seconds": 0,
154
- "models": [],
155
- }
156
- ],
157
- "roles": [
158
- {
159
- "name": "Steward",
160
- "description": "Default assistant role.",
161
- "system_prompt": "Default assistant role.",
162
- "model": None,
163
- "model_params": None,
164
- "included_tools": [],
165
- "excluded_tools": [],
166
- "is_builtin": True,
167
- }
168
- ],
169
- "version": "1.2.3",
170
- }
171
-
172
-
173
- def test_get_telegram_settings_masks_bot_token(monkeypatch):
174
- settings = Settings(
175
- telegram=TelegramSettings(
176
- bot_token="123456:ABCDE",
177
- pending_chats=[
178
- TelegramPendingChat(
179
- chat_id=1001,
180
- username="alice",
181
- display_name="Alice",
182
- first_seen_at=1.0,
183
- last_seen_at=2.0,
184
- )
185
- ],
186
- approved_chats=[
187
- TelegramApprovedChat(
188
- chat_id=-2002,
189
- username="bob",
190
- display_name="Bob",
191
- approved_at=3.0,
192
- )
193
- ],
194
- )
195
- )
196
-
197
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
198
-
199
- result = asyncio.run(get_telegram_settings())
200
-
201
- assert result == {
202
- "bot_token": "sk-...BCDE",
203
- "pending_chats": [
204
- {
205
- "chat_id": 1001,
206
- "username": "alice",
207
- "display_name": "Alice",
208
- "first_seen_at": 1.0,
209
- "last_seen_at": 2.0,
210
- }
211
- ],
212
- "approved_chats": [
213
- {
214
- "chat_id": -2002,
215
- "username": "bob",
216
- "display_name": "Bob",
217
- "approved_at": 3.0,
218
- }
219
- ],
220
- }
221
-
222
-
223
- def test_update_telegram_settings_restarts_channel_when_token_changes(monkeypatch):
224
- settings = Settings(
225
- telegram=TelegramSettings(
226
- bot_token="old-token",
227
- pending_chats=[
228
- TelegramPendingChat(
229
- chat_id=1001,
230
- username="alice",
231
- display_name="Alice",
232
- first_seen_at=1.0,
233
- last_seen_at=2.0,
234
- )
235
- ],
236
- approved_chats=[
237
- TelegramApprovedChat(
238
- chat_id=-2002,
239
- username="bob",
240
- display_name="Bob",
241
- approved_at=3.0,
242
- )
243
- ],
244
- )
245
- )
246
- saved: list[Settings] = []
247
- restarted: list[str] = []
248
-
249
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
250
- monkeypatch.setattr(
251
- "flowent.routes.settings.save_settings",
252
- lambda current: saved.append(current),
253
- )
254
- monkeypatch.setattr(
255
- "flowent.runtime.restart_telegram_channel",
256
- lambda: restarted.append("restart"),
257
- )
258
-
259
- result = asyncio.run(
260
- update_telegram_settings(
261
- UpdateTelegramSettingsRequest(
262
- bot_token="new-token",
263
- )
264
- )
265
- )
266
-
267
- assert settings.telegram == TelegramSettings(
268
- bot_token="new-token",
269
- pending_chats=[
270
- TelegramPendingChat(
271
- chat_id=1001,
272
- username="alice",
273
- display_name="Alice",
274
- first_seen_at=1.0,
275
- last_seen_at=2.0,
276
- )
277
- ],
278
- approved_chats=[
279
- TelegramApprovedChat(
280
- chat_id=-2002,
281
- username="bob",
282
- display_name="Bob",
283
- approved_at=3.0,
284
- )
285
- ],
286
- )
287
- assert saved == [settings]
288
- assert restarted == ["restart"]
289
- assert result == {
290
- "status": "saved",
291
- "telegram": {
292
- "bot_token": "sk-...oken",
293
- "pending_chats": [
294
- {
295
- "chat_id": 1001,
296
- "username": "alice",
297
- "display_name": "Alice",
298
- "first_seen_at": 1.0,
299
- "last_seen_at": 2.0,
300
- }
301
- ],
302
- "approved_chats": [
303
- {
304
- "chat_id": -2002,
305
- "username": "bob",
306
- "display_name": "Bob",
307
- "approved_at": 3.0,
308
- }
309
- ],
310
- },
311
- }
312
-
313
-
314
- def test_approve_telegram_chat_moves_pending_chat_to_approved(monkeypatch):
315
- settings = Settings(
316
- telegram=TelegramSettings(
317
- bot_token="token",
318
- pending_chats=[
319
- TelegramPendingChat(
320
- chat_id=3003,
321
- username="alice",
322
- display_name="Alice",
323
- first_seen_at=1.0,
324
- last_seen_at=2.0,
325
- )
326
- ],
327
- approved_chats=[],
328
- )
329
- )
330
- saved: list[Settings] = []
331
-
332
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
333
- monkeypatch.setattr(
334
- "flowent.routes.settings.save_settings",
335
- lambda current: saved.append(current),
336
- )
337
- monkeypatch.setattr("flowent.routes.settings.time.time", lambda: 42.0)
338
-
339
- result = asyncio.run(approve_telegram_chat(3003))
340
-
341
- assert settings.telegram.pending_chats == []
342
- assert settings.telegram.approved_chats == [
343
- TelegramApprovedChat(
344
- chat_id=3003,
345
- username="alice",
346
- display_name="Alice",
347
- approved_at=42.0,
348
- )
349
- ]
350
- assert saved == [settings]
351
- assert result == {
352
- "status": "approved",
353
- "telegram": {
354
- "bot_token": "sk-...oken",
355
- "pending_chats": [],
356
- "approved_chats": [
357
- {
358
- "chat_id": 3003,
359
- "username": "alice",
360
- "display_name": "Alice",
361
- "approved_at": 42.0,
362
- }
363
- ],
364
- },
365
- }
366
-
367
-
368
- def test_delete_pending_telegram_chat_removes_pending_chat(monkeypatch):
369
- settings = Settings(
370
- telegram=TelegramSettings(
371
- bot_token="token",
372
- pending_chats=[
373
- TelegramPendingChat(
374
- chat_id=3003,
375
- username="alice",
376
- display_name="Alice",
377
- first_seen_at=1.0,
378
- last_seen_at=2.0,
379
- )
380
- ],
381
- )
382
- )
383
- saved: list[Settings] = []
384
-
385
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
386
- monkeypatch.setattr(
387
- "flowent.routes.settings.save_settings",
388
- lambda current: saved.append(current),
389
- )
390
-
391
- result = asyncio.run(delete_pending_telegram_chat(3003))
392
-
393
- assert settings.telegram.pending_chats == []
394
- assert saved == [settings]
395
- assert result == {
396
- "status": "deleted",
397
- "telegram": {
398
- "bot_token": "sk-...oken",
399
- "pending_chats": [],
400
- "approved_chats": [],
401
- },
402
- }
403
-
404
-
405
- def test_delete_telegram_chat_removes_approved_chat(monkeypatch):
406
- settings = Settings(
407
- telegram=TelegramSettings(
408
- bot_token="token",
409
- approved_chats=[
410
- TelegramApprovedChat(
411
- chat_id=-2002,
412
- username="bob",
413
- display_name="Bob",
414
- approved_at=3.0,
415
- ),
416
- TelegramApprovedChat(
417
- chat_id=3003,
418
- username="alice",
419
- display_name="Alice",
420
- approved_at=4.0,
421
- ),
422
- ],
423
- )
424
- )
425
- saved: list[Settings] = []
426
-
427
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
428
- monkeypatch.setattr(
429
- "flowent.routes.settings.save_settings",
430
- lambda current: saved.append(current),
431
- )
432
-
433
- result = asyncio.run(delete_telegram_chat(-2002))
434
-
435
- assert settings.telegram.approved_chats == [
436
- TelegramApprovedChat(
437
- chat_id=3003,
438
- username="alice",
439
- display_name="Alice",
440
- approved_at=4.0,
441
- )
442
- ]
443
- assert saved == [settings]
444
- assert result == {
445
- "status": "deleted",
446
- "telegram": {
447
- "bot_token": "sk-...oken",
448
- "pending_chats": [],
449
- "approved_chats": [
450
- {
451
- "chat_id": 3003,
452
- "username": "alice",
453
- "display_name": "Alice",
454
- "approved_at": 4.0,
455
- }
456
- ],
457
- },
458
- }
459
-
460
-
461
- def test_update_settings_accepts_xhigh_reasoning_effort(monkeypatch):
462
- settings = Settings(
463
- roles=[RoleConfig(name="Steward", system_prompt="Default assistant role.")]
464
- )
465
- saved: list[Settings] = []
466
-
467
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
468
- monkeypatch.setattr(
469
- "flowent.routes.settings.save_settings",
470
- lambda current: saved.append(current),
471
- )
472
- monkeypatch.setattr(
473
- "flowent.providers.gateway.gateway.invalidate_cache", lambda: None
474
- )
475
-
476
- result = asyncio.run(
477
- update_settings(
478
- UpdateSettingsRequest(
479
- model={"params": {"reasoning_effort": "xhigh"}},
480
- )
481
- )
482
- )
483
-
484
- assert settings.model.params.reasoning_effort == "xhigh"
485
- assert result["settings"]["model"]["params"]["reasoning_effort"] == "xhigh"
486
- assert saved == [settings]
487
-
488
-
489
- def test_update_settings_rotates_access_code_and_requires_reauth(monkeypatch):
490
- settings = Settings(
491
- roles=[RoleConfig(name="Steward", system_prompt="Default assistant role.")]
492
- )
493
- set_access_code(settings, "OLD-ACCESS-CODE")
494
- saved: list[Settings] = []
495
- closed: list[dict[str, object]] = []
496
-
497
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
498
- monkeypatch.setattr(
499
- "flowent.routes.settings.save_settings",
500
- lambda current: saved.append(current),
501
- )
502
- monkeypatch.setattr(
503
- "flowent.providers.gateway.gateway.invalidate_cache", lambda: None
504
- )
505
- monkeypatch.setattr(
506
- "flowent.routes.settings.event_bus.close_all_connections",
507
- lambda **kwargs: closed.append(kwargs),
508
- )
509
-
510
- result = asyncio.run(
511
- update_settings(
512
- UpdateSettingsRequest(
513
- access={
514
- "new_code": "NEW-ACCESS-CODE",
515
- "confirm_code": "NEW-ACCESS-CODE",
516
- }
517
- )
518
- )
519
- )
520
-
521
- assert verify_access_code(settings.access, "NEW-ACCESS-CODE")
522
- assert not verify_access_code(settings.access, "OLD-ACCESS-CODE")
523
- assert saved == [settings]
524
- assert result["reauth_required"] is True
525
- assert result["settings"]["access"] == {"configured": True}
526
- assert closed == [{"code": 4001, "reason": "Access code rotated"}]
527
-
528
-
529
- def test_update_settings_does_not_mutate_cached_access_when_save_fails(monkeypatch):
530
- settings = Settings(
531
- roles=[RoleConfig(name="Steward", system_prompt="Default assistant role.")]
532
- )
533
- set_access_code(settings, "OLD-ACCESS-CODE")
534
-
535
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
536
- monkeypatch.setattr(
537
- "flowent.routes.settings.save_settings",
538
- lambda current: (_ for _ in ()).throw(RuntimeError("disk full")),
539
- )
540
-
541
- with pytest.raises(RuntimeError, match="disk full"):
542
- asyncio.run(
543
- update_settings(
544
- UpdateSettingsRequest(
545
- access={
546
- "new_code": "NEW-ACCESS-CODE",
547
- "confirm_code": "NEW-ACCESS-CODE",
548
- }
549
- )
550
- )
551
- )
552
-
553
- assert verify_access_code(settings.access, "OLD-ACCESS-CODE")
554
- assert not verify_access_code(settings.access, "NEW-ACCESS-CODE")
555
-
556
-
557
- def test_update_settings_accepts_model_max_retries(monkeypatch):
558
- settings = Settings(
559
- roles=[RoleConfig(name="Steward", system_prompt="Default assistant role.")]
560
- )
561
- saved: list[Settings] = []
562
-
563
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
564
- monkeypatch.setattr(
565
- "flowent.routes.settings.save_settings",
566
- lambda current: saved.append(current),
567
- )
568
- monkeypatch.setattr(
569
- "flowent.providers.gateway.gateway.invalidate_cache", lambda: None
570
- )
571
-
572
- result = asyncio.run(
573
- update_settings(
574
- UpdateSettingsRequest(
575
- model={"max_retries": 8},
576
- )
577
- )
578
- )
579
-
580
- assert settings.model.max_retries == 8
581
- assert result["settings"]["model"]["max_retries"] == 8
582
- assert saved == [settings]
583
-
584
-
585
- def test_update_settings_accepts_model_metadata_overrides_and_token_limit(
586
- monkeypatch,
587
- ):
588
- settings = Settings(
589
- providers=[
590
- ProviderConfig(
591
- id="provider-1",
592
- name="Primary",
593
- type="openai_responses",
594
- base_url="https://api.example.com/v1",
595
- api_key="secret",
596
- )
597
- ],
598
- roles=[RoleConfig(name="Steward", system_prompt="Default assistant role.")],
599
- )
600
- settings.model.active_provider_id = "provider-1"
601
- settings.model.active_model = "gpt-5.2"
602
- saved: list[Settings] = []
603
-
604
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
605
- monkeypatch.setattr(
606
- "flowent.routes.settings.save_settings",
607
- lambda current: saved.append(current),
608
- )
609
- monkeypatch.setattr(
610
- "flowent.providers.gateway.gateway.invalidate_cache", lambda: None
611
- )
612
-
613
- result = asyncio.run(
614
- update_settings(
615
- UpdateSettingsRequest(
616
- model={
617
- "context_window_tokens": 64000,
618
- "input_image": True,
619
- "output_image": False,
620
- "structured_output": True,
621
- "auto_compact_token_limit": 48000,
622
- },
623
- )
624
- )
625
- )
626
-
627
- assert settings.model.context_window_tokens == 64000
628
- assert settings.model.input_image is True
629
- assert settings.model.output_image is False
630
- assert settings.model.structured_output is True
631
- assert settings.model.auto_compact_token_limit == 48000
632
- assert result["settings"]["model"]["context_window_tokens"] == 64000
633
- assert result["settings"]["model"]["resolved_context_window_tokens"] == 64000
634
- assert result["settings"]["model"]["capabilities"] == {
635
- "input_image": True,
636
- "output_image": False,
637
- "structured_output": True,
638
- }
639
- assert result["settings"]["model"]["auto_compact_token_limit"] == 48000
640
- assert saved == [settings]
641
-
642
-
643
- def test_update_settings_accepts_model_retry_policy(monkeypatch):
644
- settings = Settings(
645
- roles=[RoleConfig(name="Steward", system_prompt="Default assistant role.")]
646
- )
647
- saved: list[Settings] = []
648
-
649
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
650
- monkeypatch.setattr(
651
- "flowent.routes.settings.save_settings",
652
- lambda current: saved.append(current),
653
- )
654
- monkeypatch.setattr(
655
- "flowent.providers.gateway.gateway.invalidate_cache", lambda: None
656
- )
657
-
658
- result = asyncio.run(
659
- update_settings(
660
- UpdateSettingsRequest(
661
- model={"retry_policy": "unlimited"},
662
- )
663
- )
664
- )
665
-
666
- assert settings.model.retry_policy == "unlimited"
667
- assert result["settings"]["model"]["retry_policy"] == "unlimited"
668
- assert saved == [settings]
669
-
670
-
671
- def test_update_settings_rejects_invalid_model_retry_policy(monkeypatch):
672
- settings = Settings(
673
- roles=[RoleConfig(name="Steward", system_prompt="Default assistant role.")]
674
- )
675
-
676
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
677
-
678
- with pytest.raises(HTTPException) as excinfo:
679
- asyncio.run(
680
- update_settings(
681
- UpdateSettingsRequest(
682
- model={"retry_policy": "forever"},
683
- )
684
- )
685
- )
686
-
687
- assert excinfo.value.status_code == 400
688
- assert (
689
- excinfo.value.detail
690
- == "model.retry_policy must be one of: limited, no_retry, unlimited"
691
- )
692
-
693
-
694
- def test_update_settings_accepts_model_timeout_ms(monkeypatch):
695
- settings = Settings(
696
- roles=[RoleConfig(name="Steward", system_prompt="Default assistant role.")]
697
- )
698
- saved: list[Settings] = []
699
-
700
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
701
- monkeypatch.setattr(
702
- "flowent.routes.settings.save_settings",
703
- lambda current: saved.append(current),
704
- )
705
- monkeypatch.setattr(
706
- "flowent.providers.gateway.gateway.invalidate_cache", lambda: None
707
- )
708
-
709
- result = asyncio.run(
710
- update_settings(
711
- UpdateSettingsRequest(
712
- model={"timeout_ms": 15000},
713
- )
714
- )
715
- )
716
-
717
- assert settings.model.timeout_ms == 15000
718
- assert result["settings"]["model"]["timeout_ms"] == 15000
719
- assert saved == [settings]
720
-
721
-
722
- def test_update_settings_accepts_retry_backoff_fields(monkeypatch):
723
- settings = Settings(
724
- roles=[RoleConfig(name="Steward", system_prompt="Default assistant role.")]
725
- )
726
- saved: list[Settings] = []
727
-
728
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
729
- monkeypatch.setattr(
730
- "flowent.routes.settings.save_settings",
731
- lambda current: saved.append(current),
732
- )
733
- monkeypatch.setattr(
734
- "flowent.providers.gateway.gateway.invalidate_cache", lambda: None
735
- )
736
-
737
- result = asyncio.run(
738
- update_settings(
739
- UpdateSettingsRequest(
740
- model={
741
- "retry_initial_delay_seconds": 0.75,
742
- "retry_max_delay_seconds": 12.0,
743
- "retry_backoff_cap_retries": 3,
744
- },
745
- )
746
- )
747
- )
748
-
749
- assert settings.model.retry_initial_delay_seconds == 0.75
750
- assert settings.model.retry_max_delay_seconds == 12.0
751
- assert settings.model.retry_backoff_cap_retries == 3
752
- assert result["settings"]["model"]["retry_initial_delay_seconds"] == 0.75
753
- assert result["settings"]["model"]["retry_max_delay_seconds"] == 12.0
754
- assert result["settings"]["model"]["retry_backoff_cap_retries"] == 3
755
- assert saved == [settings]
756
-
757
-
758
- def test_update_settings_rejects_retry_backoff_when_max_below_initial(monkeypatch):
759
- settings = Settings(
760
- roles=[RoleConfig(name="Steward", system_prompt="Default assistant role.")]
761
- )
762
-
763
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
764
-
765
- with pytest.raises(HTTPException) as excinfo:
766
- asyncio.run(
767
- update_settings(
768
- UpdateSettingsRequest(
769
- model={
770
- "retry_initial_delay_seconds": 1.5,
771
- "retry_max_delay_seconds": 1.0,
772
- },
773
- )
774
- )
775
- )
776
-
777
- assert excinfo.value.status_code == 400
778
- assert (
779
- excinfo.value.detail
780
- == "model.retry_max_delay_seconds must be greater than or equal to model.retry_initial_delay_seconds"
781
- )
782
-
783
-
784
- def test_update_settings_rejects_non_positive_model_timeout_ms(monkeypatch):
785
- settings = Settings(
786
- roles=[RoleConfig(name="Steward", system_prompt="Default assistant role.")]
787
- )
788
-
789
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
790
-
791
- with pytest.raises(HTTPException) as excinfo:
792
- asyncio.run(
793
- update_settings(
794
- UpdateSettingsRequest(
795
- model={"timeout_ms": 0},
796
- )
797
- )
798
- )
799
-
800
- assert excinfo.value.status_code == 400
801
- assert excinfo.value.detail == "model.timeout_ms must be greater than 0"
802
-
803
-
804
- def test_update_settings_persists_assistant_role(monkeypatch):
805
- settings = Settings(
806
- roles=[
807
- RoleConfig(name="Steward", system_prompt="Default assistant role."),
808
- RoleConfig(name="Reviewer", system_prompt="Review carefully."),
809
- ]
810
- )
811
- saved: list[Settings] = []
812
-
813
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
814
- monkeypatch.setattr(
815
- "flowent.routes.settings.save_settings",
816
- lambda current: saved.append(current),
817
- )
818
- monkeypatch.setattr(
819
- "flowent.providers.gateway.gateway.invalidate_cache", lambda: None
820
- )
821
-
822
- result = asyncio.run(
823
- update_settings(
824
- UpdateSettingsRequest(assistant={"role_name": "Reviewer"}),
825
- )
826
- )
827
-
828
- assert settings.assistant.role_name == "Reviewer"
829
- assert result["settings"]["assistant"] == {
830
- "role_name": "Reviewer",
831
- "allow_network": True,
832
- "write_dirs": build_default_assistant_write_dirs(),
833
- }
834
- assert saved == [settings]
835
-
836
-
837
- def test_update_settings_keeps_live_assistant_entry_semantics_for_non_steward_role(
838
- monkeypatch,
839
- ):
840
- registry.reset()
841
- settings = Settings(
842
- roles=[
843
- RoleConfig(name="Steward", system_prompt="Default assistant role."),
844
- RoleConfig(
845
- name="Reviewer",
846
- system_prompt="Review carefully.",
847
- included_tools=["read"],
848
- ),
849
- ]
850
- )
851
- assistant = Agent(
852
- NodeConfig(
853
- node_type=NodeType.ASSISTANT,
854
- role_name="Steward",
855
- tools=[
856
- "create_workflow",
857
- "delete_workflow",
858
- "set_permissions",
859
- "manage_settings",
860
- ],
861
- )
862
- )
863
- saved: list[Settings] = []
864
-
865
- registry.register(assistant)
866
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
867
- monkeypatch.setattr("flowent.settings.get_settings", lambda: settings)
868
- monkeypatch.setattr(
869
- "flowent.routes.settings.save_settings",
870
- lambda current: saved.append(current),
871
- )
872
- monkeypatch.setattr("flowent.graph_service.sync_tab_leaders", lambda reason: None)
873
- monkeypatch.setattr(
874
- "flowent.providers.gateway.gateway.invalidate_cache", lambda: None
875
- )
876
-
877
- try:
878
- result = asyncio.run(
879
- update_settings(
880
- UpdateSettingsRequest(assistant={"role_name": "Reviewer"}),
881
- )
882
- )
883
-
884
- assert result["settings"]["assistant"] == {
885
- "role_name": "Reviewer",
886
- "allow_network": True,
887
- "write_dirs": build_default_assistant_write_dirs(),
888
- }
889
- assert assistant.config.role_name == "Reviewer"
890
- assert "create_workflow" in assistant.config.tools
891
- assert "delete_workflow" in assistant.config.tools
892
- assert "set_permissions" in assistant.config.tools
893
- assert "manage_settings" in assistant.config.tools
894
- assert "read" in assistant.config.tools
895
- system_prompt = next(
896
- entry.content
897
- for entry in assistant.history
898
- if isinstance(entry, SystemEntry)
899
- )
900
- assert STEWARD_ROLE_SYSTEM_PROMPT in system_prompt
901
- assert "## Selected Role Overlay" in system_prompt
902
- assert "Review carefully." in system_prompt
903
- assert saved == [settings]
904
- finally:
905
- registry.reset()
906
-
907
-
908
- def test_update_settings_persists_assistant_permissions(monkeypatch):
909
- settings = Settings(
910
- roles=[RoleConfig(name="Steward", system_prompt="Default assistant role.")]
911
- )
912
- saved: list[Settings] = []
913
-
914
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
915
- monkeypatch.setattr(
916
- "flowent.routes.settings.save_settings",
917
- lambda current: saved.append(current),
918
- )
919
- monkeypatch.setattr(
920
- "flowent.providers.gateway.gateway.invalidate_cache", lambda: None
921
- )
922
-
923
- result = asyncio.run(
924
- update_settings(
925
- UpdateSettingsRequest(
926
- assistant={
927
- "allow_network": False,
928
- "write_dirs": [" ./tmp ", "./tmp/", ""],
929
- }
930
- ),
931
- )
932
- )
933
-
934
- expected_write_dirs = [str((Path.cwd() / "tmp").resolve())]
935
- assert settings.assistant.allow_network is False
936
- assert settings.assistant.write_dirs == expected_write_dirs
937
- assert result["settings"]["assistant"] == {
938
- "role_name": "Steward",
939
- "allow_network": False,
940
- "write_dirs": expected_write_dirs,
941
- }
942
- assert saved == [settings]
943
-
944
-
945
- def test_update_settings_persists_working_dir(monkeypatch, tmp_path):
946
- settings = Settings(
947
- roles=[RoleConfig(name="Steward", system_prompt="Default assistant role.")]
948
- )
949
- saved: list[Settings] = []
950
-
951
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
952
- monkeypatch.setattr(
953
- "flowent.routes.settings.save_settings",
954
- lambda current: saved.append(current),
955
- )
956
- monkeypatch.setattr(
957
- "flowent.providers.gateway.gateway.invalidate_cache", lambda: None
958
- )
959
-
960
- result = asyncio.run(
961
- update_settings(
962
- UpdateSettingsRequest(working_dir=str(tmp_path)),
963
- )
964
- )
965
-
966
- assert settings.working_dir == str(tmp_path.resolve())
967
- assert result["settings"]["working_dir"] == str(tmp_path.resolve())
968
- assert saved == [settings]
969
-
970
-
971
- def test_update_settings_resolves_assistant_write_dirs_against_new_working_dir(
972
- monkeypatch,
973
- tmp_path,
974
- ):
975
- settings = Settings(
976
- roles=[RoleConfig(name="Steward", system_prompt="Default assistant role.")]
977
- )
978
- saved: list[Settings] = []
979
- target_dir = tmp_path / "project"
980
- target_dir.mkdir()
981
-
982
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
983
- monkeypatch.setattr(
984
- "flowent.routes.settings.save_settings",
985
- lambda current: saved.append(current),
986
- )
987
- monkeypatch.setattr(
988
- "flowent.providers.gateway.gateway.invalidate_cache", lambda: None
989
- )
990
-
991
- result = asyncio.run(
992
- update_settings(
993
- UpdateSettingsRequest(
994
- working_dir=str(target_dir),
995
- assistant={"write_dirs": ["./out"]},
996
- ),
997
- )
998
- )
999
-
1000
- assert settings.working_dir == str(target_dir.resolve())
1001
- assert settings.assistant.write_dirs == [str((target_dir / "out").resolve())]
1002
- assert result["settings"]["assistant"]["write_dirs"] == [
1003
- str((target_dir / "out").resolve())
1004
- ]
1005
- assert saved == [settings]
1006
-
1007
-
1008
- def test_update_settings_rejects_blank_working_dir(monkeypatch):
1009
- settings = Settings(
1010
- roles=[RoleConfig(name="Steward", system_prompt="Default assistant role.")]
1011
- )
1012
-
1013
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
1014
-
1015
- with pytest.raises(HTTPException) as excinfo:
1016
- asyncio.run(
1017
- update_settings(
1018
- UpdateSettingsRequest(working_dir=" "),
1019
- )
1020
- )
1021
-
1022
- assert excinfo.value.status_code == 400
1023
- assert excinfo.value.detail == "working_dir must not be empty"
1024
-
1025
-
1026
- def test_update_settings_rejects_missing_working_dir(monkeypatch):
1027
- settings = Settings(
1028
- roles=[RoleConfig(name="Steward", system_prompt="Default assistant role.")]
1029
- )
1030
-
1031
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
1032
-
1033
- with pytest.raises(HTTPException) as excinfo:
1034
- asyncio.run(
1035
- update_settings(
1036
- UpdateSettingsRequest(
1037
- working_dir="/definitely/missing/flowent-working-dir"
1038
- ),
1039
- )
1040
- )
1041
-
1042
- assert excinfo.value.status_code == 400
1043
- assert excinfo.value.detail == "working_dir must be an existing directory"
1044
-
1045
-
1046
- def test_update_settings_persists_leader_role(monkeypatch):
1047
- settings = Settings(
1048
- roles=[
1049
- RoleConfig(name="Conductor", system_prompt="Default leader role."),
1050
- RoleConfig(name="Reviewer", system_prompt="Review carefully."),
1051
- ]
1052
- )
1053
- saved: list[Settings] = []
1054
-
1055
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
1056
- monkeypatch.setattr(
1057
- "flowent.routes.settings.save_settings",
1058
- lambda current: saved.append(current),
1059
- )
1060
- monkeypatch.setattr(
1061
- "flowent.providers.gateway.gateway.invalidate_cache", lambda: None
1062
- )
1063
-
1064
- result = asyncio.run(
1065
- update_settings(
1066
- UpdateSettingsRequest(leader={"role_name": "Reviewer"}),
1067
- )
1068
- )
1069
-
1070
- assert settings.leader.role_name == "Reviewer"
1071
- assert result["settings"]["leader"] == {"role_name": "Reviewer"}
1072
- assert saved == [settings]
1073
-
1074
-
1075
- def test_update_settings_rejects_unknown_assistant_role(monkeypatch):
1076
- settings = Settings(
1077
- roles=[RoleConfig(name="Steward", system_prompt="Default assistant role.")]
1078
- )
1079
-
1080
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
1081
-
1082
- with pytest.raises(HTTPException) as excinfo:
1083
- asyncio.run(
1084
- update_settings(
1085
- UpdateSettingsRequest(assistant={"role_name": "Ghost"}),
1086
- )
1087
- )
1088
-
1089
- assert excinfo.value.status_code == 400
1090
- assert excinfo.value.detail == "Role 'Ghost' not found"
1091
-
1092
-
1093
- def test_update_settings_rejects_invalid_assistant_allow_network(monkeypatch):
1094
- settings = Settings(
1095
- roles=[RoleConfig(name="Steward", system_prompt="Default assistant role.")]
1096
- )
1097
-
1098
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
1099
-
1100
- with pytest.raises(HTTPException) as excinfo:
1101
- asyncio.run(
1102
- update_settings(
1103
- UpdateSettingsRequest(assistant={"allow_network": "yes"}),
1104
- )
1105
- )
1106
-
1107
- assert excinfo.value.status_code == 400
1108
- assert excinfo.value.detail == "assistant.allow_network must be a boolean"
1109
-
1110
-
1111
- def test_update_settings_rejects_removed_assistant_mcp_servers(monkeypatch):
1112
- settings = Settings(
1113
- roles=[RoleConfig(name="Steward", system_prompt="Default assistant role.")]
1114
- )
1115
-
1116
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
1117
-
1118
- with pytest.raises(HTTPException) as excinfo:
1119
- asyncio.run(
1120
- update_settings(
1121
- UpdateSettingsRequest(assistant={"mcp_servers": ["filesystem"]}),
1122
- )
1123
- )
1124
-
1125
- assert excinfo.value.status_code == 400
1126
- assert excinfo.value.detail == "Unknown assistant fields: mcp_servers"
1127
-
1128
-
1129
- def test_update_settings_rejects_unknown_leader_role(monkeypatch):
1130
- settings = Settings(roles=[RoleConfig(name="Conductor", system_prompt="Default.")])
1131
-
1132
- monkeypatch.setattr("flowent.routes.settings.get_settings", lambda: settings)
1133
-
1134
- with pytest.raises(HTTPException) as excinfo:
1135
- asyncio.run(
1136
- update_settings(
1137
- UpdateSettingsRequest(leader={"role_name": "Ghost"}),
1138
- )
1139
- )
1140
-
1141
- assert excinfo.value.status_code == 400
1142
- assert excinfo.value.detail == "Role 'Ghost' not found"