code-puppy 0.0.370__tar.gz → 0.0.371__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.370 → code_puppy-0.0.371}/PKG-INFO +1 -1
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/__init__.py +6 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/agent_manager.py +205 -1
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/agent_menu.py +281 -14
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/core_commands.py +3 -3
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/model_picker_completion.py +3 -20
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/config.py +44 -8
- code_puppy-0.0.371/code_puppy/model_switching.py +63 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/antigravity_oauth/register_callbacks.py +15 -8
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +2 -2
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/claude_code_oauth/register_callbacks.py +2 -30
- {code_puppy-0.0.370 → code_puppy-0.0.371}/pyproject.toml +1 -1
- {code_puppy-0.0.370 → code_puppy-0.0.371}/.gitignore +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/LICENSE +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/README.md +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/__init__.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/__main__.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/agent_c_reviewer.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/agent_code_puppy.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/agent_code_reviewer.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/agent_creator_agent.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/agent_golang_reviewer.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/agent_pack_leader.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/agent_planning.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/agent_python_programmer.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/agent_python_reviewer.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/agent_qa_expert.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/agent_qa_kitten.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/agent_security_auditor.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/agent_terminal_qa.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/base_agent.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/event_stream_handler.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/json_agent.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/pack/__init__.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/pack/bloodhound.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/pack/husky.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/pack/retriever.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/pack/shepherd.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/pack/terrier.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/pack/watchdog.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/prompt_reviewer.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/agents/subagent_stream_handler.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/api/__init__.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/api/app.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/api/main.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/api/pty_manager.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/api/routers/__init__.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/api/routers/agents.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/api/routers/commands.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/api/routers/config.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/api/routers/sessions.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/api/templates/terminal.html +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/api/websocket.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/callbacks.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/chatgpt_codex_client.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/claude_cache_client.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/cli_runner.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/__init__.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/add_model_menu.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/attachments.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/autosave_menu.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/clipboard.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/colors_menu.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/command_handler.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/command_registry.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/config_commands.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/diff_menu.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/file_path_completion.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/load_context_completion.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp/__init__.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp/base.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp/edit_command.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp/handler.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp/help_command.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp/install_command.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp/install_menu.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp/list_command.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp/logs_command.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp/remove_command.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp/restart_command.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp/search_command.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp/start_all_command.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp/start_command.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp/status_command.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp/stop_command.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp/test_command.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp/utils.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/mcp_completion.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/model_settings_menu.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/motd.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/onboarding_slides.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/onboarding_wizard.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/pin_command_completion.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/session_commands.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/command_line/utils.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/error_logging.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/gemini_code_assist.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/gemini_model.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/http_utils.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/keymap.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/main.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/mcp_/__init__.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/mcp_/async_lifecycle.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/mcp_/blocking_startup.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/mcp_/captured_stdio_server.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/mcp_/circuit_breaker.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/mcp_/config_wizard.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/mcp_/dashboard.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/mcp_/error_isolation.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/mcp_/examples/retry_example.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/mcp_/health_monitor.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/mcp_/managed_server.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/mcp_/manager.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/mcp_/mcp_logs.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/mcp_/registry.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/mcp_/retry_manager.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/mcp_/server_registry_catalog.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/mcp_/status_tracker.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/mcp_/system_tools.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/messaging/__init__.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/messaging/bus.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/messaging/commands.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/messaging/markdown_patches.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/messaging/message_queue.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/messaging/messages.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/messaging/queue_console.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/messaging/renderers.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/messaging/rich_renderer.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/messaging/spinner/__init__.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/messaging/spinner/console_spinner.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/messaging/spinner/spinner_base.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/messaging/subagent_console.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/model_factory.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/model_utils.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/models.json +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/models_dev_parser.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/__init__.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/antigravity_oauth/__init__.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/antigravity_oauth/accounts.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/antigravity_oauth/antigravity_model.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/antigravity_oauth/config.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/antigravity_oauth/constants.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/antigravity_oauth/oauth.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/antigravity_oauth/storage.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/antigravity_oauth/test_plugin.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/antigravity_oauth/token.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/antigravity_oauth/transport.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/antigravity_oauth/utils.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/chatgpt_oauth/__init__.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/chatgpt_oauth/config.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/chatgpt_oauth/oauth_flow.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/chatgpt_oauth/test_plugin.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/chatgpt_oauth/utils.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/claude_code_oauth/README.md +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/claude_code_oauth/SETUP.md +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/claude_code_oauth/__init__.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/claude_code_oauth/config.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/claude_code_oauth/test_plugin.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/claude_code_oauth/utils.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/customizable_commands/__init__.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/customizable_commands/register_callbacks.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/example_custom_command/README.md +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/file_permission_handler/__init__.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/file_permission_handler/register_callbacks.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/frontend_emitter/__init__.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/frontend_emitter/emitter.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/frontend_emitter/register_callbacks.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/oauth_puppy_html.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/shell_safety/__init__.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/shell_safety/agent_shell_safety.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/shell_safety/command_cache.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/plugins/shell_safety/register_callbacks.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/prompts/antigravity_system_prompt.md +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/pydantic_patches.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/reopenable_async_client.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/round_robin_model.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/session_storage.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/status_display.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/summarization_agent.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/terminal_utils.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/tools/__init__.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/tools/agent_tools.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/tools/browser/__init__.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/tools/browser/browser_control.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/tools/browser/browser_interactions.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/tools/browser/browser_locators.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/tools/browser/browser_manager.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/tools/browser/browser_navigation.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/tools/browser/browser_screenshot.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/tools/browser/browser_scripts.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/tools/browser/browser_workflows.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/tools/browser/chromium_terminal_manager.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/tools/browser/terminal_command_tools.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/tools/browser/terminal_screenshot_tools.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/tools/browser/terminal_tools.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/tools/command_runner.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/tools/common.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/tools/display.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/tools/file_modifications.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/tools/file_operations.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/tools/subagent_context.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/tools/tools_content.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/uvx_detection.py +0 -0
- {code_puppy-0.0.370 → code_puppy-0.0.371}/code_puppy/version_checker.py +0 -0
|
@@ -5,9 +5,12 @@ configurations, each with their own system prompts and tool sets.
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from .agent_manager import (
|
|
8
|
+
clone_agent,
|
|
9
|
+
delete_clone_agent,
|
|
8
10
|
get_agent_descriptions,
|
|
9
11
|
get_available_agents,
|
|
10
12
|
get_current_agent,
|
|
13
|
+
is_clone_agent_name,
|
|
11
14
|
load_agent,
|
|
12
15
|
refresh_agents,
|
|
13
16
|
set_current_agent,
|
|
@@ -15,8 +18,11 @@ from .agent_manager import (
|
|
|
15
18
|
from .subagent_stream_handler import subagent_stream_handler
|
|
16
19
|
|
|
17
20
|
__all__ = [
|
|
21
|
+
"clone_agent",
|
|
22
|
+
"delete_clone_agent",
|
|
18
23
|
"get_available_agents",
|
|
19
24
|
"get_current_agent",
|
|
25
|
+
"is_clone_agent_name",
|
|
20
26
|
"set_current_agent",
|
|
21
27
|
"load_agent",
|
|
22
28
|
"get_agent_descriptions",
|
|
@@ -4,6 +4,7 @@ import importlib
|
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
6
|
import pkgutil
|
|
7
|
+
import re
|
|
7
8
|
import uuid
|
|
8
9
|
from pathlib import Path
|
|
9
10
|
from typing import Dict, List, Optional, Type, Union
|
|
@@ -13,7 +14,7 @@ from pydantic_ai.messages import ModelMessage
|
|
|
13
14
|
from code_puppy.agents.base_agent import BaseAgent
|
|
14
15
|
from code_puppy.agents.json_agent import JSONAgent, discover_json_agents
|
|
15
16
|
from code_puppy.callbacks import on_agent_reload
|
|
16
|
-
from code_puppy.messaging import emit_warning
|
|
17
|
+
from code_puppy.messaging import emit_success, emit_warning
|
|
17
18
|
|
|
18
19
|
# Registry of available agents (Python classes and JSON file paths)
|
|
19
20
|
_AGENT_REGISTRY: Dict[str, Union[Type[BaseAgent], str]] = {}
|
|
@@ -467,3 +468,206 @@ def refresh_agents():
|
|
|
467
468
|
# Generate a message group ID for agent refreshing
|
|
468
469
|
message_group_id = str(uuid.uuid4())
|
|
469
470
|
_discover_agents(message_group_id=message_group_id)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
_CLONE_NAME_PATTERN = re.compile(r"^(?P<base>.+)-clone-(?P<index>\d+)$")
|
|
474
|
+
_CLONE_DISPLAY_PATTERN = re.compile(r"\s*\(Clone\s+\d+\)$", re.IGNORECASE)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def _strip_clone_suffix(agent_name: str) -> str:
|
|
478
|
+
"""Strip a trailing -clone-N suffix from a name if present."""
|
|
479
|
+
match = _CLONE_NAME_PATTERN.match(agent_name)
|
|
480
|
+
return match.group("base") if match else agent_name
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def _strip_clone_display_suffix(display_name: str) -> str:
|
|
484
|
+
"""Remove a trailing "(Clone N)" suffix from display names."""
|
|
485
|
+
cleaned = _CLONE_DISPLAY_PATTERN.sub("", display_name).strip()
|
|
486
|
+
return cleaned or display_name
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def is_clone_agent_name(agent_name: str) -> bool:
|
|
490
|
+
"""Return True if the agent name looks like a clone."""
|
|
491
|
+
return bool(_CLONE_NAME_PATTERN.match(agent_name))
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
def _default_display_name(agent_name: str) -> str:
|
|
495
|
+
"""Build a default display name from an agent name."""
|
|
496
|
+
title = agent_name.title()
|
|
497
|
+
return f"{title} 🤖"
|
|
498
|
+
|
|
499
|
+
|
|
500
|
+
def _build_clone_display_name(display_name: str, clone_index: int) -> str:
|
|
501
|
+
"""Build a clone display name based on the source display name."""
|
|
502
|
+
base_name = _strip_clone_display_suffix(display_name)
|
|
503
|
+
return f"{base_name} (Clone {clone_index})"
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def _filter_available_tools(tool_names: List[str]) -> List[str]:
|
|
507
|
+
"""Filter a tool list to only available tool names."""
|
|
508
|
+
from code_puppy.tools import get_available_tool_names
|
|
509
|
+
|
|
510
|
+
available_tools = set(get_available_tool_names())
|
|
511
|
+
return [tool for tool in tool_names if tool in available_tools]
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def _next_clone_index(
|
|
515
|
+
base_name: str, existing_names: set[str], agents_dir: Path
|
|
516
|
+
) -> int:
|
|
517
|
+
"""Compute the next clone index for a base name."""
|
|
518
|
+
clone_pattern = re.compile(rf"^{re.escape(base_name)}-clone-(\\d+)$")
|
|
519
|
+
indices = []
|
|
520
|
+
for name in existing_names:
|
|
521
|
+
match = clone_pattern.match(name)
|
|
522
|
+
if match:
|
|
523
|
+
indices.append(int(match.group(1)))
|
|
524
|
+
|
|
525
|
+
next_index = max(indices, default=0) + 1
|
|
526
|
+
while True:
|
|
527
|
+
clone_name = f"{base_name}-clone-{next_index}"
|
|
528
|
+
clone_path = agents_dir / f"{clone_name}.json"
|
|
529
|
+
if clone_name not in existing_names and not clone_path.exists():
|
|
530
|
+
return next_index
|
|
531
|
+
next_index += 1
|
|
532
|
+
|
|
533
|
+
|
|
534
|
+
def clone_agent(agent_name: str) -> Optional[str]:
|
|
535
|
+
"""Clone an agent definition into the user agents directory.
|
|
536
|
+
|
|
537
|
+
Args:
|
|
538
|
+
agent_name: Source agent name to clone.
|
|
539
|
+
|
|
540
|
+
Returns:
|
|
541
|
+
The cloned agent name, or None if cloning failed.
|
|
542
|
+
"""
|
|
543
|
+
# Generate a message group ID for agent cloning
|
|
544
|
+
message_group_id = str(uuid.uuid4())
|
|
545
|
+
_discover_agents(message_group_id=message_group_id)
|
|
546
|
+
|
|
547
|
+
agent_ref = _AGENT_REGISTRY.get(agent_name)
|
|
548
|
+
if agent_ref is None:
|
|
549
|
+
emit_warning(f"Agent '{agent_name}' not found for cloning.")
|
|
550
|
+
return None
|
|
551
|
+
|
|
552
|
+
from ..config import get_agent_pinned_model, get_user_agents_directory
|
|
553
|
+
|
|
554
|
+
agents_dir = Path(get_user_agents_directory())
|
|
555
|
+
base_name = _strip_clone_suffix(agent_name)
|
|
556
|
+
existing_names = set(_AGENT_REGISTRY.keys())
|
|
557
|
+
clone_index = _next_clone_index(base_name, existing_names, agents_dir)
|
|
558
|
+
clone_name = f"{base_name}-clone-{clone_index}"
|
|
559
|
+
clone_path = agents_dir / f"{clone_name}.json"
|
|
560
|
+
|
|
561
|
+
try:
|
|
562
|
+
if isinstance(agent_ref, str):
|
|
563
|
+
with open(agent_ref, "r", encoding="utf-8") as f:
|
|
564
|
+
source_config = json.load(f)
|
|
565
|
+
|
|
566
|
+
source_display_name = source_config.get("display_name")
|
|
567
|
+
if not source_display_name:
|
|
568
|
+
source_display_name = _default_display_name(base_name)
|
|
569
|
+
|
|
570
|
+
clone_config = dict(source_config)
|
|
571
|
+
clone_config["name"] = clone_name
|
|
572
|
+
clone_config["display_name"] = _build_clone_display_name(
|
|
573
|
+
source_display_name, clone_index
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
tools = source_config.get("tools", [])
|
|
577
|
+
clone_config["tools"] = (
|
|
578
|
+
_filter_available_tools(tools) if isinstance(tools, list) else []
|
|
579
|
+
)
|
|
580
|
+
|
|
581
|
+
if not clone_config.get("model"):
|
|
582
|
+
clone_config.pop("model", None)
|
|
583
|
+
else:
|
|
584
|
+
agent_instance = agent_ref()
|
|
585
|
+
clone_config = {
|
|
586
|
+
"name": clone_name,
|
|
587
|
+
"display_name": _build_clone_display_name(
|
|
588
|
+
agent_instance.display_name, clone_index
|
|
589
|
+
),
|
|
590
|
+
"description": agent_instance.description,
|
|
591
|
+
"system_prompt": agent_instance.get_system_prompt(),
|
|
592
|
+
"tools": _filter_available_tools(agent_instance.get_available_tools()),
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
user_prompt = agent_instance.get_user_prompt()
|
|
596
|
+
if user_prompt is not None:
|
|
597
|
+
clone_config["user_prompt"] = user_prompt
|
|
598
|
+
|
|
599
|
+
tools_config = agent_instance.get_tools_config()
|
|
600
|
+
if tools_config is not None:
|
|
601
|
+
clone_config["tools_config"] = tools_config
|
|
602
|
+
|
|
603
|
+
pinned_model = get_agent_pinned_model(agent_instance.name)
|
|
604
|
+
if pinned_model:
|
|
605
|
+
clone_config["model"] = pinned_model
|
|
606
|
+
except Exception as exc:
|
|
607
|
+
emit_warning(f"Failed to build clone for '{agent_name}': {exc}")
|
|
608
|
+
return None
|
|
609
|
+
|
|
610
|
+
if clone_path.exists():
|
|
611
|
+
emit_warning(f"Clone target '{clone_name}' already exists.")
|
|
612
|
+
return None
|
|
613
|
+
|
|
614
|
+
try:
|
|
615
|
+
with open(clone_path, "w", encoding="utf-8") as f:
|
|
616
|
+
json.dump(clone_config, f, indent=2, ensure_ascii=False)
|
|
617
|
+
emit_success(f"Cloned '{agent_name}' to '{clone_name}'.")
|
|
618
|
+
return clone_name
|
|
619
|
+
except Exception as exc:
|
|
620
|
+
emit_warning(f"Failed to write clone file '{clone_path}': {exc}")
|
|
621
|
+
return None
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
def delete_clone_agent(agent_name: str) -> bool:
|
|
625
|
+
"""Delete a cloned JSON agent definition.
|
|
626
|
+
|
|
627
|
+
Args:
|
|
628
|
+
agent_name: Clone agent name to delete.
|
|
629
|
+
|
|
630
|
+
Returns:
|
|
631
|
+
True if the clone was deleted, False otherwise.
|
|
632
|
+
"""
|
|
633
|
+
message_group_id = str(uuid.uuid4())
|
|
634
|
+
_discover_agents(message_group_id=message_group_id)
|
|
635
|
+
|
|
636
|
+
if not is_clone_agent_name(agent_name):
|
|
637
|
+
emit_warning(f"Agent '{agent_name}' is not a clone.")
|
|
638
|
+
return False
|
|
639
|
+
|
|
640
|
+
if get_current_agent_name() == agent_name:
|
|
641
|
+
emit_warning("Cannot delete the active agent. Switch agents first.")
|
|
642
|
+
return False
|
|
643
|
+
|
|
644
|
+
agent_ref = _AGENT_REGISTRY.get(agent_name)
|
|
645
|
+
if agent_ref is None:
|
|
646
|
+
emit_warning(f"Clone '{agent_name}' not found.")
|
|
647
|
+
return False
|
|
648
|
+
|
|
649
|
+
if not isinstance(agent_ref, str):
|
|
650
|
+
emit_warning(f"Clone '{agent_name}' is not a JSON agent.")
|
|
651
|
+
return False
|
|
652
|
+
|
|
653
|
+
clone_path = Path(agent_ref)
|
|
654
|
+
if not clone_path.exists():
|
|
655
|
+
emit_warning(f"Clone file for '{agent_name}' does not exist.")
|
|
656
|
+
return False
|
|
657
|
+
|
|
658
|
+
from ..config import get_user_agents_directory
|
|
659
|
+
|
|
660
|
+
agents_dir = Path(get_user_agents_directory()).resolve()
|
|
661
|
+
if clone_path.resolve().parent != agents_dir:
|
|
662
|
+
emit_warning(f"Refusing to delete non-user clone '{agent_name}'.")
|
|
663
|
+
return False
|
|
664
|
+
|
|
665
|
+
try:
|
|
666
|
+
clone_path.unlink()
|
|
667
|
+
emit_success(f"Deleted clone '{agent_name}'.")
|
|
668
|
+
_AGENT_REGISTRY.pop(agent_name, None)
|
|
669
|
+
_AGENT_HISTORIES.pop(agent_name, None)
|
|
670
|
+
return True
|
|
671
|
+
except Exception as exc:
|
|
672
|
+
emit_warning(f"Failed to delete clone '{agent_name}': {exc}")
|
|
673
|
+
return False
|
|
@@ -16,11 +16,22 @@ from prompt_toolkit.layout.controls import FormattedTextControl
|
|
|
16
16
|
from prompt_toolkit.widgets import Frame
|
|
17
17
|
|
|
18
18
|
from code_puppy.agents import (
|
|
19
|
+
clone_agent,
|
|
20
|
+
delete_clone_agent,
|
|
19
21
|
get_agent_descriptions,
|
|
20
22
|
get_available_agents,
|
|
21
23
|
get_current_agent,
|
|
24
|
+
is_clone_agent_name,
|
|
22
25
|
)
|
|
26
|
+
from code_puppy.command_line.model_picker_completion import load_model_names
|
|
27
|
+
from code_puppy.config import (
|
|
28
|
+
clear_agent_pinned_model,
|
|
29
|
+
get_agent_pinned_model,
|
|
30
|
+
set_agent_pinned_model,
|
|
31
|
+
)
|
|
32
|
+
from code_puppy.messaging import emit_info, emit_success, emit_warning
|
|
23
33
|
from code_puppy.tools.command_runner import set_awaiting_user_input
|
|
34
|
+
from code_puppy.tools.common import arrow_select_async
|
|
24
35
|
|
|
25
36
|
PAGE_SIZE = 10 # Agents per page
|
|
26
37
|
|
|
@@ -87,6 +98,166 @@ def _sanitize_display_text(text: str) -> str:
|
|
|
87
98
|
return cleaned
|
|
88
99
|
|
|
89
100
|
|
|
101
|
+
def _get_pinned_model(agent_name: str) -> Optional[str]:
|
|
102
|
+
"""Return the pinned model for an agent, if any.
|
|
103
|
+
|
|
104
|
+
Checks both built-in agent config and JSON agent files.
|
|
105
|
+
"""
|
|
106
|
+
import json
|
|
107
|
+
|
|
108
|
+
# First check built-in agent config
|
|
109
|
+
try:
|
|
110
|
+
pinned = get_agent_pinned_model(agent_name)
|
|
111
|
+
if pinned:
|
|
112
|
+
return pinned
|
|
113
|
+
except Exception:
|
|
114
|
+
pass # Continue to check JSON agents
|
|
115
|
+
|
|
116
|
+
# Check if it's a JSON agent
|
|
117
|
+
try:
|
|
118
|
+
from code_puppy.agents.json_agent import discover_json_agents
|
|
119
|
+
|
|
120
|
+
json_agents = discover_json_agents()
|
|
121
|
+
if agent_name in json_agents:
|
|
122
|
+
agent_file_path = json_agents[agent_name]
|
|
123
|
+
with open(agent_file_path, "r", encoding="utf-8") as f:
|
|
124
|
+
agent_config = json.load(f)
|
|
125
|
+
model = agent_config.get("model")
|
|
126
|
+
return model if model else None
|
|
127
|
+
except Exception:
|
|
128
|
+
pass # Return None if we can't read the JSON file
|
|
129
|
+
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _build_model_picker_choices(
|
|
134
|
+
pinned_model: Optional[str],
|
|
135
|
+
model_names: List[str],
|
|
136
|
+
) -> List[str]:
|
|
137
|
+
"""Build model picker choices with pinned/unpin indicators."""
|
|
138
|
+
choices = ["✓ (unpin)" if not pinned_model else " (unpin)"]
|
|
139
|
+
|
|
140
|
+
for model_name in model_names:
|
|
141
|
+
if model_name == pinned_model:
|
|
142
|
+
choices.append(f"✓ {model_name} (pinned)")
|
|
143
|
+
else:
|
|
144
|
+
choices.append(f" {model_name}")
|
|
145
|
+
|
|
146
|
+
return choices
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _normalize_model_choice(choice: str) -> str:
|
|
150
|
+
"""Normalize a picker choice into a model name or '(unpin)' string."""
|
|
151
|
+
cleaned = choice.strip()
|
|
152
|
+
if cleaned.startswith("✓"):
|
|
153
|
+
cleaned = cleaned.lstrip("✓").strip()
|
|
154
|
+
if cleaned.endswith(" (pinned)"):
|
|
155
|
+
cleaned = cleaned[: -len(" (pinned)")].strip()
|
|
156
|
+
return cleaned
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
async def _select_pinned_model(agent_name: str) -> Optional[str]:
|
|
160
|
+
"""Prompt for a model to pin to the agent."""
|
|
161
|
+
try:
|
|
162
|
+
model_names = load_model_names() or []
|
|
163
|
+
except Exception as exc:
|
|
164
|
+
emit_warning(f"Failed to load models: {exc}")
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
pinned_model = _get_pinned_model(agent_name)
|
|
168
|
+
choices = _build_model_picker_choices(pinned_model, model_names)
|
|
169
|
+
if not choices:
|
|
170
|
+
emit_warning("No models available to pin.")
|
|
171
|
+
return None
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
choice = await arrow_select_async(
|
|
175
|
+
f"Select a model to pin for '{agent_name}'",
|
|
176
|
+
choices,
|
|
177
|
+
)
|
|
178
|
+
except KeyboardInterrupt:
|
|
179
|
+
emit_info("Model pinning cancelled")
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
return _normalize_model_choice(choice)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _reload_agent_if_current(
|
|
186
|
+
agent_name: str,
|
|
187
|
+
pinned_model: Optional[str],
|
|
188
|
+
) -> None:
|
|
189
|
+
"""Reload the current agent when its pinned model changes."""
|
|
190
|
+
current_agent = get_current_agent()
|
|
191
|
+
if not current_agent or current_agent.name != agent_name:
|
|
192
|
+
return
|
|
193
|
+
|
|
194
|
+
try:
|
|
195
|
+
if hasattr(current_agent, "refresh_config"):
|
|
196
|
+
current_agent.refresh_config()
|
|
197
|
+
current_agent.reload_code_generation_agent()
|
|
198
|
+
if pinned_model:
|
|
199
|
+
emit_info(f"Active agent reloaded with pinned model '{pinned_model}'")
|
|
200
|
+
else:
|
|
201
|
+
emit_info("Active agent reloaded with default model")
|
|
202
|
+
except Exception as exc:
|
|
203
|
+
emit_warning(f"Pinned model applied but reload failed: {exc}")
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _apply_pinned_model(agent_name: str, model_choice: str) -> None:
|
|
207
|
+
"""Persist a pinned model selection for an agent.
|
|
208
|
+
|
|
209
|
+
Handles both built-in agents (via config) and JSON agents (via JSON file).
|
|
210
|
+
"""
|
|
211
|
+
import json
|
|
212
|
+
|
|
213
|
+
# Check if this is a JSON agent or a built-in agent
|
|
214
|
+
try:
|
|
215
|
+
from code_puppy.agents.json_agent import discover_json_agents
|
|
216
|
+
|
|
217
|
+
json_agents = discover_json_agents()
|
|
218
|
+
is_json_agent = agent_name in json_agents
|
|
219
|
+
except Exception:
|
|
220
|
+
is_json_agent = False
|
|
221
|
+
|
|
222
|
+
try:
|
|
223
|
+
if is_json_agent:
|
|
224
|
+
# Handle JSON agent - modify the JSON file
|
|
225
|
+
agent_file_path = json_agents[agent_name]
|
|
226
|
+
|
|
227
|
+
with open(agent_file_path, "r", encoding="utf-8") as f:
|
|
228
|
+
agent_config = json.load(f)
|
|
229
|
+
|
|
230
|
+
if model_choice == "(unpin)":
|
|
231
|
+
# Remove the model key if it exists
|
|
232
|
+
if "model" in agent_config:
|
|
233
|
+
del agent_config["model"]
|
|
234
|
+
emit_success(f"Model pin cleared for '{agent_name}'")
|
|
235
|
+
pinned_model = None
|
|
236
|
+
else:
|
|
237
|
+
# Set the model
|
|
238
|
+
agent_config["model"] = model_choice
|
|
239
|
+
emit_success(f"Pinned '{model_choice}' to '{agent_name}'")
|
|
240
|
+
pinned_model = model_choice
|
|
241
|
+
|
|
242
|
+
# Save the updated configuration
|
|
243
|
+
with open(agent_file_path, "w", encoding="utf-8") as f:
|
|
244
|
+
json.dump(agent_config, f, indent=2, ensure_ascii=False)
|
|
245
|
+
else:
|
|
246
|
+
# Handle built-in Python agent - use config functions
|
|
247
|
+
if model_choice == "(unpin)":
|
|
248
|
+
clear_agent_pinned_model(agent_name)
|
|
249
|
+
emit_success(f"Model pin cleared for '{agent_name}'")
|
|
250
|
+
pinned_model = None
|
|
251
|
+
else:
|
|
252
|
+
set_agent_pinned_model(agent_name, model_choice)
|
|
253
|
+
emit_success(f"Pinned '{model_choice}' to '{agent_name}'")
|
|
254
|
+
pinned_model = model_choice
|
|
255
|
+
|
|
256
|
+
_reload_agent_if_current(agent_name, pinned_model)
|
|
257
|
+
except Exception as exc:
|
|
258
|
+
emit_warning(f"Failed to apply pinned model: {exc}")
|
|
259
|
+
|
|
260
|
+
|
|
90
261
|
def _get_agent_entries() -> List[Tuple[str, str, str]]:
|
|
91
262
|
"""Get all agents with their display names and descriptions.
|
|
92
263
|
|
|
@@ -141,6 +312,7 @@ def _render_menu_panel(
|
|
|
141
312
|
name, display_name, _ = entries[i]
|
|
142
313
|
is_selected = i == selected_idx
|
|
143
314
|
is_current = name == current_agent_name
|
|
315
|
+
pinned_model = _get_pinned_model(name)
|
|
144
316
|
|
|
145
317
|
# Sanitize display name to avoid emoji rendering issues
|
|
146
318
|
safe_display_name = _sanitize_display_text(display_name)
|
|
@@ -153,6 +325,10 @@ def _render_menu_panel(
|
|
|
153
325
|
lines.append(("", " "))
|
|
154
326
|
lines.append(("", safe_display_name))
|
|
155
327
|
|
|
328
|
+
if pinned_model:
|
|
329
|
+
safe_pinned_model = _sanitize_display_text(pinned_model)
|
|
330
|
+
lines.append(("fg:ansiyellow", f" → {safe_pinned_model}"))
|
|
331
|
+
|
|
156
332
|
# Add current marker
|
|
157
333
|
if is_current:
|
|
158
334
|
lines.append(("fg:ansicyan", " ← current"))
|
|
@@ -167,6 +343,12 @@ def _render_menu_panel(
|
|
|
167
343
|
lines.append(("", "Page\n"))
|
|
168
344
|
lines.append(("fg:green", " Enter "))
|
|
169
345
|
lines.append(("", "Select\n"))
|
|
346
|
+
lines.append(("fg:ansibrightblack", " P "))
|
|
347
|
+
lines.append(("", "Pin model\n"))
|
|
348
|
+
lines.append(("fg:ansibrightblack", " C "))
|
|
349
|
+
lines.append(("", "Clone\n"))
|
|
350
|
+
lines.append(("fg:ansibrightblack", " D "))
|
|
351
|
+
lines.append(("", "Delete clone\n"))
|
|
170
352
|
lines.append(("fg:ansibrightred", " Ctrl+C "))
|
|
171
353
|
lines.append(("", "Cancel"))
|
|
172
354
|
|
|
@@ -198,6 +380,7 @@ def _render_preview_panel(
|
|
|
198
380
|
|
|
199
381
|
name, display_name, description = entry
|
|
200
382
|
is_current = name == current_agent_name
|
|
383
|
+
pinned_model = _get_pinned_model(name)
|
|
201
384
|
|
|
202
385
|
# Sanitize text to avoid emoji rendering issues
|
|
203
386
|
safe_display_name = _sanitize_display_text(display_name)
|
|
@@ -213,6 +396,15 @@ def _render_preview_panel(
|
|
|
213
396
|
lines.append(("fg:ansicyan", safe_display_name))
|
|
214
397
|
lines.append(("", "\n\n"))
|
|
215
398
|
|
|
399
|
+
# Pinned model
|
|
400
|
+
lines.append(("bold", "Pinned Model: "))
|
|
401
|
+
if pinned_model:
|
|
402
|
+
safe_pinned_model = _sanitize_display_text(pinned_model)
|
|
403
|
+
lines.append(("fg:ansiyellow", safe_pinned_model))
|
|
404
|
+
else:
|
|
405
|
+
lines.append(("fg:ansibrightblack", "default"))
|
|
406
|
+
lines.append(("", "\n\n"))
|
|
407
|
+
|
|
216
408
|
# Description
|
|
217
409
|
lines.append(("bold", "Description:"))
|
|
218
410
|
lines.append(("", "\n"))
|
|
@@ -261,8 +453,6 @@ async def interactive_agent_picker() -> Optional[str]:
|
|
|
261
453
|
current_agent_name = current_agent.name if current_agent else ""
|
|
262
454
|
|
|
263
455
|
if not entries:
|
|
264
|
-
from code_puppy.messaging import emit_info
|
|
265
|
-
|
|
266
456
|
emit_info("No agents found.")
|
|
267
457
|
return None
|
|
268
458
|
|
|
@@ -270,14 +460,38 @@ async def interactive_agent_picker() -> Optional[str]:
|
|
|
270
460
|
selected_idx = [0] # Current selection (global index)
|
|
271
461
|
current_page = [0] # Current page
|
|
272
462
|
result = [None] # Selected agent name
|
|
463
|
+
pending_action = [None] # 'pin', 'clone', 'delete', or None
|
|
273
464
|
|
|
274
|
-
total_pages = (len(entries) + PAGE_SIZE - 1) // PAGE_SIZE
|
|
465
|
+
total_pages = [max(1, (len(entries) + PAGE_SIZE - 1) // PAGE_SIZE)]
|
|
275
466
|
|
|
276
467
|
def get_current_entry() -> Optional[Tuple[str, str, str]]:
|
|
277
468
|
if 0 <= selected_idx[0] < len(entries):
|
|
278
469
|
return entries[selected_idx[0]]
|
|
279
470
|
return None
|
|
280
471
|
|
|
472
|
+
def refresh_entries(selected_name: Optional[str] = None) -> None:
|
|
473
|
+
nonlocal entries
|
|
474
|
+
|
|
475
|
+
entries = _get_agent_entries()
|
|
476
|
+
total_pages[0] = max(1, (len(entries) + PAGE_SIZE - 1) // PAGE_SIZE)
|
|
477
|
+
|
|
478
|
+
if not entries:
|
|
479
|
+
selected_idx[0] = 0
|
|
480
|
+
current_page[0] = 0
|
|
481
|
+
return
|
|
482
|
+
|
|
483
|
+
if selected_name:
|
|
484
|
+
for idx, (name, _, _) in enumerate(entries):
|
|
485
|
+
if name == selected_name:
|
|
486
|
+
selected_idx[0] = idx
|
|
487
|
+
break
|
|
488
|
+
else:
|
|
489
|
+
selected_idx[0] = min(selected_idx[0], len(entries) - 1)
|
|
490
|
+
else:
|
|
491
|
+
selected_idx[0] = min(selected_idx[0], len(entries) - 1)
|
|
492
|
+
|
|
493
|
+
current_page[0] = selected_idx[0] // PAGE_SIZE
|
|
494
|
+
|
|
281
495
|
# Build UI
|
|
282
496
|
menu_control = FormattedTextControl(text="")
|
|
283
497
|
preview_control = FormattedTextControl(text="")
|
|
@@ -336,11 +550,29 @@ async def interactive_agent_picker() -> Optional[str]:
|
|
|
336
550
|
|
|
337
551
|
@kb.add("right")
|
|
338
552
|
def _(event):
|
|
339
|
-
if current_page[0] < total_pages - 1:
|
|
553
|
+
if current_page[0] < total_pages[0] - 1:
|
|
340
554
|
current_page[0] += 1
|
|
341
555
|
selected_idx[0] = current_page[0] * PAGE_SIZE
|
|
342
556
|
update_display()
|
|
343
557
|
|
|
558
|
+
@kb.add("p")
|
|
559
|
+
def _(event):
|
|
560
|
+
if get_current_entry():
|
|
561
|
+
pending_action[0] = "pin"
|
|
562
|
+
event.app.exit()
|
|
563
|
+
|
|
564
|
+
@kb.add("c")
|
|
565
|
+
def _(event):
|
|
566
|
+
if get_current_entry():
|
|
567
|
+
pending_action[0] = "clone"
|
|
568
|
+
event.app.exit()
|
|
569
|
+
|
|
570
|
+
@kb.add("d")
|
|
571
|
+
def _(event):
|
|
572
|
+
if get_current_entry():
|
|
573
|
+
pending_action[0] = "delete"
|
|
574
|
+
event.app.exit()
|
|
575
|
+
|
|
344
576
|
@kb.add("enter")
|
|
345
577
|
def _(event):
|
|
346
578
|
entry = get_current_entry()
|
|
@@ -370,15 +602,52 @@ async def interactive_agent_picker() -> Optional[str]:
|
|
|
370
602
|
time.sleep(0.05)
|
|
371
603
|
|
|
372
604
|
try:
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
sys.stdout.write("\033[2J\033[H")
|
|
378
|
-
sys.stdout.flush()
|
|
605
|
+
while True:
|
|
606
|
+
pending_action[0] = None
|
|
607
|
+
result[0] = None
|
|
608
|
+
update_display()
|
|
379
609
|
|
|
380
|
-
|
|
381
|
-
|
|
610
|
+
# Clear the current buffer
|
|
611
|
+
sys.stdout.write("\033[2J\033[H")
|
|
612
|
+
sys.stdout.flush()
|
|
613
|
+
|
|
614
|
+
# Run application
|
|
615
|
+
await app.run_async()
|
|
616
|
+
|
|
617
|
+
if pending_action[0] == "pin":
|
|
618
|
+
entry = get_current_entry()
|
|
619
|
+
if entry:
|
|
620
|
+
selected_model = await _select_pinned_model(entry[0])
|
|
621
|
+
if selected_model:
|
|
622
|
+
_apply_pinned_model(entry[0], selected_model)
|
|
623
|
+
continue
|
|
624
|
+
|
|
625
|
+
if pending_action[0] == "clone":
|
|
626
|
+
entry = get_current_entry()
|
|
627
|
+
selected_name = None
|
|
628
|
+
if entry:
|
|
629
|
+
cloned_name = clone_agent(entry[0])
|
|
630
|
+
selected_name = cloned_name or entry[0]
|
|
631
|
+
refresh_entries(selected_name=selected_name)
|
|
632
|
+
continue
|
|
633
|
+
|
|
634
|
+
if pending_action[0] == "delete":
|
|
635
|
+
entry = get_current_entry()
|
|
636
|
+
selected_name = None
|
|
637
|
+
if entry:
|
|
638
|
+
agent_name = entry[0]
|
|
639
|
+
selected_name = agent_name
|
|
640
|
+
if not is_clone_agent_name(agent_name):
|
|
641
|
+
emit_warning("Only cloned agents can be deleted.")
|
|
642
|
+
elif agent_name == current_agent_name:
|
|
643
|
+
emit_warning("Cannot delete the active agent. Switch first.")
|
|
644
|
+
else:
|
|
645
|
+
if delete_clone_agent(agent_name):
|
|
646
|
+
selected_name = None
|
|
647
|
+
refresh_entries(selected_name=selected_name)
|
|
648
|
+
continue
|
|
649
|
+
|
|
650
|
+
break
|
|
382
651
|
|
|
383
652
|
finally:
|
|
384
653
|
# Exit alternate screen buffer once at end
|
|
@@ -388,8 +657,6 @@ async def interactive_agent_picker() -> Optional[str]:
|
|
|
388
657
|
set_awaiting_user_input(False)
|
|
389
658
|
|
|
390
659
|
# Clear exit message
|
|
391
|
-
from code_puppy.messaging import emit_info
|
|
392
|
-
|
|
393
660
|
emit_info("✓ Exited agent picker")
|
|
394
661
|
|
|
395
662
|
return result[0]
|
|
@@ -169,7 +169,7 @@ def handle_tutorial_command(command: str) -> bool:
|
|
|
169
169
|
reset_onboarding,
|
|
170
170
|
run_onboarding_wizard,
|
|
171
171
|
)
|
|
172
|
-
from code_puppy.
|
|
172
|
+
from code_puppy.model_switching import set_model_and_reload_agent
|
|
173
173
|
|
|
174
174
|
# Always reset so user can re-run the tutorial anytime
|
|
175
175
|
reset_onboarding()
|
|
@@ -184,7 +184,7 @@ def handle_tutorial_command(command: str) -> bool:
|
|
|
184
184
|
from code_puppy.plugins.chatgpt_oauth.oauth_flow import run_oauth_flow
|
|
185
185
|
|
|
186
186
|
run_oauth_flow()
|
|
187
|
-
|
|
187
|
+
set_model_and_reload_agent("chatgpt-gpt-5.2-codex")
|
|
188
188
|
elif result == "claude":
|
|
189
189
|
emit_info("🔐 Starting Claude Code OAuth flow...")
|
|
190
190
|
from code_puppy.plugins.claude_code_oauth.register_callbacks import (
|
|
@@ -192,7 +192,7 @@ def handle_tutorial_command(command: str) -> bool:
|
|
|
192
192
|
)
|
|
193
193
|
|
|
194
194
|
_perform_authentication()
|
|
195
|
-
|
|
195
|
+
set_model_and_reload_agent("claude-code-claude-opus-4-5-20251101")
|
|
196
196
|
elif result == "completed":
|
|
197
197
|
emit_info("🎉 Tutorial complete! Happy coding!")
|
|
198
198
|
elif result == "skipped":
|