flowent 0.0.7 → 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 (386) hide show
  1. package/README.md +0 -3
  2. package/backend/README.md +0 -3
  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 +213 -3173
  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__/model_metadata.cpython-313.pyc +0 -0
  64. package/backend/src/flowent/__pycache__/network.cpython-313.pyc +0 -0
  65. package/backend/src/flowent/__pycache__/observability_service.cpython-313.pyc +0 -0
  66. package/backend/src/flowent/__pycache__/registry.cpython-313.pyc +0 -0
  67. package/backend/src/flowent/__pycache__/role_management.cpython-313.pyc +0 -0
  68. package/backend/src/flowent/__pycache__/runtime.cpython-313.pyc +0 -0
  69. package/backend/src/flowent/__pycache__/security.cpython-313.pyc +0 -0
  70. package/backend/src/flowent/__pycache__/settings.cpython-313.pyc +0 -0
  71. package/backend/src/flowent/__pycache__/settings_management.cpython-313.pyc +0 -0
  72. package/backend/src/flowent/__pycache__/state_db.cpython-313.pyc +0 -0
  73. package/backend/src/flowent/__pycache__/workspace_store.cpython-313.pyc +0 -0
  74. package/backend/src/flowent/access.py +0 -247
  75. package/backend/src/flowent/assistant_commands.py +0 -115
  76. package/backend/src/flowent/channels/__init__.py +0 -3
  77. package/backend/src/flowent/channels/__pycache__/__init__.cpython-313.pyc +0 -0
  78. package/backend/src/flowent/channels/__pycache__/telegram.cpython-313.pyc +0 -0
  79. package/backend/src/flowent/channels/telegram.py +0 -615
  80. package/backend/src/flowent/config.py +0 -14
  81. package/backend/src/flowent/dev.py +0 -3
  82. package/backend/src/flowent/events.py +0 -157
  83. package/backend/src/flowent/graph_runtime.py +0 -60
  84. package/backend/src/flowent/graph_service.py +0 -2401
  85. package/backend/src/flowent/image_assets.py +0 -356
  86. package/backend/src/flowent/model_metadata.py +0 -102
  87. package/backend/src/flowent/models/__init__.py +0 -125
  88. package/backend/src/flowent/models/__pycache__/__init__.cpython-313.pyc +0 -0
  89. package/backend/src/flowent/models/__pycache__/agent.cpython-313.pyc +0 -0
  90. package/backend/src/flowent/models/__pycache__/base.cpython-313.pyc +0 -0
  91. package/backend/src/flowent/models/__pycache__/blueprint.cpython-313.pyc +0 -0
  92. package/backend/src/flowent/models/__pycache__/content.cpython-313.pyc +0 -0
  93. package/backend/src/flowent/models/__pycache__/delta.cpython-313.pyc +0 -0
  94. package/backend/src/flowent/models/__pycache__/event.cpython-313.pyc +0 -0
  95. package/backend/src/flowent/models/__pycache__/graph.cpython-313.pyc +0 -0
  96. package/backend/src/flowent/models/__pycache__/history.cpython-313.pyc +0 -0
  97. package/backend/src/flowent/models/__pycache__/llm.cpython-313.pyc +0 -0
  98. package/backend/src/flowent/models/__pycache__/message.cpython-313.pyc +0 -0
  99. package/backend/src/flowent/models/__pycache__/tab.cpython-313.pyc +0 -0
  100. package/backend/src/flowent/models/__pycache__/todo.cpython-313.pyc +0 -0
  101. package/backend/src/flowent/models/agent.py +0 -34
  102. package/backend/src/flowent/models/base.py +0 -24
  103. package/backend/src/flowent/models/blueprint.py +0 -176
  104. package/backend/src/flowent/models/content.py +0 -164
  105. package/backend/src/flowent/models/delta.py +0 -44
  106. package/backend/src/flowent/models/event.py +0 -51
  107. package/backend/src/flowent/models/graph.py +0 -472
  108. package/backend/src/flowent/models/history.py +0 -272
  109. package/backend/src/flowent/models/llm.py +0 -62
  110. package/backend/src/flowent/models/message.py +0 -33
  111. package/backend/src/flowent/models/tab.py +0 -85
  112. package/backend/src/flowent/models/todo.py +0 -10
  113. package/backend/src/flowent/network.py +0 -146
  114. package/backend/src/flowent/observability_service.py +0 -218
  115. package/backend/src/flowent/prompts/__init__.py +0 -67
  116. package/backend/src/flowent/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  117. package/backend/src/flowent/prompts/__pycache__/common.cpython-313.pyc +0 -0
  118. package/backend/src/flowent/prompts/__pycache__/steward.cpython-313.pyc +0 -0
  119. package/backend/src/flowent/prompts/common.py +0 -250
  120. package/backend/src/flowent/prompts/steward.py +0 -64
  121. package/backend/src/flowent/providers/__init__.py +0 -23
  122. package/backend/src/flowent/providers/__pycache__/__init__.cpython-313.pyc +0 -0
  123. package/backend/src/flowent/providers/__pycache__/anthropic.cpython-313.pyc +0 -0
  124. package/backend/src/flowent/providers/__pycache__/base_url.cpython-313.pyc +0 -0
  125. package/backend/src/flowent/providers/__pycache__/configuration.cpython-313.pyc +0 -0
  126. package/backend/src/flowent/providers/__pycache__/content.cpython-313.pyc +0 -0
  127. package/backend/src/flowent/providers/__pycache__/errors.cpython-313.pyc +0 -0
  128. package/backend/src/flowent/providers/__pycache__/gateway.cpython-313.pyc +0 -0
  129. package/backend/src/flowent/providers/__pycache__/headers.cpython-313.pyc +0 -0
  130. package/backend/src/flowent/providers/__pycache__/management.cpython-313.pyc +0 -0
  131. package/backend/src/flowent/providers/__pycache__/openai.cpython-313.pyc +0 -0
  132. package/backend/src/flowent/providers/__pycache__/openai_responses.cpython-313.pyc +0 -0
  133. package/backend/src/flowent/providers/__pycache__/registry.cpython-313.pyc +0 -0
  134. package/backend/src/flowent/providers/__pycache__/sse.cpython-313.pyc +0 -0
  135. package/backend/src/flowent/providers/__pycache__/thinking.cpython-313.pyc +0 -0
  136. package/backend/src/flowent/providers/anthropic.py +0 -468
  137. package/backend/src/flowent/providers/base_url.py +0 -60
  138. package/backend/src/flowent/providers/configuration.py +0 -189
  139. package/backend/src/flowent/providers/content.py +0 -122
  140. package/backend/src/flowent/providers/errors.py +0 -223
  141. package/backend/src/flowent/providers/gateway.py +0 -169
  142. package/backend/src/flowent/providers/gemini.py +0 -447
  143. package/backend/src/flowent/providers/headers.py +0 -20
  144. package/backend/src/flowent/providers/management.py +0 -96
  145. package/backend/src/flowent/providers/ollama.py +0 -293
  146. package/backend/src/flowent/providers/openai.py +0 -422
  147. package/backend/src/flowent/providers/openai_responses.py +0 -655
  148. package/backend/src/flowent/providers/registry.py +0 -144
  149. package/backend/src/flowent/providers/sse.py +0 -31
  150. package/backend/src/flowent/providers/thinking.py +0 -79
  151. package/backend/src/flowent/registry.py +0 -73
  152. package/backend/src/flowent/role_management.py +0 -270
  153. package/backend/src/flowent/routes/__init__.py +0 -26
  154. package/backend/src/flowent/routes/__pycache__/__init__.cpython-313.pyc +0 -0
  155. package/backend/src/flowent/routes/__pycache__/access.cpython-313.pyc +0 -0
  156. package/backend/src/flowent/routes/__pycache__/assistant.cpython-313.pyc +0 -0
  157. package/backend/src/flowent/routes/__pycache__/image_assets.cpython-313.pyc +0 -0
  158. package/backend/src/flowent/routes/__pycache__/meta.cpython-313.pyc +0 -0
  159. package/backend/src/flowent/routes/__pycache__/nodes.cpython-313.pyc +0 -0
  160. package/backend/src/flowent/routes/__pycache__/prompts.cpython-313.pyc +0 -0
  161. package/backend/src/flowent/routes/__pycache__/providers_route.cpython-313.pyc +0 -0
  162. package/backend/src/flowent/routes/__pycache__/roles.cpython-313.pyc +0 -0
  163. package/backend/src/flowent/routes/__pycache__/settings.cpython-313.pyc +0 -0
  164. package/backend/src/flowent/routes/__pycache__/tabs.cpython-313.pyc +0 -0
  165. package/backend/src/flowent/routes/__pycache__/ws.cpython-313.pyc +0 -0
  166. package/backend/src/flowent/routes/access.py +0 -48
  167. package/backend/src/flowent/routes/assistant.py +0 -158
  168. package/backend/src/flowent/routes/image_assets.py +0 -33
  169. package/backend/src/flowent/routes/meta.py +0 -28
  170. package/backend/src/flowent/routes/nodes.py +0 -423
  171. package/backend/src/flowent/routes/prompts.py +0 -46
  172. package/backend/src/flowent/routes/providers_route.py +0 -365
  173. package/backend/src/flowent/routes/roles.py +0 -207
  174. package/backend/src/flowent/routes/settings.py +0 -379
  175. package/backend/src/flowent/routes/tabs.py +0 -298
  176. package/backend/src/flowent/routes/ws.py +0 -33
  177. package/backend/src/flowent/runtime.py +0 -160
  178. package/backend/src/flowent/security.py +0 -37
  179. package/backend/src/flowent/settings.py +0 -2112
  180. package/backend/src/flowent/settings_management.py +0 -394
  181. package/backend/src/flowent/state_db.py +0 -108
  182. package/backend/src/flowent/static/assets/AssistantPage-BW7XAd9I.js +0 -1
  183. package/backend/src/flowent/static/assets/ChannelsPage-tCJHgt6m.js +0 -1
  184. package/backend/src/flowent/static/assets/PageScaffold-f6g2l7XN.js +0 -1
  185. package/backend/src/flowent/static/assets/PromptsPage-C3Sxn2D7.js +0 -1
  186. package/backend/src/flowent/static/assets/ProvidersPage-BfmdXmNt.js +0 -3
  187. package/backend/src/flowent/static/assets/RolesPage-DET8wO4r.js +0 -1
  188. package/backend/src/flowent/static/assets/SettingsPage-D-g3deMm.js +0 -3
  189. package/backend/src/flowent/static/assets/ToolsPage-CDmtE2g4.js +0 -1
  190. package/backend/src/flowent/static/assets/WorkspacePage-AZsJ0sD0.js +0 -3
  191. package/backend/src/flowent/static/assets/WorkspacePanels-CteCjolX.js +0 -1
  192. package/backend/src/flowent/static/assets/alert-dialog-Duorp_S-.js +0 -1
  193. package/backend/src/flowent/static/assets/dialog-C3ixjGjN.js +0 -1
  194. package/backend/src/flowent/static/assets/elk-worker.min-C9JGDOE-.js +0 -6312
  195. package/backend/src/flowent/static/assets/graph-vendor-CHpVij2M.css +0 -1
  196. package/backend/src/flowent/static/assets/graph-vendor-DRq_-6fV.js +0 -7
  197. package/backend/src/flowent/static/assets/index--o_0fv0N.css +0 -1
  198. package/backend/src/flowent/static/assets/index-C9HuekJm.js +0 -10
  199. package/backend/src/flowent/static/assets/layout.worker-jMHqAFbP.js +0 -24
  200. package/backend/src/flowent/static/assets/markdown-vendor-C9RtvaJh.js +0 -29
  201. package/backend/src/flowent/static/assets/modelParams-DmnF2hwR.js +0 -1
  202. package/backend/src/flowent/static/assets/providerTypes-DT3Ahwl_.js +0 -1
  203. package/backend/src/flowent/static/assets/react-vendor-mEs_JJxa.js +0 -9
  204. package/backend/src/flowent/static/assets/roles-CuRT_chR.js +0 -1
  205. package/backend/src/flowent/static/assets/rolldown-runtime-BYbx6iT9.js +0 -1
  206. package/backend/src/flowent/static/assets/select-DCfeNu-F.js +0 -1
  207. package/backend/src/flowent/static/assets/surface-pWwG5ogx.js +0 -1
  208. package/backend/src/flowent/static/assets/ui-vendor-C5pJa8N7.js +0 -51
  209. package/backend/src/flowent/static/assets/useAppRoute-FgSHBKhV.js +0 -1
  210. package/backend/src/flowent/static/favicon.svg +0 -4
  211. package/backend/src/flowent/tools/__init__.py +0 -176
  212. package/backend/src/flowent/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  213. package/backend/src/flowent/tools/__pycache__/connect.cpython-313.pyc +0 -0
  214. package/backend/src/flowent/tools/__pycache__/contacts.cpython-313.pyc +0 -0
  215. package/backend/src/flowent/tools/__pycache__/create_agent.cpython-313.pyc +0 -0
  216. package/backend/src/flowent/tools/__pycache__/create_tab.cpython-313.pyc +0 -0
  217. package/backend/src/flowent/tools/__pycache__/delete_tab.cpython-313.pyc +0 -0
  218. package/backend/src/flowent/tools/__pycache__/edit.cpython-313.pyc +0 -0
  219. package/backend/src/flowent/tools/__pycache__/exec.cpython-313.pyc +0 -0
  220. package/backend/src/flowent/tools/__pycache__/fetch.cpython-313.pyc +0 -0
  221. package/backend/src/flowent/tools/__pycache__/idle.cpython-313.pyc +0 -0
  222. package/backend/src/flowent/tools/__pycache__/list_roles.cpython-313.pyc +0 -0
  223. package/backend/src/flowent/tools/__pycache__/list_tabs.cpython-313.pyc +0 -0
  224. package/backend/src/flowent/tools/__pycache__/list_tools.cpython-313.pyc +0 -0
  225. package/backend/src/flowent/tools/__pycache__/manage_prompts.cpython-313.pyc +0 -0
  226. package/backend/src/flowent/tools/__pycache__/manage_providers.cpython-313.pyc +0 -0
  227. package/backend/src/flowent/tools/__pycache__/manage_roles.cpython-313.pyc +0 -0
  228. package/backend/src/flowent/tools/__pycache__/manage_settings.cpython-313.pyc +0 -0
  229. package/backend/src/flowent/tools/__pycache__/read.cpython-313.pyc +0 -0
  230. package/backend/src/flowent/tools/__pycache__/send.cpython-313.pyc +0 -0
  231. package/backend/src/flowent/tools/__pycache__/set_permissions.cpython-313.pyc +0 -0
  232. package/backend/src/flowent/tools/__pycache__/sleep.cpython-313.pyc +0 -0
  233. package/backend/src/flowent/tools/__pycache__/todo.cpython-313.pyc +0 -0
  234. package/backend/src/flowent/tools/connect.py +0 -100
  235. package/backend/src/flowent/tools/contacts.py +0 -22
  236. package/backend/src/flowent/tools/create_agent.py +0 -191
  237. package/backend/src/flowent/tools/create_tab.py +0 -61
  238. package/backend/src/flowent/tools/delete_tab.py +0 -39
  239. package/backend/src/flowent/tools/edit.py +0 -142
  240. package/backend/src/flowent/tools/exec.py +0 -118
  241. package/backend/src/flowent/tools/fetch.py +0 -85
  242. package/backend/src/flowent/tools/idle.py +0 -27
  243. package/backend/src/flowent/tools/list_roles.py +0 -68
  244. package/backend/src/flowent/tools/list_tabs.py +0 -100
  245. package/backend/src/flowent/tools/list_tools.py +0 -28
  246. package/backend/src/flowent/tools/manage_prompts.py +0 -102
  247. package/backend/src/flowent/tools/manage_providers.py +0 -220
  248. package/backend/src/flowent/tools/manage_roles.py +0 -275
  249. package/backend/src/flowent/tools/manage_settings.py +0 -326
  250. package/backend/src/flowent/tools/read.py +0 -152
  251. package/backend/src/flowent/tools/send.py +0 -68
  252. package/backend/src/flowent/tools/set_permissions.py +0 -99
  253. package/backend/src/flowent/tools/sleep.py +0 -41
  254. package/backend/src/flowent/tools/todo.py +0 -51
  255. package/backend/src/flowent/workspace_store.py +0 -479
  256. package/backend/tests/__init__.py +0 -0
  257. package/backend/tests/__pycache__/__init__.cpython-313.pyc +0 -0
  258. package/backend/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  259. package/backend/tests/conftest.py +0 -6
  260. package/backend/tests/integration/api/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  261. package/backend/tests/integration/api/__pycache__/test_access_api.cpython-313-pytest-9.0.3.pyc +0 -0
  262. package/backend/tests/integration/api/__pycache__/test_assistant_api.cpython-313-pytest-9.0.3.pyc +0 -0
  263. package/backend/tests/integration/api/__pycache__/test_frontend_mounting.cpython-313-pytest-9.0.3.pyc +0 -0
  264. package/backend/tests/integration/api/__pycache__/test_meta_api.cpython-313-pytest-9.0.3.pyc +0 -0
  265. package/backend/tests/integration/api/__pycache__/test_nodes_api.cpython-313-pytest-9.0.3.pyc +0 -0
  266. package/backend/tests/integration/api/__pycache__/test_prompts_api.cpython-313-pytest-9.0.3.pyc +0 -0
  267. package/backend/tests/integration/api/__pycache__/test_roles_api.cpython-313-pytest-9.0.3.pyc +0 -0
  268. package/backend/tests/integration/api/__pycache__/test_tabs_api.cpython-313-pytest-9.0.3.pyc +0 -0
  269. package/backend/tests/integration/api/conftest.py +0 -29
  270. package/backend/tests/integration/api/test_access_api.py +0 -182
  271. package/backend/tests/integration/api/test_assistant_api.py +0 -422
  272. package/backend/tests/integration/api/test_frontend_mounting.py +0 -61
  273. package/backend/tests/integration/api/test_meta_api.py +0 -32
  274. package/backend/tests/integration/api/test_nodes_api.py +0 -787
  275. package/backend/tests/integration/api/test_prompts_api.py +0 -47
  276. package/backend/tests/integration/api/test_roles_api.py +0 -228
  277. package/backend/tests/integration/api/test_tabs_api.py +0 -688
  278. package/backend/tests/unit/__pycache__/test_access.cpython-313-pytest-9.0.3.pyc +0 -0
  279. package/backend/tests/unit/__pycache__/test_cli.cpython-313-pytest-9.0.3.pyc +0 -0
  280. package/backend/tests/unit/__pycache__/test_graph_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  281. package/backend/tests/unit/__pycache__/test_network.cpython-313-pytest-9.0.3.pyc +0 -0
  282. package/backend/tests/unit/__pycache__/test_state_sqlite_storage.cpython-313-pytest-9.0.3.pyc +0 -0
  283. package/backend/tests/unit/__pycache__/test_workspace_store.cpython-313-pytest-9.0.3.pyc +0 -0
  284. package/backend/tests/unit/agent/__pycache__/test_agent_public_api.cpython-313-pytest-9.0.3.pyc +0 -0
  285. package/backend/tests/unit/agent/__pycache__/test_agent_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  286. package/backend/tests/unit/agent/test_agent_public_api.py +0 -822
  287. package/backend/tests/unit/agent/test_agent_runtime.py +0 -3088
  288. package/backend/tests/unit/channels/__pycache__/test_telegram_channel.cpython-313-pytest-9.0.3.pyc +0 -0
  289. package/backend/tests/unit/channels/test_telegram_channel.py +0 -552
  290. package/backend/tests/unit/logging/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
  291. package/backend/tests/unit/logging/test_logging.py +0 -132
  292. package/backend/tests/unit/prompts/__pycache__/test_prompts.cpython-313-pytest-9.0.3.pyc +0 -0
  293. package/backend/tests/unit/prompts/test_prompts.py +0 -570
  294. package/backend/tests/unit/providers/__pycache__/test_anthropic_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  295. package/backend/tests/unit/providers/__pycache__/test_errors.cpython-313-pytest-9.0.3.pyc +0 -0
  296. package/backend/tests/unit/providers/__pycache__/test_extract_delta_parts.cpython-313-pytest-9.0.3.pyc +0 -0
  297. package/backend/tests/unit/providers/__pycache__/test_openai_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  298. package/backend/tests/unit/providers/__pycache__/test_openai_responses.cpython-313-pytest-9.0.3.pyc +0 -0
  299. package/backend/tests/unit/providers/__pycache__/test_provider_gateway.cpython-313-pytest-9.0.3.pyc +0 -0
  300. package/backend/tests/unit/providers/__pycache__/test_think_tag_parser.cpython-313-pytest-9.0.3.pyc +0 -0
  301. package/backend/tests/unit/providers/test_anthropic_provider.py +0 -185
  302. package/backend/tests/unit/providers/test_errors.py +0 -68
  303. package/backend/tests/unit/providers/test_extract_delta_parts.py +0 -22
  304. package/backend/tests/unit/providers/test_openai_provider.py +0 -139
  305. package/backend/tests/unit/providers/test_openai_responses.py +0 -402
  306. package/backend/tests/unit/providers/test_provider_gateway.py +0 -359
  307. package/backend/tests/unit/providers/test_think_tag_parser.py +0 -36
  308. package/backend/tests/unit/routes/__pycache__/test_prompts_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  309. package/backend/tests/unit/routes/__pycache__/test_providers_route.cpython-313-pytest-9.0.3.pyc +0 -0
  310. package/backend/tests/unit/routes/__pycache__/test_roles_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  311. package/backend/tests/unit/routes/__pycache__/test_settings_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  312. package/backend/tests/unit/routes/test_prompts_routes.py +0 -82
  313. package/backend/tests/unit/routes/test_providers_route.py +0 -370
  314. package/backend/tests/unit/routes/test_roles_routes.py +0 -539
  315. package/backend/tests/unit/routes/test_settings_routes.py +0 -1123
  316. package/backend/tests/unit/runtime/__pycache__/test_bootstrap_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  317. package/backend/tests/unit/runtime/test_bootstrap_runtime.py +0 -1002
  318. package/backend/tests/unit/sandbox/__pycache__/test_sandbox_tools.cpython-313-pytest-9.0.3.pyc +0 -0
  319. package/backend/tests/unit/sandbox/test_sandbox_tools.py +0 -78
  320. package/backend/tests/unit/security/__pycache__/test_security.cpython-313-pytest-9.0.3.pyc +0 -0
  321. package/backend/tests/unit/security/test_security.py +0 -124
  322. package/backend/tests/unit/settings/__pycache__/test_settings_roles.cpython-313-pytest-9.0.3.pyc +0 -0
  323. package/backend/tests/unit/settings/test_settings_roles.py +0 -703
  324. package/backend/tests/unit/test_access.py +0 -45
  325. package/backend/tests/unit/test_cli.py +0 -102
  326. package/backend/tests/unit/test_graph_runtime.py +0 -72
  327. package/backend/tests/unit/test_network.py +0 -51
  328. package/backend/tests/unit/test_state_sqlite_storage.py +0 -87
  329. package/backend/tests/unit/test_workspace_store.py +0 -228
  330. package/backend/tests/unit/tools/__pycache__/test_connect_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  331. package/backend/tests/unit/tools/__pycache__/test_create_agent_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  332. package/backend/tests/unit/tools/__pycache__/test_delete_tab_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  333. package/backend/tests/unit/tools/__pycache__/test_edit_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  334. package/backend/tests/unit/tools/__pycache__/test_exec_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  335. package/backend/tests/unit/tools/__pycache__/test_fetch_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  336. package/backend/tests/unit/tools/__pycache__/test_manage_prompts_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  337. package/backend/tests/unit/tools/__pycache__/test_manage_providers_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  338. package/backend/tests/unit/tools/__pycache__/test_manage_roles_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  339. package/backend/tests/unit/tools/__pycache__/test_manage_settings_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  340. package/backend/tests/unit/tools/__pycache__/test_read_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  341. package/backend/tests/unit/tools/__pycache__/test_set_permissions_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  342. package/backend/tests/unit/tools/__pycache__/test_todo_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  343. package/backend/tests/unit/tools/__pycache__/test_tool_registry.cpython-313-pytest-9.0.3.pyc +0 -0
  344. package/backend/tests/unit/tools/test_connect_tool.py +0 -228
  345. package/backend/tests/unit/tools/test_create_agent_tool.py +0 -404
  346. package/backend/tests/unit/tools/test_delete_tab_tool.py +0 -116
  347. package/backend/tests/unit/tools/test_edit_tool.py +0 -115
  348. package/backend/tests/unit/tools/test_exec_tool.py +0 -81
  349. package/backend/tests/unit/tools/test_fetch_tool.py +0 -65
  350. package/backend/tests/unit/tools/test_manage_prompts_tool.py +0 -92
  351. package/backend/tests/unit/tools/test_manage_providers_tool.py +0 -460
  352. package/backend/tests/unit/tools/test_manage_roles_tool.py +0 -411
  353. package/backend/tests/unit/tools/test_manage_settings_tool.py +0 -611
  354. package/backend/tests/unit/tools/test_read_tool.py +0 -33
  355. package/backend/tests/unit/tools/test_set_permissions_tool.py +0 -595
  356. package/backend/tests/unit/tools/test_todo_tool.py +0 -37
  357. package/backend/tests/unit/tools/test_tool_registry.py +0 -199
  358. package/dist/frontend/assets/AssistantPage-BW7XAd9I.js +0 -1
  359. package/dist/frontend/assets/ChannelsPage-tCJHgt6m.js +0 -1
  360. package/dist/frontend/assets/PageScaffold-f6g2l7XN.js +0 -1
  361. package/dist/frontend/assets/PromptsPage-C3Sxn2D7.js +0 -1
  362. package/dist/frontend/assets/ProvidersPage-BfmdXmNt.js +0 -3
  363. package/dist/frontend/assets/RolesPage-DET8wO4r.js +0 -1
  364. package/dist/frontend/assets/SettingsPage-D-g3deMm.js +0 -3
  365. package/dist/frontend/assets/ToolsPage-CDmtE2g4.js +0 -1
  366. package/dist/frontend/assets/WorkspacePage-AZsJ0sD0.js +0 -3
  367. package/dist/frontend/assets/WorkspacePanels-CteCjolX.js +0 -1
  368. package/dist/frontend/assets/alert-dialog-Duorp_S-.js +0 -1
  369. package/dist/frontend/assets/dialog-C3ixjGjN.js +0 -1
  370. package/dist/frontend/assets/elk-worker.min-C9JGDOE-.js +0 -6312
  371. package/dist/frontend/assets/graph-vendor-CHpVij2M.css +0 -1
  372. package/dist/frontend/assets/graph-vendor-DRq_-6fV.js +0 -7
  373. package/dist/frontend/assets/index--o_0fv0N.css +0 -1
  374. package/dist/frontend/assets/index-C9HuekJm.js +0 -10
  375. package/dist/frontend/assets/layout.worker-jMHqAFbP.js +0 -24
  376. package/dist/frontend/assets/markdown-vendor-C9RtvaJh.js +0 -29
  377. package/dist/frontend/assets/modelParams-DmnF2hwR.js +0 -1
  378. package/dist/frontend/assets/providerTypes-DT3Ahwl_.js +0 -1
  379. package/dist/frontend/assets/react-vendor-mEs_JJxa.js +0 -9
  380. package/dist/frontend/assets/roles-CuRT_chR.js +0 -1
  381. package/dist/frontend/assets/rolldown-runtime-BYbx6iT9.js +0 -1
  382. package/dist/frontend/assets/select-DCfeNu-F.js +0 -1
  383. package/dist/frontend/assets/surface-pWwG5ogx.js +0 -1
  384. package/dist/frontend/assets/ui-vendor-C5pJa8N7.js +0 -51
  385. package/dist/frontend/assets/useAppRoute-FgSHBKhV.js +0 -1
  386. package/dist/frontend/favicon.svg +0 -4
