flowent 0.0.7 → 0.0.11

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 (387) 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/__init__.py +6 -2
  5. package/backend/src/flowent/__pycache__/__init__.cpython-313.pyc +0 -0
  6. package/backend/src/flowent/__pycache__/agent.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 +376 -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 +477 -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__/cli.cpython-313.pyc +0 -0
  59. package/backend/src/flowent/__pycache__/config.cpython-313.pyc +0 -0
  60. package/backend/src/flowent/__pycache__/events.cpython-313.pyc +0 -0
  61. package/backend/src/flowent/__pycache__/graph_runtime.cpython-313.pyc +0 -0
  62. package/backend/src/flowent/__pycache__/graph_service.cpython-313.pyc +0 -0
  63. package/backend/src/flowent/__pycache__/image_assets.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 -2401
  86. package/backend/src/flowent/image_assets.py +0 -356
  87. package/backend/src/flowent/model_metadata.py +0 -102
  88. package/backend/src/flowent/models/__init__.py +0 -125
  89. package/backend/src/flowent/models/__pycache__/__init__.cpython-313.pyc +0 -0
  90. package/backend/src/flowent/models/__pycache__/agent.cpython-313.pyc +0 -0
  91. package/backend/src/flowent/models/__pycache__/base.cpython-313.pyc +0 -0
  92. package/backend/src/flowent/models/__pycache__/blueprint.cpython-313.pyc +0 -0
  93. package/backend/src/flowent/models/__pycache__/content.cpython-313.pyc +0 -0
  94. package/backend/src/flowent/models/__pycache__/delta.cpython-313.pyc +0 -0
  95. package/backend/src/flowent/models/__pycache__/event.cpython-313.pyc +0 -0
  96. package/backend/src/flowent/models/__pycache__/graph.cpython-313.pyc +0 -0
  97. package/backend/src/flowent/models/__pycache__/history.cpython-313.pyc +0 -0
  98. package/backend/src/flowent/models/__pycache__/llm.cpython-313.pyc +0 -0
  99. package/backend/src/flowent/models/__pycache__/message.cpython-313.pyc +0 -0
  100. package/backend/src/flowent/models/__pycache__/tab.cpython-313.pyc +0 -0
  101. package/backend/src/flowent/models/__pycache__/todo.cpython-313.pyc +0 -0
  102. package/backend/src/flowent/models/agent.py +0 -34
  103. package/backend/src/flowent/models/base.py +0 -24
  104. package/backend/src/flowent/models/blueprint.py +0 -176
  105. package/backend/src/flowent/models/content.py +0 -164
  106. package/backend/src/flowent/models/delta.py +0 -44
  107. package/backend/src/flowent/models/event.py +0 -51
  108. package/backend/src/flowent/models/graph.py +0 -472
  109. package/backend/src/flowent/models/history.py +0 -272
  110. package/backend/src/flowent/models/llm.py +0 -62
  111. package/backend/src/flowent/models/message.py +0 -33
  112. package/backend/src/flowent/models/tab.py +0 -85
  113. package/backend/src/flowent/models/todo.py +0 -10
  114. package/backend/src/flowent/network.py +0 -146
  115. package/backend/src/flowent/observability_service.py +0 -218
  116. package/backend/src/flowent/prompts/__init__.py +0 -67
  117. package/backend/src/flowent/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  118. package/backend/src/flowent/prompts/__pycache__/common.cpython-313.pyc +0 -0
  119. package/backend/src/flowent/prompts/__pycache__/steward.cpython-313.pyc +0 -0
  120. package/backend/src/flowent/prompts/common.py +0 -250
  121. package/backend/src/flowent/prompts/steward.py +0 -64
  122. package/backend/src/flowent/providers/__init__.py +0 -23
  123. package/backend/src/flowent/providers/__pycache__/__init__.cpython-313.pyc +0 -0
  124. package/backend/src/flowent/providers/__pycache__/anthropic.cpython-313.pyc +0 -0
  125. package/backend/src/flowent/providers/__pycache__/base_url.cpython-313.pyc +0 -0
  126. package/backend/src/flowent/providers/__pycache__/configuration.cpython-313.pyc +0 -0
  127. package/backend/src/flowent/providers/__pycache__/content.cpython-313.pyc +0 -0
  128. package/backend/src/flowent/providers/__pycache__/errors.cpython-313.pyc +0 -0
  129. package/backend/src/flowent/providers/__pycache__/gateway.cpython-313.pyc +0 -0
  130. package/backend/src/flowent/providers/__pycache__/headers.cpython-313.pyc +0 -0
  131. package/backend/src/flowent/providers/__pycache__/management.cpython-313.pyc +0 -0
  132. package/backend/src/flowent/providers/__pycache__/openai.cpython-313.pyc +0 -0
  133. package/backend/src/flowent/providers/__pycache__/openai_responses.cpython-313.pyc +0 -0
  134. package/backend/src/flowent/providers/__pycache__/registry.cpython-313.pyc +0 -0
  135. package/backend/src/flowent/providers/__pycache__/sse.cpython-313.pyc +0 -0
  136. package/backend/src/flowent/providers/__pycache__/thinking.cpython-313.pyc +0 -0
  137. package/backend/src/flowent/providers/anthropic.py +0 -468
  138. package/backend/src/flowent/providers/base_url.py +0 -60
  139. package/backend/src/flowent/providers/configuration.py +0 -189
  140. package/backend/src/flowent/providers/content.py +0 -122
  141. package/backend/src/flowent/providers/errors.py +0 -223
  142. package/backend/src/flowent/providers/gateway.py +0 -169
  143. package/backend/src/flowent/providers/gemini.py +0 -447
  144. package/backend/src/flowent/providers/headers.py +0 -20
  145. package/backend/src/flowent/providers/management.py +0 -96
  146. package/backend/src/flowent/providers/ollama.py +0 -293
  147. package/backend/src/flowent/providers/openai.py +0 -422
  148. package/backend/src/flowent/providers/openai_responses.py +0 -655
  149. package/backend/src/flowent/providers/registry.py +0 -144
  150. package/backend/src/flowent/providers/sse.py +0 -31
  151. package/backend/src/flowent/providers/thinking.py +0 -79
  152. package/backend/src/flowent/registry.py +0 -73
  153. package/backend/src/flowent/role_management.py +0 -270
  154. package/backend/src/flowent/routes/__init__.py +0 -26
  155. package/backend/src/flowent/routes/__pycache__/__init__.cpython-313.pyc +0 -0
  156. package/backend/src/flowent/routes/__pycache__/access.cpython-313.pyc +0 -0
  157. package/backend/src/flowent/routes/__pycache__/assistant.cpython-313.pyc +0 -0
  158. package/backend/src/flowent/routes/__pycache__/image_assets.cpython-313.pyc +0 -0
  159. package/backend/src/flowent/routes/__pycache__/meta.cpython-313.pyc +0 -0
  160. package/backend/src/flowent/routes/__pycache__/nodes.cpython-313.pyc +0 -0
  161. package/backend/src/flowent/routes/__pycache__/prompts.cpython-313.pyc +0 -0
  162. package/backend/src/flowent/routes/__pycache__/providers_route.cpython-313.pyc +0 -0
  163. package/backend/src/flowent/routes/__pycache__/roles.cpython-313.pyc +0 -0
  164. package/backend/src/flowent/routes/__pycache__/settings.cpython-313.pyc +0 -0
  165. package/backend/src/flowent/routes/__pycache__/tabs.cpython-313.pyc +0 -0
  166. package/backend/src/flowent/routes/__pycache__/ws.cpython-313.pyc +0 -0
  167. package/backend/src/flowent/routes/access.py +0 -48
  168. package/backend/src/flowent/routes/assistant.py +0 -158
  169. package/backend/src/flowent/routes/image_assets.py +0 -33
  170. package/backend/src/flowent/routes/meta.py +0 -28
  171. package/backend/src/flowent/routes/nodes.py +0 -423
  172. package/backend/src/flowent/routes/prompts.py +0 -46
  173. package/backend/src/flowent/routes/providers_route.py +0 -365
  174. package/backend/src/flowent/routes/roles.py +0 -207
  175. package/backend/src/flowent/routes/settings.py +0 -379
  176. package/backend/src/flowent/routes/tabs.py +0 -298
  177. package/backend/src/flowent/routes/ws.py +0 -33
  178. package/backend/src/flowent/runtime.py +0 -160
  179. package/backend/src/flowent/security.py +0 -37
  180. package/backend/src/flowent/settings.py +0 -2112
  181. package/backend/src/flowent/settings_management.py +0 -394
  182. package/backend/src/flowent/state_db.py +0 -108
  183. package/backend/src/flowent/static/assets/AssistantPage-BW7XAd9I.js +0 -1
  184. package/backend/src/flowent/static/assets/ChannelsPage-tCJHgt6m.js +0 -1
  185. package/backend/src/flowent/static/assets/PageScaffold-f6g2l7XN.js +0 -1
  186. package/backend/src/flowent/static/assets/PromptsPage-C3Sxn2D7.js +0 -1
  187. package/backend/src/flowent/static/assets/ProvidersPage-BfmdXmNt.js +0 -3
  188. package/backend/src/flowent/static/assets/RolesPage-DET8wO4r.js +0 -1
  189. package/backend/src/flowent/static/assets/SettingsPage-D-g3deMm.js +0 -3
  190. package/backend/src/flowent/static/assets/ToolsPage-CDmtE2g4.js +0 -1
  191. package/backend/src/flowent/static/assets/WorkspacePage-AZsJ0sD0.js +0 -3
  192. package/backend/src/flowent/static/assets/WorkspacePanels-CteCjolX.js +0 -1
  193. package/backend/src/flowent/static/assets/alert-dialog-Duorp_S-.js +0 -1
  194. package/backend/src/flowent/static/assets/dialog-C3ixjGjN.js +0 -1
  195. package/backend/src/flowent/static/assets/elk-worker.min-C9JGDOE-.js +0 -6312
  196. package/backend/src/flowent/static/assets/graph-vendor-CHpVij2M.css +0 -1
  197. package/backend/src/flowent/static/assets/graph-vendor-DRq_-6fV.js +0 -7
  198. package/backend/src/flowent/static/assets/index--o_0fv0N.css +0 -1
  199. package/backend/src/flowent/static/assets/index-C9HuekJm.js +0 -10
  200. package/backend/src/flowent/static/assets/layout.worker-jMHqAFbP.js +0 -24
  201. package/backend/src/flowent/static/assets/markdown-vendor-C9RtvaJh.js +0 -29
  202. package/backend/src/flowent/static/assets/modelParams-DmnF2hwR.js +0 -1
  203. package/backend/src/flowent/static/assets/providerTypes-DT3Ahwl_.js +0 -1
  204. package/backend/src/flowent/static/assets/react-vendor-mEs_JJxa.js +0 -9
  205. package/backend/src/flowent/static/assets/roles-CuRT_chR.js +0 -1
  206. package/backend/src/flowent/static/assets/rolldown-runtime-BYbx6iT9.js +0 -1
  207. package/backend/src/flowent/static/assets/select-DCfeNu-F.js +0 -1
  208. package/backend/src/flowent/static/assets/surface-pWwG5ogx.js +0 -1
  209. package/backend/src/flowent/static/assets/ui-vendor-C5pJa8N7.js +0 -51
  210. package/backend/src/flowent/static/assets/useAppRoute-FgSHBKhV.js +0 -1
  211. package/backend/src/flowent/static/favicon.svg +0 -4
  212. package/backend/src/flowent/tools/__init__.py +0 -176
  213. package/backend/src/flowent/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  214. package/backend/src/flowent/tools/__pycache__/connect.cpython-313.pyc +0 -0
  215. package/backend/src/flowent/tools/__pycache__/contacts.cpython-313.pyc +0 -0
  216. package/backend/src/flowent/tools/__pycache__/create_agent.cpython-313.pyc +0 -0
  217. package/backend/src/flowent/tools/__pycache__/create_tab.cpython-313.pyc +0 -0
  218. package/backend/src/flowent/tools/__pycache__/delete_tab.cpython-313.pyc +0 -0
  219. package/backend/src/flowent/tools/__pycache__/edit.cpython-313.pyc +0 -0
  220. package/backend/src/flowent/tools/__pycache__/exec.cpython-313.pyc +0 -0
  221. package/backend/src/flowent/tools/__pycache__/fetch.cpython-313.pyc +0 -0
  222. package/backend/src/flowent/tools/__pycache__/idle.cpython-313.pyc +0 -0
  223. package/backend/src/flowent/tools/__pycache__/list_roles.cpython-313.pyc +0 -0
  224. package/backend/src/flowent/tools/__pycache__/list_tabs.cpython-313.pyc +0 -0
  225. package/backend/src/flowent/tools/__pycache__/list_tools.cpython-313.pyc +0 -0
  226. package/backend/src/flowent/tools/__pycache__/manage_prompts.cpython-313.pyc +0 -0
  227. package/backend/src/flowent/tools/__pycache__/manage_providers.cpython-313.pyc +0 -0
  228. package/backend/src/flowent/tools/__pycache__/manage_roles.cpython-313.pyc +0 -0
  229. package/backend/src/flowent/tools/__pycache__/manage_settings.cpython-313.pyc +0 -0
  230. package/backend/src/flowent/tools/__pycache__/read.cpython-313.pyc +0 -0
  231. package/backend/src/flowent/tools/__pycache__/send.cpython-313.pyc +0 -0
  232. package/backend/src/flowent/tools/__pycache__/set_permissions.cpython-313.pyc +0 -0
  233. package/backend/src/flowent/tools/__pycache__/sleep.cpython-313.pyc +0 -0
  234. package/backend/src/flowent/tools/__pycache__/todo.cpython-313.pyc +0 -0
  235. package/backend/src/flowent/tools/connect.py +0 -100
  236. package/backend/src/flowent/tools/contacts.py +0 -22
  237. package/backend/src/flowent/tools/create_agent.py +0 -191
  238. package/backend/src/flowent/tools/create_tab.py +0 -61
  239. package/backend/src/flowent/tools/delete_tab.py +0 -39
  240. package/backend/src/flowent/tools/edit.py +0 -142
  241. package/backend/src/flowent/tools/exec.py +0 -118
  242. package/backend/src/flowent/tools/fetch.py +0 -85
  243. package/backend/src/flowent/tools/idle.py +0 -27
  244. package/backend/src/flowent/tools/list_roles.py +0 -68
  245. package/backend/src/flowent/tools/list_tabs.py +0 -100
  246. package/backend/src/flowent/tools/list_tools.py +0 -28
  247. package/backend/src/flowent/tools/manage_prompts.py +0 -102
  248. package/backend/src/flowent/tools/manage_providers.py +0 -220
  249. package/backend/src/flowent/tools/manage_roles.py +0 -275
  250. package/backend/src/flowent/tools/manage_settings.py +0 -326
  251. package/backend/src/flowent/tools/read.py +0 -152
  252. package/backend/src/flowent/tools/send.py +0 -68
  253. package/backend/src/flowent/tools/set_permissions.py +0 -99
  254. package/backend/src/flowent/tools/sleep.py +0 -41
  255. package/backend/src/flowent/tools/todo.py +0 -51
  256. package/backend/src/flowent/workspace_store.py +0 -479
  257. package/backend/tests/__init__.py +0 -0
  258. package/backend/tests/__pycache__/__init__.cpython-313.pyc +0 -0
  259. package/backend/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  260. package/backend/tests/conftest.py +0 -6
  261. package/backend/tests/integration/api/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  262. package/backend/tests/integration/api/__pycache__/test_access_api.cpython-313-pytest-9.0.3.pyc +0 -0
  263. package/backend/tests/integration/api/__pycache__/test_assistant_api.cpython-313-pytest-9.0.3.pyc +0 -0
  264. package/backend/tests/integration/api/__pycache__/test_frontend_mounting.cpython-313-pytest-9.0.3.pyc +0 -0
  265. package/backend/tests/integration/api/__pycache__/test_meta_api.cpython-313-pytest-9.0.3.pyc +0 -0
  266. package/backend/tests/integration/api/__pycache__/test_nodes_api.cpython-313-pytest-9.0.3.pyc +0 -0
  267. package/backend/tests/integration/api/__pycache__/test_prompts_api.cpython-313-pytest-9.0.3.pyc +0 -0
  268. package/backend/tests/integration/api/__pycache__/test_roles_api.cpython-313-pytest-9.0.3.pyc +0 -0
  269. package/backend/tests/integration/api/__pycache__/test_tabs_api.cpython-313-pytest-9.0.3.pyc +0 -0
  270. package/backend/tests/integration/api/conftest.py +0 -29
  271. package/backend/tests/integration/api/test_access_api.py +0 -182
  272. package/backend/tests/integration/api/test_assistant_api.py +0 -422
  273. package/backend/tests/integration/api/test_frontend_mounting.py +0 -61
  274. package/backend/tests/integration/api/test_meta_api.py +0 -32
  275. package/backend/tests/integration/api/test_nodes_api.py +0 -787
  276. package/backend/tests/integration/api/test_prompts_api.py +0 -47
  277. package/backend/tests/integration/api/test_roles_api.py +0 -228
  278. package/backend/tests/integration/api/test_tabs_api.py +0 -688
  279. package/backend/tests/unit/__pycache__/test_access.cpython-313-pytest-9.0.3.pyc +0 -0
  280. package/backend/tests/unit/__pycache__/test_cli.cpython-313-pytest-9.0.3.pyc +0 -0
  281. package/backend/tests/unit/__pycache__/test_graph_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  282. package/backend/tests/unit/__pycache__/test_network.cpython-313-pytest-9.0.3.pyc +0 -0
  283. package/backend/tests/unit/__pycache__/test_state_sqlite_storage.cpython-313-pytest-9.0.3.pyc +0 -0
  284. package/backend/tests/unit/__pycache__/test_workspace_store.cpython-313-pytest-9.0.3.pyc +0 -0
  285. package/backend/tests/unit/agent/__pycache__/test_agent_public_api.cpython-313-pytest-9.0.3.pyc +0 -0
  286. package/backend/tests/unit/agent/__pycache__/test_agent_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  287. package/backend/tests/unit/agent/test_agent_public_api.py +0 -822
  288. package/backend/tests/unit/agent/test_agent_runtime.py +0 -3088
  289. package/backend/tests/unit/channels/__pycache__/test_telegram_channel.cpython-313-pytest-9.0.3.pyc +0 -0
  290. package/backend/tests/unit/channels/test_telegram_channel.py +0 -552
  291. package/backend/tests/unit/logging/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
  292. package/backend/tests/unit/logging/test_logging.py +0 -132
  293. package/backend/tests/unit/prompts/__pycache__/test_prompts.cpython-313-pytest-9.0.3.pyc +0 -0
  294. package/backend/tests/unit/prompts/test_prompts.py +0 -570
  295. package/backend/tests/unit/providers/__pycache__/test_anthropic_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  296. package/backend/tests/unit/providers/__pycache__/test_errors.cpython-313-pytest-9.0.3.pyc +0 -0
  297. package/backend/tests/unit/providers/__pycache__/test_extract_delta_parts.cpython-313-pytest-9.0.3.pyc +0 -0
  298. package/backend/tests/unit/providers/__pycache__/test_openai_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  299. package/backend/tests/unit/providers/__pycache__/test_openai_responses.cpython-313-pytest-9.0.3.pyc +0 -0
  300. package/backend/tests/unit/providers/__pycache__/test_provider_gateway.cpython-313-pytest-9.0.3.pyc +0 -0
  301. package/backend/tests/unit/providers/__pycache__/test_think_tag_parser.cpython-313-pytest-9.0.3.pyc +0 -0
  302. package/backend/tests/unit/providers/test_anthropic_provider.py +0 -185
  303. package/backend/tests/unit/providers/test_errors.py +0 -68
  304. package/backend/tests/unit/providers/test_extract_delta_parts.py +0 -22
  305. package/backend/tests/unit/providers/test_openai_provider.py +0 -139
  306. package/backend/tests/unit/providers/test_openai_responses.py +0 -402
  307. package/backend/tests/unit/providers/test_provider_gateway.py +0 -359
  308. package/backend/tests/unit/providers/test_think_tag_parser.py +0 -36
  309. package/backend/tests/unit/routes/__pycache__/test_prompts_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  310. package/backend/tests/unit/routes/__pycache__/test_providers_route.cpython-313-pytest-9.0.3.pyc +0 -0
  311. package/backend/tests/unit/routes/__pycache__/test_roles_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  312. package/backend/tests/unit/routes/__pycache__/test_settings_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  313. package/backend/tests/unit/routes/test_prompts_routes.py +0 -82
  314. package/backend/tests/unit/routes/test_providers_route.py +0 -370
  315. package/backend/tests/unit/routes/test_roles_routes.py +0 -539
  316. package/backend/tests/unit/routes/test_settings_routes.py +0 -1123
  317. package/backend/tests/unit/runtime/__pycache__/test_bootstrap_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  318. package/backend/tests/unit/runtime/test_bootstrap_runtime.py +0 -1002
  319. package/backend/tests/unit/sandbox/__pycache__/test_sandbox_tools.cpython-313-pytest-9.0.3.pyc +0 -0
  320. package/backend/tests/unit/sandbox/test_sandbox_tools.py +0 -78
  321. package/backend/tests/unit/security/__pycache__/test_security.cpython-313-pytest-9.0.3.pyc +0 -0
  322. package/backend/tests/unit/security/test_security.py +0 -124
  323. package/backend/tests/unit/settings/__pycache__/test_settings_roles.cpython-313-pytest-9.0.3.pyc +0 -0
  324. package/backend/tests/unit/settings/test_settings_roles.py +0 -703
  325. package/backend/tests/unit/test_access.py +0 -45
  326. package/backend/tests/unit/test_cli.py +0 -102
  327. package/backend/tests/unit/test_graph_runtime.py +0 -72
  328. package/backend/tests/unit/test_network.py +0 -51
  329. package/backend/tests/unit/test_state_sqlite_storage.py +0 -87
  330. package/backend/tests/unit/test_workspace_store.py +0 -228
  331. package/backend/tests/unit/tools/__pycache__/test_connect_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  332. package/backend/tests/unit/tools/__pycache__/test_create_agent_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  333. package/backend/tests/unit/tools/__pycache__/test_delete_tab_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  334. package/backend/tests/unit/tools/__pycache__/test_edit_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  335. package/backend/tests/unit/tools/__pycache__/test_exec_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  336. package/backend/tests/unit/tools/__pycache__/test_fetch_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  337. package/backend/tests/unit/tools/__pycache__/test_manage_prompts_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  338. package/backend/tests/unit/tools/__pycache__/test_manage_providers_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  339. package/backend/tests/unit/tools/__pycache__/test_manage_roles_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  340. package/backend/tests/unit/tools/__pycache__/test_manage_settings_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  341. package/backend/tests/unit/tools/__pycache__/test_read_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  342. package/backend/tests/unit/tools/__pycache__/test_set_permissions_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  343. package/backend/tests/unit/tools/__pycache__/test_todo_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  344. package/backend/tests/unit/tools/__pycache__/test_tool_registry.cpython-313-pytest-9.0.3.pyc +0 -0
  345. package/backend/tests/unit/tools/test_connect_tool.py +0 -228
  346. package/backend/tests/unit/tools/test_create_agent_tool.py +0 -404
  347. package/backend/tests/unit/tools/test_delete_tab_tool.py +0 -116
  348. package/backend/tests/unit/tools/test_edit_tool.py +0 -115
  349. package/backend/tests/unit/tools/test_exec_tool.py +0 -81
  350. package/backend/tests/unit/tools/test_fetch_tool.py +0 -65
  351. package/backend/tests/unit/tools/test_manage_prompts_tool.py +0 -92
  352. package/backend/tests/unit/tools/test_manage_providers_tool.py +0 -460
  353. package/backend/tests/unit/tools/test_manage_roles_tool.py +0 -411
  354. package/backend/tests/unit/tools/test_manage_settings_tool.py +0 -611
  355. package/backend/tests/unit/tools/test_read_tool.py +0 -33
  356. package/backend/tests/unit/tools/test_set_permissions_tool.py +0 -595
  357. package/backend/tests/unit/tools/test_todo_tool.py +0 -37
  358. package/backend/tests/unit/tools/test_tool_registry.py +0 -199
  359. package/dist/frontend/assets/AssistantPage-BW7XAd9I.js +0 -1
  360. package/dist/frontend/assets/ChannelsPage-tCJHgt6m.js +0 -1
  361. package/dist/frontend/assets/PageScaffold-f6g2l7XN.js +0 -1
  362. package/dist/frontend/assets/PromptsPage-C3Sxn2D7.js +0 -1
  363. package/dist/frontend/assets/ProvidersPage-BfmdXmNt.js +0 -3
  364. package/dist/frontend/assets/RolesPage-DET8wO4r.js +0 -1
  365. package/dist/frontend/assets/SettingsPage-D-g3deMm.js +0 -3
  366. package/dist/frontend/assets/ToolsPage-CDmtE2g4.js +0 -1
  367. package/dist/frontend/assets/WorkspacePage-AZsJ0sD0.js +0 -3
  368. package/dist/frontend/assets/WorkspacePanels-CteCjolX.js +0 -1
  369. package/dist/frontend/assets/alert-dialog-Duorp_S-.js +0 -1
  370. package/dist/frontend/assets/dialog-C3ixjGjN.js +0 -1
  371. package/dist/frontend/assets/elk-worker.min-C9JGDOE-.js +0 -6312
  372. package/dist/frontend/assets/graph-vendor-CHpVij2M.css +0 -1
  373. package/dist/frontend/assets/graph-vendor-DRq_-6fV.js +0 -7
  374. package/dist/frontend/assets/index--o_0fv0N.css +0 -1
  375. package/dist/frontend/assets/index-C9HuekJm.js +0 -10
  376. package/dist/frontend/assets/layout.worker-jMHqAFbP.js +0 -24
  377. package/dist/frontend/assets/markdown-vendor-C9RtvaJh.js +0 -29
  378. package/dist/frontend/assets/modelParams-DmnF2hwR.js +0 -1
  379. package/dist/frontend/assets/providerTypes-DT3Ahwl_.js +0 -1
  380. package/dist/frontend/assets/react-vendor-mEs_JJxa.js +0 -9
  381. package/dist/frontend/assets/roles-CuRT_chR.js +0 -1
  382. package/dist/frontend/assets/rolldown-runtime-BYbx6iT9.js +0 -1
  383. package/dist/frontend/assets/select-DCfeNu-F.js +0 -1
  384. package/dist/frontend/assets/surface-pWwG5ogx.js +0 -1
  385. package/dist/frontend/assets/ui-vendor-C5pJa8N7.js +0 -51
  386. package/dist/frontend/assets/useAppRoute-FgSHBKhV.js +0 -1
  387. package/dist/frontend/favicon.svg +0 -4
