code-puppy 0.0.327__tar.gz → 0.0.328__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 (166) hide show
  1. {code_puppy-0.0.327 → code_puppy-0.0.328}/PKG-INFO +1 -1
  2. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/agents/base_agent.py +28 -2
  3. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/tools/command_runner.py +89 -4
  4. {code_puppy-0.0.327 → code_puppy-0.0.328}/pyproject.toml +1 -1
  5. {code_puppy-0.0.327 → code_puppy-0.0.328}/.gitignore +0 -0
  6. {code_puppy-0.0.327 → code_puppy-0.0.328}/LICENSE +0 -0
  7. {code_puppy-0.0.327 → code_puppy-0.0.328}/README.md +0 -0
  8. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/__init__.py +0 -0
  9. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/__main__.py +0 -0
  10. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/agents/__init__.py +0 -0
  11. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/agents/agent_c_reviewer.py +0 -0
  12. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/agents/agent_code_puppy.py +0 -0
  13. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/agents/agent_code_reviewer.py +0 -0
  14. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
  15. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/agents/agent_creator_agent.py +0 -0
  16. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/agents/agent_golang_reviewer.py +0 -0
  17. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
  18. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/agents/agent_manager.py +0 -0
  19. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/agents/agent_planning.py +0 -0
  20. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/agents/agent_python_programmer.py +0 -0
  21. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/agents/agent_python_reviewer.py +0 -0
  22. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/agents/agent_qa_expert.py +0 -0
  23. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/agents/agent_qa_kitten.py +0 -0
  24. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/agents/agent_security_auditor.py +0 -0
  25. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
  26. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/agents/json_agent.py +0 -0
  27. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/agents/prompt_reviewer.py +0 -0
  28. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/callbacks.py +0 -0
  29. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/chatgpt_codex_client.py +0 -0
  30. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/claude_cache_client.py +0 -0
  31. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/cli_runner.py +0 -0
  32. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/__init__.py +0 -0
  33. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/add_model_menu.py +0 -0
  34. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/attachments.py +0 -0
  35. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/autosave_menu.py +0 -0
  36. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/colors_menu.py +0 -0
  37. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/command_handler.py +0 -0
  38. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/command_registry.py +0 -0
  39. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/config_commands.py +0 -0
  40. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/core_commands.py +0 -0
  41. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/diff_menu.py +0 -0
  42. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/file_path_completion.py +0 -0
  43. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/load_context_completion.py +0 -0
  44. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/__init__.py +0 -0
  45. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/add_command.py +0 -0
  46. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/base.py +0 -0
  47. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
  48. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
  49. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
  50. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/edit_command.py +0 -0
  51. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/handler.py +0 -0
  52. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/help_command.py +0 -0
  53. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/install_command.py +0 -0
  54. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/install_menu.py +0 -0
  55. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/list_command.py +0 -0
  56. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/logs_command.py +0 -0
  57. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/remove_command.py +0 -0
  58. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/restart_command.py +0 -0
  59. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/search_command.py +0 -0
  60. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/start_all_command.py +0 -0
  61. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/start_command.py +0 -0
  62. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/status_command.py +0 -0
  63. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
  64. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/stop_command.py +0 -0
  65. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/test_command.py +0 -0
  66. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/utils.py +0 -0
  67. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
  68. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/mcp_completion.py +0 -0
  69. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/model_picker_completion.py +0 -0
  70. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/model_settings_menu.py +0 -0
  71. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/motd.py +0 -0
  72. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/pin_command_completion.py +0 -0
  73. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
  74. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/session_commands.py +0 -0
  75. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/command_line/utils.py +0 -0
  76. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/config.py +0 -0
  77. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/error_logging.py +0 -0
  78. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/gemini_code_assist.py +0 -0
  79. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/http_utils.py +0 -0
  80. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/keymap.py +0 -0
  81. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/main.py +0 -0
  82. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/mcp_/__init__.py +0 -0
  83. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/mcp_/async_lifecycle.py +0 -0
  84. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/mcp_/blocking_startup.py +0 -0
  85. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/mcp_/captured_stdio_server.py +0 -0
  86. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/mcp_/circuit_breaker.py +0 -0
  87. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/mcp_/config_wizard.py +0 -0
  88. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/mcp_/dashboard.py +0 -0
  89. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/mcp_/error_isolation.py +0 -0
  90. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/mcp_/examples/retry_example.py +0 -0
  91. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/mcp_/health_monitor.py +0 -0
  92. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/mcp_/managed_server.py +0 -0
  93. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/mcp_/manager.py +0 -0
  94. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/mcp_/mcp_logs.py +0 -0
  95. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/mcp_/registry.py +0 -0
  96. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/mcp_/retry_manager.py +0 -0
  97. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/mcp_/server_registry_catalog.py +0 -0
  98. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/mcp_/status_tracker.py +0 -0
  99. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/mcp_/system_tools.py +0 -0
  100. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/messaging/__init__.py +0 -0
  101. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/messaging/bus.py +0 -0
  102. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/messaging/commands.py +0 -0
  103. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/messaging/markdown_patches.py +0 -0
  104. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/messaging/message_queue.py +0 -0
  105. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/messaging/messages.py +0 -0
  106. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/messaging/queue_console.py +0 -0
  107. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/messaging/renderers.py +0 -0
  108. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/messaging/rich_renderer.py +0 -0
  109. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/messaging/spinner/__init__.py +0 -0
  110. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/messaging/spinner/console_spinner.py +0 -0
  111. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/messaging/spinner/spinner_base.py +0 -0
  112. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/model_factory.py +0 -0
  113. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/model_utils.py +0 -0
  114. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/models.json +0 -0
  115. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/models_dev_api.json +0 -0
  116. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/models_dev_parser.py +0 -0
  117. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/__init__.py +0 -0
  118. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/chatgpt_oauth/__init__.py +0 -0
  119. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/chatgpt_oauth/config.py +0 -0
  120. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/chatgpt_oauth/oauth_flow.py +0 -0
  121. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +0 -0
  122. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/chatgpt_oauth/test_plugin.py +0 -0
  123. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/chatgpt_oauth/utils.py +0 -0
  124. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/claude_code_oauth/README.md +0 -0
  125. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/claude_code_oauth/SETUP.md +0 -0
  126. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/claude_code_oauth/__init__.py +0 -0
  127. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/claude_code_oauth/config.py +0 -0
  128. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/claude_code_oauth/register_callbacks.py +0 -0
  129. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/claude_code_oauth/test_plugin.py +0 -0
  130. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/claude_code_oauth/utils.py +0 -0
  131. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/customizable_commands/__init__.py +0 -0
  132. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/customizable_commands/register_callbacks.py +0 -0
  133. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/example_custom_command/README.md +0 -0
  134. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
  135. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/file_permission_handler/__init__.py +0 -0
  136. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/file_permission_handler/register_callbacks.py +0 -0
  137. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/oauth_puppy_html.py +0 -0
  138. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/shell_safety/__init__.py +0 -0
  139. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/shell_safety/agent_shell_safety.py +0 -0
  140. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/shell_safety/command_cache.py +0 -0
  141. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/plugins/shell_safety/register_callbacks.py +0 -0
  142. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/prompts/codex_system_prompt.md +0 -0
  143. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/pydantic_patches.py +0 -0
  144. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/reopenable_async_client.py +0 -0
  145. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/round_robin_model.py +0 -0
  146. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/session_storage.py +0 -0
  147. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/status_display.py +0 -0
  148. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/summarization_agent.py +0 -0
  149. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/terminal_utils.py +0 -0
  150. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/tools/__init__.py +0 -0
  151. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/tools/agent_tools.py +0 -0
  152. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/tools/browser/__init__.py +0 -0
  153. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/tools/browser/browser_control.py +0 -0
  154. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/tools/browser/browser_interactions.py +0 -0
  155. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/tools/browser/browser_locators.py +0 -0
  156. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/tools/browser/browser_navigation.py +0 -0
  157. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/tools/browser/browser_screenshot.py +0 -0
  158. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/tools/browser/browser_scripts.py +0 -0
  159. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/tools/browser/browser_workflows.py +0 -0
  160. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/tools/browser/camoufox_manager.py +0 -0
  161. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/tools/browser/vqa_agent.py +0 -0
  162. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/tools/common.py +0 -0
  163. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/tools/file_modifications.py +0 -0
  164. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/tools/file_operations.py +0 -0
  165. {code_puppy-0.0.327 → code_puppy-0.0.328}/code_puppy/tools/tools_content.py +0 -0
  166. {code_puppy-0.0.327 → code_puppy-0.0.328}/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.327
