flowent 0.0.0 → 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 +70 -10
- package/assets/flowent-banner.png +0 -0
- 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 -35
- 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 +28 -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/7FFlzRe2eS-D0Lw5oEpmC/_buildManifest.js +0 -11
- package/dist/.next/static/7FFlzRe2eS-D0Lw5oEpmC/_clientMiddlewareManifest.js +0 -1
- package/dist/.next/static/7FFlzRe2eS-D0Lw5oEpmC/_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/15~9l5n.~r-.4.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 -87
- 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,22 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
5
|
+
|
|
6
|
+
from flowent.tools import Tool
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from flowent.agent import Agent
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ContactsTool(Tool):
|
|
13
|
+
name = "contacts"
|
|
14
|
+
description = "List the agents this node can message directly right now."
|
|
15
|
+
parameters: ClassVar[dict[str, Any]] = {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"properties": {},
|
|
18
|
+
"required": [],
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
def execute(self, agent: Agent, args: dict[str, Any], **_kwargs: Any) -> str:
|
|
22
|
+
return json.dumps({"contacts": agent.get_contacts_info()})
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
5
|
+
|
|
6
|
+
from flowent.graph_service import create_agent_node
|
|
7
|
+
from flowent.models import NodeType
|
|
8
|
+
from flowent.settings import build_assistant_write_dirs, resolve_path
|
|
9
|
+
from flowent.tools import Tool
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from flowent.agent import Agent
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CreateAgentTool(Tool):
|
|
16
|
+
name = "create_agent"
|
|
17
|
+
description = "Create a new agent node inside your current workflow."
|
|
18
|
+
parameters: ClassVar[dict[str, Any]] = {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": {
|
|
21
|
+
"role_name": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "Role assigned to the new agent",
|
|
24
|
+
},
|
|
25
|
+
"name": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"description": "Optional human-readable node name",
|
|
28
|
+
},
|
|
29
|
+
"tools": {
|
|
30
|
+
"type": "array",
|
|
31
|
+
"items": {"type": "string"},
|
|
32
|
+
"description": "Optional additional tools",
|
|
33
|
+
},
|
|
34
|
+
"write_dirs": {
|
|
35
|
+
"type": "array",
|
|
36
|
+
"items": {"type": "string"},
|
|
37
|
+
"description": "Optional writable directories",
|
|
38
|
+
},
|
|
39
|
+
"allow_network": {
|
|
40
|
+
"type": "boolean",
|
|
41
|
+
"description": "Whether the node can access the network",
|
|
42
|
+
"default": False,
|
|
43
|
+
},
|
|
44
|
+
"placement": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"enum": ["standalone", "after", "between"],
|
|
47
|
+
"description": (
|
|
48
|
+
"How to place the new agent inside the current workflow graph"
|
|
49
|
+
),
|
|
50
|
+
"default": "standalone",
|
|
51
|
+
},
|
|
52
|
+
"after_node_id": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"description": "Anchor node id when placement is `after`",
|
|
55
|
+
},
|
|
56
|
+
"between_from_node_id": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"description": "Upstream node id when placement is `between`",
|
|
59
|
+
},
|
|
60
|
+
"between_to_node_id": {
|
|
61
|
+
"type": "string",
|
|
62
|
+
"description": "Downstream node id when placement is `between`",
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
"required": ["role_name"],
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
def execute(self, agent: Agent, args: dict[str, Any], **_kwargs: Any) -> str:
|
|
69
|
+
if "workflow_id" in args or "tab_id" in args:
|
|
70
|
+
return json.dumps({"error": "create_agent does not accept workflow_id"})
|
|
71
|
+
if "connect_to_creator" in args:
|
|
72
|
+
return json.dumps(
|
|
73
|
+
{
|
|
74
|
+
"error": "create_agent no longer supports connect_to_creator; use placement"
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
role_name = args.get("role_name")
|
|
78
|
+
name = args.get("name")
|
|
79
|
+
tools = args.get("tools", [])
|
|
80
|
+
write_dirs = args.get("write_dirs", [])
|
|
81
|
+
allow_network = args.get("allow_network", False)
|
|
82
|
+
placement = args.get("placement", "standalone")
|
|
83
|
+
after_node_id = args.get("after_node_id")
|
|
84
|
+
between_from_node_id = args.get("between_from_node_id")
|
|
85
|
+
between_to_node_id = args.get("between_to_node_id")
|
|
86
|
+
|
|
87
|
+
if not isinstance(role_name, str) or not role_name.strip():
|
|
88
|
+
return json.dumps({"error": "role_name must be a non-empty string"})
|
|
89
|
+
if name is not None and not isinstance(name, str):
|
|
90
|
+
return json.dumps({"error": "name must be a string"})
|
|
91
|
+
if not isinstance(tools, list) or not all(
|
|
92
|
+
isinstance(item, str) for item in tools
|
|
93
|
+
):
|
|
94
|
+
return json.dumps({"error": "tools must be an array of strings"})
|
|
95
|
+
if not isinstance(write_dirs, list) or not all(
|
|
96
|
+
isinstance(item, str) for item in write_dirs
|
|
97
|
+
):
|
|
98
|
+
return json.dumps({"error": "write_dirs must be an array of strings"})
|
|
99
|
+
if not isinstance(allow_network, bool):
|
|
100
|
+
return json.dumps({"error": "allow_network must be a boolean"})
|
|
101
|
+
if placement not in {"standalone", "after", "between"}:
|
|
102
|
+
return json.dumps(
|
|
103
|
+
{"error": "placement must be standalone, after, or between"}
|
|
104
|
+
)
|
|
105
|
+
if after_node_id is not None and not isinstance(after_node_id, str):
|
|
106
|
+
return json.dumps({"error": "after_node_id must be a string"})
|
|
107
|
+
if between_from_node_id is not None and not isinstance(
|
|
108
|
+
between_from_node_id, str
|
|
109
|
+
):
|
|
110
|
+
return json.dumps({"error": "between_from_node_id must be a string"})
|
|
111
|
+
if between_to_node_id is not None and not isinstance(between_to_node_id, str):
|
|
112
|
+
return json.dumps({"error": "between_to_node_id must be a string"})
|
|
113
|
+
try:
|
|
114
|
+
write_dirs = build_assistant_write_dirs(
|
|
115
|
+
write_dirs,
|
|
116
|
+
field_name="write_dirs",
|
|
117
|
+
)
|
|
118
|
+
except ValueError as exc:
|
|
119
|
+
return json.dumps({"error": str(exc)})
|
|
120
|
+
normalized_role_name = role_name.strip()
|
|
121
|
+
if agent.node_type == NodeType.ASSISTANT:
|
|
122
|
+
return json.dumps(
|
|
123
|
+
{"error": "Assistant may not create ordinary task nodes directly"}
|
|
124
|
+
)
|
|
125
|
+
if not agent.config.tab_id:
|
|
126
|
+
return json.dumps(
|
|
127
|
+
{
|
|
128
|
+
"error": "Only a node inside a workflow may create ordinary task nodes"
|
|
129
|
+
}
|
|
130
|
+
)
|
|
131
|
+
if "create_agent" not in agent.config.tools:
|
|
132
|
+
return json.dumps({"error": "create_agent is not enabled for this node"})
|
|
133
|
+
from flowent.graph_service import get_tab_leader_id
|
|
134
|
+
|
|
135
|
+
leader_id = get_tab_leader_id(agent.config.tab_id)
|
|
136
|
+
if leader_id is None:
|
|
137
|
+
return json.dumps(
|
|
138
|
+
{"error": "Current workflow does not have a bound Leader"}
|
|
139
|
+
)
|
|
140
|
+
from flowent.registry import registry
|
|
141
|
+
from flowent.workspace_store import workspace_store
|
|
142
|
+
|
|
143
|
+
leader = registry.get(leader_id)
|
|
144
|
+
leader_record = workspace_store.get_node_record(leader_id)
|
|
145
|
+
leader_config = (
|
|
146
|
+
agent.config
|
|
147
|
+
if agent.uuid == leader_id
|
|
148
|
+
else (
|
|
149
|
+
leader.config
|
|
150
|
+
if leader is not None
|
|
151
|
+
else (leader_record.config if leader_record is not None else None)
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
if leader is None and leader_record is None:
|
|
155
|
+
return json.dumps({"error": f"Leader '{leader_id}' was not found"})
|
|
156
|
+
leader_write_dirs_source = (
|
|
157
|
+
leader_config.write_dirs if leader_config is not None else []
|
|
158
|
+
)
|
|
159
|
+
leader_allow_network = (
|
|
160
|
+
leader_config.allow_network if leader_config is not None else False
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
parent_write_dirs = [resolve_path(path) for path in agent.config.write_dirs]
|
|
164
|
+
invalid_write_dirs = sorted(
|
|
165
|
+
path
|
|
166
|
+
for path in write_dirs
|
|
167
|
+
if not any(
|
|
168
|
+
resolve_path(path).is_relative_to(parent_path)
|
|
169
|
+
for parent_path in parent_write_dirs
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
if invalid_write_dirs:
|
|
173
|
+
return json.dumps(
|
|
174
|
+
{
|
|
175
|
+
"error": "write_dirs boundary exceeded: "
|
|
176
|
+
+ ", ".join(invalid_write_dirs)
|
|
177
|
+
}
|
|
178
|
+
)
|
|
179
|
+
if allow_network and not agent.config.allow_network:
|
|
180
|
+
return json.dumps(
|
|
181
|
+
{
|
|
182
|
+
"error": "allow_network boundary exceeded: parent disallows network access"
|
|
183
|
+
}
|
|
184
|
+
)
|
|
185
|
+
leader_write_dirs = [resolve_path(path) for path in leader_write_dirs_source]
|
|
186
|
+
leader_invalid_write_dirs = sorted(
|
|
187
|
+
path
|
|
188
|
+
for path in write_dirs
|
|
189
|
+
if not any(
|
|
190
|
+
resolve_path(path).is_relative_to(parent_path)
|
|
191
|
+
for parent_path in leader_write_dirs
|
|
192
|
+
)
|
|
193
|
+
)
|
|
194
|
+
if leader_invalid_write_dirs:
|
|
195
|
+
return json.dumps(
|
|
196
|
+
{
|
|
197
|
+
"error": "write_dirs boundary exceeded: "
|
|
198
|
+
+ ", ".join(leader_invalid_write_dirs)
|
|
199
|
+
}
|
|
200
|
+
)
|
|
201
|
+
if allow_network and not leader_allow_network:
|
|
202
|
+
return json.dumps(
|
|
203
|
+
{
|
|
204
|
+
"error": "allow_network boundary exceeded: workflow Leader disallows network access"
|
|
205
|
+
}
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
record, error = create_agent_node(
|
|
209
|
+
role_name=normalized_role_name,
|
|
210
|
+
tab_id=agent.config.tab_id,
|
|
211
|
+
name=name,
|
|
212
|
+
tools=tools,
|
|
213
|
+
write_dirs=write_dirs,
|
|
214
|
+
allow_network=allow_network,
|
|
215
|
+
)
|
|
216
|
+
if error is not None or record is None:
|
|
217
|
+
return json.dumps({"error": error or "Failed to create agent"})
|
|
218
|
+
if placement == "after":
|
|
219
|
+
if not isinstance(after_node_id, str) or not after_node_id.strip():
|
|
220
|
+
return json.dumps(
|
|
221
|
+
{"error": "after_node_id is required when placement=after"}
|
|
222
|
+
)
|
|
223
|
+
from flowent.graph_service import create_edge
|
|
224
|
+
|
|
225
|
+
edge, edge_error = create_edge(
|
|
226
|
+
tab_id=agent.config.tab_id,
|
|
227
|
+
from_node_id=after_node_id,
|
|
228
|
+
to_node_id=record.id,
|
|
229
|
+
)
|
|
230
|
+
if edge_error is not None or edge is None:
|
|
231
|
+
return json.dumps({"error": edge_error or "Failed to place agent"})
|
|
232
|
+
elif placement == "between":
|
|
233
|
+
if (
|
|
234
|
+
not isinstance(between_from_node_id, str)
|
|
235
|
+
or not between_from_node_id.strip()
|
|
236
|
+
):
|
|
237
|
+
return json.dumps(
|
|
238
|
+
{"error": "between_from_node_id is required when placement=between"}
|
|
239
|
+
)
|
|
240
|
+
if (
|
|
241
|
+
not isinstance(between_to_node_id, str)
|
|
242
|
+
or not between_to_node_id.strip()
|
|
243
|
+
):
|
|
244
|
+
return json.dumps(
|
|
245
|
+
{"error": "between_to_node_id is required when placement=between"}
|
|
246
|
+
)
|
|
247
|
+
from flowent.graph_service import create_edge, delete_edge
|
|
248
|
+
|
|
249
|
+
deleted, delete_error = delete_edge(
|
|
250
|
+
tab_id=agent.config.tab_id,
|
|
251
|
+
from_node_id=between_from_node_id,
|
|
252
|
+
to_node_id=between_to_node_id,
|
|
253
|
+
)
|
|
254
|
+
if delete_error is not None or deleted is None:
|
|
255
|
+
return json.dumps({"error": delete_error or "Failed to place agent"})
|
|
256
|
+
first_edge, first_error = create_edge(
|
|
257
|
+
tab_id=agent.config.tab_id,
|
|
258
|
+
from_node_id=between_from_node_id,
|
|
259
|
+
to_node_id=record.id,
|
|
260
|
+
)
|
|
261
|
+
if first_error is not None or first_edge is None:
|
|
262
|
+
return json.dumps({"error": first_error or "Failed to place agent"})
|
|
263
|
+
second_edge, second_error = create_edge(
|
|
264
|
+
tab_id=agent.config.tab_id,
|
|
265
|
+
from_node_id=record.id,
|
|
266
|
+
to_node_id=between_to_node_id,
|
|
267
|
+
)
|
|
268
|
+
if second_error is not None or second_edge is None:
|
|
269
|
+
return json.dumps({"error": second_error or "Failed to place agent"})
|
|
270
|
+
return json.dumps(record.serialize())
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
5
|
+
|
|
6
|
+
from flowent.graph_service import create_tab, serialize_tab_summary
|
|
7
|
+
from flowent.tools import Tool
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from flowent.agent import Agent
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CreateTabTool(Tool):
|
|
14
|
+
name = "create_workflow"
|
|
15
|
+
description = (
|
|
16
|
+
"Create a new workflow with its bound Leader and empty Workflow Graph."
|
|
17
|
+
)
|
|
18
|
+
parameters: ClassVar[dict[str, Any]] = {
|
|
19
|
+
"type": "object",
|
|
20
|
+
"properties": {
|
|
21
|
+
"title": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "Human-readable workflow title",
|
|
24
|
+
},
|
|
25
|
+
"allow_network": {
|
|
26
|
+
"type": "boolean",
|
|
27
|
+
"description": "Whether the workflow's leader should have network access (default False)",
|
|
28
|
+
},
|
|
29
|
+
"write_dirs": {
|
|
30
|
+
"type": "array",
|
|
31
|
+
"items": {"type": "string"},
|
|
32
|
+
"description": "List of directory paths the workflow's leader is allowed to write to",
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
"required": ["title"],
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
def execute(self, agent: Agent, args: dict[str, Any], **_kwargs: Any) -> str:
|
|
39
|
+
title = args.get("title")
|
|
40
|
+
allow_network = args.get("allow_network", False)
|
|
41
|
+
write_dirs = args.get("write_dirs", [])
|
|
42
|
+
if not isinstance(title, str) or not title.strip():
|
|
43
|
+
return json.dumps({"error": "title must be a non-empty string"})
|
|
44
|
+
if not isinstance(allow_network, bool):
|
|
45
|
+
return json.dumps({"error": "allow_network must be a boolean"})
|
|
46
|
+
if not isinstance(write_dirs, list) or not all(
|
|
47
|
+
isinstance(x, str) for x in write_dirs
|
|
48
|
+
):
|
|
49
|
+
return json.dumps({"error": "write_dirs must be a list of strings"})
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
tab = create_tab(
|
|
53
|
+
title=title,
|
|
54
|
+
allow_network=allow_network,
|
|
55
|
+
write_dirs=write_dirs,
|
|
56
|
+
)
|
|
57
|
+
except ValueError as exc:
|
|
58
|
+
return json.dumps({"error": str(exc)})
|
|
59
|
+
return json.dumps(serialize_tab_summary(tab))
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
5
|
+
|
|
6
|
+
from flowent.graph_service import delete_tab
|
|
7
|
+
from flowent.models import NodeType
|
|
8
|
+
from flowent.tools import Tool
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from flowent.agent import Agent
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class DeleteTabTool(Tool):
|
|
15
|
+
name = "delete_workflow"
|
|
16
|
+
description = "Delete a workflow and clean up its graph."
|
|
17
|
+
parameters: ClassVar[dict[str, Any]] = {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"properties": {
|
|
20
|
+
"workflow_id": {
|
|
21
|
+
"type": "string",
|
|
22
|
+
"description": "ID of the workflow to delete",
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"required": ["workflow_id"],
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
def execute(self, agent: Agent, args: dict[str, Any], **_kwargs: Any) -> str:
|
|
29
|
+
if agent.node_type != NodeType.ASSISTANT:
|
|
30
|
+
return json.dumps({"error": "Only the Assistant may delete workflows"})
|
|
31
|
+
|
|
32
|
+
workflow_id = args.get("workflow_id")
|
|
33
|
+
if not isinstance(workflow_id, str) or not workflow_id.strip():
|
|
34
|
+
return json.dumps({"error": "workflow_id must be a non-empty string"})
|
|
35
|
+
|
|
36
|
+
deleted, error = delete_tab(tab_id=workflow_id.strip())
|
|
37
|
+
if error is not None or deleted is None:
|
|
38
|
+
return json.dumps({"error": error or "Failed to delete workflow"})
|
|
39
|
+
return json.dumps(deleted)
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from collections.abc import Callable
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
8
|
+
|
|
9
|
+
from loguru import logger
|
|
10
|
+
|
|
11
|
+
from flowent.tools import Tool, re_raise_interrupt
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from flowent.agent import Agent
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class EditTool(Tool):
|
|
18
|
+
name = "edit"
|
|
19
|
+
description = (
|
|
20
|
+
"Apply one or more line-based edits to a file in order. "
|
|
21
|
+
"Use the read tool first to get the exact line numbers. "
|
|
22
|
+
"Each edit uses 1-indexed inclusive start_line and end_line. "
|
|
23
|
+
"new_content replaces those lines exactly as given (include a trailing newline if needed). "
|
|
24
|
+
"If the file does not exist it will be created."
|
|
25
|
+
)
|
|
26
|
+
parameters: ClassVar[dict[str, Any]] = {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"properties": {
|
|
29
|
+
"path": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "Absolute path to the file to edit or create",
|
|
32
|
+
},
|
|
33
|
+
"edits": {
|
|
34
|
+
"type": "array",
|
|
35
|
+
"items": {
|
|
36
|
+
"type": "object",
|
|
37
|
+
"properties": {
|
|
38
|
+
"start_line": {
|
|
39
|
+
"type": "integer",
|
|
40
|
+
"description": "First line to replace (1-indexed, inclusive). Use 1 for a new file.",
|
|
41
|
+
},
|
|
42
|
+
"end_line": {
|
|
43
|
+
"type": "integer",
|
|
44
|
+
"description": "Last line to replace (1-indexed, inclusive).",
|
|
45
|
+
},
|
|
46
|
+
"new_content": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"description": "Replacement text for the specified line range. Use an empty string to delete lines.",
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
"required": ["start_line", "end_line", "new_content"],
|
|
52
|
+
},
|
|
53
|
+
"description": "Edits to apply in order. Later line numbers are based on the file state after earlier edits.",
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
"required": ["path", "edits"],
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
def execute(self, agent: Agent, args: dict[str, Any], **kwargs: Any) -> str:
|
|
60
|
+
path_str = args["path"]
|
|
61
|
+
real_path = Path(path_str)
|
|
62
|
+
on_output: Callable[[str], None] | None = kwargs.get("on_output")
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
edits = args["edits"]
|
|
66
|
+
if not isinstance(edits, list):
|
|
67
|
+
return json.dumps({"error": "edits must be an array"})
|
|
68
|
+
|
|
69
|
+
if not real_path.exists():
|
|
70
|
+
os.makedirs(real_path.parent, exist_ok=True)
|
|
71
|
+
real_path.write_text("", encoding="utf-8")
|
|
72
|
+
if on_output is not None:
|
|
73
|
+
on_output(f"Created {path_str}\n")
|
|
74
|
+
|
|
75
|
+
with open(real_path, encoding="utf-8") as f:
|
|
76
|
+
lines = f.readlines()
|
|
77
|
+
|
|
78
|
+
applied_edits: list[dict[str, int | str]] = []
|
|
79
|
+
for index, raw_edit in enumerate(edits, start=1):
|
|
80
|
+
if not isinstance(raw_edit, dict):
|
|
81
|
+
return json.dumps({"error": "each edit must be an object"})
|
|
82
|
+
|
|
83
|
+
start_line = int(raw_edit["start_line"])
|
|
84
|
+
end_line = int(raw_edit["end_line"])
|
|
85
|
+
new_content: str = raw_edit["new_content"]
|
|
86
|
+
|
|
87
|
+
if start_line < 1:
|
|
88
|
+
return json.dumps({"error": "start_line must be >= 1"})
|
|
89
|
+
if end_line < start_line:
|
|
90
|
+
return json.dumps({"error": "end_line must be >= start_line"})
|
|
91
|
+
|
|
92
|
+
total_lines = len(lines)
|
|
93
|
+
if start_line > total_lines + 1:
|
|
94
|
+
return json.dumps(
|
|
95
|
+
{
|
|
96
|
+
"error": f"start_line {start_line} exceeds file length {total_lines}"
|
|
97
|
+
}
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
current_end_line = min(end_line, total_lines)
|
|
101
|
+
if on_output is not None:
|
|
102
|
+
on_output(
|
|
103
|
+
f"Applying edit {index}/{len(edits)} at lines {start_line}-{current_end_line}\n"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
replacement = []
|
|
107
|
+
if new_content:
|
|
108
|
+
replacement = new_content.splitlines(keepends=True)
|
|
109
|
+
if replacement and not replacement[-1].endswith("\n"):
|
|
110
|
+
replacement[-1] += "\n"
|
|
111
|
+
|
|
112
|
+
lines = lines[: start_line - 1] + replacement + lines[current_end_line:]
|
|
113
|
+
applied_edits.append(
|
|
114
|
+
{
|
|
115
|
+
"start_line": start_line,
|
|
116
|
+
"end_line": current_end_line,
|
|
117
|
+
"replacement_line_count": len(replacement),
|
|
118
|
+
}
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
with open(real_path, "w", encoding="utf-8") as f:
|
|
122
|
+
f.writelines(lines)
|
|
123
|
+
if on_output is not None:
|
|
124
|
+
on_output(f"Wrote {path_str}\n")
|
|
125
|
+
|
|
126
|
+
logger.debug(
|
|
127
|
+
"Edited file: {} ({} edit(s), new_line_count={})",
|
|
128
|
+
path_str,
|
|
129
|
+
len(applied_edits),
|
|
130
|
+
len(lines),
|
|
131
|
+
)
|
|
132
|
+
return json.dumps(
|
|
133
|
+
{
|
|
134
|
+
"status": "edited",
|
|
135
|
+
"path": path_str,
|
|
136
|
+
"applied_edits": applied_edits,
|
|
137
|
+
"new_line_count": len(lines),
|
|
138
|
+
}
|
|
139
|
+
)
|
|
140
|
+
except Exception as e:
|
|
141
|
+
re_raise_interrupt(agent, e)
|
|
142
|
+
return json.dumps({"error": str(e)})
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import subprocess
|
|
5
|
+
import threading
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TYPE_CHECKING, Any, ClassVar
|
|
9
|
+
|
|
10
|
+
from loguru import logger
|
|
11
|
+
|
|
12
|
+
from flowent.sandbox import build_bwrap_cmd
|
|
13
|
+
from flowent.tools import Tool, re_raise_interrupt
|
|
14
|
+
|
|
15
|
+
if TYPE_CHECKING:
|
|
16
|
+
from flowent.agent import Agent
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ExecTool(Tool):
|
|
20
|
+
name = "exec"
|
|
21
|
+
description = "Execute a shell command in a sandboxed environment."
|
|
22
|
+
parameters: ClassVar[dict[str, Any]] = {
|
|
23
|
+
"type": "object",
|
|
24
|
+
"properties": {
|
|
25
|
+
"command": {
|
|
26
|
+
"type": "string",
|
|
27
|
+
"description": "Shell command to execute",
|
|
28
|
+
},
|
|
29
|
+
"timeout": {
|
|
30
|
+
"type": "number",
|
|
31
|
+
"description": "Timeout in seconds (default 30)",
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
"required": ["command"],
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
def execute(self, agent: Agent, args: dict[str, Any], **kwargs: Any) -> str:
|
|
38
|
+
on_output: Callable[[str], None] | None = kwargs.get("on_output")
|
|
39
|
+
from flowent.settings import get_runtime_working_dir_path
|
|
40
|
+
|
|
41
|
+
command = args["command"]
|
|
42
|
+
timeout = int(args.get("timeout", 30))
|
|
43
|
+
write_dirs = agent.config.write_dirs
|
|
44
|
+
cwd = Path(get_runtime_working_dir_path())
|
|
45
|
+
|
|
46
|
+
bwrap_cmd = build_bwrap_cmd(
|
|
47
|
+
write_dirs,
|
|
48
|
+
command,
|
|
49
|
+
allow_network=agent.config.allow_network,
|
|
50
|
+
cwd=cwd,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
logger.debug(
|
|
54
|
+
"Executing command: {} (timeout={}s, cwd={})",
|
|
55
|
+
command,
|
|
56
|
+
timeout,
|
|
57
|
+
cwd,
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
proc: subprocess.Popen[str] | None = None
|
|
61
|
+
try:
|
|
62
|
+
proc = subprocess.Popen(
|
|
63
|
+
bwrap_cmd,
|
|
64
|
+
stdout=subprocess.PIPE,
|
|
65
|
+
stderr=subprocess.PIPE,
|
|
66
|
+
text=True,
|
|
67
|
+
cwd=str(cwd),
|
|
68
|
+
)
|
|
69
|
+
agent.set_interrupt_callback(
|
|
70
|
+
lambda: proc.kill() if proc.poll() is None else None
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
stdout_lines: list[str] = []
|
|
74
|
+
stderr_lines: list[str] = []
|
|
75
|
+
|
|
76
|
+
def _read_stderr() -> None:
|
|
77
|
+
assert proc.stderr is not None
|
|
78
|
+
for line in proc.stderr:
|
|
79
|
+
stderr_lines.append(line)
|
|
80
|
+
|
|
81
|
+
stderr_thread = threading.Thread(target=_read_stderr, daemon=True)
|
|
82
|
+
stderr_thread.start()
|
|
83
|
+
|
|
84
|
+
assert proc.stdout is not None
|
|
85
|
+
for line in proc.stdout:
|
|
86
|
+
stdout_lines.append(line)
|
|
87
|
+
if on_output:
|
|
88
|
+
on_output(line)
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
proc.wait(timeout=timeout)
|
|
92
|
+
except subprocess.TimeoutExpired:
|
|
93
|
+
proc.kill()
|
|
94
|
+
proc.wait()
|
|
95
|
+
logger.warning("Command timed out after {}s: {}", timeout, command)
|
|
96
|
+
return json.dumps({"error": f"Command timed out after {timeout}s"})
|
|
97
|
+
|
|
98
|
+
stderr_thread.join(timeout=5)
|
|
99
|
+
|
|
100
|
+
stdout = "".join(stdout_lines)
|
|
101
|
+
stderr = "".join(stderr_lines)
|
|
102
|
+
logger.debug("Command exited with code {}", proc.returncode)
|
|
103
|
+
return json.dumps(
|
|
104
|
+
{
|
|
105
|
+
"returncode": proc.returncode,
|
|
106
|
+
"stdout": stdout[:5000],
|
|
107
|
+
"stderr": stderr[:2000],
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
except Exception as e:
|
|
111
|
+
if proc is not None and proc.poll() is None:
|
|
112
|
+
proc.kill()
|
|
113
|
+
proc.wait()
|
|
114
|
+
re_raise_interrupt(agent, e)
|
|
115
|
+
return json.dumps({"error": str(e)})
|
|
116
|
+
finally:
|
|
117
|
+
agent.set_interrupt_callback(None)
|