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
@@ -0,0 +1,578 @@
1
+ from fastapi.testclient import TestClient
2
+
3
+ from flowent.agent import FLOWENT_AGENT_SYSTEM_PROMPT
4
+ from flowent.main import create_app
5
+
6
+
7
+ def configure_provider(
8
+ client: TestClient,
9
+ *,
10
+ base_url: str = "",
11
+ model: str = "gpt-5.1",
12
+ name: str = "OpenAI",
13
+ provider_id: str = "provider-openai",
14
+ provider_type: str = "openai",
15
+ ) -> None:
16
+ client.post(
17
+ "/api/providers",
18
+ json={
19
+ "api_key": "sk-local",
20
+ "base_url": base_url,
21
+ "id": provider_id,
22
+ "models": [model],
23
+ "name": name,
24
+ "type": provider_type,
25
+ },
26
+ )
27
+ client.put(
28
+ "/api/settings",
29
+ json={
30
+ "selected_model": model,
31
+ "selected_provider_id": provider_id,
32
+ },
33
+ )
34
+
35
+
36
+ def project_context_message(request: dict[str, object]) -> dict[str, object] | None:
37
+ for message in request["messages"]:
38
+ if str(message["content"]).startswith("# AGENTS.md instructions for "):
39
+ return message
40
+ return None
41
+
42
+
43
+ def environment_context_message(request: dict[str, object]) -> dict[str, object]:
44
+ for message in request["messages"]:
45
+ if str(message["content"]).startswith("<environment_context>"):
46
+ return message
47
+ raise AssertionError("Environment context was not sent.")
48
+
49
+
50
+ def stream_events(content: str) -> list[dict[str, object]]:
51
+ events: list[dict[str, object]] = []
52
+ for raw_event in content.strip().split("\n\n"):
53
+ event_type = ""
54
+ data = ""
55
+ for line in raw_event.splitlines():
56
+ if line.startswith("event: "):
57
+ event_type = line.removeprefix("event: ")
58
+ if line.startswith("data: "):
59
+ data = line.removeprefix("data: ")
60
+ events.append({"event": event_type, "data": data})
61
+ return events
62
+
63
+
64
+ def test_workspace_response_streams_selected_provider_model_and_history(
65
+ tmp_path, monkeypatch
66
+ ) -> None:
67
+ monkeypatch.chdir(tmp_path)
68
+ monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path / "data"))
69
+ captured_request: dict[str, object] = {}
70
+
71
+ async def fake_completion(**request: object) -> object:
72
+ captured_request.update(request)
73
+
74
+ async def chunks() -> object:
75
+ yield {"choices": [{"delta": {"content": "Here is "}}]}
76
+ yield {"choices": [{"delta": {"content": "the launch checklist."}}]}
77
+
78
+ return chunks()
79
+
80
+ client = TestClient(
81
+ create_app(serve_frontend=False, chat_completion=fake_completion)
82
+ )
83
+ configure_provider(
84
+ client,
85
+ base_url="https://api.example.test/v1",
86
+ model="claude-sonnet-4-5",
87
+ name="Anthropic",
88
+ provider_id="provider-anthropic",
89
+ provider_type="anthropic",
90
+ )
91
+
92
+ response = client.post(
93
+ "/api/workspace/respond",
94
+ json={"content": "Draft a launch checklist."},
95
+ )
96
+
97
+ assert response.status_code == 200
98
+ assert response.headers["content-type"].startswith("text/event-stream")
99
+ events = stream_events(response.text)
100
+ assert events[0]["event"] == "start"
101
+ assert events[1] == {"event": "output_start", "data": '{"index": 1}'}
102
+ assert events[2] == {"event": "delta", "data": '{"content": "Here is "}'}
103
+ assert events[3] == {
104
+ "event": "delta",
105
+ "data": '{"content": "the launch checklist."}',
106
+ }
107
+ assert '"author": "assistant"' in str(events[4]["data"])
108
+ assert '"content": "Here is the launch checklist."' in str(events[4]["data"])
109
+ assert captured_request["api_base"] == "https://api.example.test/v1"
110
+ assert captured_request["api_key"] == "sk-local"
111
+ assert captured_request["messages"][0] == {
112
+ "role": "system",
113
+ "content": FLOWENT_AGENT_SYSTEM_PROMPT,
114
+ }
115
+ assert project_context_message(captured_request) is None
116
+ assert environment_context_message(captured_request)["role"] == "user"
117
+ assert captured_request["messages"][-1] == {
118
+ "role": "user",
119
+ "content": "Draft a launch checklist.",
120
+ }
121
+ assert captured_request["model"] == "anthropic/claude-sonnet-4-5"
122
+ assert captured_request["stream"] is True
123
+ assert isinstance(captured_request["tools"], list)
124
+
125
+
126
+ def test_workspace_response_requires_selected_provider_and_model(
127
+ tmp_path, monkeypatch
128
+ ) -> None:
129
+ monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path))
130
+ client = TestClient(create_app(serve_frontend=False))
131
+
132
+ response = client.post(
133
+ "/api/workspace/respond",
134
+ json={"content": "Draft a launch checklist."},
135
+ )
136
+
137
+ assert response.status_code == 400
138
+ assert response.json()["detail"] == "Choose a provider and model before sending."
139
+
140
+
141
+ def test_workspace_compact_persists_compacted_context(tmp_path, monkeypatch) -> None:
142
+ monkeypatch.chdir(tmp_path)
143
+ monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path / "data"))
144
+ captured_request: dict[str, object] = {}
145
+
146
+ async def fake_completion(**request: object) -> dict[str, object]:
147
+ captured_request.update(request)
148
+ return {
149
+ "choices": [
150
+ {
151
+ "message": {
152
+ "content": "Keep the launch checklist and provider setup decisions.",
153
+ "role": "assistant",
154
+ }
155
+ }
156
+ ]
157
+ }
158
+
159
+ client = TestClient(
160
+ create_app(serve_frontend=False, chat_completion=fake_completion)
161
+ )
162
+ configure_provider(client)
163
+ client.put(
164
+ "/api/workspace/messages",
165
+ json={
166
+ "messages": [
167
+ {
168
+ "author": "user",
169
+ "content": "Draft a launch checklist.",
170
+ "id": "message-1",
171
+ },
172
+ {
173
+ "author": "assistant",
174
+ "content": "Use provider setup first.",
175
+ "id": "message-2",
176
+ },
177
+ ]
178
+ },
179
+ )
180
+
181
+ response = client.post("/api/workspace/compact")
182
+
183
+ assert response.status_code == 200
184
+ body = response.json()
185
+ assert body == {
186
+ "message": {
187
+ "author": "system",
188
+ "content": "Context compacted",
189
+ "id": body["message"]["id"],
190
+ "tools": [],
191
+ }
192
+ }
193
+ assert captured_request["model"] == "openai/gpt-5.1"
194
+ assert captured_request["messages"][0] == {
195
+ "role": "system",
196
+ "content": "You are compacting Flowent workspace context.",
197
+ }
198
+ assert "AGENTS.md instructions" not in captured_request["messages"][-1]["content"]
199
+ assert "<environment_context>" in captured_request["messages"][-1]["content"]
200
+ assert captured_request["messages"][-1]["role"] == "user"
201
+ assert "Draft a launch checklist." in captured_request["messages"][-1]["content"]
202
+ assert "Use provider setup first." in captured_request["messages"][-1]["content"]
203
+
204
+ state = client.get("/api/state").json()
205
+ assert state["messages"][-1] == body["message"]
206
+
207
+
208
+ def test_workspace_response_uses_compacted_context_after_compact(
209
+ tmp_path, monkeypatch
210
+ ) -> None:
211
+ monkeypatch.chdir(tmp_path)
212
+ monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path / "data"))
213
+ captured_requests: list[dict[str, object]] = []
214
+
215
+ async def fake_completion(**request: object) -> object:
216
+ captured_requests.append(request)
217
+ if len(captured_requests) == 1:
218
+ return {
219
+ "choices": [
220
+ {
221
+ "message": {
222
+ "content": "Keep the provider setup decision.",
223
+ "role": "assistant",
224
+ }
225
+ }
226
+ ]
227
+ }
228
+
229
+ async def chunks() -> object:
230
+ yield {"choices": [{"delta": {"content": "Continuing."}}]}
231
+
232
+ return chunks()
233
+
234
+ client = TestClient(
235
+ create_app(serve_frontend=False, chat_completion=fake_completion)
236
+ )
237
+ configure_provider(client)
238
+ client.put(
239
+ "/api/workspace/messages",
240
+ json={
241
+ "messages": [
242
+ {
243
+ "author": "user",
244
+ "content": "Original detailed request.",
245
+ "id": "message-1",
246
+ },
247
+ {
248
+ "author": "assistant",
249
+ "content": "Original detailed reply.",
250
+ "id": "message-2",
251
+ },
252
+ ]
253
+ },
254
+ )
255
+
256
+ compact_response = client.post("/api/workspace/compact")
257
+ response = client.post(
258
+ "/api/workspace/respond",
259
+ json={"content": "Continue from there."},
260
+ )
261
+
262
+ assert compact_response.status_code == 200
263
+ assert response.status_code == 200
264
+ response_messages = captured_requests[1]["messages"]
265
+ assert response_messages[0] == {
266
+ "role": "system",
267
+ "content": FLOWENT_AGENT_SYSTEM_PROMPT,
268
+ }
269
+ assert project_context_message(captured_requests[1]) is None
270
+ assert environment_context_message(captured_requests[1])["role"] == "user"
271
+ assert response_messages[-3:] == [
272
+ {"role": "user", "content": "Context compacted"},
273
+ {"role": "assistant", "content": "Keep the provider setup decision."},
274
+ {"role": "user", "content": "Continue from there."},
275
+ ]
276
+
277
+
278
+ def test_workspace_response_includes_project_and_environment_context(
279
+ tmp_path, monkeypatch
280
+ ) -> None:
281
+ monkeypatch.chdir(tmp_path)
282
+ monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path / "data"))
283
+ (tmp_path / ".git").mkdir()
284
+ (tmp_path / "AGENTS.md").write_text("Use concise replies.")
285
+ captured_request: dict[str, object] = {}
286
+
287
+ async def fake_completion(**request: object) -> object:
288
+ captured_request.update(request)
289
+
290
+ async def chunks() -> object:
291
+ yield {"choices": [{"delta": {"content": "Done."}}]}
292
+
293
+ return chunks()
294
+
295
+ client = TestClient(
296
+ create_app(serve_frontend=False, chat_completion=fake_completion)
297
+ )
298
+ configure_provider(client)
299
+
300
+ response = client.post("/api/workspace/respond", json={"content": "Hello."})
301
+
302
+ assert response.status_code == 200
303
+ assert captured_request["messages"][0] == {
304
+ "role": "system",
305
+ "content": FLOWENT_AGENT_SYSTEM_PROMPT,
306
+ }
307
+ project_message = project_context_message(captured_request)
308
+ assert project_message == {
309
+ "role": "user",
310
+ "content": (
311
+ f"# AGENTS.md instructions for {tmp_path}\n\n"
312
+ "<INSTRUCTIONS>\nUse concise replies.\n</INSTRUCTIONS>"
313
+ ),
314
+ }
315
+ environment_message = environment_context_message(captured_request)
316
+ assert environment_message["role"] == "user"
317
+ assert f"<cwd>{tmp_path}</cwd>" in environment_message["content"]
318
+ assert "<filesystem>workspace-write</filesystem>" in environment_message["content"]
319
+ assert "<network>enabled</network>" in environment_message["content"]
320
+ assert "<tool>read_file</tool>" in environment_message["content"]
321
+ assert captured_request["messages"][-1] == {
322
+ "role": "user",
323
+ "content": "Hello.",
324
+ }
325
+
326
+
327
+ def test_workspace_response_prefers_agents_override(tmp_path, monkeypatch) -> None:
328
+ monkeypatch.chdir(tmp_path)
329
+ monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path / "data"))
330
+ (tmp_path / ".git").mkdir()
331
+ (tmp_path / "AGENTS.md").write_text("Versioned instructions.")
332
+ (tmp_path / "AGENTS.override.md").write_text("Local override instructions.")
333
+ captured_request: dict[str, object] = {}
334
+
335
+ async def fake_completion(**request: object) -> object:
336
+ captured_request.update(request)
337
+
338
+ async def chunks() -> object:
339
+ yield {"choices": [{"delta": {"content": "Done."}}]}
340
+
341
+ return chunks()
342
+
343
+ client = TestClient(
344
+ create_app(serve_frontend=False, chat_completion=fake_completion)
345
+ )
346
+ configure_provider(client)
347
+
348
+ response = client.post("/api/workspace/respond", json={"content": "Hello."})
349
+
350
+ assert response.status_code == 200
351
+ project_message = project_context_message(captured_request)
352
+ assert project_message is not None
353
+ assert "Local override instructions." in project_message["content"]
354
+ assert "Versioned instructions." not in project_message["content"]
355
+
356
+
357
+ def test_workspace_response_merges_project_instructions_from_root_to_cwd(
358
+ tmp_path, monkeypatch
359
+ ) -> None:
360
+ repo = tmp_path / "repo"
361
+ nested = repo / "packages" / "agent"
362
+ nested.mkdir(parents=True)
363
+ (repo / ".git").mkdir()
364
+ (repo / "AGENTS.md").write_text("Root instructions.")
365
+ (nested / "AGENTS.md").write_text("Nested instructions.")
366
+ monkeypatch.chdir(nested)
367
+ monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path / "data"))
368
+ captured_request: dict[str, object] = {}
369
+
370
+ async def fake_completion(**request: object) -> object:
371
+ captured_request.update(request)
372
+
373
+ async def chunks() -> object:
374
+ yield {"choices": [{"delta": {"content": "Done."}}]}
375
+
376
+ return chunks()
377
+
378
+ client = TestClient(
379
+ create_app(serve_frontend=False, chat_completion=fake_completion)
380
+ )
381
+ configure_provider(client)
382
+
383
+ response = client.post("/api/workspace/respond", json={"content": "Hello."})
384
+
385
+ assert response.status_code == 200
386
+ project_message = project_context_message(captured_request)
387
+ assert project_message is not None
388
+ assert project_message["content"].index("Root instructions.") < project_message[
389
+ "content"
390
+ ].index("Nested instructions.")
391
+
392
+
393
+ def test_workspace_response_uses_updated_project_instructions(
394
+ tmp_path, monkeypatch
395
+ ) -> None:
396
+ monkeypatch.chdir(tmp_path)
397
+ monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path / "data"))
398
+ (tmp_path / ".git").mkdir()
399
+ agents_file = tmp_path / "AGENTS.md"
400
+ agents_file.write_text("Old instructions.")
401
+ captured_requests: list[dict[str, object]] = []
402
+
403
+ async def fake_completion(**request: object) -> object:
404
+ captured_requests.append(request)
405
+
406
+ async def chunks() -> object:
407
+ yield {"choices": [{"delta": {"content": "Done."}}]}
408
+
409
+ return chunks()
410
+
411
+ client = TestClient(
412
+ create_app(serve_frontend=False, chat_completion=fake_completion)
413
+ )
414
+ configure_provider(client)
415
+
416
+ first_response = client.post("/api/workspace/respond", json={"content": "First."})
417
+ agents_file.write_text("Updated instructions.")
418
+ second_response = client.post("/api/workspace/respond", json={"content": "Second."})
419
+
420
+ assert first_response.status_code == 200
421
+ assert second_response.status_code == 200
422
+ first_project_message = project_context_message(captured_requests[0])
423
+ second_project_message = project_context_message(captured_requests[1])
424
+ assert first_project_message is not None
425
+ assert second_project_message is not None
426
+ assert "Old instructions." in first_project_message["content"]
427
+ assert "Updated instructions." in second_project_message["content"]
428
+ assert "Old instructions." not in second_project_message["content"]
429
+
430
+
431
+ def test_workspace_context_is_not_persisted_in_state(tmp_path, monkeypatch) -> None:
432
+ monkeypatch.chdir(tmp_path)
433
+ monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path / "data"))
434
+ (tmp_path / ".git").mkdir()
435
+ (tmp_path / "AGENTS.md").write_text("Hidden instructions.")
436
+
437
+ async def fake_completion(**request: object) -> object:
438
+ async def chunks() -> object:
439
+ yield {"choices": [{"delta": {"content": "Done."}}]}
440
+
441
+ return chunks()
442
+
443
+ client = TestClient(
444
+ create_app(serve_frontend=False, chat_completion=fake_completion)
445
+ )
446
+ configure_provider(client)
447
+
448
+ response = client.post("/api/workspace/respond", json={"content": "Hello."})
449
+ state = client.get("/api/state").json()
450
+
451
+ assert response.status_code == 200
452
+ persisted_content = "\n".join(message["content"] for message in state["messages"])
453
+ assert "Hidden instructions." not in persisted_content
454
+ assert "<environment_context>" not in persisted_content
455
+
456
+
457
+ def test_workspace_clear_keeps_runtime_context_available(tmp_path, monkeypatch) -> None:
458
+ monkeypatch.chdir(tmp_path)
459
+ monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path / "data"))
460
+ (tmp_path / ".git").mkdir()
461
+ (tmp_path / "AGENTS.md").write_text("Instructions after clear.")
462
+ captured_requests: list[dict[str, object]] = []
463
+
464
+ async def fake_completion(**request: object) -> object:
465
+ captured_requests.append(request)
466
+
467
+ async def chunks() -> object:
468
+ yield {"choices": [{"delta": {"content": "Done."}}]}
469
+
470
+ return chunks()
471
+
472
+ client = TestClient(
473
+ create_app(serve_frontend=False, chat_completion=fake_completion)
474
+ )
475
+ configure_provider(client)
476
+
477
+ first_response = client.post("/api/workspace/respond", json={"content": "First."})
478
+ clear_response = client.put("/api/workspace/messages", json={"messages": []})
479
+ second_response = client.post("/api/workspace/respond", json={"content": "Second."})
480
+
481
+ assert first_response.status_code == 200
482
+ assert clear_response.status_code == 200
483
+ assert second_response.status_code == 200
484
+ project_message = project_context_message(captured_requests[1])
485
+ assert project_message is not None
486
+ assert "Instructions after clear." in project_message["content"]
487
+ assert environment_context_message(captured_requests[1])["role"] == "user"
488
+
489
+
490
+ def test_workspace_compacted_response_includes_latest_runtime_context(
491
+ tmp_path, monkeypatch
492
+ ) -> None:
493
+ monkeypatch.chdir(tmp_path)
494
+ monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path / "data"))
495
+ (tmp_path / ".git").mkdir()
496
+ agents_file = tmp_path / "AGENTS.md"
497
+ agents_file.write_text("Instructions before compact.")
498
+ captured_requests: list[dict[str, object]] = []
499
+
500
+ async def fake_completion(**request: object) -> object:
501
+ captured_requests.append(request)
502
+ if len(captured_requests) == 1:
503
+ return {
504
+ "choices": [
505
+ {
506
+ "message": {
507
+ "content": "Keep compacted state.",
508
+ "role": "assistant",
509
+ }
510
+ }
511
+ ]
512
+ }
513
+
514
+ async def chunks() -> object:
515
+ yield {"choices": [{"delta": {"content": "Done."}}]}
516
+
517
+ return chunks()
518
+
519
+ client = TestClient(
520
+ create_app(serve_frontend=False, chat_completion=fake_completion)
521
+ )
522
+ configure_provider(client)
523
+ client.put(
524
+ "/api/workspace/messages",
525
+ json={
526
+ "messages": [
527
+ {"author": "user", "content": "Original request.", "id": "message-1"}
528
+ ]
529
+ },
530
+ )
531
+
532
+ compact_response = client.post("/api/workspace/compact")
533
+ agents_file.write_text("Instructions after compact.")
534
+ response = client.post("/api/workspace/respond", json={"content": "Continue."})
535
+
536
+ assert compact_response.status_code == 200
537
+ assert response.status_code == 200
538
+ response_messages = captured_requests[1]["messages"]
539
+ project_message = project_context_message(captured_requests[1])
540
+ assert project_message is not None
541
+ assert "Instructions after compact." in project_message["content"]
542
+ assert environment_context_message(captured_requests[1])["role"] == "user"
543
+ assert {
544
+ "role": "assistant",
545
+ "content": "Keep compacted state.",
546
+ } in response_messages
547
+
548
+
549
+ def test_project_instructions_are_truncated_to_size_limit(
550
+ tmp_path, monkeypatch
551
+ ) -> None:
552
+ monkeypatch.chdir(tmp_path)
553
+ monkeypatch.setenv("FLOWENT_DATA_DIR", str(tmp_path / "data"))
554
+ monkeypatch.setenv("FLOWENT_PROJECT_INSTRUCTIONS_MAX_BYTES", "12")
555
+ (tmp_path / ".git").mkdir()
556
+ (tmp_path / "AGENTS.md").write_text("1234567890abcdef")
557
+ captured_request: dict[str, object] = {}
558
+
559
+ async def fake_completion(**request: object) -> object:
560
+ captured_request.update(request)
561
+
562
+ async def chunks() -> object:
563
+ yield {"choices": [{"delta": {"content": "Done."}}]}
564
+
565
+ return chunks()
566
+
567
+ client = TestClient(
568
+ create_app(serve_frontend=False, chat_completion=fake_completion)
569
+ )
570
+ configure_provider(client)
571
+
572
+ response = client.post("/api/workspace/respond", json={"content": "Hello."})
573
+
574
+ assert response.status_code == 200
575
+ project_message = project_context_message(captured_request)
576
+ assert project_message is not None
577
+ assert "1234567890ab" in project_message["content"]
578
+ assert "cdef" not in project_message["content"]