talkops-opscloud 0.3.0b8__tar.gz → 0.3.0b9__tar.gz
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.
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/PKG-INFO +1 -1
- talkops_opscloud-0.3.0b9/docs/issues/MCP_PERFORMANCE_AND_CONTEXT_EXPLOSION_AUDIT.md +275 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/pyproject.toml +1 -1
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/mcp/discovery.py +14 -4
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/mcp/preload.py +51 -30
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/mcp/session_manager.py +49 -28
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/tool_filter.py +35 -1
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/plugins/adapters/mcp.py +1 -1
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/plugins/models.py +10 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_mcp.py +17 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_tool_filter.py +72 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/uv.lock +1 -1
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/.dockerignore +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/.env.example +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/.github/workflows/ci.yml +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/.github/workflows/release-pypi.yml +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/.gitignore +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/.python-version +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/LICENSE +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/README.md +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/assets/opscloud-banner.gif +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/docs/opscloud-docs/Configuration.md +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/docs/opscloud-docs/approval-mode.md +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/docs/opscloud-docs/cli-reference.md +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/docs/opscloud-docs/config.toml.md +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/docs/opscloud-docs/credentials.md +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/docs/opscloud-docs/goal-and-rubrics.md +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/docs/opscloud-docs/hooks.md +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/docs/opscloud-docs/mcp-tools.md +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/docs/opscloud-docs/memory-and-skills.md +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/docs/opscloud-docs/model-providers.md +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/docs/opscloud-docs/overview.md +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/docs/opscloud-docs/plugins.md +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/docs/opscloud-docs/quickstart.md +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/docs/opscloud-docs/remote-sandboxes.md +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/docs/opscloud-docs/subagents.md +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/scripts/install.sh +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/__main__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/_constants.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/_debug.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/_debug_buffer.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/_textual_patches.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/_version.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/agent/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/agent/config.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/agent/factory.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/approval_mode.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/backend/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/backend/composite.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/backend/local.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/backend/registry.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/backend/sandbox_config.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/backend/sandbox_factory.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/built_in_skills/remember/SKILL.md +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/built_in_skills/skill-creator/SKILL.md +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/built_in_skills/skill-creator/scripts/init_skill.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/built_in_skills/skill-creator/scripts/quick_validate.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/cli/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/cli/commands/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/cli/commands/auth.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/cli/commands/doctor.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/cli/commands/mcp.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/cli/commands/threads.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/cli/interactive.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/cli/main.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/cli/non_interactive.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/cli/server_manager.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/_base.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/_guards.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/_router.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/_types.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/core/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/core/auth.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/core/bug.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/core/clear.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/core/cloud.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/core/compact.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/core/config_cmd.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/core/context.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/core/cost.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/core/doctor.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/core/effort.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/core/exit_cmd.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/core/fast.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/core/help_cmd.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/core/mcp.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/core/model.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/core/permissions.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/core/plugins.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/core/pool.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/core/resume.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/core/skills.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/power/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/power/agents.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/power/btw.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/power/copy.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/power/goal.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/power/loop.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/power/memory.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/power/review.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/power/rubric.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/power/runtime.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/power/skill_creator.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/power/skill_invoke.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/power/tasks.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/power/trace.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/power/ui_toggles.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/power/version.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/commands/registry.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/config/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/config/adapters/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/config/adapters/postgres.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/config/adapters/sqlite.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/config/aws.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/config/cloud_profiles.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/config/env_vars.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/config/langsmith.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/config/manifest.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/config/metadata.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/config/paths.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/config/plugins.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/config/settings.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/config/store.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/config/store_factory.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/config/toml_config.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/editor.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/exceptions.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/file_ops.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/hooks/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/hooks/env.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/hooks/interrupt.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/hooks/models/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/hooks/models/adapters.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/hooks/models/config.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/hooks/models/domain.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/hooks/models/transport.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/hooks/tools.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/input.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/integrations/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/integrations/base.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/integrations/content_filter.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/integrations/event_bus.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/integrations/hooks.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/integrations/identity.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/integrations/notifications.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/integrations/stream_bridge.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/integrations/thread_store.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/json_types.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/mcp/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/mcp/config.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/mcp/mcp_info.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/mcp/middleware.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/mcp/raw_config.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/mcp/semantic_profiler.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/mcp/trust.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/memory/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/memory/branch.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/memory/guard.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/memory/onboarding.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/memory/registry.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/memory/store.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/_repository_bounds.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/ask_user.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/auto_mode.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/auto_mode_hitl.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/compaction.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/configurable_model.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/cost_tracking.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/glm_stall_recovery.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/goal_criteria.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/goal_state_notice.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/goal_tools.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/headless_mcp_guard.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/jev_model_router.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/local_context.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/mcp_context.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/mcp_middleware.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/memory_guard.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/model_retry.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/registry.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/reliable_rubric.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/resume_state.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/server_hooks.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/shell_allow_list.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/skills.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/subagent_artifacts.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/subagent_telemetry.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/subagents.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/unified_system_message.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/model/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/model/config.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/model/factory.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/model/pool.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/model/reasoning.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/offload.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/output.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/plugins/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/plugins/_json.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/plugins/adapters/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/plugins/adapters/agents.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/plugins/adapters/commands.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/plugins/adapters/skills.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/plugins/commands_cli.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/plugins/discovery.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/plugins/manifest.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/plugins/marketplace.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/plugins/project_plugins.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/plugins/protocol.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/plugins/store.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/plugins/substitution.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/project_utils.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/prompts/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/prompts/system.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/prompts/templates/system_prompt.md +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/prompts/types.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/rubrics/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/rubrics/evaluator.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/rubrics/evidence_extractor.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/rubrics/generator.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/rubrics/jev_compiler.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/rubrics/jev_grader.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/rubrics/middleware.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/schema/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/schema/interrupts.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/security/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/security/approval_mode.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/security/approval_mode_source.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/security/cli_ast_evaluator.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/security/jev_classifier.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/security/shell_safety.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/security/unicode_security.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/security/url_validation.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/server/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/server/_server_config.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/server/server.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/server/server_graph.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/skills/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/skills/commands.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/skills/invocation.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/skills/load.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/skills/loader.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/skills/merge.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/skills/registry.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/skills/sources.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/skills/trust.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/state/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/state/base.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/state/cloud_context.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/state/goal_channels.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/state/goal_state_limits.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/state/resume_state.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/state/service_context.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/state/session.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/state/state_migration.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/subagents/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/subagents/loader.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/subagents/subagents_parser.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/subagents/types.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/tools/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/tools/catalog.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/tools/display.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/tools/fetch_url.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/tools/goal_tools.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/tools/registry.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/tools/thread.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/tools/web_search.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/app.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/app.tcss +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/clipboard.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/command_registry.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/message_store.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/permission_store.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/preferences.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/prompt_manager.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/remote_client.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/textual_adapter.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/theme.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/ui_help.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/_ask_user_types.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/_copy_spans.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/_inline_prompt.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/_js_eval_display.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/_links.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/_tool_stream.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/agent_selector.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/approval.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/ask_user.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/auth.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/auth_manager.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/auto_mode_notice.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/autocomplete.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/chat_input.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/cloud_selector.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/config_manager.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/debug_console.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/devops_renderers.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/diff.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/effort_selector.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/goal_review.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/goal_status.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/infra_panel.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/install_confirm.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/loading.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/mcp_viewer.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/messages.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/model_selector.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/notification_center.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/notification_settings.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/operation_card.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/permissions_manager.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/plugin_manager.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/pool_selector.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/preamble.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/skills_viewer.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/status.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/subagent_panel.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/theme_selector.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/thread_selector.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/toast.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/tool_display.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/tool_renderers.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/tool_widgets.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/welcome.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/ui/widgets/welcome_popup.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/utils/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/utils/cost_estimation.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/utils/git.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/utils/logger.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/utils/session_stats.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/utils/startup_error.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/conftest.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/evals/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/evals/datasets/aws_cloud_ops.yaml +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/evals/evaluators/__init__.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/evals/evaluators/output_evaluator.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/evals/evaluators/trajectory_evaluator.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/evals/runners/test_eval_runner.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/integration_tests/test_agent_lifecycle.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/integration_tests/test_server_lifecycle.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_agent_factory.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_approval_widget.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_ask_user.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_ast_evaluator.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_backend.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_chat_input_responsiveness.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_checkpointer.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_claude_opus_reasoning.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_cli_enhancements.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_cli_rubric.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_cloud_command.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_commands.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_config.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_cost_tracking.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_default_marketplace.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_goal_criteria.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_goal_state_limits.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_goal_state_notice.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_grader_tools.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_hitl.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_integrations_and_hooks.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_interrupt_schemas.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_jev_model_router.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_jev_rubrics.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_local_context.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_logging.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_mcp_middleware.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_mcp_viewer_and_command.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_memory.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_memory_enhancements.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_middleware_stack.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_model_providers.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_model_retry.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_plugin_agent_skill_mcp_scoping.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_plugin_manager_search.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_plugins.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_pool_persistence.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_project_plugins.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_prompts_and_schema.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_reliable_rubric.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_reload_restart.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_resume_state.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_resumed_thread_latency_fix.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_rubrics.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_security.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_server.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_server_hooks.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_skill_creator.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_skills.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_state.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_subagents.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_tools.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_tui_app.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/tests/unit_tests/test_utils.py +0 -0
- {talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/uv-requirements.txt +0 -0
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# Engineering Issue & Architectural Audit: MCP Tool Schema Explosion & Extreme Latency in Plugin Skills Flow
|
|
2
|
+
|
|
3
|
+
**Issue ID:** `ISSUE-OPSCLOUD-MCP-001`
|
|
4
|
+
**Date:** October 8, 2026
|
|
5
|
+
**Severity:** **P0 — Blocker**
|
|
6
|
+
**Component:** Deep Agent Core / Subagent Flow / MCP Session Manager / Plugin Adapters
|
|
7
|
+
**Target Environments:** `src/opscloud` (OpsCloud Orchestrator) vs. `reference/dcode` (Reference Implementation)
|
|
8
|
+
**Authors:** AI Architecture & Core Systems Audit
|
|
9
|
+
**Audience:** Core Engineering Team, Agent Platform Engineers, DevOps & Infrastructure
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 1. Executive Summary & Impact Analysis
|
|
14
|
+
|
|
15
|
+
During testing of skill plugins equipped with MCP (Model Context Protocol) servers (e.g., `aws-containers@talkops-devops-plugins`), the Deep Agent experienced catastrophic response latency and token usage degradation:
|
|
16
|
+
|
|
17
|
+
* **OpsCloud Turn 1 Performance:** **315.79 seconds (5.22 minutes)** total turn duration, processing **211,300 input tokens** ($0.1531 cost) on a simple user command.
|
|
18
|
+
* **Reference `dcode` Performance:** **15.10s P50** across 4 turns (Turn 1: 0.73s, Turn 2: 25.39s [45.37K tokens], Turn 3: 15.09s [46.6K tokens], Turn 4: 40.15s [254.8K tokens]).
|
|
19
|
+
* **Discrepancy Ratio:** **~21x Latency Explosion**, **~4.7x Initial Prompt Context Explosion**.
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
┌───────────────────────────────────────────────────────────────────────────────┐
|
|
23
|
+
│ INITIAL TURN LATENCY & CONTEXT COMPARISON │
|
|
24
|
+
├──────────────────┬─────────────────────────────┬──────────────────────────────┤
|
|
25
|
+
│ Metric │ OpsCloud (Current) │ reference/dcode (Benchmark) │
|
|
26
|
+
├──────────────────┼─────────────────────────────┼──────────────────────────────┤
|
|
27
|
+
│ Turn 1 Latency │ 315.79s (5m 15s) ⚠️ BLOCKER │ 0.73s - 25.39s (P50: 15.10s) │
|
|
28
|
+
│ Turn 1 Tokens │ 211,300 tokens (Context full)│ ~45,370 tokens │
|
|
29
|
+
│ Tool Injection │ Eager / Global / Unscoped │ Scoped / Filtered │
|
|
30
|
+
│ Schema Strategy │ Recursive $ref Dereference │ Protocol Native (Compact) │
|
|
31
|
+
│ Session / Loop │ Ephemeral Thread Loop + Stdio│ Server Loop Router Client │
|
|
32
|
+
└──────────────────┴─────────────────────────────┴──────────────────────────────┘
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The system became completely unresponsive for 5+ minutes because **the entire LLM context window was inundated with over 211K tokens of recursively inlined JSON schemas for dozens of un-scoped Kubernetes/AWS tools**, which then had to be re-evaluated across every internal graph step over disjointed stdio subprocess loops.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 2. Trace & Incident Evidence Breakdown
|
|
40
|
+
|
|
41
|
+
### 2.1 OpsCloud Trace (Image 1: `media_1791459244211.png`)
|
|
42
|
+
* **Execution Status:** 1 Turn, Duration: **315.79s**, Context: **211.3K tokens**, Cost: **$0.1531**.
|
|
43
|
+
* **Observed Bound Tools:**
|
|
44
|
+
* Core: `create_temp_artifact`, `delete_temp_artifact`, `fetch_url`.
|
|
45
|
+
* Finch MCP: `plugin__aws-containers_ta_finch_build_container_ima_df47730a85e8`, `plugin__aws-containers_talkops-dev_finch_push_image_6b02bcde1560`, `plugin__aws-containers_talkop_finch_create_ecr_repo_a8f88f0a1f9f`.
|
|
46
|
+
* EKS / Kubernetes MCP: `plugin__aws-containers_talkops-d_list_k8s_resources_5aa672efc864`, `plugin__aws-containers_talkops-devops-_get_pod_logs_a03a76775bc1`, `plugin__aws-containers_talkops-devop_get_k8s_events_465d1e587238`, `plugin__aws-containers_talkops-de_list_api_versions_c0a261010b5e`, `plugin__aws-containers_talkops-_manage_k8s_resource_b877895269a7`, `plugin__aws-containers_talkops-devops-pl_apply_yaml_34e08bc5a89a`, `plugin__aws-containers_talkop_generate_app_manifest_555c8188f2bb`...
|
|
47
|
+
* **Symptom:** Every single tool from all MCP servers declared in the plugin was injected directly into the main agent's LLM prompt, expanding schemas recursively.
|
|
48
|
+
|
|
49
|
+
### 2.2 Reference `dcode` Trace (Image 2: `media_1791459309416.png`)
|
|
50
|
+
* **Execution Status:** 4 Turns, P50: **15.10s**, Cost: **$0.1014**.
|
|
51
|
+
* Turn 1: **0.73s** (Goal initialization)
|
|
52
|
+
* Turn 2: **25.39s** (45.37K tokens)
|
|
53
|
+
* Turn 3: **15.09s** (46.6K tokens)
|
|
54
|
+
* Turn 4: **40.15s** (254.8K tokens after 4 rounds of conversation history accumulation)
|
|
55
|
+
* **Observed Bound Tools:**
|
|
56
|
+
* Core: `update_goal`, `ask_user`, `js_eval`, `create_temp_artifact`, `delete_temp_artifact`, `fetch_url`, `get_current_thread_id`.
|
|
57
|
+
* Compute MCP (`aws-compute`): Only 6 focused tools (`aws__get_regional_availa`, `aws__search_documentatio`, `aws__read_documentation`, `aws__retrieve_skill`, `aws__list_regions`, `aws__get_tasks`).
|
|
58
|
+
* **Symptom:** Fast execution, minimal initial token overhead, and precise tool availability.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## 3. Deep Root-Cause Analysis (Forensic Audit)
|
|
63
|
+
|
|
64
|
+
Our cross-codebase audit identified **5 compounding architectural flaws** in OpsCloud that do not exist in `reference/dcode`.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
### Root Cause 1: Recursive `$ref` Dereferencing in `_clean_mcp_schema`
|
|
69
|
+
**Location:** [`src/opscloud/mcp/session_manager.py:68–85`](file:///Users/structbinary/Documents/work/talkops/opscloud/src/opscloud/mcp/session_manager.py#L68-L85)
|
|
70
|
+
|
|
71
|
+
#### What OpsCloud Does:
|
|
72
|
+
```python
|
|
73
|
+
def _clean_mcp_schema(schema: Any) -> dict[str, Any]:
|
|
74
|
+
if not isinstance(schema, dict):
|
|
75
|
+
return {}
|
|
76
|
+
|
|
77
|
+
from langchain_core.utils.json_schema import dereference_refs
|
|
78
|
+
|
|
79
|
+
try:
|
|
80
|
+
resolved = dereference_refs(schema)
|
|
81
|
+
except Exception as exc:
|
|
82
|
+
logger.debug("Failed to dereference MCP schema refs: %s", exc)
|
|
83
|
+
resolved = schema
|
|
84
|
+
|
|
85
|
+
cleaned = dict(resolved) if isinstance(resolved, dict) else dict(schema)
|
|
86
|
+
for key in ("$schema", "$id", "$defs", "definitions", "additionalProperties"):
|
|
87
|
+
cleaned.pop(key, None)
|
|
88
|
+
...
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
#### Why This Explodes Context to 211.3K Tokens:
|
|
92
|
+
* When `awslabs.eks-mcp-server` loads, tools like `apply_yaml`, `manage_k8s_resource`, and `generate_app_manifest` define parameters that accept complete Kubernetes manifests.
|
|
93
|
+
* The underlying JSON schema contains deep OpenAPI definitions (`#/$defs/ObjectMeta`, `#/$defs/PodSpec`, `#/$defs/Container`, `#/$defs/EnvVar`, etc.) with multiple recursive and cross-referencing branches.
|
|
94
|
+
* `dereference_refs()` replaces every `{"$ref": ...}` pointer with a **deep, full copy** of the referenced definition.
|
|
95
|
+
* If a single schema references sub-models 15 times, inlining them causes exponential duplication. A single tool's schema grows from a compact 1.5KB reference graph into a **60KB–100KB JSON behemoth**.
|
|
96
|
+
* Multiplying this by 10+ Kubernetes tools in `aws-containers` results in **150,000+ tokens generated from tool schema definitions alone**.
|
|
97
|
+
|
|
98
|
+
#### What `reference/dcode` Does Differently:
|
|
99
|
+
**Location:** [`reference/dcode/mcp_tools.py:1514–1520`](file:///Users/structbinary/Documents/work/talkops/opscloud/reference/dcode/mcp_tools.py#L1514-L1520)
|
|
100
|
+
* `dcode` passes the raw MCP schema directly to `langchain.mcp.as_langchain_tool(routed_tool, client)`.
|
|
101
|
+
* It **never calls `dereference_refs`**. The schema remains compact and standard, allowing the LLM provider's API client to handle references natively without exponential duplication.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
### Root Cause 2: Global Eager Tool Injection of Vertical Plugins into Main Coordinator Agent
|
|
106
|
+
**Locations:**
|
|
107
|
+
* [`src/opscloud/plugins/models.py:174–185`](file:///Users/structbinary/Documents/work/talkops/opscloud/src/opscloud/plugins/models.py#L174-L185)
|
|
108
|
+
* [`src/opscloud/plugins/adapters/mcp.py:200–203`](file:///Users/structbinary/Documents/work/talkops/opscloud/src/opscloud/plugins/adapters/mcp.py#L200-L203)
|
|
109
|
+
* [`src/opscloud/agent/factory.py:604–660`](file:///Users/structbinary/Documents/work/talkops/opscloud/src/opscloud/agent/factory.py#L604-L660)
|
|
110
|
+
|
|
111
|
+
#### The Fault Chain:
|
|
112
|
+
1. In `src/opscloud/plugins/models.py`:
|
|
113
|
+
```python
|
|
114
|
+
@property
|
|
115
|
+
def is_agent_plugin(self) -> bool:
|
|
116
|
+
"""Return True if this plugin provides autonomous agents (agent-based plugin)."""
|
|
117
|
+
if self.inventory.agents:
|
|
118
|
+
...
|
|
119
|
+
if self.root and isinstance(self.root, Path):
|
|
120
|
+
agents_dir = self.root / "agents"
|
|
121
|
+
return agents_dir.is_dir() and any(agents_dir.glob("*.md"))
|
|
122
|
+
return False
|
|
123
|
+
```
|
|
124
|
+
2. In `src/opscloud/plugins/adapters/mcp.py`:
|
|
125
|
+
```python
|
|
126
|
+
def plugin_mcp_configs(plugins, *, include_subagents: bool = False):
|
|
127
|
+
for plugin in plugins:
|
|
128
|
+
if not include_subagents and plugin.is_agent_plugin:
|
|
129
|
+
continue # Dynamic subagent plugins isolate their own MCP servers
|
|
130
|
+
```
|
|
131
|
+
3. Vertical plugins (such as `aws-containers`, `aws-networking`, `aws-web-and-mobile`) and skill plugins do **not** define an `agents/*.md` directory. Therefore, `plugin.is_agent_plugin` evaluates to **`False`**.
|
|
132
|
+
4. Consequently, when `factory.py` calls `discover_mcp_configs()` to build the **Main Coordinator Agent**, all MCP servers declared by `aws-containers` (`finch`, `eks`, `ecs`, `aws-mcp`) are treated as global coordinator tools!
|
|
133
|
+
5. In `factory.py` lines 654–657:
|
|
134
|
+
```python
|
|
135
|
+
discovered_tools = build_mcp_tools_from_server_infos(coordinator_infos, mcp_manager)
|
|
136
|
+
if discovered_tools:
|
|
137
|
+
mcp_tools_list.extend(discovered_tools)
|
|
138
|
+
all_tools.extend(discovered_tools) # <-- Injected into all_tools!
|
|
139
|
+
```
|
|
140
|
+
6. In `factory.py` line 1081:
|
|
141
|
+
```python
|
|
142
|
+
graph = create_deep_agent(
|
|
143
|
+
model=active_model,
|
|
144
|
+
tools=all_tools, # <-- ALL vertical plugin tools bound to Main Agent!
|
|
145
|
+
...
|
|
146
|
+
)
|
|
147
|
+
```
|
|
148
|
+
7. **The result:** The main agent is inundated with 40+ specialized container/Kubernetes tools that should have been isolated to a specialized subagent or dynamically mounted skill.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
### Root Cause 3: `ToolFilterMiddleware` Does NOT Filter Tools from the Model Request
|
|
153
|
+
**Location:** [`src/opscloud/middleware/tool_filter.py:412–431`](file:///Users/structbinary/Documents/work/talkops/opscloud/src/opscloud/middleware/tool_filter.py#L412-L431)
|
|
154
|
+
|
|
155
|
+
#### The Missing Hook:
|
|
156
|
+
* `ToolFilterMiddleware` only implements:
|
|
157
|
+
- `wrap_tool_call(self, request: ToolCallRequest, handler)`
|
|
158
|
+
- `awrap_tool_call(self, request: ToolCallRequest, handler)`
|
|
159
|
+
* It **does not implement** `wrap_model_call`, `awrap_model_call`, or `modify_request`.
|
|
160
|
+
* In LangGraph / DeepAgents, `tools` are passed to `model.bind_tools(self.tools)`.
|
|
161
|
+
* Because `ToolFilterMiddleware` does not intercept the model call request to prune `request.tools`, **100% of all registered tools are sent in the system prompt / tool definitions to the LLM on every single turn**.
|
|
162
|
+
* The filter only functions as an execution-time authorization gate (blocking the tool *after* the LLM decides to call it). It provides **zero context-reduction benefits**.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
### Root Cause 4: Multi-Step Compounding Turn Latency (315 Seconds TTFT)
|
|
167
|
+
* Processing **211,300 input tokens** on remote LLM endpoints (Gemini 2.5 / 3.7 Flash, Claude 3.5 Sonnet) incurs an extreme Time-To-First-Token (TTFT) penalty: ~30 to 70 seconds per call.
|
|
168
|
+
* When the agent runs an agentic step (e.g., executing a command or asking for user clarification), LangGraph's Pregel engine executes sequential model nodes (`model` -> `tools` -> `model` -> `tools` -> `model`).
|
|
169
|
+
* Because the tool schema footprint remains at 211K tokens, **each model call must re-send and the LLM must re-process the full 211K tokens**.
|
|
170
|
+
* 4 sequential steps × 75 seconds per step = **300+ seconds (5.22 minutes)**.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### Root Cause 5: Event Loop Mismatch & Stdio Process Locking/Churn
|
|
175
|
+
**Locations:**
|
|
176
|
+
* [`src/opscloud/agent/factory.py:643–646`](file:///Users/structbinary/Documents/work/talkops/opscloud/src/opscloud/agent/factory.py#L643-L646)
|
|
177
|
+
* [`src/opscloud/mcp/session_manager.py:530–547`](file:///Users/structbinary/Documents/work/talkops/opscloud/src/opscloud/mcp/session_manager.py#L530-L547)
|
|
178
|
+
|
|
179
|
+
#### Thread & Event Loop Lifecycles in OpsCloud:
|
|
180
|
+
1. In `factory.py`:
|
|
181
|
+
```python
|
|
182
|
+
if loop is not None and loop.is_running():
|
|
183
|
+
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
|
|
184
|
+
new_infos = pool.submit(asyncio.run, preload_mcp_metadata(missing_configs)).result()
|
|
185
|
+
else:
|
|
186
|
+
new_infos = asyncio.run(preload_mcp_metadata(missing_configs))
|
|
187
|
+
```
|
|
188
|
+
`asyncio.run()` creates a temporary event loop. Preloading connects via stdio, fetches tool names, and when `asyncio.run()` finishes, **that event loop is destroyed**. Any stdio pipes or process handles bound to that loop become dead or detached.
|
|
189
|
+
2. Later, when an agent turn executes on the server's `uvloop`, `get_session(server_name)` finds that no live session exists for that loop and calls `connect_server()` to start a brand new stdio connection via `uvx`.
|
|
190
|
+
3. Running multiple `uvx` instances (`uvx awslabs.eks-mcp-server@latest`, `uvx awslabs.finch-mcp-server@latest`, `uvx ecs-mcp-server`) concurrently causes:
|
|
191
|
+
- Python virtual environment creation / cache resolution latency for each `uvx` invocation.
|
|
192
|
+
- Stdio process blocking and mutex lock contention under `_get_server_lock(server_name)`.
|
|
193
|
+
- Thread stalls while waiting for child processes to initialize their IPC pipes.
|
|
194
|
+
|
|
195
|
+
#### What `reference/dcode` Does Differently:
|
|
196
|
+
**Locations:**
|
|
197
|
+
* [`reference/dcode/server_graph.py:161–168`](file:///Users/structbinary/Documents/work/talkops/opscloud/reference/dcode/server_graph.py#L161-L168)
|
|
198
|
+
* [`reference/dcode/mcp_tools.py:2247–2251`](file:///Users/structbinary/Documents/work/talkops/opscloud/reference/dcode/mcp_tools.py#L2247-L2251)
|
|
199
|
+
* `dcode` initializes a single, shared `MCPSessionManager` directly on the server's running event loop via `_get_mcp_session_manager()`.
|
|
200
|
+
* Backends are mounted onto a single FastMCP router client.
|
|
201
|
+
* Preflight and connection setup are executed concurrently with bounded concurrency:
|
|
202
|
+
```python
|
|
203
|
+
preflight_results = await _gather_bounded(
|
|
204
|
+
[functools.partial(_preflight_and_connect, name, cfg) for name, cfg in server_items],
|
|
205
|
+
limit=_MCP_LOAD_CONCURRENCY,
|
|
206
|
+
)
|
|
207
|
+
```
|
|
208
|
+
* All tools route through this unified client. There are no detached thread pools or orphaned event loops.
|
|
209
|
+
|
|
210
|
+
---
|
|
211
|
+
|
|
212
|
+
## 4. Architectural Comparison: OpsCloud vs. `dcode`
|
|
213
|
+
|
|
214
|
+
| Architectural Dimension | OpsCloud (`src/opscloud`) | Reference Implementation (`reference/dcode`) |
|
|
215
|
+
| :--- | :--- | :--- |
|
|
216
|
+
| **Tool Adapter Layer** | `langchain_mcp_adapters` with custom `StructuredTool` wrapping | `fastmcp.Client` + `langchain.mcp.as_langchain_tool` |
|
|
217
|
+
| **JSON Schema Handling** | **Recursive `dereference_refs()`** expanding all definitions inline | Native schema pass-through without ref dereferencing |
|
|
218
|
+
| **Plugin MCP Scoping** | Only checks `plugin.is_agent_plugin` (`agents/*.md`). Vertical/Skill plugins dump all tools to Main Agent | Strict config discovery; plugins inject scoped configs with user trust gating |
|
|
219
|
+
| **Model Prompt Filtering** | **None.** `ToolFilterMiddleware` only acts at tool execution time | `_apply_tool_filter` prunes tools before agent construction via `allowedTools`/`disabledTools` |
|
|
220
|
+
| **Session Lifetime** | Eager thread preloading with `asyncio.run()`, on-demand reconnect on separate `uvloop` | Singleton `MCPSessionManager` bound to server event loop |
|
|
221
|
+
| **Connection Pooling** | Separate `_MCPSessionEntry` per server with individual locks | Single unified FastMCP router client multiplexing all backends |
|
|
222
|
+
| **Teardown & Cleanup** | `session.invalidate()` per server | Bounded 5s teardown via `AsyncExitStack` |
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## 5. Engineering Action Plan & Implementation Guidance
|
|
227
|
+
|
|
228
|
+
To permanently resolve this blocker and achieve the ~15s response latency of `reference/dcode`, the engineering team must implement the following four-phase remediation:
|
|
229
|
+
|
|
230
|
+
### Phase 1: Eliminate Recursive Schema Expansion (Immediate Fix)
|
|
231
|
+
**Target:** [`src/opscloud/mcp/session_manager.py:68–103`](file:///Users/structbinary/Documents/work/talkops/opscloud/src/opscloud/mcp/session_manager.py#L68-L103)
|
|
232
|
+
1. **Remove `dereference_refs()`**: Cease using `langchain_core.utils.json_schema.dereference_refs` on raw MCP tool schemas.
|
|
233
|
+
2. **Schema Sanitization**: Instead of deep inlining, clean schemas by stripping provider-incompatible root keys (`$schema`, `$id`) while preserving local `$defs` or flattening top-level properties shallowly.
|
|
234
|
+
3. **Verify Schemas**: Ensure parameters for tools like `apply_yaml` and `manage_k8s_resource` pass concise schemas to the model.
|
|
235
|
+
|
|
236
|
+
### Phase 2: Restrict Plugin MCP Tool Scope to Subagents / Skills
|
|
237
|
+
**Target:** [`src/opscloud/plugins/adapters/mcp.py`](file:///Users/structbinary/Documents/work/talkops/opscloud/src/opscloud/plugins/adapters/mcp.py) & [`src/opscloud/agent/factory.py`](file:///Users/structbinary/Documents/work/talkops/opscloud/src/opscloud/agent/factory.py)
|
|
238
|
+
1. **Update Plugin Classification**: Plugins that provide skills with MCP servers must not automatically dump their MCP tools into the Main Coordinator Agent (`all_tools`).
|
|
239
|
+
2. **Scope Vertical Plugins**: Tools from vertical plugins (`aws-containers`, `aws-networking`, etc.) should only be attached to their respective specialized subagents or mounted when their specific skill is activated.
|
|
240
|
+
3. **Main Agent Tool Budget**: Ensure the Main Coordinator Agent is initialized with only core tools (`fetch_url`, `web_search`, `read_file`, `run_command`, `task`, `ask_user`), keeping initial context below **15,000 tokens**.
|
|
241
|
+
|
|
242
|
+
### Phase 3: Implement Model-Level Tool Filtering in `ToolFilterMiddleware`
|
|
243
|
+
**Target:** [`src/opscloud/middleware/tool_filter.py`](file:///Users/structbinary/Documents/work/talkops/opscloud/src/opscloud/middleware/tool_filter.py)
|
|
244
|
+
1. Implement `wrap_model_call` and `awrap_model_call` (or `modify_request`):
|
|
245
|
+
```python
|
|
246
|
+
async def awrap_model_call(
|
|
247
|
+
self,
|
|
248
|
+
request: ModelRequest,
|
|
249
|
+
handler: Callable[[ModelRequest], Awaitable[ModelResponse]],
|
|
250
|
+
) -> ModelResponse:
|
|
251
|
+
if self._allowed_patterns and request.tools:
|
|
252
|
+
filtered_tools = [
|
|
253
|
+
t for t in request.tools
|
|
254
|
+
if self.is_tool_allowed(getattr(t, "name", str(t)), tool_obj=t)
|
|
255
|
+
]
|
|
256
|
+
request = request.override(tools=filtered_tools)
|
|
257
|
+
return await handler(request)
|
|
258
|
+
```
|
|
259
|
+
2. This guarantees that subagents with restricted tool lists (e.g., `allowed_tools: ["aws-finops-*"]`) only send their authorized tools to the LLM prompt.
|
|
260
|
+
|
|
261
|
+
### Phase 4: Align Session Lifecycle with `dcode` Pattern
|
|
262
|
+
**Target:** [`src/opscloud/mcp/session_manager.py`](file:///Users/structbinary/Documents/work/talkops/opscloud/src/opscloud/mcp/session_manager.py) & [`src/opscloud/server/server_graph.py`](file:///Users/structbinary/Documents/work/talkops/opscloud/src/opscloud/server/server_graph.py)
|
|
263
|
+
1. **Bind Session Manager to Server Loop**: Ensure `MCPSessionManager` is instantiated on the server's primary event loop, eliminating cross-thread `asyncio.run()` preloading.
|
|
264
|
+
2. **Router Client Multiplexing**: Adopt FastMCP router client mounting so all stdio/http transports share a single client context.
|
|
265
|
+
3. **Graceful Subprocess Management**: Ensure bounded subprocess shutdown (5s timeout) to prevent dangling `uvx` / `docker` processes on server reload.
|
|
266
|
+
|
|
267
|
+
---
|
|
268
|
+
|
|
269
|
+
## 6. Verification & Acceptance Criteria
|
|
270
|
+
|
|
271
|
+
The fix will be considered complete and verified when:
|
|
272
|
+
1. **Context Benchmark:** Turn 1 prompt context with `aws-containers` installed does not exceed **45,000 tokens** (comparable to `dcode`).
|
|
273
|
+
2. **Latency Benchmark:** Turn 1 execution latency drops from **315 seconds to under 25 seconds** on the same test query.
|
|
274
|
+
3. **No Schema Inlining Bloat:** Inspection of LangSmith tool definitions confirms that Kubernetes OpenAPI definitions are not duplicated into thousands of lines of JSON.
|
|
275
|
+
4. **Subagent Isolation:** Tools from `aws-containers` only appear in turns where the container subagent/skill is explicitly dispatched.
|
|
@@ -117,7 +117,11 @@ class MCPDiscovery:
|
|
|
117
117
|
"""
|
|
118
118
|
self._store = store
|
|
119
119
|
|
|
120
|
-
def discover(
|
|
120
|
+
def discover(
|
|
121
|
+
self,
|
|
122
|
+
project_root: Path | None = None,
|
|
123
|
+
include_subagents: bool = False,
|
|
124
|
+
) -> dict[str, MCPServerConfig]:
|
|
121
125
|
"""Discover and merge MCP server configs across global, user, plugin, and project files.
|
|
122
126
|
|
|
123
127
|
Precedence (lowest to highest):
|
|
@@ -158,7 +162,10 @@ class MCPDiscovery:
|
|
|
158
162
|
try:
|
|
159
163
|
from opscloud.plugins.adapters.mcp import discover_plugin_mcp_configs
|
|
160
164
|
|
|
161
|
-
plugin_configs = discover_plugin_mcp_configs(
|
|
165
|
+
plugin_configs = discover_plugin_mcp_configs(
|
|
166
|
+
project_dir=effective_project_root,
|
|
167
|
+
include_subagents=include_subagents,
|
|
168
|
+
)
|
|
162
169
|
if isinstance(plugin_configs, dict):
|
|
163
170
|
for name, config in plugin_configs.items():
|
|
164
171
|
if isinstance(config, dict):
|
|
@@ -224,6 +231,9 @@ class MCPDiscovery:
|
|
|
224
231
|
return results
|
|
225
232
|
|
|
226
233
|
|
|
227
|
-
def discover_mcp_configs(
|
|
234
|
+
def discover_mcp_configs(
|
|
235
|
+
project_root: Path | None = None,
|
|
236
|
+
include_subagents: bool = False,
|
|
237
|
+
) -> dict[str, MCPServerConfig]:
|
|
228
238
|
"""Discover active MCP server configs from disk."""
|
|
229
|
-
return MCPDiscovery().discover(project_root)
|
|
239
|
+
return MCPDiscovery().discover(project_root, include_subagents=include_subagents)
|
|
@@ -77,42 +77,63 @@ def _filter_tool_names(
|
|
|
77
77
|
return True
|
|
78
78
|
|
|
79
79
|
|
|
80
|
-
def _clean_mcp_schema(schema: Any) -> dict[str, Any]:
|
|
80
|
+
def _clean_mcp_schema(schema: Any, max_ref_depth: int = 3) -> dict[str, Any]:
|
|
81
81
|
"""Clean JSON Schema for compatibility with LLM provider tool schemas (Gemini, Anthropic, OpenAI).
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
Safely inlines local `$ref` pointers (such as `#/$defs/...` or `#/definitions/...`)
|
|
84
|
+
up to a bounded ref depth, strips provider-incompatible metadata keys (`$schema`, `$id`,
|
|
85
|
+
`additionalProperties`), and prevents recursive bloat or corrupted empty keys.
|
|
86
86
|
"""
|
|
87
87
|
if not isinstance(schema, dict):
|
|
88
88
|
return {}
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
90
|
+
defs: dict[str, Any] = {}
|
|
91
|
+
if isinstance(schema.get("$defs"), dict):
|
|
92
|
+
defs.update(schema["$defs"])
|
|
93
|
+
if isinstance(schema.get("definitions"), dict):
|
|
94
|
+
defs.update(schema["definitions"])
|
|
95
|
+
|
|
96
|
+
def _resolve(node: Any, ref_depth: int, seen_refs: set[str]) -> Any:
|
|
97
|
+
if isinstance(node, dict):
|
|
98
|
+
if "$ref" in node:
|
|
99
|
+
ref = node["$ref"]
|
|
100
|
+
if (
|
|
101
|
+
ref_depth >= max_ref_depth
|
|
102
|
+
or not isinstance(ref, str)
|
|
103
|
+
or not ref.startswith("#/")
|
|
104
|
+
or ref in seen_refs
|
|
105
|
+
):
|
|
106
|
+
return {"type": "object"}
|
|
107
|
+
|
|
108
|
+
parts = ref.lstrip("#/").split("/")
|
|
109
|
+
target = None
|
|
110
|
+
if len(parts) >= 2 and parts[0] in ("$defs", "definitions"):
|
|
111
|
+
target = defs.get(parts[1])
|
|
112
|
+
elif len(parts) == 1 and parts[0] in defs:
|
|
113
|
+
target = defs.get(parts[0])
|
|
114
|
+
|
|
115
|
+
if isinstance(target, dict):
|
|
116
|
+
merged = dict(target)
|
|
117
|
+
for k, v in node.items():
|
|
118
|
+
if k != "$ref":
|
|
119
|
+
merged[k] = v
|
|
120
|
+
return _resolve(merged, ref_depth + 1, seen_refs | {ref})
|
|
121
|
+
return {"type": "object"}
|
|
122
|
+
|
|
123
|
+
res = {}
|
|
124
|
+
for k, v in node.items():
|
|
125
|
+
if k in ("$schema", "$id", "additionalProperties", "$defs", "definitions"):
|
|
126
|
+
continue
|
|
127
|
+
res[k] = _resolve(v, ref_depth, seen_refs)
|
|
128
|
+
return res
|
|
129
|
+
|
|
130
|
+
if isinstance(node, list):
|
|
131
|
+
return [_resolve(item, ref_depth, seen_refs) for item in node]
|
|
132
|
+
|
|
133
|
+
return node
|
|
134
|
+
|
|
135
|
+
result = _resolve(schema, 0, set())
|
|
136
|
+
return result if isinstance(result, dict) else {}
|
|
116
137
|
|
|
117
138
|
|
|
118
139
|
def _clean_stderr_diagnostic(stderr_text: str | None) -> str | None:
|
|
@@ -65,42 +65,63 @@ def _normalize_mcp_arguments(arguments: dict[str, Any], input_schema: Any) -> di
|
|
|
65
65
|
return cleaned
|
|
66
66
|
|
|
67
67
|
|
|
68
|
-
def _clean_mcp_schema(schema: Any) -> dict[str, Any]:
|
|
68
|
+
def _clean_mcp_schema(schema: Any, max_ref_depth: int = 3) -> dict[str, Any]:
|
|
69
69
|
"""Clean JSON Schema for compatibility with LLM provider tool schemas (Gemini, Anthropic, OpenAI).
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
Safely inlines local `$ref` pointers (such as `#/$defs/...` or `#/definitions/...`)
|
|
72
|
+
up to a bounded ref depth, strips provider-incompatible metadata keys (`$schema`, `$id`,
|
|
73
|
+
`additionalProperties`), and prevents recursive bloat or corrupted empty keys.
|
|
74
74
|
"""
|
|
75
75
|
if not isinstance(schema, dict):
|
|
76
76
|
return {}
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
defs: dict[str, Any] = {}
|
|
79
|
+
if isinstance(schema.get("$defs"), dict):
|
|
80
|
+
defs.update(schema["$defs"])
|
|
81
|
+
if isinstance(schema.get("definitions"), dict):
|
|
82
|
+
defs.update(schema["definitions"])
|
|
83
|
+
|
|
84
|
+
def _resolve(node: Any, ref_depth: int, seen_refs: set[str]) -> Any:
|
|
85
|
+
if isinstance(node, dict):
|
|
86
|
+
if "$ref" in node:
|
|
87
|
+
ref = node["$ref"]
|
|
88
|
+
if (
|
|
89
|
+
ref_depth >= max_ref_depth
|
|
90
|
+
or not isinstance(ref, str)
|
|
91
|
+
or not ref.startswith("#/")
|
|
92
|
+
or ref in seen_refs
|
|
93
|
+
):
|
|
94
|
+
return {"type": "object"}
|
|
95
|
+
|
|
96
|
+
parts = ref.lstrip("#/").split("/")
|
|
97
|
+
target = None
|
|
98
|
+
if len(parts) >= 2 and parts[0] in ("$defs", "definitions"):
|
|
99
|
+
target = defs.get(parts[1])
|
|
100
|
+
elif len(parts) == 1 and parts[0] in defs:
|
|
101
|
+
target = defs.get(parts[0])
|
|
102
|
+
|
|
103
|
+
if isinstance(target, dict):
|
|
104
|
+
merged = dict(target)
|
|
105
|
+
for k, v in node.items():
|
|
106
|
+
if k != "$ref":
|
|
107
|
+
merged[k] = v
|
|
108
|
+
return _resolve(merged, ref_depth + 1, seen_refs | {ref})
|
|
109
|
+
return {"type": "object"}
|
|
110
|
+
|
|
111
|
+
res = {}
|
|
112
|
+
for k, v in node.items():
|
|
113
|
+
if k in ("$schema", "$id", "additionalProperties", "$defs", "definitions"):
|
|
114
|
+
continue
|
|
115
|
+
res[k] = _resolve(v, ref_depth, seen_refs)
|
|
116
|
+
return res
|
|
79
117
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
except Exception as exc:
|
|
83
|
-
logger.debug("Failed to dereference MCP schema refs: %s", exc)
|
|
84
|
-
resolved = schema
|
|
85
|
-
|
|
86
|
-
cleaned = dict(resolved) if isinstance(resolved, dict) else dict(schema)
|
|
87
|
-
for key in ("$schema", "$id", "$defs", "definitions", "additionalProperties"):
|
|
88
|
-
cleaned.pop(key, None)
|
|
89
|
-
|
|
90
|
-
properties = cleaned.get("properties")
|
|
91
|
-
if isinstance(properties, dict):
|
|
92
|
-
new_props = {}
|
|
93
|
-
for prop_name, prop_val in properties.items():
|
|
94
|
-
if isinstance(prop_val, dict):
|
|
95
|
-
p_clean = dict(prop_val)
|
|
96
|
-
p_clean.pop("additionalProperties", None)
|
|
97
|
-
p_clean.pop("$schema", None)
|
|
98
|
-
new_props[prop_name] = p_clean
|
|
99
|
-
else:
|
|
100
|
-
new_props[prop_name] = prop_val
|
|
101
|
-
cleaned["properties"] = new_props
|
|
118
|
+
if isinstance(node, list):
|
|
119
|
+
return [_resolve(item, ref_depth, seen_refs) for item in node]
|
|
102
120
|
|
|
103
|
-
|
|
121
|
+
return node
|
|
122
|
+
|
|
123
|
+
result = _resolve(schema, 0, set())
|
|
124
|
+
return result if isinstance(result, dict) else {}
|
|
104
125
|
|
|
105
126
|
|
|
106
127
|
def _filter_tool_name(
|
{talkops_opscloud-0.3.0b8 → talkops_opscloud-0.3.0b9}/src/opscloud/middleware/tool_filter.py
RENAMED
|
@@ -6,7 +6,7 @@ from collections.abc import Callable, Mapping, Sequence
|
|
|
6
6
|
import fnmatch
|
|
7
7
|
from typing import Any
|
|
8
8
|
|
|
9
|
-
from langchain.agents.middleware.types import AgentMiddleware
|
|
9
|
+
from langchain.agents.middleware.types import AgentMiddleware, ModelRequest
|
|
10
10
|
from langchain_core.messages import ToolMessage
|
|
11
11
|
from langgraph.prebuilt.tool_node import ToolCallRequest
|
|
12
12
|
|
|
@@ -429,6 +429,40 @@ class ToolFilterMiddleware(AgentMiddleware[Any, Any]):
|
|
|
429
429
|
return err
|
|
430
430
|
return await handler(request)
|
|
431
431
|
|
|
432
|
+
def wrap_model_call(
|
|
433
|
+
self,
|
|
434
|
+
request: ModelRequest[Any],
|
|
435
|
+
handler: Callable[[ModelRequest[Any]], Any],
|
|
436
|
+
) -> Any:
|
|
437
|
+
"""Prune unavailable tools from model request before sending to LLM."""
|
|
438
|
+
if self._allowed_patterns and request.tools:
|
|
439
|
+
filtered_tools = []
|
|
440
|
+
for t in request.tools:
|
|
441
|
+
raw_name = getattr(t, "name", None) if not isinstance(t, dict) else t.get("name")
|
|
442
|
+
name = str(raw_name) if raw_name is not None else ""
|
|
443
|
+
tool_obj = t if not isinstance(t, dict) else None
|
|
444
|
+
if name and self.is_tool_allowed(name, tool_obj=tool_obj):
|
|
445
|
+
filtered_tools.append(t)
|
|
446
|
+
request = request.override(tools=filtered_tools)
|
|
447
|
+
return handler(request)
|
|
448
|
+
|
|
449
|
+
async def awrap_model_call(
|
|
450
|
+
self,
|
|
451
|
+
request: ModelRequest[Any],
|
|
452
|
+
handler: Callable[[ModelRequest[Any]], Any],
|
|
453
|
+
) -> Any:
|
|
454
|
+
"""Asynchronously prune unavailable tools from model request before sending to LLM."""
|
|
455
|
+
if self._allowed_patterns and request.tools:
|
|
456
|
+
filtered_tools = []
|
|
457
|
+
for t in request.tools:
|
|
458
|
+
raw_name = getattr(t, "name", None) if not isinstance(t, dict) else t.get("name")
|
|
459
|
+
name = str(raw_name) if raw_name is not None else ""
|
|
460
|
+
tool_obj = t if not isinstance(t, dict) else None
|
|
461
|
+
if name and self.is_tool_allowed(name, tool_obj=tool_obj):
|
|
462
|
+
filtered_tools.append(t)
|
|
463
|
+
request = request.override(tools=filtered_tools)
|
|
464
|
+
return await handler(request)
|
|
465
|
+
|
|
432
466
|
|
|
433
467
|
__all__ = [
|
|
434
468
|
"ALWAYS_ALLOWED_SYSTEM_TOOLS",
|
|
@@ -199,7 +199,7 @@ def plugin_mcp_configs(
|
|
|
199
199
|
configs: list[dict[str, Any]] = []
|
|
200
200
|
for plugin in plugins:
|
|
201
201
|
if not include_subagents and plugin.is_agent_plugin:
|
|
202
|
-
continue #
|
|
202
|
+
continue # Agent-based plugins isolate their MCP servers to their own subagents
|
|
203
203
|
|
|
204
204
|
if plugin.data_dir is not None:
|
|
205
205
|
try:
|
|
@@ -189,6 +189,16 @@ class PluginInstance:
|
|
|
189
189
|
"""Alias for is_agent_plugin."""
|
|
190
190
|
return self.is_agent_plugin
|
|
191
191
|
|
|
192
|
+
@property
|
|
193
|
+
def is_partner_plugin(self) -> bool:
|
|
194
|
+
"""Return True if plugin provides MCP tools and skills without autonomous agents."""
|
|
195
|
+
return not self.is_agent_plugin and bool(self.inventory.mcp_files or (self.manifest and self.manifest.inline_mcp))
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def is_vertical_plugin(self) -> bool:
|
|
199
|
+
"""Return True if plugin provides specialized skills without agents or MCP servers."""
|
|
200
|
+
return not self.is_agent_plugin and not self.is_partner_plugin and bool(self.inventory.skills)
|
|
201
|
+
|
|
192
202
|
|
|
193
203
|
def is_agent_plugin_dir(plugin_dir: Path) -> bool:
|
|
194
204
|
"""Return True if a plugin directory contains an agents/ component with markdown definitions."""
|
|
@@ -192,6 +192,23 @@ def test_clean_mcp_schema() -> None:
|
|
|
192
192
|
assert "Field" in items_schema["properties"]
|
|
193
193
|
assert "Value" in items_schema["properties"]
|
|
194
194
|
|
|
195
|
+
# Verify circular references and bounded recursion break safely without empty keys or infinite loop
|
|
196
|
+
circular_schema = {
|
|
197
|
+
"type": "object",
|
|
198
|
+
"properties": {"node": {"$ref": "#/$defs/Node"}},
|
|
199
|
+
"$defs": {
|
|
200
|
+
"Node": {
|
|
201
|
+
"type": "object",
|
|
202
|
+
"properties": {"child": {"$ref": "#/$defs/Node"}},
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
}
|
|
206
|
+
cleaned_circ = _clean_mcp_schema(circular_schema)
|
|
207
|
+
assert "$defs" not in cleaned_circ
|
|
208
|
+
assert "$ref" not in str(cleaned_circ)
|
|
209
|
+
assert "" not in cleaned_circ
|
|
210
|
+
assert cleaned_circ["properties"]["node"]["properties"]["child"] == {"type": "object"}
|
|
211
|
+
|
|
195
212
|
|
|
196
213
|
def test_is_transient_session_error() -> None:
|
|
197
214
|
assert _is_transient_session_error(BrokenPipeError()) is True
|