flowent 0.0.4 → 0.0.5
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 +1 -1
- package/backend/README.md +74 -0
- package/backend/pyproject.toml +2 -1
- package/backend/src/flowent/__pycache__/__init__.cpython-313.pyc +0 -0
- 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__/agent.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/assistant_commands.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/cli.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__/logging.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/main.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/mcp_service.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__/{stats_service.cpython-313.pyc → 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__/sandbox.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/agent.py +364 -52
- package/backend/src/flowent/assistant_commands.py +31 -22
- 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 +4 -4
- package/backend/src/flowent/graph_service.py +1307 -145
- package/backend/src/flowent/mcp_service.py +21 -7
- package/backend/src/flowent/model_metadata.py +4 -0
- package/backend/src/flowent/models/__init__.py +6 -2
- 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 +1 -0
- package/backend/src/flowent/models/graph.py +44 -9
- package/backend/src/flowent/models/history.py +73 -15
- package/backend/src/flowent/models/llm.py +1 -0
- package/backend/src/flowent/models/message.py +6 -0
- package/backend/src/flowent/models/tab.py +38 -1
- package/backend/src/flowent/{stats_service.py → observability_service.py} +4 -4
- 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 +2 -2
- package/backend/src/flowent/prompts/steward.py +2 -2
- 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/configuration.py +7 -0
- package/backend/src/flowent/role_management.py +12 -0
- package/backend/src/flowent/routes/__init__.py +0 -2
- 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__/mcp.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/assistant.py +4 -4
- package/backend/src/flowent/routes/nodes.py +54 -6
- package/backend/src/flowent/routes/providers_route.py +1 -0
- package/backend/src/flowent/routes/roles.py +1 -1
- package/backend/src/flowent/routes/settings.py +4 -0
- package/backend/src/flowent/routes/tabs.py +29 -11
- package/backend/src/flowent/runtime.py +7 -30
- package/backend/src/flowent/security.py +23 -8
- package/backend/src/flowent/settings.py +56 -5
- package/backend/src/flowent/settings_management.py +12 -0
- package/backend/src/flowent/static/assets/AssistantPage-VBohhz4d.js +1 -0
- package/backend/src/flowent/static/assets/ChannelsPage-CIydPZA_.js +1 -0
- package/backend/src/flowent/static/assets/McpPage-CHPm2TPY.js +7 -0
- package/backend/src/flowent/static/assets/PageScaffold-DteOA8V7.js +1 -0
- package/backend/src/flowent/static/assets/PromptsPage-CSmJ3sZg.js +1 -0
- package/backend/src/flowent/static/assets/ProvidersPage-sl2jeG4e.js +3 -0
- package/backend/src/flowent/static/assets/RolesPage-DCe7W6Km.js +1 -0
- package/backend/src/flowent/static/assets/SettingsPage-Bix9e63E.js +3 -0
- package/backend/src/flowent/static/assets/ToolsPage-favNkj5C.js +1 -0
- package/backend/src/flowent/static/assets/WorkspaceCommandDialog-DRS6wiD6.js +1 -0
- package/backend/src/flowent/static/assets/WorkspacePage-KuaDjt_D.js +3 -0
- package/backend/src/flowent/static/assets/WorkspacePanels-BZxBw8M5.js +1 -0
- package/backend/src/flowent/static/assets/alert-dialog-DIBUCmqM.js +1 -0
- package/backend/src/flowent/static/assets/{dialog-BeGSweF6.js → dialog-BOvHIBrg.js} +1 -1
- package/backend/src/flowent/static/assets/index-Biio-CoI.js +10 -0
- package/backend/src/flowent/static/assets/index-CmQvO7sl.css +1 -0
- package/backend/src/flowent/static/assets/modelParams-DcEhGnu0.js +1 -0
- package/backend/src/flowent/static/assets/roles-BbIEIMeG.js +1 -0
- package/backend/src/flowent/static/assets/select-D9SwnlXF.js +1 -0
- package/backend/src/flowent/static/assets/surface-Bzr1FRG4.js +1 -0
- package/backend/src/flowent/static/assets/{ui-vendor-Dg9NNnWX.js → ui-vendor-UazN8rcv.js} +15 -15
- package/backend/src/flowent/static/index.html +3 -4
- package/backend/src/flowent/tools/__init__.py +76 -2
- 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__/mcp.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 +10 -66
- package/backend/src/flowent/tools/contacts.py +1 -1
- package/backend/src/flowent/tools/create_agent.py +9 -88
- package/backend/src/flowent/tools/create_tab.py +7 -5
- package/backend/src/flowent/tools/exec.py +3 -2
- package/backend/src/flowent/tools/list_roles.py +29 -4
- package/backend/src/flowent/tools/list_tabs.py +4 -0
- package/backend/src/flowent/tools/list_tools.py +5 -1
- package/backend/src/flowent/tools/manage_settings.py +18 -0
- package/backend/src/flowent/tools/send.py +21 -3
- package/backend/src/flowent/tools/set_permissions.py +21 -6
- 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/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_mcp_api.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/test_assistant_api.py +1 -1
- package/backend/tests/integration/api/test_nodes_api.py +257 -21
- package/backend/tests/integration/api/test_roles_api.py +3 -2
- package/backend/tests/integration/api/test_tabs_api.py +312 -11
- 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 +162 -71
- package/backend/tests/unit/agent/test_agent_runtime.py +285 -69
- package/backend/tests/unit/channels/__pycache__/test_telegram_channel.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/logging/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
- 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 +3 -2
- 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/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_providers_route.py +2 -0
- package/backend/tests/unit/routes/test_roles_routes.py +109 -0
- package/backend/tests/unit/routes/test_settings_routes.py +4 -0
- 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 +8 -18
- package/backend/tests/unit/sandbox/__pycache__/test_sandbox_tools.cpython-313-pytest-9.0.3.pyc +0 -0
- 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 +16 -2
- 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 +40 -0
- package/backend/tests/unit/test_state_sqlite_storage.py +67 -1
- 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 +2 -3
- package/backend/tests/unit/tools/test_create_agent_tool.py +9 -97
- package/backend/tests/unit/tools/test_delete_tab_tool.py +33 -0
- package/backend/tests/unit/tools/test_manage_providers_tool.py +2 -0
- package/backend/tests/unit/tools/test_manage_settings_tool.py +3 -0
- package/backend/tests/unit/tools/test_set_permissions_tool.py +216 -12
- package/backend/tests/unit/tools/test_tool_registry.py +103 -0
- package/backend/uv.lock +1 -1
- package/dist/frontend/assets/AssistantPage-VBohhz4d.js +1 -0
- package/dist/frontend/assets/ChannelsPage-CIydPZA_.js +1 -0
- package/dist/frontend/assets/McpPage-CHPm2TPY.js +7 -0
- package/dist/frontend/assets/PageScaffold-DteOA8V7.js +1 -0
- package/dist/frontend/assets/PromptsPage-CSmJ3sZg.js +1 -0
- package/dist/frontend/assets/ProvidersPage-sl2jeG4e.js +3 -0
- package/dist/frontend/assets/RolesPage-DCe7W6Km.js +1 -0
- package/dist/frontend/assets/SettingsPage-Bix9e63E.js +3 -0
- package/dist/frontend/assets/ToolsPage-favNkj5C.js +1 -0
- package/dist/frontend/assets/WorkspaceCommandDialog-DRS6wiD6.js +1 -0
- package/dist/frontend/assets/WorkspacePage-KuaDjt_D.js +3 -0
- package/dist/frontend/assets/WorkspacePanels-BZxBw8M5.js +1 -0
- package/dist/frontend/assets/alert-dialog-DIBUCmqM.js +1 -0
- package/dist/frontend/assets/{dialog-BeGSweF6.js → dialog-BOvHIBrg.js} +1 -1
- package/dist/frontend/assets/index-Biio-CoI.js +10 -0
- package/dist/frontend/assets/index-CmQvO7sl.css +1 -0
- package/dist/frontend/assets/modelParams-DcEhGnu0.js +1 -0
- package/dist/frontend/assets/roles-BbIEIMeG.js +1 -0
- package/dist/frontend/assets/select-D9SwnlXF.js +1 -0
- package/dist/frontend/assets/surface-Bzr1FRG4.js +1 -0
- package/dist/frontend/assets/{ui-vendor-Dg9NNnWX.js → ui-vendor-UazN8rcv.js} +15 -15
- package/dist/frontend/index.html +3 -4
- package/package.json +3 -3
- package/backend/src/flowent/routes/__pycache__/stats.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/stats.py +0 -229
- package/backend/src/flowent/static/assets/AssistantPage-B3Xc08AS.js +0 -1
- package/backend/src/flowent/static/assets/ChannelsPage-ByLd28xk.js +0 -1
- package/backend/src/flowent/static/assets/HomePage-C0hAx9_l.js +0 -3
- package/backend/src/flowent/static/assets/McpPage-DkrYLvBv.js +0 -7
- package/backend/src/flowent/static/assets/PageScaffold-D4jO9ooX.js +0 -1
- package/backend/src/flowent/static/assets/PromptsPage-DWA7rRJd.js +0 -1
- package/backend/src/flowent/static/assets/ProvidersPage-PUWT8seJ.js +0 -3
- package/backend/src/flowent/static/assets/RolesPage-CqcclGRw.js +0 -1
- package/backend/src/flowent/static/assets/SettingsPage-8tS2cJgX.js +0 -3
- package/backend/src/flowent/static/assets/StatsPage-BX9khYzu.js +0 -1
- package/backend/src/flowent/static/assets/ToolsPage-9Tl9FdeD.js +0 -1
- package/backend/src/flowent/static/assets/WorkspaceCommandDialog-CCXxjDL8.js +0 -1
- package/backend/src/flowent/static/assets/WorkspacePanels-aMdJ7ZH7.js +0 -1
- package/backend/src/flowent/static/assets/alert-dialog-kFYVQ7oX.js +0 -1
- package/backend/src/flowent/static/assets/badge-74-3jsCg.js +0 -1
- package/backend/src/flowent/static/assets/constants-XUzFf6i1.js +0 -1
- package/backend/src/flowent/static/assets/index-BHC1Vhy8.css +0 -1
- package/backend/src/flowent/static/assets/index-CL1ALZ3r.js +0 -10
- package/backend/src/flowent/static/assets/modelParams-CaHd0903.js +0 -1
- package/backend/src/flowent/static/assets/roles-2OLDeTc5.js +0 -1
- package/backend/src/flowent/static/assets/select-DL_LPeDj.js +0 -1
- package/backend/src/flowent/static/assets/shared-CMxbpLeQ.js +0 -1
- package/backend/tests/unit/routes/__pycache__/test_stats_routes.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/routes/test_stats_routes.py +0 -149
- package/dist/frontend/assets/AssistantPage-B3Xc08AS.js +0 -1
- package/dist/frontend/assets/ChannelsPage-ByLd28xk.js +0 -1
- package/dist/frontend/assets/HomePage-C0hAx9_l.js +0 -3
- package/dist/frontend/assets/McpPage-DkrYLvBv.js +0 -7
- package/dist/frontend/assets/PageScaffold-D4jO9ooX.js +0 -1
- package/dist/frontend/assets/PromptsPage-DWA7rRJd.js +0 -1
- package/dist/frontend/assets/ProvidersPage-PUWT8seJ.js +0 -3
- package/dist/frontend/assets/RolesPage-CqcclGRw.js +0 -1
- package/dist/frontend/assets/SettingsPage-8tS2cJgX.js +0 -3
- package/dist/frontend/assets/StatsPage-BX9khYzu.js +0 -1
- package/dist/frontend/assets/ToolsPage-9Tl9FdeD.js +0 -1
- package/dist/frontend/assets/WorkspaceCommandDialog-CCXxjDL8.js +0 -1
- package/dist/frontend/assets/WorkspacePanels-aMdJ7ZH7.js +0 -1
- package/dist/frontend/assets/alert-dialog-kFYVQ7oX.js +0 -1
- package/dist/frontend/assets/badge-74-3jsCg.js +0 -1
- package/dist/frontend/assets/constants-XUzFf6i1.js +0 -1
- package/dist/frontend/assets/index-BHC1Vhy8.css +0 -1
- package/dist/frontend/assets/index-CL1ALZ3r.js +0 -10
- package/dist/frontend/assets/modelParams-CaHd0903.js +0 -1
- package/dist/frontend/assets/roles-2OLDeTc5.js +0 -1
- package/dist/frontend/assets/select-DL_LPeDj.js +0 -1
- package/dist/frontend/assets/shared-CMxbpLeQ.js +0 -1
- /package/backend/src/flowent/static/assets/{datetime-m6_O_Ci9.js → datetime-eJqd0V2S.js} +0 -0
- /package/backend/src/flowent/static/assets/{markdown-vendor-DVdy_w12.js → markdown-vendor-C9RtvaJh.js} +0 -0
- /package/backend/src/flowent/static/assets/{triState-DEr3NkXV.js → triState-DgLlKdRR.js} +0 -0
- /package/dist/frontend/assets/{datetime-m6_O_Ci9.js → datetime-eJqd0V2S.js} +0 -0
- /package/dist/frontend/assets/{markdown-vendor-DVdy_w12.js → markdown-vendor-C9RtvaJh.js} +0 -0
- /package/dist/frontend/assets/{triState-DEr3NkXV.js → triState-DgLlKdRR.js} +0 -0
|
@@ -71,6 +71,10 @@ class ManageSettingsTool(Tool):
|
|
|
71
71
|
"type": ["boolean", "null"],
|
|
72
72
|
"description": "Explicit output_image override for the active system model",
|
|
73
73
|
},
|
|
74
|
+
"structured_output": {
|
|
75
|
+
"type": ["boolean", "null"],
|
|
76
|
+
"description": "Explicit structured_output override for the active system model",
|
|
77
|
+
},
|
|
74
78
|
"max_retries": {
|
|
75
79
|
"type": "integer",
|
|
76
80
|
"description": "Maximum retries for transient LLM call failures when retry_policy is limited",
|
|
@@ -139,6 +143,7 @@ class ManageSettingsTool(Tool):
|
|
|
139
143
|
build_model_retry_initial_delay_seconds,
|
|
140
144
|
build_model_retry_max_delay_seconds,
|
|
141
145
|
build_model_retry_policy,
|
|
146
|
+
build_model_structured_output,
|
|
142
147
|
build_model_timeout_ms,
|
|
143
148
|
get_settings,
|
|
144
149
|
save_settings,
|
|
@@ -161,6 +166,7 @@ class ManageSettingsTool(Tool):
|
|
|
161
166
|
context_window_tokens = args.get("context_window_tokens")
|
|
162
167
|
input_image = args.get("input_image")
|
|
163
168
|
output_image = args.get("output_image")
|
|
169
|
+
structured_output = args.get("structured_output")
|
|
164
170
|
auto_compact_token_limit = args.get("auto_compact_token_limit")
|
|
165
171
|
model_params = args.get("model_params")
|
|
166
172
|
timestamp_format = args.get("timestamp_format")
|
|
@@ -237,6 +243,14 @@ class ManageSettingsTool(Tool):
|
|
|
237
243
|
build_model_output_image(output_image, field_name="output_image")
|
|
238
244
|
except ValueError as exc:
|
|
239
245
|
return json.dumps({"error": str(exc)})
|
|
246
|
+
if "structured_output" in args:
|
|
247
|
+
try:
|
|
248
|
+
build_model_structured_output(
|
|
249
|
+
structured_output,
|
|
250
|
+
field_name="structured_output",
|
|
251
|
+
)
|
|
252
|
+
except ValueError as exc:
|
|
253
|
+
return json.dumps({"error": str(exc)})
|
|
240
254
|
if "context_window_tokens" in args:
|
|
241
255
|
try:
|
|
242
256
|
build_model_context_window_tokens(
|
|
@@ -293,6 +307,9 @@ class ManageSettingsTool(Tool):
|
|
|
293
307
|
),
|
|
294
308
|
input_image=input_image if "input_image" in args else MISSING,
|
|
295
309
|
output_image=output_image if "output_image" in args else MISSING,
|
|
310
|
+
structured_output=(
|
|
311
|
+
structured_output if "structured_output" in args else MISSING
|
|
312
|
+
),
|
|
296
313
|
max_retries=max_retries if max_retries is not None else MISSING,
|
|
297
314
|
retry_policy=retry_policy if retry_policy is not None else MISSING,
|
|
298
315
|
timeout_ms=timeout_ms if timeout_ms is not None else MISSING,
|
|
@@ -331,6 +348,7 @@ class ManageSettingsTool(Tool):
|
|
|
331
348
|
retry_backoff_cap_retries_field_name="retry_backoff_cap_retries",
|
|
332
349
|
input_image_field_name="input_image",
|
|
333
350
|
output_image_field_name="output_image",
|
|
351
|
+
structured_output_field_name="structured_output",
|
|
334
352
|
context_window_tokens_field_name="context_window_tokens",
|
|
335
353
|
auto_compact_token_limit_field_name="auto_compact_token_limit",
|
|
336
354
|
)
|
|
@@ -10,7 +10,7 @@ if TYPE_CHECKING:
|
|
|
10
10
|
|
|
11
11
|
class SendTool(Tool):
|
|
12
12
|
name = "send"
|
|
13
|
-
description = "Send
|
|
13
|
+
description = "Send to one current contact or output-port path."
|
|
14
14
|
parameters: ClassVar[dict[str, Any]] = {
|
|
15
15
|
"type": "object",
|
|
16
16
|
"properties": {
|
|
@@ -18,9 +18,20 @@ class SendTool(Tool):
|
|
|
18
18
|
"type": "string",
|
|
19
19
|
"description": "One target id, name, or unique short id from contacts.",
|
|
20
20
|
},
|
|
21
|
+
"from_output_port_key": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "Source output port key for workflow path sends.",
|
|
24
|
+
},
|
|
25
|
+
"to_input_port_key": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"description": "Target input port key for workflow path sends.",
|
|
28
|
+
},
|
|
29
|
+
"value": {
|
|
30
|
+
"description": "Typed value for workflow path sends. Use ordered parts for parts ports, a string for string ports, or an object for json ports.",
|
|
31
|
+
},
|
|
21
32
|
"parts": {
|
|
22
33
|
"type": "array",
|
|
23
|
-
"description": "Ordered message parts
|
|
34
|
+
"description": "Ordered message parts for Assistant and Leader entry contacts.",
|
|
24
35
|
"items": {
|
|
25
36
|
"type": "object",
|
|
26
37
|
"properties": {
|
|
@@ -36,7 +47,7 @@ class SendTool(Tool):
|
|
|
36
47
|
},
|
|
37
48
|
},
|
|
38
49
|
},
|
|
39
|
-
"required": ["target"
|
|
50
|
+
"required": ["target"],
|
|
40
51
|
"additionalProperties": False,
|
|
41
52
|
}
|
|
42
53
|
|
|
@@ -44,6 +55,13 @@ class SendTool(Tool):
|
|
|
44
55
|
target = args.get("target")
|
|
45
56
|
if not isinstance(target, str) or not target.strip():
|
|
46
57
|
raise ValueError("send.target must be a non-empty string")
|
|
58
|
+
if not agent._is_entry_level_sender():
|
|
59
|
+
return agent.send_port_value(
|
|
60
|
+
target_ref=target.strip(),
|
|
61
|
+
from_output_port_key=args.get("from_output_port_key"),
|
|
62
|
+
to_input_port_key=args.get("to_input_port_key"),
|
|
63
|
+
raw_value=args.get("value", args.get("parts")),
|
|
64
|
+
)
|
|
47
65
|
return agent.send_message(
|
|
48
66
|
target_ref=target.strip(),
|
|
49
67
|
raw_parts=args.get("parts"),
|
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import json
|
|
4
4
|
from typing import TYPE_CHECKING, Any, ClassVar
|
|
5
5
|
|
|
6
|
+
from flowent.models import NodeType
|
|
6
7
|
from flowent.tools import Tool
|
|
7
8
|
|
|
8
9
|
if TYPE_CHECKING:
|
|
@@ -12,8 +13,8 @@ if TYPE_CHECKING:
|
|
|
12
13
|
class SetPermissionsTool(Tool):
|
|
13
14
|
name = "set_permissions"
|
|
14
15
|
description = (
|
|
15
|
-
"Update a workflow's permission boundary by patching its
|
|
16
|
-
"
|
|
16
|
+
"Update a workflow's permission boundary by patching its allow_network "
|
|
17
|
+
"and write_dirs."
|
|
17
18
|
)
|
|
18
19
|
parameters: ClassVar[dict[str, Any]] = {
|
|
19
20
|
"type": "object",
|
|
@@ -36,7 +37,10 @@ class SetPermissionsTool(Tool):
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
def execute(self, agent: Agent, args: dict[str, Any], **_kwargs: Any) -> str:
|
|
39
|
-
from flowent.graph_service import
|
|
40
|
+
from flowent.graph_service import (
|
|
41
|
+
resolve_effective_permissions_for_agent,
|
|
42
|
+
set_tab_permissions,
|
|
43
|
+
)
|
|
40
44
|
from flowent.settings import (
|
|
41
45
|
build_assistant_allow_network,
|
|
42
46
|
build_assistant_write_dirs,
|
|
@@ -48,6 +52,14 @@ class SetPermissionsTool(Tool):
|
|
|
48
52
|
|
|
49
53
|
if not isinstance(workflow_id, str) or not workflow_id.strip():
|
|
50
54
|
return json.dumps({"error": "workflow_id must be a non-empty string"})
|
|
55
|
+
normalized_workflow_id = workflow_id.strip()
|
|
56
|
+
if (
|
|
57
|
+
agent.node_type != NodeType.ASSISTANT
|
|
58
|
+
and agent.config.tab_id != normalized_workflow_id
|
|
59
|
+
):
|
|
60
|
+
return json.dumps(
|
|
61
|
+
{"error": "Workflow permissions can only be changed for this workflow"}
|
|
62
|
+
)
|
|
51
63
|
|
|
52
64
|
allow_network: bool | None = None
|
|
53
65
|
if "allow_network" in args:
|
|
@@ -69,12 +81,15 @@ class SetPermissionsTool(Tool):
|
|
|
69
81
|
except ValueError as exc:
|
|
70
82
|
return json.dumps({"error": str(exc)})
|
|
71
83
|
|
|
84
|
+
caller_allow_network, caller_write_dirs = (
|
|
85
|
+
resolve_effective_permissions_for_agent(agent)
|
|
86
|
+
)
|
|
72
87
|
result, error = set_tab_permissions(
|
|
73
|
-
tab_id=
|
|
88
|
+
tab_id=normalized_workflow_id,
|
|
74
89
|
allow_network=allow_network,
|
|
75
90
|
write_dirs=write_dirs,
|
|
76
|
-
caller_allow_network=
|
|
77
|
-
caller_write_dirs=
|
|
91
|
+
caller_allow_network=caller_allow_network,
|
|
92
|
+
caller_write_dirs=caller_write_dirs,
|
|
78
93
|
actor_id=agent.uuid,
|
|
79
94
|
)
|
|
80
95
|
if error is not None or result is None:
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/backend/tests/integration/api/__pycache__/test_access_api.cpython-313-pytest-9.0.3.pyc
CHANGED
|
Binary file
|
package/backend/tests/integration/api/__pycache__/test_assistant_api.cpython-313-pytest-9.0.3.pyc
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/backend/tests/integration/api/__pycache__/test_meta_api.cpython-313-pytest-9.0.3.pyc
CHANGED
|
Binary file
|
package/backend/tests/integration/api/__pycache__/test_nodes_api.cpython-313-pytest-9.0.3.pyc
CHANGED
|
Binary file
|
package/backend/tests/integration/api/__pycache__/test_prompts_api.cpython-313-pytest-9.0.3.pyc
CHANGED
|
Binary file
|
package/backend/tests/integration/api/__pycache__/test_roles_api.cpython-313-pytest-9.0.3.pyc
CHANGED
|
Binary file
|
package/backend/tests/integration/api/__pycache__/test_tabs_api.cpython-313-pytest-9.0.3.pyc
CHANGED
|
Binary file
|
|
@@ -129,7 +129,7 @@ def test_compact_command_replaces_history_with_summary(monkeypatch, client):
|
|
|
129
129
|
entry["type"] == "CommandResultEntry"
|
|
130
130
|
and entry["command_name"] == "/compact"
|
|
131
131
|
and entry.get("include_in_context") is False
|
|
132
|
-
and "Compacted
|
|
132
|
+
and "Compacted this chat for future replies." in entry["content"]
|
|
133
133
|
and "Focus: slash command rollout" in entry["content"]
|
|
134
134
|
and "Ship the slash commands." not in entry["content"]
|
|
135
135
|
for entry in detail["history"]
|
|
@@ -8,6 +8,7 @@ from flowent.models import (
|
|
|
8
8
|
AgentState,
|
|
9
9
|
AssistantText,
|
|
10
10
|
ImagePart,
|
|
11
|
+
LLMResponse,
|
|
11
12
|
ReceivedMessage,
|
|
12
13
|
TextPart,
|
|
13
14
|
)
|
|
@@ -66,40 +67,72 @@ def test_get_node_detail_includes_runtime_config(client: TestClient):
|
|
|
66
67
|
assert isinstance(data["tools"], list)
|
|
67
68
|
assert isinstance(data["write_dirs"], list)
|
|
68
69
|
assert isinstance(data["allow_network"], bool)
|
|
70
|
+
assert data["workflow_permissions"] == {
|
|
71
|
+
"allow_network": data["allow_network"],
|
|
72
|
+
"write_dirs": data["write_dirs"],
|
|
73
|
+
}
|
|
69
74
|
|
|
70
75
|
|
|
71
|
-
def
|
|
76
|
+
def test_worker_contacts_follow_output_paths(
|
|
72
77
|
client: TestClient,
|
|
73
78
|
):
|
|
74
79
|
tab = client.post(
|
|
75
80
|
"/api/workflows",
|
|
76
|
-
json={"title": "Execution"},
|
|
81
|
+
json={"title": "Execution", "allow_network": True, "write_dirs": ["/tmp"]},
|
|
77
82
|
).json()
|
|
78
83
|
worker = client.post(
|
|
79
84
|
f"/api/workflows/{tab['id']}/nodes",
|
|
80
85
|
json={"role_name": "Worker", "name": "Worker"},
|
|
81
86
|
).json()
|
|
87
|
+
reviewer = client.post(
|
|
88
|
+
f"/api/workflows/{tab['id']}/nodes",
|
|
89
|
+
json={"role_name": "Worker", "name": "Reviewer"},
|
|
90
|
+
).json()
|
|
82
91
|
|
|
83
92
|
detail_without_edge = client.get(f"/api/nodes/{worker['id']}")
|
|
84
93
|
|
|
85
94
|
assert detail_without_edge.status_code == 200
|
|
86
|
-
|
|
95
|
+
worker_detail = detail_without_edge.json()
|
|
96
|
+
assert worker_detail["contacts"] == []
|
|
97
|
+
assert worker_detail["allow_network"] is True
|
|
98
|
+
assert worker_detail["write_dirs"] == ["/tmp"]
|
|
99
|
+
assert worker_detail["workflow_permissions"] == {
|
|
100
|
+
"allow_network": True,
|
|
101
|
+
"write_dirs": ["/tmp"],
|
|
102
|
+
}
|
|
87
103
|
leader_without_edge = client.get(f"/api/nodes/{tab['leader_id']}")
|
|
88
104
|
assert leader_without_edge.status_code == 200
|
|
89
|
-
assert
|
|
105
|
+
assert any(
|
|
106
|
+
contact["id"] == worker["id"]
|
|
107
|
+
for contact in leader_without_edge.json()["contacts"]
|
|
108
|
+
)
|
|
90
109
|
|
|
91
110
|
edge_response = client.post(
|
|
92
111
|
f"/api/workflows/{tab['id']}/edges",
|
|
93
112
|
json={
|
|
94
|
-
"from_node_id":
|
|
95
|
-
"to_node_id":
|
|
113
|
+
"from_node_id": worker["id"],
|
|
114
|
+
"to_node_id": reviewer["id"],
|
|
96
115
|
},
|
|
97
116
|
)
|
|
98
117
|
|
|
99
|
-
assert edge_response.status_code ==
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
118
|
+
assert edge_response.status_code == 200
|
|
119
|
+
edge = edge_response.json()
|
|
120
|
+
worker_with_edge = client.get(f"/api/nodes/{worker['id']}").json()
|
|
121
|
+
assert worker_with_edge["contacts"] == [
|
|
122
|
+
{
|
|
123
|
+
"id": reviewer["id"],
|
|
124
|
+
"target_id": reviewer["id"],
|
|
125
|
+
"node_type": "agent",
|
|
126
|
+
"role_name": "Worker",
|
|
127
|
+
"name": "Reviewer",
|
|
128
|
+
"state": "idle",
|
|
129
|
+
"is_leader": False,
|
|
130
|
+
"from_output_port_key": "out",
|
|
131
|
+
"to_input_port_key": "in",
|
|
132
|
+
"port_type": "parts",
|
|
133
|
+
"edge_id": edge["id"],
|
|
134
|
+
}
|
|
135
|
+
]
|
|
103
136
|
|
|
104
137
|
|
|
105
138
|
def test_direct_node_message_api_is_not_available(client: TestClient):
|
|
@@ -131,9 +164,13 @@ def test_get_assistant_detail_includes_tools_and_permissions(client: TestClient)
|
|
|
131
164
|
assert set(data["tools"]) == set(MINIMUM_TOOLS) | set(STEWARD_ROLE_INCLUDED_TOOLS)
|
|
132
165
|
assert data["write_dirs"] == [os.getcwd()]
|
|
133
166
|
assert data["allow_network"] is True
|
|
167
|
+
assert data["workflow_permissions"] == {
|
|
168
|
+
"allow_network": True,
|
|
169
|
+
"write_dirs": [os.getcwd()],
|
|
170
|
+
}
|
|
134
171
|
|
|
135
172
|
|
|
136
|
-
def
|
|
173
|
+
def test_get_node_detail_keeps_state_out_of_history(client: TestClient):
|
|
137
174
|
assistant_id = _get_assistant_id(client)
|
|
138
175
|
assistant = registry.get(assistant_id)
|
|
139
176
|
assert assistant is not None
|
|
@@ -142,11 +179,9 @@ def test_get_node_detail_includes_state_entries_in_history(client: TestClient):
|
|
|
142
179
|
response = client.get(f"/api/nodes/{assistant_id}")
|
|
143
180
|
|
|
144
181
|
assert response.status_code == 200
|
|
145
|
-
|
|
146
|
-
assert
|
|
147
|
-
|
|
148
|
-
for entry in history
|
|
149
|
-
)
|
|
182
|
+
data = response.json()
|
|
183
|
+
assert data["state"] == "running"
|
|
184
|
+
assert all(entry["type"] != "StateEntry" for entry in data["history"])
|
|
150
185
|
|
|
151
186
|
|
|
152
187
|
def test_assistant_cannot_be_terminated_via_nodes_api(client: TestClient):
|
|
@@ -233,15 +268,17 @@ def test_assistant_chat_can_be_cleared_via_nodes_api(client: TestClient):
|
|
|
233
268
|
)
|
|
234
269
|
|
|
235
270
|
|
|
236
|
-
def
|
|
271
|
+
def test_unknown_slash_input_can_be_sent_directly_to_workflow_leader(
|
|
272
|
+
client: TestClient,
|
|
273
|
+
):
|
|
237
274
|
tab = client.post(
|
|
238
275
|
"/api/workflows",
|
|
239
|
-
json={"title": "Execution"},
|
|
276
|
+
json={"title": "Execution", "allow_network": True},
|
|
240
277
|
).json()
|
|
241
278
|
|
|
242
279
|
response = client.post(
|
|
243
280
|
f"/api/nodes/{tab['leader_id']}/messages",
|
|
244
|
-
json={"content": "/
|
|
281
|
+
json={"content": "/unknown investigate the failure"},
|
|
245
282
|
)
|
|
246
283
|
|
|
247
284
|
assert response.status_code == 200
|
|
@@ -256,7 +293,7 @@ def test_human_input_can_be_sent_directly_to_workflow_leader(client: TestClient)
|
|
|
256
293
|
entry["type"] == "ReceivedMessage"
|
|
257
294
|
and entry["from_id"] == "human"
|
|
258
295
|
and entry["message_id"] == message_id
|
|
259
|
-
and entry["content"] == "/
|
|
296
|
+
and entry["content"] == "/unknown investigate the failure"
|
|
260
297
|
for entry in history
|
|
261
298
|
):
|
|
262
299
|
break
|
|
@@ -266,11 +303,210 @@ def test_human_input_can_be_sent_directly_to_workflow_leader(client: TestClient)
|
|
|
266
303
|
entry["type"] == "ReceivedMessage"
|
|
267
304
|
and entry["from_id"] == "human"
|
|
268
305
|
and entry["message_id"] == message_id
|
|
269
|
-
and entry["content"] == "/
|
|
306
|
+
and entry["content"] == "/unknown investigate the failure"
|
|
270
307
|
for entry in history
|
|
271
308
|
)
|
|
272
309
|
|
|
273
310
|
|
|
311
|
+
def test_leader_help_command_returns_visible_command_feedback(client: TestClient):
|
|
312
|
+
tab = client.post(
|
|
313
|
+
"/api/workflows",
|
|
314
|
+
json={"title": "Execution"},
|
|
315
|
+
).json()
|
|
316
|
+
|
|
317
|
+
response = client.post(
|
|
318
|
+
f"/api/nodes/{tab['leader_id']}/messages",
|
|
319
|
+
json={"content": "/help"},
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
assert response.status_code == 200
|
|
323
|
+
assert response.json() == {
|
|
324
|
+
"status": "command_executed",
|
|
325
|
+
"command_name": "/help",
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
|
|
329
|
+
|
|
330
|
+
assert any(
|
|
331
|
+
entry["type"] == "CommandResultEntry"
|
|
332
|
+
and entry["command_name"] == "/help"
|
|
333
|
+
and "Available commands" in entry["content"]
|
|
334
|
+
and "/compact" in entry["content"]
|
|
335
|
+
for entry in detail["history"]
|
|
336
|
+
)
|
|
337
|
+
assert not any(
|
|
338
|
+
entry["type"] == "ReceivedMessage" and entry.get("content") == "/help"
|
|
339
|
+
for entry in detail["history"]
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def test_leader_clear_command_clears_only_workflow_chat(client: TestClient):
|
|
344
|
+
assistant_id = _get_assistant_id(client)
|
|
345
|
+
assistant = registry.get(assistant_id)
|
|
346
|
+
assert assistant is not None
|
|
347
|
+
assistant.history.append(
|
|
348
|
+
ReceivedMessage(content="Assistant stays", from_id="human")
|
|
349
|
+
)
|
|
350
|
+
tab = client.post(
|
|
351
|
+
"/api/workflows",
|
|
352
|
+
json={"title": "Execution"},
|
|
353
|
+
).json()
|
|
354
|
+
worker = client.post(
|
|
355
|
+
f"/api/workflows/{tab['id']}/nodes",
|
|
356
|
+
json={"role_name": "Worker", "name": "Worker"},
|
|
357
|
+
).json()
|
|
358
|
+
leader = registry.get(tab["leader_id"])
|
|
359
|
+
assert leader is not None
|
|
360
|
+
leader.history.append(ReceivedMessage(content="Old workflow chat", from_id="human"))
|
|
361
|
+
leader.history.append(AssistantText(content="Old workflow reply"))
|
|
362
|
+
|
|
363
|
+
response = client.post(
|
|
364
|
+
f"/api/nodes/{tab['leader_id']}/messages",
|
|
365
|
+
json={"content": "/clear"},
|
|
366
|
+
)
|
|
367
|
+
|
|
368
|
+
assert response.status_code == 200
|
|
369
|
+
assert response.json() == {
|
|
370
|
+
"status": "command_executed",
|
|
371
|
+
"command_name": "/clear",
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
leader_detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
|
|
375
|
+
assistant_detail = client.get(f"/api/nodes/{assistant_id}").json()
|
|
376
|
+
workflow = client.get(f"/api/workflows/{tab['id']}").json()
|
|
377
|
+
|
|
378
|
+
assert not any(
|
|
379
|
+
entry["type"] in {"ReceivedMessage", "AssistantText"}
|
|
380
|
+
and entry.get("content") in {"Old workflow chat", "Old workflow reply"}
|
|
381
|
+
for entry in leader_detail["history"]
|
|
382
|
+
)
|
|
383
|
+
assert not any(
|
|
384
|
+
entry["type"] == "CommandResultEntry" and entry["command_name"] == "/clear"
|
|
385
|
+
for entry in leader_detail["history"]
|
|
386
|
+
)
|
|
387
|
+
assert any(
|
|
388
|
+
entry["type"] == "ReceivedMessage" and entry.get("content") == "Assistant stays"
|
|
389
|
+
for entry in assistant_detail["history"]
|
|
390
|
+
)
|
|
391
|
+
assert any(node["id"] == worker["id"] for node in workflow["nodes"])
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def test_leader_compact_command_keeps_history_and_adds_system_feedback(
|
|
395
|
+
monkeypatch,
|
|
396
|
+
client: TestClient,
|
|
397
|
+
):
|
|
398
|
+
tab = client.post(
|
|
399
|
+
"/api/workflows",
|
|
400
|
+
json={"title": "Execution", "allow_network": True},
|
|
401
|
+
).json()
|
|
402
|
+
leader = registry.get(tab["leader_id"])
|
|
403
|
+
assert leader is not None
|
|
404
|
+
leader.history.extend(
|
|
405
|
+
[
|
|
406
|
+
ReceivedMessage(content="Need a concise recap", from_id="human"),
|
|
407
|
+
AssistantText(content="I will summarize the workflow."),
|
|
408
|
+
]
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
monkeypatch.setattr(
|
|
412
|
+
"flowent.agent.gateway.chat",
|
|
413
|
+
lambda *args, **kwargs: LLMResponse(
|
|
414
|
+
content=(
|
|
415
|
+
"## Current Goal\nShip the shared commands.\n\n"
|
|
416
|
+
"## Active Task Boundary\nKeep it in the workflow chat.\n\n"
|
|
417
|
+
"## Key Constraints\nDo not clear visible history.\n\n"
|
|
418
|
+
"## Confirmed Decisions\nUse shared commands.\n\n"
|
|
419
|
+
"## Open Questions\nNone.\n\n"
|
|
420
|
+
"## Next Actions\nVerify the workflow panel."
|
|
421
|
+
)
|
|
422
|
+
),
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
response = client.post(
|
|
426
|
+
f"/api/nodes/{tab['leader_id']}/messages",
|
|
427
|
+
json={"content": "/compact workflow command rollout"},
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
assert response.status_code == 200
|
|
431
|
+
assert response.json() == {
|
|
432
|
+
"status": "command_executed",
|
|
433
|
+
"command_name": "/compact",
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
|
|
437
|
+
|
|
438
|
+
assert any(
|
|
439
|
+
entry["type"] == "ReceivedMessage"
|
|
440
|
+
and entry.get("content") == "Need a concise recap"
|
|
441
|
+
for entry in detail["history"]
|
|
442
|
+
)
|
|
443
|
+
assert any(
|
|
444
|
+
entry["type"] == "AssistantText"
|
|
445
|
+
and entry.get("content") == "I will summarize the workflow."
|
|
446
|
+
for entry in detail["history"]
|
|
447
|
+
)
|
|
448
|
+
assert any(
|
|
449
|
+
entry["type"] == "CommandResultEntry"
|
|
450
|
+
and entry["command_name"] == "/compact"
|
|
451
|
+
and entry.get("include_in_context") is False
|
|
452
|
+
and "Compacted this chat for future replies." in entry["content"]
|
|
453
|
+
and "Focus: workflow command rollout" in entry["content"]
|
|
454
|
+
for entry in detail["history"]
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
def test_leader_image_message_bypasses_conversation_commands(
|
|
459
|
+
client: TestClient,
|
|
460
|
+
monkeypatch,
|
|
461
|
+
):
|
|
462
|
+
tab = client.post(
|
|
463
|
+
"/api/workflows",
|
|
464
|
+
json={"title": "Execution"},
|
|
465
|
+
).json()
|
|
466
|
+
leader = registry.get(tab["leader_id"])
|
|
467
|
+
assert leader is not None
|
|
468
|
+
monkeypatch.setattr(leader, "supports_input_image", lambda: True)
|
|
469
|
+
|
|
470
|
+
upload_response = client.post(
|
|
471
|
+
"/api/image-assets",
|
|
472
|
+
files={"file": ("pixel.png", _ONE_PIXEL_PNG, "image/png")},
|
|
473
|
+
)
|
|
474
|
+
assert upload_response.status_code == 200
|
|
475
|
+
asset_id = upload_response.json()["id"]
|
|
476
|
+
|
|
477
|
+
response = client.post(
|
|
478
|
+
f"/api/nodes/{tab['leader_id']}/messages",
|
|
479
|
+
json={
|
|
480
|
+
"parts": [
|
|
481
|
+
{"type": "text", "text": "/help"},
|
|
482
|
+
{
|
|
483
|
+
"type": "image",
|
|
484
|
+
"asset_id": asset_id,
|
|
485
|
+
"mime_type": "image/png",
|
|
486
|
+
"width": 1,
|
|
487
|
+
"height": 1,
|
|
488
|
+
},
|
|
489
|
+
]
|
|
490
|
+
},
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
assert response.status_code == 200
|
|
494
|
+
assert response.json()["status"] == "sent"
|
|
495
|
+
|
|
496
|
+
detail = client.get(f"/api/nodes/{tab['leader_id']}").json()
|
|
497
|
+
|
|
498
|
+
assert any(
|
|
499
|
+
entry["type"] == "ReceivedMessage"
|
|
500
|
+
and entry["from_id"] == "human"
|
|
501
|
+
and entry["content"] == "/help[image]"
|
|
502
|
+
for entry in detail["history"]
|
|
503
|
+
)
|
|
504
|
+
assert not any(
|
|
505
|
+
entry["type"] == "CommandResultEntry" and entry["command_name"] == "/help"
|
|
506
|
+
for entry in detail["history"]
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
|
|
274
510
|
def test_leader_retry_rewrites_tail_and_reuses_image_parts(monkeypatch, client):
|
|
275
511
|
assistant_id = _get_assistant_id(client)
|
|
276
512
|
tab = client.post(
|
|
@@ -64,9 +64,10 @@ def test_roles_bootstrap_includes_tools_and_providers(client: TestClient, monkey
|
|
|
64
64
|
]
|
|
65
65
|
assert {role["name"] for role in payload["roles"]} == {"Steward", "Reviewer"}
|
|
66
66
|
tool_names = {tool["name"] for tool in payload["tools"]}
|
|
67
|
-
assert "delete_workflow" in tool_names
|
|
67
|
+
assert "delete_workflow" not in tool_names
|
|
68
|
+
assert "list_workflows" not in tool_names
|
|
68
69
|
assert "create_agent" in tool_names
|
|
69
|
-
assert "manage_roles" in tool_names
|
|
70
|
+
assert "manage_roles" not in tool_names
|
|
70
71
|
|
|
71
72
|
|
|
72
73
|
def test_create_role_rejects_duplicate_name(client: TestClient, monkeypatch):
|