flowent 0.0.7 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -3
- package/backend/README.md +0 -3
- package/backend/pyproject.toml +2 -8
- package/backend/src/flowent/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/agent.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/cli.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/context.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/llm.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/logging.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/main.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/patch.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/paths.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/sandbox.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/storage.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/tools.cpython-313.pyc +0 -0
- package/backend/src/flowent/agent.py +213 -3173
- package/backend/src/flowent/cli.py +19 -24
- package/backend/src/flowent/context.py +127 -0
- package/backend/src/flowent/llm.py +256 -0
- package/backend/src/flowent/logging.py +170 -129
- package/backend/src/flowent/main.py +321 -70
- package/backend/src/flowent/patch.py +182 -0
- package/backend/src/flowent/paths.py +11 -0
- package/backend/src/flowent/sandbox.py +214 -40
- package/backend/src/flowent/static/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
- package/backend/src/flowent/static/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
- package/backend/src/flowent/static/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
- package/backend/src/flowent/static/assets/index-C76K95ty.js +81 -0
- package/backend/src/flowent/static/assets/index-iUMNKvlU.css +2 -0
- package/backend/src/flowent/static/flowent.png +0 -0
- package/backend/src/flowent/static/index.html +5 -25
- package/backend/src/flowent/storage.py +302 -0
- package/backend/src/flowent/tools.py +364 -0
- package/backend/tests/__pycache__/test_agent_tools.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/__pycache__/test_health.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/__pycache__/test_llm_providers.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/__pycache__/test_persistence.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/__pycache__/test_workspace_chat.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/test_agent_tools.py +449 -0
- package/backend/tests/test_health.py +12 -0
- package/backend/tests/test_llm_providers.py +113 -0
- package/backend/tests/test_logging.py +182 -0
- package/backend/tests/test_persistence.py +125 -0
- package/backend/tests/test_workspace_chat.py +578 -0
- package/backend/uv.lock +803 -99
- package/dist/frontend/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
- package/dist/frontend/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
- package/dist/frontend/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
- package/dist/frontend/assets/index-C76K95ty.js +81 -0
- package/dist/frontend/assets/index-iUMNKvlU.css +2 -0
- package/dist/frontend/flowent.png +0 -0
- package/dist/frontend/index.html +5 -25
- package/package.json +1 -2
- package/backend/src/flowent/__pycache__/_version.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/access.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/assistant_commands.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/config.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/events.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/graph_runtime.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/graph_service.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/image_assets.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/model_metadata.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/network.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/observability_service.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/registry.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/role_management.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/runtime.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/security.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/settings.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/settings_management.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/state_db.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/workspace_store.cpython-313.pyc +0 -0
- package/backend/src/flowent/access.py +0 -247
- package/backend/src/flowent/assistant_commands.py +0 -115
- package/backend/src/flowent/channels/__init__.py +0 -3
- package/backend/src/flowent/channels/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/channels/__pycache__/telegram.cpython-313.pyc +0 -0
- package/backend/src/flowent/channels/telegram.py +0 -615
- package/backend/src/flowent/config.py +0 -14
- package/backend/src/flowent/dev.py +0 -3
- package/backend/src/flowent/events.py +0 -157
- package/backend/src/flowent/graph_runtime.py +0 -60
- package/backend/src/flowent/graph_service.py +0 -2401
- package/backend/src/flowent/image_assets.py +0 -356
- package/backend/src/flowent/model_metadata.py +0 -102
- package/backend/src/flowent/models/__init__.py +0 -125
- package/backend/src/flowent/models/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/agent.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/base.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/blueprint.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/content.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/delta.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/event.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/graph.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/history.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/llm.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/message.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/tab.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/todo.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/agent.py +0 -34
- package/backend/src/flowent/models/base.py +0 -24
- package/backend/src/flowent/models/blueprint.py +0 -176
- package/backend/src/flowent/models/content.py +0 -164
- package/backend/src/flowent/models/delta.py +0 -44
- package/backend/src/flowent/models/event.py +0 -51
- package/backend/src/flowent/models/graph.py +0 -472
- package/backend/src/flowent/models/history.py +0 -272
- package/backend/src/flowent/models/llm.py +0 -62
- package/backend/src/flowent/models/message.py +0 -33
- package/backend/src/flowent/models/tab.py +0 -85
- package/backend/src/flowent/models/todo.py +0 -10
- package/backend/src/flowent/network.py +0 -146
- package/backend/src/flowent/observability_service.py +0 -218
- package/backend/src/flowent/prompts/__init__.py +0 -67
- package/backend/src/flowent/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/prompts/__pycache__/common.cpython-313.pyc +0 -0
- package/backend/src/flowent/prompts/__pycache__/steward.cpython-313.pyc +0 -0
- package/backend/src/flowent/prompts/common.py +0 -250
- package/backend/src/flowent/prompts/steward.py +0 -64
- package/backend/src/flowent/providers/__init__.py +0 -23
- package/backend/src/flowent/providers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/anthropic.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/base_url.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/configuration.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/content.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/errors.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/gateway.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/headers.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/management.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/openai.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/openai_responses.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/registry.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/sse.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/thinking.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/anthropic.py +0 -468
- package/backend/src/flowent/providers/base_url.py +0 -60
- package/backend/src/flowent/providers/configuration.py +0 -189
- package/backend/src/flowent/providers/content.py +0 -122
- package/backend/src/flowent/providers/errors.py +0 -223
- package/backend/src/flowent/providers/gateway.py +0 -169
- package/backend/src/flowent/providers/gemini.py +0 -447
- package/backend/src/flowent/providers/headers.py +0 -20
- package/backend/src/flowent/providers/management.py +0 -96
- package/backend/src/flowent/providers/ollama.py +0 -293
- package/backend/src/flowent/providers/openai.py +0 -422
- package/backend/src/flowent/providers/openai_responses.py +0 -655
- package/backend/src/flowent/providers/registry.py +0 -144
- package/backend/src/flowent/providers/sse.py +0 -31
- package/backend/src/flowent/providers/thinking.py +0 -79
- package/backend/src/flowent/registry.py +0 -73
- package/backend/src/flowent/role_management.py +0 -270
- package/backend/src/flowent/routes/__init__.py +0 -26
- package/backend/src/flowent/routes/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/access.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/assistant.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/image_assets.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/meta.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/nodes.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/prompts.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/providers_route.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/roles.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/settings.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/tabs.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/ws.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/access.py +0 -48
- package/backend/src/flowent/routes/assistant.py +0 -158
- package/backend/src/flowent/routes/image_assets.py +0 -33
- package/backend/src/flowent/routes/meta.py +0 -28
- package/backend/src/flowent/routes/nodes.py +0 -423
- package/backend/src/flowent/routes/prompts.py +0 -46
- package/backend/src/flowent/routes/providers_route.py +0 -365
- package/backend/src/flowent/routes/roles.py +0 -207
- package/backend/src/flowent/routes/settings.py +0 -379
- package/backend/src/flowent/routes/tabs.py +0 -298
- package/backend/src/flowent/routes/ws.py +0 -33
- package/backend/src/flowent/runtime.py +0 -160
- package/backend/src/flowent/security.py +0 -37
- package/backend/src/flowent/settings.py +0 -2112
- package/backend/src/flowent/settings_management.py +0 -394
- package/backend/src/flowent/state_db.py +0 -108
- package/backend/src/flowent/static/assets/AssistantPage-BW7XAd9I.js +0 -1
- package/backend/src/flowent/static/assets/ChannelsPage-tCJHgt6m.js +0 -1
- package/backend/src/flowent/static/assets/PageScaffold-f6g2l7XN.js +0 -1
- package/backend/src/flowent/static/assets/PromptsPage-C3Sxn2D7.js +0 -1
- package/backend/src/flowent/static/assets/ProvidersPage-BfmdXmNt.js +0 -3
- package/backend/src/flowent/static/assets/RolesPage-DET8wO4r.js +0 -1
- package/backend/src/flowent/static/assets/SettingsPage-D-g3deMm.js +0 -3
- package/backend/src/flowent/static/assets/ToolsPage-CDmtE2g4.js +0 -1
- package/backend/src/flowent/static/assets/WorkspacePage-AZsJ0sD0.js +0 -3
- package/backend/src/flowent/static/assets/WorkspacePanels-CteCjolX.js +0 -1
- package/backend/src/flowent/static/assets/alert-dialog-Duorp_S-.js +0 -1
- package/backend/src/flowent/static/assets/dialog-C3ixjGjN.js +0 -1
- package/backend/src/flowent/static/assets/elk-worker.min-C9JGDOE-.js +0 -6312
- package/backend/src/flowent/static/assets/graph-vendor-CHpVij2M.css +0 -1
- package/backend/src/flowent/static/assets/graph-vendor-DRq_-6fV.js +0 -7
- package/backend/src/flowent/static/assets/index--o_0fv0N.css +0 -1
- package/backend/src/flowent/static/assets/index-C9HuekJm.js +0 -10
- package/backend/src/flowent/static/assets/layout.worker-jMHqAFbP.js +0 -24
- package/backend/src/flowent/static/assets/markdown-vendor-C9RtvaJh.js +0 -29
- package/backend/src/flowent/static/assets/modelParams-DmnF2hwR.js +0 -1
- package/backend/src/flowent/static/assets/providerTypes-DT3Ahwl_.js +0 -1
- package/backend/src/flowent/static/assets/react-vendor-mEs_JJxa.js +0 -9
- package/backend/src/flowent/static/assets/roles-CuRT_chR.js +0 -1
- package/backend/src/flowent/static/assets/rolldown-runtime-BYbx6iT9.js +0 -1
- package/backend/src/flowent/static/assets/select-DCfeNu-F.js +0 -1
- package/backend/src/flowent/static/assets/surface-pWwG5ogx.js +0 -1
- package/backend/src/flowent/static/assets/ui-vendor-C5pJa8N7.js +0 -51
- package/backend/src/flowent/static/assets/useAppRoute-FgSHBKhV.js +0 -1
- package/backend/src/flowent/static/favicon.svg +0 -4
- package/backend/src/flowent/tools/__init__.py +0 -176
- package/backend/src/flowent/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/connect.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/contacts.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/create_agent.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/create_tab.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/delete_tab.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/edit.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/exec.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/fetch.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/idle.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/list_roles.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/list_tabs.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/list_tools.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/manage_prompts.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/manage_providers.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/manage_roles.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/manage_settings.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/read.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/send.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/set_permissions.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/sleep.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/todo.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/connect.py +0 -100
- package/backend/src/flowent/tools/contacts.py +0 -22
- package/backend/src/flowent/tools/create_agent.py +0 -191
- package/backend/src/flowent/tools/create_tab.py +0 -61
- package/backend/src/flowent/tools/delete_tab.py +0 -39
- package/backend/src/flowent/tools/edit.py +0 -142
- package/backend/src/flowent/tools/exec.py +0 -118
- package/backend/src/flowent/tools/fetch.py +0 -85
- package/backend/src/flowent/tools/idle.py +0 -27
- package/backend/src/flowent/tools/list_roles.py +0 -68
- package/backend/src/flowent/tools/list_tabs.py +0 -100
- package/backend/src/flowent/tools/list_tools.py +0 -28
- package/backend/src/flowent/tools/manage_prompts.py +0 -102
- package/backend/src/flowent/tools/manage_providers.py +0 -220
- package/backend/src/flowent/tools/manage_roles.py +0 -275
- package/backend/src/flowent/tools/manage_settings.py +0 -326
- package/backend/src/flowent/tools/read.py +0 -152
- package/backend/src/flowent/tools/send.py +0 -68
- package/backend/src/flowent/tools/set_permissions.py +0 -99
- package/backend/src/flowent/tools/sleep.py +0 -41
- package/backend/src/flowent/tools/todo.py +0 -51
- package/backend/src/flowent/workspace_store.py +0 -479
- package/backend/tests/__init__.py +0 -0
- package/backend/tests/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/conftest.py +0 -6
- package/backend/tests/integration/api/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_access_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_assistant_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_frontend_mounting.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_meta_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_nodes_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_prompts_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_roles_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_tabs_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/conftest.py +0 -29
- package/backend/tests/integration/api/test_access_api.py +0 -182
- package/backend/tests/integration/api/test_assistant_api.py +0 -422
- package/backend/tests/integration/api/test_frontend_mounting.py +0 -61
- package/backend/tests/integration/api/test_meta_api.py +0 -32
- package/backend/tests/integration/api/test_nodes_api.py +0 -787
- package/backend/tests/integration/api/test_prompts_api.py +0 -47
- package/backend/tests/integration/api/test_roles_api.py +0 -228
- package/backend/tests/integration/api/test_tabs_api.py +0 -688
- package/backend/tests/unit/__pycache__/test_access.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/__pycache__/test_cli.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/__pycache__/test_graph_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/__pycache__/test_network.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/__pycache__/test_state_sqlite_storage.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/__pycache__/test_workspace_store.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/agent/__pycache__/test_agent_public_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/agent/__pycache__/test_agent_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/agent/test_agent_public_api.py +0 -822
- package/backend/tests/unit/agent/test_agent_runtime.py +0 -3088
- package/backend/tests/unit/channels/__pycache__/test_telegram_channel.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/channels/test_telegram_channel.py +0 -552
- package/backend/tests/unit/logging/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/logging/test_logging.py +0 -132
- package/backend/tests/unit/prompts/__pycache__/test_prompts.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/prompts/test_prompts.py +0 -570
- package/backend/tests/unit/providers/__pycache__/test_anthropic_provider.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_errors.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_extract_delta_parts.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_openai_provider.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_openai_responses.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_provider_gateway.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_think_tag_parser.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/test_anthropic_provider.py +0 -185
- package/backend/tests/unit/providers/test_errors.py +0 -68
- package/backend/tests/unit/providers/test_extract_delta_parts.py +0 -22
- package/backend/tests/unit/providers/test_openai_provider.py +0 -139
- package/backend/tests/unit/providers/test_openai_responses.py +0 -402
- package/backend/tests/unit/providers/test_provider_gateway.py +0 -359
- package/backend/tests/unit/providers/test_think_tag_parser.py +0 -36
- package/backend/tests/unit/routes/__pycache__/test_prompts_routes.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/routes/__pycache__/test_providers_route.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/routes/__pycache__/test_roles_routes.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/routes/__pycache__/test_settings_routes.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/routes/test_prompts_routes.py +0 -82
- package/backend/tests/unit/routes/test_providers_route.py +0 -370
- package/backend/tests/unit/routes/test_roles_routes.py +0 -539
- package/backend/tests/unit/routes/test_settings_routes.py +0 -1123
- package/backend/tests/unit/runtime/__pycache__/test_bootstrap_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/runtime/test_bootstrap_runtime.py +0 -1002
- package/backend/tests/unit/sandbox/__pycache__/test_sandbox_tools.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/sandbox/test_sandbox_tools.py +0 -78
- package/backend/tests/unit/security/__pycache__/test_security.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/security/test_security.py +0 -124
- package/backend/tests/unit/settings/__pycache__/test_settings_roles.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/settings/test_settings_roles.py +0 -703
- package/backend/tests/unit/test_access.py +0 -45
- package/backend/tests/unit/test_cli.py +0 -102
- package/backend/tests/unit/test_graph_runtime.py +0 -72
- package/backend/tests/unit/test_network.py +0 -51
- package/backend/tests/unit/test_state_sqlite_storage.py +0 -87
- package/backend/tests/unit/test_workspace_store.py +0 -228
- package/backend/tests/unit/tools/__pycache__/test_connect_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_create_agent_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_delete_tab_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_edit_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_exec_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_fetch_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_manage_prompts_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_manage_providers_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_manage_roles_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_manage_settings_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_read_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_set_permissions_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_todo_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_tool_registry.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/test_connect_tool.py +0 -228
- package/backend/tests/unit/tools/test_create_agent_tool.py +0 -404
- package/backend/tests/unit/tools/test_delete_tab_tool.py +0 -116
- package/backend/tests/unit/tools/test_edit_tool.py +0 -115
- package/backend/tests/unit/tools/test_exec_tool.py +0 -81
- package/backend/tests/unit/tools/test_fetch_tool.py +0 -65
- package/backend/tests/unit/tools/test_manage_prompts_tool.py +0 -92
- package/backend/tests/unit/tools/test_manage_providers_tool.py +0 -460
- package/backend/tests/unit/tools/test_manage_roles_tool.py +0 -411
- package/backend/tests/unit/tools/test_manage_settings_tool.py +0 -611
- package/backend/tests/unit/tools/test_read_tool.py +0 -33
- package/backend/tests/unit/tools/test_set_permissions_tool.py +0 -595
- package/backend/tests/unit/tools/test_todo_tool.py +0 -37
- package/backend/tests/unit/tools/test_tool_registry.py +0 -199
- package/dist/frontend/assets/AssistantPage-BW7XAd9I.js +0 -1
- package/dist/frontend/assets/ChannelsPage-tCJHgt6m.js +0 -1
- package/dist/frontend/assets/PageScaffold-f6g2l7XN.js +0 -1
- package/dist/frontend/assets/PromptsPage-C3Sxn2D7.js +0 -1
- package/dist/frontend/assets/ProvidersPage-BfmdXmNt.js +0 -3
- package/dist/frontend/assets/RolesPage-DET8wO4r.js +0 -1
- package/dist/frontend/assets/SettingsPage-D-g3deMm.js +0 -3
- package/dist/frontend/assets/ToolsPage-CDmtE2g4.js +0 -1
- package/dist/frontend/assets/WorkspacePage-AZsJ0sD0.js +0 -3
- package/dist/frontend/assets/WorkspacePanels-CteCjolX.js +0 -1
- package/dist/frontend/assets/alert-dialog-Duorp_S-.js +0 -1
- package/dist/frontend/assets/dialog-C3ixjGjN.js +0 -1
- package/dist/frontend/assets/elk-worker.min-C9JGDOE-.js +0 -6312
- package/dist/frontend/assets/graph-vendor-CHpVij2M.css +0 -1
- package/dist/frontend/assets/graph-vendor-DRq_-6fV.js +0 -7
- package/dist/frontend/assets/index--o_0fv0N.css +0 -1
- package/dist/frontend/assets/index-C9HuekJm.js +0 -10
- package/dist/frontend/assets/layout.worker-jMHqAFbP.js +0 -24
- package/dist/frontend/assets/markdown-vendor-C9RtvaJh.js +0 -29
- package/dist/frontend/assets/modelParams-DmnF2hwR.js +0 -1
- package/dist/frontend/assets/providerTypes-DT3Ahwl_.js +0 -1
- package/dist/frontend/assets/react-vendor-mEs_JJxa.js +0 -9
- package/dist/frontend/assets/roles-CuRT_chR.js +0 -1
- package/dist/frontend/assets/rolldown-runtime-BYbx6iT9.js +0 -1
- package/dist/frontend/assets/select-DCfeNu-F.js +0 -1
- package/dist/frontend/assets/surface-pWwG5ogx.js +0 -1
- package/dist/frontend/assets/ui-vendor-C5pJa8N7.js +0 -51
- package/dist/frontend/assets/useAppRoute-FgSHBKhV.js +0 -1
- package/dist/frontend/favicon.svg +0 -4
|
@@ -1,787 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import threading
|
|
3
|
-
import time
|
|
4
|
-
from uuid import UUID
|
|
5
|
-
|
|
6
|
-
from fastapi.testclient import TestClient
|
|
7
|
-
|
|
8
|
-
from flowent.models import (
|
|
9
|
-
AgentState,
|
|
10
|
-
AssistantText,
|
|
11
|
-
ErrorEntry,
|
|
12
|
-
ImagePart,
|
|
13
|
-
LLMResponse,
|
|
14
|
-
ReceivedMessage,
|
|
15
|
-
TextPart,
|
|
16
|
-
)
|
|
17
|
-
from flowent.providers.errors import LLMProviderError
|
|
18
|
-
from flowent.registry import registry
|
|
19
|
-
from flowent.settings import STEWARD_ROLE_INCLUDED_TOOLS
|
|
20
|
-
from flowent.tools import MINIMUM_TOOLS
|
|
21
|
-
|
|
22
|
-
_ONE_PIXEL_PNG = bytes.fromhex(
|
|
23
|
-
"89504e470d0a1a0a0000000d49484452000000010000000108060000001f15c489"
|
|
24
|
-
"0000000d49444154789c6360000002000154a24f5d0000000049454e44ae426082"
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def _get_assistant_id(client: TestClient) -> str:
|
|
29
|
-
response = client.get("/api/assistant")
|
|
30
|
-
|
|
31
|
-
assert response.status_code == 200
|
|
32
|
-
assistant_id = response.json()["id"]
|
|
33
|
-
UUID(assistant_id)
|
|
34
|
-
return assistant_id
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def test_health_check(client: TestClient):
|
|
38
|
-
response = client.get("/health")
|
|
39
|
-
|
|
40
|
-
assert response.status_code == 200
|
|
41
|
-
assert response.json() == {"status": "healthy"}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def test_list_agents(client: TestClient):
|
|
45
|
-
response = client.get("/api/nodes")
|
|
46
|
-
|
|
47
|
-
assert response.status_code == 200
|
|
48
|
-
data = response.json()
|
|
49
|
-
assert "nodes" in data
|
|
50
|
-
assert isinstance(data["nodes"], list)
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
def test_get_agent_not_found(client: TestClient):
|
|
54
|
-
response = client.get("/api/nodes/non-existent-id")
|
|
55
|
-
|
|
56
|
-
assert response.status_code == 404
|
|
57
|
-
assert "Node not found" in response.json()["detail"]
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def test_get_node_detail_includes_runtime_config(client: TestClient):
|
|
61
|
-
assistant_id = _get_assistant_id(client)
|
|
62
|
-
response = client.get(f"/api/nodes/{assistant_id}")
|
|
63
|
-
|
|
64
|
-
assert response.status_code == 200
|
|
65
|
-
data = response.json()
|
|
66
|
-
assert data["id"] == assistant_id
|
|
67
|
-
assert isinstance(data["contacts"], list)
|
|
68
|
-
assert isinstance(data["history"], list)
|
|
69
|
-
assert isinstance(data["tools"], list)
|
|
70
|
-
assert isinstance(data["write_dirs"], list)
|
|
71
|
-
assert isinstance(data["allow_network"], bool)
|
|
72
|
-
assert data["workflow_permissions"] == {
|
|
73
|
-
"allow_network": data["allow_network"],
|
|
74
|
-
"write_dirs": data["write_dirs"],
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def test_worker_contacts_follow_output_paths(
|
|
79
|
-
client: TestClient,
|
|
80
|
-
):
|
|
81
|
-
tab = client.post(
|
|
82
|
-
"/api/workflows",
|
|
83
|
-
json={"title": "Execution", "allow_network": True, "write_dirs": ["/tmp"]},
|
|
84
|
-
).json()
|
|
85
|
-
worker = client.post(
|
|
86
|
-
f"/api/workflows/{tab['id']}/nodes",
|
|
87
|
-
json={"role_name": "Worker", "name": "Worker"},
|
|
88
|
-
).json()
|
|
89
|
-
reviewer = client.post(
|
|
90
|
-
f"/api/workflows/{tab['id']}/nodes",
|
|
91
|
-
json={"role_name": "Worker", "name": "Reviewer"},
|
|
92
|
-
).json()
|
|
93
|
-
|
|
94
|
-
detail_without_edge = client.get(f"/api/nodes/{worker['id']}")
|
|
95
|
-
|
|
96
|
-
assert detail_without_edge.status_code == 200
|
|
97
|
-
worker_detail = detail_without_edge.json()
|
|
98
|
-
assert worker_detail["contacts"] == []
|
|
99
|
-
assert worker_detail["allow_network"] is True
|
|
100
|
-
assert worker_detail["write_dirs"] == ["/tmp"]
|
|
101
|
-
assert worker_detail["workflow_permissions"] == {
|
|
102
|
-
"allow_network": True,
|
|
103
|
-
"write_dirs": ["/tmp"],
|
|
104
|
-
}
|
|
105
|
-
leader_without_edge = client.get(f"/api/nodes/{tab['leader_id']}")
|
|
106
|
-
assert leader_without_edge.status_code == 200
|
|
107
|
-
assert any(
|
|
108
|
-
contact["id"] == worker["id"]
|
|
109
|
-
for contact in leader_without_edge.json()["contacts"]
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
edge_response = client.post(
|
|
113
|
-
f"/api/workflows/{tab['id']}/edges",
|
|
114
|
-
json={
|
|
115
|
-
"from_node_id": worker["id"],
|
|
116
|
-
"to_node_id": reviewer["id"],
|
|
117
|
-
},
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
assert edge_response.status_code == 200
|
|
121
|
-
edge = edge_response.json()
|
|
122
|
-
worker_with_edge = client.get(f"/api/nodes/{worker['id']}").json()
|
|
123
|
-
assert worker_with_edge["contacts"] == [
|
|
124
|
-
{
|
|
125
|
-
"id": reviewer["id"],
|
|
126
|
-
"target_id": reviewer["id"],
|
|
127
|
-
"node_type": "agent",
|
|
128
|
-
"role_name": "Worker",
|
|
129
|
-
"name": "Reviewer",
|
|
130
|
-
"state": "idle",
|
|
131
|
-
"is_leader": False,
|
|
132
|
-
"from_output_port_key": "out",
|
|
133
|
-
"to_input_port_key": "in",
|
|
134
|
-
"port_type": "parts",
|
|
135
|
-
"edge_id": edge["id"],
|
|
136
|
-
}
|
|
137
|
-
]
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def test_only_assistant_node_exists_at_startup(client: TestClient):
|
|
141
|
-
list_response = client.get("/api/nodes")
|
|
142
|
-
|
|
143
|
-
assert list_response.status_code == 200
|
|
144
|
-
nodes = list_response.json()["nodes"]
|
|
145
|
-
assert len(nodes) == 1
|
|
146
|
-
UUID(nodes[0]["id"])
|
|
147
|
-
assert nodes[0]["node_type"] == "assistant"
|
|
148
|
-
assert nodes[0]["name"] == "Assistant"
|
|
149
|
-
assert nodes[0]["role_name"] == "Steward"
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
def test_get_assistant_detail_includes_tools_and_permissions(client: TestClient):
|
|
153
|
-
assistant_id = _get_assistant_id(client)
|
|
154
|
-
response = client.get(f"/api/nodes/{assistant_id}")
|
|
155
|
-
|
|
156
|
-
assert response.status_code == 200
|
|
157
|
-
data = response.json()
|
|
158
|
-
assert data["id"] == assistant_id
|
|
159
|
-
assert set(data["tools"]) == set(MINIMUM_TOOLS) | set(STEWARD_ROLE_INCLUDED_TOOLS)
|
|
160
|
-
assert data["write_dirs"] == [os.getcwd()]
|
|
161
|
-
assert data["allow_network"] is True
|
|
162
|
-
assert data["workflow_permissions"] == {
|
|
163
|
-
"allow_network": True,
|
|
164
|
-
"write_dirs": [os.getcwd()],
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
def test_get_node_detail_keeps_state_out_of_history(client: TestClient):
|
|
169
|
-
assistant_id = _get_assistant_id(client)
|
|
170
|
-
assistant = registry.get(assistant_id)
|
|
171
|
-
assert assistant is not None
|
|
172
|
-
assistant.set_state(AgentState.RUNNING, "processing")
|
|
173
|
-
|
|
174
|
-
response = client.get(f"/api/nodes/{assistant_id}")
|
|
175
|
-
|
|
176
|
-
assert response.status_code == 200
|
|
177
|
-
data = response.json()
|
|
178
|
-
assert data["state"] == "running"
|
|
179
|
-
assert all(entry["type"] != "StateEntry" for entry in data["history"])
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def test_assistant_cannot_be_terminated_via_nodes_api(client: TestClient):
|
|
183
|
-
assistant_id = _get_assistant_id(client)
|
|
184
|
-
response = client.post(f"/api/nodes/{assistant_id}/terminate")
|
|
185
|
-
|
|
186
|
-
assert response.status_code == 400
|
|
187
|
-
assert response.json() == {"detail": "Cannot terminate assistant"}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
def test_tab_leader_cannot_be_terminated_directly(client: TestClient):
|
|
191
|
-
created_tab = client.post(
|
|
192
|
-
"/api/workflows",
|
|
193
|
-
json={"title": "Execution"},
|
|
194
|
-
).json()
|
|
195
|
-
|
|
196
|
-
response = client.post(f"/api/nodes/{created_tab['leader_id']}/terminate")
|
|
197
|
-
|
|
198
|
-
assert response.status_code == 400
|
|
199
|
-
assert response.json() == {"detail": "Cannot terminate a workflow Leader directly"}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
def test_assistant_retry_is_not_available_via_nodes_api(client: TestClient):
|
|
203
|
-
assistant_id = _get_assistant_id(client)
|
|
204
|
-
|
|
205
|
-
response = client.post(f"/api/nodes/{assistant_id}/messages/msg-1/retry")
|
|
206
|
-
|
|
207
|
-
assert response.status_code == 400
|
|
208
|
-
assert response.json() == {
|
|
209
|
-
"detail": "Use /api/assistant/messages/{message_id}/retry for Assistant retry"
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
def test_assistant_can_be_interrupted_via_nodes_api_when_running(client: TestClient):
|
|
214
|
-
assistant_id = _get_assistant_id(client)
|
|
215
|
-
assistant = registry.get(assistant_id)
|
|
216
|
-
assert assistant is not None
|
|
217
|
-
assistant.set_state(AgentState.RUNNING, "processing")
|
|
218
|
-
|
|
219
|
-
response = client.post(f"/api/nodes/{assistant_id}/interrupt")
|
|
220
|
-
|
|
221
|
-
assert response.status_code == 200
|
|
222
|
-
assert response.json() == {"status": "interrupting"}
|
|
223
|
-
assert assistant._interrupt_requested.is_set()
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
def test_assistant_can_be_interrupted_via_nodes_api_when_sleeping(client: TestClient):
|
|
227
|
-
assistant_id = _get_assistant_id(client)
|
|
228
|
-
assistant = registry.get(assistant_id)
|
|
229
|
-
assert assistant is not None
|
|
230
|
-
assistant.set_state(AgentState.SLEEPING, "waiting for reply")
|
|
231
|
-
|
|
232
|
-
response = client.post(f"/api/nodes/{assistant_id}/interrupt")
|
|
233
|
-
|
|
234
|
-
assert response.status_code == 200
|
|
235
|
-
assert response.json() == {"status": "interrupting"}
|
|
236
|
-
assert assistant._interrupt_requested.is_set()
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
def test_interrupt_ignores_idle_node(client: TestClient):
|
|
240
|
-
assistant_id = _get_assistant_id(client)
|
|
241
|
-
response = client.post(f"/api/nodes/{assistant_id}/interrupt")
|
|
242
|
-
|
|
243
|
-
assert response.status_code == 200
|
|
244
|
-
assert response.json() == {"status": "ignored"}
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
def test_assistant_chat_can_be_cleared_via_nodes_api(client: TestClient):
|
|
248
|
-
assistant_id = _get_assistant_id(client)
|
|
249
|
-
assistant = registry.get(assistant_id)
|
|
250
|
-
assert assistant is not None
|
|
251
|
-
assistant.history.append(ReceivedMessage(content="Old message", from_id="human"))
|
|
252
|
-
assistant.history.append(AssistantText(content="Old reply"))
|
|
253
|
-
|
|
254
|
-
response = client.post(f"/api/nodes/{assistant_id}/clear-chat")
|
|
255
|
-
|
|
256
|
-
assert response.status_code == 200
|
|
257
|
-
assert response.json() == {"status": "cleared"}
|
|
258
|
-
|
|
259
|
-
detail = client.get(f"/api/nodes/{assistant_id}").json()
|
|
260
|
-
assert not any(
|
|
261
|
-
entry["type"] in {"ReceivedMessage", "AssistantText"}
|
|
262
|
-
for entry in detail["history"]
|
|
263
|
-
)
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
def test_unknown_slash_input_can_be_sent_directly_to_workflow_leader(
|
|
267
|
-
client: TestClient,
|
|
268
|
-
):
|
|
269
|
-
tab = client.post(
|
|
270
|
-
"/api/workflows",
|
|
271
|
-
json={"title": "Execution", "allow_network": True},
|
|
272
|
-
).json()
|
|
273
|
-
|
|
274
|
-
response = client.post(
|
|
275
|
-
f"/api/nodes/{tab['leader_id']}/messages",
|
|
276
|
-
json={"content": "/unknown investigate the failure"},
|
|
277
|
-
)
|
|
278
|
-
|
|
279
|
-
assert response.status_code == 200
|
|
280
|
-
message_id = response.json()["message_id"]
|
|
281
|
-
assert isinstance(message_id, str)
|
|
282
|
-
|
|
283
|
-
history = []
|
|
284
|
-
for _ in range(20):
|
|
285
|
-
detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
|
|
286
|
-
history = detail["history"]
|
|
287
|
-
if any(
|
|
288
|
-
entry["type"] == "ReceivedMessage"
|
|
289
|
-
and entry["from_id"] == "human"
|
|
290
|
-
and entry["message_id"] == message_id
|
|
291
|
-
and entry["content"] == "/unknown investigate the failure"
|
|
292
|
-
for entry in history
|
|
293
|
-
):
|
|
294
|
-
break
|
|
295
|
-
time.sleep(0.01)
|
|
296
|
-
|
|
297
|
-
assert any(
|
|
298
|
-
entry["type"] == "ReceivedMessage"
|
|
299
|
-
and entry["from_id"] == "human"
|
|
300
|
-
and entry["message_id"] == message_id
|
|
301
|
-
and entry["content"] == "/unknown investigate the failure"
|
|
302
|
-
for entry in history
|
|
303
|
-
)
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
def test_leader_accepts_new_message_after_error(monkeypatch, client: TestClient):
|
|
307
|
-
tab = client.post(
|
|
308
|
-
"/api/workflows",
|
|
309
|
-
json={"title": "Execution", "allow_network": True},
|
|
310
|
-
).json()
|
|
311
|
-
leader = registry.get(tab["leader_id"])
|
|
312
|
-
assert leader is not None
|
|
313
|
-
chat_started = threading.Event()
|
|
314
|
-
release_chat = threading.Event()
|
|
315
|
-
|
|
316
|
-
def fake_chat(**kwargs):
|
|
317
|
-
chat_started.set()
|
|
318
|
-
release_chat.wait(timeout=2)
|
|
319
|
-
raise LLMProviderError(
|
|
320
|
-
"LLM API error\nDetail: still invalid",
|
|
321
|
-
transient=False,
|
|
322
|
-
)
|
|
323
|
-
|
|
324
|
-
monkeypatch.setattr("flowent.agent.gateway.chat", fake_chat)
|
|
325
|
-
leader.history.append(ErrorEntry(content="LLM API error\nDetail: Invalid key"))
|
|
326
|
-
leader.set_state(AgentState.ERROR, "LLM API error")
|
|
327
|
-
|
|
328
|
-
try:
|
|
329
|
-
response = client.post(
|
|
330
|
-
f"/api/nodes/{tab['leader_id']}/messages",
|
|
331
|
-
json={"content": "Continue the workflow chat"},
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
assert response.status_code == 200
|
|
335
|
-
message_id = response.json()["message_id"]
|
|
336
|
-
assert isinstance(message_id, str)
|
|
337
|
-
assert chat_started.wait(timeout=1)
|
|
338
|
-
|
|
339
|
-
detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
|
|
340
|
-
history = detail["history"]
|
|
341
|
-
assert any(
|
|
342
|
-
entry["type"] == "ErrorEntry"
|
|
343
|
-
and entry["content"] == "LLM API error\nDetail: Invalid key"
|
|
344
|
-
for entry in history
|
|
345
|
-
)
|
|
346
|
-
assert any(
|
|
347
|
-
entry["type"] == "ReceivedMessage"
|
|
348
|
-
and entry["from_id"] == "human"
|
|
349
|
-
and entry["message_id"] == message_id
|
|
350
|
-
and entry["content"] == "Continue the workflow chat"
|
|
351
|
-
for entry in history
|
|
352
|
-
)
|
|
353
|
-
assert registry.get(tab["leader_id"]).state == AgentState.RUNNING
|
|
354
|
-
finally:
|
|
355
|
-
release_chat.set()
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
def test_leader_rejects_message_when_terminated(client: TestClient):
|
|
359
|
-
tab = client.post(
|
|
360
|
-
"/api/workflows",
|
|
361
|
-
json={"title": "Execution"},
|
|
362
|
-
).json()
|
|
363
|
-
leader = registry.get(tab["leader_id"])
|
|
364
|
-
assert leader is not None
|
|
365
|
-
leader.set_state(AgentState.TERMINATED, "done")
|
|
366
|
-
|
|
367
|
-
response = client.post(
|
|
368
|
-
f"/api/nodes/{tab['leader_id']}/messages",
|
|
369
|
-
json={"content": "Continue the workflow chat"},
|
|
370
|
-
)
|
|
371
|
-
|
|
372
|
-
assert response.status_code == 409
|
|
373
|
-
assert response.json()["detail"] == "This workflow chat is no longer available"
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
def test_leader_help_command_returns_visible_command_feedback(client: TestClient):
|
|
377
|
-
tab = client.post(
|
|
378
|
-
"/api/workflows",
|
|
379
|
-
json={"title": "Execution"},
|
|
380
|
-
).json()
|
|
381
|
-
|
|
382
|
-
response = client.post(
|
|
383
|
-
f"/api/nodes/{tab['leader_id']}/messages",
|
|
384
|
-
json={"content": "/help"},
|
|
385
|
-
)
|
|
386
|
-
|
|
387
|
-
assert response.status_code == 200
|
|
388
|
-
assert response.json() == {
|
|
389
|
-
"status": "command_executed",
|
|
390
|
-
"command_name": "/help",
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
|
|
394
|
-
|
|
395
|
-
assert any(
|
|
396
|
-
entry["type"] == "CommandResultEntry"
|
|
397
|
-
and entry["command_name"] == "/help"
|
|
398
|
-
and "Available commands" in entry["content"]
|
|
399
|
-
and "/compact" in entry["content"]
|
|
400
|
-
for entry in detail["history"]
|
|
401
|
-
)
|
|
402
|
-
assert not any(
|
|
403
|
-
entry["type"] == "ReceivedMessage" and entry.get("content") == "/help"
|
|
404
|
-
for entry in detail["history"]
|
|
405
|
-
)
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
def test_leader_clear_command_clears_only_workflow_chat(client: TestClient):
|
|
409
|
-
assistant_id = _get_assistant_id(client)
|
|
410
|
-
assistant = registry.get(assistant_id)
|
|
411
|
-
assert assistant is not None
|
|
412
|
-
assistant.history.append(
|
|
413
|
-
ReceivedMessage(content="Assistant stays", from_id="human")
|
|
414
|
-
)
|
|
415
|
-
tab = client.post(
|
|
416
|
-
"/api/workflows",
|
|
417
|
-
json={"title": "Execution"},
|
|
418
|
-
).json()
|
|
419
|
-
worker = client.post(
|
|
420
|
-
f"/api/workflows/{tab['id']}/nodes",
|
|
421
|
-
json={"role_name": "Worker", "name": "Worker"},
|
|
422
|
-
).json()
|
|
423
|
-
leader = registry.get(tab["leader_id"])
|
|
424
|
-
assert leader is not None
|
|
425
|
-
leader.history.append(ReceivedMessage(content="Old workflow chat", from_id="human"))
|
|
426
|
-
leader.history.append(AssistantText(content="Old workflow reply"))
|
|
427
|
-
|
|
428
|
-
response = client.post(
|
|
429
|
-
f"/api/nodes/{tab['leader_id']}/messages",
|
|
430
|
-
json={"content": "/clear"},
|
|
431
|
-
)
|
|
432
|
-
|
|
433
|
-
assert response.status_code == 200
|
|
434
|
-
assert response.json() == {
|
|
435
|
-
"status": "command_executed",
|
|
436
|
-
"command_name": "/clear",
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
leader_detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
|
|
440
|
-
assistant_detail = client.get(f"/api/nodes/{assistant_id}").json()
|
|
441
|
-
workflow = client.get(f"/api/workflows/{tab['id']}").json()
|
|
442
|
-
|
|
443
|
-
assert not any(
|
|
444
|
-
entry["type"] in {"ReceivedMessage", "AssistantText"}
|
|
445
|
-
and entry.get("content") in {"Old workflow chat", "Old workflow reply"}
|
|
446
|
-
for entry in leader_detail["history"]
|
|
447
|
-
)
|
|
448
|
-
assert not any(
|
|
449
|
-
entry["type"] == "CommandResultEntry" and entry["command_name"] == "/clear"
|
|
450
|
-
for entry in leader_detail["history"]
|
|
451
|
-
)
|
|
452
|
-
assert any(
|
|
453
|
-
entry["type"] == "ReceivedMessage" and entry.get("content") == "Assistant stays"
|
|
454
|
-
for entry in assistant_detail["history"]
|
|
455
|
-
)
|
|
456
|
-
assert any(node["id"] == worker["id"] for node in workflow["nodes"])
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
def test_leader_compact_command_keeps_history_and_adds_system_feedback(
|
|
460
|
-
monkeypatch,
|
|
461
|
-
client: TestClient,
|
|
462
|
-
):
|
|
463
|
-
tab = client.post(
|
|
464
|
-
"/api/workflows",
|
|
465
|
-
json={"title": "Execution", "allow_network": True},
|
|
466
|
-
).json()
|
|
467
|
-
leader = registry.get(tab["leader_id"])
|
|
468
|
-
assert leader is not None
|
|
469
|
-
leader.history.extend(
|
|
470
|
-
[
|
|
471
|
-
ReceivedMessage(content="Need a concise recap", from_id="human"),
|
|
472
|
-
AssistantText(content="I will summarize the workflow."),
|
|
473
|
-
]
|
|
474
|
-
)
|
|
475
|
-
|
|
476
|
-
monkeypatch.setattr(
|
|
477
|
-
"flowent.agent.gateway.chat",
|
|
478
|
-
lambda *args, **kwargs: LLMResponse(
|
|
479
|
-
content=(
|
|
480
|
-
"## Current Goal\nShip the shared commands.\n\n"
|
|
481
|
-
"## Active Task Boundary\nKeep it in the workflow chat.\n\n"
|
|
482
|
-
"## Key Constraints\nDo not clear visible history.\n\n"
|
|
483
|
-
"## Confirmed Decisions\nUse shared commands.\n\n"
|
|
484
|
-
"## Open Questions\nNone.\n\n"
|
|
485
|
-
"## Next Actions\nVerify the workflow panel."
|
|
486
|
-
)
|
|
487
|
-
),
|
|
488
|
-
)
|
|
489
|
-
|
|
490
|
-
response = client.post(
|
|
491
|
-
f"/api/nodes/{tab['leader_id']}/messages",
|
|
492
|
-
json={"content": "/compact workflow command rollout"},
|
|
493
|
-
)
|
|
494
|
-
|
|
495
|
-
assert response.status_code == 200
|
|
496
|
-
assert response.json() == {
|
|
497
|
-
"status": "command_executed",
|
|
498
|
-
"command_name": "/compact",
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
|
|
502
|
-
|
|
503
|
-
assert any(
|
|
504
|
-
entry["type"] == "ReceivedMessage"
|
|
505
|
-
and entry.get("content") == "Need a concise recap"
|
|
506
|
-
for entry in detail["history"]
|
|
507
|
-
)
|
|
508
|
-
assert any(
|
|
509
|
-
entry["type"] == "AssistantText"
|
|
510
|
-
and entry.get("content") == "I will summarize the workflow."
|
|
511
|
-
for entry in detail["history"]
|
|
512
|
-
)
|
|
513
|
-
assert any(
|
|
514
|
-
entry["type"] == "CommandResultEntry"
|
|
515
|
-
and entry["command_name"] == "/compact"
|
|
516
|
-
and entry.get("include_in_context") is False
|
|
517
|
-
and "Compacted this chat for future replies." in entry["content"]
|
|
518
|
-
and "Focus: workflow command rollout" in entry["content"]
|
|
519
|
-
for entry in detail["history"]
|
|
520
|
-
)
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
def test_leader_image_message_bypasses_conversation_commands(
|
|
524
|
-
client: TestClient,
|
|
525
|
-
monkeypatch,
|
|
526
|
-
):
|
|
527
|
-
tab = client.post(
|
|
528
|
-
"/api/workflows",
|
|
529
|
-
json={"title": "Execution"},
|
|
530
|
-
).json()
|
|
531
|
-
leader = registry.get(tab["leader_id"])
|
|
532
|
-
assert leader is not None
|
|
533
|
-
monkeypatch.setattr(leader, "supports_input_image", lambda: True)
|
|
534
|
-
|
|
535
|
-
upload_response = client.post(
|
|
536
|
-
"/api/image-assets",
|
|
537
|
-
files={"file": ("pixel.png", _ONE_PIXEL_PNG, "image/png")},
|
|
538
|
-
)
|
|
539
|
-
assert upload_response.status_code == 200
|
|
540
|
-
asset_id = upload_response.json()["id"]
|
|
541
|
-
|
|
542
|
-
response = client.post(
|
|
543
|
-
f"/api/nodes/{tab['leader_id']}/messages",
|
|
544
|
-
json={
|
|
545
|
-
"parts": [
|
|
546
|
-
{"type": "text", "text": "/help"},
|
|
547
|
-
{
|
|
548
|
-
"type": "image",
|
|
549
|
-
"asset_id": asset_id,
|
|
550
|
-
"mime_type": "image/png",
|
|
551
|
-
"width": 1,
|
|
552
|
-
"height": 1,
|
|
553
|
-
},
|
|
554
|
-
]
|
|
555
|
-
},
|
|
556
|
-
)
|
|
557
|
-
|
|
558
|
-
assert response.status_code == 200
|
|
559
|
-
assert response.json()["status"] == "sent"
|
|
560
|
-
|
|
561
|
-
detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
|
|
562
|
-
|
|
563
|
-
assert any(
|
|
564
|
-
entry["type"] == "ReceivedMessage"
|
|
565
|
-
and entry["from_id"] == "human"
|
|
566
|
-
and entry["content"] == "/help[image]"
|
|
567
|
-
for entry in detail["history"]
|
|
568
|
-
)
|
|
569
|
-
assert not any(
|
|
570
|
-
entry["type"] == "CommandResultEntry" and entry["command_name"] == "/help"
|
|
571
|
-
for entry in detail["history"]
|
|
572
|
-
)
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
def test_leader_retry_rewrites_tail_and_reuses_image_parts(monkeypatch, client):
|
|
576
|
-
assistant_id = _get_assistant_id(client)
|
|
577
|
-
tab = client.post(
|
|
578
|
-
"/api/workflows",
|
|
579
|
-
json={"title": "Execution"},
|
|
580
|
-
).json()
|
|
581
|
-
leader = registry.get(tab["leader_id"])
|
|
582
|
-
assert leader is not None
|
|
583
|
-
queued_messages = []
|
|
584
|
-
|
|
585
|
-
upload_response = client.post(
|
|
586
|
-
"/api/image-assets",
|
|
587
|
-
files={"file": ("pixel.png", _ONE_PIXEL_PNG, "image/png")},
|
|
588
|
-
)
|
|
589
|
-
assert upload_response.status_code == 200
|
|
590
|
-
asset_id = upload_response.json()["id"]
|
|
591
|
-
|
|
592
|
-
leader.history.extend(
|
|
593
|
-
[
|
|
594
|
-
ReceivedMessage(
|
|
595
|
-
content="Initial brief",
|
|
596
|
-
from_id=assistant_id,
|
|
597
|
-
message_id="brief-1",
|
|
598
|
-
),
|
|
599
|
-
AssistantText(content="Leader summary"),
|
|
600
|
-
ReceivedMessage(
|
|
601
|
-
from_id="human",
|
|
602
|
-
parts=[
|
|
603
|
-
TextPart(text="Retry this leader request"),
|
|
604
|
-
ImagePart(
|
|
605
|
-
asset_id=asset_id,
|
|
606
|
-
mime_type="image/png",
|
|
607
|
-
width=1,
|
|
608
|
-
height=1,
|
|
609
|
-
),
|
|
610
|
-
],
|
|
611
|
-
message_id="msg-2",
|
|
612
|
-
),
|
|
613
|
-
AssistantText(content="Discard this leader reply"),
|
|
614
|
-
]
|
|
615
|
-
)
|
|
616
|
-
|
|
617
|
-
monkeypatch.setattr(leader, "supports_input_image", lambda: True)
|
|
618
|
-
monkeypatch.setattr(
|
|
619
|
-
leader,
|
|
620
|
-
"enqueue_message",
|
|
621
|
-
lambda message: queued_messages.append(message),
|
|
622
|
-
)
|
|
623
|
-
|
|
624
|
-
response = client.post(f"/api/nodes/{tab['leader_id']}/messages/msg-2/retry")
|
|
625
|
-
|
|
626
|
-
assert response.status_code == 200
|
|
627
|
-
payload = response.json()
|
|
628
|
-
assert payload["status"] == "retried"
|
|
629
|
-
assert payload["message_id"] != "msg-2"
|
|
630
|
-
|
|
631
|
-
detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
|
|
632
|
-
|
|
633
|
-
assert any(
|
|
634
|
-
entry["type"] == "ReceivedMessage"
|
|
635
|
-
and entry.get("message_id") == "brief-1"
|
|
636
|
-
and entry.get("from_id") == assistant_id
|
|
637
|
-
for entry in detail["history"]
|
|
638
|
-
)
|
|
639
|
-
assert not any(
|
|
640
|
-
entry["type"] == "ReceivedMessage" and entry.get("message_id") == "msg-2"
|
|
641
|
-
for entry in detail["history"]
|
|
642
|
-
)
|
|
643
|
-
assert not any(
|
|
644
|
-
entry["type"] == "AssistantText"
|
|
645
|
-
and entry.get("content") == "Discard this leader reply"
|
|
646
|
-
for entry in detail["history"]
|
|
647
|
-
)
|
|
648
|
-
assert any(
|
|
649
|
-
entry["type"] == "ReceivedMessage"
|
|
650
|
-
and entry.get("message_id") == payload["message_id"]
|
|
651
|
-
and entry.get("content") == "Retry this leader request[image]"
|
|
652
|
-
for entry in detail["history"]
|
|
653
|
-
)
|
|
654
|
-
assert len(queued_messages) == 1
|
|
655
|
-
assert queued_messages[0].message_id == payload["message_id"]
|
|
656
|
-
assert queued_messages[0].parts[0].text == "Retry this leader request"
|
|
657
|
-
assert queued_messages[0].parts[1].asset_id == asset_id
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
def test_leader_retry_rejects_non_human_anchor(client: TestClient):
|
|
661
|
-
assistant_id = _get_assistant_id(client)
|
|
662
|
-
tab = client.post(
|
|
663
|
-
"/api/workflows",
|
|
664
|
-
json={"title": "Execution"},
|
|
665
|
-
).json()
|
|
666
|
-
leader = registry.get(tab["leader_id"])
|
|
667
|
-
assert leader is not None
|
|
668
|
-
leader.history.append(
|
|
669
|
-
ReceivedMessage(
|
|
670
|
-
content="Initial brief",
|
|
671
|
-
from_id=assistant_id,
|
|
672
|
-
message_id="brief-1",
|
|
673
|
-
)
|
|
674
|
-
)
|
|
675
|
-
|
|
676
|
-
response = client.post(f"/api/nodes/{tab['leader_id']}/messages/brief-1/retry")
|
|
677
|
-
|
|
678
|
-
assert response.status_code == 404
|
|
679
|
-
assert response.json()["detail"] == "Leader human message `brief-1` was not found."
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
def test_regular_worker_retry_is_not_available_via_nodes_api(client: TestClient):
|
|
683
|
-
tab = client.post(
|
|
684
|
-
"/api/workflows",
|
|
685
|
-
json={"title": "Execution"},
|
|
686
|
-
).json()
|
|
687
|
-
worker = client.post(
|
|
688
|
-
f"/api/workflows/{tab['id']}/nodes",
|
|
689
|
-
json={"role_name": "Worker", "name": "Worker"},
|
|
690
|
-
).json()
|
|
691
|
-
|
|
692
|
-
response = client.post(f"/api/nodes/{worker['id']}/messages/msg-1/retry")
|
|
693
|
-
|
|
694
|
-
assert response.status_code == 400
|
|
695
|
-
assert response.json() == {
|
|
696
|
-
"detail": "Only a Workflow Leader can retry chat history"
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
def test_human_input_cannot_target_regular_worker(client: TestClient):
|
|
701
|
-
tab = client.post(
|
|
702
|
-
"/api/workflows",
|
|
703
|
-
json={"title": "Execution"},
|
|
704
|
-
).json()
|
|
705
|
-
worker = client.post(
|
|
706
|
-
f"/api/workflows/{tab['id']}/nodes",
|
|
707
|
-
json={"role_name": "Worker", "name": "Worker"},
|
|
708
|
-
).json()
|
|
709
|
-
|
|
710
|
-
response = client.post(
|
|
711
|
-
f"/api/nodes/{worker['id']}/messages",
|
|
712
|
-
json={"content": "Do the work"},
|
|
713
|
-
)
|
|
714
|
-
|
|
715
|
-
assert response.status_code == 400
|
|
716
|
-
assert response.json()["detail"] == (
|
|
717
|
-
"Human input can only target Assistant or a Workflow Leader"
|
|
718
|
-
)
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
def test_browser_cannot_spoof_non_human_sender_for_node_messages(client: TestClient):
|
|
722
|
-
tab = client.post(
|
|
723
|
-
"/api/workflows",
|
|
724
|
-
json={"title": "Execution"},
|
|
725
|
-
).json()
|
|
726
|
-
worker = client.post(
|
|
727
|
-
f"/api/workflows/{tab['id']}/nodes",
|
|
728
|
-
json={"role_name": "Worker", "name": "Worker"},
|
|
729
|
-
).json()
|
|
730
|
-
|
|
731
|
-
response = client.post(
|
|
732
|
-
f"/api/nodes/{worker['id']}/messages",
|
|
733
|
-
json={"content": "Do the work", "from_id": "assistant"},
|
|
734
|
-
)
|
|
735
|
-
|
|
736
|
-
assert response.status_code == 400
|
|
737
|
-
assert response.json()["detail"] == (
|
|
738
|
-
"Web UI node messages must originate from `human`"
|
|
739
|
-
)
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
def test_leader_message_supports_structured_parts_and_image_validation(
|
|
743
|
-
monkeypatch, client: TestClient
|
|
744
|
-
):
|
|
745
|
-
tab = client.post(
|
|
746
|
-
"/api/workflows",
|
|
747
|
-
json={"title": "Execution"},
|
|
748
|
-
).json()
|
|
749
|
-
leader = registry.get(tab["leader_id"])
|
|
750
|
-
assert leader is not None
|
|
751
|
-
|
|
752
|
-
upload_response = client.post(
|
|
753
|
-
"/api/image-assets",
|
|
754
|
-
files={"file": ("pixel.png", _ONE_PIXEL_PNG, "image/png")},
|
|
755
|
-
)
|
|
756
|
-
assert upload_response.status_code == 200
|
|
757
|
-
asset_id = upload_response.json()["id"]
|
|
758
|
-
|
|
759
|
-
monkeypatch.setattr(leader, "supports_input_image", lambda: True)
|
|
760
|
-
|
|
761
|
-
response = client.post(
|
|
762
|
-
f"/api/nodes/{tab['leader_id']}/messages",
|
|
763
|
-
json={
|
|
764
|
-
"parts": [
|
|
765
|
-
{"type": "text", "text": "Inspect this screenshot"},
|
|
766
|
-
{
|
|
767
|
-
"type": "image",
|
|
768
|
-
"asset_id": asset_id,
|
|
769
|
-
"mime_type": "image/png",
|
|
770
|
-
"width": 1,
|
|
771
|
-
"height": 1,
|
|
772
|
-
"alt": "Pixel",
|
|
773
|
-
},
|
|
774
|
-
]
|
|
775
|
-
},
|
|
776
|
-
)
|
|
777
|
-
|
|
778
|
-
assert response.status_code == 200
|
|
779
|
-
detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
|
|
780
|
-
entry = next(
|
|
781
|
-
history_entry
|
|
782
|
-
for history_entry in detail["history"]
|
|
783
|
-
if history_entry["type"] == "ReceivedMessage"
|
|
784
|
-
and history_entry["message_id"] == response.json()["message_id"]
|
|
785
|
-
)
|
|
786
|
-
assert entry["parts"][0]["text"] == "Inspect this screenshot"
|
|
787
|
-
assert entry["parts"][1]["asset_id"] == asset_id
|