code-puppy 0.0.340__tar.gz → 0.0.341__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.341}/PKG-INFO +1 -1
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/agents/base_agent.py +24 -1
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/claude_cache_client.py +104 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/autosave_menu.py +18 -24
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/prompt_toolkit_completion.py +23 -17
- {code_puppy-0.0.340 → code_puppy-0.0.341}/pyproject.toml +1 -1
- {code_puppy-0.0.340 → code_puppy-0.0.341}/.gitignore +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/LICENSE +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/README.md +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/__main__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/agents/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/agents/agent_c_reviewer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/agents/agent_code_puppy.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/agents/agent_code_reviewer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/agents/agent_creator_agent.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/agents/agent_golang_reviewer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/agents/agent_manager.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/agents/agent_planning.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/agents/agent_python_programmer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/agents/agent_python_reviewer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/agents/agent_qa_expert.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/agents/agent_qa_kitten.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/agents/agent_security_auditor.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/agents/json_agent.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/agents/prompt_reviewer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/callbacks.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/chatgpt_codex_client.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/cli_runner.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/add_model_menu.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/attachments.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/clipboard.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/colors_menu.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/command_handler.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/command_registry.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/config_commands.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/core_commands.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/diff_menu.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/file_path_completion.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/load_context_completion.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/add_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/base.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/edit_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/handler.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/help_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/install_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/install_menu.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/list_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/logs_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/remove_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/restart_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/search_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/start_all_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/start_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/status_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/stop_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/test_command.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/utils.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/mcp_completion.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/model_picker_completion.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/model_settings_menu.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/motd.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/onboarding_slides.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/onboarding_wizard.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/pin_command_completion.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/session_commands.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/command_line/utils.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/config.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/error_logging.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/gemini_code_assist.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/http_utils.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/keymap.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/main.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/mcp_/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/mcp_/async_lifecycle.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/mcp_/blocking_startup.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/mcp_/captured_stdio_server.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/mcp_/circuit_breaker.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/mcp_/config_wizard.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/mcp_/dashboard.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/mcp_/error_isolation.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/mcp_/examples/retry_example.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/mcp_/health_monitor.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/mcp_/managed_server.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/mcp_/manager.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/mcp_/mcp_logs.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/mcp_/registry.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/mcp_/retry_manager.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/mcp_/server_registry_catalog.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/mcp_/status_tracker.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/mcp_/system_tools.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/messaging/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/messaging/bus.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/messaging/commands.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/messaging/markdown_patches.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/messaging/message_queue.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/messaging/messages.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/messaging/queue_console.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/messaging/renderers.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/messaging/rich_renderer.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/messaging/spinner/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/messaging/spinner/console_spinner.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/messaging/spinner/spinner_base.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/model_factory.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/model_utils.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/models.json +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/models_dev_parser.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/antigravity_oauth/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/antigravity_oauth/accounts.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/antigravity_oauth/antigravity_model.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/antigravity_oauth/config.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/antigravity_oauth/constants.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/antigravity_oauth/oauth.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/antigravity_oauth/register_callbacks.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/antigravity_oauth/storage.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/antigravity_oauth/test_plugin.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/antigravity_oauth/token.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/antigravity_oauth/transport.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/antigravity_oauth/utils.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/chatgpt_oauth/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/chatgpt_oauth/config.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/chatgpt_oauth/oauth_flow.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/chatgpt_oauth/test_plugin.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/chatgpt_oauth/utils.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/claude_code_oauth/README.md +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/claude_code_oauth/SETUP.md +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/claude_code_oauth/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/claude_code_oauth/config.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/claude_code_oauth/register_callbacks.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/claude_code_oauth/test_plugin.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/claude_code_oauth/utils.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/customizable_commands/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/customizable_commands/register_callbacks.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/example_custom_command/README.md +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/file_permission_handler/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/file_permission_handler/register_callbacks.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/oauth_puppy_html.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/shell_safety/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/shell_safety/agent_shell_safety.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/shell_safety/command_cache.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/shell_safety/register_callbacks.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/prompts/codex_system_prompt.md +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/pydantic_patches.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/reopenable_async_client.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/round_robin_model.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/session_storage.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/status_display.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/summarization_agent.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/terminal_utils.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/tools/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/tools/agent_tools.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/tools/browser/__init__.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/tools/browser/browser_control.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/tools/browser/browser_interactions.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/tools/browser/browser_locators.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/tools/browser/browser_navigation.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/tools/browser/browser_screenshot.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/tools/browser/browser_scripts.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/tools/browser/browser_workflows.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/tools/browser/camoufox_manager.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/tools/browser/vqa_agent.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/tools/command_runner.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/tools/common.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/tools/file_modifications.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/tools/file_operations.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/tools/tools_content.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/uvx_detection.py +0 -0
- {code_puppy-0.0.340 → code_puppy-0.0.341}/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.341}/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
|
|
@@ -663,16 +662,18 @@ async def get_input_with_combined_completion(
|
|
|
663
662
|
placeholder = capture_clipboard_image_to_pending()
|
|
664
663
|
if placeholder:
|
|
665
664
|
event.app.current_buffer.insert_text(placeholder + " ")
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
665
|
+
# The placeholder itself is visible feedback - no need for extra output
|
|
666
|
+
# Use bell for audible feedback (works in most terminals)
|
|
667
|
+
event.app.output.bell()
|
|
669
668
|
return # Don't also paste the text data
|
|
670
669
|
except Exception:
|
|
671
670
|
pass
|
|
672
671
|
|
|
673
|
-
# No image - insert the pasted text as normal
|
|
672
|
+
# No image - insert the pasted text as normal, sanitizing Windows newlines
|
|
674
673
|
if pasted_data:
|
|
675
|
-
|
|
674
|
+
# Normalize Windows line endings to Unix style
|
|
675
|
+
sanitized_data = pasted_data.replace("\r\n", "\n").replace("\r", "\n")
|
|
676
|
+
event.app.current_buffer.insert_text(sanitized_data)
|
|
676
677
|
|
|
677
678
|
# Fallback Ctrl+V for terminals without bracketed paste support
|
|
678
679
|
@bindings.add("c-v", eager=True)
|
|
@@ -684,9 +685,9 @@ async def get_input_with_combined_completion(
|
|
|
684
685
|
placeholder = capture_clipboard_image_to_pending()
|
|
685
686
|
if placeholder:
|
|
686
687
|
event.app.current_buffer.insert_text(placeholder + " ")
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
688
|
+
# The placeholder itself is visible feedback - no need for extra output
|
|
689
|
+
# Use bell for audible feedback (works in most terminals)
|
|
690
|
+
event.app.output.bell()
|
|
690
691
|
return # Don't also paste text
|
|
691
692
|
except Exception:
|
|
692
693
|
pass # Fall through to text paste on any error
|
|
@@ -715,7 +716,7 @@ async def get_input_with_combined_completion(
|
|
|
715
716
|
timeout=2,
|
|
716
717
|
)
|
|
717
718
|
if result.returncode == 0:
|
|
718
|
-
text = result.stdout
|
|
719
|
+
text = result.stdout
|
|
719
720
|
else: # Linux
|
|
720
721
|
# Try xclip first, then xsel
|
|
721
722
|
for cmd in [
|
|
@@ -733,6 +734,10 @@ async def get_input_with_combined_completion(
|
|
|
733
734
|
continue
|
|
734
735
|
|
|
735
736
|
if text:
|
|
737
|
+
# Normalize Windows line endings to Unix style
|
|
738
|
+
text = text.replace("\r\n", "\n").replace("\r", "\n")
|
|
739
|
+
# Strip trailing newline that clipboard tools often add
|
|
740
|
+
text = text.rstrip("\n")
|
|
736
741
|
event.app.current_buffer.insert_text(text)
|
|
737
742
|
except Exception:
|
|
738
743
|
pass # Silently fail if text paste doesn't work
|
|
@@ -746,15 +751,16 @@ async def get_input_with_combined_completion(
|
|
|
746
751
|
placeholder = capture_clipboard_image_to_pending()
|
|
747
752
|
if placeholder:
|
|
748
753
|
event.app.current_buffer.insert_text(placeholder + " ")
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
754
|
+
# The placeholder itself is visible feedback
|
|
755
|
+
# Use bell for audible feedback (works in most terminals)
|
|
756
|
+
event.app.output.bell()
|
|
752
757
|
else:
|
|
753
|
-
|
|
754
|
-
|
|
758
|
+
# Insert a transient message that user can delete
|
|
759
|
+
event.app.current_buffer.insert_text("[⚠️ no image in clipboard] ")
|
|
760
|
+
event.app.output.bell()
|
|
755
761
|
except Exception:
|
|
756
|
-
|
|
757
|
-
|
|
762
|
+
event.app.current_buffer.insert_text("[❌ clipboard error] ")
|
|
763
|
+
event.app.output.bell()
|
|
758
764
|
|
|
759
765
|
session = PromptSession(
|
|
760
766
|
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.341}/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.341}/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.341}/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.341}/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.341}/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.341}/code_puppy/plugins/antigravity_oauth/register_callbacks.py
RENAMED
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.340 → code_puppy-0.0.341}/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.341}/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.341}/code_puppy/plugins/claude_code_oauth/register_callbacks.py
RENAMED
|
File without changes
|
{code_puppy-0.0.340 → code_puppy-0.0.341}/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.341}/code_puppy/plugins/customizable_commands/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.340 → code_puppy-0.0.341}/code_puppy/plugins/example_custom_command/README.md
RENAMED
|
File without changes
|
|
File without changes
|
{code_puppy-0.0.340 → code_puppy-0.0.341}/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.341}/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.341}/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
|