code-puppy 0.0.340__tar.gz → 0.0.342__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.340 → code_puppy-0.0.342}/PKG-INFO +1 -1
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/agents/base_agent.py +24 -1
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/claude_cache_client.py +104 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/autosave_menu.py +18 -24
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/prompt_toolkit_completion.py +24 -32
- {code_puppy-0.0.340 → code_puppy-0.0.342}/pyproject.toml +1 -1
- {code_puppy-0.0.340 → code_puppy-0.0.342}/.gitignore +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/LICENSE +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/README.md +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/__main__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/agents/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/agents/agent_c_reviewer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/agents/agent_code_puppy.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/agents/agent_code_reviewer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/agents/agent_creator_agent.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/agents/agent_golang_reviewer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/agents/agent_manager.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/agents/agent_planning.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/agents/agent_python_programmer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/agents/agent_python_reviewer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/agents/agent_qa_expert.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/agents/agent_qa_kitten.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/agents/agent_security_auditor.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/agents/json_agent.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/agents/prompt_reviewer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/callbacks.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/chatgpt_codex_client.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/cli_runner.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/add_model_menu.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/attachments.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/clipboard.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/colors_menu.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/command_handler.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/command_registry.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/config_commands.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/core_commands.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/diff_menu.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/file_path_completion.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/load_context_completion.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/add_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/base.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/edit_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/handler.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/help_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/install_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/install_menu.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/list_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/logs_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/remove_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/restart_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/search_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/start_all_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/start_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/status_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/stop_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/test_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/utils.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp_completion.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/model_picker_completion.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/model_settings_menu.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/motd.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/onboarding_slides.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/onboarding_wizard.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/pin_command_completion.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/session_commands.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/utils.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/config.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/error_logging.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/gemini_code_assist.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/http_utils.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/keymap.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/main.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/mcp_/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/mcp_/async_lifecycle.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/mcp_/blocking_startup.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/mcp_/captured_stdio_server.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/mcp_/circuit_breaker.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/mcp_/config_wizard.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/mcp_/dashboard.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/mcp_/error_isolation.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/mcp_/examples/retry_example.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/mcp_/health_monitor.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/mcp_/managed_server.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/mcp_/manager.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/mcp_/mcp_logs.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/mcp_/registry.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/mcp_/retry_manager.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/mcp_/server_registry_catalog.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/mcp_/status_tracker.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/mcp_/system_tools.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/messaging/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/messaging/bus.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/messaging/commands.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/messaging/markdown_patches.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/messaging/message_queue.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/messaging/messages.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/messaging/queue_console.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/messaging/renderers.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/messaging/rich_renderer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/messaging/spinner/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/messaging/spinner/console_spinner.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/messaging/spinner/spinner_base.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/model_factory.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/model_utils.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/models.json +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/models_dev_parser.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/antigravity_oauth/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/antigravity_oauth/accounts.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/antigravity_oauth/antigravity_model.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/antigravity_oauth/config.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/antigravity_oauth/constants.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/antigravity_oauth/oauth.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/antigravity_oauth/register_callbacks.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/antigravity_oauth/storage.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/antigravity_oauth/test_plugin.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/antigravity_oauth/token.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/antigravity_oauth/transport.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/antigravity_oauth/utils.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/chatgpt_oauth/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/chatgpt_oauth/config.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/chatgpt_oauth/oauth_flow.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/chatgpt_oauth/test_plugin.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/chatgpt_oauth/utils.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/claude_code_oauth/README.md +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/claude_code_oauth/SETUP.md +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/claude_code_oauth/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/claude_code_oauth/config.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/claude_code_oauth/register_callbacks.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/claude_code_oauth/test_plugin.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/claude_code_oauth/utils.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/customizable_commands/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/customizable_commands/register_callbacks.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/example_custom_command/README.md +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/file_permission_handler/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/file_permission_handler/register_callbacks.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/oauth_puppy_html.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/shell_safety/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/shell_safety/agent_shell_safety.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/shell_safety/command_cache.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/shell_safety/register_callbacks.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/prompts/codex_system_prompt.md +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/pydantic_patches.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/reopenable_async_client.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/round_robin_model.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/session_storage.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/status_display.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/summarization_agent.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/terminal_utils.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/tools/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/tools/agent_tools.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/tools/browser/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/tools/browser/browser_control.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/tools/browser/browser_interactions.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/tools/browser/browser_locators.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/tools/browser/browser_navigation.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/tools/browser/browser_screenshot.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/tools/browser/browser_scripts.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/tools/browser/browser_workflows.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/tools/browser/camoufox_manager.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/tools/browser/vqa_agent.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/tools/command_runner.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/tools/common.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/tools/file_modifications.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/tools/file_operations.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/tools/tools_content.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/uvx_detection.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/version_checker.py +0 -0
|
@@ -913,6 +913,11 @@ class BaseAgent(ABC):
|
|
|
913
913
|
"""
|
|
914
914
|
Truncate message history to manage token usage.
|
|
915
915
|
|
|
916
|
+
Protects:
|
|
917
|
+
- The first message (system prompt) - always kept
|
|
918
|
+
- The second message if it contains a ThinkingPart (extended thinking context)
|
|
919
|
+
- The most recent messages up to protected_tokens
|
|
920
|
+
|
|
916
921
|
Args:
|
|
917
922
|
messages: List of messages to truncate
|
|
918
923
|
protected_tokens: Number of tokens to protect
|
|
@@ -924,12 +929,30 @@ class BaseAgent(ABC):
|
|
|
924
929
|
|
|
925
930
|
emit_info("Truncating message history to manage token usage")
|
|
926
931
|
result = [messages[0]] # Always keep the first message (system prompt)
|
|
932
|
+
|
|
933
|
+
# Check if second message exists and contains a ThinkingPart
|
|
934
|
+
# If so, protect it (extended thinking context shouldn't be lost)
|
|
935
|
+
skip_second = False
|
|
936
|
+
if len(messages) > 1:
|
|
937
|
+
second_msg = messages[1]
|
|
938
|
+
has_thinking = any(
|
|
939
|
+
isinstance(part, ThinkingPart) for part in second_msg.parts
|
|
940
|
+
)
|
|
941
|
+
if has_thinking:
|
|
942
|
+
result.append(second_msg)
|
|
943
|
+
skip_second = True
|
|
944
|
+
|
|
927
945
|
num_tokens = 0
|
|
928
946
|
stack = queue.LifoQueue()
|
|
929
947
|
|
|
948
|
+
# Determine which messages to consider for the recent-tokens window
|
|
949
|
+
# Skip first message (already added), and skip second if it has thinking
|
|
950
|
+
start_idx = 2 if skip_second else 1
|
|
951
|
+
messages_to_scan = messages[start_idx:]
|
|
952
|
+
|
|
930
953
|
# Put messages in reverse order (most recent first) into the stack
|
|
931
954
|
# but break when we exceed protected_tokens
|
|
932
|
-
for
|
|
955
|
+
for msg in reversed(messages_to_scan):
|
|
933
956
|
num_tokens += self.estimate_tokens_for_message(msg)
|
|
934
957
|
if num_tokens > protected_tokens:
|
|
935
958
|
break
|
|
@@ -9,14 +9,19 @@ serialization, avoiding httpx/Pydantic internals.
|
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
|
+
import base64
|
|
12
13
|
import json
|
|
13
14
|
import logging
|
|
15
|
+
import time
|
|
14
16
|
from typing import Any, Callable, MutableMapping
|
|
15
17
|
|
|
16
18
|
import httpx
|
|
17
19
|
|
|
18
20
|
logger = logging.getLogger(__name__)
|
|
19
21
|
|
|
22
|
+
# Refresh token if it's older than 1 hour (3600 seconds)
|
|
23
|
+
TOKEN_MAX_AGE_SECONDS = 3600
|
|
24
|
+
|
|
20
25
|
try:
|
|
21
26
|
from anthropic import AsyncAnthropic
|
|
22
27
|
except ImportError: # pragma: no cover - optional dep
|
|
@@ -24,9 +29,108 @@ except ImportError: # pragma: no cover - optional dep
|
|
|
24
29
|
|
|
25
30
|
|
|
26
31
|
class ClaudeCacheAsyncClient(httpx.AsyncClient):
|
|
32
|
+
def _get_jwt_age_seconds(self, token: str | None) -> float | None:
|
|
33
|
+
"""Decode a JWT and return its age in seconds.
|
|
34
|
+
|
|
35
|
+
Returns None if the token can't be decoded or has no timestamp claims.
|
|
36
|
+
Uses 'iat' (issued at) if available, otherwise calculates from 'exp'.
|
|
37
|
+
"""
|
|
38
|
+
if not token:
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
# JWT format: header.payload.signature
|
|
43
|
+
# We only need the payload (second part)
|
|
44
|
+
parts = token.split(".")
|
|
45
|
+
if len(parts) != 3:
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
# Decode the payload (base64url encoded)
|
|
49
|
+
payload_b64 = parts[1]
|
|
50
|
+
# Add padding if needed (base64url doesn't require padding)
|
|
51
|
+
padding = 4 - len(payload_b64) % 4
|
|
52
|
+
if padding != 4:
|
|
53
|
+
payload_b64 += "=" * padding
|
|
54
|
+
|
|
55
|
+
payload_bytes = base64.urlsafe_b64decode(payload_b64)
|
|
56
|
+
payload = json.loads(payload_bytes.decode("utf-8"))
|
|
57
|
+
|
|
58
|
+
now = time.time()
|
|
59
|
+
|
|
60
|
+
# Prefer 'iat' (issued at) claim if available
|
|
61
|
+
if "iat" in payload:
|
|
62
|
+
iat = float(payload["iat"])
|
|
63
|
+
age = now - iat
|
|
64
|
+
return age
|
|
65
|
+
|
|
66
|
+
# Fall back to calculating from 'exp' claim
|
|
67
|
+
# Assume tokens are typically valid for 1 hour
|
|
68
|
+
if "exp" in payload:
|
|
69
|
+
exp = float(payload["exp"])
|
|
70
|
+
# If exp is in the future, calculate how long until expiry
|
|
71
|
+
# and assume the token was issued 1 hour before expiry
|
|
72
|
+
time_until_exp = exp - now
|
|
73
|
+
# If token has less than 1 hour left, it's "old"
|
|
74
|
+
age = TOKEN_MAX_AGE_SECONDS - time_until_exp
|
|
75
|
+
return max(0, age)
|
|
76
|
+
|
|
77
|
+
return None
|
|
78
|
+
except Exception as exc:
|
|
79
|
+
logger.debug("Failed to decode JWT age: %s", exc)
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
def _extract_bearer_token(self, request: httpx.Request) -> str | None:
|
|
83
|
+
"""Extract the bearer token from request headers."""
|
|
84
|
+
auth_header = request.headers.get("Authorization") or request.headers.get(
|
|
85
|
+
"authorization"
|
|
86
|
+
)
|
|
87
|
+
if auth_header and auth_header.lower().startswith("bearer "):
|
|
88
|
+
return auth_header[7:] # Strip "Bearer " prefix
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
def _should_refresh_token(self, request: httpx.Request) -> bool:
|
|
92
|
+
"""Check if the token in the request is older than 1 hour."""
|
|
93
|
+
token = self._extract_bearer_token(request)
|
|
94
|
+
if not token:
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
age = self._get_jwt_age_seconds(token)
|
|
98
|
+
if age is None:
|
|
99
|
+
return False
|
|
100
|
+
|
|
101
|
+
should_refresh = age >= TOKEN_MAX_AGE_SECONDS
|
|
102
|
+
if should_refresh:
|
|
103
|
+
logger.info(
|
|
104
|
+
"JWT token is %.1f seconds old (>= %d), will refresh proactively",
|
|
105
|
+
age,
|
|
106
|
+
TOKEN_MAX_AGE_SECONDS,
|
|
107
|
+
)
|
|
108
|
+
return should_refresh
|
|
109
|
+
|
|
27
110
|
async def send(
|
|
28
111
|
self, request: httpx.Request, *args: Any, **kwargs: Any
|
|
29
112
|
) -> httpx.Response: # type: ignore[override]
|
|
113
|
+
# Proactive token refresh: check JWT age before every request
|
|
114
|
+
if not request.extensions.get("claude_oauth_refresh_attempted"):
|
|
115
|
+
try:
|
|
116
|
+
if self._should_refresh_token(request):
|
|
117
|
+
refreshed_token = self._refresh_claude_oauth_token()
|
|
118
|
+
if refreshed_token:
|
|
119
|
+
logger.info("Proactively refreshed token before request")
|
|
120
|
+
# Rebuild request with new token
|
|
121
|
+
headers = dict(request.headers)
|
|
122
|
+
self._update_auth_headers(headers, refreshed_token)
|
|
123
|
+
body_bytes = self._extract_body_bytes(request)
|
|
124
|
+
request = self.build_request(
|
|
125
|
+
method=request.method,
|
|
126
|
+
url=request.url,
|
|
127
|
+
headers=headers,
|
|
128
|
+
content=body_bytes,
|
|
129
|
+
)
|
|
130
|
+
request.extensions["claude_oauth_refresh_attempted"] = True
|
|
131
|
+
except Exception as exc:
|
|
132
|
+
logger.debug("Error during proactive token refresh check: %s", exc)
|
|
133
|
+
|
|
30
134
|
try:
|
|
31
135
|
if request.url.path.endswith("/v1/messages"):
|
|
32
136
|
body_bytes = self._extract_body_bytes(request)
|
|
@@ -69,12 +69,21 @@ def _get_session_entries(base_dir: Path) -> List[Tuple[str, dict]]:
|
|
|
69
69
|
|
|
70
70
|
|
|
71
71
|
def _extract_last_user_message(history: list) -> str:
|
|
72
|
-
"""Extract the most recent user message from history.
|
|
72
|
+
"""Extract the most recent user message from history.
|
|
73
|
+
|
|
74
|
+
Joins all content parts from the message since messages can have
|
|
75
|
+
multiple parts (e.g., text + attachments, multi-part prompts).
|
|
76
|
+
"""
|
|
73
77
|
# Walk backwards through history to find last user message
|
|
74
78
|
for msg in reversed(history):
|
|
79
|
+
content_parts = []
|
|
75
80
|
for part in msg.parts:
|
|
76
81
|
if hasattr(part, "content"):
|
|
77
|
-
|
|
82
|
+
content = part.content
|
|
83
|
+
if isinstance(content, str) and content.strip():
|
|
84
|
+
content_parts.append(content)
|
|
85
|
+
if content_parts:
|
|
86
|
+
return "\n\n".join(content_parts)
|
|
78
87
|
return "[No messages found]"
|
|
79
88
|
|
|
80
89
|
|
|
@@ -298,19 +307,13 @@ def _render_message_browser_panel(
|
|
|
298
307
|
# Don't override Rich's ANSI styling - use empty style
|
|
299
308
|
text_color = ""
|
|
300
309
|
|
|
301
|
-
#
|
|
302
|
-
message_lines = rendered.split("\n")
|
|
303
|
-
is_truncated = len(rendered.split("\n")) > 35
|
|
310
|
+
# Show full message without truncation
|
|
311
|
+
message_lines = rendered.split("\n")
|
|
304
312
|
|
|
305
313
|
for line in message_lines:
|
|
306
314
|
lines.append((text_color, f" {line}"))
|
|
307
315
|
lines.append(("", "\n"))
|
|
308
316
|
|
|
309
|
-
if is_truncated:
|
|
310
|
-
lines.append(("", "\n"))
|
|
311
|
-
lines.append(("fg:yellow", " ... truncated (message too long)"))
|
|
312
|
-
lines.append(("", "\n"))
|
|
313
|
-
|
|
314
317
|
except Exception as e:
|
|
315
318
|
lines.append(("fg:red", f" Error rendering message: {e}"))
|
|
316
319
|
lines.append(("", "\n"))
|
|
@@ -359,7 +362,7 @@ def _render_preview_panel(base_dir: Path, entry: Optional[Tuple[str, dict]]) ->
|
|
|
359
362
|
lines.append(("", "\n\n"))
|
|
360
363
|
|
|
361
364
|
lines.append(("bold", " Last Message:"))
|
|
362
|
-
lines.append(("fg:ansibrightblack", " (press 'e' to browse
|
|
365
|
+
lines.append(("fg:ansibrightblack", " (press 'e' to browse full history)"))
|
|
363
366
|
lines.append(("", "\n"))
|
|
364
367
|
|
|
365
368
|
# Try to load and preview the last message
|
|
@@ -367,15 +370,11 @@ def _render_preview_panel(base_dir: Path, entry: Optional[Tuple[str, dict]]) ->
|
|
|
367
370
|
history = load_session(session_name, base_dir)
|
|
368
371
|
last_message = _extract_last_user_message(history)
|
|
369
372
|
|
|
370
|
-
#
|
|
371
|
-
original_lines = last_message.split("\n") if last_message else []
|
|
372
|
-
is_long = len(original_lines) > 30
|
|
373
|
-
|
|
374
|
-
# Render markdown with rich but strip ANSI codes
|
|
373
|
+
# Render markdown with rich
|
|
375
374
|
console = Console(
|
|
376
375
|
file=StringIO(),
|
|
377
376
|
legacy_windows=False,
|
|
378
|
-
no_color=False,
|
|
377
|
+
no_color=False,
|
|
379
378
|
force_terminal=False,
|
|
380
379
|
width=76,
|
|
381
380
|
)
|
|
@@ -383,19 +382,14 @@ def _render_preview_panel(base_dir: Path, entry: Optional[Tuple[str, dict]]) ->
|
|
|
383
382
|
console.print(md)
|
|
384
383
|
rendered = console.file.getvalue()
|
|
385
384
|
|
|
386
|
-
#
|
|
387
|
-
message_lines = rendered.split("\n")
|
|
385
|
+
# Show full message without truncation
|
|
386
|
+
message_lines = rendered.split("\n")
|
|
388
387
|
|
|
389
388
|
for line in message_lines:
|
|
390
389
|
# Rich already rendered the markdown, just display it dimmed
|
|
391
390
|
lines.append(("fg:ansibrightblack", f" {line}"))
|
|
392
391
|
lines.append(("", "\n"))
|
|
393
392
|
|
|
394
|
-
if is_long:
|
|
395
|
-
lines.append(("", "\n"))
|
|
396
|
-
lines.append(("fg:yellow", " ... truncated"))
|
|
397
|
-
lines.append(("", "\n"))
|
|
398
|
-
|
|
399
393
|
except Exception as e:
|
|
400
394
|
lines.append(("fg:red", f" Error loading preview: {e}"))
|
|
401
395
|
lines.append(("", "\n"))
|
{code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/prompt_toolkit_completion.py
RENAMED
|
@@ -29,7 +29,6 @@ from code_puppy.command_line.attachments import (
|
|
|
29
29
|
)
|
|
30
30
|
from code_puppy.command_line.clipboard import (
|
|
31
31
|
capture_clipboard_image_to_pending,
|
|
32
|
-
get_clipboard_manager,
|
|
33
32
|
has_image_in_clipboard,
|
|
34
33
|
)
|
|
35
34
|
from code_puppy.command_line.command_registry import get_unique_commands
|
|
@@ -649,30 +648,18 @@ async def get_input_with_combined_completion(
|
|
|
649
648
|
else:
|
|
650
649
|
event.current_buffer.validate_and_handle()
|
|
651
650
|
|
|
652
|
-
# Handle bracketed paste
|
|
653
|
-
#
|
|
651
|
+
# Handle bracketed paste - TEXT ONLY, never check clipboard for images.
|
|
652
|
+
# Drag-and-drop sends file paths through this handler (as text), and paste
|
|
653
|
+
# operations send text through here too. We should NOT conflate this with
|
|
654
|
+
# clipboard image capture - those are separate operations (Ctrl+V / F3).
|
|
654
655
|
@bindings.add(Keys.BracketedPaste)
|
|
655
656
|
def handle_bracketed_paste(event):
|
|
656
|
-
"""Handle bracketed paste -
|
|
657
|
-
# The pasted data is in event.data
|
|
657
|
+
"""Handle bracketed paste - insert text data only."""
|
|
658
658
|
pasted_data = event.data
|
|
659
|
-
|
|
660
|
-
# Check if clipboard has an image (the pasted text might just be empty or a file path)
|
|
661
|
-
try:
|
|
662
|
-
if has_image_in_clipboard():
|
|
663
|
-
placeholder = capture_clipboard_image_to_pending()
|
|
664
|
-
if placeholder:
|
|
665
|
-
event.app.current_buffer.insert_text(placeholder + " ")
|
|
666
|
-
count = get_clipboard_manager().get_pending_count()
|
|
667
|
-
sys.stdout.write(f"\033[36m📋 +image ({count})\033[0m ")
|
|
668
|
-
sys.stdout.flush()
|
|
669
|
-
return # Don't also paste the text data
|
|
670
|
-
except Exception:
|
|
671
|
-
pass
|
|
672
|
-
|
|
673
|
-
# No image - insert the pasted text as normal
|
|
674
659
|
if pasted_data:
|
|
675
|
-
|
|
660
|
+
# Normalize Windows line endings to Unix style
|
|
661
|
+
sanitized_data = pasted_data.replace("\r\n", "\n").replace("\r", "\n")
|
|
662
|
+
event.app.current_buffer.insert_text(sanitized_data)
|
|
676
663
|
|
|
677
664
|
# Fallback Ctrl+V for terminals without bracketed paste support
|
|
678
665
|
@bindings.add("c-v", eager=True)
|
|
@@ -684,9 +671,9 @@ async def get_input_with_combined_completion(
|
|
|
684
671
|
placeholder = capture_clipboard_image_to_pending()
|
|
685
672
|
if placeholder:
|
|
686
673
|
event.app.current_buffer.insert_text(placeholder + " ")
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
674
|
+
# The placeholder itself is visible feedback - no need for extra output
|
|
675
|
+
# Use bell for audible feedback (works in most terminals)
|
|
676
|
+
event.app.output.bell()
|
|
690
677
|
return # Don't also paste text
|
|
691
678
|
except Exception:
|
|
692
679
|
pass # Fall through to text paste on any error
|
|
@@ -715,7 +702,7 @@ async def get_input_with_combined_completion(
|
|
|
715
702
|
timeout=2,
|
|
716
703
|
)
|
|
717
704
|
if result.returncode == 0:
|
|
718
|
-
text = result.stdout
|
|
705
|
+
text = result.stdout
|
|
719
706
|
else: # Linux
|
|
720
707
|
# Try xclip first, then xsel
|
|
721
708
|
for cmd in [
|
|
@@ -733,6 +720,10 @@ async def get_input_with_combined_completion(
|
|
|
733
720
|
continue
|
|
734
721
|
|
|
735
722
|
if text:
|
|
723
|
+
# Normalize Windows line endings to Unix style
|
|
724
|
+
text = text.replace("\r\n", "\n").replace("\r", "\n")
|
|
725
|
+
# Strip trailing newline that clipboard tools often add
|
|
726
|
+
text = text.rstrip("\n")
|
|
736
727
|
event.app.current_buffer.insert_text(text)
|
|
737
728
|
except Exception:
|
|
738
729
|
pass # Silently fail if text paste doesn't work
|
|
@@ -746,15 +737,16 @@ async def get_input_with_combined_completion(
|
|
|
746
737
|
placeholder = capture_clipboard_image_to_pending()
|
|
747
738
|
if placeholder:
|
|
748
739
|
event.app.current_buffer.insert_text(placeholder + " ")
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
740
|
+
# The placeholder itself is visible feedback
|
|
741
|
+
# Use bell for audible feedback (works in most terminals)
|
|
742
|
+
event.app.output.bell()
|
|
752
743
|
else:
|
|
753
|
-
|
|
754
|
-
|
|
744
|
+
# Insert a transient message that user can delete
|
|
745
|
+
event.app.current_buffer.insert_text("[⚠️ no image in clipboard] ")
|
|
746
|
+
event.app.output.bell()
|
|
755
747
|
except Exception:
|
|
756
|
-
|
|
757
|
-
|
|
748
|
+
event.app.current_buffer.insert_text("[❌ clipboard error] ")
|
|
749
|
+
event.app.output.bell()
|
|
758
750
|
|
|
759
751
|
session = PromptSession(
|
|
760
752
|
completer=completer,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/load_context_completion.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/catalog_server_installer.py
RENAMED
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/mcp/custom_server_installer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/command_line/model_picker_completion.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/antigravity_oauth/antigravity_model.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/antigravity_oauth/register_callbacks.py
RENAMED
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/antigravity_oauth/test_plugin.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/chatgpt_oauth/register_callbacks.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/claude_code_oauth/register_callbacks.py
RENAMED
|
File without changes
|
{code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/claude_code_oauth/test_plugin.py
RENAMED
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/customizable_commands/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/example_custom_command/README.md
RENAMED
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/file_permission_handler/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/shell_safety/agent_shell_safety.py
RENAMED
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.340 → code_puppy-0.0.342}/code_puppy/plugins/shell_safety/register_callbacks.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|