3
+ Version: 0.0.328
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
@@ -75,6 +75,8 @@ from code_puppy.model_factory import ModelFactory, make_model_settings
75
75
  from code_puppy.summarization_agent import run_summarization_sync
76
76
  from code_puppy.tools.agent_tools import _active_subagent_tasks
77
77
  from code_puppy.tools.command_runner import (
78
+ _add_windows_ctrl_handler,
79
+ _remove_windows_ctrl_handler,
78
80
  is_awaiting_user_input,
79
81
  )
80
82
 
@@ -1951,8 +1953,6 @@ class BaseAgent(ABC):
1951
1953
  def graceful_sigint_handler(_sig, _frame):
1952
1954
  # When using keyboard-based cancel, SIGINT should be a no-op
1953
1955
  # (just show a hint to user about the configured cancel key)
1954
- import sys
1955
-
1956
1956
  from code_puppy.keymap import get_cancel_agent_display_name
1957
1957
 
1958
1958
  cancel_key = get_cancel_agent_display_name()
@@ -1961,6 +1961,12 @@ class BaseAgent(ABC):
1961
1961
  original_handler = None
1962
1962
  key_listener_stop_event = None
1963
1963
  _key_listener_thread = None
1964
+ windows_ctrl_handler = None
1965
+
1966
+ # Check if we're on Windows
1967
+ import sys
1968
+
1969
+ is_windows = sys.platform.startswith("win")
1964
1970
 
