code-puppy 0.0.356__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.
Files changed (219) hide show
  1. {code_puppy-0.0.356 → code_puppy-0.0.357}/PKG-INFO +1 -1
  2. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/agent_qa_kitten.py +10 -5
  3. code_puppy-0.0.357/code_puppy/agents/agent_terminal_qa.py +323 -0
  4. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/api/app.py +79 -2
  5. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/api/routers/commands.py +21 -2
  6. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/api/routers/sessions.py +49 -8
  7. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/config.py +5 -2
  8. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/tools/__init__.py +37 -0
  9. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/tools/agent_tools.py +26 -1
  10. code_puppy-0.0.357/code_puppy/tools/browser/__init__.py +41 -0
  11. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/tools/browser/browser_control.py +6 -6
  12. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/tools/browser/browser_interactions.py +21 -20
  13. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/tools/browser/browser_locators.py +9 -9
  14. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/tools/browser/browser_navigation.py +7 -7
  15. code_puppy-0.0.357/code_puppy/tools/browser/browser_screenshot.py +166 -0
  16. code_puppy-0.0.357/code_puppy/tools/browser/browser_screenshot_vqa.py +195 -0
  17. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/tools/browser/browser_scripts.py +15 -13
  18. code_puppy-0.0.357/code_puppy/tools/browser/camoufox_manager.py +397 -0
  19. code_puppy-0.0.357/code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  20. code_puppy-0.0.357/code_puppy/tools/browser/terminal_command_tools.py +521 -0
  21. code_puppy-0.0.357/code_puppy/tools/browser/terminal_screenshot_tools.py +520 -0
  22. code_puppy-0.0.357/code_puppy/tools/browser/terminal_tools.py +525 -0
  23. code_puppy-0.0.357/code_puppy/tools/browser/vqa_agent.py +194 -0
  24. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/tools/command_runner.py +0 -1
  25. {code_puppy-0.0.356 → code_puppy-0.0.357}/pyproject.toml +1 -1
  26. code_puppy-0.0.356/code_puppy/tools/browser/__init__.py +0 -0
  27. code_puppy-0.0.356/code_puppy/tools/browser/browser_screenshot.py +0 -241
  28. code_puppy-0.0.356/code_puppy/tools/browser/camoufox_manager.py +0 -235
  29. code_puppy-0.0.356/code_puppy/tools/browser/vqa_agent.py +0 -90
  30. {code_puppy-0.0.356 → code_puppy-0.0.357}/.gitignore +0 -0
  31. {code_puppy-0.0.356 → code_puppy-0.0.357}/LICENSE +0 -0
  32. {code_puppy-0.0.356 → code_puppy-0.0.357}/README.md +0 -0
  33. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/__init__.py +0 -0
  34. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/__main__.py +0 -0
  35. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/__init__.py +0 -0
  36. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/agent_c_reviewer.py +0 -0
  37. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/agent_code_puppy.py +0 -0
  38. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/agent_code_reviewer.py +0 -0
  39. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
  40. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/agent_creator_agent.py +0 -0
  41. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/agent_golang_reviewer.py +0 -0
  42. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
  43. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/agent_manager.py +0 -0
  44. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/agent_pack_leader.py +0 -0
  45. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/agent_planning.py +0 -0
  46. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/agent_python_programmer.py +0 -0
  47. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/agent_python_reviewer.py +0 -0
  48. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/agent_qa_expert.py +0 -0
  49. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/agent_security_auditor.py +0 -0
  50. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
  51. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/base_agent.py +0 -0
  52. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/event_stream_handler.py +0 -0
  53. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/json_agent.py +0 -0
  54. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/pack/__init__.py +0 -0
  55. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/pack/bloodhound.py +0 -0
  56. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/pack/husky.py +0 -0
  57. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/pack/retriever.py +0 -0
  58. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/pack/shepherd.py +0 -0
  59. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/pack/terrier.py +0 -0
  60. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/pack/watchdog.py +0 -0
  61. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/prompt_reviewer.py +0 -0
  62. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/agents/subagent_stream_handler.py +0 -0
  63. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/api/__init__.py +0 -0
  64. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/api/main.py +0 -0
  65. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/api/pty_manager.py +0 -0
  66. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/api/routers/__init__.py +0 -0
  67. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/api/routers/agents.py +0 -0
  68. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/api/routers/config.py +0 -0
  69. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/api/templates/terminal.html +0 -0
  70. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/api/websocket.py +0 -0
  71. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/callbacks.py +0 -0
  72. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/chatgpt_codex_client.py +0 -0
  73. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/claude_cache_client.py +0 -0
  74. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/cli_runner.py +0 -0
  75. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/__init__.py +0 -0
  76. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/add_model_menu.py +0 -0
  77. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/attachments.py +0 -0
  78. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/autosave_menu.py +0 -0
  79. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/clipboard.py +0 -0
  80. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/colors_menu.py +0 -0
  81. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/command_handler.py +0 -0
  82. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/command_registry.py +0 -0
  83. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/config_commands.py +0 -0
  84. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/core_commands.py +0 -0
  85. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/diff_menu.py +0 -0
  86. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/file_path_completion.py +0 -0
  87. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/load_context_completion.py +0 -0
  88. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp/__init__.py +0 -0
  89. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp/base.py +0 -0
  90. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
  91. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
  92. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
  93. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp/edit_command.py +0 -0
  94. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp/handler.py +0 -0
  95. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp/help_command.py +0 -0
  96. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp/install_command.py +0 -0
  97. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp/install_menu.py +0 -0
  98. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp/list_command.py +0 -0
  99. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp/logs_command.py +0 -0
  100. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp/remove_command.py +0 -0
  101. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp/restart_command.py +0 -0
  102. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp/search_command.py +0 -0
  103. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp/start_all_command.py +0 -0
  104. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp/start_command.py +0 -0
  105. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp/status_command.py +0 -0
  106. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
  107. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp/stop_command.py +0 -0
  108. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp/test_command.py +0 -0
  109. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp/utils.py +0 -0
  110. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
  111. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/mcp_completion.py +0 -0
  112. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/model_picker_completion.py +0 -0
  113. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/model_settings_menu.py +0 -0
  114. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/motd.py +0 -0
  115. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/onboarding_slides.py +0 -0
  116. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/onboarding_wizard.py +0 -0
  117. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/pin_command_completion.py +0 -0
  118. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
  119. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/session_commands.py +0 -0
  120. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/command_line/utils.py +0 -0
  121. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/error_logging.py +0 -0
  122. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/gemini_code_assist.py +0 -0
  123. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/http_utils.py +0 -0
  124. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/keymap.py +0 -0
  125. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/main.py +0 -0
  126. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/mcp_/__init__.py +0 -0
  127. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/mcp_/async_lifecycle.py +0 -0
  128. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/mcp_/blocking_startup.py +0 -0
  129. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/mcp_/captured_stdio_server.py +0 -0
  130. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/mcp_/circuit_breaker.py +0 -0
  131. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/mcp_/config_wizard.py +0 -0
  132. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/mcp_/dashboard.py +0 -0
  133. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/mcp_/error_isolation.py +0 -0
  134. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/mcp_/examples/retry_example.py +0 -0
  135. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/mcp_/health_monitor.py +0 -0
  136. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/mcp_/managed_server.py +0 -0
  137. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/mcp_/manager.py +0 -0
  138. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/mcp_/mcp_logs.py +0 -0
  139. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/mcp_/registry.py +0 -0
  140. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/mcp_/retry_manager.py +0 -0
  141. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/mcp_/server_registry_catalog.py +0 -0
  142. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/mcp_/status_tracker.py +0 -0
  143. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/mcp_/system_tools.py +0 -0
  144. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/messaging/__init__.py +0 -0
  145. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/messaging/bus.py +0 -0
  146. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/messaging/commands.py +0 -0
  147. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/messaging/markdown_patches.py +0 -0
  148. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/messaging/message_queue.py +0 -0
  149. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/messaging/messages.py +0 -0
  150. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/messaging/queue_console.py +0 -0
  151. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/messaging/renderers.py +0 -0
  152. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/messaging/rich_renderer.py +0 -0
  153. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/messaging/spinner/__init__.py +0 -0
  154. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/messaging/spinner/console_spinner.py +0 -0
  155. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/messaging/spinner/spinner_base.py +0 -0
  156. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/messaging/subagent_console.py +0 -0
  157. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/model_factory.py +0 -0
  158. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/model_utils.py +0 -0
  159. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/models.json +0 -0
  160. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/models_dev_api.json +0 -0
  161. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/models_dev_parser.py +0 -0
  162. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/__init__.py +0 -0
  163. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/__init__.py +0 -0
  164. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/accounts.py +0 -0
  165. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/antigravity_model.py +0 -0
  166. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/config.py +0 -0
  167. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/constants.py +0 -0
  168. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/oauth.py +0 -0
  169. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/register_callbacks.py +0 -0
  170. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/storage.py +0 -0
  171. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/test_plugin.py +0 -0
  172. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/token.py +0 -0
  173. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/transport.py +0 -0
  174. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/antigravity_oauth/utils.py +0 -0
  175. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/chatgpt_oauth/__init__.py +0 -0
  176. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/chatgpt_oauth/config.py +0 -0
  177. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/chatgpt_oauth/oauth_flow.py +0 -0
  178. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +0 -0
  179. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/chatgpt_oauth/test_plugin.py +0 -0
  180. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/chatgpt_oauth/utils.py +0 -0
  181. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/claude_code_oauth/README.md +0 -0
  182. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/claude_code_oauth/SETUP.md +0 -0
  183. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/claude_code_oauth/__init__.py +0 -0
  184. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/claude_code_oauth/config.py +0 -0
  185. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/claude_code_oauth/register_callbacks.py +0 -0
  186. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/claude_code_oauth/test_plugin.py +0 -0
  187. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/claude_code_oauth/utils.py +0 -0
  188. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/customizable_commands/__init__.py +0 -0
  189. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/customizable_commands/register_callbacks.py +0 -0
  190. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/example_custom_command/README.md +0 -0
  191. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
  192. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/file_permission_handler/__init__.py +0 -0
  193. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/file_permission_handler/register_callbacks.py +0 -0
  194. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/frontend_emitter/__init__.py +0 -0
  195. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/frontend_emitter/emitter.py +0 -0
  196. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/frontend_emitter/register_callbacks.py +0 -0
  197. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/oauth_puppy_html.py +0 -0
  198. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/shell_safety/__init__.py +0 -0
  199. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/shell_safety/agent_shell_safety.py +0 -0
  200. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/shell_safety/command_cache.py +0 -0
  201. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/plugins/shell_safety/register_callbacks.py +0 -0
  202. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/prompts/antigravity_system_prompt.md +0 -0
  203. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/prompts/codex_system_prompt.md +0 -0
  204. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/pydantic_patches.py +0 -0
  205. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/reopenable_async_client.py +0 -0
  206. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/round_robin_model.py +0 -0
  207. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/session_storage.py +0 -0
  208. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/status_display.py +0 -0
  209. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/summarization_agent.py +0 -0
  210. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/terminal_utils.py +0 -0
  211. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/tools/browser/browser_workflows.py +0 -0
  212. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/tools/common.py +0 -0
  213. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/tools/display.py +0 -0
  214. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/tools/file_modifications.py +0 -0
  215. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/tools/file_operations.py +0 -0
  216. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/tools/subagent_context.py +0 -0
  217. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/tools/tools_content.py +0 -0
  218. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/uvx_detection.py +0 -0
  219. {code_puppy-0.0.356 → code_puppy-0.0.357}/code_puppy/version_checker.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-puppy
3
- Version: 0.0.356
3
+ Version: 0.0.357
4
4
  Summary: Code generation agent
5
5
  Project-URL: repository, https://github.com/mpfaffenberger/code_puppy
6
6
  Project-URL: HomePage, https://github.com/mpfaffenberger/code_puppy
@@ -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
- "browser_screenshot_analyze",
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 browser_screenshot_analyze to verify results
121
- - **VQA questions**: Ask specific, actionable questions like "Is the login button highlighted?"
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 browser_screenshot_analyze for intelligent page analysis
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
- result = handle_command(command)
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
- with open(txt_file, "r") as f:
89
- metadata = json.load(f)
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
- with open(txt_file, "r") as f:
127
- metadata = json.load(f)
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
- with open(pkl_file, "rb") as f:
160
- messages = pickle.load(f)
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 an inferred default."""
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
- return _default_vqa_model_from_models_json()
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