flowent 0.0.6 → 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (398) hide show
  1. package/README.md +1 -4
  2. package/backend/README.md +1 -4
  3. package/backend/pyproject.toml +2 -8
  4. package/backend/src/flowent/__pycache__/__init__.cpython-313.pyc +0 -0
  5. package/backend/src/flowent/__pycache__/agent.cpython-313.pyc +0 -0
  6. package/backend/src/flowent/__pycache__/cli.cpython-313.pyc +0 -0
  7. package/backend/src/flowent/__pycache__/context.cpython-313.pyc +0 -0
  8. package/backend/src/flowent/__pycache__/llm.cpython-313.pyc +0 -0
  9. package/backend/src/flowent/__pycache__/logging.cpython-313.pyc +0 -0
  10. package/backend/src/flowent/__pycache__/main.cpython-313.pyc +0 -0
  11. package/backend/src/flowent/__pycache__/patch.cpython-313.pyc +0 -0
  12. package/backend/src/flowent/__pycache__/paths.cpython-313.pyc +0 -0
  13. package/backend/src/flowent/__pycache__/sandbox.cpython-313.pyc +0 -0
  14. package/backend/src/flowent/__pycache__/storage.cpython-313.pyc +0 -0
  15. package/backend/src/flowent/__pycache__/tools.cpython-313.pyc +0 -0
  16. package/backend/src/flowent/agent.py +217 -3094
  17. package/backend/src/flowent/cli.py +19 -24
  18. package/backend/src/flowent/context.py +127 -0
  19. package/backend/src/flowent/llm.py +256 -0
  20. package/backend/src/flowent/logging.py +170 -129
  21. package/backend/src/flowent/main.py +321 -70
  22. package/backend/src/flowent/patch.py +182 -0
  23. package/backend/src/flowent/paths.py +11 -0
  24. package/backend/src/flowent/sandbox.py +214 -40
  25. package/backend/src/flowent/static/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
  26. package/backend/src/flowent/static/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
  27. package/backend/src/flowent/static/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
  28. package/backend/src/flowent/static/assets/index-C76K95ty.js +81 -0
  29. package/backend/src/flowent/static/assets/index-iUMNKvlU.css +2 -0
  30. package/backend/src/flowent/static/flowent.png +0 -0
  31. package/backend/src/flowent/static/index.html +5 -25
  32. package/backend/src/flowent/storage.py +302 -0
  33. package/backend/src/flowent/tools.py +364 -0
  34. package/backend/tests/__pycache__/test_agent_tools.cpython-313-pytest-9.0.3.pyc +0 -0
  35. package/backend/tests/__pycache__/test_health.cpython-313-pytest-9.0.3.pyc +0 -0
  36. package/backend/tests/__pycache__/test_llm_providers.cpython-313-pytest-9.0.3.pyc +0 -0
  37. package/backend/tests/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
  38. package/backend/tests/__pycache__/test_persistence.cpython-313-pytest-9.0.3.pyc +0 -0
  39. package/backend/tests/__pycache__/test_workspace_chat.cpython-313-pytest-9.0.3.pyc +0 -0
  40. package/backend/tests/test_agent_tools.py +449 -0
  41. package/backend/tests/test_health.py +12 -0
  42. package/backend/tests/test_llm_providers.py +113 -0
  43. package/backend/tests/test_logging.py +182 -0
  44. package/backend/tests/test_persistence.py +125 -0
  45. package/backend/tests/test_workspace_chat.py +578 -0
  46. package/backend/uv.lock +803 -99
  47. package/dist/frontend/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
  48. package/dist/frontend/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
  49. package/dist/frontend/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
  50. package/dist/frontend/assets/index-C76K95ty.js +81 -0
  51. package/dist/frontend/assets/index-iUMNKvlU.css +2 -0
  52. package/dist/frontend/flowent.png +0 -0
  53. package/dist/frontend/index.html +5 -25
  54. package/package.json +1 -2
  55. package/backend/src/flowent/__pycache__/_version.cpython-313.pyc +0 -0
  56. package/backend/src/flowent/__pycache__/access.cpython-313.pyc +0 -0
  57. package/backend/src/flowent/__pycache__/assistant_commands.cpython-313.pyc +0 -0
  58. package/backend/src/flowent/__pycache__/config.cpython-313.pyc +0 -0
  59. package/backend/src/flowent/__pycache__/events.cpython-313.pyc +0 -0
  60. package/backend/src/flowent/__pycache__/graph_runtime.cpython-313.pyc +0 -0
  61. package/backend/src/flowent/__pycache__/graph_service.cpython-313.pyc +0 -0
  62. package/backend/src/flowent/__pycache__/image_assets.cpython-313.pyc +0 -0
  63. package/backend/src/flowent/__pycache__/mcp_service.cpython-313.pyc +0 -0
  64. package/backend/src/flowent/__pycache__/model_metadata.cpython-313.pyc +0 -0
  65. package/backend/src/flowent/__pycache__/network.cpython-313.pyc +0 -0
  66. package/backend/src/flowent/__pycache__/observability_service.cpython-313.pyc +0 -0
  67. package/backend/src/flowent/__pycache__/registry.cpython-313.pyc +0 -0
  68. package/backend/src/flowent/__pycache__/role_management.cpython-313.pyc +0 -0
  69. package/backend/src/flowent/__pycache__/runtime.cpython-313.pyc +0 -0
  70. package/backend/src/flowent/__pycache__/security.cpython-313.pyc +0 -0
  71. package/backend/src/flowent/__pycache__/settings.cpython-313.pyc +0 -0
  72. package/backend/src/flowent/__pycache__/settings_management.cpython-313.pyc +0 -0
  73. package/backend/src/flowent/__pycache__/state_db.cpython-313.pyc +0 -0
  74. package/backend/src/flowent/__pycache__/workspace_store.cpython-313.pyc +0 -0
  75. package/backend/src/flowent/access.py +0 -247
  76. package/backend/src/flowent/assistant_commands.py +0 -115
  77. package/backend/src/flowent/channels/__init__.py +0 -3
  78. package/backend/src/flowent/channels/__pycache__/__init__.cpython-313.pyc +0 -0
  79. package/backend/src/flowent/channels/__pycache__/telegram.cpython-313.pyc +0 -0
  80. package/backend/src/flowent/channels/telegram.py +0 -615
  81. package/backend/src/flowent/config.py +0 -14
  82. package/backend/src/flowent/dev.py +0 -3
  83. package/backend/src/flowent/events.py +0 -157
  84. package/backend/src/flowent/graph_runtime.py +0 -60
  85. package/backend/src/flowent/graph_service.py +0 -2508
  86. package/backend/src/flowent/image_assets.py +0 -356
  87. package/backend/src/flowent/mcp_service.py +0 -1918
  88. package/backend/src/flowent/model_metadata.py +0 -102
  89. package/backend/src/flowent/models/__init__.py +0 -125
  90. package/backend/src/flowent/models/__pycache__/__init__.cpython-313.pyc +0 -0
  91. package/backend/src/flowent/models/__pycache__/agent.cpython-313.pyc +0 -0
  92. package/backend/src/flowent/models/__pycache__/base.cpython-313.pyc +0 -0
  93. package/backend/src/flowent/models/__pycache__/blueprint.cpython-313.pyc +0 -0
  94. package/backend/src/flowent/models/__pycache__/content.cpython-313.pyc +0 -0
  95. package/backend/src/flowent/models/__pycache__/delta.cpython-313.pyc +0 -0
  96. package/backend/src/flowent/models/__pycache__/event.cpython-313.pyc +0 -0
  97. package/backend/src/flowent/models/__pycache__/graph.cpython-313.pyc +0 -0
  98. package/backend/src/flowent/models/__pycache__/history.cpython-313.pyc +0 -0
  99. package/backend/src/flowent/models/__pycache__/llm.cpython-313.pyc +0 -0
  100. package/backend/src/flowent/models/__pycache__/message.cpython-313.pyc +0 -0
  101. package/backend/src/flowent/models/__pycache__/tab.cpython-313.pyc +0 -0
  102. package/backend/src/flowent/models/__pycache__/todo.cpython-313.pyc +0 -0
  103. package/backend/src/flowent/models/agent.py +0 -34
  104. package/backend/src/flowent/models/base.py +0 -24
  105. package/backend/src/flowent/models/blueprint.py +0 -176
  106. package/backend/src/flowent/models/content.py +0 -164
  107. package/backend/src/flowent/models/delta.py +0 -44
  108. package/backend/src/flowent/models/event.py +0 -51
  109. package/backend/src/flowent/models/graph.py +0 -472
  110. package/backend/src/flowent/models/history.py +0 -272
  111. package/backend/src/flowent/models/llm.py +0 -62
  112. package/backend/src/flowent/models/message.py +0 -33
  113. package/backend/src/flowent/models/tab.py +0 -85
  114. package/backend/src/flowent/models/todo.py +0 -10
  115. package/backend/src/flowent/network.py +0 -146
  116. package/backend/src/flowent/observability_service.py +0 -218
  117. package/backend/src/flowent/prompts/__init__.py +0 -67
  118. package/backend/src/flowent/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
  119. package/backend/src/flowent/prompts/__pycache__/common.cpython-313.pyc +0 -0
  120. package/backend/src/flowent/prompts/__pycache__/steward.cpython-313.pyc +0 -0
  121. package/backend/src/flowent/prompts/common.py +0 -250
  122. package/backend/src/flowent/prompts/steward.py +0 -64
  123. package/backend/src/flowent/providers/__init__.py +0 -23
  124. package/backend/src/flowent/providers/__pycache__/__init__.cpython-313.pyc +0 -0
  125. package/backend/src/flowent/providers/__pycache__/anthropic.cpython-313.pyc +0 -0
  126. package/backend/src/flowent/providers/__pycache__/base_url.cpython-313.pyc +0 -0
  127. package/backend/src/flowent/providers/__pycache__/configuration.cpython-313.pyc +0 -0
  128. package/backend/src/flowent/providers/__pycache__/content.cpython-313.pyc +0 -0
  129. package/backend/src/flowent/providers/__pycache__/errors.cpython-313.pyc +0 -0
  130. package/backend/src/flowent/providers/__pycache__/gateway.cpython-313.pyc +0 -0
  131. package/backend/src/flowent/providers/__pycache__/headers.cpython-313.pyc +0 -0
  132. package/backend/src/flowent/providers/__pycache__/management.cpython-313.pyc +0 -0
  133. package/backend/src/flowent/providers/__pycache__/openai.cpython-313.pyc +0 -0
  134. package/backend/src/flowent/providers/__pycache__/openai_responses.cpython-313.pyc +0 -0
  135. package/backend/src/flowent/providers/__pycache__/registry.cpython-313.pyc +0 -0
  136. package/backend/src/flowent/providers/__pycache__/sse.cpython-313.pyc +0 -0
  137. package/backend/src/flowent/providers/__pycache__/thinking.cpython-313.pyc +0 -0
  138. package/backend/src/flowent/providers/anthropic.py +0 -468
  139. package/backend/src/flowent/providers/base_url.py +0 -60
  140. package/backend/src/flowent/providers/configuration.py +0 -189
  141. package/backend/src/flowent/providers/content.py +0 -122
  142. package/backend/src/flowent/providers/errors.py +0 -223
  143. package/backend/src/flowent/providers/gateway.py +0 -169
  144. package/backend/src/flowent/providers/gemini.py +0 -447
  145. package/backend/src/flowent/providers/headers.py +0 -20
  146. package/backend/src/flowent/providers/management.py +0 -96
  147. package/backend/src/flowent/providers/ollama.py +0 -293
  148. package/backend/src/flowent/providers/openai.py +0 -422
  149. package/backend/src/flowent/providers/openai_responses.py +0 -655
  150. package/backend/src/flowent/providers/registry.py +0 -144
  151. package/backend/src/flowent/providers/sse.py +0 -31
  152. package/backend/src/flowent/providers/thinking.py +0 -79
  153. package/backend/src/flowent/registry.py +0 -73
  154. package/backend/src/flowent/role_management.py +0 -267
  155. package/backend/src/flowent/routes/__init__.py +0 -28
  156. package/backend/src/flowent/routes/__pycache__/__init__.cpython-313.pyc +0 -0
  157. package/backend/src/flowent/routes/__pycache__/access.cpython-313.pyc +0 -0
  158. package/backend/src/flowent/routes/__pycache__/assistant.cpython-313.pyc +0 -0
  159. package/backend/src/flowent/routes/__pycache__/image_assets.cpython-313.pyc +0 -0
  160. package/backend/src/flowent/routes/__pycache__/mcp.cpython-313.pyc +0 -0
  161. package/backend/src/flowent/routes/__pycache__/meta.cpython-313.pyc +0 -0
  162. package/backend/src/flowent/routes/__pycache__/nodes.cpython-313.pyc +0 -0
  163. package/backend/src/flowent/routes/__pycache__/prompts.cpython-313.pyc +0 -0
  164. package/backend/src/flowent/routes/__pycache__/providers_route.cpython-313.pyc +0 -0
  165. package/backend/src/flowent/routes/__pycache__/roles.cpython-313.pyc +0 -0
  166. package/backend/src/flowent/routes/__pycache__/settings.cpython-313.pyc +0 -0
  167. package/backend/src/flowent/routes/__pycache__/tabs.cpython-313.pyc +0 -0
  168. package/backend/src/flowent/routes/__pycache__/ws.cpython-313.pyc +0 -0
  169. package/backend/src/flowent/routes/access.py +0 -48
  170. package/backend/src/flowent/routes/assistant.py +0 -155
  171. package/backend/src/flowent/routes/image_assets.py +0 -33
  172. package/backend/src/flowent/routes/mcp.py +0 -125
  173. package/backend/src/flowent/routes/meta.py +0 -28
  174. package/backend/src/flowent/routes/nodes.py +0 -413
  175. package/backend/src/flowent/routes/prompts.py +0 -46
  176. package/backend/src/flowent/routes/providers_route.py +0 -365
  177. package/backend/src/flowent/routes/roles.py +0 -207
  178. package/backend/src/flowent/routes/settings.py +0 -328
  179. package/backend/src/flowent/routes/tabs.py +0 -310
  180. package/backend/src/flowent/routes/ws.py +0 -33
  181. package/backend/src/flowent/runtime.py +0 -165
  182. package/backend/src/flowent/security.py +0 -57
  183. package/backend/src/flowent/settings.py +0 -2518
  184. package/backend/src/flowent/settings_management.py +0 -298
  185. package/backend/src/flowent/state_db.py +0 -120
  186. package/backend/src/flowent/static/assets/AssistantPage-VBohhz4d.js +0 -1
  187. package/backend/src/flowent/static/assets/ChannelsPage-CIydPZA_.js +0 -1
  188. package/backend/src/flowent/static/assets/McpPage-CHPm2TPY.js +0 -7
  189. package/backend/src/flowent/static/assets/PageScaffold-DteOA8V7.js +0 -1
  190. package/backend/src/flowent/static/assets/PromptsPage-CSmJ3sZg.js +0 -1
  191. package/backend/src/flowent/static/assets/ProvidersPage-sl2jeG4e.js +0 -3
  192. package/backend/src/flowent/static/assets/RolesPage-DCe7W6Km.js +0 -1
  193. package/backend/src/flowent/static/assets/SettingsPage-Bix9e63E.js +0 -3
  194. package/backend/src/flowent/static/assets/ToolsPage-favNkj5C.js +0 -1
  195. package/backend/src/flowent/static/assets/WorkspaceCommandDialog-DRS6wiD6.js +0 -1
  196. package/backend/src/flowent/static/assets/WorkspacePage-KuaDjt_D.js +0 -3
  197. package/backend/src/flowent/static/assets/WorkspacePanels-BZxBw8M5.js +0 -1
  198. package/backend/src/flowent/static/assets/alert-dialog-DIBUCmqM.js +0 -1
  199. package/backend/src/flowent/static/assets/datetime-eJqd0V2S.js +0 -1
  200. package/backend/src/flowent/static/assets/dialog-BOvHIBrg.js +0 -1
  201. package/backend/src/flowent/static/assets/elk-worker.min-C9JGDOE-.js +0 -6312
  202. package/backend/src/flowent/static/assets/graph-vendor-CHpVij2M.css +0 -1
  203. package/backend/src/flowent/static/assets/graph-vendor-DRq_-6fV.js +0 -7
  204. package/backend/src/flowent/static/assets/index-Biio-CoI.js +0 -10
  205. package/backend/src/flowent/static/assets/index-CmQvO7sl.css +0 -1
  206. package/backend/src/flowent/static/assets/layout.worker-jMHqAFbP.js +0 -24
  207. package/backend/src/flowent/static/assets/markdown-vendor-C9RtvaJh.js +0 -29
  208. package/backend/src/flowent/static/assets/modelParams-DcEhGnu0.js +0 -1
  209. package/backend/src/flowent/static/assets/react-vendor-mEs_JJxa.js +0 -9
  210. package/backend/src/flowent/static/assets/roles-BbIEIMeG.js +0 -1
  211. package/backend/src/flowent/static/assets/rolldown-runtime-BYbx6iT9.js +0 -1
  212. package/backend/src/flowent/static/assets/select-D9SwnlXF.js +0 -1
  213. package/backend/src/flowent/static/assets/surface-Bzr1FRG4.js +0 -1
  214. package/backend/src/flowent/static/assets/triState-DgLlKdRR.js +0 -1
  215. package/backend/src/flowent/static/assets/ui-vendor-UazN8rcv.js +0 -51
  216. package/backend/src/flowent/static/favicon.svg +0 -4
  217. package/backend/src/flowent/tools/__init__.py +0 -275
  218. package/backend/src/flowent/tools/__pycache__/__init__.cpython-313.pyc +0 -0
  219. package/backend/src/flowent/tools/__pycache__/connect.cpython-313.pyc +0 -0
  220. package/backend/src/flowent/tools/__pycache__/contacts.cpython-313.pyc +0 -0
  221. package/backend/src/flowent/tools/__pycache__/create_agent.cpython-313.pyc +0 -0
  222. package/backend/src/flowent/tools/__pycache__/create_tab.cpython-313.pyc +0 -0
  223. package/backend/src/flowent/tools/__pycache__/delete_tab.cpython-313.pyc +0 -0
  224. package/backend/src/flowent/tools/__pycache__/edit.cpython-313.pyc +0 -0
  225. package/backend/src/flowent/tools/__pycache__/exec.cpython-313.pyc +0 -0
  226. package/backend/src/flowent/tools/__pycache__/fetch.cpython-313.pyc +0 -0
  227. package/backend/src/flowent/tools/__pycache__/idle.cpython-313.pyc +0 -0
  228. package/backend/src/flowent/tools/__pycache__/list_roles.cpython-313.pyc +0 -0
  229. package/backend/src/flowent/tools/__pycache__/list_tabs.cpython-313.pyc +0 -0
  230. package/backend/src/flowent/tools/__pycache__/list_tools.cpython-313.pyc +0 -0
  231. package/backend/src/flowent/tools/__pycache__/manage_prompts.cpython-313.pyc +0 -0
  232. package/backend/src/flowent/tools/__pycache__/manage_providers.cpython-313.pyc +0 -0
  233. package/backend/src/flowent/tools/__pycache__/manage_roles.cpython-313.pyc +0 -0
  234. package/backend/src/flowent/tools/__pycache__/manage_settings.cpython-313.pyc +0 -0
  235. package/backend/src/flowent/tools/__pycache__/mcp.cpython-313.pyc +0 -0
  236. package/backend/src/flowent/tools/__pycache__/read.cpython-313.pyc +0 -0
  237. package/backend/src/flowent/tools/__pycache__/send.cpython-313.pyc +0 -0
  238. package/backend/src/flowent/tools/__pycache__/set_permissions.cpython-313.pyc +0 -0
  239. package/backend/src/flowent/tools/__pycache__/sleep.cpython-313.pyc +0 -0
  240. package/backend/src/flowent/tools/__pycache__/todo.cpython-313.pyc +0 -0
  241. package/backend/src/flowent/tools/connect.py +0 -100
  242. package/backend/src/flowent/tools/contacts.py +0 -22
  243. package/backend/src/flowent/tools/create_agent.py +0 -191
  244. package/backend/src/flowent/tools/create_tab.py +0 -61
  245. package/backend/src/flowent/tools/delete_tab.py +0 -39
  246. package/backend/src/flowent/tools/edit.py +0 -142
  247. package/backend/src/flowent/tools/exec.py +0 -118
  248. package/backend/src/flowent/tools/fetch.py +0 -85
  249. package/backend/src/flowent/tools/idle.py +0 -27
  250. package/backend/src/flowent/tools/list_roles.py +0 -75
  251. package/backend/src/flowent/tools/list_tabs.py +0 -100
  252. package/backend/src/flowent/tools/list_tools.py +0 -28
  253. package/backend/src/flowent/tools/manage_prompts.py +0 -102
  254. package/backend/src/flowent/tools/manage_providers.py +0 -220
  255. package/backend/src/flowent/tools/manage_roles.py +0 -275
  256. package/backend/src/flowent/tools/manage_settings.py +0 -364
  257. package/backend/src/flowent/tools/mcp.py +0 -199
  258. package/backend/src/flowent/tools/read.py +0 -152
  259. package/backend/src/flowent/tools/send.py +0 -68
  260. package/backend/src/flowent/tools/set_permissions.py +0 -99
  261. package/backend/src/flowent/tools/sleep.py +0 -41
  262. package/backend/src/flowent/tools/todo.py +0 -51
  263. package/backend/src/flowent/workspace_store.py +0 -479
  264. package/backend/tests/__init__.py +0 -0
  265. package/backend/tests/__pycache__/__init__.cpython-313.pyc +0 -0
  266. package/backend/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  267. package/backend/tests/conftest.py +0 -6
  268. package/backend/tests/integration/api/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
  269. package/backend/tests/integration/api/__pycache__/test_access_api.cpython-313-pytest-9.0.3.pyc +0 -0
  270. package/backend/tests/integration/api/__pycache__/test_assistant_api.cpython-313-pytest-9.0.3.pyc +0 -0
  271. package/backend/tests/integration/api/__pycache__/test_frontend_mounting.cpython-313-pytest-9.0.3.pyc +0 -0
  272. package/backend/tests/integration/api/__pycache__/test_mcp_api.cpython-313-pytest-9.0.3.pyc +0 -0
  273. package/backend/tests/integration/api/__pycache__/test_meta_api.cpython-313-pytest-9.0.3.pyc +0 -0
  274. package/backend/tests/integration/api/__pycache__/test_nodes_api.cpython-313-pytest-9.0.3.pyc +0 -0
  275. package/backend/tests/integration/api/__pycache__/test_prompts_api.cpython-313-pytest-9.0.3.pyc +0 -0
  276. package/backend/tests/integration/api/__pycache__/test_roles_api.cpython-313-pytest-9.0.3.pyc +0 -0
  277. package/backend/tests/integration/api/__pycache__/test_tabs_api.cpython-313-pytest-9.0.3.pyc +0 -0
  278. package/backend/tests/integration/api/conftest.py +0 -29
  279. package/backend/tests/integration/api/test_access_api.py +0 -182
  280. package/backend/tests/integration/api/test_assistant_api.py +0 -354
  281. package/backend/tests/integration/api/test_frontend_mounting.py +0 -61
  282. package/backend/tests/integration/api/test_mcp_api.py +0 -116
  283. package/backend/tests/integration/api/test_meta_api.py +0 -33
  284. package/backend/tests/integration/api/test_nodes_api.py +0 -722
  285. package/backend/tests/integration/api/test_prompts_api.py +0 -47
  286. package/backend/tests/integration/api/test_roles_api.py +0 -228
  287. package/backend/tests/integration/api/test_tabs_api.py +0 -802
  288. package/backend/tests/unit/__pycache__/test_access.cpython-313-pytest-9.0.3.pyc +0 -0
  289. package/backend/tests/unit/__pycache__/test_cli.cpython-313-pytest-9.0.3.pyc +0 -0
  290. package/backend/tests/unit/__pycache__/test_graph_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  291. package/backend/tests/unit/__pycache__/test_network.cpython-313-pytest-9.0.3.pyc +0 -0
  292. package/backend/tests/unit/__pycache__/test_state_sqlite_storage.cpython-313-pytest-9.0.3.pyc +0 -0
  293. package/backend/tests/unit/__pycache__/test_workspace_store.cpython-313-pytest-9.0.3.pyc +0 -0
  294. package/backend/tests/unit/agent/__pycache__/test_agent_public_api.cpython-313-pytest-9.0.3.pyc +0 -0
  295. package/backend/tests/unit/agent/__pycache__/test_agent_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  296. package/backend/tests/unit/agent/test_agent_public_api.py +0 -837
  297. package/backend/tests/unit/agent/test_agent_runtime.py +0 -2942
  298. package/backend/tests/unit/channels/__pycache__/test_telegram_channel.cpython-313-pytest-9.0.3.pyc +0 -0
  299. package/backend/tests/unit/channels/test_telegram_channel.py +0 -552
  300. package/backend/tests/unit/logging/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
  301. package/backend/tests/unit/logging/test_logging.py +0 -132
  302. package/backend/tests/unit/prompts/__pycache__/test_prompts.cpython-313-pytest-9.0.3.pyc +0 -0
  303. package/backend/tests/unit/prompts/test_prompts.py +0 -570
  304. package/backend/tests/unit/providers/__pycache__/test_anthropic_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  305. package/backend/tests/unit/providers/__pycache__/test_errors.cpython-313-pytest-9.0.3.pyc +0 -0
  306. package/backend/tests/unit/providers/__pycache__/test_extract_delta_parts.cpython-313-pytest-9.0.3.pyc +0 -0
  307. package/backend/tests/unit/providers/__pycache__/test_openai_provider.cpython-313-pytest-9.0.3.pyc +0 -0
  308. package/backend/tests/unit/providers/__pycache__/test_openai_responses.cpython-313-pytest-9.0.3.pyc +0 -0
  309. package/backend/tests/unit/providers/__pycache__/test_provider_gateway.cpython-313-pytest-9.0.3.pyc +0 -0
  310. package/backend/tests/unit/providers/__pycache__/test_think_tag_parser.cpython-313-pytest-9.0.3.pyc +0 -0
  311. package/backend/tests/unit/providers/test_anthropic_provider.py +0 -185
  312. package/backend/tests/unit/providers/test_errors.py +0 -68
  313. package/backend/tests/unit/providers/test_extract_delta_parts.py +0 -22
  314. package/backend/tests/unit/providers/test_openai_provider.py +0 -139
  315. package/backend/tests/unit/providers/test_openai_responses.py +0 -402
  316. package/backend/tests/unit/providers/test_provider_gateway.py +0 -359
  317. package/backend/tests/unit/providers/test_think_tag_parser.py +0 -36
  318. package/backend/tests/unit/routes/__pycache__/test_prompts_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  319. package/backend/tests/unit/routes/__pycache__/test_providers_route.cpython-313-pytest-9.0.3.pyc +0 -0
  320. package/backend/tests/unit/routes/__pycache__/test_roles_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  321. package/backend/tests/unit/routes/__pycache__/test_settings_routes.cpython-313-pytest-9.0.3.pyc +0 -0
  322. package/backend/tests/unit/routes/test_prompts_routes.py +0 -104
  323. package/backend/tests/unit/routes/test_providers_route.py +0 -370
  324. package/backend/tests/unit/routes/test_roles_routes.py +0 -535
  325. package/backend/tests/unit/routes/test_settings_routes.py +0 -1142
  326. package/backend/tests/unit/runtime/__pycache__/test_bootstrap_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
  327. package/backend/tests/unit/runtime/test_bootstrap_runtime.py +0 -1002
  328. package/backend/tests/unit/sandbox/__pycache__/test_sandbox_tools.cpython-313-pytest-9.0.3.pyc +0 -0
  329. package/backend/tests/unit/sandbox/test_sandbox_tools.py +0 -78
  330. package/backend/tests/unit/security/__pycache__/test_security.cpython-313-pytest-9.0.3.pyc +0 -0
  331. package/backend/tests/unit/security/test_security.py +0 -124
  332. package/backend/tests/unit/settings/__pycache__/test_settings_roles.cpython-313-pytest-9.0.3.pyc +0 -0
  333. package/backend/tests/unit/settings/test_settings_roles.py +0 -751
  334. package/backend/tests/unit/test_access.py +0 -45
  335. package/backend/tests/unit/test_cli.py +0 -124
  336. package/backend/tests/unit/test_graph_runtime.py +0 -72
  337. package/backend/tests/unit/test_network.py +0 -51
  338. package/backend/tests/unit/test_state_sqlite_storage.py +0 -159
  339. package/backend/tests/unit/test_workspace_store.py +0 -231
  340. package/backend/tests/unit/tools/__pycache__/test_connect_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  341. package/backend/tests/unit/tools/__pycache__/test_create_agent_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  342. package/backend/tests/unit/tools/__pycache__/test_delete_tab_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  343. package/backend/tests/unit/tools/__pycache__/test_edit_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  344. package/backend/tests/unit/tools/__pycache__/test_exec_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  345. package/backend/tests/unit/tools/__pycache__/test_fetch_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  346. package/backend/tests/unit/tools/__pycache__/test_manage_prompts_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  347. package/backend/tests/unit/tools/__pycache__/test_manage_providers_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  348. package/backend/tests/unit/tools/__pycache__/test_manage_roles_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  349. package/backend/tests/unit/tools/__pycache__/test_manage_settings_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  350. package/backend/tests/unit/tools/__pycache__/test_read_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  351. package/backend/tests/unit/tools/__pycache__/test_set_permissions_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  352. package/backend/tests/unit/tools/__pycache__/test_todo_tool.cpython-313-pytest-9.0.3.pyc +0 -0
  353. package/backend/tests/unit/tools/__pycache__/test_tool_registry.cpython-313-pytest-9.0.3.pyc +0 -0
  354. package/backend/tests/unit/tools/test_connect_tool.py +0 -228
  355. package/backend/tests/unit/tools/test_create_agent_tool.py +0 -436
  356. package/backend/tests/unit/tools/test_delete_tab_tool.py +0 -116
  357. package/backend/tests/unit/tools/test_edit_tool.py +0 -115
  358. package/backend/tests/unit/tools/test_exec_tool.py +0 -81
  359. package/backend/tests/unit/tools/test_fetch_tool.py +0 -65
  360. package/backend/tests/unit/tools/test_manage_prompts_tool.py +0 -117
  361. package/backend/tests/unit/tools/test_manage_providers_tool.py +0 -460
  362. package/backend/tests/unit/tools/test_manage_roles_tool.py +0 -411
  363. package/backend/tests/unit/tools/test_manage_settings_tool.py +0 -611
  364. package/backend/tests/unit/tools/test_read_tool.py +0 -33
  365. package/backend/tests/unit/tools/test_set_permissions_tool.py +0 -595
  366. package/backend/tests/unit/tools/test_todo_tool.py +0 -37
  367. package/backend/tests/unit/tools/test_tool_registry.py +0 -194
  368. package/dist/frontend/assets/AssistantPage-VBohhz4d.js +0 -1
  369. package/dist/frontend/assets/ChannelsPage-CIydPZA_.js +0 -1
  370. package/dist/frontend/assets/McpPage-CHPm2TPY.js +0 -7
  371. package/dist/frontend/assets/PageScaffold-DteOA8V7.js +0 -1
  372. package/dist/frontend/assets/PromptsPage-CSmJ3sZg.js +0 -1
  373. package/dist/frontend/assets/ProvidersPage-sl2jeG4e.js +0 -3
  374. package/dist/frontend/assets/RolesPage-DCe7W6Km.js +0 -1
  375. package/dist/frontend/assets/SettingsPage-Bix9e63E.js +0 -3
  376. package/dist/frontend/assets/ToolsPage-favNkj5C.js +0 -1
  377. package/dist/frontend/assets/WorkspaceCommandDialog-DRS6wiD6.js +0 -1
  378. package/dist/frontend/assets/WorkspacePage-KuaDjt_D.js +0 -3
  379. package/dist/frontend/assets/WorkspacePanels-BZxBw8M5.js +0 -1
  380. package/dist/frontend/assets/alert-dialog-DIBUCmqM.js +0 -1
  381. package/dist/frontend/assets/datetime-eJqd0V2S.js +0 -1
  382. package/dist/frontend/assets/dialog-BOvHIBrg.js +0 -1
  383. package/dist/frontend/assets/elk-worker.min-C9JGDOE-.js +0 -6312
  384. package/dist/frontend/assets/graph-vendor-CHpVij2M.css +0 -1
  385. package/dist/frontend/assets/graph-vendor-DRq_-6fV.js +0 -7
  386. package/dist/frontend/assets/index-Biio-CoI.js +0 -10
  387. package/dist/frontend/assets/index-CmQvO7sl.css +0 -1
  388. package/dist/frontend/assets/layout.worker-jMHqAFbP.js +0 -24
  389. package/dist/frontend/assets/markdown-vendor-C9RtvaJh.js +0 -29
  390. package/dist/frontend/assets/modelParams-DcEhGnu0.js +0 -1
  391. package/dist/frontend/assets/react-vendor-mEs_JJxa.js +0 -9
  392. package/dist/frontend/assets/roles-BbIEIMeG.js +0 -1
  393. package/dist/frontend/assets/rolldown-runtime-BYbx6iT9.js +0 -1
  394. package/dist/frontend/assets/select-D9SwnlXF.js +0 -1
  395. package/dist/frontend/assets/surface-Bzr1FRG4.js +0 -1
  396. package/dist/frontend/assets/triState-DgLlKdRR.js +0 -1
  397. package/dist/frontend/assets/ui-vendor-UazN8rcv.js +0 -51
  398. package/dist/frontend/favicon.svg +0 -4
@@ -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"]