1965
1971
  try:
1966
1972
  if cancel_agent_uses_signal():
@@ -1968,10 +1974,27 @@ class BaseAgent(ABC):
1968
1974
  original_handler = signal.signal(
1969
1975
  signal.SIGINT, keyboard_interrupt_handler
1970
1976
  )
1977
+ # On Windows, also use SetConsoleCtrlHandler for reliable Ctrl+C with uvx
1978
+ if is_windows:
1979
+ windows_ctrl_handler = _add_windows_ctrl_handler(
1980
+ schedule_agent_cancel
1981
+ )
1971
1982
  else:
1972
1983
  # Use keyboard listener for agent cancellation
1973
1984
  # Set a graceful SIGINT handler that shows a hint
1974
1985
  original_handler = signal.signal(signal.SIGINT, graceful_sigint_handler)
1986
+ # On Windows, SetConsoleCtrlHandler should also show the hint
1987
+ if is_windows:
1988
+
1989
+ def graceful_ctrl_handler():
1990
+ from code_puppy.keymap import get_cancel_agent_display_name
1991
+
1992
+ cancel_key = get_cancel_agent_display_name()
1993
+ emit_info(f"Use {cancel_key} to cancel the agent task.")
1994
+
1995
+ windows_ctrl_handler = _add_windows_ctrl_handler(
1996
+ graceful_ctrl_handler
1997
+ )
1975
1998
  # Spawn keyboard listener with the cancel agent callback
1976
1999
  key_listener_stop_event = threading.Event()