@@ -1,552 +0,0 @@
1
- import asyncio
2
-
3
- import pytest
4
-
5
- from flowent.channels.telegram import (
6
- IMAGE_INPUT_UNSUPPORTED_MESSAGE,
7
- IMAGE_OUTPUT_UNSUPPORTED_MESSAGE,
8
- PRIVATE_ONLY_MESSAGE,
9
- TelegramChannel,
10
- )
11
- from flowent.models import Event, EventType
12
- from flowent.settings import (
13
- Settings,
14
- TelegramApprovedChat,
15
- TelegramPendingChat,
16
- TelegramSettings,
17
- )
18
-
19
-
20
- class DummyAssistant:
21
- def __init__(self) -> None:
22
- self.uuid = "assistant-1"
23
- self.messages: list[object] = []
24
-
25
- def enqueue_message(self, message) -> None:
26
- self.messages.append(message)
27
-
28
- def supports_input_image(self) -> bool:
29
- return True
30
-
31
-
32
- class _FakeTelegramResponse:
33
- def __init__(self, status_code: int, payload: dict[str, object]) -> None:
34
- self.status_code = status_code
35
- self._payload = payload
36
-
37
- def json(self) -> dict[str, object]:
38
- return self._payload
39
-
40
-
41
- class _FakeAsyncSession:
42
- def __init__(self, response: _FakeTelegramResponse) -> None:
43
- self._response = response
44
- self.requests: list[tuple[str, dict[str, object]]] = []
45
-
46
- async def __aenter__(self) -> "_FakeAsyncSession":
47
- return self
48
-
49
- async def __aexit__(self, exc_type, exc, tb) -> bool:
50
- return False
51
-
52
- async def post(self, url: str, data: dict[str, object]):
53
- self.requests.append((url, data))
54
- return self._response
55
-
56
-
57
- def test_telegram_channel_replies_with_private_only_message_for_group_chat(
58
- monkeypatch,
59
- ):
60
- settings = Settings(telegram=TelegramSettings(bot_token="123456:ABCDE"))
61
- sent_messages: list[tuple[int, str, bool]] = []
62
-
63
- monkeypatch.setattr("flowent.channels.telegram.get_settings", lambda: settings)
64
-
65
- channel = TelegramChannel()
66
-
67
- async def fake_send_message(chat_id: int, text: str, *, markdown: bool) -> int:
68
- sent_messages.append((chat_id, text, markdown))
69
- return 1
70
-
71
- monkeypatch.setattr(channel, "_send_message", fake_send_message)
72
-
73
- asyncio.run(
74
- channel._process_update(
75
- {
76
- "message": {
77
- "from": {"id": 2002},
78
- "chat": {"id": -3003, "type": "group"},
79
- "text": "hello",
80
- }
81
- }
82
- )
83
- )
84
-
85
- assert sent_messages == [(-3003, PRIVATE_ONLY_MESSAGE, False)]
86
-
87
-
88
- def test_telegram_channel_tracks_pending_private_chat_and_replies_with_chat_id(
89
- monkeypatch,
90
- ):
91
- settings = Settings(telegram=TelegramSettings(bot_token="123456:ABCDE"))
92
- saved: list[Settings] = []
93
- sent_messages: list[tuple[int, str, bool]] = []
94
-
95
- monkeypatch.setattr("flowent.channels.telegram.get_settings", lambda: settings)
96
- monkeypatch.setattr(
97
- "flowent.channels.telegram.save_settings",
98
- lambda current: saved.append(current),
99
- )
100
-
101
- channel = TelegramChannel()
102
-
103
- async def fake_send_message(chat_id: int, text: str, *, markdown: bool) -> int:
104
- sent_messages.append((chat_id, text, markdown))
105
- return 1
106
-
107
- monkeypatch.setattr(channel, "_send_message", fake_send_message)
108
-
109
- asyncio.run(
110
- channel._process_update(
111
- {
112
- "message": {
113
- "from": {
114
- "id": 2002,
115
- "username": "alice",
116
- "first_name": "Alice",
117
- },
118
- "chat": {"id": 3003, "type": "private"},
119
- "text": "hello",
120
- }
121
- }
122
- )
123
- )
124
-
125
- assert len(saved) == 1
126
- assert settings.telegram.pending_chats == [
127
- TelegramPendingChat(
128
- chat_id=3003,
129
- username="alice",
130
- display_name="Alice",
131
- first_seen_at=settings.telegram.pending_chats[0].first_seen_at,
132
- last_seen_at=settings.telegram.pending_chats[0].last_seen_at,
133
- )
134
- ]
135
- assert sent_messages == [
136
- (
137
- 3003,
138
- "⏳ This chat is pending approval in Flowent.\nChat ID: `3003`",
139
- True,
140
- )
141
- ]
142
-
143
-
144
- def test_telegram_channel_delivers_messages_from_approved_private_chat(monkeypatch):
145
- settings = Settings(
146
- telegram=TelegramSettings(
147
- bot_token="123456:ABCDE",
148
- approved_chats=[
149
- TelegramApprovedChat(
150
- chat_id=4004,
151
- username="alice",
152
- display_name="Alice",
153
- approved_at=1.0,
154
- )
155
- ],
156
- )
157
- )
158
- assistant = DummyAssistant()
159
-
160
- monkeypatch.setattr("flowent.channels.telegram.get_settings", lambda: settings)
161
- monkeypatch.setattr(
162
- "flowent.channels.telegram.registry.get_assistant",
163
- lambda: assistant,
164
- )
165
-
166
- channel = TelegramChannel()
167
-
168
- asyncio.run(
169
- channel._process_update(
170
- {
171
- "message": {
172
- "from": {"id": 1001},
173
- "chat": {"id": 4004, "type": "private"},
174
- "text": "check status",
175
- }
176
- }
177
- )
178
- )
179
-
180
- assert len(assistant.messages) == 1
181
- assert assistant.messages[0].from_id == "human"
182
- assert assistant.messages[0].to_id == assistant.uuid
183
- assert assistant.messages[0].content == "check status"
184
-
185
-
186
- def test_telegram_channel_rejects_image_input_from_approved_private_chat(monkeypatch):
187
- settings = Settings(
188
- telegram=TelegramSettings(
189
- bot_token="123456:ABCDE",
190
- approved_chats=[
191
- TelegramApprovedChat(
192
- chat_id=4004,
193
- username="alice",
194
- display_name="Alice",
195
- approved_at=1.0,
196
- )
197
- ],
198
- )
199
- )
200
- assistant = DummyAssistant()
201
- sent_messages: list[tuple[int, str, bool]] = []
202
-
203
- monkeypatch.setattr("flowent.channels.telegram.get_settings", lambda: settings)
204
- monkeypatch.setattr(
205
- "flowent.channels.telegram.registry.get_assistant",
206
- lambda: assistant,
207
- )
208
-
209
- channel = TelegramChannel()
210
-
211
- async def fake_send_message(chat_id: int, text: str, *, markdown: bool) -> int:
212
- sent_messages.append((chat_id, text, markdown))
213
- return 1
214
-
215
- monkeypatch.setattr(channel, "_send_message", fake_send_message)
216
-
217
- asyncio.run(
218
- channel._process_update(
219
- {
220
- "message": {
221
- "from": {"id": 1001},
222
- "chat": {"id": 4004, "type": "private"},
223
- "caption": "look at this",
224
- "photo": [{"file_id": "photo-1"}],
225
- }
226
- }
227
- )
228
- )
229
-
230
- assert assistant.messages == []
231
- assert sent_messages == [(4004, IMAGE_INPUT_UNSUPPORTED_MESSAGE, False)]
232
-
233
-
234
- def test_telegram_channel_sends_typing_while_running_before_first_visible_text(
235
- monkeypatch,
236
- ):
237
- settings = Settings(
238
- telegram=TelegramSettings(
239
- bot_token="123456:ABCDE",
240
- approved_chats=[
241
- TelegramApprovedChat(
242
- chat_id=4004,
243
- username="alice",
244
- display_name="Alice",
245
- approved_at=1.0,
246
- )
247
- ],
248
- )
249
- )
250
- assistant = DummyAssistant()
251
- sent_actions: list[tuple[int, str]] = []
252
-
253
- monkeypatch.setattr("flowent.channels.telegram.get_settings", lambda: settings)
254
- monkeypatch.setattr(
255
- "flowent.channels.telegram.registry.get_assistant",
256
- lambda: assistant,
257
- )
258
-
259
- channel = TelegramChannel()
260
-
261
- async def fake_send_chat_action(chat_id: int, *, action: str) -> None:
262
- sent_actions.append((chat_id, action))
263
-
264
- async def fake_sleep(_: float) -> None:
265
- channel._assistant_running = False
266
-
267
- monkeypatch.setattr(channel, "_send_chat_action", fake_send_chat_action)
268
- monkeypatch.setattr("flowent.channels.telegram.asyncio.sleep", fake_sleep)
269
-
270
- async def run_test() -> None:
271
- await channel._process_event(
272
- Event(
273
- type=EventType.NODE_STATE_CHANGED,
274
- agent_id=assistant.uuid,
275
- data={"new_state": "running"},
276
- )
277
- )
278
- assert channel._typing_task is not None
279
- await channel._typing_task
280
-
281
- asyncio.run(run_test())
282
-
283
- assert sent_actions == [(4004, "typing")]
284
-
285
-
286
- def test_telegram_channel_stops_typing_after_first_visible_text(monkeypatch):
287
- settings = Settings(
288
- telegram=TelegramSettings(
289
- bot_token="123456:ABCDE",
290
- approved_chats=[
291
- TelegramApprovedChat(
292
- chat_id=4004,
293
- username="alice",
294
- display_name="Alice",
295
- approved_at=1.0,
296
- )
297
- ],
298
- )
299
- )
300
- sent_messages: list[tuple[int, str, bool]] = []
301
- sent_actions: list[tuple[int, str]] = []
302
-
303
- monkeypatch.setattr("flowent.channels.telegram.get_settings", lambda: settings)
304
-
305
- channel = TelegramChannel()
306
-
307
- async def fake_send_message(chat_id: int, text: str, *, markdown: bool) -> int:
308
- sent_messages.append((chat_id, text, markdown))
309
- return 7
310
-
311
- async def fake_send_chat_action(chat_id: int, *, action: str) -> None:
312
- sent_actions.append((chat_id, action))
313
-
314
- monkeypatch.setattr(channel, "_send_message", fake_send_message)
315
- monkeypatch.setattr(channel, "_send_chat_action", fake_send_chat_action)
316
-
317
- async def run_test() -> None:
318
- channel._assistant_running = True
319
- await channel._handle_assistant_content("hello")
320
- await channel._send_typing_feedback_once()
321
-
322
- asyncio.run(run_test())
323
-
324
- assert sent_messages == [(4004, "hello", False)]
325
- assert sent_actions == []
326
- assert channel._stream_message_ids == {4004: 7}
327
-
328
-
329
- def test_telegram_channel_sends_explicit_notice_for_image_output(monkeypatch):
330
- settings = Settings(
331
- telegram=TelegramSettings(
332
- bot_token="123456:ABCDE",
333
- approved_chats=[
334
- TelegramApprovedChat(
335
- chat_id=4004,
336
- username="alice",
337
- display_name="Alice",
338
- approved_at=1.0,
339
- )
340
- ],
341
- )
342
- )
343
- assistant = DummyAssistant()
344
- sent_messages: list[tuple[int, str, bool]] = []
345
-
346
- monkeypatch.setattr("flowent.channels.telegram.get_settings", lambda: settings)
347
- monkeypatch.setattr(
348
- "flowent.channels.telegram.registry.get_assistant",
349
- lambda: assistant,
350
- )
351
-
352
- channel = TelegramChannel()
353
-
354
- async def fake_send_message(chat_id: int, text: str, *, markdown: bool) -> int:
355
- sent_messages.append((chat_id, text, markdown))
356
- return 1
357
-
358
- monkeypatch.setattr(channel, "_send_message", fake_send_message)
359
-
360
- asyncio.run(
361
- channel._process_event(
362
- Event(
363
- type=EventType.HISTORY_ENTRY_ADDED,
364
- agent_id=assistant.uuid,
365
- data={
366
- "type": "AssistantText",
367
- "parts": [{"type": "image", "asset_id": "asset-1"}],
368
- },
369
- )
370
- )
371
- )
372
-
373
- assert sent_messages == [(4004, IMAGE_OUTPUT_UNSUPPORTED_MESSAGE, False)]
374
-
375
-
376
- def test_telegram_channel_ignores_tool_progress_events(monkeypatch):
377
- settings = Settings(
378
- telegram=TelegramSettings(
379
- bot_token="123456:ABCDE",
380
- approved_chats=[
381
- TelegramApprovedChat(
382
- chat_id=4004,
383
- username="alice",
384
- display_name="Alice",
385
- approved_at=1.0,
386
- )
387
- ],
388
- )
389
- )
390
- assistant = DummyAssistant()
391
- broadcast_messages: list[str] = []
392
-
393
- monkeypatch.setattr("flowent.channels.telegram.get_settings", lambda: settings)
394
- monkeypatch.setattr(
395
- "flowent.channels.telegram.registry.get_assistant",
396
- lambda: assistant,
397
- )
398
-
399
- channel = TelegramChannel()
400
-
401
- async def fake_send_message(chat_id: int, text: str, *, markdown: bool) -> int:
402
- broadcast_messages.append(text)
403
- return 1
404
-
405
- monkeypatch.setattr(channel, "_send_message", fake_send_message)
406
-
407
- asyncio.run(
408
- channel._process_event(
409
- Event(
410
- type=EventType.TOOL_CALLED,
411
- agent_id=assistant.uuid,
412
- data={"tool": "read"},
413
- )
414
- )
415
- )
416
-
417
- assert broadcast_messages == []
418
-
419
-
420
- @pytest.mark.parametrize("end_state", ["idle", "error", "terminated"])
421
- def test_telegram_channel_stops_without_placeholder_when_running_ends_no_content(
422
- monkeypatch,
423
- end_state,
424
- ):
425
- settings = Settings(
426
- telegram=TelegramSettings(
427
- bot_token="123456:ABCDE",
428
- approved_chats=[
429
- TelegramApprovedChat(
430
- chat_id=4004,
431
- username="alice",
432
- display_name="Alice",
433
- approved_at=1.0,
434
- )
435
- ],
436
- )
437
- )
438
- assistant = DummyAssistant()
439
- sent_messages: list[tuple[int, str, bool]] = []
440
- sent_actions: list[tuple[int, str]] = []
441
-
442
- monkeypatch.setattr("flowent.channels.telegram.get_settings", lambda: settings)
443
- monkeypatch.setattr(
444
- "flowent.channels.telegram.registry.get_assistant",
445
- lambda: assistant,
446
- )
447
-
448
- channel = TelegramChannel()
449
-
450
- async def fake_send_message(chat_id: int, text: str, *, markdown: bool) -> int:
451
- sent_messages.append((chat_id, text, markdown))
452
- return 1
453
-
454
- async def fake_send_chat_action(chat_id: int, *, action: str) -> None:
455
- sent_actions.append((chat_id, action))
456
-
457
- async def fake_sleep(_: float) -> None:
458
- await channel._process_event(
459
- Event(
460
- type=EventType.NODE_STATE_CHANGED,
461
- agent_id=assistant.uuid,
462
- data={"new_state": end_state},
463
- )
464
- )
465
-
466
- monkeypatch.setattr(channel, "_send_message", fake_send_message)
467
- monkeypatch.setattr(channel, "_send_chat_action", fake_send_chat_action)
468
- monkeypatch.setattr("flowent.channels.telegram.asyncio.sleep", fake_sleep)
469
-
470
- async def run_test() -> None:
471
- await channel._process_event(
472
- Event(
473
- type=EventType.NODE_STATE_CHANGED,
474
- agent_id=assistant.uuid,
475
- data={"new_state": "running"},
476
- )
477
- )
478
- assert channel._typing_task is not None
479
- await channel._typing_task
480
-
481
- asyncio.run(run_test())
482
-
483
- assert sent_actions == [(4004, "typing")]
484
- assert sent_messages == []
485
- assert channel._stream_message_ids == {}
486
-
487
-
488
- def test_telegram_channel_stop_cancels_typing_task_on_app_loop(monkeypatch):
489
- cancelled: list[str] = []
490
-
491
- class FakeTask:
492
- def cancel(self) -> None:
493
- cancelled.append("typing")
494
-
495
- class FakeLoop:
496
- def __init__(self) -> None:
497
- self.calls: list[str] = []
498
-
499
- def is_closed(self) -> bool:
500
- return False
501
-
502
- def call_soon_threadsafe(self, callback) -> None:
503
- self.calls.append(callback.__name__)
504
- callback()
505
-
506
- monkeypatch.setattr(
507
- "flowent.channels.telegram.event_bus.unsubscribe", lambda _: None
508
- )
509
-
510
- channel = TelegramChannel()
511
- app_loop = FakeLoop()
512
- polling_loop = FakeLoop()
513
- channel._app_loop = app_loop
514
- channel._thread_loop = polling_loop
515
- channel._typing_task = FakeTask()
516
-
517
- channel.stop()
518
-
519
- assert app_loop.calls == ["cancel"]
520
- assert polling_loop.calls == []
521
- assert cancelled == ["typing"]
522
-
523
-
524
- def test_telegram_channel_call_api_uses_shared_async_transport(monkeypatch):
525
- settings = Settings(telegram=TelegramSettings(bot_token="123456:ABCDE"))
526
- fake_session = _FakeAsyncSession(
527
- _FakeTelegramResponse(200, {"ok": True, "result": {"message_id": 1}})
528
- )
529
-
530
- monkeypatch.setattr("flowent.channels.telegram.get_settings", lambda: settings)
531
- monkeypatch.setattr(
532
- "flowent.channels.telegram.create_async_http_session",
533
- lambda timeout: fake_session,
534
- )
535
-
536
- channel = TelegramChannel()
537
-
538
- result = asyncio.run(
539
- channel._call_api(
540
- "sendMessage",
541
- {"chat_id": 3003, "text": "hello"},
542
- parse_mode="Markdown",
543
- )
544
- )
545
-
546
- assert result == {"message_id": 1}
547
- assert fake_session.requests == [
548
- (
549
- "https://api.telegram.org/bot123456:ABCDE/sendMessage",
550
- {"chat_id": 3003, "text": "hello", "parse_mode": "Markdown"},
551
- )
552
- ]
@@ -1,132 +0,0 @@
1
- import logging
2
- import re
3
- from collections.abc import Iterator
4
- from pathlib import Path
5
-
6
- import pytest
7
- from loguru import logger
8
-
9
- from flowent.logging import (
10
- HealthcheckAccessFilter,
11
- configure_uvicorn_access_logging,
12
- detect_runtime_mode,
13
- setup_logging,
14
- )
15
-
16
-
17
- @pytest.fixture(autouse=True)
18
- def cleanup_loguru() -> Iterator[None]:
19
- yield
20
- logger.remove()
21
-
22
-
23
- def test_detect_runtime_mode_from_fastapi_command() -> None:
24
- assert detect_runtime_mode(["/venv/bin/fastapi", "dev", "app/main.py"]) == "dev"
25
- assert detect_runtime_mode(["/venv/bin/fastapi", "run", "app/main.py"]) == "release"
26
-
27
-
28
- def test_detect_runtime_mode_from_reload_flag() -> None:
29
- assert detect_runtime_mode(["uvicorn", "flowent.main:app", "--reload"]) == "dev"
30
- assert detect_runtime_mode(["uvicorn", "flowent.main:app"]) == "release"
31
-
32
-
33
- def test_setup_logging_creates_timestamped_file_and_prunes_old_logs(
34
- tmp_path: Path,
35
- ) -> None:
36
- log_dir = tmp_path / "logs"
37
- log_dir.mkdir()
38
- for i in range(1, 12):
39
- (log_dir / f"{i}.log").write_text(f"old-{i}", encoding="utf-8")
40
-
41
- setup_logging(argv=["fastapi", "run"], runtime_dir=tmp_path)
42
-
43
- log_files = sorted(path.name for path in log_dir.iterdir() if path.is_file())
44
- assert len(log_files) == 10
45
- assert "1.log" not in log_files
46
- assert "2.log" not in log_files
47
- assert any(
48
- re.fullmatch(r"\d{4}-\d{2}-\d{2}_\d{6}_\d+\.log", path) for path in log_files
49
- )
50
-
51
-
52
- def test_setup_logging_writes_source_and_agent_id_to_file(tmp_path: Path) -> None:
53
- setup_logging(argv=["fastapi", "dev"], runtime_dir=tmp_path)
54
-
55
- with logger.contextualize(agent_id="agent-123"):
56
- logger.info("hello file log")
57
-
58
- log_files = sorted((tmp_path / "logs").iterdir())
59
- content = log_files[-1].read_text(encoding="utf-8")
60
-
61
- assert "hello file log" in content
62
- assert "agent:agent-123" in content
63
- assert "test_setup_logging_writes_source_and_agent_id_to_file" in content
64
- assert "hello file log\n\n" not in content
65
-
66
-
67
- def test_setup_logging_sets_console_level_from_runtime_mode(
68
- capsys,
69
- tmp_path: Path,
70
- ) -> None:
71
- setup_logging(argv=["fastapi", "run"], runtime_dir=tmp_path)
72
- logger.debug("hidden release debug")
73
- logger.info("visible release info")
74
- release_output = capsys.readouterr().err
75
-
76
- setup_logging(argv=["fastapi", "dev"], runtime_dir=tmp_path)
77
- logger.debug("visible dev debug")
78
- dev_output = capsys.readouterr().err
79
-
80
- assert "hidden release debug" not in release_output
81
- assert "visible release info" in release_output
82
- assert "visible release info\n\n" not in release_output
83
- assert "visible dev debug" in dev_output
84
-
85
-
86
- def test_healthcheck_access_filter_blocks_health_endpoint() -> None:
87
- access_filter = HealthcheckAccessFilter()
88
- record = logging.LogRecord(
89
- name="uvicorn.access",
90
- level=logging.INFO,
91
- pathname=__file__,
92
- lineno=1,
93
- msg='%s - "%s %s HTTP/%s" %d',
94
- args=("127.0.0.1:12345", "GET", "/health?source=docker", "1.1", 200),
95
- exc_info=None,
96
- )
97
-
98
- assert access_filter.filter(record) is False
99
-
100
-
101
- def test_healthcheck_access_filter_keeps_other_requests() -> None:
102
- access_filter = HealthcheckAccessFilter()
103
- record = logging.LogRecord(
104
- name="uvicorn.access",
105
- level=logging.INFO,
106
- pathname=__file__,
107
- lineno=1,
108
- msg='%s - "%s %s HTTP/%s" %d',
109
- args=("127.0.0.1:12345", "GET", "/api/meta", "1.1", 200),
110
- exc_info=None,
111
- )
112
-
113
- assert access_filter.filter(record) is True
114
-
115
-
116
- def test_configure_uvicorn_access_logging_installs_filter_once() -> None:
117
- access_logger = logging.getLogger("uvicorn.access")
118
- original_filters = list(access_logger.filters)
119
- access_logger.filters = [
120
- f for f in access_logger.filters if not isinstance(f, HealthcheckAccessFilter)
121
- ]
122
-
123
- try:
124
- configure_uvicorn_access_logging()
125
- configure_uvicorn_access_logging()
126
-
127
- filters = [
128
- f for f in access_logger.filters if isinstance(f, HealthcheckAccessFilter)
129
- ]
130
- assert len(filters) == 1
131
- finally:
132
- access_logger.filters = original_filters