code-puppy 0.0.315__tar.gz → 0.0.320__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.
- {code_puppy-0.0.315 → code_puppy-0.0.320}/PKG-INFO +1 -1
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/agents/base_agent.py +51 -100
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/cli_runner.py +6 -0
- code_puppy-0.0.320/code_puppy/command_line/mcp/logs_command.py +235 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/model_settings_menu.py +6 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/mcp_/__init__.py +17 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/mcp_/blocking_startup.py +61 -32
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/mcp_/managed_server.py +23 -3
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/mcp_/manager.py +65 -0
- code_puppy-0.0.320/code_puppy/mcp_/mcp_logs.py +224 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/messaging/__init__.py +9 -0
- code_puppy-0.0.320/code_puppy/messaging/markdown_patches.py +57 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/model_factory.py +54 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/models.json +1 -1
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/claude_code_oauth/utils.py +1 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/pyproject.toml +2 -1
- code_puppy-0.0.315/code_puppy/command_line/mcp/logs_command.py +0 -126
- {code_puppy-0.0.315 → code_puppy-0.0.320}/.gitignore +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/LICENSE +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/README.md +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/__init__.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/__main__.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/agents/__init__.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/agents/agent_c_reviewer.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/agents/agent_code_puppy.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/agents/agent_code_reviewer.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/agents/agent_creator_agent.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/agents/agent_golang_reviewer.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/agents/agent_manager.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/agents/agent_planning.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/agents/agent_python_programmer.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/agents/agent_python_reviewer.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/agents/agent_qa_expert.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/agents/agent_qa_kitten.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/agents/agent_security_auditor.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/agents/json_agent.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/agents/prompt_reviewer.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/callbacks.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/chatgpt_codex_client.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/claude_cache_client.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/__init__.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/add_model_menu.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/attachments.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/autosave_menu.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/colors_menu.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/command_handler.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/command_registry.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/config_commands.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/core_commands.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/diff_menu.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/file_path_completion.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/load_context_completion.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp/__init__.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp/add_command.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp/base.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp/edit_command.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp/handler.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp/help_command.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp/install_command.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp/install_menu.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp/list_command.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp/remove_command.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp/restart_command.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp/search_command.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp/start_all_command.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp/start_command.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp/status_command.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp/stop_command.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp/test_command.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp/utils.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/mcp_completion.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/model_picker_completion.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/motd.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/pin_command_completion.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/session_commands.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/command_line/utils.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/config.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/error_logging.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/gemini_code_assist.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/http_utils.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/keymap.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/main.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/mcp_/async_lifecycle.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/mcp_/captured_stdio_server.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/mcp_/circuit_breaker.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/mcp_/config_wizard.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/mcp_/dashboard.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/mcp_/error_isolation.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/mcp_/examples/retry_example.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/mcp_/health_monitor.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/mcp_/registry.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/mcp_/retry_manager.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/mcp_/server_registry_catalog.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/mcp_/status_tracker.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/mcp_/system_tools.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/messaging/bus.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/messaging/commands.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/messaging/message_queue.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/messaging/messages.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/messaging/queue_console.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/messaging/renderers.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/messaging/rich_renderer.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/messaging/spinner/__init__.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/messaging/spinner/console_spinner.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/messaging/spinner/spinner_base.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/model_utils.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/models_dev_parser.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/__init__.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/chatgpt_oauth/__init__.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/chatgpt_oauth/config.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/chatgpt_oauth/oauth_flow.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/chatgpt_oauth/test_plugin.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/chatgpt_oauth/utils.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/claude_code_oauth/README.md +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/claude_code_oauth/SETUP.md +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/claude_code_oauth/__init__.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/claude_code_oauth/config.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/claude_code_oauth/register_callbacks.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/claude_code_oauth/test_plugin.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/customizable_commands/__init__.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/customizable_commands/register_callbacks.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/example_custom_command/README.md +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/file_permission_handler/__init__.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/file_permission_handler/register_callbacks.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/oauth_puppy_html.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/shell_safety/__init__.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/shell_safety/agent_shell_safety.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/shell_safety/command_cache.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/plugins/shell_safety/register_callbacks.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/prompts/codex_system_prompt.md +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/pydantic_patches.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/reopenable_async_client.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/round_robin_model.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/session_storage.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/status_display.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/summarization_agent.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/terminal_utils.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/tools/__init__.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/tools/agent_tools.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/tools/browser/__init__.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/tools/browser/browser_control.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/tools/browser/browser_interactions.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/tools/browser/browser_locators.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/tools/browser/browser_navigation.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/tools/browser/browser_screenshot.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/tools/browser/browser_scripts.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/tools/browser/browser_workflows.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/tools/browser/camoufox_manager.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/tools/browser/vqa_agent.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/tools/command_runner.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/tools/common.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/tools/file_modifications.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/tools/file_operations.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/tools/tools_content.py +0 -0
- {code_puppy-0.0.315 → code_puppy-0.0.320}/code_puppy/version_checker.py +0 -0
|
@@ -47,11 +47,10 @@ from code_puppy.config import (
|
|
|
47
47
|
get_protected_token_count,
|
|
48
48
|
get_use_dbos,
|
|
49
49
|
get_value,
|
|
50
|
-
load_mcp_server_configs,
|
|
51
50
|
)
|
|
52
51
|
from code_puppy.error_logging import log_error
|
|
53
52
|
from code_puppy.keymap import cancel_agent_uses_signal, get_cancel_agent_char_code
|
|
54
|
-
from code_puppy.mcp_ import
|
|
53
|
+
from code_puppy.mcp_ import get_mcp_manager
|
|
55
54
|
from code_puppy.messaging import (
|
|
56
55
|
emit_error,
|
|
57
56
|
emit_info,
|
|
@@ -90,6 +89,9 @@ class BaseAgent(ABC):
|
|
|
90
89
|
# Cache for MCP tool definitions (for token estimation)
|
|
91
90
|
# This is populated after the first successful run when MCP tools are retrieved
|
|
92
91
|
self._mcp_tool_definitions_cache: List[Dict[str, Any]] = []
|
|
92
|
+
# Shared console for streaming output - should be set by cli_runner
|
|
93
|
+
# to avoid conflicts between spinner's Live display and response streaming
|
|
94
|
+
self._console: Optional[Any] = None
|
|
93
95
|
|
|
94
96
|
@property
|
|
95
97
|
@abstractmethod
|
|
@@ -989,45 +991,31 @@ class BaseAgent(ABC):
|
|
|
989
991
|
return self._puppy_rules
|
|
990
992
|
|
|
991
993
|
def load_mcp_servers(self, extra_headers: Optional[Dict[str, str]] = None):
|
|
992
|
-
"""Load MCP servers through the manager and return pydantic-ai compatible servers.
|
|
994
|
+
"""Load MCP servers through the manager and return pydantic-ai compatible servers.
|
|
995
|
+
|
|
996
|
+
Note: The manager automatically syncs from mcp_servers.json during initialization,
|
|
997
|
+
so we don't need to sync here. Use reload_mcp_servers() to force a re-sync.
|
|
998
|
+
"""
|
|
993
999
|
|
|
994
1000
|
mcp_disabled = get_value("disable_mcp_servers")
|
|
995
1001
|
if mcp_disabled and str(mcp_disabled).lower() in ("1", "true", "yes", "on"):
|
|
996
1002
|
return []
|
|
997
1003
|
|
|
998
1004
|
manager = get_mcp_manager()
|
|
999
|
-
configs = load_mcp_server_configs()
|
|
1000
|
-
if not configs:
|
|
1001
|
-
existing_servers = manager.list_servers()
|
|
1002
|
-
if not existing_servers:
|
|
1003
|
-
return []
|
|
1004
|
-
else:
|
|
1005
|
-
for name, conf in configs.items():
|
|
1006
|
-
try:
|
|
1007
|
-
server_config = ServerConfig(
|
|
1008
|
-
id=conf.get("id", f"{name}_{hash(name)}"),
|
|
1009
|
-
name=name,
|
|
1010
|
-
type=conf.get("type", "sse"),
|
|
1011
|
-
enabled=conf.get("enabled", True),
|
|
1012
|
-
config=conf,
|
|
1013
|
-
)
|
|
1014
|
-
existing = manager.get_server_by_name(name)
|
|
1015
|
-
if not existing:
|
|
1016
|
-
manager.register_server(server_config)
|
|
1017
|
-
else:
|
|
1018
|
-
if existing.config != server_config.config:
|
|
1019
|
-
manager.update_server(existing.id, server_config)
|
|
1020
|
-
except Exception:
|
|
1021
|
-
continue
|
|
1022
|
-
|
|
1023
1005
|
return manager.get_servers_for_agent()
|
|
1024
1006
|
|
|
1025
1007
|
def reload_mcp_servers(self):
|
|
1026
|
-
"""Reload MCP servers and return updated servers.
|
|
1008
|
+
"""Reload MCP servers and return updated servers.
|
|
1009
|
+
|
|
1010
|
+
Forces a re-sync from mcp_servers.json to pick up any configuration changes.
|
|
1011
|
+
"""
|
|
1027
1012
|
# Clear the MCP tool cache when servers are reloaded
|
|
1028
1013
|
self._mcp_tool_definitions_cache = []
|
|
1029
|
-
|
|
1014
|
+
|
|
1015
|
+
# Force re-sync from mcp_servers.json
|
|
1030
1016
|
manager = get_mcp_manager()
|
|
1017
|
+
manager.sync_from_config()
|
|
1018
|
+
|
|
1031
1019
|
return manager.get_servers_for_agent()
|
|
1032
1020
|
|
|
1033
1021
|
def _load_model_with_fallback(
|
|
@@ -1279,27 +1267,22 @@ class BaseAgent(ABC):
|
|
|
1279
1267
|
ctx: The run context.
|
|
1280
1268
|
events: Async iterable of streaming events (PartStartEvent, PartDeltaEvent, etc.).
|
|
1281
1269
|
"""
|
|
1282
|
-
import os
|
|
1283
|
-
import time as time_module
|
|
1284
|
-
|
|
1285
1270
|
from pydantic_ai import PartDeltaEvent, PartStartEvent
|
|
1286
1271
|
from pydantic_ai.messages import TextPartDelta, ThinkingPartDelta
|
|
1287
1272
|
from rich.console import Console
|
|
1288
|
-
from rich.live import Live
|
|
1289
1273
|
from rich.markdown import Markdown
|
|
1290
1274
|
from rich.markup import escape
|
|
1291
1275
|
|
|
1292
1276
|
from code_puppy.messaging.spinner import pause_all_spinners
|
|
1293
1277
|
|
|
1294
|
-
console
|
|
1295
|
-
|
|
1296
|
-
#
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
)
|
|
1278
|
+
# IMPORTANT: Use the shared console (set by cli_runner) to avoid conflicts
|
|
1279
|
+
# with the spinner's Live display. Multiple Console instances with separate
|
|
1280
|
+
# Live displays cause cursor positioning chaos and line duplication.
|
|
1281
|
+
if self._console is not None:
|
|
1282
|
+
console = self._console
|
|
1283
|
+
else:
|
|
1284
|
+
# Fallback if console not set (shouldn't happen in normal use)
|
|
1285
|
+
console = Console()
|
|
1303
1286
|
|
|
1304
1287
|
# Track which part indices we're currently streaming (for Text/Thinking parts)
|
|
1305
1288
|
streaming_parts: set[int] = set()
|
|
@@ -1308,11 +1291,9 @@ class BaseAgent(ABC):
|
|
|
1308
1291
|
) # Track which parts are thinking (for dim style)
|
|
1309
1292
|
text_parts: set[int] = set() # Track which parts are text
|
|
1310
1293
|
banner_printed: set[int] = set() # Track if banner was already printed
|
|
1311
|
-
text_buffer: dict[int, list[str]] = {} # Buffer text for markdown
|
|
1312
|
-
|
|
1294
|
+
text_buffer: dict[int, list[str]] = {} # Buffer text for final markdown render
|
|
1295
|
+
token_count: dict[int, int] = {} # Track token count per text part
|
|
1313
1296
|
did_stream_anything = False # Track if we streamed any content
|
|
1314
|
-
last_render_time: dict[int, float] = {} # Track last render time per part
|
|
1315
|
-
render_interval = 0.1 # Only re-render markdown every 100ms (throttle)
|
|
1316
1297
|
|
|
1317
1298
|
def _print_thinking_banner() -> None:
|
|
1318
1299
|
"""Print the THINKING banner with spinner pause and line clear."""
|
|
@@ -1377,9 +1358,11 @@ class BaseAgent(ABC):
|
|
|
1377
1358
|
streaming_parts.add(event.index)
|
|
1378
1359
|
text_parts.add(event.index)
|
|
1379
1360
|
text_buffer[event.index] = [] # Initialize buffer
|
|
1361
|
+
token_count[event.index] = 0 # Initialize token counter
|
|
1380
1362
|
# Buffer initial content if present
|
|
1381
1363
|
if part.content and part.content.strip():
|
|
1382
1364
|
text_buffer[event.index].append(part.content)
|
|
1365
|
+
token_count[event.index] += 1
|
|
1383
1366
|
|
|
1384
1367
|
# PartDeltaEvent - stream the content as it arrives
|
|
1385
1368
|
elif isinstance(event, PartDeltaEvent):
|
|
@@ -1387,43 +1370,23 @@ class BaseAgent(ABC):
|
|
|
1387
1370
|
delta = event.delta
|
|
1388
1371
|
if isinstance(delta, (TextPartDelta, ThinkingPartDelta)):
|
|
1389
1372
|
if delta.content_delta:
|
|
1390
|
-
# For text parts,
|
|
1373
|
+
# For text parts, show token counter then render at end
|
|
1391
1374
|
if event.index in text_parts:
|
|
1392
|
-
|
|
1375
|
+
import sys
|
|
1376
|
+
|
|
1377
|
+
# Print banner on first content
|
|
1393
1378
|
if event.index not in banner_printed:
|
|
1394
1379
|
_print_response_banner()
|
|
1395
1380
|
banner_printed.add(event.index)
|
|
1396
|
-
|
|
1397
|
-
if use_live_display:
|
|
1398
|
-
live = Live(
|
|
1399
|
-
Markdown(""),
|
|
1400
|
-
console=console,
|
|
1401
|
-
refresh_per_second=10,
|
|
1402
|
-
vertical_overflow="visible", # Allow scrolling for long content
|
|
1403
|
-
)
|
|
1404
|
-
live.start()
|
|
1405
|
-
live_displays[event.index] = live
|
|
1406
|
-
# Accumulate text and throttle markdown rendering
|
|
1407
|
-
# (Markdown parsing is O(n), doing it on every token = O(n²) death)
|
|
1381
|
+
# Accumulate text for final markdown render
|
|
1408
1382
|
text_buffer[event.index].append(delta.content_delta)
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
and now - last_render >= render_interval
|
|
1417
|
-
):
|
|
1418
|
-
content = "".join(text_buffer[event.index])
|
|
1419
|
-
if event.index in live_displays:
|
|
1420
|
-
try:
|
|
1421
|
-
live_displays[event.index].update(
|
|
1422
|
-
Markdown(content)
|
|
1423
|
-
)
|
|
1424
|
-
last_render_time[event.index] = now
|
|
1425
|
-
except Exception:
|
|
1426
|
-
pass
|
|
1383
|
+
token_count[event.index] += 1
|
|
1384
|
+
# Update token counter in place (single line)
|
|
1385
|
+
count = token_count[event.index]
|
|
1386
|
+
sys.stdout.write(
|
|
1387
|
+
f"\r\x1b[K ⏳ Receiving... {count} tokens"
|
|
1388
|
+
)
|
|
1389
|
+
sys.stdout.flush()
|
|
1427
1390
|
else:
|
|
1428
1391
|
# For thinking parts, stream immediately (dim)
|
|
1429
1392
|
if event.index not in banner_printed:
|
|
@@ -1435,36 +1398,24 @@ class BaseAgent(ABC):
|
|
|
1435
1398
|
# PartEndEvent - finish the streaming with a newline
|
|
1436
1399
|
elif isinstance(event, PartEndEvent):
|
|
1437
1400
|
if event.index in streaming_parts:
|
|
1438
|
-
# For text parts,
|
|
1401
|
+
# For text parts, clear counter line and render markdown
|
|
1439
1402
|
if event.index in text_parts:
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
)
|
|
1448
|
-
except Exception:
|
|
1449
|
-
pass
|
|
1450
|
-
if event.index in live_displays:
|
|
1451
|
-
try:
|
|
1452
|
-
live_displays[event.index].stop()
|
|
1453
|
-
except Exception:
|
|
1454
|
-
pass
|
|
1455
|
-
del live_displays[event.index]
|
|
1456
|
-
# When not using Live display, print the final content as markdown
|
|
1457
|
-
elif event.index in text_buffer:
|
|
1403
|
+
import sys
|
|
1404
|
+
|
|
1405
|
+
# Clear the token counter line
|
|
1406
|
+
sys.stdout.write("\r\x1b[K")
|
|
1407
|
+
sys.stdout.flush()
|
|
1408
|
+
# Render the final markdown nicely
|
|
1409
|
+
if event.index in text_buffer:
|
|
1458
1410
|
try:
|
|
1459
1411
|
final_content = "".join(text_buffer[event.index])
|
|
1460
1412
|
if final_content.strip():
|
|
1461
1413
|
console.print(Markdown(final_content))
|
|
1462
1414
|
except Exception:
|
|
1463
1415
|
pass
|
|
1464
|
-
if event.index in text_buffer:
|
|
1465
1416
|
del text_buffer[event.index]
|
|
1466
|
-
# Clean up
|
|
1467
|
-
|
|
1417
|
+
# Clean up token count
|
|
1418
|
+
token_count.pop(event.index, None)
|
|
1468
1419
|
# For thinking parts, just print newline
|
|
1469
1420
|
elif event.index in banner_printed:
|
|
1470
1421
|
console.print() # Final newline after streaming
|
|
@@ -706,6 +706,12 @@ async def run_prompt_with_attachments(
|
|
|
706
706
|
attachments = [attachment.content for attachment in processed_prompt.attachments]
|
|
707
707
|
link_attachments = [link.url_part for link in processed_prompt.link_attachments]
|
|
708
708
|
|
|
709
|
+
# IMPORTANT: Set the shared console on the agent so that streaming output
|
|
710
|
+
# uses the same console as the spinner. This prevents Live display conflicts
|
|
711
|
+
# that cause line duplication during markdown streaming.
|
|
712
|
+
if spinner_console is not None:
|
|
713
|
+
agent._console = spinner_console
|
|
714
|
+
|
|
709
715
|
# Create the agent task first so we can track and cancel it
|
|
710
716
|
agent_task = asyncio.create_task(
|
|
711
717
|
agent.run_with_mcp(
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP Logs Command - Shows server logs from persistent log files.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import List, Optional
|
|
7
|
+
|
|
8
|
+
from rich.panel import Panel
|
|
9
|
+
from rich.syntax import Syntax
|
|
10
|
+
from rich.text import Text
|
|
11
|
+
|
|
12
|
+
from code_puppy.mcp_.mcp_logs import (
|
|
13
|
+
clear_logs,
|
|
14
|
+
get_log_file_path,
|
|
15
|
+
get_log_stats,
|
|
16
|
+
list_servers_with_logs,
|
|
17
|
+
read_logs,
|
|
18
|
+
)
|
|
19
|
+
from code_puppy.messaging import emit_error, emit_info
|
|
20
|
+
|
|
21
|
+
from .base import MCPCommandBase
|
|
22
|
+
from .utils import find_server_id_by_name, suggest_similar_servers
|
|
23
|
+
|
|
24
|
+
# Configure logging
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class LogsCommand(MCPCommandBase):
|
|
29
|
+
"""
|
|
30
|
+
Command handler for showing MCP server logs.
|
|
31
|
+
|
|
32
|
+
Shows logs from persistent log files stored in ~/.code_puppy/mcp_logs/.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def execute(self, args: List[str], group_id: Optional[str] = None) -> None:
|
|
36
|
+
"""
|
|
37
|
+
Show logs for a server.
|
|
38
|
+
|
|
39
|
+
Usage:
|
|
40
|
+
/mcp logs - List servers with logs
|
|
41
|
+
/mcp logs <server_name> - Show last 50 lines
|
|
42
|
+
/mcp logs <server_name> 100 - Show last 100 lines
|
|
43
|
+
/mcp logs <server_name> all - Show all logs
|
|
44
|
+
/mcp logs <server_name> --clear - Clear logs for server
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
args: Command arguments
|
|
48
|
+
group_id: Optional message group ID for grouping related messages
|
|
49
|
+
"""
|
|
50
|
+
if group_id is None:
|
|
51
|
+
group_id = self.generate_group_id()
|
|
52
|
+
|
|
53
|
+
# No args - list servers with logs
|
|
54
|
+
if not args:
|
|
55
|
+
self._list_servers_with_logs(group_id)
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
server_name = args[0]
|
|
59
|
+
|
|
60
|
+
# Check for --clear flag
|
|
61
|
+
if len(args) > 1 and args[1] == "--clear":
|
|
62
|
+
self._clear_logs(server_name, group_id)
|
|
63
|
+
return
|
|
64
|
+
|
|
65
|
+
# Determine number of lines
|
|
66
|
+
lines = 50 # Default
|
|
67
|
+
show_all = False
|
|
68
|
+
|
|
69
|
+
if len(args) > 1:
|
|
70
|
+
if args[1].lower() == "all":
|
|
71
|
+
show_all = True
|
|
72
|
+
else:
|
|
73
|
+
try:
|
|
74
|
+
lines = int(args[1])
|
|
75
|
+
if lines <= 0:
|
|
76
|
+
emit_info(
|
|
77
|
+
"Lines must be positive, using default: 50",
|
|
78
|
+
message_group=group_id,
|
|
79
|
+
)
|
|
80
|
+
lines = 50
|
|
81
|
+
except ValueError:
|
|
82
|
+
emit_info(
|
|
83
|
+
f"Invalid number '{args[1]}', using default: 50",
|
|
84
|
+
message_group=group_id,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
self._show_logs(server_name, lines if not show_all else None, group_id)
|
|
88
|
+
|
|
89
|
+
def _list_servers_with_logs(self, group_id: str) -> None:
|
|
90
|
+
"""List all servers that have log files."""
|
|
91
|
+
servers = list_servers_with_logs()
|
|
92
|
+
|
|
93
|
+
if not servers:
|
|
94
|
+
emit_info(
|
|
95
|
+
"📋 No MCP server logs found.\n"
|
|
96
|
+
"Logs are created when servers are started.",
|
|
97
|
+
message_group=group_id,
|
|
98
|
+
)
|
|
99
|
+
return
|
|
100
|
+
|
|
101
|
+
lines = ["📋 **Servers with logs:**\n"]
|
|
102
|
+
|
|
103
|
+
for server in servers:
|
|
104
|
+
stats = get_log_stats(server)
|
|
105
|
+
size_kb = stats["total_size_bytes"] / 1024
|
|
106
|
+
size_str = (
|
|
107
|
+
f"{size_kb:.1f} KB" if size_kb < 1024 else f"{size_kb / 1024:.1f} MB"
|
|
108
|
+
)
|
|
109
|
+
rotated = (
|
|
110
|
+
f" (+{stats['rotated_count']} rotated)"
|
|
111
|
+
if stats["rotated_count"]
|
|
112
|
+
else ""
|
|
113
|
+
)
|
|
114
|
+
lines.append(
|
|
115
|
+
f" • **{server}** - {stats['line_count']} lines, {size_str}{rotated}"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
lines.append("\n**Usage:** `/mcp logs <server_name> [lines|all]`")
|
|
119
|
+
|
|
120
|
+
emit_info("\n".join(lines), message_group=group_id)
|
|
121
|
+
|
|
122
|
+
def _show_logs(self, server_name: str, lines: Optional[int], group_id: str) -> None:
|
|
123
|
+
"""
|
|
124
|
+
Show logs for a specific server.
|
|
125
|
+
|
|
126
|
+
Args:
|
|
127
|
+
server_name: Name of the server
|
|
128
|
+
lines: Number of lines to show, or None for all
|
|
129
|
+
group_id: Message group ID
|
|
130
|
+
"""
|
|
131
|
+
try:
|
|
132
|
+
# Verify server exists in manager
|
|
133
|
+
server_id = find_server_id_by_name(self.manager, server_name)
|
|
134
|
+
if not server_id:
|
|
135
|
+
# Server not configured, but might have logs from before
|
|
136
|
+
stats = get_log_stats(server_name)
|
|
137
|
+
if not stats["exists"]:
|
|
138
|
+
emit_info(
|
|
139
|
+
f"Server '{server_name}' not found and has no logs.",
|
|
140
|
+
message_group=group_id,
|
|
141
|
+
)
|
|
142
|
+
suggest_similar_servers(
|
|
143
|
+
self.manager, server_name, group_id=group_id
|
|
144
|
+
)
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
# Read logs
|
|
148
|
+
log_lines = read_logs(server_name, lines=lines)
|
|
149
|
+
|
|
150
|
+
if not log_lines:
|
|
151
|
+
emit_info(
|
|
152
|
+
f"📋 No logs found for server: **{server_name}**\n"
|
|
153
|
+
f"Log file: `{get_log_file_path(server_name)}`",
|
|
154
|
+
message_group=group_id,
|
|
155
|
+
)
|
|
156
|
+
return
|
|
157
|
+
|
|
158
|
+
# Get stats for header
|
|
159
|
+
stats = get_log_stats(server_name)
|
|
160
|
+
total_lines = stats["line_count"]
|
|
161
|
+
showing = len(log_lines)
|
|
162
|
+
|
|
163
|
+
# Format header
|
|
164
|
+
if lines is None:
|
|
165
|
+
header = f"📋 Logs for {server_name} (all {total_lines} lines)"
|
|
166
|
+
else:
|
|
167
|
+
header = (
|
|
168
|
+
f"📋 Logs for {server_name} (last {showing} of {total_lines} lines)"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Format log content with syntax highlighting
|
|
172
|
+
log_content = "\n".join(log_lines)
|
|
173
|
+
|
|
174
|
+
# Create a panel with the logs
|
|
175
|
+
syntax = Syntax(
|
|
176
|
+
log_content,
|
|
177
|
+
"log",
|
|
178
|
+
theme="monokai",
|
|
179
|
+
word_wrap=True,
|
|
180
|
+
line_numbers=False,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
panel = Panel(
|
|
184
|
+
syntax,
|
|
185
|
+
title=header,
|
|
186
|
+
subtitle=f"Log file: {get_log_file_path(server_name)}",
|
|
187
|
+
border_style="dim",
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
emit_info(panel, message_group=group_id)
|
|
191
|
+
|
|
192
|
+
# Show hint for more options
|
|
193
|
+
if lines is not None and showing < total_lines:
|
|
194
|
+
emit_info(
|
|
195
|
+
Text.from_markup(
|
|
196
|
+
f"[dim]💡 Use `/mcp logs {server_name} all` to see all logs, "
|
|
197
|
+
f"or `/mcp logs {server_name} <number>` for specific count[/dim]"
|
|
198
|
+
),
|
|
199
|
+
message_group=group_id,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
except Exception as e:
|
|
203
|
+
logger.error(f"Error getting logs for server '{server_name}': {e}")
|
|
204
|
+
emit_error(f"Error getting logs: {e}", message_group=group_id)
|
|
205
|
+
|
|
206
|
+
def _clear_logs(self, server_name: str, group_id: str) -> None:
|
|
207
|
+
"""
|
|
208
|
+
Clear logs for a specific server.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
server_name: Name of the server
|
|
212
|
+
group_id: Message group ID
|
|
213
|
+
"""
|
|
214
|
+
try:
|
|
215
|
+
stats = get_log_stats(server_name)
|
|
216
|
+
|
|
217
|
+
if not stats["exists"] and stats["rotated_count"] == 0:
|
|
218
|
+
emit_info(
|
|
219
|
+
f"No logs to clear for server: {server_name}",
|
|
220
|
+
message_group=group_id,
|
|
221
|
+
)
|
|
222
|
+
return
|
|
223
|
+
|
|
224
|
+
# Clear the logs
|
|
225
|
+
clear_logs(server_name, include_rotated=True)
|
|
226
|
+
|
|
227
|
+
cleared_count = 1 + stats["rotated_count"]
|
|
228
|
+
emit_info(
|
|
229
|
+
f"🗑️ Cleared {cleared_count} log file(s) for **{server_name}**",
|
|
230
|
+
message_group=group_id,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
except Exception as e:
|
|
234
|
+
logger.error(f"Error clearing logs for server '{server_name}': {e}")
|
|
235
|
+
emit_error(f"Error clearing logs: {e}", message_group=group_id)
|
|
@@ -84,6 +84,12 @@ SETTING_DEFINITIONS: Dict[str, Dict] = {
|
|
|
84
84
|
"default": 10000,
|
|
85
85
|
"format": "{:.0f}",
|
|
86
86
|
},
|
|
87
|
+
"interleaved_thinking": {
|
|
88
|
+
"name": "Interleaved Thinking",
|
|
89
|
+
"description": "Enable thinking between tool calls (Claude 4 only: Opus 4.5, Opus 4.1, Opus 4, Sonnet 4). Adds beta header. WARNING: On Vertex/Bedrock, this FAILS for non-Claude 4 models!",
|
|
90
|
+
"type": "boolean",
|
|
91
|
+
"default": False,
|
|
92
|
+
},
|
|
87
93
|
}
|
|
88
94
|
|
|
89
95
|
|
|
@@ -17,6 +17,15 @@ from .error_isolation import (
|
|
|
17
17
|
)
|
|
18
18
|
from .managed_server import ManagedMCPServer, ServerConfig, ServerState
|
|
19
19
|
from .manager import MCPManager, ServerInfo, get_mcp_manager
|
|
20
|
+
from .mcp_logs import (
|
|
21
|
+
clear_logs,
|
|
22
|
+
get_log_file_path,
|
|
23
|
+
get_log_stats,
|
|
24
|
+
get_mcp_logs_dir,
|
|
25
|
+
list_servers_with_logs,
|
|
26
|
+
read_logs,
|
|
27
|
+
write_log,
|
|
28
|
+
)
|
|
20
29
|
from .registry import ServerRegistry
|
|
21
30
|
from .retry_manager import RetryManager, RetryStats, get_retry_manager, retry_mcp_call
|
|
22
31
|
from .status_tracker import Event, ServerStatusTracker
|
|
@@ -46,4 +55,12 @@ __all__ = [
|
|
|
46
55
|
"MCPDashboard",
|
|
47
56
|
"MCPConfigWizard",
|
|
48
57
|
"run_add_wizard",
|
|
58
|
+
# Log management
|
|
59
|
+
"get_mcp_logs_dir",
|
|
60
|
+
"get_log_file_path",
|
|
61
|
+
"read_logs",
|
|
62
|
+
"write_log",
|
|
63
|
+
"clear_logs",
|
|
64
|
+
"list_servers_with_logs",
|
|
65
|
+
"get_log_stats",
|
|
49
66
|
]
|