1977
2000
  _key_listener_thread = self._spawn_ctrl_x_key_listener(
@@ -2001,6 +2024,9 @@ class BaseAgent(ABC):
2001
2024
  # Stop keyboard listener if it was started
2002
2025
  if key_listener_stop_event is not None:
2003
2026
  key_listener_stop_event.set()
2027
+ # Remove Windows console handler
2028
+ if windows_ctrl_handler is not None:
2029
+ _remove_windows_ctrl_handler(windows_ctrl_handler)
2004
2030
  # Restore original signal handler
2005
2031
  if (
2006
2032
  original_handler is not None
@@ -44,9 +44,70 @@ def _truncate_line(line: str) -> str:
44
44
  if sys.platform.startswith("win"):
45
45
  import msvcrt
46
46
 
47
- # Load kernel32 for PeekNamedPipe
47
+ # Load kernel32 for PeekNamedPipe and SetConsoleCtrlHandler
48
48
  _kernel32 = ctypes.windll.kernel32
49
49
 
50
+ # SetConsoleCtrlHandler types for Ctrl+C handling on Windows
51
+ # This is more reliable than signal.SIGINT when running under uvx
52
+ _CTRL_C_EVENT = 0
53
+ _CTRL_BREAK_EVENT = 1
54
+ _HANDLER_ROUTINE = ctypes.WINFUNCTYPE(ctypes.c_int, ctypes.c_ulong)
55
+
56
+ # Track registered handlers to prevent garbage collection
57
+ _registered_console_handlers: list = []
58
+
59
+ def _add_windows_ctrl_handler(callback: Callable[[], None]) -> Optional[Callable]:
60
+ """Register a Windows console control handler for Ctrl+C/Ctrl+Break.
61
+
62
+ Args:
63
+ callback: Function to call when Ctrl+C or Ctrl+Break is pressed.
64
+ Should take no arguments.
65
+
66
+ Returns:
67
+ The wrapped handler function (needed for removal), or None if failed.
68
+ """
69
+
70
+ def handler(ctrl_type: int) -> int:
71
+ if ctrl_type in (_CTRL_C_EVENT, _CTRL_BREAK_EVENT):
72
+ try:
73
+ callback()
74
+ except Exception:
75
+ pass
76
+ return 1 # TRUE = we handled it, don't pass to next handler
77
+ return 0 # FALSE = let next handler deal with it
78
+
79
+ # Wrap in WINFUNCTYPE to make it callable from C
80
+ wrapped = _HANDLER_ROUTINE(handler)
81
+ # Keep reference to prevent GC
82
+ _registered_console_handlers.append(wrapped)
83
+
84
+ try:
85
+ if _kernel32.SetConsoleCtrlHandler(wrapped, True):
86
+ return wrapped
87
+ except Exception:
88
+ pass
89
+ return None
90
+
91
+ def _remove_windows_ctrl_handler(handler: Callable) -> bool:
92
+ """Remove a previously registered Windows console control handler.
93
+
94
+ Args:
95
+ handler: The handler returned by _add_windows_ctrl_handler.
96
+
97
+ Returns:
98
+ True if successfully removed, False otherwise.
99
+ """
100
+ if handler is None:
101
+ return False
102
+ try:
103
+ result = _kernel32.SetConsoleCtrlHandler(handler, False)
104
+ # Clean up our reference
105
+ if handler in _registered_console_handlers:
106
+ _registered_console_handlers.remove(handler)
107
+ return bool(result)
108
+ except Exception:
109
+ return False
110
+
50
111
  def _win32_pipe_has_data(pipe) -> bool:
51
112
  """Check if a Windows pipe has data available without blocking.
52
113
 
@@ -87,8 +148,15 @@ if sys.platform.startswith("win"):
87
148
  except (ValueError, OSError, ctypes.ArgumentError):
88
149
  # Handle closed, invalid, or other errors
89
150
  return False
151
+
90
152
  else:
91
- # POSIX stub - not used, but keeps the code clean
153
+ # Non-Windows: provide no-op stubs
154
+ def _add_windows_ctrl_handler(callback: Callable[[], None]) -> Optional[Callable]:
155
+ return None
156
+
157
+ def _remove_windows_ctrl_handler(handler: Callable) -> bool:
158
+ return False
159
+
92
160
  def _win32_pipe_has_data(pipe) -> bool:
93
161
  return False
94
162
 
@@ -106,6 +174,7 @@ _USER_KILLED_PROCESSES = set()
106
174
  _SHELL_CTRL_X_STOP_EVENT: Optional[threading.Event] = None
107
175
  _SHELL_CTRL_X_THREAD: Optional[threading.Thread] = None
108
176
  _ORIGINAL_SIGINT_HANDLER = None
177
+ _WINDOWS_CTRL_HANDLER = None # For SetConsoleCtrlHandler on Windows
109
178
 
110
179
  # Stop event to signal reader threads to terminate
111
180
  _READER_STOP_EVENT: Optional[threading.Event] = None
@@ -435,8 +504,10 @@ def _shell_command_keyboard_context():
435
504
  1. Disables the agent's Ctrl-C handler (so it doesn't cancel the agent)
436
505
  2. Enables a Ctrl-X listener to kill the running shell process
437
506
  3. Restores the original Ctrl-C handler when done
507
+ 4. On Windows, uses SetConsoleCtrlHandler for reliable Ctrl+C with uvx
438
508
  """
439
509
  global _SHELL_CTRL_X_STOP_EVENT, _SHELL_CTRL_X_THREAD, _ORIGINAL_SIGINT_HANDLER
510
+ global _WINDOWS_CTRL_HANDLER
440
511
 
441
512
  # Handler for Ctrl-X: kill all running shell processes
442
513
  def handle_ctrl_x_press() -> None:
@@ -444,11 +515,15 @@ def _shell_command_keyboard_context():
444
515
  kill_all_running_shell_processes()
445
516
 
446
517
  # Handler for Ctrl-C during shell execution: just kill the shell process, don't cancel agent
447
- def shell_sigint_handler(_sig, _frame):
518
+ def shell_ctrl_c_callback():
448
519
  """During shell execution, Ctrl-C kills the shell but doesn't cancel the agent."""
449
520
  emit_warning("\n🛑 Ctrl-C detected! Interrupting shell command...")
450
521
  kill_all_running_shell_processes()
451
522
 
523
+ def shell_sigint_handler(_sig, _frame):
524
+ """Signal handler wrapper for SIGINT."""
525
+ shell_ctrl_c_callback()
526
+
452
527
  # Set up Ctrl-X listener
453
528
  _SHELL_CTRL_X_STOP_EVENT = threading.Event()
454
529
  _SHELL_CTRL_X_THREAD = _spawn_ctrl_x_key_listener(
@@ -456,7 +531,12 @@ def _shell_command_keyboard_context():
456
531
  handle_ctrl_x_press,
457
532
  )
458
533
 
459
- # Replace SIGINT handler temporarily
534
+ # On Windows, use SetConsoleCtrlHandler for reliable Ctrl+C handling
535
+ # This works even when running under uvx where SIGINT doesn't propagate properly
536
+ if sys.platform.startswith("win"):
537
+ _WINDOWS_CTRL_HANDLER = _add_windows_ctrl_handler(shell_ctrl_c_callback)
538
+
539
+ # Also set SIGINT handler (works on POSIX, may work on some Windows setups)
460
540
  try:
461
541
  _ORIGINAL_SIGINT_HANDLER = signal.signal(signal.SIGINT, shell_sigint_handler)
462
542
  except (ValueError, OSError):
@@ -476,6 +556,10 @@ def _shell_command_keyboard_context():
476
556
  except Exception:
477
557
  pass
478
558
 
559
+ # Remove Windows console handler
560
+ if _WINDOWS_CTRL_HANDLER is not None:
561
+ _remove_windows_ctrl_handler(_WINDOWS_CTRL_HANDLER)
562
+
479
563
  # Restore original SIGINT handler
480
564
  if _ORIGINAL_SIGINT_HANDLER is not None:
481
565
  try:
@@ -487,6 +571,7 @@ def _shell_command_keyboard_context():
487
571
  _SHELL_CTRL_X_STOP_EVENT = None
488
572
  _SHELL_CTRL_X_THREAD = None
489
573
  _ORIGINAL_SIGINT_HANDLER = None
574
+ _WINDOWS_CTRL_HANDLER = None
490
575
 
491
576
 
492
577
  def run_shell_command_streaming(
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "code-puppy"
7
- version = "0.0.327"
7
+ version = "0.0.328"
8
8
  description = "Code generation agent"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11,<3.14"
File without changes
File without changes
File without changes