flowent 0.0.4 → 0.0.6
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
|
@@ -4,7 +4,7 @@ import pytest
|
|
|
4
4
|
|
|
5
5
|
from flowent.agent import Agent
|
|
6
6
|
from flowent.graph_service import create_tab
|
|
7
|
-
from flowent.models import
|
|
7
|
+
from flowent.models import NodeConfig, NodeType
|
|
8
8
|
from flowent.registry import registry
|
|
9
9
|
from flowent.settings import CONDUCTOR_ROLE_NAME, RoleConfig, Settings
|
|
10
10
|
from flowent.tools.create_agent import CreateAgentTool
|
|
@@ -309,14 +309,12 @@ def test_create_agent_rejects_reserved_conductor_role(monkeypatch):
|
|
|
309
309
|
}
|
|
310
310
|
|
|
311
311
|
|
|
312
|
-
def
|
|
312
|
+
def test_create_agent_rejects_node_level_permissions(monkeypatch, tmp_path):
|
|
313
313
|
monkeypatch.setattr(
|
|
314
314
|
"flowent.settings.get_settings",
|
|
315
315
|
lambda: Settings(roles=[RoleConfig(name="Worker", system_prompt="Do work.")]),
|
|
316
316
|
)
|
|
317
317
|
|
|
318
|
-
allowed_dir = tmp_path / "allowed"
|
|
319
|
-
allowed_dir.mkdir()
|
|
320
318
|
disallowed_dir = tmp_path / "disallowed"
|
|
321
319
|
disallowed_dir.mkdir()
|
|
322
320
|
tab = create_tab(title="Task")
|
|
@@ -327,8 +325,6 @@ def test_create_agent_respects_write_dir_and_network_boundaries(monkeypatch, tmp
|
|
|
327
325
|
role_name="Conductor",
|
|
328
326
|
tab_id=tab.id,
|
|
329
327
|
tools=["create_agent"],
|
|
330
|
-
write_dirs=[str(allowed_dir)],
|
|
331
|
-
allow_network=False,
|
|
332
328
|
),
|
|
333
329
|
uuid=tab.leader_id,
|
|
334
330
|
)
|
|
@@ -353,90 +349,16 @@ def test_create_agent_respects_write_dir_and_network_boundaries(monkeypatch, tmp
|
|
|
353
349
|
)
|
|
354
350
|
|
|
355
351
|
assert write_dir_result == {
|
|
356
|
-
"error":
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
"error": "allow_network boundary exceeded: parent disallows network access"
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
def test_create_agent_also_respects_tab_leader_boundaries(monkeypatch, tmp_path):
|
|
364
|
-
monkeypatch.setattr(
|
|
365
|
-
"flowent.settings.get_settings",
|
|
366
|
-
lambda: Settings(roles=[RoleConfig(name="Worker", system_prompt="Do work.")]),
|
|
367
|
-
)
|
|
368
|
-
|
|
369
|
-
leader_dir = tmp_path / "leader"
|
|
370
|
-
leader_dir.mkdir()
|
|
371
|
-
creator_dir = tmp_path / "creator"
|
|
372
|
-
creator_dir.mkdir()
|
|
373
|
-
creator_child = creator_dir / "child"
|
|
374
|
-
creator_child.mkdir()
|
|
375
|
-
tab = create_tab(title="Task")
|
|
376
|
-
|
|
377
|
-
leader = Agent(
|
|
378
|
-
NodeConfig(
|
|
379
|
-
node_type=NodeType.AGENT,
|
|
380
|
-
role_name="Conductor",
|
|
381
|
-
tab_id=tab.id,
|
|
382
|
-
tools=[],
|
|
383
|
-
write_dirs=[str(leader_dir)],
|
|
384
|
-
allow_network=False,
|
|
385
|
-
),
|
|
386
|
-
uuid=tab.leader_id,
|
|
387
|
-
)
|
|
388
|
-
creator = Agent(
|
|
389
|
-
NodeConfig(
|
|
390
|
-
node_type=NodeType.AGENT,
|
|
391
|
-
role_name="Worker",
|
|
392
|
-
tab_id=tab.id,
|
|
393
|
-
tools=["create_agent"],
|
|
394
|
-
write_dirs=[str(creator_dir)],
|
|
395
|
-
allow_network=True,
|
|
396
|
-
),
|
|
397
|
-
uuid="worker",
|
|
398
|
-
)
|
|
399
|
-
registry.register(leader)
|
|
400
|
-
registry.register(creator)
|
|
401
|
-
workspace_store.upsert_node_record(
|
|
402
|
-
GraphNodeRecord(
|
|
403
|
-
id=leader.uuid,
|
|
404
|
-
config=leader.config,
|
|
405
|
-
state=AgentState.INITIALIZING,
|
|
406
|
-
)
|
|
407
|
-
)
|
|
408
|
-
workspace_store.upsert_node_record(
|
|
409
|
-
GraphNodeRecord(
|
|
410
|
-
id=creator.uuid,
|
|
411
|
-
config=creator.config,
|
|
412
|
-
state=AgentState.INITIALIZING,
|
|
352
|
+
"error": (
|
|
353
|
+
"create_agent uses the current workflow permissions; update the "
|
|
354
|
+
"workflow permissions instead"
|
|
413
355
|
)
|
|
414
|
-
)
|
|
415
|
-
|
|
416
|
-
write_dir_result = json.loads(
|
|
417
|
-
CreateAgentTool().execute(
|
|
418
|
-
creator,
|
|
419
|
-
{
|
|
420
|
-
"role_name": "Worker",
|
|
421
|
-
"write_dirs": [str(creator_child)],
|
|
422
|
-
},
|
|
423
|
-
)
|
|
424
|
-
)
|
|
425
|
-
network_result = json.loads(
|
|
426
|
-
CreateAgentTool().execute(
|
|
427
|
-
creator,
|
|
428
|
-
{
|
|
429
|
-
"role_name": "Worker",
|
|
430
|
-
"allow_network": True,
|
|
431
|
-
},
|
|
432
|
-
)
|
|
433
|
-
)
|
|
434
|
-
|
|
435
|
-
assert write_dir_result == {
|
|
436
|
-
"error": f"write_dirs boundary exceeded: {creator_child}"
|
|
437
356
|
}
|
|
438
357
|
assert network_result == {
|
|
439
|
-
"error":
|
|
358
|
+
"error": (
|
|
359
|
+
"create_agent uses the current workflow permissions; update the "
|
|
360
|
+
"workflow permissions instead"
|
|
361
|
+
)
|
|
440
362
|
}
|
|
441
363
|
|
|
442
364
|
|
|
@@ -489,16 +411,6 @@ def test_create_agent_tool_schema_exposes_workflow_placement_options():
|
|
|
489
411
|
"items": {"type": "string"},
|
|
490
412
|
"description": "Optional additional tools",
|
|
491
413
|
},
|
|
492
|
-
"write_dirs": {
|
|
493
|
-
"type": "array",
|
|
494
|
-
"items": {"type": "string"},
|
|
495
|
-
"description": "Optional writable directories",
|
|
496
|
-
},
|
|
497
|
-
"allow_network": {
|
|
498
|
-
"type": "boolean",
|
|
499
|
-
"description": "Whether the node can access the network",
|
|
500
|
-
"default": False,
|
|
501
|
-
},
|
|
502
414
|
"placement": {
|
|
503
415
|
"type": "string",
|
|
504
416
|
"enum": ["standalone", "after", "between"],
|
|
@@ -7,7 +7,9 @@ from flowent.graph_service import create_agent_node, create_edge, create_tab
|
|
|
7
7
|
from flowent.models import NodeConfig, NodeType
|
|
8
8
|
from flowent.registry import registry
|
|
9
9
|
from flowent.settings import RoleConfig, Settings
|
|
10
|
+
from flowent.tools.create_tab import CreateTabTool
|
|
10
11
|
from flowent.tools.delete_tab import DeleteTabTool
|
|
12
|
+
from flowent.tools.list_tabs import ListTabsTool
|
|
11
13
|
from flowent.workspace_store import workspace_store
|
|
12
14
|
|
|
13
15
|
|
|
@@ -81,3 +83,34 @@ def test_delete_tab_tool_rejects_non_assistant():
|
|
|
81
83
|
result = json.loads(DeleteTabTool().execute(agent, {"workflow_id": "tab-1"}))
|
|
82
84
|
|
|
83
85
|
assert result == {"error": "Only the Assistant may delete workflows"}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_create_tab_tool_rejects_non_assistant():
|
|
89
|
+
agent = Agent(
|
|
90
|
+
NodeConfig(
|
|
91
|
+
node_type=NodeType.AGENT,
|
|
92
|
+
role_name="Worker",
|
|
93
|
+
tools=["create_workflow"],
|
|
94
|
+
),
|
|
95
|
+
uuid="worker",
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
result = json.loads(CreateTabTool().execute(agent, {"title": "Draft"}))
|
|
99
|
+
|
|
100
|
+
assert result == {"error": "Only the Assistant may create workflows"}
|
|
101
|
+
assert workspace_store.list_tabs() == []
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_list_tabs_tool_rejects_non_assistant():
|
|
105
|
+
agent = Agent(
|
|
106
|
+
NodeConfig(
|
|
107
|
+
node_type=NodeType.AGENT,
|
|
108
|
+
role_name="Worker",
|
|
109
|
+
tools=["list_workflows"],
|
|
110
|
+
),
|
|
111
|
+
uuid="worker",
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
result = json.loads(ListTabsTool().execute(agent, {}))
|
|
115
|
+
|
|
116
|
+
assert result == {"error": "Only the Assistant may list workflows"}
|
|
@@ -444,6 +444,7 @@ def test_manage_providers_list_models_streams_model_ids(monkeypatch):
|
|
|
444
444
|
"context_window_tokens": None,
|
|
445
445
|
"input_image": False,
|
|
446
446
|
"output_image": False,
|
|
447
|
+
"structured_output": False,
|
|
447
448
|
},
|
|
448
449
|
{
|
|
449
450
|
"model": "provider-1-b",
|
|
@@ -451,6 +452,7 @@ def test_manage_providers_list_models_streams_model_ids(monkeypatch):
|
|
|
451
452
|
"context_window_tokens": None,
|
|
452
453
|
"input_image": False,
|
|
453
454
|
"output_image": False,
|
|
455
|
+
"structured_output": False,
|
|
454
456
|
},
|
|
455
457
|
]
|
|
456
458
|
assert "".join(chunks) == (
|
|
@@ -48,6 +48,7 @@ def test_manage_settings_get_returns_current_settings(monkeypatch):
|
|
|
48
48
|
"active_model": "gpt-4o",
|
|
49
49
|
"input_image": None,
|
|
50
50
|
"output_image": None,
|
|
51
|
+
"structured_output": None,
|
|
51
52
|
"context_window_tokens": None,
|
|
52
53
|
"capabilities": None,
|
|
53
54
|
"resolved_context_window_tokens": None,
|
|
@@ -103,6 +104,7 @@ def test_manage_settings_update_changes_active_provider_and_model(monkeypatch):
|
|
|
103
104
|
"active_model": "gpt-4.1",
|
|
104
105
|
"input_image": None,
|
|
105
106
|
"output_image": None,
|
|
107
|
+
"structured_output": None,
|
|
106
108
|
"context_window_tokens": None,
|
|
107
109
|
"capabilities": None,
|
|
108
110
|
"resolved_context_window_tokens": None,
|
|
@@ -448,6 +450,7 @@ def test_manage_settings_update_changes_model_metadata_overrides_and_token_limit
|
|
|
448
450
|
assert result["model"]["capabilities"] == {
|
|
449
451
|
"input_image": True,
|
|
450
452
|
"output_image": False,
|
|
453
|
+
"structured_output": False,
|
|
451
454
|
}
|
|
452
455
|
assert result["model"]["auto_compact_token_limit"] == 48000
|
|
453
456
|
assert settings.model.context_window_tokens == 64000
|
|
@@ -5,6 +5,7 @@ import pytest
|
|
|
5
5
|
from flowent.agent import Agent
|
|
6
6
|
from flowent.models import AgentState, GraphNodeRecord, NodeConfig, NodeType, Tab
|
|
7
7
|
from flowent.registry import registry
|
|
8
|
+
from flowent.settings import AssistantSettings, Settings
|
|
8
9
|
from flowent.tools.set_permissions import SetPermissionsTool
|
|
9
10
|
from flowent.workspace_store import workspace_store
|
|
10
11
|
|
|
@@ -67,15 +68,40 @@ def _register_live_node(record: GraphNodeRecord) -> Agent:
|
|
|
67
68
|
return node
|
|
68
69
|
|
|
69
70
|
|
|
70
|
-
def
|
|
71
|
+
def _grant_assistant_permissions(
|
|
72
|
+
monkeypatch,
|
|
73
|
+
*,
|
|
74
|
+
write_dirs: list[str],
|
|
75
|
+
allow_network: bool,
|
|
76
|
+
) -> None:
|
|
77
|
+
monkeypatch.setattr(
|
|
78
|
+
"flowent.settings.get_settings",
|
|
79
|
+
lambda: Settings(
|
|
80
|
+
assistant=AssistantSettings(
|
|
81
|
+
write_dirs=list(write_dirs),
|
|
82
|
+
allow_network=allow_network,
|
|
83
|
+
)
|
|
84
|
+
),
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def test_set_permissions_updates_workflow_boundary_for_all_nodes(monkeypatch, tmp_path):
|
|
71
89
|
root_dir = tmp_path / "root"
|
|
72
90
|
keep_boundary = root_dir / "keep"
|
|
73
91
|
keep_dir = keep_boundary / "child"
|
|
74
92
|
drop_dir = root_dir / "drop"
|
|
75
93
|
keep_dir.mkdir(parents=True)
|
|
76
94
|
drop_dir.mkdir(parents=True)
|
|
95
|
+
_grant_assistant_permissions(
|
|
96
|
+
monkeypatch,
|
|
97
|
+
write_dirs=[str(root_dir)],
|
|
98
|
+
allow_network=True,
|
|
99
|
+
)
|
|
77
100
|
|
|
78
101
|
tab = Tab(id="tab-1", title="Task", leader_id="leader-1")
|
|
102
|
+
tab.allow_network = True
|
|
103
|
+
tab.write_dirs = [str(root_dir)]
|
|
104
|
+
tab.permissions_initialized = True
|
|
79
105
|
workspace_store.upsert_tab(tab)
|
|
80
106
|
leader = _make_record(
|
|
81
107
|
node_id="leader-1",
|
|
@@ -135,21 +161,33 @@ def test_set_permissions_updates_leader_and_clamps_existing_workers(tmp_path):
|
|
|
135
161
|
"write_dirs": [str(keep_boundary)],
|
|
136
162
|
"updated_node_ids": ["leader-1", "worker-keep", "worker-drop"],
|
|
137
163
|
}
|
|
138
|
-
|
|
139
|
-
assert
|
|
140
|
-
assert
|
|
164
|
+
updated_tab = workspace_store.get_tab(tab.id)
|
|
165
|
+
assert updated_tab is not None
|
|
166
|
+
assert updated_tab.allow_network is False
|
|
167
|
+
assert updated_tab.write_dirs == [str(keep_boundary)]
|
|
168
|
+
assert leader_live.config.allow_network is True
|
|
169
|
+
assert leader_live.config.write_dirs == [str(root_dir)]
|
|
170
|
+
assert keep_live.config.allow_network is True
|
|
141
171
|
assert keep_live.config.write_dirs == [str(keep_dir)]
|
|
142
|
-
assert drop_live.config.allow_network is
|
|
143
|
-
assert drop_live.config.write_dirs == []
|
|
172
|
+
assert drop_live.config.allow_network is True
|
|
173
|
+
assert drop_live.config.write_dirs == [str(drop_dir)]
|
|
144
174
|
|
|
145
175
|
|
|
146
|
-
def test_set_permissions_keeps_omitted_fields_unchanged(tmp_path):
|
|
176
|
+
def test_set_permissions_keeps_omitted_fields_unchanged(monkeypatch, tmp_path):
|
|
147
177
|
root_dir = tmp_path / "root"
|
|
148
178
|
narrowed_dir = root_dir / "narrowed"
|
|
149
179
|
root_dir.mkdir()
|
|
150
180
|
narrowed_dir.mkdir()
|
|
181
|
+
_grant_assistant_permissions(
|
|
182
|
+
monkeypatch,
|
|
183
|
+
write_dirs=[str(root_dir)],
|
|
184
|
+
allow_network=False,
|
|
185
|
+
)
|
|
151
186
|
|
|
152
187
|
tab = Tab(id="tab-1", title="Task", leader_id="leader-1")
|
|
188
|
+
tab.allow_network = True
|
|
189
|
+
tab.write_dirs = [str(root_dir)]
|
|
190
|
+
tab.permissions_initialized = True
|
|
153
191
|
workspace_store.upsert_tab(tab)
|
|
154
192
|
leader = _make_record(
|
|
155
193
|
node_id="leader-1",
|
|
@@ -183,17 +221,32 @@ def test_set_permissions_keeps_omitted_fields_unchanged(tmp_path):
|
|
|
183
221
|
|
|
184
222
|
assert result["allow_network"] is True
|
|
185
223
|
assert result["write_dirs"] == [str(narrowed_dir)]
|
|
224
|
+
updated_tab = workspace_store.get_tab(tab.id)
|
|
225
|
+
assert updated_tab is not None
|
|
226
|
+
assert updated_tab.allow_network is True
|
|
227
|
+
assert updated_tab.write_dirs == [str(narrowed_dir)]
|
|
186
228
|
assert leader_live.config.allow_network is True
|
|
187
|
-
assert leader_live.config.write_dirs == [str(
|
|
229
|
+
assert leader_live.config.write_dirs == [str(root_dir)]
|
|
188
230
|
|
|
189
231
|
|
|
190
|
-
def
|
|
232
|
+
def test_set_permissions_broadens_workflow_boundary_without_mutating_workers(
|
|
233
|
+
monkeypatch,
|
|
234
|
+
tmp_path,
|
|
235
|
+
):
|
|
191
236
|
root_dir = tmp_path / "root"
|
|
192
237
|
current_boundary = root_dir / "current"
|
|
193
238
|
worker_dir = current_boundary / "child"
|
|
194
239
|
worker_dir.mkdir(parents=True)
|
|
240
|
+
_grant_assistant_permissions(
|
|
241
|
+
monkeypatch,
|
|
242
|
+
write_dirs=[str(root_dir)],
|
|
243
|
+
allow_network=True,
|
|
244
|
+
)
|
|
195
245
|
|
|
196
246
|
tab = Tab(id="tab-1", title="Task", leader_id="leader-1")
|
|
247
|
+
tab.allow_network = False
|
|
248
|
+
tab.write_dirs = [str(current_boundary)]
|
|
249
|
+
tab.permissions_initialized = True
|
|
197
250
|
workspace_store.upsert_tab(tab)
|
|
198
251
|
leader = _make_record(
|
|
199
252
|
node_id="leader-1",
|
|
@@ -237,15 +290,30 @@ def test_set_permissions_does_not_auto_broaden_existing_workers(tmp_path):
|
|
|
237
290
|
|
|
238
291
|
assert result["allow_network"] is True
|
|
239
292
|
assert result["write_dirs"] == [str(root_dir)]
|
|
293
|
+
updated_tab = workspace_store.get_tab(tab.id)
|
|
294
|
+
assert updated_tab is not None
|
|
295
|
+
assert updated_tab.allow_network is True
|
|
296
|
+
assert updated_tab.write_dirs == [str(root_dir)]
|
|
240
297
|
assert worker_live.config.allow_network is False
|
|
241
298
|
assert worker_live.config.write_dirs == [str(worker_dir)]
|
|
242
299
|
|
|
243
300
|
|
|
244
|
-
def test_set_permissions_rejects_allow_network_outside_caller_boundary(
|
|
301
|
+
def test_set_permissions_rejects_allow_network_outside_caller_boundary(
|
|
302
|
+
monkeypatch,
|
|
303
|
+
tmp_path,
|
|
304
|
+
):
|
|
245
305
|
root_dir = tmp_path / "root"
|
|
246
306
|
root_dir.mkdir()
|
|
307
|
+
_grant_assistant_permissions(
|
|
308
|
+
monkeypatch,
|
|
309
|
+
write_dirs=[str(root_dir)],
|
|
310
|
+
allow_network=False,
|
|
311
|
+
)
|
|
247
312
|
|
|
248
313
|
tab = Tab(id="tab-1", title="Task", leader_id="leader-1")
|
|
314
|
+
tab.allow_network = False
|
|
315
|
+
tab.write_dirs = [str(root_dir)]
|
|
316
|
+
tab.permissions_initialized = True
|
|
249
317
|
workspace_store.upsert_tab(tab)
|
|
250
318
|
workspace_store.upsert_node_record(
|
|
251
319
|
_make_record(
|
|
@@ -282,13 +350,24 @@ def test_set_permissions_rejects_allow_network_outside_caller_boundary(tmp_path)
|
|
|
282
350
|
}
|
|
283
351
|
|
|
284
352
|
|
|
285
|
-
def test_set_permissions_rejects_write_dirs_outside_caller_boundary(
|
|
353
|
+
def test_set_permissions_rejects_write_dirs_outside_caller_boundary(
|
|
354
|
+
monkeypatch,
|
|
355
|
+
tmp_path,
|
|
356
|
+
):
|
|
286
357
|
caller_dir = tmp_path / "caller"
|
|
287
358
|
other_dir = tmp_path / "other"
|
|
288
359
|
caller_dir.mkdir()
|
|
289
360
|
other_dir.mkdir()
|
|
361
|
+
_grant_assistant_permissions(
|
|
362
|
+
monkeypatch,
|
|
363
|
+
write_dirs=[str(caller_dir)],
|
|
364
|
+
allow_network=True,
|
|
365
|
+
)
|
|
290
366
|
|
|
291
367
|
tab = Tab(id="tab-1", title="Task", leader_id="leader-1")
|
|
368
|
+
tab.allow_network = False
|
|
369
|
+
tab.write_dirs = [str(caller_dir)]
|
|
370
|
+
tab.permissions_initialized = True
|
|
292
371
|
workspace_store.upsert_tab(tab)
|
|
293
372
|
workspace_store.upsert_node_record(
|
|
294
373
|
_make_record(
|
|
@@ -323,13 +402,18 @@ def test_set_permissions_rejects_write_dirs_outside_caller_boundary(tmp_path):
|
|
|
323
402
|
assert result == {"error": f"write_dirs boundary exceeded: {other_dir}"}
|
|
324
403
|
|
|
325
404
|
|
|
326
|
-
def
|
|
405
|
+
def test_set_permissions_allows_explicitly_granted_non_assistant_agent_for_own_workflow(
|
|
406
|
+
tmp_path,
|
|
407
|
+
):
|
|
327
408
|
root_dir = tmp_path / "root"
|
|
328
409
|
narrowed_dir = root_dir / "narrowed"
|
|
329
410
|
root_dir.mkdir()
|
|
330
411
|
narrowed_dir.mkdir()
|
|
331
412
|
|
|
332
413
|
tab = Tab(id="tab-1", title="Task", leader_id="leader-1")
|
|
414
|
+
tab.allow_network = True
|
|
415
|
+
tab.write_dirs = [str(root_dir)]
|
|
416
|
+
tab.permissions_initialized = True
|
|
333
417
|
workspace_store.upsert_tab(tab)
|
|
334
418
|
workspace_store.upsert_node_record(
|
|
335
419
|
_make_record(
|
|
@@ -367,6 +451,126 @@ def test_set_permissions_allows_explicitly_granted_non_assistant_agent(tmp_path)
|
|
|
367
451
|
|
|
368
452
|
assert result["tab_id"] == tab.id
|
|
369
453
|
assert result["write_dirs"] == [str(narrowed_dir)]
|
|
454
|
+
updated_tab = workspace_store.get_tab(tab.id)
|
|
455
|
+
assert updated_tab is not None
|
|
456
|
+
assert updated_tab.write_dirs == [str(narrowed_dir)]
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def test_set_permissions_rejects_non_assistant_cross_workflow(tmp_path):
|
|
460
|
+
root_dir = tmp_path / "root"
|
|
461
|
+
root_dir.mkdir()
|
|
462
|
+
|
|
463
|
+
own_tab = Tab(id="tab-1", title="Own", leader_id="leader-1")
|
|
464
|
+
own_tab.allow_network = True
|
|
465
|
+
own_tab.write_dirs = [str(root_dir)]
|
|
466
|
+
own_tab.permissions_initialized = True
|
|
467
|
+
other_tab = Tab(id="tab-2", title="Other", leader_id="leader-2")
|
|
468
|
+
other_tab.allow_network = True
|
|
469
|
+
other_tab.write_dirs = [str(root_dir)]
|
|
470
|
+
other_tab.permissions_initialized = True
|
|
471
|
+
workspace_store.upsert_tab(own_tab)
|
|
472
|
+
workspace_store.upsert_tab(other_tab)
|
|
473
|
+
workspace_store.upsert_node_record(
|
|
474
|
+
_make_record(
|
|
475
|
+
node_id="leader-1",
|
|
476
|
+
tab_id=own_tab.id,
|
|
477
|
+
role_name="Conductor",
|
|
478
|
+
name="Leader",
|
|
479
|
+
write_dirs=[str(root_dir)],
|
|
480
|
+
allow_network=True,
|
|
481
|
+
tools=["set_permissions"],
|
|
482
|
+
)
|
|
483
|
+
)
|
|
484
|
+
workspace_store.upsert_node_record(
|
|
485
|
+
_make_record(
|
|
486
|
+
node_id="leader-2",
|
|
487
|
+
tab_id=other_tab.id,
|
|
488
|
+
role_name="Conductor",
|
|
489
|
+
name="Leader",
|
|
490
|
+
write_dirs=[str(root_dir)],
|
|
491
|
+
allow_network=True,
|
|
492
|
+
)
|
|
493
|
+
)
|
|
494
|
+
leader = Agent(
|
|
495
|
+
NodeConfig(
|
|
496
|
+
node_type=NodeType.AGENT,
|
|
497
|
+
role_name="Conductor",
|
|
498
|
+
tab_id=own_tab.id,
|
|
499
|
+
name="Leader",
|
|
500
|
+
tools=["set_permissions"],
|
|
501
|
+
write_dirs=[str(root_dir)],
|
|
502
|
+
allow_network=True,
|
|
503
|
+
),
|
|
504
|
+
uuid="leader-1",
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
result = json.loads(
|
|
508
|
+
SetPermissionsTool().execute(
|
|
509
|
+
leader,
|
|
510
|
+
{
|
|
511
|
+
"workflow_id": other_tab.id,
|
|
512
|
+
"write_dirs": [],
|
|
513
|
+
},
|
|
514
|
+
)
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
assert result == {
|
|
518
|
+
"error": "Workflow permissions can only be changed for this workflow"
|
|
519
|
+
}
|
|
520
|
+
updated_other_tab = workspace_store.get_tab(other_tab.id)
|
|
521
|
+
assert updated_other_tab is not None
|
|
522
|
+
assert updated_other_tab.write_dirs == [str(root_dir)]
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def test_set_permissions_migrates_legacy_leader_permissions(tmp_path):
|
|
526
|
+
root_dir = tmp_path / "root"
|
|
527
|
+
narrowed_dir = root_dir / "narrowed"
|
|
528
|
+
root_dir.mkdir()
|
|
529
|
+
narrowed_dir.mkdir()
|
|
530
|
+
|
|
531
|
+
tab = Tab(id="tab-1", title="Task", leader_id="leader-1")
|
|
532
|
+
workspace_store.upsert_tab(tab)
|
|
533
|
+
workspace_store.upsert_node_record(
|
|
534
|
+
_make_record(
|
|
535
|
+
node_id="leader-1",
|
|
536
|
+
tab_id=tab.id,
|
|
537
|
+
role_name="Conductor",
|
|
538
|
+
name="Leader",
|
|
539
|
+
write_dirs=[str(root_dir)],
|
|
540
|
+
allow_network=True,
|
|
541
|
+
tools=["set_permissions"],
|
|
542
|
+
)
|
|
543
|
+
)
|
|
544
|
+
leader = Agent(
|
|
545
|
+
NodeConfig(
|
|
546
|
+
node_type=NodeType.AGENT,
|
|
547
|
+
role_name="Conductor",
|
|
548
|
+
tab_id=tab.id,
|
|
549
|
+
name="Leader",
|
|
550
|
+
tools=["set_permissions"],
|
|
551
|
+
write_dirs=[str(root_dir)],
|
|
552
|
+
allow_network=True,
|
|
553
|
+
),
|
|
554
|
+
uuid="leader-1",
|
|
555
|
+
)
|
|
556
|
+
|
|
557
|
+
result = json.loads(
|
|
558
|
+
SetPermissionsTool().execute(
|
|
559
|
+
leader,
|
|
560
|
+
{
|
|
561
|
+
"workflow_id": tab.id,
|
|
562
|
+
"write_dirs": [str(narrowed_dir)],
|
|
563
|
+
},
|
|
564
|
+
)
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
updated_tab = workspace_store.get_tab(tab.id)
|
|
568
|
+
assert result["allow_network"] is True
|
|
569
|
+
assert result["write_dirs"] == [str(narrowed_dir)]
|
|
570
|
+
assert updated_tab is not None
|
|
571
|
+
assert updated_tab.permissions_initialized is True
|
|
572
|
+
assert updated_tab.allow_network is True
|
|
573
|
+
assert updated_tab.write_dirs == [str(narrowed_dir)]
|
|
370
574
|
|
|
371
575
|
|
|
372
576
|
def test_set_permissions_tool_schema_matches_patch_contract():
|
|
@@ -3,6 +3,17 @@ from flowent.models import NodeConfig, NodeType
|
|
|
3
3
|
from flowent.tools import build_tool_registry
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
class _FakeMCPService:
|
|
7
|
+
def __init__(self, descriptors):
|
|
8
|
+
self._descriptors = descriptors
|
|
9
|
+
|
|
10
|
+
def list_agent_dynamic_tools(self, agent):
|
|
11
|
+
return list(self._descriptors)
|
|
12
|
+
|
|
13
|
+
def list_discovered_tools(self):
|
|
14
|
+
return list(self._descriptors)
|
|
15
|
+
|
|
16
|
+
|
|
6
17
|
def test_empty_tools_list_grants_minimum_tools():
|
|
7
18
|
agent = Agent(NodeConfig(node_type=NodeType.AGENT, tools=[]))
|
|
8
19
|
|
|
@@ -78,6 +89,98 @@ def test_tool_registry_grants_workflow_graph_tools_when_explicitly_allowed():
|
|
|
78
89
|
]
|
|
79
90
|
|
|
80
91
|
|
|
92
|
+
def test_tool_registry_filters_assistant_only_tools_for_workflow_nodes():
|
|
93
|
+
agent = Agent(
|
|
94
|
+
NodeConfig(
|
|
95
|
+
node_type=NodeType.AGENT,
|
|
96
|
+
role_name="Conductor",
|
|
97
|
+
tools=[
|
|
98
|
+
"create_workflow",
|
|
99
|
+
"delete_workflow",
|
|
100
|
+
"set_permissions",
|
|
101
|
+
"create_agent",
|
|
102
|
+
"connect",
|
|
103
|
+
"list_workflows",
|
|
104
|
+
"manage_settings",
|
|
105
|
+
],
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
tools = build_tool_registry().get_tools_for_agent(agent)
|
|
110
|
+
|
|
111
|
+
assert [tool.name for tool in tools] == [
|
|
112
|
+
"idle",
|
|
113
|
+
"sleep",
|
|
114
|
+
"todo",
|
|
115
|
+
"contacts",
|
|
116
|
+
"send",
|
|
117
|
+
"set_permissions",
|
|
118
|
+
"create_agent",
|
|
119
|
+
"connect",
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_tool_registry_filters_assistant_only_mcp_tools_for_workflow_nodes(
|
|
124
|
+
monkeypatch,
|
|
125
|
+
):
|
|
126
|
+
from flowent.mcp_service import MCPToolDescriptor
|
|
127
|
+
|
|
128
|
+
workflow_tool = MCPToolDescriptor(
|
|
129
|
+
server_name="flowent",
|
|
130
|
+
tool_name="list_workflows",
|
|
131
|
+
fully_qualified_id="mcp__flowent__list_workflows",
|
|
132
|
+
)
|
|
133
|
+
regular_tool = MCPToolDescriptor(
|
|
134
|
+
server_name="flowent",
|
|
135
|
+
tool_name="search_notes",
|
|
136
|
+
fully_qualified_id="mcp__flowent__search_notes",
|
|
137
|
+
)
|
|
138
|
+
fake_service = _FakeMCPService([workflow_tool, regular_tool])
|
|
139
|
+
monkeypatch.setattr("flowent.mcp_service.mcp_service", fake_service)
|
|
140
|
+
agent = Agent(
|
|
141
|
+
NodeConfig(
|
|
142
|
+
node_type=NodeType.AGENT,
|
|
143
|
+
role_name="Worker",
|
|
144
|
+
tools=[
|
|
145
|
+
"mcp__flowent__list_workflows",
|
|
146
|
+
"mcp__flowent__search_notes",
|
|
147
|
+
],
|
|
148
|
+
)
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
tools = build_tool_registry().get_tools_for_agent(agent)
|
|
152
|
+
|
|
153
|
+
assert "mcp__flowent__list_workflows" not in {tool.name for tool in tools}
|
|
154
|
+
assert "mcp__flowent__search_notes" in {tool.name for tool in tools}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_build_tools_for_role_filters_assistant_only_mcp_tools(monkeypatch):
|
|
158
|
+
from flowent.graph_service import build_tools_for_role
|
|
159
|
+
from flowent.settings import RoleConfig, Settings
|
|
160
|
+
|
|
161
|
+
monkeypatch.setattr(
|
|
162
|
+
"flowent.settings.get_settings",
|
|
163
|
+
lambda: Settings(
|
|
164
|
+
roles=[
|
|
165
|
+
RoleConfig(
|
|
166
|
+
name="Worker",
|
|
167
|
+
system_prompt="Do work.",
|
|
168
|
+
included_tools=["mcp__flowent__list_workflows", "read"],
|
|
169
|
+
)
|
|
170
|
+
]
|
|
171
|
+
),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
tools = build_tools_for_role(
|
|
175
|
+
"Worker",
|
|
176
|
+
requested_tools=["mcp__flowent__delete_workflow", "mcp__flowent__search_notes"],
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
assert "mcp__flowent__list_workflows" not in tools
|
|
180
|
+
assert "mcp__flowent__delete_workflow" not in tools
|
|
181
|
+
assert "mcp__flowent__search_notes" in tools
|
|
182
|
+
|
|
183
|
+
|
|
81
184
|
def test_tool_registry_shows_management_tools_in_agent_visible_list():
|
|
82
185
|
visible_tool_names = {
|
|
83
186
|
tool.name for tool in build_tool_registry().list_tools(agent_visible_only=True)
|