code-puppy 0.0.314__tar.gz → 0.0.316__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.314 → code_puppy-0.0.316}/PKG-INFO +1 -1
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/agents/base_agent.py +237 -29
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/chatgpt_codex_client.py +3 -1
- code_puppy-0.0.314/code_puppy/main.py → code_puppy-0.0.316/code_puppy/cli_runner.py +20 -32
- code_puppy-0.0.316/code_puppy/command_line/colors_menu.py +515 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/config_commands.py +31 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/model_settings_menu.py +46 -6
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/prompt_toolkit_completion.py +4 -9
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/config.py +100 -12
- code_puppy-0.0.316/code_puppy/main.py +10 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/mcp_/blocking_startup.py +11 -3
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/mcp_/managed_server.py +22 -2
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/mcp_/manager.py +65 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/messaging/rich_renderer.py +66 -43
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/messaging/spinner/console_spinner.py +6 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/model_factory.py +2 -2
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/models.json +4 -12
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/chatgpt_oauth/utils.py +11 -0
- code_puppy-0.0.316/code_puppy/terminal_utils.py +126 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/tools/agent_tools.py +4 -1
- {code_puppy-0.0.314 → code_puppy-0.0.316}/pyproject.toml +2 -1
- {code_puppy-0.0.314 → code_puppy-0.0.316}/.gitignore +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/LICENSE +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/README.md +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/__init__.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/__main__.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/agents/__init__.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/agents/agent_c_reviewer.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/agents/agent_code_puppy.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/agents/agent_code_reviewer.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/agents/agent_creator_agent.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/agents/agent_golang_reviewer.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/agents/agent_manager.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/agents/agent_planning.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/agents/agent_python_programmer.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/agents/agent_python_reviewer.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/agents/agent_qa_expert.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/agents/agent_qa_kitten.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/agents/agent_security_auditor.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/agents/json_agent.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/agents/prompt_reviewer.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/callbacks.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/claude_cache_client.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/__init__.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/add_model_menu.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/attachments.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/autosave_menu.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/command_handler.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/command_registry.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/core_commands.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/diff_menu.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/file_path_completion.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/load_context_completion.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/__init__.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/add_command.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/base.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/edit_command.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/handler.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/help_command.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/install_command.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/install_menu.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/list_command.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/logs_command.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/remove_command.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/restart_command.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/search_command.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/start_all_command.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/start_command.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/status_command.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/stop_command.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/test_command.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/utils.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/mcp_completion.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/model_picker_completion.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/motd.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/pin_command_completion.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/session_commands.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/command_line/utils.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/error_logging.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/gemini_code_assist.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/http_utils.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/keymap.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/mcp_/__init__.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/mcp_/async_lifecycle.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/mcp_/captured_stdio_server.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/mcp_/circuit_breaker.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/mcp_/config_wizard.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/mcp_/dashboard.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/mcp_/error_isolation.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/mcp_/examples/retry_example.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/mcp_/health_monitor.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/mcp_/registry.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/mcp_/retry_manager.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/mcp_/server_registry_catalog.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/mcp_/status_tracker.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/mcp_/system_tools.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/messaging/__init__.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/messaging/bus.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/messaging/commands.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/messaging/message_queue.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/messaging/messages.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/messaging/queue_console.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/messaging/renderers.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/messaging/spinner/__init__.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/messaging/spinner/spinner_base.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/model_utils.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/models_dev_parser.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/__init__.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/chatgpt_oauth/__init__.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/chatgpt_oauth/config.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/chatgpt_oauth/oauth_flow.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/chatgpt_oauth/test_plugin.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/claude_code_oauth/README.md +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/claude_code_oauth/SETUP.md +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/claude_code_oauth/__init__.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/claude_code_oauth/config.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/claude_code_oauth/register_callbacks.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/claude_code_oauth/test_plugin.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/claude_code_oauth/utils.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/customizable_commands/__init__.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/customizable_commands/register_callbacks.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/example_custom_command/README.md +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/file_permission_handler/__init__.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/file_permission_handler/register_callbacks.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/oauth_puppy_html.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/shell_safety/__init__.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/shell_safety/agent_shell_safety.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/shell_safety/command_cache.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/plugins/shell_safety/register_callbacks.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/prompts/codex_system_prompt.md +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/pydantic_patches.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/reopenable_async_client.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/round_robin_model.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/session_storage.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/status_display.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/summarization_agent.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/tools/__init__.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/tools/browser/__init__.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/tools/browser/browser_control.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/tools/browser/browser_interactions.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/tools/browser/browser_locators.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/tools/browser/browser_navigation.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/tools/browser/browser_screenshot.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/tools/browser/browser_scripts.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/tools/browser/browser_workflows.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/tools/browser/camoufox_manager.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/tools/browser/vqa_agent.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/tools/command_runner.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/tools/common.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/tools/file_modifications.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/tools/file_operations.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/tools/tools_content.py +0 -0
- {code_puppy-0.0.314 → code_puppy-0.0.316}/code_puppy/version_checker.py +0 -0
|
@@ -7,6 +7,7 @@ import signal
|
|
|
7
7
|
import threading
|
|
8
8
|
import uuid
|
|
9
9
|
from abc import ABC, abstractmethod
|
|
10
|
+
from collections.abc import AsyncIterable
|
|
10
11
|
from typing import Any, Callable, Dict, List, Optional, Sequence, Set, Tuple, Union
|
|
11
12
|
|
|
12
13
|
import mcp
|
|
@@ -18,6 +19,7 @@ from pydantic_ai import (
|
|
|
18
19
|
BinaryContent,
|
|
19
20
|
DocumentUrl,
|
|
20
21
|
ImageUrl,
|
|
22
|
+
PartEndEvent,
|
|
21
23
|
RunContext,
|
|
22
24
|
UsageLimitExceeded,
|
|
23
25
|
UsageLimits,
|
|
@@ -45,11 +47,10 @@ from code_puppy.config import (
|
|
|
45
47
|
get_protected_token_count,
|
|
46
48
|
get_use_dbos,
|
|
47
49
|
get_value,
|
|
48
|
-
load_mcp_server_configs,
|
|
49
50
|
)
|
|
50
51
|
from code_puppy.error_logging import log_error
|
|
51
52
|
from code_puppy.keymap import cancel_agent_uses_signal, get_cancel_agent_char_code
|
|
52
|
-
from code_puppy.mcp_ import
|
|
53
|
+
from code_puppy.mcp_ import get_mcp_manager
|
|
53
54
|
from code_puppy.messaging import (
|
|
54
55
|
emit_error,
|
|
55
56
|
emit_info,
|
|
@@ -987,45 +988,31 @@ class BaseAgent(ABC):
|
|
|
987
988
|
return self._puppy_rules
|
|
988
989
|
|
|
989
990
|
def load_mcp_servers(self, extra_headers: Optional[Dict[str, str]] = None):
|
|
990
|
-
"""Load MCP servers through the manager and return pydantic-ai compatible servers.
|
|
991
|
+
"""Load MCP servers through the manager and return pydantic-ai compatible servers.
|
|
992
|
+
|
|
993
|
+
Note: The manager automatically syncs from mcp_servers.json during initialization,
|
|
994
|
+
so we don't need to sync here. Use reload_mcp_servers() to force a re-sync.
|
|
995
|
+
"""
|
|
991
996
|
|
|
992
997
|
mcp_disabled = get_value("disable_mcp_servers")
|
|
993
998
|
if mcp_disabled and str(mcp_disabled).lower() in ("1", "true", "yes", "on"):
|
|
994
999
|
return []
|
|
995
1000
|
|
|
996
1001
|
manager = get_mcp_manager()
|
|
997
|
-
configs = load_mcp_server_configs()
|
|
998
|
-
if not configs:
|
|
999
|
-
existing_servers = manager.list_servers()
|
|
1000
|
-
if not existing_servers:
|
|
1001
|
-
return []
|
|
1002
|
-
else:
|
|
1003
|
-
for name, conf in configs.items():
|
|
1004
|
-
try:
|
|
1005
|
-
server_config = ServerConfig(
|
|
1006
|
-
id=conf.get("id", f"{name}_{hash(name)}"),
|
|
1007
|
-
name=name,
|
|
1008
|
-
type=conf.get("type", "sse"),
|
|
1009
|
-
enabled=conf.get("enabled", True),
|
|
1010
|
-
config=conf,
|
|
1011
|
-
)
|
|
1012
|
-
existing = manager.get_server_by_name(name)
|
|
1013
|
-
if not existing:
|
|
1014
|
-
manager.register_server(server_config)
|
|
1015
|
-
else:
|
|
1016
|
-
if existing.config != server_config.config:
|
|
1017
|
-
manager.update_server(existing.id, server_config)
|
|
1018
|
-
except Exception:
|
|
1019
|
-
continue
|
|
1020
|
-
|
|
1021
1002
|
return manager.get_servers_for_agent()
|
|
1022
1003
|
|
|
1023
1004
|
def reload_mcp_servers(self):
|
|
1024
|
-
"""Reload MCP servers and return updated servers.
|
|
1005
|
+
"""Reload MCP servers and return updated servers.
|
|
1006
|
+
|
|
1007
|
+
Forces a re-sync from mcp_servers.json to pick up any configuration changes.
|
|
1008
|
+
"""
|
|
1025
1009
|
# Clear the MCP tool cache when servers are reloaded
|
|
1026
1010
|
self._mcp_tool_definitions_cache = []
|
|
1027
|
-
|
|
1011
|
+
|
|
1012
|
+
# Force re-sync from mcp_servers.json
|
|
1028
1013
|
manager = get_mcp_manager()
|
|
1014
|
+
manager.sync_from_config()
|
|
1015
|
+
|
|
1029
1016
|
return manager.get_servers_for_agent()
|
|
1030
1017
|
|
|
1031
1018
|
def _load_model_with_fallback(
|
|
@@ -1265,6 +1252,224 @@ class BaseAgent(ABC):
|
|
|
1265
1252
|
self.set_message_history(result_messages_filtered_empty_thinking)
|
|
1266
1253
|
return self.get_message_history()
|
|
1267
1254
|
|
|
1255
|
+
async def _event_stream_handler(
|
|
1256
|
+
self, ctx: RunContext, events: AsyncIterable[Any]
|
|
1257
|
+
) -> None:
|
|
1258
|
+
"""Handle streaming events from the agent run.
|
|
1259
|
+
|
|
1260
|
+
This method processes streaming events and emits TextPart and ThinkingPart
|
|
1261
|
+
content with styled banners as they stream in.
|
|
1262
|
+
|
|
1263
|
+
Args:
|
|
1264
|
+
ctx: The run context.
|
|
1265
|
+
events: Async iterable of streaming events (PartStartEvent, PartDeltaEvent, etc.).
|
|
1266
|
+
"""
|
|
1267
|
+
import os
|
|
1268
|
+
import time as time_module
|
|
1269
|
+
|
|
1270
|
+
from pydantic_ai import PartDeltaEvent, PartStartEvent
|
|
1271
|
+
from pydantic_ai.messages import TextPartDelta, ThinkingPartDelta
|
|
1272
|
+
from rich.console import Console
|
|
1273
|
+
from rich.live import Live
|
|
1274
|
+
from rich.markdown import Markdown
|
|
1275
|
+
from rich.markup import escape
|
|
1276
|
+
|
|
1277
|
+
from code_puppy.messaging.spinner import pause_all_spinners
|
|
1278
|
+
|
|
1279
|
+
console = Console()
|
|
1280
|
+
|
|
1281
|
+
# Disable Live display in test mode or non-interactive environments
|
|
1282
|
+
# This fixes issues with pexpect PTY where Live() hangs
|
|
1283
|
+
use_live_display = (
|
|
1284
|
+
console.is_terminal
|
|
1285
|
+
and os.environ.get("CODE_PUPPY_TEST_FAST", "").lower() not in ("1", "true")
|
|
1286
|
+
and os.environ.get("CI", "").lower() not in ("1", "true")
|
|
1287
|
+
)
|
|
1288
|
+
|
|
1289
|
+
# Track which part indices we're currently streaming (for Text/Thinking parts)
|
|
1290
|
+
streaming_parts: set[int] = set()
|
|
1291
|
+
thinking_parts: set[int] = (
|
|
1292
|
+
set()
|
|
1293
|
+
) # Track which parts are thinking (for dim style)
|
|
1294
|
+
text_parts: set[int] = set() # Track which parts are text
|
|
1295
|
+
banner_printed: set[int] = set() # Track if banner was already printed
|
|
1296
|
+
text_buffer: dict[int, list[str]] = {} # Buffer text for markdown
|
|
1297
|
+
live_displays: dict[int, Live] = {} # Live displays for streaming markdown
|
|
1298
|
+
did_stream_anything = False # Track if we streamed any content
|
|
1299
|
+
last_render_time: dict[int, float] = {} # Track last render time per part
|
|
1300
|
+
render_interval = 0.1 # Only re-render markdown every 100ms (throttle)
|
|
1301
|
+
|
|
1302
|
+
def _print_thinking_banner() -> None:
|
|
1303
|
+
"""Print the THINKING banner with spinner pause and line clear."""
|
|
1304
|
+
nonlocal did_stream_anything
|
|
1305
|
+
import sys
|
|
1306
|
+
import time
|
|
1307
|
+
|
|
1308
|
+
from code_puppy.config import get_banner_color
|
|
1309
|
+
|
|
1310
|
+
pause_all_spinners()
|
|
1311
|
+
time.sleep(0.1) # Delay to let spinner fully clear
|
|
1312
|
+
sys.stdout.write("\r\x1b[K") # Clear line
|
|
1313
|
+
sys.stdout.flush()
|
|
1314
|
+
console.print() # Newline before banner
|
|
1315
|
+
# Bold banner with configurable color and lightning bolt
|
|
1316
|
+
thinking_color = get_banner_color("thinking")
|
|
1317
|
+
console.print(
|
|
1318
|
+
Text.from_markup(
|
|
1319
|
+
f"[bold white on {thinking_color}] THINKING [/bold white on {thinking_color}] [dim]⚡ "
|
|
1320
|
+
),
|
|
1321
|
+
end="",
|
|
1322
|
+
)
|
|
1323
|
+
sys.stdout.flush()
|
|
1324
|
+
did_stream_anything = True
|
|
1325
|
+
|
|
1326
|
+
def _print_response_banner() -> None:
|
|
1327
|
+
"""Print the AGENT RESPONSE banner with spinner pause and line clear."""
|
|
1328
|
+
nonlocal did_stream_anything
|
|
1329
|
+
import sys
|
|
1330
|
+
import time
|
|
1331
|
+
|
|
1332
|
+
from code_puppy.config import get_banner_color
|
|
1333
|
+
|
|
1334
|
+
pause_all_spinners()
|
|
1335
|
+
time.sleep(0.1) # Delay to let spinner fully clear
|
|
1336
|
+
sys.stdout.write("\r\x1b[K") # Clear line
|
|
1337
|
+
sys.stdout.flush()
|
|
1338
|
+
console.print() # Newline before banner
|
|
1339
|
+
response_color = get_banner_color("agent_response")
|
|
1340
|
+
console.print(
|
|
1341
|
+
Text.from_markup(
|
|
1342
|
+
f"[bold white on {response_color}] AGENT RESPONSE [/bold white on {response_color}]"
|
|
1343
|
+
)
|
|
1344
|
+
)
|
|
1345
|
+
sys.stdout.flush()
|
|
1346
|
+
did_stream_anything = True
|
|
1347
|
+
|
|
1348
|
+
async for event in events:
|
|
1349
|
+
# PartStartEvent - register the part but defer banner until content arrives
|
|
1350
|
+
if isinstance(event, PartStartEvent):
|
|
1351
|
+
part = event.part
|
|
1352
|
+
if isinstance(part, ThinkingPart):
|
|
1353
|
+
streaming_parts.add(event.index)
|
|
1354
|
+
thinking_parts.add(event.index)
|
|
1355
|
+
# If there's initial content, print banner + content now
|
|
1356
|
+
if part.content and part.content.strip():
|
|
1357
|
+
_print_thinking_banner()
|
|
1358
|
+
escaped = escape(part.content)
|
|
1359
|
+
console.print(f"[dim]{escaped}[/dim]", end="")
|
|
1360
|
+
banner_printed.add(event.index)
|
|
1361
|
+
elif isinstance(part, TextPart):
|
|
1362
|
+
streaming_parts.add(event.index)
|
|
1363
|
+
text_parts.add(event.index)
|
|
1364
|
+
text_buffer[event.index] = [] # Initialize buffer
|
|
1365
|
+
# Buffer initial content if present
|
|
1366
|
+
if part.content and part.content.strip():
|
|
1367
|
+
text_buffer[event.index].append(part.content)
|
|
1368
|
+
|
|
1369
|
+
# PartDeltaEvent - stream the content as it arrives
|
|
1370
|
+
elif isinstance(event, PartDeltaEvent):
|
|
1371
|
+
if event.index in streaming_parts:
|
|
1372
|
+
delta = event.delta
|
|
1373
|
+
if isinstance(delta, (TextPartDelta, ThinkingPartDelta)):
|
|
1374
|
+
if delta.content_delta:
|
|
1375
|
+
# For text parts, stream markdown with Live display
|
|
1376
|
+
if event.index in text_parts:
|
|
1377
|
+
# Print banner and start Live on first content
|
|
1378
|
+
if event.index not in banner_printed:
|
|
1379
|
+
_print_response_banner()
|
|
1380
|
+
banner_printed.add(event.index)
|
|
1381
|
+
# Only use Live display if enabled (disabled in test/CI)
|
|
1382
|
+
if use_live_display:
|
|
1383
|
+
live = Live(
|
|
1384
|
+
Markdown(""),
|
|
1385
|
+
console=console,
|
|
1386
|
+
refresh_per_second=10,
|
|
1387
|
+
vertical_overflow="visible", # Allow scrolling for long content
|
|
1388
|
+
)
|
|
1389
|
+
live.start()
|
|
1390
|
+
live_displays[event.index] = live
|
|
1391
|
+
# Accumulate text and throttle markdown rendering
|
|
1392
|
+
# (Markdown parsing is O(n), doing it on every token = O(n²) death)
|
|
1393
|
+
text_buffer[event.index].append(delta.content_delta)
|
|
1394
|
+
now = time_module.monotonic()
|
|
1395
|
+
last_render = last_render_time.get(event.index, 0)
|
|
1396
|
+
|
|
1397
|
+
# Only re-render if enough time has passed (throttle)
|
|
1398
|
+
# Skip Live updates when not using live display
|
|
1399
|
+
if (
|
|
1400
|
+
use_live_display
|
|
1401
|
+
and now - last_render >= render_interval
|
|
1402
|
+
):
|
|
1403
|
+
content = "".join(text_buffer[event.index])
|
|
1404
|
+
if event.index in live_displays:
|
|
1405
|
+
try:
|
|
1406
|
+
live_displays[event.index].update(
|
|
1407
|
+
Markdown(content)
|
|
1408
|
+
)
|
|
1409
|
+
last_render_time[event.index] = now
|
|
1410
|
+
except Exception:
|
|
1411
|
+
pass
|
|
1412
|
+
else:
|
|
1413
|
+
# For thinking parts, stream immediately (dim)
|
|
1414
|
+
if event.index not in banner_printed:
|
|
1415
|
+
_print_thinking_banner()
|
|
1416
|
+
banner_printed.add(event.index)
|
|
1417
|
+
escaped = escape(delta.content_delta)
|
|
1418
|
+
console.print(f"[dim]{escaped}[/dim]", end="")
|
|
1419
|
+
|
|
1420
|
+
# PartEndEvent - finish the streaming with a newline
|
|
1421
|
+
elif isinstance(event, PartEndEvent):
|
|
1422
|
+
if event.index in streaming_parts:
|
|
1423
|
+
# For text parts, do final render then stop the Live display
|
|
1424
|
+
if event.index in text_parts:
|
|
1425
|
+
# Final render to ensure we show complete content
|
|
1426
|
+
# (throttling may have skipped the last few tokens)
|
|
1427
|
+
if event.index in live_displays and event.index in text_buffer:
|
|
1428
|
+
try:
|
|
1429
|
+
final_content = "".join(text_buffer[event.index])
|
|
1430
|
+
live_displays[event.index].update(
|
|
1431
|
+
Markdown(final_content)
|
|
1432
|
+
)
|
|
1433
|
+
except Exception:
|
|
1434
|
+
pass
|
|
1435
|
+
if event.index in live_displays:
|
|
1436
|
+
try:
|
|
1437
|
+
live_displays[event.index].stop()
|
|
1438
|
+
except Exception:
|
|
1439
|
+
pass
|
|
1440
|
+
del live_displays[event.index]
|
|
1441
|
+
# When not using Live display, print the final content as markdown
|
|
1442
|
+
elif event.index in text_buffer:
|
|
1443
|
+
try:
|
|
1444
|
+
final_content = "".join(text_buffer[event.index])
|
|
1445
|
+
if final_content.strip():
|
|
1446
|
+
console.print(Markdown(final_content))
|
|
1447
|
+
except Exception:
|
|
1448
|
+
pass
|
|
1449
|
+
if event.index in text_buffer:
|
|
1450
|
+
del text_buffer[event.index]
|
|
1451
|
+
# Clean up render time tracking
|
|
1452
|
+
last_render_time.pop(event.index, None)
|
|
1453
|
+
# For thinking parts, just print newline
|
|
1454
|
+
elif event.index in banner_printed:
|
|
1455
|
+
console.print() # Final newline after streaming
|
|
1456
|
+
# Clean up all tracking sets
|
|
1457
|
+
streaming_parts.discard(event.index)
|
|
1458
|
+
thinking_parts.discard(event.index)
|
|
1459
|
+
text_parts.discard(event.index)
|
|
1460
|
+
banner_printed.discard(event.index)
|
|
1461
|
+
|
|
1462
|
+
# Resume spinner if next part is NOT text/thinking (avoid race condition)
|
|
1463
|
+
# If next part is a tool call or None, it's safe to resume
|
|
1464
|
+
# Note: spinner itself handles blank line before appearing
|
|
1465
|
+
from code_puppy.messaging.spinner import resume_all_spinners
|
|
1466
|
+
|
|
1467
|
+
next_kind = getattr(event, "next_part_kind", None)
|
|
1468
|
+
if next_kind not in ("text", "thinking"):
|
|
1469
|
+
resume_all_spinners()
|
|
1470
|
+
|
|
1471
|
+
# Spinner is resumed in PartEndEvent when appropriate (based on next_part_kind)
|
|
1472
|
+
|
|
1268
1473
|
def _spawn_ctrl_x_key_listener(
|
|
1269
1474
|
self,
|
|
1270
1475
|
stop_event: threading.Event,
|
|
@@ -1523,6 +1728,7 @@ class BaseAgent(ABC):
|
|
|
1523
1728
|
prompt_payload,
|
|
1524
1729
|
message_history=self.get_message_history(),
|
|
1525
1730
|
usage_limits=usage_limits,
|
|
1731
|
+
event_stream_handler=self._event_stream_handler,
|
|
1526
1732
|
**kwargs,
|
|
1527
1733
|
)
|
|
1528
1734
|
finally:
|
|
@@ -1535,6 +1741,7 @@ class BaseAgent(ABC):
|
|
|
1535
1741
|
prompt_payload,
|
|
1536
1742
|
message_history=self.get_message_history(),
|
|
1537
1743
|
usage_limits=usage_limits,
|
|
1744
|
+
event_stream_handler=self._event_stream_handler,
|
|
1538
1745
|
**kwargs,
|
|
1539
1746
|
)
|
|
1540
1747
|
else:
|
|
@@ -1543,6 +1750,7 @@ class BaseAgent(ABC):
|
|
|
1543
1750
|
prompt_payload,
|
|
1544
1751
|
message_history=self.get_message_history(),
|
|
1545
1752
|
usage_limits=usage_limits,
|
|
1753
|
+
event_stream_handler=self._event_stream_handler,
|
|
1546
1754
|
**kwargs,
|
|
1547
1755
|
)
|
|
1548
1756
|
return result_
|
|
@@ -140,9 +140,11 @@ class ChatGPTCodexAsyncClient(httpx.AsyncClient):
|
|
|
140
140
|
modified = True
|
|
141
141
|
|
|
142
142
|
# CRITICAL: ChatGPT Codex backend requires stream=true
|
|
143
|
+
# If stream is already true (e.g., pydantic-ai with event_stream_handler),
|
|
144
|
+
# don't force conversion - let streaming events flow through naturally
|
|
143
145
|
if data.get("stream") is not True:
|
|
144
146
|
data["stream"] = True
|
|
145
|
-
forced_stream = True
|
|
147
|
+
forced_stream = True # Only convert if WE forced streaming
|
|
146
148
|
modified = True
|
|
147
149
|
|
|
148
150
|
# Add reasoning settings for reasoning models (gpt-5.2, o-series, etc.)
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
"""CLI runner for Code Puppy.
|
|
2
|
+
|
|
3
|
+
Contains the main application logic, interactive mode, and entry point.
|
|
4
|
+
"""
|
|
5
|
+
|
|
1
6
|
# Apply pydantic-ai patches BEFORE any pydantic-ai imports
|
|
2
7
|
from code_puppy.pydantic_patches import apply_all_patches
|
|
3
8
|
|
|
@@ -6,8 +11,6 @@ apply_all_patches()
|
|
|
6
11
|
import argparse
|
|
7
12
|
import asyncio
|
|
8
13
|
import os
|
|
9
|
-
import platform
|
|
10
|
-
import subprocess
|
|
11
14
|
import sys
|
|
12
15
|
import time
|
|
13
16
|
import traceback
|
|
@@ -39,15 +42,19 @@ from code_puppy.keymap import (
|
|
|
39
42
|
validate_cancel_agent_key,
|
|
40
43
|
)
|
|
41
44
|
from code_puppy.messaging import emit_info
|
|
45
|
+
from code_puppy.terminal_utils import (
|
|
46
|
+
reset_unix_terminal,
|
|
47
|
+
reset_windows_terminal_ansi,
|
|
48
|
+
reset_windows_terminal_full,
|
|
49
|
+
)
|
|
42
50
|
from code_puppy.tools.common import console
|
|
43
|
-
|
|
44
|
-
# message_history_accumulator and prune_interrupted_tool_calls have been moved to BaseAgent class
|
|
45
51
|
from code_puppy.version_checker import default_version_mismatch_behavior
|
|
46
52
|
|
|
47
53
|
plugins.load_plugin_callbacks()
|
|
48
54
|
|
|
49
55
|
|
|
50
56
|
async def main():
|
|
57
|
+
"""Main async entry point for Code Puppy CLI."""
|
|
51
58
|
parser = argparse.ArgumentParser(description="Code Puppy - A code generation agent")
|
|
52
59
|
parser.add_argument(
|
|
53
60
|
"--version",
|
|
@@ -295,11 +302,9 @@ async def main():
|
|
|
295
302
|
DBOS.destroy()
|
|
296
303
|
|
|
297
304
|
|
|
298
|
-
# Add the file handling functionality for interactive mode
|
|
299
305
|
async def interactive_mode(message_renderer, initial_command: str = None) -> None:
|
|
300
|
-
from code_puppy.command_line.command_handler import handle_command
|
|
301
|
-
|
|
302
306
|
"""Run the agent in interactive mode."""
|
|
307
|
+
from code_puppy.command_line.command_handler import handle_command
|
|
303
308
|
|
|
304
309
|
display_console = message_renderer.console
|
|
305
310
|
from code_puppy.messaging import emit_info, emit_system_message
|
|
@@ -429,12 +434,7 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
|
429
434
|
# Use prompt_toolkit for enhanced input with path completion
|
|
430
435
|
try:
|
|
431
436
|
# Windows-specific: Reset terminal state before prompting
|
|
432
|
-
|
|
433
|
-
try:
|
|
434
|
-
sys.stdout.write("\x1b[0m") # Reset ANSI formatting
|
|
435
|
-
sys.stdout.flush()
|
|
436
|
-
except Exception:
|
|
437
|
-
pass
|
|
437
|
+
reset_windows_terminal_ansi()
|
|
438
438
|
|
|
439
439
|
# Use the async version of get_input_with_combined_completion
|
|
440
440
|
task = await get_input_with_combined_completion(
|
|
@@ -446,6 +446,9 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
|
446
446
|
|
|
447
447
|
except (KeyboardInterrupt, EOFError):
|
|
448
448
|
# Handle Ctrl+C or Ctrl+D
|
|
449
|
+
# Windows-specific: Reset terminal state after interrupt to prevent
|
|
450
|
+
# the terminal from becoming unresponsive (can't type characters)
|
|
451
|
+
reset_windows_terminal_full()
|
|
449
452
|
from code_puppy.messaging import emit_warning
|
|
450
453
|
|
|
451
454
|
emit_warning("\nInput cancelled")
|
|
@@ -601,14 +604,7 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
|
601
604
|
# Check if the task was cancelled (but don't show message if we just killed processes)
|
|
602
605
|
if result is None:
|
|
603
606
|
# Windows-specific: Reset terminal state after cancellation
|
|
604
|
-
|
|
605
|
-
try:
|
|
606
|
-
sys.stdout.write("\x1b[0m") # Reset ANSI formatting
|
|
607
|
-
sys.stdout.flush()
|
|
608
|
-
sys.stderr.write("\x1b[0m")
|
|
609
|
-
sys.stderr.flush()
|
|
610
|
-
except Exception:
|
|
611
|
-
pass
|
|
607
|
+
reset_windows_terminal_ansi()
|
|
612
608
|
continue
|
|
613
609
|
# Get the structured response
|
|
614
610
|
agent_response = result.output
|
|
@@ -651,6 +647,8 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
|
651
647
|
|
|
652
648
|
|
|
653
649
|
def prettier_code_blocks():
|
|
650
|
+
"""Configure Rich to use prettier code block rendering."""
|
|
651
|
+
|
|
654
652
|
class SimpleCodeBlock(CodeBlock):
|
|
655
653
|
def __rich_console__(
|
|
656
654
|
self, console: Console, options: ConsoleOptions
|
|
@@ -787,14 +785,4 @@ def main_entry():
|
|
|
787
785
|
return 0
|
|
788
786
|
finally:
|
|
789
787
|
# Reset terminal on Unix-like systems (not Windows)
|
|
790
|
-
|
|
791
|
-
try:
|
|
792
|
-
# Reset terminal to sanity state
|
|
793
|
-
subprocess.run(["reset"], check=True, capture_output=True)
|
|
794
|
-
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
795
|
-
# Silently fail if reset command isn't available
|
|
796
|
-
pass
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
if __name__ == "__main__":
|
|
800
|
-
main_entry()
|
|
788
|
+
reset_unix_terminal()
|