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
@@ -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"]