flowent 0.0.1 → 0.0.4
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 +19 -8
- package/backend/.python-version +1 -0
- package/backend/pyproject.toml +57 -0
- package/backend/src/flowent/__init__.py +3 -0
- 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__/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__/stats_service.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/workspace_store.cpython-313.pyc +0 -0
- package/backend/src/flowent/_version.py +7 -0
- package/backend/src/flowent/access.py +247 -0
- package/backend/src/flowent/agent.py +2808 -0
- package/backend/src/flowent/assistant_commands.py +106 -0
- package/backend/src/flowent/channels/__init__.py +3 -0
- 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 +615 -0
- package/backend/src/flowent/cli.py +85 -0
- package/backend/src/flowent/config.py +14 -0
- package/backend/src/flowent/dev.py +3 -0
- package/backend/src/flowent/events.py +157 -0
- package/backend/src/flowent/graph_runtime.py +60 -0
- package/backend/src/flowent/graph_service.py +1346 -0
- package/backend/src/flowent/image_assets.py +356 -0
- package/backend/src/flowent/logging.py +155 -0
- package/backend/src/flowent/main.py +124 -0
- package/backend/src/flowent/mcp_service.py +1904 -0
- package/backend/src/flowent/model_metadata.py +98 -0
- package/backend/src/flowent/models/__init__.py +121 -0
- 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 +33 -0
- package/backend/src/flowent/models/base.py +24 -0
- package/backend/src/flowent/models/blueprint.py +176 -0
- package/backend/src/flowent/models/content.py +164 -0
- package/backend/src/flowent/models/delta.py +44 -0
- package/backend/src/flowent/models/event.py +51 -0
- package/backend/src/flowent/models/graph.py +437 -0
- package/backend/src/flowent/models/history.py +214 -0
- package/backend/src/flowent/models/llm.py +61 -0
- package/backend/src/flowent/models/message.py +27 -0
- package/backend/src/flowent/models/tab.py +48 -0
- package/backend/src/flowent/models/todo.py +10 -0
- package/backend/src/flowent/network.py +146 -0
- package/backend/src/flowent/prompts/__init__.py +67 -0
- 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 +250 -0
- package/backend/src/flowent/prompts/steward.py +64 -0
- package/backend/src/flowent/providers/__init__.py +23 -0
- package/backend/src/flowent/providers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/anthropic.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/base_url.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/configuration.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/content.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/errors.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/gateway.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/headers.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/management.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/openai.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/openai_responses.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/registry.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/sse.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/thinking.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/anthropic.py +468 -0
- package/backend/src/flowent/providers/base_url.py +60 -0
- package/backend/src/flowent/providers/configuration.py +182 -0
- package/backend/src/flowent/providers/content.py +122 -0
- package/backend/src/flowent/providers/errors.py +223 -0
- package/backend/src/flowent/providers/gateway.py +169 -0
- package/backend/src/flowent/providers/gemini.py +447 -0
- package/backend/src/flowent/providers/headers.py +20 -0
- package/backend/src/flowent/providers/management.py +96 -0
- package/backend/src/flowent/providers/ollama.py +293 -0
- package/backend/src/flowent/providers/openai.py +422 -0
- package/backend/src/flowent/providers/openai_responses.py +655 -0
- package/backend/src/flowent/providers/registry.py +144 -0
- package/backend/src/flowent/providers/sse.py +31 -0
- package/backend/src/flowent/providers/thinking.py +79 -0
- package/backend/src/flowent/registry.py +73 -0
- package/backend/src/flowent/role_management.py +255 -0
- package/backend/src/flowent/routes/__init__.py +30 -0
- 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__/stats.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/tabs.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/ws.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/access.py +48 -0
- package/backend/src/flowent/routes/assistant.py +155 -0
- package/backend/src/flowent/routes/image_assets.py +33 -0
- package/backend/src/flowent/routes/mcp.py +125 -0
- package/backend/src/flowent/routes/meta.py +28 -0
- package/backend/src/flowent/routes/nodes.py +365 -0
- package/backend/src/flowent/routes/prompts.py +46 -0
- package/backend/src/flowent/routes/providers_route.py +364 -0
- package/backend/src/flowent/routes/roles.py +207 -0
- package/backend/src/flowent/routes/settings.py +324 -0
- package/backend/src/flowent/routes/stats.py +229 -0
- package/backend/src/flowent/routes/tabs.py +292 -0
- package/backend/src/flowent/routes/ws.py +33 -0
- package/backend/src/flowent/runtime.py +188 -0
- package/backend/src/flowent/sandbox.py +45 -0
- package/backend/src/flowent/security.py +42 -0
- package/backend/src/flowent/settings.py +2467 -0
- package/backend/src/flowent/settings_management.py +286 -0
- package/backend/src/flowent/state_db.py +120 -0
- package/backend/src/flowent/static/assets/AssistantPage-B3Xc08AS.js +1 -0
- package/backend/src/flowent/static/assets/ChannelsPage-ByLd28xk.js +1 -0
- package/backend/src/flowent/static/assets/HomePage-C0hAx9_l.js +3 -0
- package/backend/src/flowent/static/assets/McpPage-DkrYLvBv.js +7 -0
- package/backend/src/flowent/static/assets/PageScaffold-D4jO9ooX.js +1 -0
- package/backend/src/flowent/static/assets/PromptsPage-DWA7rRJd.js +1 -0
- package/backend/src/flowent/static/assets/ProvidersPage-PUWT8seJ.js +3 -0
- package/backend/src/flowent/static/assets/RolesPage-CqcclGRw.js +1 -0
- package/backend/src/flowent/static/assets/SettingsPage-8tS2cJgX.js +3 -0
- package/backend/src/flowent/static/assets/StatsPage-BX9khYzu.js +1 -0
- package/backend/src/flowent/static/assets/ToolsPage-9Tl9FdeD.js +1 -0
- package/backend/src/flowent/static/assets/WorkspaceCommandDialog-CCXxjDL8.js +1 -0
- package/backend/src/flowent/static/assets/WorkspacePanels-aMdJ7ZH7.js +1 -0
- package/backend/src/flowent/static/assets/alert-dialog-kFYVQ7oX.js +1 -0
- package/backend/src/flowent/static/assets/badge-74-3jsCg.js +1 -0
- package/backend/src/flowent/static/assets/constants-XUzFf6i1.js +1 -0
- package/backend/src/flowent/static/assets/datetime-m6_O_Ci9.js +1 -0
- package/backend/src/flowent/static/assets/dialog-BeGSweF6.js +1 -0
- package/backend/src/flowent/static/assets/elk-worker.min-C9JGDOE-.js +6312 -0
- package/backend/src/flowent/static/assets/graph-vendor-CHpVij2M.css +1 -0
- package/backend/src/flowent/static/assets/graph-vendor-DRq_-6fV.js +7 -0
- package/backend/src/flowent/static/assets/index-BHC1Vhy8.css +1 -0
- package/backend/src/flowent/static/assets/index-CL1ALZ3r.js +10 -0
- package/backend/src/flowent/static/assets/layout.worker-jMHqAFbP.js +24 -0
- package/backend/src/flowent/static/assets/markdown-vendor-DVdy_w12.js +29 -0
- package/backend/src/flowent/static/assets/modelParams-CaHd0903.js +1 -0
- package/backend/src/flowent/static/assets/react-vendor-mEs_JJxa.js +9 -0
- package/backend/src/flowent/static/assets/roles-2OLDeTc5.js +1 -0
- package/backend/src/flowent/static/assets/rolldown-runtime-BYbx6iT9.js +1 -0
- package/backend/src/flowent/static/assets/select-DL_LPeDj.js +1 -0
- package/backend/src/flowent/static/assets/shared-CMxbpLeQ.js +1 -0
- package/backend/src/flowent/static/assets/triState-DEr3NkXV.js +1 -0
- package/backend/src/flowent/static/assets/ui-vendor-Dg9NNnWX.js +51 -0
- package/backend/src/flowent/static/index.html +36 -0
- package/backend/src/flowent/stats_service.py +218 -0
- package/backend/src/flowent/tools/__init__.py +201 -0
- 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 +156 -0
- package/backend/src/flowent/tools/contacts.py +22 -0
- package/backend/src/flowent/tools/create_agent.py +270 -0
- package/backend/src/flowent/tools/create_tab.py +59 -0
- package/backend/src/flowent/tools/delete_tab.py +39 -0
- package/backend/src/flowent/tools/edit.py +142 -0
- package/backend/src/flowent/tools/exec.py +117 -0
- package/backend/src/flowent/tools/fetch.py +85 -0
- package/backend/src/flowent/tools/idle.py +27 -0
- package/backend/src/flowent/tools/list_roles.py +50 -0
- package/backend/src/flowent/tools/list_tabs.py +96 -0
- package/backend/src/flowent/tools/list_tools.py +24 -0
- package/backend/src/flowent/tools/manage_prompts.py +102 -0
- package/backend/src/flowent/tools/manage_providers.py +220 -0
- package/backend/src/flowent/tools/manage_roles.py +275 -0
- package/backend/src/flowent/tools/manage_settings.py +346 -0
- package/backend/src/flowent/tools/mcp.py +199 -0
- package/backend/src/flowent/tools/read.py +152 -0
- package/backend/src/flowent/tools/send.py +50 -0
- package/backend/src/flowent/tools/set_permissions.py +84 -0
- package/backend/src/flowent/tools/sleep.py +41 -0
- package/backend/src/flowent/tools/todo.py +51 -0
- package/backend/src/flowent/workspace_store.py +479 -0
- package/backend/tests/__init__.py +0 -0
- package/backend/tests/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/conftest.py +6 -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/conftest.py +29 -0
- package/backend/tests/integration/api/test_access_api.py +182 -0
- package/backend/tests/integration/api/test_assistant_api.py +354 -0
- package/backend/tests/integration/api/test_frontend_mounting.py +61 -0
- package/backend/tests/integration/api/test_mcp_api.py +116 -0
- package/backend/tests/integration/api/test_meta_api.py +33 -0
- package/backend/tests/integration/api/test_nodes_api.py +486 -0
- package/backend/tests/integration/api/test_prompts_api.py +47 -0
- package/backend/tests/integration/api/test_roles_api.py +227 -0
- package/backend/tests/integration/api/test_tabs_api.py +501 -0
- 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 +746 -0
- package/backend/tests/unit/agent/test_agent_runtime.py +2726 -0
- package/backend/tests/unit/channels/__pycache__/test_telegram_channel.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/channels/test_telegram_channel.py +552 -0
- package/backend/tests/unit/logging/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/logging/test_logging.py +132 -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 +569 -0
- package/backend/tests/unit/providers/__pycache__/test_anthropic_provider.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_errors.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_extract_delta_parts.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_openai_provider.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_openai_responses.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_provider_gateway.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_think_tag_parser.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/test_anthropic_provider.py +185 -0
- package/backend/tests/unit/providers/test_errors.py +68 -0
- package/backend/tests/unit/providers/test_extract_delta_parts.py +22 -0
- package/backend/tests/unit/providers/test_openai_provider.py +139 -0
- package/backend/tests/unit/providers/test_openai_responses.py +402 -0
- package/backend/tests/unit/providers/test_provider_gateway.py +359 -0
- package/backend/tests/unit/providers/test_think_tag_parser.py +36 -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/__pycache__/test_stats_routes.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/routes/test_prompts_routes.py +104 -0
- package/backend/tests/unit/routes/test_providers_route.py +368 -0
- package/backend/tests/unit/routes/test_roles_routes.py +426 -0
- package/backend/tests/unit/routes/test_settings_routes.py +1138 -0
- package/backend/tests/unit/routes/test_stats_routes.py +149 -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 +1012 -0
- package/backend/tests/unit/sandbox/__pycache__/test_sandbox_tools.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/sandbox/test_sandbox_tools.py +78 -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 +110 -0
- 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 +711 -0
- package/backend/tests/unit/test_access.py +45 -0
- package/backend/tests/unit/test_cli.py +124 -0
- package/backend/tests/unit/test_graph_runtime.py +72 -0
- package/backend/tests/unit/test_network.py +51 -0
- package/backend/tests/unit/test_state_sqlite_storage.py +93 -0
- package/backend/tests/unit/test_workspace_store.py +231 -0
- 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 +229 -0
- package/backend/tests/unit/tools/test_create_agent_tool.py +524 -0
- package/backend/tests/unit/tools/test_delete_tab_tool.py +83 -0
- package/backend/tests/unit/tools/test_edit_tool.py +115 -0
- package/backend/tests/unit/tools/test_exec_tool.py +81 -0
- package/backend/tests/unit/tools/test_fetch_tool.py +65 -0
- package/backend/tests/unit/tools/test_manage_prompts_tool.py +117 -0
- package/backend/tests/unit/tools/test_manage_providers_tool.py +458 -0
- package/backend/tests/unit/tools/test_manage_roles_tool.py +411 -0
- package/backend/tests/unit/tools/test_manage_settings_tool.py +608 -0
- package/backend/tests/unit/tools/test_read_tool.py +33 -0
- package/backend/tests/unit/tools/test_set_permissions_tool.py +391 -0
- package/backend/tests/unit/tools/test_todo_tool.py +37 -0
- package/backend/tests/unit/tools/test_tool_registry.py +91 -0
- package/backend/uv.lock +1144 -0
- package/bin/flowent.mjs +62 -36
- package/dist/frontend/assets/AssistantPage-B3Xc08AS.js +1 -0
- package/dist/frontend/assets/ChannelsPage-ByLd28xk.js +1 -0
- package/dist/frontend/assets/HomePage-C0hAx9_l.js +3 -0
- package/dist/frontend/assets/McpPage-DkrYLvBv.js +7 -0
- package/dist/frontend/assets/PageScaffold-D4jO9ooX.js +1 -0
- package/dist/frontend/assets/PromptsPage-DWA7rRJd.js +1 -0
- package/dist/frontend/assets/ProvidersPage-PUWT8seJ.js +3 -0
- package/dist/frontend/assets/RolesPage-CqcclGRw.js +1 -0
- package/dist/frontend/assets/SettingsPage-8tS2cJgX.js +3 -0
- package/dist/frontend/assets/StatsPage-BX9khYzu.js +1 -0
- package/dist/frontend/assets/ToolsPage-9Tl9FdeD.js +1 -0
- package/dist/frontend/assets/WorkspaceCommandDialog-CCXxjDL8.js +1 -0
- package/dist/frontend/assets/WorkspacePanels-aMdJ7ZH7.js +1 -0
- package/dist/frontend/assets/alert-dialog-kFYVQ7oX.js +1 -0
- package/dist/frontend/assets/badge-74-3jsCg.js +1 -0
- package/dist/frontend/assets/constants-XUzFf6i1.js +1 -0
- package/dist/frontend/assets/datetime-m6_O_Ci9.js +1 -0
- package/dist/frontend/assets/dialog-BeGSweF6.js +1 -0
- package/dist/frontend/assets/elk-worker.min-C9JGDOE-.js +6312 -0
- package/dist/frontend/assets/graph-vendor-CHpVij2M.css +1 -0
- package/dist/frontend/assets/graph-vendor-DRq_-6fV.js +7 -0
- package/dist/frontend/assets/index-BHC1Vhy8.css +1 -0
- package/dist/frontend/assets/index-CL1ALZ3r.js +10 -0
- package/dist/frontend/assets/layout.worker-jMHqAFbP.js +24 -0
- package/dist/frontend/assets/markdown-vendor-DVdy_w12.js +29 -0
- package/dist/frontend/assets/modelParams-CaHd0903.js +1 -0
- package/dist/frontend/assets/react-vendor-mEs_JJxa.js +9 -0
- package/dist/frontend/assets/roles-2OLDeTc5.js +1 -0
- package/dist/frontend/assets/rolldown-runtime-BYbx6iT9.js +1 -0
- package/dist/frontend/assets/select-DL_LPeDj.js +1 -0
- package/dist/frontend/assets/shared-CMxbpLeQ.js +1 -0
- package/dist/frontend/assets/triState-DEr3NkXV.js +1 -0
- package/dist/frontend/assets/ui-vendor-Dg9NNnWX.js +51 -0
- package/dist/frontend/index.html +36 -0
- package/package.json +27 -41
- package/dist/.next/BUILD_ID +0 -1
- package/dist/.next/app-path-routes-manifest.json +0 -6
- package/dist/.next/build-manifest.json +0 -20
- package/dist/.next/package.json +0 -1
- package/dist/.next/prerender-manifest.json +0 -114
- package/dist/.next/required-server-files.json +0 -333
- package/dist/.next/routes-manifest.json +0 -69
- package/dist/.next/server/app/_global-error/page/app-paths-manifest.json +0 -3
- package/dist/.next/server/app/_global-error/page/build-manifest.json +0 -16
- package/dist/.next/server/app/_global-error/page/next-font-manifest.json +0 -6
- package/dist/.next/server/app/_global-error/page/react-loadable-manifest.json +0 -1
- package/dist/.next/server/app/_global-error/page/server-reference-manifest.json +0 -4
- package/dist/.next/server/app/_global-error/page.js +0 -9
- package/dist/.next/server/app/_global-error/page.js.map +0 -5
- package/dist/.next/server/app/_global-error/page.js.nft.json +0 -1
- package/dist/.next/server/app/_global-error/page_client-reference-manifest.js +0 -3
- package/dist/.next/server/app/_global-error.html +0 -1
- package/dist/.next/server/app/_global-error.meta +0 -15
- package/dist/.next/server/app/_global-error.rsc +0 -14
- package/dist/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +0 -5
- package/dist/.next/server/app/_global-error.segments/_full.segment.rsc +0 -14
- package/dist/.next/server/app/_global-error.segments/_head.segment.rsc +0 -5
- package/dist/.next/server/app/_global-error.segments/_index.segment.rsc +0 -5
- package/dist/.next/server/app/_global-error.segments/_tree.segment.rsc +0 -1
- package/dist/.next/server/app/_not-found/page/app-paths-manifest.json +0 -3
- package/dist/.next/server/app/_not-found/page/build-manifest.json +0 -16
- package/dist/.next/server/app/_not-found/page/next-font-manifest.json +0 -10
- package/dist/.next/server/app/_not-found/page/react-loadable-manifest.json +0 -1
- package/dist/.next/server/app/_not-found/page/server-reference-manifest.json +0 -4
- package/dist/.next/server/app/_not-found/page.js +0 -13
- package/dist/.next/server/app/_not-found/page.js.map +0 -5
- package/dist/.next/server/app/_not-found/page.js.nft.json +0 -1
- package/dist/.next/server/app/_not-found/page_client-reference-manifest.js +0 -3
- package/dist/.next/server/app/_not-found.html +0 -1
- package/dist/.next/server/app/_not-found.meta +0 -16
- package/dist/.next/server/app/_not-found.rsc +0 -16
- package/dist/.next/server/app/_not-found.segments/_full.segment.rsc +0 -16
- package/dist/.next/server/app/_not-found.segments/_head.segment.rsc +0 -6
- package/dist/.next/server/app/_not-found.segments/_index.segment.rsc +0 -5
- package/dist/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +0 -5
- package/dist/.next/server/app/_not-found.segments/_not-found.segment.rsc +0 -5
- package/dist/.next/server/app/_not-found.segments/_tree.segment.rsc +0 -2
- package/dist/.next/server/app/icon.svg/route/app-paths-manifest.json +0 -3
- package/dist/.next/server/app/icon.svg/route/build-manifest.json +0 -9
- package/dist/.next/server/app/icon.svg/route.js +0 -6
- package/dist/.next/server/app/icon.svg/route.js.map +0 -5
- package/dist/.next/server/app/icon.svg/route.js.nft.json +0 -1
- package/dist/.next/server/app/icon.svg.meta +0 -1
- package/dist/.next/server/app/index.html +0 -1
- package/dist/.next/server/app/index.meta +0 -14
- package/dist/.next/server/app/index.rsc +0 -15
- package/dist/.next/server/app/index.segments/__PAGE__.segment.rsc +0 -5
- package/dist/.next/server/app/index.segments/_full.segment.rsc +0 -15
- package/dist/.next/server/app/index.segments/_head.segment.rsc +0 -6
- package/dist/.next/server/app/index.segments/_index.segment.rsc +0 -5
- package/dist/.next/server/app/index.segments/_tree.segment.rsc +0 -3
- package/dist/.next/server/app/page/app-paths-manifest.json +0 -3
- package/dist/.next/server/app/page/build-manifest.json +0 -16
- package/dist/.next/server/app/page/next-font-manifest.json +0 -10
- package/dist/.next/server/app/page/react-loadable-manifest.json +0 -1
- package/dist/.next/server/app/page/server-reference-manifest.json +0 -4
- package/dist/.next/server/app/page.js +0 -14
- package/dist/.next/server/app/page.js.map +0 -5
- package/dist/.next/server/app/page.js.nft.json +0 -1
- package/dist/.next/server/app/page_client-reference-manifest.js +0 -3
- package/dist/.next/server/app-paths-manifest.json +0 -6
- package/dist/.next/server/chunks/[externals]_next_dist_0arv.vj._.js +0 -3
- package/dist/.next/server/chunks/[root-of-the-server]__0vcj1q1._.js +0 -13
- package/dist/.next/server/chunks/[turbopack]_runtime.js +0 -903
- package/dist/.next/server/chunks/_next-internal_server_app_icon_svg_route_actions_0-0ehc~.js +0 -3
- package/dist/.next/server/chunks/ssr/05w9_next_dist_0ihu0u9._.js +0 -6
- package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_12u3mib._.js +0 -3
- package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_builtin_forbidden_04fbe_..js +0 -3
- package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_builtin_global-error_0brpl_..js +0 -3
- package/dist/.next/server/chunks/ssr/05w9_next_dist_client_components_builtin_unauthorized_0~2g66g.js +0 -3
- package/dist/.next/server/chunks/ssr/05w9_next_dist_esm_build_templates_app-page_0~cyr1_.js +0 -4
- package/dist/.next/server/chunks/ssr/05w9_next_dist_esm_build_templates_app-page_1105emf.js +0 -4
- package/dist/.next/server/chunks/ssr/05w9_next_dist_esm_build_templates_app-page_11uhyqv.js +0 -4
- package/dist/.next/server/chunks/ssr/[root-of-the-server]__0.t9_75._.js +0 -33
- package/dist/.next/server/chunks/ssr/[root-of-the-server]__0c0ud_z._.js +0 -3
- package/dist/.next/server/chunks/ssr/[root-of-the-server]__0f9_8d4._.js +0 -3
- package/dist/.next/server/chunks/ssr/[root-of-the-server]__0l5ko41._.js +0 -19
- package/dist/.next/server/chunks/ssr/[root-of-the-server]__0mn6z7i._.js +0 -3
- package/dist/.next/server/chunks/ssr/[root-of-the-server]__0npxxst._.js +0 -33
- package/dist/.next/server/chunks/ssr/[root-of-the-server]__0qjhaca._.js +0 -3
- package/dist/.next/server/chunks/ssr/[root-of-the-server]__0rwgw3s._.js +0 -3
- package/dist/.next/server/chunks/ssr/[turbopack]_runtime.js +0 -903
- package/dist/.next/server/chunks/ssr/_next-internal_server_app__global-error_page_actions_0k77kol.js +0 -3
- package/dist/.next/server/chunks/ssr/_next-internal_server_app__not-found_page_actions_0eq97pa.js +0 -3
- package/dist/.next/server/chunks/ssr/_next-internal_server_app_page_actions_09-gtaw.js +0 -3
- package/dist/.next/server/chunks/ssr/node_modules__pnpm_056~6.6._.js +0 -3
- package/dist/.next/server/chunks/ssr/node_modules__pnpm_0~j0k.e._.js +0 -33
- package/dist/.next/server/functions-config-manifest.json +0 -4
- package/dist/.next/server/middleware-build-manifest.js +0 -20
- package/dist/.next/server/middleware-manifest.json +0 -6
- package/dist/.next/server/next-font-manifest.js +0 -1
- package/dist/.next/server/next-font-manifest.json +0 -13
- package/dist/.next/server/pages/404.html +0 -1
- package/dist/.next/server/pages/500.html +0 -1
- package/dist/.next/server/pages-manifest.json +0 -4
- package/dist/.next/server/prefetch-hints.json +0 -1
- package/dist/.next/server/server-reference-manifest.js +0 -1
- package/dist/.next/server/server-reference-manifest.json +0 -5
- package/dist/.next/static/SMWpxFVvkpYFxY7uuFvGB/_buildManifest.js +0 -11
- package/dist/.next/static/SMWpxFVvkpYFxY7uuFvGB/_clientMiddlewareManifest.js +0 -1
- package/dist/.next/static/SMWpxFVvkpYFxY7uuFvGB/_ssgManifest.js +0 -1
- package/dist/.next/static/chunks/01qk2~bgf76vu.js +0 -1
- package/dist/.next/static/chunks/03~yq9q893hmn.js +0 -1
- package/dist/.next/static/chunks/080queev.r2uy.js +0 -31
- package/dist/.next/static/chunks/0v3lyuj75aq50.js +0 -1
- package/dist/.next/static/chunks/10b~xdx5c-i7s.js +0 -5
- package/dist/.next/static/chunks/14gla2ascffgv.css +0 -2
- package/dist/.next/static/chunks/turbopack-0m-970~qvs7sc.js +0 -1
- package/dist/.next/static/media/7178b3e590c64307-s.11.cyxs5p-0z~.woff2 +0 -0
- package/dist/.next/static/media/8a480f0b521d4e75-s.06d3mdzz5bre_.woff2 +0 -0
- package/dist/.next/static/media/caa3a2e1cccd8315-s.p.16t1db8_9y2o~.woff2 +0 -0
- package/dist/package.json +0 -88
- package/dist/server.js +0 -38
- /package/{dist/.next/server/app/icon.svg.body → backend/src/flowent/static/favicon.svg} +0 -0
- /package/dist/{.next/static/media/icon.0.r~afrtrocz9.svg → frontend/favicon.svg} +0 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, HTTPException
|
|
4
|
+
from pydantic import BaseModel, ConfigDict
|
|
5
|
+
|
|
6
|
+
from flowent.graph_service import (
|
|
7
|
+
create_agent_node,
|
|
8
|
+
create_edge,
|
|
9
|
+
create_graph_node,
|
|
10
|
+
create_tab,
|
|
11
|
+
delete_agent_node,
|
|
12
|
+
delete_edge,
|
|
13
|
+
delete_tab,
|
|
14
|
+
duplicate_tab,
|
|
15
|
+
list_node_connection_ids,
|
|
16
|
+
list_tab_edges,
|
|
17
|
+
list_workflow_nodes,
|
|
18
|
+
serialize_tab_summary,
|
|
19
|
+
update_tab_definition,
|
|
20
|
+
)
|
|
21
|
+
from flowent.models import AgentState, EdgeKind, WorkflowNodeKind
|
|
22
|
+
from flowent.registry import registry
|
|
23
|
+
from flowent.workspace_store import workspace_store
|
|
24
|
+
|
|
25
|
+
router = APIRouter()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CreateTabRequest(BaseModel):
|
|
29
|
+
model_config = ConfigDict(extra="forbid")
|
|
30
|
+
title: str
|
|
31
|
+
allow_network: bool = False
|
|
32
|
+
write_dirs: list[str] = []
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CreateTabNodeRequest(BaseModel):
|
|
36
|
+
model_config = ConfigDict(extra="forbid")
|
|
37
|
+
node_type: str = WorkflowNodeKind.AGENT.value
|
|
38
|
+
role_name: str | None = None
|
|
39
|
+
name: str | None = None
|
|
40
|
+
config: dict[str, object] = {}
|
|
41
|
+
tools: list[str] = []
|
|
42
|
+
write_dirs: list[str] = []
|
|
43
|
+
allow_network: bool = False
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class CreateTabEdgeRequest(BaseModel):
|
|
47
|
+
model_config = ConfigDict(extra="forbid")
|
|
48
|
+
from_node_id: str
|
|
49
|
+
from_port_key: str = "out"
|
|
50
|
+
to_node_id: str
|
|
51
|
+
to_port_key: str = "in"
|
|
52
|
+
kind: str = EdgeKind.CONTROL.value
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class UpdateTabDefinitionRequest(BaseModel):
|
|
56
|
+
model_config = ConfigDict(extra="forbid")
|
|
57
|
+
definition: dict[str, object]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _serialize_workflow_node(
|
|
61
|
+
*,
|
|
62
|
+
tab_id: str,
|
|
63
|
+
node_id: str,
|
|
64
|
+
) -> dict[str, object]:
|
|
65
|
+
tab = workspace_store.get_tab(tab_id)
|
|
66
|
+
if tab is None:
|
|
67
|
+
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
68
|
+
definition = tab.definition.get_node(node_id)
|
|
69
|
+
if definition is None:
|
|
70
|
+
raise HTTPException(status_code=404, detail="Node not found")
|
|
71
|
+
record = workspace_store.get_node_record(node_id)
|
|
72
|
+
live_node = registry.get(node_id)
|
|
73
|
+
config = dict(definition.config)
|
|
74
|
+
role_name = config.get("role_name")
|
|
75
|
+
name = config.get("name")
|
|
76
|
+
position = tab.definition.view.positions.get(node_id)
|
|
77
|
+
todos = (
|
|
78
|
+
[todo.serialize() for todo in live_node.get_todos_snapshot()]
|
|
79
|
+
if live_node is not None
|
|
80
|
+
else [todo.serialize() for todo in (record.todos if record is not None else [])]
|
|
81
|
+
)
|
|
82
|
+
state = (
|
|
83
|
+
live_node.state.value
|
|
84
|
+
if live_node is not None
|
|
85
|
+
else (record.state.value if record is not None else AgentState.IDLE.value)
|
|
86
|
+
)
|
|
87
|
+
return {
|
|
88
|
+
"id": definition.id,
|
|
89
|
+
"node_type": definition.type.value,
|
|
90
|
+
"workflow_id": tab_id,
|
|
91
|
+
"role_name": role_name if isinstance(role_name, str) else None,
|
|
92
|
+
"is_leader": False,
|
|
93
|
+
"state": state,
|
|
94
|
+
"connections": list_node_connection_ids(tab_id=tab_id, node_id=definition.id)
|
|
95
|
+
if definition.type == WorkflowNodeKind.AGENT
|
|
96
|
+
else [],
|
|
97
|
+
"name": name if isinstance(name, str) else None,
|
|
98
|
+
"todos": todos if definition.type == WorkflowNodeKind.AGENT else [],
|
|
99
|
+
"position": position.serialize() if position is not None else None,
|
|
100
|
+
"config": config,
|
|
101
|
+
"inputs": [port.serialize() for port in definition.inputs],
|
|
102
|
+
"outputs": [port.serialize() for port in definition.outputs],
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@router.get("/api/workflows")
|
|
107
|
+
async def list_workflows() -> dict[str, object]:
|
|
108
|
+
tabs = workspace_store.list_tabs()
|
|
109
|
+
return {"workflows": [serialize_tab_summary(tab) for tab in tabs]}
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@router.post("/api/workflows")
|
|
113
|
+
async def create_workflow_route(req: CreateTabRequest) -> dict[str, object]:
|
|
114
|
+
title = req.title.strip()
|
|
115
|
+
if not title:
|
|
116
|
+
raise HTTPException(status_code=400, detail="title must not be empty")
|
|
117
|
+
try:
|
|
118
|
+
tab = create_tab(
|
|
119
|
+
title=title,
|
|
120
|
+
allow_network=req.allow_network,
|
|
121
|
+
write_dirs=req.write_dirs,
|
|
122
|
+
)
|
|
123
|
+
except ValueError as exc:
|
|
124
|
+
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
|
125
|
+
return serialize_tab_summary(tab)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
@router.post("/api/workflows/{tab_id}/duplicate")
|
|
129
|
+
async def duplicate_workflow_route(tab_id: str) -> dict[str, object]:
|
|
130
|
+
duplicated, error = duplicate_tab(tab_id=tab_id)
|
|
131
|
+
if error is not None or duplicated is None:
|
|
132
|
+
raise HTTPException(
|
|
133
|
+
status_code=404 if error and error.endswith("not found") else 400,
|
|
134
|
+
detail=error or "Failed to duplicate workflow",
|
|
135
|
+
)
|
|
136
|
+
return serialize_tab_summary(duplicated)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@router.get("/api/workflows/{tab_id}")
|
|
140
|
+
async def get_workflow(tab_id: str) -> dict[str, object]:
|
|
141
|
+
tab = workspace_store.get_tab(tab_id)
|
|
142
|
+
if tab is None:
|
|
143
|
+
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
144
|
+
nodes = [
|
|
145
|
+
_serialize_workflow_node(tab_id=tab_id, node_id=node.id)
|
|
146
|
+
for node in list_workflow_nodes(tab_id)
|
|
147
|
+
]
|
|
148
|
+
edges = [edge.serialize() for edge in list_tab_edges(tab_id)]
|
|
149
|
+
return {
|
|
150
|
+
"workflow": serialize_tab_summary(tab),
|
|
151
|
+
"nodes": nodes,
|
|
152
|
+
"edges": edges,
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@router.put("/api/workflows/{tab_id}/definition")
|
|
157
|
+
async def update_workflow_definition_route(
|
|
158
|
+
tab_id: str,
|
|
159
|
+
req: UpdateTabDefinitionRequest,
|
|
160
|
+
) -> dict[str, object]:
|
|
161
|
+
updated, error = update_tab_definition(
|
|
162
|
+
tab_id=tab_id,
|
|
163
|
+
definition_payload=req.definition,
|
|
164
|
+
actor_id=tab_id,
|
|
165
|
+
)
|
|
166
|
+
if error is not None or updated is None:
|
|
167
|
+
raise HTTPException(
|
|
168
|
+
status_code=400 if error and not error.endswith("not found") else 404,
|
|
169
|
+
detail=error or "Failed to update workflow definition",
|
|
170
|
+
)
|
|
171
|
+
return serialize_tab_summary(updated)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@router.delete("/api/workflows/{tab_id}")
|
|
175
|
+
async def delete_workflow_route(tab_id: str) -> dict[str, object]:
|
|
176
|
+
deleted, error = delete_tab(tab_id=tab_id)
|
|
177
|
+
if error is not None or deleted is None:
|
|
178
|
+
status_code = 404 if error and error.endswith("not found") else 400
|
|
179
|
+
raise HTTPException(
|
|
180
|
+
status_code=status_code,
|
|
181
|
+
detail=error or "Failed to delete workflow",
|
|
182
|
+
)
|
|
183
|
+
return deleted
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@router.post("/api/workflows/{tab_id}/nodes")
|
|
187
|
+
async def create_workflow_node(
|
|
188
|
+
tab_id: str,
|
|
189
|
+
req: CreateTabNodeRequest,
|
|
190
|
+
) -> dict[str, object]:
|
|
191
|
+
try:
|
|
192
|
+
node_type = WorkflowNodeKind(req.node_type.strip())
|
|
193
|
+
except ValueError as exc:
|
|
194
|
+
raise HTTPException(status_code=400, detail="Invalid node_type") from exc
|
|
195
|
+
|
|
196
|
+
if node_type == WorkflowNodeKind.AGENT:
|
|
197
|
+
if not isinstance(req.role_name, str) or not req.role_name.strip():
|
|
198
|
+
raise HTTPException(status_code=400, detail="role_name is required")
|
|
199
|
+
record, error = create_agent_node(
|
|
200
|
+
role_name=req.role_name,
|
|
201
|
+
tab_id=tab_id,
|
|
202
|
+
name=req.name,
|
|
203
|
+
tools=req.tools,
|
|
204
|
+
write_dirs=req.write_dirs,
|
|
205
|
+
allow_network=req.allow_network,
|
|
206
|
+
)
|
|
207
|
+
if error is not None or record is None:
|
|
208
|
+
raise HTTPException(
|
|
209
|
+
status_code=400, detail=error or "Failed to create node"
|
|
210
|
+
)
|
|
211
|
+
return _serialize_workflow_node(tab_id=tab_id, node_id=record.id)
|
|
212
|
+
|
|
213
|
+
node, error = create_graph_node(
|
|
214
|
+
tab_id=tab_id,
|
|
215
|
+
node_type=node_type,
|
|
216
|
+
config={
|
|
217
|
+
**req.config,
|
|
218
|
+
**(
|
|
219
|
+
{"name": req.name}
|
|
220
|
+
if isinstance(req.name, str) and req.name.strip()
|
|
221
|
+
else {}
|
|
222
|
+
),
|
|
223
|
+
},
|
|
224
|
+
actor_id=tab_id,
|
|
225
|
+
)
|
|
226
|
+
if error is not None or node is None:
|
|
227
|
+
raise HTTPException(status_code=400, detail=error or "Failed to create node")
|
|
228
|
+
return _serialize_workflow_node(tab_id=tab_id, node_id=node.id)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
@router.post("/api/workflows/{tab_id}/edges")
|
|
232
|
+
async def create_workflow_edge(
|
|
233
|
+
tab_id: str,
|
|
234
|
+
req: CreateTabEdgeRequest,
|
|
235
|
+
) -> dict[str, object]:
|
|
236
|
+
tab = workspace_store.get_tab(tab_id)
|
|
237
|
+
if tab is None:
|
|
238
|
+
raise HTTPException(status_code=404, detail="Workflow not found")
|
|
239
|
+
try:
|
|
240
|
+
edge_kind = EdgeKind(req.kind.strip())
|
|
241
|
+
except ValueError as exc:
|
|
242
|
+
raise HTTPException(status_code=400, detail="Invalid edge kind") from exc
|
|
243
|
+
edge, error = create_edge(
|
|
244
|
+
tab_id=tab_id,
|
|
245
|
+
from_node_id=req.from_node_id,
|
|
246
|
+
from_port_key=req.from_port_key,
|
|
247
|
+
to_node_id=req.to_node_id,
|
|
248
|
+
to_port_key=req.to_port_key,
|
|
249
|
+
kind=edge_kind,
|
|
250
|
+
)
|
|
251
|
+
if error is not None or edge is None:
|
|
252
|
+
raise HTTPException(status_code=400, detail=error or "Failed to create edge")
|
|
253
|
+
return edge.serialize()
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@router.delete("/api/workflows/{tab_id}/nodes/{node_id}")
|
|
257
|
+
async def delete_workflow_node(tab_id: str, node_id: str) -> dict[str, object]:
|
|
258
|
+
deleted, error = delete_agent_node(
|
|
259
|
+
tab_id=tab_id,
|
|
260
|
+
node_id=node_id,
|
|
261
|
+
)
|
|
262
|
+
if error is not None or deleted is None:
|
|
263
|
+
status_code = 404 if error and error.endswith("not found") else 400
|
|
264
|
+
raise HTTPException(
|
|
265
|
+
status_code=status_code, detail=error or "Failed to delete node"
|
|
266
|
+
)
|
|
267
|
+
return deleted
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
@router.delete("/api/workflows/{tab_id}/edges")
|
|
271
|
+
async def delete_workflow_edge(
|
|
272
|
+
tab_id: str,
|
|
273
|
+
edge_id: str | None = None,
|
|
274
|
+
from_node_id: str | None = None,
|
|
275
|
+
to_node_id: str | None = None,
|
|
276
|
+
from_port_key: str | None = None,
|
|
277
|
+
to_port_key: str | None = None,
|
|
278
|
+
) -> dict[str, object]:
|
|
279
|
+
deleted, error = delete_edge(
|
|
280
|
+
tab_id=tab_id,
|
|
281
|
+
edge_id=edge_id,
|
|
282
|
+
from_node_id=from_node_id,
|
|
283
|
+
to_node_id=to_node_id,
|
|
284
|
+
from_port_key=from_port_key,
|
|
285
|
+
to_port_key=to_port_key,
|
|
286
|
+
)
|
|
287
|
+
if error is not None or deleted is None:
|
|
288
|
+
status_code = 404 if error and error.endswith("not found") else 400
|
|
289
|
+
raise HTTPException(
|
|
290
|
+
status_code=status_code, detail=error or "Failed to delete edge"
|
|
291
|
+
)
|
|
292
|
+
return deleted
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter
|
|
4
|
+
from starlette.websockets import WebSocket, WebSocketDisconnect
|
|
5
|
+
|
|
6
|
+
from flowent.access import authorize_websocket
|
|
7
|
+
from flowent.events import event_bus
|
|
8
|
+
|
|
9
|
+
router = APIRouter()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@router.websocket("/ws/events")
|
|
13
|
+
async def ws_events(ws: WebSocket) -> None:
|
|
14
|
+
if not await authorize_websocket(ws):
|
|
15
|
+
return
|
|
16
|
+
await event_bus.connect_display(ws)
|
|
17
|
+
try:
|
|
18
|
+
while True:
|
|
19
|
+
await ws.receive_text()
|
|
20
|
+
except WebSocketDisconnect:
|
|
21
|
+
event_bus.disconnect_display(ws)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@router.websocket("/ws/updates")
|
|
25
|
+
async def ws_updates(ws: WebSocket) -> None:
|
|
26
|
+
if not await authorize_websocket(ws):
|
|
27
|
+
return
|
|
28
|
+
await event_bus.connect_updates(ws)
|
|
29
|
+
try:
|
|
30
|
+
while True:
|
|
31
|
+
await ws.receive_text()
|
|
32
|
+
except WebSocketDisconnect:
|
|
33
|
+
event_bus.disconnect_updates(ws)
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import threading
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from loguru import logger
|
|
7
|
+
|
|
8
|
+
from flowent.registry import registry
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from flowent.channels.telegram import TelegramChannel
|
|
12
|
+
|
|
13
|
+
SYSTEM_NODE_TIMEOUT = 5.0
|
|
14
|
+
_telegram_channel: TelegramChannel | None = None
|
|
15
|
+
_telegram_channel_lock = threading.Lock()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _stop_telegram_channel() -> None:
|
|
19
|
+
global _telegram_channel
|
|
20
|
+
with _telegram_channel_lock:
|
|
21
|
+
channel = _telegram_channel
|
|
22
|
+
_telegram_channel = None
|
|
23
|
+
|
|
24
|
+
if channel is not None:
|
|
25
|
+
channel.stop()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def restart_telegram_channel() -> None:
|
|
29
|
+
from flowent.channels.telegram import TelegramChannel
|
|
30
|
+
from flowent.settings import get_settings
|
|
31
|
+
|
|
32
|
+
_stop_telegram_channel()
|
|
33
|
+
|
|
34
|
+
settings = get_settings()
|
|
35
|
+
if not settings.telegram.bot_token.strip():
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
channel = TelegramChannel()
|
|
39
|
+
channel.start()
|
|
40
|
+
with _telegram_channel_lock:
|
|
41
|
+
global _telegram_channel
|
|
42
|
+
_telegram_channel = channel
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def bootstrap_runtime() -> None:
|
|
46
|
+
from flowent.access import ensure_access_bootstrap
|
|
47
|
+
from flowent.agent import Agent
|
|
48
|
+
from flowent.graph_service import (
|
|
49
|
+
build_assistant_tools,
|
|
50
|
+
ensure_tab_leaders,
|
|
51
|
+
)
|
|
52
|
+
from flowent.mcp_service import mcp_service
|
|
53
|
+
from flowent.models import AgentState, NodeConfig, NodeType, StateEntry
|
|
54
|
+
from flowent.settings import (
|
|
55
|
+
ensure_builtin_roles,
|
|
56
|
+
get_settings,
|
|
57
|
+
save_settings,
|
|
58
|
+
)
|
|
59
|
+
from flowent.workspace_store import workspace_store
|
|
60
|
+
|
|
61
|
+
workspace_store.reset_cache()
|
|
62
|
+
mcp_service.clear_runtime_state()
|
|
63
|
+
settings = get_settings()
|
|
64
|
+
settings_changed = ensure_builtin_roles(settings)
|
|
65
|
+
generated_access_code = ensure_access_bootstrap(settings)
|
|
66
|
+
if settings_changed or generated_access_code is not None:
|
|
67
|
+
save_settings(settings)
|
|
68
|
+
mcp_service.bootstrap()
|
|
69
|
+
assistant_tools = build_assistant_tools(settings=settings)
|
|
70
|
+
|
|
71
|
+
assistant_record = next(
|
|
72
|
+
(
|
|
73
|
+
record
|
|
74
|
+
for record in workspace_store.list_node_records()
|
|
75
|
+
if record.config.node_type == NodeType.ASSISTANT
|
|
76
|
+
and record.state != AgentState.TERMINATED
|
|
77
|
+
),
|
|
78
|
+
None,
|
|
79
|
+
)
|
|
80
|
+
assistant = Agent(
|
|
81
|
+
NodeConfig(
|
|
82
|
+
node_type=NodeType.ASSISTANT,
|
|
83
|
+
role_name=settings.assistant.role_name,
|
|
84
|
+
name="Assistant",
|
|
85
|
+
tools=assistant_tools,
|
|
86
|
+
write_dirs=list(settings.assistant.write_dirs),
|
|
87
|
+
allow_network=settings.assistant.allow_network,
|
|
88
|
+
),
|
|
89
|
+
uuid=assistant_record.id if assistant_record is not None else None,
|
|
90
|
+
)
|
|
91
|
+
if assistant_record is not None:
|
|
92
|
+
assistant.history = list(assistant_record.history)
|
|
93
|
+
assistant._set_execution_context(
|
|
94
|
+
summary=assistant_record.execution_context_summary,
|
|
95
|
+
history_cutoff=assistant_record.execution_context_history_cutoff,
|
|
96
|
+
)
|
|
97
|
+
if not any(isinstance(entry, StateEntry) for entry in assistant.history):
|
|
98
|
+
assistant.history.insert(
|
|
99
|
+
0,
|
|
100
|
+
StateEntry(state=assistant_record.state.value, reason="restored"),
|
|
101
|
+
)
|
|
102
|
+
if assistant_record.state in {
|
|
103
|
+
AgentState.INITIALIZING,
|
|
104
|
+
AgentState.IDLE,
|
|
105
|
+
AgentState.RUNNING,
|
|
106
|
+
AgentState.SLEEPING,
|
|
107
|
+
}:
|
|
108
|
+
assistant.history.append(
|
|
109
|
+
StateEntry(state=AgentState.IDLE.value, reason="restored")
|
|
110
|
+
)
|
|
111
|
+
assistant.todos = list(assistant_record.todos)
|
|
112
|
+
assistant.prime_runtime_state(
|
|
113
|
+
AgentState.ERROR
|
|
114
|
+
if assistant_record.state == AgentState.ERROR
|
|
115
|
+
else AgentState.IDLE
|
|
116
|
+
)
|
|
117
|
+
registry.register(assistant)
|
|
118
|
+
assistant.start()
|
|
119
|
+
logger.info("Assistant started with role {}", settings.assistant.role_name)
|
|
120
|
+
|
|
121
|
+
ensure_tab_leaders()
|
|
122
|
+
|
|
123
|
+
restored_node_ids: set[str] = set()
|
|
124
|
+
for record in workspace_store.list_node_records():
|
|
125
|
+
if record.config.node_type == NodeType.ASSISTANT:
|
|
126
|
+
continue
|
|
127
|
+
if record.state == AgentState.TERMINATED:
|
|
128
|
+
continue
|
|
129
|
+
node = Agent(
|
|
130
|
+
NodeConfig(
|
|
131
|
+
node_type=record.config.node_type,
|
|
132
|
+
role_name=record.config.role_name,
|
|
133
|
+
tab_id=record.config.tab_id,
|
|
134
|
+
name=record.config.name,
|
|
135
|
+
tools=list(record.config.tools),
|
|
136
|
+
write_dirs=list(record.config.write_dirs),
|
|
137
|
+
allow_network=record.config.allow_network,
|
|
138
|
+
),
|
|
139
|
+
uuid=record.id,
|
|
140
|
+
)
|
|
141
|
+
node.history = list(record.history)
|
|
142
|
+
node._set_execution_context(
|
|
143
|
+
summary=record.execution_context_summary,
|
|
144
|
+
history_cutoff=record.execution_context_history_cutoff,
|
|
145
|
+
)
|
|
146
|
+
if not any(isinstance(entry, StateEntry) for entry in node.history):
|
|
147
|
+
node.history.insert(
|
|
148
|
+
0,
|
|
149
|
+
StateEntry(state=record.state.value, reason="restored"),
|
|
150
|
+
)
|
|
151
|
+
if record.state in {
|
|
152
|
+
AgentState.INITIALIZING,
|
|
153
|
+
AgentState.RUNNING,
|
|
154
|
+
AgentState.SLEEPING,
|
|
155
|
+
}:
|
|
156
|
+
node.history.append(
|
|
157
|
+
StateEntry(state=AgentState.IDLE.value, reason="restored")
|
|
158
|
+
)
|
|
159
|
+
node.todos = list(record.todos)
|
|
160
|
+
node.prime_runtime_state(
|
|
161
|
+
AgentState.ERROR if record.state == AgentState.ERROR else AgentState.IDLE
|
|
162
|
+
)
|
|
163
|
+
registry.register(node)
|
|
164
|
+
node.start()
|
|
165
|
+
restored_node_ids.add(node.uuid)
|
|
166
|
+
|
|
167
|
+
if settings.telegram.bot_token.strip():
|
|
168
|
+
restart_telegram_channel()
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def shutdown_runtime(timeout: float = SYSTEM_NODE_TIMEOUT) -> None:
|
|
172
|
+
from flowent.mcp_service import mcp_service
|
|
173
|
+
from flowent.models import NodeType
|
|
174
|
+
|
|
175
|
+
logger.info("Shutting down runtime")
|
|
176
|
+
_stop_telegram_channel()
|
|
177
|
+
mcp_service.clear_runtime_state()
|
|
178
|
+
persistent_agents = []
|
|
179
|
+
for agent in registry.get_all():
|
|
180
|
+
if agent.node_type == NodeType.ASSISTANT or agent.config.tab_id:
|
|
181
|
+
agent.request_process_exit()
|
|
182
|
+
persistent_agents.append(agent)
|
|
183
|
+
continue
|
|
184
|
+
agent.terminate_and_wait(timeout=timeout)
|
|
185
|
+
for agent in persistent_agents:
|
|
186
|
+
agent.wait_for_termination(timeout=timeout)
|
|
187
|
+
registry.reset()
|
|
188
|
+
logger.info("Runtime shutdown complete")
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def is_path_writable(path: str | Path, write_dirs: list[str]) -> bool:
|
|
7
|
+
from flowent.settings import resolve_path
|
|
8
|
+
|
|
9
|
+
p = resolve_path(path)
|
|
10
|
+
return any(p.is_relative_to(resolve_path(directory)) for directory in write_dirs)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def build_bwrap_cmd(
|
|
14
|
+
write_dirs: list[str],
|
|
15
|
+
command: str,
|
|
16
|
+
*,
|
|
17
|
+
allow_network: bool = False,
|
|
18
|
+
cwd: str | Path | None = None,
|
|
19
|
+
) -> list[str]:
|
|
20
|
+
from flowent.settings import resolve_path
|
|
21
|
+
|
|
22
|
+
resolved_cwd = resolve_path(cwd) if cwd is not None else None
|
|
23
|
+
cmd = [
|
|
24
|
+
"bwrap",
|
|
25
|
+
"--ro-bind",
|
|
26
|
+
"/",
|
|
27
|
+
"/",
|
|
28
|
+
"--dev",
|
|
29
|
+
"/dev",
|
|
30
|
+
"--proc",
|
|
31
|
+
"/proc",
|
|
32
|
+
"--tmpfs",
|
|
33
|
+
"/tmp",
|
|
34
|
+
]
|
|
35
|
+
if resolved_cwd is not None:
|
|
36
|
+
cmd.extend(["--ro-bind", str(resolved_cwd), str(resolved_cwd)])
|
|
37
|
+
for directory in write_dirs:
|
|
38
|
+
resolved_dir = resolve_path(directory)
|
|
39
|
+
cmd.extend(["--bind", str(resolved_dir), str(resolved_dir)])
|
|
40
|
+
if not allow_network:
|
|
41
|
+
cmd.append("--unshare-net")
|
|
42
|
+
if resolved_cwd is not None:
|
|
43
|
+
cmd.extend(["--chdir", str(resolved_cwd)])
|
|
44
|
+
cmd.extend(["--die-with-parent", "--new-session", "--", "bash", "-c", command])
|
|
45
|
+
return cmd
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from flowent.sandbox import is_path_writable
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from flowent.agent import Agent
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def authorize(tool_name: str, agent: Agent, args: dict[str, Any]) -> str | None:
|
|
12
|
+
if tool_name.startswith("mcp__"):
|
|
13
|
+
from flowent.mcp_service import mcp_service
|
|
14
|
+
from flowent.settings import find_mcp_server, get_settings
|
|
15
|
+
|
|
16
|
+
descriptor = mcp_service.get_dynamic_tool_descriptor(tool_name)
|
|
17
|
+
if descriptor is None:
|
|
18
|
+
return f"MCP tool not found: {tool_name}"
|
|
19
|
+
server = find_mcp_server(get_settings(), descriptor.server_name)
|
|
20
|
+
if (
|
|
21
|
+
server is not None
|
|
22
|
+
and server.transport == "streamable_http"
|
|
23
|
+
and not agent.config.allow_network
|
|
24
|
+
):
|
|
25
|
+
return "Network access is disabled for this agent"
|
|
26
|
+
if descriptor.open_world_hint and not agent.config.allow_network:
|
|
27
|
+
return "Network access is disabled for this agent"
|
|
28
|
+
return None
|
|
29
|
+
|
|
30
|
+
if tool_name == "edit":
|
|
31
|
+
write_dirs = agent.config.write_dirs
|
|
32
|
+
if not write_dirs:
|
|
33
|
+
return "Write access is disabled for this agent"
|
|
34
|
+
path = args.get("path")
|
|
35
|
+
if isinstance(path, str) and not is_path_writable(path, write_dirs):
|
|
36
|
+
return f"Path not in write_dirs: {path}"
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
if tool_name == "fetch" and not agent.config.allow_network:
|
|
40
|
+
return "Network access is disabled for this agent"
|
|
41
|
+
|
|
42
|
+
return None
|