@@ -1,615 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import asyncio
4
- import threading
5
- import time
6
- from contextlib import suppress
7
- from typing import Any
8
-
9
- from loguru import logger
10
-
11
- from flowent.assistant_commands import (
12
- ConversationCommandError,
13
- execute_conversation_command_input,
14
- )
15
- from flowent.events import event_bus
16
- from flowent.models import Event, EventType, Message
17
- from flowent.network import create_async_http_session, is_success_status
18
- from flowent.providers.errors import LLMProviderError
19
- from flowent.registry import registry
20
- from flowent.settings import (
21
- TelegramPendingChat,
22
- get_settings,
23
- save_settings,
24
- )
25
-
26
- TELEGRAM_API_BASE_URL = "https://api.telegram.org"
27
- TELEGRAM_LONG_POLL_TIMEOUT_SECONDS = 30
28
- TELEGRAM_REQUEST_TIMEOUT_SECONDS = 35
29
- TELEGRAM_EDIT_INTERVAL_SECONDS = 1.0
30
- TELEGRAM_EDIT_THRESHOLD_CHARS = 100
31
- TELEGRAM_TYPING_INTERVAL_SECONDS = 4.0
32
- TELEGRAM_MAX_TEXT_LENGTH = 4000
33
- PRIVATE_ONLY_MESSAGE = "🔒 Telegram channel currently supports private chats only."
34
- IMAGE_INPUT_UNSUPPORTED_MESSAGE = "Telegram currently does not support image input yet."
35
- IMAGE_OUTPUT_UNSUPPORTED_MESSAGE = (
36
- "Telegram currently does not support image output yet."
37
- )
38
-
39
-
40
- class TelegramChannel:
41
- def __init__(self) -> None:
42
- settings = get_settings()
43
- self._bot_token = settings.telegram.bot_token.strip()
44
- self._app_loop = event_bus.get_loop()
45
- self._stop_event = threading.Event()
46
- self._thread: threading.Thread | None = None
47
- self._thread_loop: asyncio.AbstractEventLoop | None = None
48
- self._polling_task: asyncio.Task[None] | None = None
49
- self._offset: int | None = None
50
- self._event_lock: asyncio.Lock | None = None
51
- self._stream_buffer = ""
52
- self._stream_message_ids: dict[int, int] = {}
53
- self._typing_task: asyncio.Task[None] | None = None
54
- self._assistant_running = False
55
- self._last_edit_at = 0.0
56
- self._last_sent_length = 0
57
-
58
- def start(self) -> None:
59
- if not self._bot_token:
60
- logger.info("Telegram channel not started: bot token is empty")
61
- return
62
- if self._thread is not None and self._thread.is_alive():
63
- return
64
-
65
- self._app_loop = event_bus.get_loop()
66
- if self._app_loop is None or self._app_loop.is_closed():
67
- raise RuntimeError("Event loop is not available for Telegram channel")
68
-
69
- self._stop_event.clear()
70
- self._offset = None
71
- self._event_lock = None
72
- self._stream_buffer = ""
73
- self._stream_message_ids = {}
74
- self._typing_task = None
75
- self._assistant_running = False
76
- self._last_edit_at = 0.0
77
- self._last_sent_length = 0
78
-
79
- event_bus.subscribe(self._on_event)
80
- self._thread = threading.Thread(
81
- target=self._run_polling_thread,
82
- name="telegram-channel",
83
- daemon=True,
84
- )
85
- self._thread.start()
86
- logger.info("Telegram channel started")
87
-
88
- def stop(self) -> None:
89
- event_bus.unsubscribe(self._on_event)
90
- self._stop_event.set()
91
-
92
- polling_loop = self._thread_loop
93
- task = self._polling_task
94
- if polling_loop is not None and task is not None:
95
- polling_loop.call_soon_threadsafe(task.cancel)
96
- typing_task = self._typing_task
97
- app_loop = self._app_loop
98
- if (
99
- app_loop is not None
100
- and not app_loop.is_closed()
101
- and typing_task is not None
102
- ):
103
- app_loop.call_soon_threadsafe(typing_task.cancel)
104
-
105
- if self._thread is not None and self._thread.is_alive():
106
- self._thread.join(timeout=5.0)
107
-
108
- self._thread = None
109
- self._thread_loop = None
110
- self._polling_task = None
111
- self._typing_task = None
112
- self._assistant_running = False
113
- self._reset_stream_state()
114
- logger.info("Telegram channel stopped")
115
-
116
- def _run_polling_thread(self) -> None:
117
- loop = asyncio.new_event_loop()
118
- self._thread_loop = loop
119
- asyncio.set_event_loop(loop)
120
- self._polling_task = loop.create_task(self._poll_updates_loop())
121
- try:
122
- loop.run_until_complete(self._polling_task)
123
- except asyncio.CancelledError:
124
- pass
125
- finally:
126
- pending = asyncio.all_tasks(loop)
127
- for task in pending:
128
- task.cancel()
129
- if pending:
130
- loop.run_until_complete(
131
- asyncio.gather(*pending, return_exceptions=True)
132
- )
133
- loop.run_until_complete(loop.shutdown_asyncgens())
134
- loop.close()
135
-
136
- async def _poll_updates_loop(self) -> None:
137
- while not self._stop_event.is_set():
138
- try:
139
- result = await self._call_api(
140
- "getUpdates",
141
- {
142
- "offset": self._offset,
143
- "timeout": TELEGRAM_LONG_POLL_TIMEOUT_SECONDS,
144
- },
145
- parse_mode=None,
146
- )
147
- if not isinstance(result, list):
148
- continue
149
- for update in result:
150
- if not isinstance(update, dict):
151
- continue
152
- update_id = update.get("update_id")
153
- if isinstance(update_id, int):
154
- self._offset = update_id + 1
155
- await self._process_update(update)
156
- except asyncio.CancelledError:
157
- raise
158
- except Exception:
159
- logger.exception("Telegram polling loop failed")
160
- await asyncio.sleep(1.0)
161
-
162
- async def _process_update(self, update: dict[str, Any]) -> None:
163
- message = update.get("message")
164
- if not isinstance(message, dict):
165
- return
166
-
167
- from_data = message.get("from")
168
- chat_data = message.get("chat")
169
- chat_id = chat_data.get("id") if isinstance(chat_data, dict) else None
170
- chat_type = chat_data.get("type") if isinstance(chat_data, dict) else None
171
- text = message.get("text")
172
- caption = message.get("caption")
173
- photo = message.get("photo")
174
- document = message.get("document")
175
- username = from_data.get("username") if isinstance(from_data, dict) else None
176
- first_name = (
177
- from_data.get("first_name") if isinstance(from_data, dict) else None
178
- )
179
- last_name = from_data.get("last_name") if isinstance(from_data, dict) else None
180
-
181
- if not isinstance(chat_id, int):
182
- return
183
- if chat_type != "private":
184
- await self._send_message(chat_id, PRIVATE_ONLY_MESSAGE, markdown=False)
185
- return
186
-
187
- display_name = self._build_display_name(first_name, last_name, username)
188
-
189
- settings = get_settings()
190
- if any(chat.chat_id == chat_id for chat in settings.telegram.approved_chats):
191
- document_is_image = isinstance(document, dict) and str(
192
- document.get("mime_type", "")
193
- ).startswith("image/")
194
- has_image_input = (
195
- isinstance(photo, list) and len(photo) > 0
196
- ) or document_is_image
197
- if has_image_input:
198
- await self._send_message(
199
- chat_id,
200
- IMAGE_INPUT_UNSUPPORTED_MESSAGE,
201
- markdown=False,
202
- )
203
- return
204
-
205
- incoming_text = text if isinstance(text, str) else caption
206
- if not isinstance(incoming_text, str) or not incoming_text.strip():
207
- return
208
-
209
- if not isinstance(text, str) or not text.strip():
210
- text = incoming_text
211
-
212
- assistant = registry.get_assistant()
213
- if assistant is None:
214
- logger.warning("Telegram message dropped: assistant not available")
215
- return
216
-
217
- try:
218
- executed_command = execute_conversation_command_input(assistant, text)
219
- except ConversationCommandError as exc:
220
- await self._send_message(chat_id, str(exc), markdown=False)
221
- return
222
- except (RuntimeError, TimeoutError, LLMProviderError) as exc:
223
- await self._send_message(chat_id, str(exc), markdown=False)
224
- return
225
-
226
- if executed_command is not None:
227
- await self._send_message(
228
- chat_id, executed_command.feedback, markdown=False
229
- )
230
- return
231
-
232
- assistant.enqueue_message(
233
- Message(from_id="human", to_id=assistant.uuid, content=text)
234
- )
235
- return
236
-
237
- if self._upsert_pending_chat(settings, chat_id, username, display_name):
238
- save_settings(settings)
239
- await self._send_message(
240
- chat_id,
241
- (f"⏳ This chat is pending approval in Flowent.\nChat ID: `{chat_id}`"),
242
- markdown=True,
243
- )
244
-
245
- def _on_event(self, event: Event) -> None:
246
- if event.type not in {
247
- EventType.ASSISTANT_CONTENT,
248
- EventType.HISTORY_ENTRY_ADDED,
249
- EventType.NODE_STATE_CHANGED,
250
- }:
251
- return
252
-
253
- loop = self._app_loop
254
- if loop is None or loop.is_closed():
255
- return
256
-
257
- future = asyncio.run_coroutine_threadsafe(self._process_event(event), loop)
258
- future.add_done_callback(self._log_event_error)
259
-
260
- @staticmethod
261
- def _log_event_error(future: Any) -> None:
262
- try:
263
- future.result()
264
- except asyncio.CancelledError:
265
- return
266
- except Exception:
267
- logger.exception("Telegram event handling failed")
268
-
269
- async def _process_event(self, event: Event) -> None:
270
- if self._event_lock is None:
271
- self._event_lock = asyncio.Lock()
272
-
273
- async with self._event_lock:
274
- assistant = registry.get_assistant()
275
- if assistant is None or event.agent_id != assistant.uuid:
276
- return
277
-
278
- if event.type == EventType.ASSISTANT_CONTENT:
279
- content = event.data.get("content")
280
- if isinstance(content, str) and content:
281
- await self._handle_assistant_content(content)
282
- return
283
-
284
- if event.type == EventType.HISTORY_ENTRY_ADDED:
285
- entry_type = event.data.get("type")
286
- if entry_type != "AssistantText":
287
- return
288
- parts = event.data.get("parts")
289
- if not isinstance(parts, list):
290
- return
291
- if not any(
292
- isinstance(part, dict) and part.get("type") == "image"
293
- for part in parts
294
- ):
295
- return
296
- for chat_id in self._get_approved_chat_ids():
297
- await self._send_message(
298
- chat_id,
299
- IMAGE_OUTPUT_UNSUPPORTED_MESSAGE,
300
- markdown=False,
301
- )
302
- return
303
-
304
- if event.type != EventType.NODE_STATE_CHANGED:
305
- return
306
-
307
- new_state = event.data.get("new_state")
308
- if new_state == "running":
309
- await self._begin_running_feedback()
310
- return
311
-
312
- await self._end_running_feedback()
313
-
314
- @staticmethod
315
- def _get_approved_chat_ids() -> list[int]:
316
- settings = get_settings()
317
- return [chat.chat_id for chat in settings.telegram.approved_chats]
318
-
319
- async def _handle_assistant_content(self, chunk: str) -> None:
320
- self._stream_buffer += chunk
321
- await self._ensure_stream_messages()
322
- now = time.monotonic()
323
- if (
324
- len(self._stream_buffer) - self._last_sent_length
325
- >= TELEGRAM_EDIT_THRESHOLD_CHARS
326
- or now - self._last_edit_at >= TELEGRAM_EDIT_INTERVAL_SECONDS
327
- ):
328
- await self._flush_stream()
329
-
330
- async def _finalize_stream(self) -> None:
331
- if not self._stream_buffer and not self._stream_message_ids:
332
- self._reset_stream_state()
333
- return
334
- await self._flush_stream(force=True)
335
- self._reset_stream_state()
336
-
337
- async def _begin_running_feedback(self) -> None:
338
- if self._assistant_running:
339
- return
340
- await self._stop_typing_task()
341
- self._assistant_running = True
342
- self._reset_stream_state()
343
- self._typing_task = asyncio.create_task(self._typing_loop())
344
-
345
- async def _end_running_feedback(self) -> None:
346
- self._assistant_running = False
347
- await self._stop_typing_task()
348
- await self._finalize_stream()
349
-
350
- async def _stop_typing_task(self) -> None:
351
- task = self._typing_task
352
- if task is None:
353
- return
354
- self._typing_task = None
355
- task.cancel()
356
- with suppress(asyncio.CancelledError):
357
- await task
358
-
359
- async def _typing_loop(self) -> None:
360
- while self._assistant_running and not self._stop_event.is_set():
361
- try:
362
- await self._send_typing_feedback_once()
363
- except asyncio.CancelledError:
364
- raise
365
- except Exception:
366
- logger.exception("Telegram typing loop failed")
367
- await asyncio.sleep(TELEGRAM_TYPING_INTERVAL_SECONDS)
368
-
369
- async def _send_typing_feedback_once(self) -> None:
370
- settings = get_settings()
371
- approved_chat_ids = {chat.chat_id for chat in settings.telegram.approved_chats}
372
- for chat_id in list(self._stream_message_ids):
373
- if chat_id not in approved_chat_ids:
374
- self._stream_message_ids.pop(chat_id, None)
375
- for approved_chat in settings.telegram.approved_chats:
376
- if approved_chat.chat_id in self._stream_message_ids:
377
- continue
378
- await self._send_chat_action(approved_chat.chat_id, action="typing")
379
-
380
- async def _ensure_stream_messages(self) -> None:
381
- if not self._stream_buffer:
382
- return
383
- settings = get_settings()
384
- had_stream_messages = bool(self._stream_message_ids)
385
- for approved_chat in settings.telegram.approved_chats:
386
- if approved_chat.chat_id in self._stream_message_ids:
387
- continue
388
- message_id = await self._send_message(
389
- approved_chat.chat_id,
390
- self._stream_buffer,
391
- markdown=False,
392
- )
393
- if message_id is not None:
394
- self._stream_message_ids[approved_chat.chat_id] = message_id
395
- if not had_stream_messages and self._stream_message_ids:
396
- self._last_edit_at = time.monotonic()
397
- self._last_sent_length = len(self._stream_buffer)
398
-
399
- async def _flush_stream(self, *, force: bool = False) -> None:
400
- if not self._stream_buffer and not force:
401
- return
402
- await self._ensure_stream_messages()
403
- if not self._stream_message_ids or not self._stream_buffer:
404
- return
405
-
406
- current_chat_ids = {
407
- chat.chat_id for chat in get_settings().telegram.approved_chats
408
- }
409
- text = self._format_text(self._stream_buffer)
410
- for chat_id, message_id in list(self._stream_message_ids.items()):
411
- if chat_id not in current_chat_ids:
412
- self._stream_message_ids.pop(chat_id, None)
413
- continue
414
- updated = await self._edit_message(chat_id, message_id, text)
415
- if not updated:
416
- self._stream_message_ids.pop(chat_id, None)
417
-
418
- self._last_edit_at = time.monotonic()
419
- self._last_sent_length = len(self._stream_buffer)
420
-
421
- async def _send_message(
422
- self,
423
- chat_id: int,
424
- text: str,
425
- *,
426
- markdown: bool,
427
- ) -> int | None:
428
- payload: dict[str, Any] = {
429
- "chat_id": chat_id,
430
- "text": self._format_text(text),
431
- }
432
- result = await self._call_api(
433
- "sendMessage",
434
- payload,
435
- parse_mode="Markdown" if markdown else None,
436
- )
437
- if not isinstance(result, dict):
438
- return None
439
- message_id = result.get("message_id")
440
- return message_id if isinstance(message_id, int) else None
441
-
442
- async def _send_chat_action(self, chat_id: int, *, action: str) -> None:
443
- await self._call_api(
444
- "sendChatAction",
445
- {
446
- "chat_id": chat_id,
447
- "action": action,
448
- },
449
- parse_mode=None,
450
- )
451
-
452
- async def _edit_message(
453
- self,
454
- chat_id: int,
455
- message_id: int,
456
- text: str,
457
- ) -> bool:
458
- payload = {
459
- "chat_id": chat_id,
460
- "message_id": message_id,
461
- "text": self._format_text(text),
462
- }
463
- result = await self._call_api(
464
- "editMessageText",
465
- payload,
466
- parse_mode="Markdown",
467
- )
468
- return result is not None
469
-
470
- def _format_text(self, text: str) -> str:
471
- if len(text) <= TELEGRAM_MAX_TEXT_LENGTH:
472
- return text
473
- return text[: TELEGRAM_MAX_TEXT_LENGTH - 1] + "…"
474
-
475
- def _reset_stream_state(self) -> None:
476
- self._stream_buffer = ""
477
- self._stream_message_ids = {}
478
- self._last_edit_at = 0.0
479
- self._last_sent_length = 0
480
-
481
- @staticmethod
482
- def _build_display_name(
483
- first_name: object,
484
- last_name: object,
485
- username: object,
486
- ) -> str:
487
- parts = []
488
- if isinstance(first_name, str) and first_name.strip():
489
- parts.append(first_name.strip())
490
- if isinstance(last_name, str) and last_name.strip():
491
- parts.append(last_name.strip())
492
- if parts:
493
- return " ".join(parts)
494
- if isinstance(username, str) and username.strip():
495
- return username.strip()
496
- return ""
497
-
498
- @staticmethod
499
- def _upsert_pending_chat(
500
- settings: Any,
501
- chat_id: int,
502
- username: object,
503
- display_name: str,
504
- ) -> bool:
505
- username_value = (
506
- username.strip() if isinstance(username, str) and username.strip() else None
507
- )
508
- now = time.time()
509
- for pending_chat in settings.telegram.pending_chats:
510
- if pending_chat.chat_id != chat_id:
511
- continue
512
-
513
- changed = False
514
- if pending_chat.username != username_value:
515
- pending_chat.username = username_value
516
- changed = True
517
- if pending_chat.display_name != display_name:
518
- pending_chat.display_name = display_name
519
- changed = True
520
- if pending_chat.last_seen_at != now:
521
- pending_chat.last_seen_at = now
522
- changed = True
523
- return changed
524
-
525
- settings.telegram.pending_chats.append(
526
- TelegramPendingChat(
527
- chat_id=chat_id,
528
- username=username_value,
529
- display_name=display_name,
530
- first_seen_at=now,
531
- last_seen_at=now,
532
- )
533
- )
534
- return True
535
-
536
- async def _call_api(
537
- self,
538
- method: str,
539
- payload: dict[str, Any],
540
- *,
541
- parse_mode: str | None,
542
- ) -> Any:
543
- if not self._bot_token:
544
- return None
545
-
546
- request_payload = {
547
- key: value for key, value in payload.items() if value is not None
548
- }
549
- if parse_mode is not None:
550
- request_payload["parse_mode"] = parse_mode
551
-
552
- while not self._stop_event.is_set():
553
- try:
554
- async with create_async_http_session(
555
- timeout=TELEGRAM_REQUEST_TIMEOUT_SECONDS
556
- ) as client:
557
- response = await client.post(
558
- f"{TELEGRAM_API_BASE_URL}/bot{self._bot_token}/{method}",
559
- data=request_payload,
560
- )
561
- except asyncio.CancelledError:
562
- raise
563
- except Exception as exc:
564
- logger.warning("Telegram API request failed ({}): {}", method, exc)
565
- return None
566
-
567
- response_data: dict[str, Any] | None = None
568
- try:
569
- response_data = response.json()
570
- except ValueError:
571
- response_data = None
572
-
573
- if response.status_code == 429:
574
- retry_after = self._extract_retry_after(response_data)
575
- await asyncio.sleep(retry_after)
576
- continue
577
-
578
- if is_success_status(response.status_code) and isinstance(
579
- response_data, dict
580
- ):
581
- if response_data.get("ok") is True:
582
- return response_data.get("result")
583
- logger.warning(
584
- "Telegram API returned error for {}: {}",
585
- method,
586
- response_data.get("description", "unknown error"),
587
- )
588
- elif parse_mode is not None and method in {
589
- "sendMessage",
590
- "editMessageText",
591
- }:
592
- request_payload.pop("parse_mode", None)
593
- parse_mode = None
594
- continue
595
- else:
596
- logger.warning(
597
- "Telegram API request failed for {} with status {}",
598
- method,
599
- response.status_code,
600
- )
601
- return None
602
-
603
- return None
604
-
605
- @staticmethod
606
- def _extract_retry_after(response_data: dict[str, Any] | None) -> float:
607
- if not isinstance(response_data, dict):
608
- return 1.0
609
- parameters = response_data.get("parameters")
610
- if not isinstance(parameters, dict):
611
- return 1.0
612
- retry_after = parameters.get("retry_after")
613
- if isinstance(retry_after, (int, float)) and retry_after > 0:
614
- return float(retry_after)
615
- return 1.0
@@ -1,14 +0,0 @@
1
- from pydantic_settings import BaseSettings, SettingsConfigDict
2
-
3
-
4
- class Config(BaseSettings):
5
- model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
6
-
7
- APP_NAME: str = "Flowent"
8
- DEBUG: bool = False
9
- SESSION_SECRET: str = ""
10
- SESSION_COOKIE_NAME: str = "flowent_admin_session"
11
- SESSION_MAX_AGE_SECONDS: int = 604800
12
-
13
- LOG_LEVEL: str = "INFO"
14
- LOG_DIR: str = "logs"
@@ -1,3 +0,0 @@
1
- from flowent.main import create_app
2
-
3
- app = create_app(serve_frontend=False)