code-puppy 0.0.355__tar.gz → 0.0.357__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.355 → code_puppy-0.0.357}/PKG-INFO +1 -1
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/agent_qa_kitten.py +10 -5
- code_puppy-0.0.357/code_puppy/agents/agent_terminal_qa.py +323 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/api/app.py +79 -2
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/api/routers/commands.py +21 -2
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/api/routers/sessions.py +49 -8
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/config.py +5 -2
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/tools/__init__.py +37 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/tools/agent_tools.py +26 -1
- code_puppy-0.0.357/code_puppy/tools/browser/__init__.py +41 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/tools/browser/browser_control.py +6 -6
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/tools/browser/browser_interactions.py +21 -20
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/tools/browser/browser_locators.py +9 -9
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/tools/browser/browser_navigation.py +7 -7
- code_puppy-0.0.357/code_puppy/tools/browser/browser_screenshot.py +166 -0
- code_puppy-0.0.357/code_puppy/tools/browser/browser_screenshot_vqa.py +195 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/tools/browser/browser_scripts.py +15 -13
- code_puppy-0.0.357/code_puppy/tools/browser/camoufox_manager.py +397 -0
- code_puppy-0.0.357/code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
- code_puppy-0.0.357/code_puppy/tools/browser/terminal_command_tools.py +521 -0
- code_puppy-0.0.357/code_puppy/tools/browser/terminal_screenshot_tools.py +520 -0
- code_puppy-0.0.357/code_puppy/tools/browser/terminal_tools.py +525 -0
- code_puppy-0.0.357/code_puppy/tools/browser/vqa_agent.py +194 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/tools/command_runner.py +292 -101
- {code_puppy-0.0.355 → code_puppy-0.0.357}/pyproject.toml +1 -1
- code_puppy-0.0.355/code_puppy/tools/browser/__init__.py +0 -0
- code_puppy-0.0.355/code_puppy/tools/browser/browser_screenshot.py +0 -241
- code_puppy-0.0.355/code_puppy/tools/browser/camoufox_manager.py +0 -235
- code_puppy-0.0.355/code_puppy/tools/browser/vqa_agent.py +0 -90
- {code_puppy-0.0.355 → code_puppy-0.0.357}/.gitignore +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/LICENSE +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/README.md +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/__init__.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/__main__.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/__init__.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/agent_c_reviewer.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/agent_code_puppy.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/agent_code_reviewer.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/agent_creator_agent.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/agent_golang_reviewer.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/agent_manager.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/agent_pack_leader.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/agent_planning.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/agent_python_programmer.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/agent_python_reviewer.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/agent_qa_expert.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/agent_security_auditor.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/base_agent.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/event_stream_handler.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/json_agent.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/pack/__init__.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/pack/bloodhound.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/pack/husky.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/pack/retriever.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/pack/shepherd.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/pack/terrier.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/pack/watchdog.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/prompt_reviewer.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/agents/subagent_stream_handler.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/api/__init__.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/api/main.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/api/pty_manager.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/api/routers/__init__.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/api/routers/agents.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/api/routers/config.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/api/templates/terminal.html +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/api/websocket.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/callbacks.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/chatgpt_codex_client.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/claude_cache_client.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/cli_runner.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/__init__.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/add_model_menu.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/attachments.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/autosave_menu.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/clipboard.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/colors_menu.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/command_handler.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/command_registry.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/config_commands.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/core_commands.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/diff_menu.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/file_path_completion.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/load_context_completion.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp/__init__.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp/base.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp/edit_command.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp/handler.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp/help_command.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp/install_command.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp/install_menu.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp/list_command.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp/logs_command.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp/remove_command.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp/restart_command.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp/search_command.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp/start_all_command.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp/start_command.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp/status_command.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp/stop_command.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp/test_command.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp/utils.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/mcp_completion.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/model_picker_completion.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/model_settings_menu.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/motd.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/onboarding_slides.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/onboarding_wizard.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/pin_command_completion.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/session_commands.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/command_line/utils.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/error_logging.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/gemini_code_assist.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/http_utils.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/keymap.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/main.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/mcp_/__init__.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/mcp_/async_lifecycle.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/mcp_/blocking_startup.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/mcp_/captured_stdio_server.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/mcp_/circuit_breaker.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/mcp_/config_wizard.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/mcp_/dashboard.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/mcp_/error_isolation.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/mcp_/examples/retry_example.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/mcp_/health_monitor.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/mcp_/managed_server.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/mcp_/manager.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/mcp_/mcp_logs.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/mcp_/registry.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/mcp_/retry_manager.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/mcp_/server_registry_catalog.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/mcp_/status_tracker.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/mcp_/system_tools.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/messaging/__init__.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/messaging/bus.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/messaging/commands.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/messaging/markdown_patches.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/messaging/message_queue.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/messaging/messages.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/messaging/queue_console.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/messaging/renderers.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/messaging/rich_renderer.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/messaging/spinner/__init__.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/messaging/spinner/console_spinner.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/messaging/spinner/spinner_base.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/messaging/subagent_console.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/model_factory.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/model_utils.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/models.json +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/models_dev_api.json +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/models_dev_parser.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/__init__.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/__init__.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/accounts.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/antigravity_model.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/config.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/constants.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/oauth.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/register_callbacks.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/storage.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/test_plugin.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/token.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/transport.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/utils.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/chatgpt_oauth/__init__.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/chatgpt_oauth/config.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/chatgpt_oauth/oauth_flow.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/chatgpt_oauth/test_plugin.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/chatgpt_oauth/utils.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/claude_code_oauth/README.md +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/claude_code_oauth/SETUP.md +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/claude_code_oauth/__init__.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/claude_code_oauth/config.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/claude_code_oauth/register_callbacks.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/claude_code_oauth/test_plugin.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/claude_code_oauth/utils.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/customizable_commands/__init__.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/customizable_commands/register_callbacks.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/example_custom_command/README.md +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/file_permission_handler/__init__.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/file_permission_handler/register_callbacks.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/frontend_emitter/__init__.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/frontend_emitter/emitter.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/frontend_emitter/register_callbacks.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/oauth_puppy_html.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/shell_safety/__init__.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/shell_safety/agent_shell_safety.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/shell_safety/command_cache.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/plugins/shell_safety/register_callbacks.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/prompts/antigravity_system_prompt.md +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/prompts/codex_system_prompt.md +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/pydantic_patches.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/reopenable_async_client.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/round_robin_model.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/session_storage.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/status_display.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/summarization_agent.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/terminal_utils.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/tools/browser/browser_workflows.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/tools/common.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/tools/display.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/tools/file_modifications.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/tools/file_operations.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/tools/subagent_context.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/tools/tools_content.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/uvx_detection.py +0 -0
- {code_puppy-0.0.355 → code_puppy-0.0.357}/code_puppy/version_checker.py +0 -0
|
@@ -63,8 +63,8 @@ class QualityAssuranceKittenAgent(BaseAgent):
|
|
|
63
63
|
"browser_wait_for_element",
|
|
64
64
|
"browser_highlight_element",
|
|
65
65
|
"browser_clear_highlights",
|
|
66
|
-
# Screenshots and VQA
|
|
67
|
-
"
|
|
66
|
+
# Screenshots and VQA (uses dedicated VQA agent for context management)
|
|
67
|
+
"browser_screenshot_vqa",
|
|
68
68
|
# Workflow management
|
|
69
69
|
"browser_save_workflow",
|
|
70
70
|
"browser_list_workflows",
|
|
@@ -117,8 +117,12 @@ For any browser task, follow this approach:
|
|
|
117
117
|
|
|
118
118
|
### Visual Verification Workflow
|
|
119
119
|
- **Before critical actions**: Use browser_highlight_element to visually confirm
|
|
120
|
-
- **After interactions**: Use
|
|
121
|
-
- **
|
|
120
|
+
- **After interactions**: Use browser_screenshot_vqa to verify results
|
|
121
|
+
- **Ask specific questions**: The VQA tool requires a question like:
|
|
122
|
+
- "Is the login button visible?"
|
|
123
|
+
- "What error message is displayed?"
|
|
124
|
+
- "Is the form filled out correctly?"
|
|
125
|
+
- "What is the main heading text?"
|
|
122
126
|
|
|
123
127
|
### Form Input Best Practices
|
|
124
128
|
- **ALWAYS check current values** with browser_get_value before typing
|
|
@@ -183,7 +187,7 @@ For any browser task, follow this approach:
|
|
|
183
187
|
## Specialized Capabilities
|
|
184
188
|
|
|
185
189
|
🌐 **WCAG 2.2 Level AA Compliance**: Always prioritize accessibility in element discovery
|
|
186
|
-
📸 **Visual Question Answering**: Use
|
|
190
|
+
📸 **Visual Question Answering**: Use browser_screenshot_vqa for intelligent page analysis (uses dedicated VQA agent)
|
|
187
191
|
🚀 **Semantic Web Navigation**: Prefer role-based and label-based element discovery
|
|
188
192
|
⚡ **Playwright Power**: Full access to modern browser automation capabilities
|
|
189
193
|
📋 **Workflow Management**: Save, load, and reuse automation patterns for consistency
|
|
@@ -192,6 +196,7 @@ For any browser task, follow this approach:
|
|
|
192
196
|
|
|
193
197
|
- **ALWAYS check for existing workflows first** - Use browser_list_workflows at the start of new tasks
|
|
194
198
|
- **ALWAYS use browser_initialize before any browser operations**
|
|
199
|
+
- **ALWAYS close the browser at the end of every task** using browser_close
|
|
195
200
|
- **PREFER semantic locators over XPath** - they're more maintainable and accessible
|
|
196
201
|
- **Use visual verification for critical actions** - highlight elements and take screenshots
|
|
197
202
|
- **Be explicit about your reasoning** - use share_your_reasoning for complex workflows
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"""Terminal QA Agent - Terminal and TUI application testing with visual analysis."""
|
|
2
|
+
|
|
3
|
+
from .base_agent import BaseAgent
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TerminalQAAgent(BaseAgent):
|
|
7
|
+
"""Terminal QA Agent - Specialized for terminal and TUI application testing.
|
|
8
|
+
|
|
9
|
+
This agent tests terminal/TUI applications using Code Puppy's API server,
|
|
10
|
+
combining terminal command execution with visual analysis capabilities.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
@property
|
|
14
|
+
def name(self) -> str:
|
|
15
|
+
return "terminal-qa"
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def display_name(self) -> str:
|
|
19
|
+
return "Terminal QA Agent 🖥️"
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def description(self) -> str:
|
|
23
|
+
return "Terminal and TUI application testing agent with visual analysis"
|
|
24
|
+
|
|
25
|
+
def get_available_tools(self) -> list[str]:
|
|
26
|
+
"""Get the list of tools available to Terminal QA Agent.
|
|
27
|
+
|
|
28
|
+
Terminal-only tools for TUI/CLI testing. NO browser tools - those use
|
|
29
|
+
a different browser (CamoufoxManager) and don't work with terminals.
|
|
30
|
+
|
|
31
|
+
For terminal/TUI apps, you interact via keyboard (send_keys), not
|
|
32
|
+
by clicking on DOM elements like in a web browser.
|
|
33
|
+
"""
|
|
34
|
+
return [
|
|
35
|
+
# Core agent tools
|
|
36
|
+
"agent_share_your_reasoning",
|
|
37
|
+
# Terminal connection tools
|
|
38
|
+
"start_api_server",
|
|
39
|
+
"terminal_check_server",
|
|
40
|
+
"terminal_open",
|
|
41
|
+
"terminal_close",
|
|
42
|
+
# Terminal command execution tools
|
|
43
|
+
"terminal_run_command",
|
|
44
|
+
"terminal_send_keys",
|
|
45
|
+
"terminal_wait_output",
|
|
46
|
+
# Terminal screenshot and analysis tools
|
|
47
|
+
"terminal_screenshot_analyze",
|
|
48
|
+
"terminal_read_output",
|
|
49
|
+
"terminal_compare_mockup",
|
|
50
|
+
"load_image_for_analysis",
|
|
51
|
+
# NOTE: Browser tools (browser_click, browser_find_by_text, etc.)
|
|
52
|
+
# are NOT included because:
|
|
53
|
+
# 1. They use CamoufoxManager (web browser), not ChromiumTerminalManager
|
|
54
|
+
# 2. Terminal/TUI apps use keyboard input, not DOM clicking
|
|
55
|
+
# 3. Use terminal_send_keys for all terminal interaction!
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
def get_system_prompt(self) -> str:
|
|
59
|
+
"""Get Terminal QA Agent's specialized system prompt."""
|
|
60
|
+
return """
|
|
61
|
+
You are Terminal QA Agent 🖥️, a specialized agent for testing terminal and TUI (Text User Interface) applications!
|
|
62
|
+
|
|
63
|
+
You test terminal applications through Code Puppy's API server, which provides a browser-based terminal interface with xterm.js. This allows you to:
|
|
64
|
+
- Execute commands in a real terminal environment
|
|
65
|
+
- Take screenshots and analyze them with visual AI
|
|
66
|
+
- Compare terminal output to mockup designs
|
|
67
|
+
- Interact with terminal elements through the browser
|
|
68
|
+
|
|
69
|
+
## ⚠️ CRITICAL: Always Close the Browser!
|
|
70
|
+
|
|
71
|
+
**You MUST call `terminal_close()` before returning from ANY task!**
|
|
72
|
+
|
|
73
|
+
The browser window stays open and consumes resources until explicitly closed.
|
|
74
|
+
Always close it when you're done, even if the task failed or was interrupted.
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
# ALWAYS do this at the end of your task:
|
|
78
|
+
terminal_close()
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Core Workflow
|
|
82
|
+
|
|
83
|
+
For any terminal testing task, follow this workflow:
|
|
84
|
+
|
|
85
|
+
### 1. Start API Server (if needed)
|
|
86
|
+
First, ensure the Code Puppy API server is running. You can start it yourself:
|
|
87
|
+
```
|
|
88
|
+
start_api_server(port=8765)
|
|
89
|
+
```
|
|
90
|
+
This starts the server in the background. It's safe to call even if already running.
|
|
91
|
+
|
|
92
|
+
### 2. Check Server Health
|
|
93
|
+
Verify the server is healthy and ready:
|
|
94
|
+
```
|
|
95
|
+
terminal_check_server(host="localhost", port=8765)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 3. Open Terminal Browser
|
|
99
|
+
Open the browser-based terminal interface:
|
|
100
|
+
```
|
|
101
|
+
terminal_open(host="localhost", port=8765)
|
|
102
|
+
```
|
|
103
|
+
This launches a Chromium browser connected to the terminal endpoint.
|
|
104
|
+
|
|
105
|
+
### 4. Execute Commands
|
|
106
|
+
Run commands and read the output:
|
|
107
|
+
```
|
|
108
|
+
terminal_run_command(command="ls -la", wait_for_prompt=True)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### 5. Read Terminal Output (PRIMARY METHOD)
|
|
112
|
+
**Always prefer `terminal_read_output` over screenshots!**
|
|
113
|
+
|
|
114
|
+
Screenshots are EXPENSIVE (tokens) and should be avoided unless you specifically
|
|
115
|
+
need to see visual elements like colors, layouts, or TUI graphics.
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
# Use this for most tasks - fast and token-efficient!
|
|
119
|
+
terminal_read_output(lines=50)
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
This extracts the actual text from the terminal, which is perfect for:
|
|
123
|
+
- Verifying command output
|
|
124
|
+
- Checking for errors
|
|
125
|
+
- Parsing results
|
|
126
|
+
- Any text-based verification
|
|
127
|
+
|
|
128
|
+
### 6. Compare to Mockups
|
|
129
|
+
When given a mockup image, compare the terminal output:
|
|
130
|
+
```
|
|
131
|
+
terminal_compare_mockup(
|
|
132
|
+
mockup_path="/path/to/expected_output.png",
|
|
133
|
+
question="Does the terminal match the expected layout?"
|
|
134
|
+
)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 7. Interactive Testing
|
|
138
|
+
Use keyboard commands for interactive testing:
|
|
139
|
+
```
|
|
140
|
+
# Send Ctrl+C to interrupt
|
|
141
|
+
terminal_send_keys(keys="c", modifiers=["Control"])
|
|
142
|
+
|
|
143
|
+
# Send Tab for autocomplete
|
|
144
|
+
terminal_send_keys(keys="Tab")
|
|
145
|
+
|
|
146
|
+
# Navigate command history
|
|
147
|
+
terminal_send_keys(keys="ArrowUp")
|
|
148
|
+
|
|
149
|
+
# Navigate down 5 items in a menu (repeat parameter!)
|
|
150
|
+
terminal_send_keys(keys="ArrowDown", repeat=5)
|
|
151
|
+
|
|
152
|
+
# Move right 3 times with a delay for slow TUIs
|
|
153
|
+
terminal_send_keys(keys="ArrowRight", repeat=3, delay_ms=100)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### 8. Close Terminal (REQUIRED!)
|
|
157
|
+
**⚠️ You MUST always call this before returning!**
|
|
158
|
+
```
|
|
159
|
+
terminal_close()
|
|
160
|
+
```
|
|
161
|
+
Do NOT skip this step. Always close the browser when done.
|
|
162
|
+
|
|
163
|
+
## Tool Usage Guidelines
|
|
164
|
+
|
|
165
|
+
### ⚠️ IMPORTANT: Avoid Screenshots When Possible!
|
|
166
|
+
|
|
167
|
+
Screenshots are EXPENSIVE in terms of tokens and can cause context overflow.
|
|
168
|
+
**Use `terminal_read_output` as your PRIMARY tool for reading terminal state.**
|
|
169
|
+
|
|
170
|
+
### Reading Terminal Output (PREFERRED)
|
|
171
|
+
```python
|
|
172
|
+
# This is fast, cheap, and gives you actual text to work with
|
|
173
|
+
result = terminal_read_output(lines=50)
|
|
174
|
+
print(result["output"]) # The actual terminal text
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Use `terminal_read_output` for:
|
|
178
|
+
- ✅ Verifying command output
|
|
179
|
+
- ✅ Checking for error messages
|
|
180
|
+
- ✅ Parsing CLI results
|
|
181
|
+
- ✅ Any text-based verification
|
|
182
|
+
- ✅ Most testing scenarios!
|
|
183
|
+
|
|
184
|
+
### Screenshots (USE SPARINGLY)
|
|
185
|
+
Only use `terminal_screenshot` when you SPECIFICALLY need to see:
|
|
186
|
+
- 🎨 Colors or syntax highlighting
|
|
187
|
+
- 📐 Visual layout/positioning of TUI elements
|
|
188
|
+
- 🖼️ Graphics, charts, or visual elements
|
|
189
|
+
- 📊 When comparing to a visual mockup
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
# Only when visual verification is truly needed
|
|
193
|
+
terminal_screenshot() # Returns base64 image
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Mockup Comparison
|
|
197
|
+
When testing against design specifications:
|
|
198
|
+
1. Use `terminal_compare_mockup` with the mockup path
|
|
199
|
+
2. You'll receive both images as base64 - compare them visually
|
|
200
|
+
3. Report whether they match and any differences
|
|
201
|
+
|
|
202
|
+
### Interacting with Terminal/TUI Apps
|
|
203
|
+
Terminals use KEYBOARD input, not mouse clicks!
|
|
204
|
+
|
|
205
|
+
Use `terminal_send_keys` for ALL terminal interaction.
|
|
206
|
+
|
|
207
|
+
#### ⚠️ IMPORTANT: Use `repeat` parameter for multiple keypresses!
|
|
208
|
+
Don't call `terminal_send_keys` multiple times in a row - use the `repeat` parameter instead!
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
# ❌ BAD - Don't do this:
|
|
212
|
+
terminal_send_keys(keys="ArrowDown")
|
|
213
|
+
terminal_send_keys(keys="ArrowDown")
|
|
214
|
+
terminal_send_keys(keys="ArrowDown")
|
|
215
|
+
|
|
216
|
+
# ✅ GOOD - Use repeat parameter:
|
|
217
|
+
terminal_send_keys(keys="ArrowDown", repeat=3) # Move down 3 times in one call!
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
#### Navigation Examples:
|
|
221
|
+
```python
|
|
222
|
+
# Navigate down 5 items in a menu
|
|
223
|
+
terminal_send_keys(keys="ArrowDown", repeat=5)
|
|
224
|
+
|
|
225
|
+
# Navigate up 3 items
|
|
226
|
+
terminal_send_keys(keys="ArrowUp", repeat=3)
|
|
227
|
+
|
|
228
|
+
# Move right through tabs/panels
|
|
229
|
+
terminal_send_keys(keys="ArrowRight", repeat=2)
|
|
230
|
+
|
|
231
|
+
# Tab through 4 form fields
|
|
232
|
+
terminal_send_keys(keys="Tab", repeat=4)
|
|
233
|
+
|
|
234
|
+
# Select current item
|
|
235
|
+
terminal_send_keys(keys="Enter")
|
|
236
|
+
|
|
237
|
+
# For slow TUIs, add delay between keypresses
|
|
238
|
+
terminal_send_keys(keys="ArrowDown", repeat=10, delay_ms=100)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
#### Special Keys:
|
|
242
|
+
```python
|
|
243
|
+
terminal_send_keys(keys="Escape") # Cancel/back
|
|
244
|
+
terminal_send_keys(keys="c", modifiers=["Control"]) # Ctrl+C
|
|
245
|
+
terminal_send_keys(keys="d", modifiers=["Control"]) # Ctrl+D (EOF)
|
|
246
|
+
terminal_send_keys(keys="q") # Quit (common in TUIs)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
#### Type text:
|
|
250
|
+
```python
|
|
251
|
+
terminal_run_command("some text") # Type and press Enter
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**DO NOT use browser_* tools** - those are for web pages, not terminals!
|
|
255
|
+
|
|
256
|
+
## Testing Best Practices
|
|
257
|
+
|
|
258
|
+
### 1. Verify Before Acting
|
|
259
|
+
- Check server health before opening terminal
|
|
260
|
+
- Wait for commands to complete before analyzing
|
|
261
|
+
- Use `terminal_wait_output` when expecting specific output
|
|
262
|
+
|
|
263
|
+
### 2. Clear Error Detection
|
|
264
|
+
- Use `terminal_read_output` to check for error messages (NOT screenshots!)
|
|
265
|
+
- Search the text output for error patterns
|
|
266
|
+
- Check exit codes when possible
|
|
267
|
+
|
|
268
|
+
### 3. Visual Verification (Only When Necessary)
|
|
269
|
+
- Only take screenshots when you need to verify VISUAL elements
|
|
270
|
+
- For text verification, always use `terminal_read_output` instead
|
|
271
|
+
- Compare against mockups only when specifically requested
|
|
272
|
+
|
|
273
|
+
### 4. Structured Reporting
|
|
274
|
+
Always use `agent_share_your_reasoning` to explain:
|
|
275
|
+
- What you're testing
|
|
276
|
+
- What you observed
|
|
277
|
+
- Whether the test passed or failed
|
|
278
|
+
- Any issues or anomalies found
|
|
279
|
+
|
|
280
|
+
## Common Testing Scenarios
|
|
281
|
+
|
|
282
|
+
### TUI Application Testing
|
|
283
|
+
1. Launch the TUI application
|
|
284
|
+
2. Use `terminal_read_output` to verify text content
|
|
285
|
+
3. Send navigation keys (arrows, tab)
|
|
286
|
+
4. Read output again to verify changes
|
|
287
|
+
5. Only screenshot if you need to verify visual layout/colors
|
|
288
|
+
|
|
289
|
+
### CLI Output Verification
|
|
290
|
+
1. Run the CLI command
|
|
291
|
+
2. Use `terminal_read_output` to capture output (NOT screenshots!)
|
|
292
|
+
3. Verify expected output is present in the text
|
|
293
|
+
4. Check for unexpected errors in the text
|
|
294
|
+
|
|
295
|
+
### Interactive Session Testing
|
|
296
|
+
1. Start interactive session (e.g., Python REPL)
|
|
297
|
+
2. Send commands via `terminal_run_command`
|
|
298
|
+
3. Verify responses
|
|
299
|
+
4. Exit cleanly with appropriate keys
|
|
300
|
+
|
|
301
|
+
### Error Handling Verification
|
|
302
|
+
1. Trigger error conditions intentionally
|
|
303
|
+
2. Verify error messages appear correctly
|
|
304
|
+
3. Confirm recovery behavior
|
|
305
|
+
4. Document error scenarios
|
|
306
|
+
|
|
307
|
+
## Important Notes
|
|
308
|
+
|
|
309
|
+
- The terminal runs via a browser-based xterm.js interface
|
|
310
|
+
- Screenshots are saved to a temp directory for reference
|
|
311
|
+
- The terminal session persists until `terminal_close` is called
|
|
312
|
+
- Multiple commands can be run in sequence without reopening
|
|
313
|
+
|
|
314
|
+
## 🛑 FINAL REMINDER: ALWAYS CLOSE THE BROWSER!
|
|
315
|
+
|
|
316
|
+
Before you finish and return your response, you MUST call:
|
|
317
|
+
```
|
|
318
|
+
terminal_close()
|
|
319
|
+
```
|
|
320
|
+
This is not optional. Leaving the browser open wastes resources and can cause issues.
|
|
321
|
+
|
|
322
|
+
You are a thorough QA engineer who tests terminal applications systematically. Always verify your observations, provide clear test results, and ALWAYS close the terminal when done! 🖥️✅
|
|
323
|
+
"""
|
|
@@ -1,15 +1,89 @@
|
|
|
1
1
|
"""FastAPI application factory for Code Puppy API."""
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
4
|
+
import logging
|
|
5
|
+
from contextlib import asynccontextmanager
|
|
3
6
|
from pathlib import Path
|
|
7
|
+
from typing import AsyncGenerator
|
|
4
8
|
|
|
5
|
-
from fastapi import FastAPI
|
|
9
|
+
from fastapi import FastAPI, Request
|
|
6
10
|
from fastapi.middleware.cors import CORSMiddleware
|
|
7
|
-
from fastapi.responses import FileResponse, HTMLResponse
|
|
11
|
+
from fastapi.responses import FileResponse, HTMLResponse, JSONResponse
|
|
12
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
# Default request timeout (seconds) - fail fast!
|
|
17
|
+
REQUEST_TIMEOUT = 30.0
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class TimeoutMiddleware(BaseHTTPMiddleware):
|
|
21
|
+
"""Middleware to enforce request timeouts and prevent hanging requests."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, app, timeout: float = REQUEST_TIMEOUT):
|
|
24
|
+
super().__init__(app)
|
|
25
|
+
self.timeout = timeout
|
|
26
|
+
|
|
27
|
+
async def dispatch(self, request: Request, call_next):
|
|
28
|
+
# Skip timeout for WebSocket upgrades and streaming endpoints
|
|
29
|
+
if request.headers.get(
|
|
30
|
+
"upgrade", ""
|
|
31
|
+
).lower() == "websocket" or request.url.path.startswith("/ws/"):
|
|
32
|
+
return await call_next(request)
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
return await asyncio.wait_for(
|
|
36
|
+
call_next(request),
|
|
37
|
+
timeout=self.timeout,
|
|
38
|
+
)
|
|
39
|
+
except asyncio.TimeoutError:
|
|
40
|
+
return JSONResponse(
|
|
41
|
+
status_code=504,
|
|
42
|
+
content={
|
|
43
|
+
"detail": f"Request timed out after {self.timeout}s",
|
|
44
|
+
"error": "timeout",
|
|
45
|
+
},
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@asynccontextmanager
|
|
50
|
+
async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
|
|
51
|
+
"""Lifespan context manager for startup and shutdown events.
|
|
52
|
+
|
|
53
|
+
Handles graceful cleanup of resources when the server shuts down.
|
|
54
|
+
"""
|
|
55
|
+
# Startup: nothing special needed yet, but this is where you'd do it
|
|
56
|
+
logger.info("🐶 Code Puppy API starting up...")
|
|
57
|
+
yield
|
|
58
|
+
# Shutdown: clean up all the things!
|
|
59
|
+
logger.info("🐶 Code Puppy API shutting down, cleaning up...")
|
|
60
|
+
|
|
61
|
+
# 1. Close all PTY sessions
|
|
62
|
+
try:
|
|
63
|
+
from code_puppy.api.pty_manager import get_pty_manager
|
|
64
|
+
|
|
65
|
+
pty_manager = get_pty_manager()
|
|
66
|
+
await pty_manager.close_all()
|
|
67
|
+
logger.info("✓ All PTY sessions closed")
|
|
68
|
+
except Exception as e:
|
|
69
|
+
logger.error(f"Error closing PTY sessions: {e}")
|
|
70
|
+
|
|
71
|
+
# 2. Remove PID file so /api status knows we're gone
|
|
72
|
+
try:
|
|
73
|
+
from code_puppy.config import STATE_DIR
|
|
74
|
+
|
|
75
|
+
pid_file = Path(STATE_DIR) / "api_server.pid"
|
|
76
|
+
if pid_file.exists():
|
|
77
|
+
pid_file.unlink()
|
|
78
|
+
logger.info("✓ PID file removed")
|
|
79
|
+
except Exception as e:
|
|
80
|
+
logger.error(f"Error removing PID file: {e}")
|
|
8
81
|
|
|
9
82
|
|
|
10
83
|
def create_app() -> FastAPI:
|
|
11
84
|
"""Create and configure the FastAPI application."""
|
|
12
85
|
app = FastAPI(
|
|
86
|
+
lifespan=lifespan,
|
|
13
87
|
title="Code Puppy API",
|
|
14
88
|
description="REST API and Interactive Terminal for Code Puppy",
|
|
15
89
|
version="1.0.0",
|
|
@@ -17,6 +91,9 @@ def create_app() -> FastAPI:
|
|
|
17
91
|
redoc_url="/redoc",
|
|
18
92
|
)
|
|
19
93
|
|
|
94
|
+
# Timeout middleware - added first so it wraps everything
|
|
95
|
+
app.add_middleware(TimeoutMiddleware, timeout=REQUEST_TIMEOUT)
|
|
96
|
+
|
|
20
97
|
# CORS middleware for frontend access
|
|
21
98
|
app.add_middleware(
|
|
22
99
|
CORSMiddleware,
|
|
@@ -7,11 +7,19 @@ This router provides REST endpoints for:
|
|
|
7
7
|
- Autocomplete suggestions for partial commands
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
import asyncio
|
|
11
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
10
12
|
from typing import Any, List, Optional
|
|
11
13
|
|
|
12
14
|
from fastapi import APIRouter, HTTPException
|
|
13
15
|
from pydantic import BaseModel
|
|
14
16
|
|
|
17
|
+
# Thread pool for blocking command execution
|
|
18
|
+
_executor = ThreadPoolExecutor(max_workers=4)
|
|
19
|
+
|
|
20
|
+
# Timeout for command execution (seconds)
|
|
21
|
+
COMMAND_TIMEOUT = 30.0
|
|
22
|
+
|
|
15
23
|
router = APIRouter()
|
|
16
24
|
|
|
17
25
|
|
|
@@ -126,7 +134,8 @@ async def execute_command(request: CommandExecuteRequest) -> CommandExecuteRespo
|
|
|
126
134
|
"""Execute a slash command.
|
|
127
135
|
|
|
128
136
|
Takes a command string (with or without leading /) and executes it
|
|
129
|
-
using the command handler.
|
|
137
|
+
using the command handler. Runs in a thread pool to avoid blocking
|
|
138
|
+
the event loop, with a timeout to prevent hangs.
|
|
130
139
|
|
|
131
140
|
Args:
|
|
132
141
|
request: CommandExecuteRequest with the command to execute.
|
|
@@ -140,9 +149,19 @@ async def execute_command(request: CommandExecuteRequest) -> CommandExecuteRespo
|
|
|
140
149
|
if not command.startswith("/"):
|
|
141
150
|
command = "/" + command
|
|
142
151
|
|
|
152
|
+
loop = asyncio.get_running_loop()
|
|
153
|
+
|
|
143
154
|
try:
|
|
144
|
-
|
|
155
|
+
# Run blocking command in thread pool with timeout
|
|
156
|
+
result = await asyncio.wait_for(
|
|
157
|
+
loop.run_in_executor(_executor, handle_command, command),
|
|
158
|
+
timeout=COMMAND_TIMEOUT,
|
|
159
|
+
)
|
|
145
160
|
return CommandExecuteResponse(success=True, result=result)
|
|
161
|
+
except asyncio.TimeoutError:
|
|
162
|
+
return CommandExecuteResponse(
|
|
163
|
+
success=False, error=f"Command timed out after {COMMAND_TIMEOUT}s"
|
|
164
|
+
)
|
|
146
165
|
except Exception as e:
|
|
147
166
|
return CommandExecuteResponse(success=False, error=str(e))
|
|
148
167
|
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
"""Sessions API endpoints for retrieving subagent session data."""
|
|
2
2
|
|
|
3
|
+
import asyncio
|
|
3
4
|
import json
|
|
4
5
|
import pickle
|
|
6
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
5
7
|
from pathlib import Path
|
|
6
8
|
from typing import Any, Dict, List, Optional
|
|
7
9
|
|
|
8
10
|
from fastapi import APIRouter, HTTPException
|
|
9
11
|
from pydantic import BaseModel
|
|
10
12
|
|
|
13
|
+
# Thread pool for blocking file I/O
|
|
14
|
+
_executor = ThreadPoolExecutor(max_workers=2)
|
|
15
|
+
|
|
16
|
+
# Timeout for file operations (seconds)
|
|
17
|
+
FILE_IO_TIMEOUT = 10.0
|
|
18
|
+
|
|
11
19
|
router = APIRouter()
|
|
12
20
|
|
|
13
21
|
|
|
@@ -70,6 +78,18 @@ def _serialize_message(msg: Any) -> Dict[str, Any]:
|
|
|
70
78
|
return {"content": str(msg)}
|
|
71
79
|
|
|
72
80
|
|
|
81
|
+
def _load_json_sync(file_path: Path) -> dict:
|
|
82
|
+
"""Synchronous JSON file load (for use in executor)."""
|
|
83
|
+
with open(file_path, "r") as f:
|
|
84
|
+
return json.load(f)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _load_pickle_sync(file_path: Path) -> Any:
|
|
88
|
+
"""Synchronous pickle file load (for use in executor)."""
|
|
89
|
+
with open(file_path, "rb") as f:
|
|
90
|
+
return pickle.load(f)
|
|
91
|
+
|
|
92
|
+
|
|
73
93
|
@router.get("/")
|
|
74
94
|
async def list_sessions() -> List[SessionInfo]:
|
|
75
95
|
"""List all available sessions.
|
|
@@ -81,12 +101,17 @@ async def list_sessions() -> List[SessionInfo]:
|
|
|
81
101
|
if not sessions_dir.exists():
|
|
82
102
|
return []
|
|
83
103
|
|
|
104
|
+
loop = asyncio.get_running_loop()
|
|
84
105
|
sessions = []
|
|
106
|
+
|
|
85
107
|
for txt_file in sessions_dir.glob("*.txt"):
|
|
86
108
|
session_id = txt_file.stem
|
|
87
109
|
try:
|
|
88
|
-
|
|
89
|
-
|
|
110
|
+
# Run blocking I/O in thread pool with timeout
|
|
111
|
+
metadata = await asyncio.wait_for(
|
|
112
|
+
loop.run_in_executor(_executor, _load_json_sync, txt_file),
|
|
113
|
+
timeout=FILE_IO_TIMEOUT,
|
|
114
|
+
)
|
|
90
115
|
sessions.append(
|
|
91
116
|
SessionInfo(
|
|
92
117
|
session_id=session_id,
|
|
@@ -97,6 +122,9 @@ async def list_sessions() -> List[SessionInfo]:
|
|
|
97
122
|
message_count=metadata.get("message_count", 0),
|
|
98
123
|
)
|
|
99
124
|
)
|
|
125
|
+
except asyncio.TimeoutError:
|
|
126
|
+
# Timed out reading file, include basic info
|
|
127
|
+
sessions.append(SessionInfo(session_id=session_id))
|
|
100
128
|
except Exception:
|
|
101
129
|
# If we can't parse metadata, still include basic session info
|
|
102
130
|
sessions.append(SessionInfo(session_id=session_id))
|
|
@@ -115,7 +143,7 @@ async def get_session(session_id: str) -> SessionInfo:
|
|
|
115
143
|
SessionInfo with metadata for the specified session
|
|
116
144
|
|
|
117
145
|
Raises:
|
|
118
|
-
HTTPException: 404 if session not found
|
|
146
|
+
HTTPException: 404 if session not found, 504 on timeout
|
|
119
147
|
"""
|
|
120
148
|
sessions_dir = _get_sessions_dir()
|
|
121
149
|
txt_file = sessions_dir / f"{session_id}.txt"
|
|
@@ -123,8 +151,15 @@ async def get_session(session_id: str) -> SessionInfo:
|
|
|
123
151
|
if not txt_file.exists():
|
|
124
152
|
raise HTTPException(404, f"Session '{session_id}' not found")
|
|
125
153
|
|
|
126
|
-
|
|
127
|
-
|
|
154
|
+
loop = asyncio.get_running_loop()
|
|
155
|
+
|
|
156
|
+
try:
|
|
157
|
+
metadata = await asyncio.wait_for(
|
|
158
|
+
loop.run_in_executor(_executor, _load_json_sync, txt_file),
|
|
159
|
+
timeout=FILE_IO_TIMEOUT,
|
|
160
|
+
)
|
|
161
|
+
except asyncio.TimeoutError:
|
|
162
|
+
raise HTTPException(504, f"Timeout reading session '{session_id}'")
|
|
128
163
|
|
|
129
164
|
return SessionInfo(
|
|
130
165
|
session_id=session_id,
|
|
@@ -147,7 +182,7 @@ async def get_session_messages(session_id: str) -> List[Dict[str, Any]]:
|
|
|
147
182
|
List of serialized message dictionaries
|
|
148
183
|
|
|
149
184
|
Raises:
|
|
150
|
-
HTTPException: 404 if session messages not found, 500 on load error
|
|
185
|
+
HTTPException: 404 if session messages not found, 500 on load error, 504 on timeout
|
|
151
186
|
"""
|
|
152
187
|
sessions_dir = _get_sessions_dir()
|
|
153
188
|
pkl_file = sessions_dir / f"{session_id}.pkl"
|
|
@@ -155,10 +190,16 @@ async def get_session_messages(session_id: str) -> List[Dict[str, Any]]:
|
|
|
155
190
|
if not pkl_file.exists():
|
|
156
191
|
raise HTTPException(404, f"Session '{session_id}' messages not found")
|
|
157
192
|
|
|
193
|
+
loop = asyncio.get_running_loop()
|
|
194
|
+
|
|
158
195
|
try:
|
|
159
|
-
|
|
160
|
-
|
|
196
|
+
messages = await asyncio.wait_for(
|
|
197
|
+
loop.run_in_executor(_executor, _load_pickle_sync, pkl_file),
|
|
198
|
+
timeout=FILE_IO_TIMEOUT,
|
|
199
|
+
)
|
|
161
200
|
return [_serialize_message(msg) for msg in messages]
|
|
201
|
+
except asyncio.TimeoutError:
|
|
202
|
+
raise HTTPException(504, f"Timeout loading session '{session_id}' messages")
|
|
162
203
|
except Exception as e:
|
|
163
204
|
raise HTTPException(500, f"Error loading session messages: {e}")
|
|
164
205
|
|
|
@@ -504,11 +504,12 @@ def set_model_name(model: str):
|
|
|
504
504
|
|
|
505
505
|
|
|
506
506
|
def get_vqa_model_name() -> str:
|
|
507
|
-
"""Return the configured VQA model, falling back to
|
|
507
|
+
"""Return the configured VQA model, falling back to the global model."""
|
|
508
508
|
stored_model = get_value("vqa_model_name")
|
|
509
509
|
if stored_model and _validate_model_exists(stored_model):
|
|
510
510
|
return stored_model
|
|
511
|
-
|
|
511
|
+
# Fall back to the global model if no specific VQA model is set
|
|
512
|
+
return get_global_model_name()
|
|
512
513
|
|
|
513
514
|
|
|
514
515
|
def set_vqa_model_name(model: str):
|
|
@@ -1323,6 +1324,8 @@ DEFAULT_BANNER_COLORS = {
|
|
|
1323
1324
|
"invoke_agent": "deep_pink4", # Ruby - agent invocation
|
|
1324
1325
|
"subagent_response": "sea_green3", # Emerald - sub-agent success
|
|
1325
1326
|
"list_agents": "dark_slate_gray3", # Slate - neutral listing
|
|
1327
|
+
# Browser/Terminal tools - same color as edit_file (gold)
|
|
1328
|
+
"terminal_tool": "dark_goldenrod", # Gold - browser terminal operations
|
|
1326
1329
|
}
|
|
1327
1330
|
|
|
1328
1331
|
|