flowent 0.0.4 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/backend/README.md +74 -0
- package/backend/pyproject.toml +2 -1
- package/backend/src/flowent/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/_version.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/access.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/agent.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/assistant_commands.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/cli.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/config.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/events.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/graph_runtime.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/graph_service.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/image_assets.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/logging.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/main.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/mcp_service.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/model_metadata.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/network.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/{stats_service.cpython-313.pyc → observability_service.cpython-313.pyc} +0 -0
- package/backend/src/flowent/__pycache__/registry.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/role_management.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/runtime.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/sandbox.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/security.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/settings.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/settings_management.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/state_db.cpython-313.pyc +0 -0
- package/backend/src/flowent/__pycache__/workspace_store.cpython-313.pyc +0 -0
- package/backend/src/flowent/agent.py +364 -52
- package/backend/src/flowent/assistant_commands.py +31 -22
- package/backend/src/flowent/channels/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/channels/__pycache__/telegram.cpython-313.pyc +0 -0
- package/backend/src/flowent/channels/telegram.py +4 -4
- package/backend/src/flowent/graph_service.py +1307 -145
- package/backend/src/flowent/mcp_service.py +21 -7
- package/backend/src/flowent/model_metadata.py +4 -0
- package/backend/src/flowent/models/__init__.py +6 -2
- package/backend/src/flowent/models/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/agent.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/base.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/blueprint.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/content.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/delta.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/event.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/graph.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/history.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/llm.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/message.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/tab.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/__pycache__/todo.cpython-313.pyc +0 -0
- package/backend/src/flowent/models/agent.py +1 -0
- package/backend/src/flowent/models/graph.py +44 -9
- package/backend/src/flowent/models/history.py +73 -15
- package/backend/src/flowent/models/llm.py +1 -0
- package/backend/src/flowent/models/message.py +6 -0
- package/backend/src/flowent/models/tab.py +38 -1
- package/backend/src/flowent/{stats_service.py → observability_service.py} +4 -4
- package/backend/src/flowent/prompts/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/prompts/__pycache__/common.cpython-313.pyc +0 -0
- package/backend/src/flowent/prompts/__pycache__/steward.cpython-313.pyc +0 -0
- package/backend/src/flowent/prompts/common.py +2 -2
- package/backend/src/flowent/prompts/steward.py +2 -2
- package/backend/src/flowent/providers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/anthropic.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/base_url.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/configuration.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/content.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/errors.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/gateway.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/headers.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/management.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/openai.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/openai_responses.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/registry.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/sse.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/__pycache__/thinking.cpython-313.pyc +0 -0
- package/backend/src/flowent/providers/configuration.py +7 -0
- package/backend/src/flowent/role_management.py +12 -0
- package/backend/src/flowent/routes/__init__.py +0 -2
- package/backend/src/flowent/routes/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/access.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/assistant.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/image_assets.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/mcp.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/meta.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/nodes.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/prompts.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/providers_route.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/roles.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/settings.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/tabs.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/__pycache__/ws.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/assistant.py +4 -4
- package/backend/src/flowent/routes/nodes.py +54 -6
- package/backend/src/flowent/routes/providers_route.py +1 -0
- package/backend/src/flowent/routes/roles.py +1 -1
- package/backend/src/flowent/routes/settings.py +4 -0
- package/backend/src/flowent/routes/tabs.py +29 -11
- package/backend/src/flowent/runtime.py +7 -30
- package/backend/src/flowent/security.py +23 -8
- package/backend/src/flowent/settings.py +56 -5
- package/backend/src/flowent/settings_management.py +12 -0
- package/backend/src/flowent/static/assets/AssistantPage-VBohhz4d.js +1 -0
- package/backend/src/flowent/static/assets/ChannelsPage-CIydPZA_.js +1 -0
- package/backend/src/flowent/static/assets/McpPage-CHPm2TPY.js +7 -0
- package/backend/src/flowent/static/assets/PageScaffold-DteOA8V7.js +1 -0
- package/backend/src/flowent/static/assets/PromptsPage-CSmJ3sZg.js +1 -0
- package/backend/src/flowent/static/assets/ProvidersPage-sl2jeG4e.js +3 -0
- package/backend/src/flowent/static/assets/RolesPage-DCe7W6Km.js +1 -0
- package/backend/src/flowent/static/assets/SettingsPage-Bix9e63E.js +3 -0
- package/backend/src/flowent/static/assets/ToolsPage-favNkj5C.js +1 -0
- package/backend/src/flowent/static/assets/WorkspaceCommandDialog-DRS6wiD6.js +1 -0
- package/backend/src/flowent/static/assets/WorkspacePage-KuaDjt_D.js +3 -0
- package/backend/src/flowent/static/assets/WorkspacePanels-BZxBw8M5.js +1 -0
- package/backend/src/flowent/static/assets/alert-dialog-DIBUCmqM.js +1 -0
- package/backend/src/flowent/static/assets/{dialog-BeGSweF6.js → dialog-BOvHIBrg.js} +1 -1
- package/backend/src/flowent/static/assets/index-Biio-CoI.js +10 -0
- package/backend/src/flowent/static/assets/index-CmQvO7sl.css +1 -0
- package/backend/src/flowent/static/assets/modelParams-DcEhGnu0.js +1 -0
- package/backend/src/flowent/static/assets/roles-BbIEIMeG.js +1 -0
- package/backend/src/flowent/static/assets/select-D9SwnlXF.js +1 -0
- package/backend/src/flowent/static/assets/surface-Bzr1FRG4.js +1 -0
- package/backend/src/flowent/static/assets/{ui-vendor-Dg9NNnWX.js → ui-vendor-UazN8rcv.js} +15 -15
- package/backend/src/flowent/static/index.html +3 -4
- package/backend/src/flowent/tools/__init__.py +76 -2
- package/backend/src/flowent/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/connect.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/contacts.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/create_agent.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/create_tab.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/delete_tab.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/edit.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/exec.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/fetch.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/idle.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/list_roles.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/list_tabs.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/list_tools.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/manage_prompts.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/manage_providers.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/manage_roles.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/manage_settings.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/mcp.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/read.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/send.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/set_permissions.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/sleep.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/__pycache__/todo.cpython-313.pyc +0 -0
- package/backend/src/flowent/tools/connect.py +10 -66
- package/backend/src/flowent/tools/contacts.py +1 -1
- package/backend/src/flowent/tools/create_agent.py +9 -88
- package/backend/src/flowent/tools/create_tab.py +7 -5
- package/backend/src/flowent/tools/exec.py +3 -2
- package/backend/src/flowent/tools/list_roles.py +29 -4
- package/backend/src/flowent/tools/list_tabs.py +4 -0
- package/backend/src/flowent/tools/list_tools.py +5 -1
- package/backend/src/flowent/tools/manage_settings.py +18 -0
- package/backend/src/flowent/tools/send.py +21 -3
- package/backend/src/flowent/tools/set_permissions.py +21 -6
- package/backend/tests/__pycache__/__init__.cpython-313.pyc +0 -0
- package/backend/tests/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/conftest.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_access_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_assistant_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_frontend_mounting.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_mcp_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_meta_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_nodes_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_prompts_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_roles_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/__pycache__/test_tabs_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/integration/api/test_assistant_api.py +1 -1
- package/backend/tests/integration/api/test_nodes_api.py +257 -21
- package/backend/tests/integration/api/test_roles_api.py +3 -2
- package/backend/tests/integration/api/test_tabs_api.py +312 -11
- package/backend/tests/unit/__pycache__/test_access.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/__pycache__/test_cli.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/__pycache__/test_graph_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/__pycache__/test_network.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/__pycache__/test_state_sqlite_storage.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/__pycache__/test_workspace_store.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/agent/__pycache__/test_agent_public_api.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/agent/__pycache__/test_agent_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/agent/test_agent_public_api.py +162 -71
- package/backend/tests/unit/agent/test_agent_runtime.py +285 -69
- package/backend/tests/unit/channels/__pycache__/test_telegram_channel.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/logging/__pycache__/test_logging.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/prompts/__pycache__/test_prompts.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/prompts/test_prompts.py +3 -2
- package/backend/tests/unit/providers/__pycache__/test_anthropic_provider.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_errors.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_extract_delta_parts.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_openai_provider.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_openai_responses.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_provider_gateway.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/providers/__pycache__/test_think_tag_parser.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/routes/__pycache__/test_prompts_routes.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/routes/__pycache__/test_providers_route.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/routes/__pycache__/test_roles_routes.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/routes/__pycache__/test_settings_routes.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/routes/test_providers_route.py +2 -0
- package/backend/tests/unit/routes/test_roles_routes.py +109 -0
- package/backend/tests/unit/routes/test_settings_routes.py +4 -0
- package/backend/tests/unit/runtime/__pycache__/test_bootstrap_runtime.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/runtime/test_bootstrap_runtime.py +8 -18
- package/backend/tests/unit/sandbox/__pycache__/test_sandbox_tools.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/security/__pycache__/test_security.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/security/test_security.py +16 -2
- package/backend/tests/unit/settings/__pycache__/test_settings_roles.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/settings/test_settings_roles.py +40 -0
- package/backend/tests/unit/test_state_sqlite_storage.py +67 -1
- package/backend/tests/unit/tools/__pycache__/test_connect_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_create_agent_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_delete_tab_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_edit_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_exec_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_fetch_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_manage_prompts_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_manage_providers_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_manage_roles_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_manage_settings_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_read_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_set_permissions_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_todo_tool.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/__pycache__/test_tool_registry.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/tools/test_connect_tool.py +2 -3
- package/backend/tests/unit/tools/test_create_agent_tool.py +9 -97
- package/backend/tests/unit/tools/test_delete_tab_tool.py +33 -0
- package/backend/tests/unit/tools/test_manage_providers_tool.py +2 -0
- package/backend/tests/unit/tools/test_manage_settings_tool.py +3 -0
- package/backend/tests/unit/tools/test_set_permissions_tool.py +216 -12
- package/backend/tests/unit/tools/test_tool_registry.py +103 -0
- package/backend/uv.lock +1 -1
- package/dist/frontend/assets/AssistantPage-VBohhz4d.js +1 -0
- package/dist/frontend/assets/ChannelsPage-CIydPZA_.js +1 -0
- package/dist/frontend/assets/McpPage-CHPm2TPY.js +7 -0
- package/dist/frontend/assets/PageScaffold-DteOA8V7.js +1 -0
- package/dist/frontend/assets/PromptsPage-CSmJ3sZg.js +1 -0
- package/dist/frontend/assets/ProvidersPage-sl2jeG4e.js +3 -0
- package/dist/frontend/assets/RolesPage-DCe7W6Km.js +1 -0
- package/dist/frontend/assets/SettingsPage-Bix9e63E.js +3 -0
- package/dist/frontend/assets/ToolsPage-favNkj5C.js +1 -0
- package/dist/frontend/assets/WorkspaceCommandDialog-DRS6wiD6.js +1 -0
- package/dist/frontend/assets/WorkspacePage-KuaDjt_D.js +3 -0
- package/dist/frontend/assets/WorkspacePanels-BZxBw8M5.js +1 -0
- package/dist/frontend/assets/alert-dialog-DIBUCmqM.js +1 -0
- package/dist/frontend/assets/{dialog-BeGSweF6.js → dialog-BOvHIBrg.js} +1 -1
- package/dist/frontend/assets/index-Biio-CoI.js +10 -0
- package/dist/frontend/assets/index-CmQvO7sl.css +1 -0
- package/dist/frontend/assets/modelParams-DcEhGnu0.js +1 -0
- package/dist/frontend/assets/roles-BbIEIMeG.js +1 -0
- package/dist/frontend/assets/select-D9SwnlXF.js +1 -0
- package/dist/frontend/assets/surface-Bzr1FRG4.js +1 -0
- package/dist/frontend/assets/{ui-vendor-Dg9NNnWX.js → ui-vendor-UazN8rcv.js} +15 -15
- package/dist/frontend/index.html +3 -4
- package/package.json +3 -3
- package/backend/src/flowent/routes/__pycache__/stats.cpython-313.pyc +0 -0
- package/backend/src/flowent/routes/stats.py +0 -229
- package/backend/src/flowent/static/assets/AssistantPage-B3Xc08AS.js +0 -1
- package/backend/src/flowent/static/assets/ChannelsPage-ByLd28xk.js +0 -1
- package/backend/src/flowent/static/assets/HomePage-C0hAx9_l.js +0 -3
- package/backend/src/flowent/static/assets/McpPage-DkrYLvBv.js +0 -7
- package/backend/src/flowent/static/assets/PageScaffold-D4jO9ooX.js +0 -1
- package/backend/src/flowent/static/assets/PromptsPage-DWA7rRJd.js +0 -1
- package/backend/src/flowent/static/assets/ProvidersPage-PUWT8seJ.js +0 -3
- package/backend/src/flowent/static/assets/RolesPage-CqcclGRw.js +0 -1
- package/backend/src/flowent/static/assets/SettingsPage-8tS2cJgX.js +0 -3
- package/backend/src/flowent/static/assets/StatsPage-BX9khYzu.js +0 -1
- package/backend/src/flowent/static/assets/ToolsPage-9Tl9FdeD.js +0 -1
- package/backend/src/flowent/static/assets/WorkspaceCommandDialog-CCXxjDL8.js +0 -1
- package/backend/src/flowent/static/assets/WorkspacePanels-aMdJ7ZH7.js +0 -1
- package/backend/src/flowent/static/assets/alert-dialog-kFYVQ7oX.js +0 -1
- package/backend/src/flowent/static/assets/badge-74-3jsCg.js +0 -1
- package/backend/src/flowent/static/assets/constants-XUzFf6i1.js +0 -1
- package/backend/src/flowent/static/assets/index-BHC1Vhy8.css +0 -1
- package/backend/src/flowent/static/assets/index-CL1ALZ3r.js +0 -10
- package/backend/src/flowent/static/assets/modelParams-CaHd0903.js +0 -1
- package/backend/src/flowent/static/assets/roles-2OLDeTc5.js +0 -1
- package/backend/src/flowent/static/assets/select-DL_LPeDj.js +0 -1
- package/backend/src/flowent/static/assets/shared-CMxbpLeQ.js +0 -1
- package/backend/tests/unit/routes/__pycache__/test_stats_routes.cpython-313-pytest-9.0.3.pyc +0 -0
- package/backend/tests/unit/routes/test_stats_routes.py +0 -149
- package/dist/frontend/assets/AssistantPage-B3Xc08AS.js +0 -1
- package/dist/frontend/assets/ChannelsPage-ByLd28xk.js +0 -1
- package/dist/frontend/assets/HomePage-C0hAx9_l.js +0 -3
- package/dist/frontend/assets/McpPage-DkrYLvBv.js +0 -7
- package/dist/frontend/assets/PageScaffold-D4jO9ooX.js +0 -1
- package/dist/frontend/assets/PromptsPage-DWA7rRJd.js +0 -1
- package/dist/frontend/assets/ProvidersPage-PUWT8seJ.js +0 -3
- package/dist/frontend/assets/RolesPage-CqcclGRw.js +0 -1
- package/dist/frontend/assets/SettingsPage-8tS2cJgX.js +0 -3
- package/dist/frontend/assets/StatsPage-BX9khYzu.js +0 -1
- package/dist/frontend/assets/ToolsPage-9Tl9FdeD.js +0 -1
- package/dist/frontend/assets/WorkspaceCommandDialog-CCXxjDL8.js +0 -1
- package/dist/frontend/assets/WorkspacePanels-aMdJ7ZH7.js +0 -1
- package/dist/frontend/assets/alert-dialog-kFYVQ7oX.js +0 -1
- package/dist/frontend/assets/badge-74-3jsCg.js +0 -1
- package/dist/frontend/assets/constants-XUzFf6i1.js +0 -1
- package/dist/frontend/assets/index-BHC1Vhy8.css +0 -1
- package/dist/frontend/assets/index-CL1ALZ3r.js +0 -10
- package/dist/frontend/assets/modelParams-CaHd0903.js +0 -1
- package/dist/frontend/assets/roles-2OLDeTc5.js +0 -1
- package/dist/frontend/assets/select-DL_LPeDj.js +0 -1
- package/dist/frontend/assets/shared-CMxbpLeQ.js +0 -1
- /package/backend/src/flowent/static/assets/{datetime-m6_O_Ci9.js → datetime-eJqd0V2S.js} +0 -0
- /package/backend/src/flowent/static/assets/{markdown-vendor-DVdy_w12.js → markdown-vendor-C9RtvaJh.js} +0 -0
- /package/backend/src/flowent/static/assets/{triState-DEr3NkXV.js → triState-DgLlKdRR.js} +0 -0
- /package/dist/frontend/assets/{datetime-m6_O_Ci9.js → datetime-eJqd0V2S.js} +0 -0
- /package/dist/frontend/assets/{markdown-vendor-DVdy_w12.js → markdown-vendor-C9RtvaJh.js} +0 -0
- /package/dist/frontend/assets/{triState-DEr3NkXV.js → triState-DgLlKdRR.js} +0 -0
|
@@ -10,10 +10,11 @@ from dataclasses import dataclass, field
|
|
|
10
10
|
from functools import lru_cache, partial
|
|
11
11
|
from queue import Empty, Queue
|
|
12
12
|
from typing import Any
|
|
13
|
+
from urllib.parse import urlparse
|
|
13
14
|
|
|
14
15
|
from loguru import logger
|
|
15
16
|
|
|
16
|
-
from flowent.assistant_commands import
|
|
17
|
+
from flowent.assistant_commands import build_conversation_help_text
|
|
17
18
|
from flowent.events import event_bus
|
|
18
19
|
from flowent.image_assets import create_image_asset, require_image_asset
|
|
19
20
|
from flowent.models import (
|
|
@@ -35,15 +36,17 @@ from flowent.models import (
|
|
|
35
36
|
ModelInfo,
|
|
36
37
|
NodeConfig,
|
|
37
38
|
NodeType,
|
|
39
|
+
PortInboundEntry,
|
|
38
40
|
ReceivedMessage,
|
|
39
41
|
SentMessage,
|
|
40
|
-
StateEntry,
|
|
41
42
|
SystemEntry,
|
|
42
43
|
TextPart,
|
|
43
44
|
ThinkingDelta,
|
|
44
45
|
TodoItem,
|
|
45
46
|
ToolCall,
|
|
46
47
|
ToolResultDelta,
|
|
48
|
+
WorkflowActivationState,
|
|
49
|
+
WorkflowNodeKind,
|
|
47
50
|
content_parts_to_text,
|
|
48
51
|
deserialize_content_parts,
|
|
49
52
|
has_image_parts,
|
|
@@ -123,6 +126,7 @@ class ResolvedModelSource:
|
|
|
123
126
|
provider_id: str | None
|
|
124
127
|
provider_name: str | None
|
|
125
128
|
provider_type: str | None
|
|
129
|
+
provider_base_url: str | None
|
|
126
130
|
model: str | None
|
|
127
131
|
model_info: ModelInfo | None
|
|
128
132
|
|
|
@@ -145,7 +149,6 @@ class Agent:
|
|
|
145
149
|
self.todos: list[TodoItem] = []
|
|
146
150
|
self.connections: list[str] = []
|
|
147
151
|
self.history: list[HistoryEntry] = []
|
|
148
|
-
self.history.append(StateEntry(state=self.state.value, reason="created"))
|
|
149
152
|
self._terminate = threading.Event()
|
|
150
153
|
self._interrupt_requested = threading.Event()
|
|
151
154
|
self._interrupt_callback_lock = threading.Lock()
|
|
@@ -329,7 +332,7 @@ class Agent:
|
|
|
329
332
|
return max(0.0, _time.perf_counter() - started_at)
|
|
330
333
|
|
|
331
334
|
def get_contact_ids_snapshot(self) -> list[str]:
|
|
332
|
-
from flowent.graph_service import get_tab_leader_id
|
|
335
|
+
from flowent.graph_service import get_tab_leader_id, list_agent_contact_paths
|
|
333
336
|
from flowent.registry import registry
|
|
334
337
|
from flowent.workspace_store import workspace_store
|
|
335
338
|
|
|
@@ -361,8 +364,6 @@ class Agent:
|
|
|
361
364
|
assistant = registry.get_assistant()
|
|
362
365
|
if is_leader and assistant is not None:
|
|
363
366
|
append_contact(assistant.uuid)
|
|
364
|
-
if not is_leader and leader_id is not None:
|
|
365
|
-
append_contact(leader_id)
|
|
366
367
|
|
|
367
368
|
if is_leader:
|
|
368
369
|
for node in registry.get_all():
|
|
@@ -375,13 +376,34 @@ class Agent:
|
|
|
375
376
|
append_contact(node.uuid)
|
|
376
377
|
return contact_ids
|
|
377
378
|
|
|
379
|
+
for path in list_agent_contact_paths(
|
|
380
|
+
tab_id=self.config.tab_id,
|
|
381
|
+
node_id=self.uuid,
|
|
382
|
+
):
|
|
383
|
+
append_contact(path.target_id)
|
|
378
384
|
return contact_ids
|
|
379
385
|
|
|
380
386
|
def get_contacts_info(self) -> list[dict[str, Any]]:
|
|
381
|
-
from flowent.graph_service import
|
|
387
|
+
from flowent.graph_service import (
|
|
388
|
+
is_tab_leader,
|
|
389
|
+
list_agent_contact_paths,
|
|
390
|
+
)
|
|
382
391
|
from flowent.registry import registry
|
|
383
392
|
|
|
384
393
|
result: list[dict[str, Any]] = []
|
|
394
|
+
if (
|
|
395
|
+
self.config.tab_id is not None
|
|
396
|
+
and not is_tab_leader(node_id=self.uuid, tab_id=self.config.tab_id)
|
|
397
|
+
and self.node_type == NodeType.AGENT
|
|
398
|
+
):
|
|
399
|
+
return [
|
|
400
|
+
path.serialize()
|
|
401
|
+
for path in list_agent_contact_paths(
|
|
402
|
+
tab_id=self.config.tab_id,
|
|
403
|
+
node_id=self.uuid,
|
|
404
|
+
)
|
|
405
|
+
]
|
|
406
|
+
|
|
385
407
|
for contact_id in self.get_contact_ids_snapshot():
|
|
386
408
|
node = registry.get(contact_id)
|
|
387
409
|
if node is None:
|
|
@@ -504,7 +526,7 @@ class Agent:
|
|
|
504
526
|
if self.state not in {AgentState.RUNNING, AgentState.SLEEPING}:
|
|
505
527
|
return False
|
|
506
528
|
if not self._command_interrupt_lock.acquire(timeout=timeout):
|
|
507
|
-
raise TimeoutError("
|
|
529
|
+
raise TimeoutError("Current chat did not pause for the command in time")
|
|
508
530
|
|
|
509
531
|
self._pause_after_interrupt_requested.set()
|
|
510
532
|
self._paused_for_command.clear()
|
|
@@ -517,7 +539,7 @@ class Agent:
|
|
|
517
539
|
self._command_interrupt_lock.release()
|
|
518
540
|
return False
|
|
519
541
|
if not self._paused_for_command.wait(timeout=timeout):
|
|
520
|
-
raise TimeoutError("
|
|
542
|
+
raise TimeoutError("Current chat did not pause after interrupt")
|
|
521
543
|
return True
|
|
522
544
|
except Exception:
|
|
523
545
|
self._pause_after_interrupt_requested.clear()
|
|
@@ -532,7 +554,10 @@ class Agent:
|
|
|
532
554
|
|
|
533
555
|
def clear_chat_history(self, *, interrupt_timeout: float = 5.0) -> None:
|
|
534
556
|
if self.node_type != NodeType.ASSISTANT:
|
|
535
|
-
|
|
557
|
+
from flowent.graph_service import is_tab_leader
|
|
558
|
+
|
|
559
|
+
if not is_tab_leader(node_id=self.uuid, tab_id=self.config.tab_id):
|
|
560
|
+
raise RuntimeError("Only Assistant or workflow chats can be cleared")
|
|
536
561
|
|
|
537
562
|
paused_for_command = self._pause_for_command_execution(
|
|
538
563
|
timeout=interrupt_timeout
|
|
@@ -543,9 +568,7 @@ class Agent:
|
|
|
543
568
|
self._pending_runtime_notices.clear()
|
|
544
569
|
with self._history_lock:
|
|
545
570
|
self.history = [
|
|
546
|
-
entry
|
|
547
|
-
for entry in self.history
|
|
548
|
-
if isinstance(entry, (SystemEntry, StateEntry))
|
|
571
|
+
entry for entry in self.history if isinstance(entry, SystemEntry)
|
|
549
572
|
]
|
|
550
573
|
self._pending_input_turn = False
|
|
551
574
|
self._turn_started_with_pending_input = False
|
|
@@ -556,7 +579,11 @@ class Agent:
|
|
|
556
579
|
Event(
|
|
557
580
|
type=EventType.HISTORY_CLEARED,
|
|
558
581
|
agent_id=self.uuid,
|
|
559
|
-
data={
|
|
582
|
+
data={
|
|
583
|
+
"scope": "assistant_chat"
|
|
584
|
+
if self.node_type == NodeType.ASSISTANT
|
|
585
|
+
else "workflow_chat"
|
|
586
|
+
},
|
|
560
587
|
)
|
|
561
588
|
)
|
|
562
589
|
self._persist_workspace_node()
|
|
@@ -634,8 +661,7 @@ class Agent:
|
|
|
634
661
|
self.history = [
|
|
635
662
|
entry
|
|
636
663
|
for index, entry in enumerate(previous_history)
|
|
637
|
-
if index < anchor_index
|
|
638
|
-
or isinstance(entry, (SystemEntry, StateEntry))
|
|
664
|
+
if index < anchor_index or isinstance(entry, SystemEntry)
|
|
639
665
|
]
|
|
640
666
|
retried_message_id = str(_uuid.uuid4())
|
|
641
667
|
self.history.append(
|
|
@@ -785,8 +811,7 @@ class Agent:
|
|
|
785
811
|
self.history = [
|
|
786
812
|
entry
|
|
787
813
|
for index, entry in enumerate(previous_history)
|
|
788
|
-
if index < anchor_index
|
|
789
|
-
or isinstance(entry, (SystemEntry, StateEntry))
|
|
814
|
+
if index < anchor_index or isinstance(entry, SystemEntry)
|
|
790
815
|
]
|
|
791
816
|
retried_message_id = str(_uuid.uuid4())
|
|
792
817
|
self.history.append(
|
|
@@ -862,14 +887,20 @@ class Agent:
|
|
|
862
887
|
interrupt_timeout: float = 5.0,
|
|
863
888
|
) -> CommandResultEntry:
|
|
864
889
|
if self.node_type != NodeType.ASSISTANT:
|
|
865
|
-
|
|
890
|
+
from flowent.graph_service import is_tab_leader
|
|
891
|
+
|
|
892
|
+
if not is_tab_leader(node_id=self.uuid, tab_id=self.config.tab_id):
|
|
893
|
+
raise RuntimeError("Only Assistant or workflow chats can be compacted")
|
|
866
894
|
|
|
867
895
|
paused_for_command = self._pause_for_command_execution(
|
|
868
896
|
timeout=interrupt_timeout
|
|
869
897
|
)
|
|
870
898
|
try:
|
|
871
|
-
self.
|
|
872
|
-
|
|
899
|
+
self._run_compact_with_observability(
|
|
900
|
+
trigger_type="manual",
|
|
901
|
+
focus=focus,
|
|
902
|
+
)
|
|
903
|
+
content = "Compacted this chat for future replies."
|
|
873
904
|
if focus and focus.strip():
|
|
874
905
|
content += f"\n\nFocus: {focus.strip()}"
|
|
875
906
|
|
|
@@ -885,7 +916,7 @@ class Agent:
|
|
|
885
916
|
if paused_for_command:
|
|
886
917
|
self._resume_after_command_execution()
|
|
887
918
|
|
|
888
|
-
def
|
|
919
|
+
def execute_conversation_command(
|
|
889
920
|
self,
|
|
890
921
|
*,
|
|
891
922
|
command_name: str,
|
|
@@ -897,7 +928,7 @@ class Agent:
|
|
|
897
928
|
self.clear_chat_history(interrupt_timeout=interrupt_timeout)
|
|
898
929
|
entry = CommandResultEntry(
|
|
899
930
|
command_name=command_name,
|
|
900
|
-
content="Cleared the current
|
|
931
|
+
content="Cleared the current chat.",
|
|
901
932
|
include_in_context=False,
|
|
902
933
|
)
|
|
903
934
|
append_to_history = False
|
|
@@ -909,15 +940,28 @@ class Agent:
|
|
|
909
940
|
elif command_name == "/help":
|
|
910
941
|
entry = CommandResultEntry(
|
|
911
942
|
command_name=command_name,
|
|
912
|
-
content=
|
|
943
|
+
content=build_conversation_help_text(),
|
|
913
944
|
)
|
|
914
945
|
else:
|
|
915
|
-
raise RuntimeError(f"Unsupported
|
|
946
|
+
raise RuntimeError(f"Unsupported conversation command: {command_name}")
|
|
916
947
|
|
|
917
948
|
if append_to_history:
|
|
918
949
|
self._append_history(entry)
|
|
919
950
|
return entry
|
|
920
951
|
|
|
952
|
+
def execute_assistant_command(
|
|
953
|
+
self,
|
|
954
|
+
*,
|
|
955
|
+
command_name: str,
|
|
956
|
+
argument: str = "",
|
|
957
|
+
interrupt_timeout: float = 5.0,
|
|
958
|
+
) -> CommandResultEntry:
|
|
959
|
+
return self.execute_conversation_command(
|
|
960
|
+
command_name=command_name,
|
|
961
|
+
argument=argument,
|
|
962
|
+
interrupt_timeout=interrupt_timeout,
|
|
963
|
+
)
|
|
964
|
+
|
|
921
965
|
def _run(self) -> None:
|
|
922
966
|
with logger.contextualize(
|
|
923
967
|
agent_id=self.uuid[:8],
|
|
@@ -1237,11 +1281,20 @@ class Agent:
|
|
|
1237
1281
|
has_todos: bool,
|
|
1238
1282
|
pending_agent_dispatches: list[str],
|
|
1239
1283
|
) -> dict[str, str]:
|
|
1284
|
+
if self._is_entry_level_sender():
|
|
1285
|
+
send_lines = [
|
|
1286
|
+
"- To send a formal message to another node, use `send` with a single `target` and ordered `parts`.",
|
|
1287
|
+
"- Use `contacts` to inspect the node ids and names you can currently message directly.",
|
|
1288
|
+
]
|
|
1289
|
+
else:
|
|
1290
|
+
send_lines = [
|
|
1291
|
+
"- To send through a workflow path, use `send` with one target, source output port, target input port, and a value matching that port type.",
|
|
1292
|
+
"- Use `contacts` to inspect the current output-port paths you can send through.",
|
|
1293
|
+
]
|
|
1240
1294
|
lines = [
|
|
1241
1295
|
"Runtime post prompt:",
|
|
1242
1296
|
"- Plain content is never delivered to other agents.",
|
|
1243
|
-
|
|
1244
|
-
"- Use `contacts` to inspect the node ids and names you can currently message directly.",
|
|
1297
|
+
*send_lines,
|
|
1245
1298
|
"- `@target:` or any other `@name:` text inside normal content is just text. It does not send anything.",
|
|
1246
1299
|
]
|
|
1247
1300
|
if pending_agent_dispatches:
|
|
@@ -1416,6 +1469,10 @@ class Agent:
|
|
|
1416
1469
|
),
|
|
1417
1470
|
)
|
|
1418
1471
|
|
|
1472
|
+
@staticmethod
|
|
1473
|
+
def _parts_value(parts: list[TextPart | ImagePart]) -> list[dict[str, Any]]:
|
|
1474
|
+
return [part.serialize() for part in parts]
|
|
1475
|
+
|
|
1419
1476
|
def _record_text_output(
|
|
1420
1477
|
self,
|
|
1421
1478
|
content: str,
|
|
@@ -1526,6 +1583,68 @@ class Agent:
|
|
|
1526
1583
|
raise ValueError(f"Send failed: target `{target_ref}` is not in contacts.")
|
|
1527
1584
|
return target
|
|
1528
1585
|
|
|
1586
|
+
def _is_entry_level_sender(self) -> bool:
|
|
1587
|
+
if self.node_type == NodeType.ASSISTANT:
|
|
1588
|
+
return True
|
|
1589
|
+
if self.config.tab_id is None:
|
|
1590
|
+
return False
|
|
1591
|
+
from flowent.graph_service import is_tab_leader
|
|
1592
|
+
|
|
1593
|
+
return is_tab_leader(node_id=self.uuid, tab_id=self.config.tab_id)
|
|
1594
|
+
|
|
1595
|
+
def _resolve_port_send_path(
|
|
1596
|
+
self,
|
|
1597
|
+
*,
|
|
1598
|
+
target_ref: str,
|
|
1599
|
+
from_output_port_key: str,
|
|
1600
|
+
to_input_port_key: str,
|
|
1601
|
+
):
|
|
1602
|
+
if self.config.tab_id is None:
|
|
1603
|
+
raise ValueError("Send failed: node is not part of a workflow.")
|
|
1604
|
+
from flowent.graph_service import (
|
|
1605
|
+
resolve_agent_contact_path,
|
|
1606
|
+
resolve_workflow_node_ref,
|
|
1607
|
+
)
|
|
1608
|
+
|
|
1609
|
+
target_id = resolve_workflow_node_ref(
|
|
1610
|
+
tab_id=self.config.tab_id,
|
|
1611
|
+
node_ref=target_ref,
|
|
1612
|
+
)
|
|
1613
|
+
if target_id is None:
|
|
1614
|
+
raise ValueError(f"Send failed: target `{target_ref}` was not found.")
|
|
1615
|
+
path = resolve_agent_contact_path(
|
|
1616
|
+
tab_id=self.config.tab_id,
|
|
1617
|
+
source_node_id=self.uuid,
|
|
1618
|
+
target_node_id=target_id,
|
|
1619
|
+
from_output_port_key=from_output_port_key,
|
|
1620
|
+
to_input_port_key=to_input_port_key,
|
|
1621
|
+
)
|
|
1622
|
+
if path is None:
|
|
1623
|
+
raise ValueError(
|
|
1624
|
+
f"Send failed: target `{target_ref}` is not connected from `{from_output_port_key}` to `{to_input_port_key}`."
|
|
1625
|
+
)
|
|
1626
|
+
return path
|
|
1627
|
+
|
|
1628
|
+
def _ensure_can_dispatch_to_contact(self, target: Agent) -> None:
|
|
1629
|
+
if self.config.tab_id is None:
|
|
1630
|
+
return
|
|
1631
|
+
|
|
1632
|
+
from flowent.graph_service import is_tab_leader
|
|
1633
|
+
from flowent.workspace_store import workspace_store
|
|
1634
|
+
|
|
1635
|
+
if not is_tab_leader(node_id=self.uuid, tab_id=self.config.tab_id):
|
|
1636
|
+
return
|
|
1637
|
+
if target.node_type != NodeType.AGENT:
|
|
1638
|
+
return
|
|
1639
|
+
if is_tab_leader(node_id=target.uuid, tab_id=target.config.tab_id):
|
|
1640
|
+
return
|
|
1641
|
+
|
|
1642
|
+
tab = workspace_store.get_tab(self.config.tab_id)
|
|
1643
|
+
if tab is not None and tab.activation_state != WorkflowActivationState.ACTIVE:
|
|
1644
|
+
raise ValueError(
|
|
1645
|
+
"Activate this workflow before sending work to agent nodes."
|
|
1646
|
+
)
|
|
1647
|
+
|
|
1529
1648
|
def supports_input_image(self) -> bool:
|
|
1530
1649
|
_, model_info = self._get_effective_model_info()
|
|
1531
1650
|
if model_info is None:
|
|
@@ -1543,9 +1662,20 @@ class Agent:
|
|
|
1543
1662
|
*,
|
|
1544
1663
|
target_ref: str,
|
|
1545
1664
|
raw_parts: Any,
|
|
1665
|
+
from_output_port_key: str | None = None,
|
|
1666
|
+
to_input_port_key: str | None = None,
|
|
1667
|
+
raw_value: Any | None = None,
|
|
1546
1668
|
) -> str:
|
|
1669
|
+
if not self._is_entry_level_sender():
|
|
1670
|
+
return self.send_port_value(
|
|
1671
|
+
target_ref=target_ref,
|
|
1672
|
+
from_output_port_key=from_output_port_key,
|
|
1673
|
+
to_input_port_key=to_input_port_key,
|
|
1674
|
+
raw_value=raw_value if raw_value is not None else raw_parts,
|
|
1675
|
+
)
|
|
1547
1676
|
parts = parse_content_parts_payload(raw_parts)
|
|
1548
1677
|
target = self._resolve_contact_target(target_ref)
|
|
1678
|
+
self._ensure_can_dispatch_to_contact(target)
|
|
1549
1679
|
if has_image_parts(parts) and not target.supports_input_image():
|
|
1550
1680
|
raise ValueError(
|
|
1551
1681
|
f"Send failed: target `{target_ref}` does not support `input_image`."
|
|
@@ -1566,6 +1696,78 @@ class Agent:
|
|
|
1566
1696
|
)
|
|
1567
1697
|
return json.dumps({"status": "sent", "target_id": target.uuid})
|
|
1568
1698
|
|
|
1699
|
+
def send_port_value(
|
|
1700
|
+
self,
|
|
1701
|
+
*,
|
|
1702
|
+
target_ref: str,
|
|
1703
|
+
from_output_port_key: str | None,
|
|
1704
|
+
to_input_port_key: str | None,
|
|
1705
|
+
raw_value: Any,
|
|
1706
|
+
) -> str:
|
|
1707
|
+
if self._is_entry_level_sender():
|
|
1708
|
+
raise ValueError("Send failed: entry contacts use target and parts.")
|
|
1709
|
+
if (
|
|
1710
|
+
not isinstance(from_output_port_key, str)
|
|
1711
|
+
or not from_output_port_key.strip()
|
|
1712
|
+
):
|
|
1713
|
+
raise ValueError("send.from_output_port_key must be a non-empty string")
|
|
1714
|
+
if not isinstance(to_input_port_key, str) or not to_input_port_key.strip():
|
|
1715
|
+
raise ValueError("send.to_input_port_key must be a non-empty string")
|
|
1716
|
+
|
|
1717
|
+
path = self._resolve_port_send_path(
|
|
1718
|
+
target_ref=target_ref,
|
|
1719
|
+
from_output_port_key=from_output_port_key.strip(),
|
|
1720
|
+
to_input_port_key=to_input_port_key.strip(),
|
|
1721
|
+
)
|
|
1722
|
+
value = raw_value
|
|
1723
|
+
if path.port_type == "parts":
|
|
1724
|
+
parts = parse_content_parts_payload(raw_value)
|
|
1725
|
+
value = self._parts_value(parts)
|
|
1726
|
+
if path.target_node_type == WorkflowNodeKind.AGENT.value:
|
|
1727
|
+
target = self._resolve_contact_target(path.target_id)
|
|
1728
|
+
if has_image_parts(parts) and not target.supports_input_image():
|
|
1729
|
+
raise ValueError(
|
|
1730
|
+
f"Send failed: target `{target_ref}` does not support `input_image`."
|
|
1731
|
+
)
|
|
1732
|
+
for part in parts:
|
|
1733
|
+
asset_id = getattr(part, "asset_id", None)
|
|
1734
|
+
if isinstance(asset_id, str):
|
|
1735
|
+
require_image_asset(asset_id)
|
|
1736
|
+
|
|
1737
|
+
message_id = str(_uuid.uuid4())
|
|
1738
|
+
from flowent.graph_service import dispatch_port_value
|
|
1739
|
+
|
|
1740
|
+
payload, error = dispatch_port_value(
|
|
1741
|
+
tab_id=self.config.tab_id or "",
|
|
1742
|
+
source_node_id=self.uuid,
|
|
1743
|
+
source_output_port_key=path.from_output_port_key,
|
|
1744
|
+
target_node_id=path.target_id,
|
|
1745
|
+
target_input_port_key=path.to_input_port_key,
|
|
1746
|
+
value=value,
|
|
1747
|
+
source_is_agent_send=True,
|
|
1748
|
+
message_id=message_id,
|
|
1749
|
+
)
|
|
1750
|
+
if error is not None or payload is None:
|
|
1751
|
+
raise ValueError(f"Send failed: {error or 'path unavailable'}.")
|
|
1752
|
+
|
|
1753
|
+
self._mark_turn_progress()
|
|
1754
|
+
sent_parts = (
|
|
1755
|
+
parse_content_parts_payload(value) if path.port_type == "parts" else []
|
|
1756
|
+
)
|
|
1757
|
+
value_summary = str(payload.get("value_summary", ""))
|
|
1758
|
+
self._append_history(
|
|
1759
|
+
SentMessage(
|
|
1760
|
+
to_id=path.target_id,
|
|
1761
|
+
parts=sent_parts,
|
|
1762
|
+
content=value_summary if path.port_type != "parts" else "",
|
|
1763
|
+
message_id=message_id,
|
|
1764
|
+
from_output_port_key=path.from_output_port_key,
|
|
1765
|
+
to_input_port_key=path.to_input_port_key,
|
|
1766
|
+
value_summary=value_summary,
|
|
1767
|
+
)
|
|
1768
|
+
)
|
|
1769
|
+
return json.dumps(payload)
|
|
1770
|
+
|
|
1569
1771
|
def _mark_turn_progress(self) -> None:
|
|
1570
1772
|
self._turn_made_progress = True
|
|
1571
1773
|
self._pending_input_turn = False
|
|
@@ -1662,6 +1864,7 @@ class Agent:
|
|
|
1662
1864
|
retry_limit = self._get_llm_max_retries()
|
|
1663
1865
|
retry_count = 0
|
|
1664
1866
|
started_at = _time.time()
|
|
1867
|
+
self._raise_if_llm_network_disallowed()
|
|
1665
1868
|
|
|
1666
1869
|
while True:
|
|
1667
1870
|
stream_state = StreamingContentState()
|
|
@@ -1682,7 +1885,7 @@ class Agent:
|
|
|
1682
1885
|
finally:
|
|
1683
1886
|
self.set_interrupt_callback(None)
|
|
1684
1887
|
self._raise_if_interrupt_requested()
|
|
1685
|
-
self.
|
|
1888
|
+
self._record_request_observability(
|
|
1686
1889
|
started_at=started_at,
|
|
1687
1890
|
ended_at=_time.time(),
|
|
1688
1891
|
retry_count=retry_count,
|
|
@@ -1705,7 +1908,7 @@ class Agent:
|
|
|
1705
1908
|
elif retry_policy == "unlimited":
|
|
1706
1909
|
should_retry = True
|
|
1707
1910
|
if not should_retry:
|
|
1708
|
-
self.
|
|
1911
|
+
self._record_request_observability(
|
|
1709
1912
|
started_at=started_at,
|
|
1710
1913
|
ended_at=_time.time(),
|
|
1711
1914
|
retry_count=retry_count,
|
|
@@ -1726,7 +1929,7 @@ class Agent:
|
|
|
1726
1929
|
)
|
|
1727
1930
|
self._wait_for_llm_retry_delay(delay_seconds)
|
|
1728
1931
|
except Exception as exc:
|
|
1729
|
-
self.
|
|
1932
|
+
self._record_request_observability(
|
|
1730
1933
|
started_at=started_at,
|
|
1731
1934
|
ended_at=_time.time(),
|
|
1732
1935
|
retry_count=retry_count,
|
|
@@ -1767,6 +1970,8 @@ class Agent:
|
|
|
1767
1970
|
if not context_messages:
|
|
1768
1971
|
return "- No prior execution context was available to compact."
|
|
1769
1972
|
|
|
1973
|
+
self._raise_if_llm_network_disallowed()
|
|
1974
|
+
|
|
1770
1975
|
focus_text = focus.strip() if focus else ""
|
|
1771
1976
|
request_lines = [
|
|
1772
1977
|
"Compact this agent execution context into a durable markdown summary.",
|
|
@@ -1889,12 +2094,20 @@ class Agent:
|
|
|
1889
2094
|
|
|
1890
2095
|
elif isinstance(entry, ReceivedMessage):
|
|
1891
2096
|
self._flush_tool_calls(messages, pending_tool_calls)
|
|
2097
|
+
if entry.from_output_port_key and entry.to_input_port_key:
|
|
2098
|
+
prefix = (
|
|
2099
|
+
f'<message from="{entry.from_id}" '
|
|
2100
|
+
f'from_output="{entry.from_output_port_key}" '
|
|
2101
|
+
f'to_input="{entry.to_input_port_key}">'
|
|
2102
|
+
)
|
|
2103
|
+
else:
|
|
2104
|
+
prefix = f'<message from="{entry.from_id}">'
|
|
1892
2105
|
messages.append(
|
|
1893
2106
|
{
|
|
1894
2107
|
"role": "user",
|
|
1895
2108
|
"content": self._wrap_context_parts(
|
|
1896
2109
|
entry.parts,
|
|
1897
|
-
prefix=
|
|
2110
|
+
prefix=prefix,
|
|
1898
2111
|
suffix="</message>",
|
|
1899
2112
|
),
|
|
1900
2113
|
}
|
|
@@ -1911,12 +2124,20 @@ class Agent:
|
|
|
1911
2124
|
|
|
1912
2125
|
elif isinstance(entry, SentMessage):
|
|
1913
2126
|
self._flush_tool_calls(messages, pending_tool_calls)
|
|
2127
|
+
if entry.from_output_port_key and entry.to_input_port_key:
|
|
2128
|
+
prefix = (
|
|
2129
|
+
f'<message to="{entry.to_id}" '
|
|
2130
|
+
f'from_output="{entry.from_output_port_key}" '
|
|
2131
|
+
f'to_input="{entry.to_input_port_key}">'
|
|
2132
|
+
)
|
|
2133
|
+
else:
|
|
2134
|
+
prefix = f'<message to="{entry.to_id}">'
|
|
1914
2135
|
messages.append(
|
|
1915
2136
|
{
|
|
1916
2137
|
"role": "assistant",
|
|
1917
2138
|
"content": self._wrap_context_parts(
|
|
1918
2139
|
entry.parts,
|
|
1919
|
-
prefix=
|
|
2140
|
+
prefix=prefix,
|
|
1920
2141
|
suffix="</message>",
|
|
1921
2142
|
),
|
|
1922
2143
|
}
|
|
@@ -1967,6 +2188,18 @@ class Agent:
|
|
|
1967
2188
|
self._flush_tool_calls(messages, pending_tool_calls)
|
|
1968
2189
|
messages.append(self._build_runtime_system_message(entry.content))
|
|
1969
2190
|
|
|
2191
|
+
elif isinstance(entry, PortInboundEntry):
|
|
2192
|
+
self._flush_tool_calls(messages, pending_tool_calls)
|
|
2193
|
+
messages.append(
|
|
2194
|
+
self._build_runtime_system_message(
|
|
2195
|
+
"Port input received: "
|
|
2196
|
+
f"from {entry.from_id}.{entry.from_output_port_key} "
|
|
2197
|
+
f"to {entry.to_input_port_key} "
|
|
2198
|
+
f"({entry.port_type})\n"
|
|
2199
|
+
f"{json.dumps(entry.value, ensure_ascii=False, sort_keys=True)}"
|
|
2200
|
+
)
|
|
2201
|
+
)
|
|
2202
|
+
|
|
1970
2203
|
self._flush_tool_calls(messages, pending_tool_calls)
|
|
1971
2204
|
return messages
|
|
1972
2205
|
|
|
@@ -2047,7 +2280,7 @@ class Agent:
|
|
|
2047
2280
|
runtime_tail_messages=list(prepared_context.runtime_tail_messages),
|
|
2048
2281
|
)
|
|
2049
2282
|
|
|
2050
|
-
def
|
|
2283
|
+
def _get_observability_node_label(self) -> str:
|
|
2051
2284
|
if self.config.name:
|
|
2052
2285
|
return self.config.name
|
|
2053
2286
|
if self.config.role_name:
|
|
@@ -2060,7 +2293,7 @@ class Agent:
|
|
|
2060
2293
|
return "Leader"
|
|
2061
2294
|
return "Agent"
|
|
2062
2295
|
|
|
2063
|
-
def
|
|
2296
|
+
def _get_observability_tab_title(self) -> str | None:
|
|
2064
2297
|
if not self.config.tab_id:
|
|
2065
2298
|
return None
|
|
2066
2299
|
from flowent.workspace_store import workspace_store
|
|
@@ -2096,6 +2329,7 @@ class Agent:
|
|
|
2096
2329
|
provider_id=None,
|
|
2097
2330
|
provider_name=None,
|
|
2098
2331
|
provider_type=None,
|
|
2332
|
+
provider_base_url=None,
|
|
2099
2333
|
model=None,
|
|
2100
2334
|
model_info=None,
|
|
2101
2335
|
)
|
|
@@ -2105,6 +2339,7 @@ class Agent:
|
|
|
2105
2339
|
provider_id=None,
|
|
2106
2340
|
provider_name=None,
|
|
2107
2341
|
provider_type=None,
|
|
2342
|
+
provider_base_url=None,
|
|
2108
2343
|
model=None,
|
|
2109
2344
|
model_info=None,
|
|
2110
2345
|
)
|
|
@@ -2112,6 +2347,7 @@ class Agent:
|
|
|
2112
2347
|
provider_id=provider.id,
|
|
2113
2348
|
provider_name=provider.name,
|
|
2114
2349
|
provider_type=provider.type,
|
|
2350
|
+
provider_base_url=provider.base_url,
|
|
2115
2351
|
model=model_id,
|
|
2116
2352
|
model_info=resolve_model_info(
|
|
2117
2353
|
provider=provider,
|
|
@@ -2130,11 +2366,34 @@ class Agent:
|
|
|
2130
2366
|
),
|
|
2131
2367
|
)
|
|
2132
2368
|
|
|
2369
|
+
def _raise_if_llm_network_disallowed(self) -> None:
|
|
2370
|
+
from flowent.graph_service import resolve_effective_permissions_for_agent
|
|
2371
|
+
|
|
2372
|
+
allow_network, _ = resolve_effective_permissions_for_agent(self)
|
|
2373
|
+
if allow_network:
|
|
2374
|
+
return
|
|
2375
|
+
source = self._get_effective_model_source()
|
|
2376
|
+
if not self._model_source_requires_network(source):
|
|
2377
|
+
return
|
|
2378
|
+
raise LLMProviderError(
|
|
2379
|
+
"Network access is disabled for this workflow",
|
|
2380
|
+
transient=False,
|
|
2381
|
+
)
|
|
2382
|
+
|
|
2383
|
+
@staticmethod
|
|
2384
|
+
def _model_source_requires_network(source: ResolvedModelSource) -> bool:
|
|
2385
|
+
base_url = source.provider_base_url
|
|
2386
|
+
if not base_url:
|
|
2387
|
+
return False
|
|
2388
|
+
parsed = urlparse(base_url)
|
|
2389
|
+
host = (parsed.hostname or "").lower()
|
|
2390
|
+
return host not in {"localhost", "127.0.0.1", "::1"}
|
|
2391
|
+
|
|
2133
2392
|
def _get_effective_model_info(self) -> tuple[str | None, ModelInfo | None]:
|
|
2134
2393
|
resolved_source = self._get_effective_model_source()
|
|
2135
2394
|
return resolved_source.provider_type, resolved_source.model_info
|
|
2136
2395
|
|
|
2137
|
-
def
|
|
2396
|
+
def _record_request_observability(
|
|
2138
2397
|
self,
|
|
2139
2398
|
*,
|
|
2140
2399
|
started_at: float,
|
|
@@ -2145,16 +2404,19 @@ class Agent:
|
|
|
2145
2404
|
raw_usage: dict[str, Any] | None = None,
|
|
2146
2405
|
error_summary: str | None = None,
|
|
2147
2406
|
) -> None:
|
|
2148
|
-
from flowent.
|
|
2407
|
+
from flowent.observability_service import (
|
|
2408
|
+
RequestRecordInput,
|
|
2409
|
+
observability_store,
|
|
2410
|
+
)
|
|
2149
2411
|
|
|
2150
2412
|
resolved_source = self._get_effective_model_source()
|
|
2151
|
-
|
|
2413
|
+
observability_store.record_request(
|
|
2152
2414
|
RequestRecordInput(
|
|
2153
2415
|
node_id=self.uuid,
|
|
2154
|
-
node_label=self.
|
|
2416
|
+
node_label=self._get_observability_node_label(),
|
|
2155
2417
|
role_name=self.config.role_name,
|
|
2156
2418
|
tab_id=self.config.tab_id,
|
|
2157
|
-
tab_title=self.
|
|
2419
|
+
tab_title=self._get_observability_tab_title(),
|
|
2158
2420
|
provider_id=resolved_source.provider_id,
|
|
2159
2421
|
provider_name=resolved_source.provider_name,
|
|
2160
2422
|
provider_type=resolved_source.provider_type,
|
|
@@ -2169,26 +2431,29 @@ class Agent:
|
|
|
2169
2431
|
)
|
|
2170
2432
|
)
|
|
2171
2433
|
|
|
2172
|
-
def
|
|
2434
|
+
def _run_compact_with_observability(
|
|
2173
2435
|
self,
|
|
2174
2436
|
*,
|
|
2175
2437
|
trigger_type: str,
|
|
2176
2438
|
focus: str | None = None,
|
|
2177
2439
|
) -> str:
|
|
2178
|
-
from flowent.
|
|
2440
|
+
from flowent.observability_service import (
|
|
2441
|
+
CompactRecordInput,
|
|
2442
|
+
observability_store,
|
|
2443
|
+
)
|
|
2179
2444
|
|
|
2180
2445
|
started_at = _time.time()
|
|
2181
2446
|
resolved_source = self._get_effective_model_source()
|
|
2182
2447
|
try:
|
|
2183
2448
|
result = self._compact_execution_context(focus=focus)
|
|
2184
2449
|
except Exception as exc:
|
|
2185
|
-
|
|
2450
|
+
observability_store.record_compact(
|
|
2186
2451
|
CompactRecordInput(
|
|
2187
2452
|
node_id=self.uuid,
|
|
2188
|
-
node_label=self.
|
|
2453
|
+
node_label=self._get_observability_node_label(),
|
|
2189
2454
|
role_name=self.config.role_name,
|
|
2190
2455
|
tab_id=self.config.tab_id,
|
|
2191
|
-
tab_title=self.
|
|
2456
|
+
tab_title=self._get_observability_tab_title(),
|
|
2192
2457
|
provider_id=resolved_source.provider_id,
|
|
2193
2458
|
provider_name=resolved_source.provider_name,
|
|
2194
2459
|
provider_type=resolved_source.provider_type,
|
|
@@ -2201,13 +2466,13 @@ class Agent:
|
|
|
2201
2466
|
)
|
|
2202
2467
|
)
|
|
2203
2468
|
raise
|
|
2204
|
-
|
|
2469
|
+
observability_store.record_compact(
|
|
2205
2470
|
CompactRecordInput(
|
|
2206
2471
|
node_id=self.uuid,
|
|
2207
|
-
node_label=self.
|
|
2472
|
+
node_label=self._get_observability_node_label(),
|
|
2208
2473
|
role_name=self.config.role_name,
|
|
2209
2474
|
tab_id=self.config.tab_id,
|
|
2210
|
-
tab_title=self.
|
|
2475
|
+
tab_title=self._get_observability_tab_title(),
|
|
2211
2476
|
provider_id=resolved_source.provider_id,
|
|
2212
2477
|
provider_name=resolved_source.provider_name,
|
|
2213
2478
|
provider_type=resolved_source.provider_type,
|
|
@@ -2311,7 +2576,7 @@ class Agent:
|
|
|
2311
2576
|
preflight.context_window_tokens,
|
|
2312
2577
|
)
|
|
2313
2578
|
try:
|
|
2314
|
-
self.
|
|
2579
|
+
self._run_compact_with_observability(trigger_type="auto")
|
|
2315
2580
|
except Exception as exc:
|
|
2316
2581
|
if (
|
|
2317
2582
|
preflight.safe_input_tokens is not None
|
|
@@ -2385,6 +2650,7 @@ class Agent:
|
|
|
2385
2650
|
from_id = message.get("from", "")
|
|
2386
2651
|
message_id = message.get("message_id")
|
|
2387
2652
|
history_recorded = bool(message.get("history_recorded", False))
|
|
2653
|
+
port_inbound_recorded = bool(message.get("port_inbound_recorded", False))
|
|
2388
2654
|
if (
|
|
2389
2655
|
not isinstance(content, str)
|
|
2390
2656
|
or not isinstance(from_id, str)
|
|
@@ -2405,6 +2671,23 @@ class Agent:
|
|
|
2405
2671
|
message_id=message_id,
|
|
2406
2672
|
),
|
|
2407
2673
|
)
|
|
2674
|
+
elif not port_inbound_recorded and message.get("port_type") in {
|
|
2675
|
+
"string",
|
|
2676
|
+
"json",
|
|
2677
|
+
}:
|
|
2678
|
+
value = message.get("value")
|
|
2679
|
+
self._append_history(
|
|
2680
|
+
PortInboundEntry(
|
|
2681
|
+
from_id=from_id,
|
|
2682
|
+
from_output_port_key=str(
|
|
2683
|
+
message.get("from_output_port_key", "")
|
|
2684
|
+
),
|
|
2685
|
+
to_input_port_key=str(message.get("to_input_port_key", "")),
|
|
2686
|
+
port_type=str(message.get("port_type", "")),
|
|
2687
|
+
value=value,
|
|
2688
|
+
value_summary=str(message.get("value_summary", "")),
|
|
2689
|
+
)
|
|
2690
|
+
)
|
|
2408
2691
|
|
|
2409
2692
|
def _wait_for_input(self) -> None:
|
|
2410
2693
|
signal = self._wait_for_wakeup()
|
|
@@ -2422,6 +2705,9 @@ class Agent:
|
|
|
2422
2705
|
from_id = message.get("from")
|
|
2423
2706
|
message_id = message.get("message_id")
|
|
2424
2707
|
history_recorded = bool(message.get("history_recorded", False))
|
|
2708
|
+
port_inbound_recorded = bool(
|
|
2709
|
+
message.get("port_inbound_recorded", False)
|
|
2710
|
+
)
|
|
2425
2711
|
if (
|
|
2426
2712
|
isinstance(content, str)
|
|
2427
2713
|
and isinstance(from_id, str)
|
|
@@ -2436,6 +2722,24 @@ class Agent:
|
|
|
2436
2722
|
message_id=message_id,
|
|
2437
2723
|
)
|
|
2438
2724
|
)
|
|
2725
|
+
elif (
|
|
2726
|
+
isinstance(from_id, str)
|
|
2727
|
+
and history_recorded
|
|
2728
|
+
and not port_inbound_recorded
|
|
2729
|
+
and message.get("port_type") in {"string", "json"}
|
|
2730
|
+
):
|
|
2731
|
+
self._append_history(
|
|
2732
|
+
PortInboundEntry(
|
|
2733
|
+
from_id=from_id,
|
|
2734
|
+
from_output_port_key=str(
|
|
2735
|
+
message.get("from_output_port_key", "")
|
|
2736
|
+
),
|
|
2737
|
+
to_input_port_key=str(message.get("to_input_port_key", "")),
|
|
2738
|
+
port_type=str(message.get("port_type", "")),
|
|
2739
|
+
value=message.get("value"),
|
|
2740
|
+
value_summary=str(message.get("value_summary", "")),
|
|
2741
|
+
)
|
|
2742
|
+
)
|
|
2439
2743
|
|
|
2440
2744
|
if signal.reason != "termination":
|
|
2441
2745
|
self.set_state(
|
|
@@ -2657,6 +2961,18 @@ class Agent:
|
|
|
2657
2961
|
"parts": [part.serialize() for part in msg.parts],
|
|
2658
2962
|
"history_recorded": msg.history_recorded,
|
|
2659
2963
|
}
|
|
2964
|
+
if msg.from_output_port_key is not None:
|
|
2965
|
+
payload["from_output_port_key"] = msg.from_output_port_key
|
|
2966
|
+
if msg.to_input_port_key is not None:
|
|
2967
|
+
payload["to_input_port_key"] = msg.to_input_port_key
|
|
2968
|
+
if msg.port_type is not None:
|
|
2969
|
+
payload["port_type"] = msg.port_type
|
|
2970
|
+
if msg.value is not None:
|
|
2971
|
+
payload["value"] = msg.value
|
|
2972
|
+
if msg.value_summary is not None:
|
|
2973
|
+
payload["value_summary"] = msg.value_summary
|
|
2974
|
+
if msg.port_inbound_recorded:
|
|
2975
|
+
payload["port_inbound_recorded"] = msg.port_inbound_recorded
|
|
2660
2976
|
if msg.message_id is not None:
|
|
2661
2977
|
payload["message_id"] = msg.message_id
|
|
2662
2978
|
self._wake_queue.put(
|
|
@@ -2688,10 +3004,6 @@ class Agent:
|
|
|
2688
3004
|
self._idle_started_by_tool_call_id = None
|
|
2689
3005
|
self._idle_state_event.clear()
|
|
2690
3006
|
if old != state or force_emit:
|
|
2691
|
-
if old != state:
|
|
2692
|
-
self._append_history(
|
|
2693
|
-
StateEntry(state=state.value, reason=reason),
|
|
2694
|
-
)
|
|
2695
3007
|
self._log.debug(
|
|
2696
3008
|
"State: {} -> {}{}",
|
|
2697
3009
|
old.value,
|