code-puppy 0.0.322__tar.gz → 0.0.324__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.322 → code_puppy-0.0.324}/PKG-INFO +1 -1
  2. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/agents/base_agent.py +64 -11
  3. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/cli_runner.py +2 -1
  4. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/keymap.py +8 -2
  5. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/models.json +2 -2
  6. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/tools/command_runner.py +48 -21
  7. {code_puppy-0.0.322 → code_puppy-0.0.324}/pyproject.toml +1 -1
  8. {code_puppy-0.0.322 → code_puppy-0.0.324}/.gitignore +0 -0
  9. {code_puppy-0.0.322 → code_puppy-0.0.324}/LICENSE +0 -0
  10. {code_puppy-0.0.322 → code_puppy-0.0.324}/README.md +0 -0
  11. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/__init__.py +0 -0
  12. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/__main__.py +0 -0
  13. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/agents/__init__.py +0 -0
  14. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/agents/agent_c_reviewer.py +0 -0
  15. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/agents/agent_code_puppy.py +0 -0
  16. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/agents/agent_code_reviewer.py +0 -0
  17. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
  18. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/agents/agent_creator_agent.py +0 -0
  19. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/agents/agent_golang_reviewer.py +0 -0
  20. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
  21. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/agents/agent_manager.py +0 -0
  22. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/agents/agent_planning.py +0 -0
  23. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/agents/agent_python_programmer.py +0 -0
  24. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/agents/agent_python_reviewer.py +0 -0
  25. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/agents/agent_qa_expert.py +0 -0
  26. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/agents/agent_qa_kitten.py +0 -0
  27. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/agents/agent_security_auditor.py +0 -0
  28. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
  29. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/agents/json_agent.py +0 -0
  30. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/agents/prompt_reviewer.py +0 -0
  31. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/callbacks.py +0 -0
  32. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/chatgpt_codex_client.py +0 -0
  33. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/claude_cache_client.py +0 -0
  34. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/__init__.py +0 -0
  35. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/add_model_menu.py +0 -0
  36. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/attachments.py +0 -0
  37. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/autosave_menu.py +0 -0
  38. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/colors_menu.py +0 -0
  39. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/command_handler.py +0 -0
  40. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/command_registry.py +0 -0
  41. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/config_commands.py +0 -0
  42. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/core_commands.py +0 -0
  43. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/diff_menu.py +0 -0
  44. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/file_path_completion.py +0 -0
  45. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/load_context_completion.py +0 -0
  46. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/__init__.py +0 -0
  47. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/add_command.py +0 -0
  48. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/base.py +0 -0
  49. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
  50. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
  51. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
  52. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/edit_command.py +0 -0
  53. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/handler.py +0 -0
  54. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/help_command.py +0 -0
  55. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/install_command.py +0 -0
  56. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/install_menu.py +0 -0
  57. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/list_command.py +0 -0
  58. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/logs_command.py +0 -0
  59. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/remove_command.py +0 -0
  60. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/restart_command.py +0 -0
  61. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/search_command.py +0 -0
  62. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/start_all_command.py +0 -0
  63. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/start_command.py +0 -0
  64. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/status_command.py +0 -0
  65. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
  66. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/stop_command.py +0 -0
  67. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/test_command.py +0 -0
  68. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/utils.py +0 -0
  69. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
  70. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/mcp_completion.py +0 -0
  71. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/model_picker_completion.py +0 -0
  72. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/model_settings_menu.py +0 -0
  73. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/motd.py +0 -0
  74. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/pin_command_completion.py +0 -0
  75. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
  76. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/session_commands.py +0 -0
  77. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/command_line/utils.py +0 -0
  78. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/config.py +0 -0
  79. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/error_logging.py +0 -0
  80. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/gemini_code_assist.py +0 -0
  81. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/http_utils.py +0 -0
  82. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/main.py +0 -0
  83. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/mcp_/__init__.py +0 -0
  84. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/mcp_/async_lifecycle.py +0 -0
  85. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/mcp_/blocking_startup.py +0 -0
  86. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/mcp_/captured_stdio_server.py +0 -0
  87. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/mcp_/circuit_breaker.py +0 -0
  88. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/mcp_/config_wizard.py +0 -0
  89. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/mcp_/dashboard.py +0 -0
  90. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/mcp_/error_isolation.py +0 -0
  91. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/mcp_/examples/retry_example.py +0 -0
  92. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/mcp_/health_monitor.py +0 -0
  93. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/mcp_/managed_server.py +0 -0
  94. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/mcp_/manager.py +0 -0
  95. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/mcp_/mcp_logs.py +0 -0
  96. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/mcp_/registry.py +0 -0
  97. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/mcp_/retry_manager.py +0 -0
  98. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/mcp_/server_registry_catalog.py +0 -0
  99. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/mcp_/status_tracker.py +0 -0
  100. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/mcp_/system_tools.py +0 -0
  101. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/messaging/__init__.py +0 -0
  102. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/messaging/bus.py +0 -0
  103. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/messaging/commands.py +0 -0
  104. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/messaging/markdown_patches.py +0 -0
  105. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/messaging/message_queue.py +0 -0
  106. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/messaging/messages.py +0 -0
  107. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/messaging/queue_console.py +0 -0
  108. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/messaging/renderers.py +0 -0
  109. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/messaging/rich_renderer.py +0 -0
  110. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/messaging/spinner/__init__.py +0 -0
  111. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/messaging/spinner/console_spinner.py +0 -0
  112. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/messaging/spinner/spinner_base.py +0 -0
  113. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/model_factory.py +0 -0
  114. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/model_utils.py +0 -0
  115. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/models_dev_api.json +0 -0
  116. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/models_dev_parser.py +0 -0
  117. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/__init__.py +0 -0
  118. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/chatgpt_oauth/__init__.py +0 -0
  119. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/chatgpt_oauth/config.py +0 -0
  120. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/chatgpt_oauth/oauth_flow.py +0 -0
  121. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +0 -0
  122. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/chatgpt_oauth/test_plugin.py +0 -0
  123. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/chatgpt_oauth/utils.py +0 -0
  124. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/claude_code_oauth/README.md +0 -0
  125. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/claude_code_oauth/SETUP.md +0 -0
  126. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/claude_code_oauth/__init__.py +0 -0
  127. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/claude_code_oauth/config.py +0 -0
  128. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/claude_code_oauth/register_callbacks.py +0 -0
  129. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/claude_code_oauth/test_plugin.py +0 -0
  130. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/claude_code_oauth/utils.py +0 -0
  131. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/customizable_commands/__init__.py +0 -0
  132. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/customizable_commands/register_callbacks.py +0 -0
  133. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/example_custom_command/README.md +0 -0
  134. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
  135. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/file_permission_handler/__init__.py +0 -0
  136. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/file_permission_handler/register_callbacks.py +0 -0
  137. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/oauth_puppy_html.py +0 -0
  138. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/shell_safety/__init__.py +0 -0
  139. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/shell_safety/agent_shell_safety.py +0 -0
  140. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/shell_safety/command_cache.py +0 -0
  141. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/plugins/shell_safety/register_callbacks.py +0 -0
  142. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/prompts/codex_system_prompt.md +0 -0
  143. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/pydantic_patches.py +0 -0
  144. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/reopenable_async_client.py +0 -0
  145. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/round_robin_model.py +0 -0
  146. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/session_storage.py +0 -0
  147. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/status_display.py +0 -0
  148. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/summarization_agent.py +0 -0
  149. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/terminal_utils.py +0 -0
  150. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/tools/__init__.py +0 -0
  151. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/tools/agent_tools.py +0 -0
  152. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/tools/browser/__init__.py +0 -0
  153. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/tools/browser/browser_control.py +0 -0
  154. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/tools/browser/browser_interactions.py +0 -0
  155. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/tools/browser/browser_locators.py +0 -0
  156. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/tools/browser/browser_navigation.py +0 -0
  157. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/tools/browser/browser_screenshot.py +0 -0
  158. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/tools/browser/browser_scripts.py +0 -0
  159. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/tools/browser/browser_workflows.py +0 -0
  160. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/tools/browser/camoufox_manager.py +0 -0
  161. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/tools/browser/vqa_agent.py +0 -0
  162. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/tools/common.py +0 -0
  163. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/tools/file_modifications.py +0 -0
  164. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/tools/file_operations.py +0 -0
  165. {code_puppy-0.0.322 → code_puppy-0.0.324}/code_puppy/tools/tools_content.py +0 -0
  166. {code_puppy-0.0.322 → code_puppy-0.0.324}/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.322
3
+ Version: 0.0.324
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
@@ -4,6 +4,7 @@ import asyncio
4
4
  import json
5
5
  import math
6
6
  import signal
7
+ import sys
7
8
  import threading
8
9
  import uuid
9
10
  from abc import ABC, abstractmethod
@@ -1910,29 +1911,72 @@ class BaseAgent(ABC):
1910
1911
  # When using keyboard-based cancel, SIGINT should be a no-op
1911
1912
  # (just show a hint to user about the configured cancel key)
1912
1913
  from code_puppy.keymap import get_cancel_agent_display_name
1914
+ import sys
1913
1915
 
1914
1916
  cancel_key = get_cancel_agent_display_name()
1915
- emit_info(f"Use {cancel_key} to cancel the agent task.")
1917
+ if sys.platform == "win32":
1918
+ # On Windows, we use keyboard listener, so SIGINT might still fire
1919
+ # but we handle cancellation via the key listener
1920
+ pass # Silent on Windows - the key listener handles it
1921
+ else:
1922
+ emit_info(f"Use {cancel_key} to cancel the agent task.")
1916
1923
 
1917
1924
  original_handler = None
1918
1925
  key_listener_stop_event = None
1919
1926
  _key_listener_thread = None
1927
+ _windows_ctrl_handler = None # Store reference to prevent garbage collection
1920
1928
 
1921
1929
  try:
1922
- if cancel_agent_uses_signal():
1923
- # Use SIGINT-based cancellation (default Ctrl+C behavior)
1930
+ if sys.platform == "win32":
1931
+ # Windows: Use SetConsoleCtrlHandler for reliable Ctrl+C handling
1932
+ import ctypes
1933
+
1934
+ # Define the handler function type
1935
+ HANDLER_ROUTINE = ctypes.WINFUNCTYPE(ctypes.c_bool, ctypes.c_ulong)
1936
+
1937
+ def windows_ctrl_handler(ctrl_type):
1938
+ """Handle Windows console control events."""
1939
+ CTRL_C_EVENT = 0
1940
+ CTRL_BREAK_EVENT = 1
1941
+
1942
+ if ctrl_type in (CTRL_C_EVENT, CTRL_BREAK_EVENT):
1943
+ # Check if we're awaiting user input
1944
+ if is_awaiting_user_input():
1945
+ return False # Let default handler run
1946
+
1947
+ # Schedule agent cancellation
1948
+ schedule_agent_cancel()
1949
+ return True # We handled it, don't terminate
1950
+
1951
+ return False # Let other handlers process it
1952
+
1953
+ # Create the callback - must keep reference alive!
1954
+ _windows_ctrl_handler = HANDLER_ROUTINE(windows_ctrl_handler)
1955
+
1956
+ # Register the handler
1957
+ kernel32 = ctypes.windll.kernel32
1958
+ if not kernel32.SetConsoleCtrlHandler(_windows_ctrl_handler, True):
1959
+ emit_warning("Failed to set Windows Ctrl+C handler")
1960
+
1961
+ # Also spawn keyboard listener for Ctrl+X (shell cancel) and other keys
1962
+ key_listener_stop_event = threading.Event()
1963
+ _key_listener_thread = self._spawn_ctrl_x_key_listener(
1964
+ key_listener_stop_event,
1965
+ on_escape=lambda: None, # Ctrl+X handled by command_runner
1966
+ on_cancel_agent=None, # Ctrl+C handled by SetConsoleCtrlHandler above
1967
+ )
1968
+ elif cancel_agent_uses_signal():
1969
+ # Unix with Ctrl+C: Use SIGINT-based cancellation
1924
1970
  original_handler = signal.signal(
1925
1971
  signal.SIGINT, keyboard_interrupt_handler
1926
1972
  )
1927
1973
  else:
1928
- # Use keyboard listener for agent cancellation
1929
- # Set a graceful SIGINT handler that shows a hint
1974
+ # Unix with different cancel key: Use keyboard listener
1930
1975
  original_handler = signal.signal(signal.SIGINT, graceful_sigint_handler)
1931
- # Spawn keyboard listener with the cancel agent callback
1932
1976
  key_listener_stop_event = threading.Event()
1933
1977
  _key_listener_thread = self._spawn_ctrl_x_key_listener(
1934
1978
  key_listener_stop_event,
1935
- on_escape=lambda: None, # Ctrl+X handled by command_runner
1979
+ on_escape=lambda: None,
1936
1980
  on_cancel_agent=schedule_agent_cancel,
1937
1981
  )
1938
1982
 
@@ -1957,8 +2001,17 @@ class BaseAgent(ABC):
1957
2001
  # Stop keyboard listener if it was started
1958
2002
  if key_listener_stop_event is not None:
1959
2003
  key_listener_stop_event.set()
1960
- # Restore original signal handler
1961
- if (
1962
- original_handler is not None
1963
- ): # Explicit None check - SIG_DFL can be 0/falsy!
2004
+
2005
+ # Unregister Windows Ctrl handler
2006
+ if sys.platform == "win32" and _windows_ctrl_handler is not None:
2007
+ try:
2008
+ import ctypes
2009
+
2010
+ kernel32 = ctypes.windll.kernel32
2011
+ kernel32.SetConsoleCtrlHandler(_windows_ctrl_handler, False)
2012
+ except Exception:
2013
+ pass # Best effort cleanup
2014
+
2015
+ # Restore original signal handler (Unix)
2016
+ if original_handler is not None:
1964
2017
  signal.signal(signal.SIGINT, original_handler)
@@ -790,5 +790,6 @@ def main_entry():
790
790
  DBOS.destroy()
791
791
  return 0
792
792
  finally:
793
- # Reset terminal on Unix-like systems (not Windows)
793
+ # Reset terminal on all platforms for clean state
794
+ reset_windows_terminal_full() # Safe no-op on non-Windows
794
795
  reset_unix_terminal()
@@ -86,9 +86,15 @@ def cancel_agent_uses_signal() -> bool:
86
86
  """Check if the cancel agent key uses SIGINT (Ctrl+C).
87
87
 
88
88
  Returns:
89
- True if the cancel key is ctrl+c (uses SIGINT handler),
90
- False if it uses keyboard listener approach.
89
+ True if the cancel key is ctrl+c AND we're not on Windows
90
+ (uses SIGINT handler), False if it uses keyboard listener approach.
91
91
  """
92
+ import sys
93
+
94
+ # On Windows, always use keyboard listener - SIGINT is unreliable
95
+ if sys.platform == "win32":
96
+ return False
97
+
92
98
  return get_cancel_agent_key() == "ctrl+c"
93
99
 
94
100
 
@@ -9,9 +9,9 @@
9
9
  "context_length": 200000,
10
10
  "supported_settings": ["temperature", "seed"]
11
11
  },
12
- "synthetic-MiniMax-M2": {
12
+ "synthetic-MiniMax-M2.1": {
13
13
  "type": "custom_openai",
14
- "name": "hf:MiniMaxAI/MiniMax-M2",
14
+ "name": "hf:MiniMaxAI/MiniMax-M2.1",
15
15
  "custom_endpoint": {
16
16
  "url": "https://api.synthetic.new/openai/v1/",
17
17
  "api_key": "$SYN_API_KEY"
@@ -192,6 +192,11 @@ def kill_all_running_shell_processes() -> int:
192
192
  """Kill all currently tracked running shell processes and stop reader threads.
193
193
 
194
194
  Returns the number of processes signaled.
195
+
196
+ Implementation notes:
197
+ - Atomically snapshot and clear the registry to prevent race conditions
198
+ - Deduplicate by PID to ensure each process is killed at most once
199
+ - Let exceptions from _kill_process_group propagate (tests expect this)
195
200
  """
196
201
  global _READER_STOP_EVENT
197
202
 
@@ -199,30 +204,52 @@ def kill_all_running_shell_processes() -> int:
199
204
  if _READER_STOP_EVENT:
200
205
  _READER_STOP_EVENT.set()
201
206
 
202
- procs: list[subprocess.Popen]
207
+ # Atomically take snapshot and clear registry
208
+ # This prevents other threads from seeing/processing the same processes
203
209
  with _RUNNING_PROCESSES_LOCK:
204
- procs = list(_RUNNING_PROCESSES)
205
- count = 0
206
- for p in procs:
210
+ procs_snapshot = list(_RUNNING_PROCESSES)
211
+ _RUNNING_PROCESSES.clear()
212
+
213
+ # Deduplicate by pid to ensure at-most-one kill per process
214
+ seen_pids: set = set()
215
+ killed_count = 0
216
+
217
+ for proc in procs_snapshot:
218
+ if proc is None:
219
+ continue
220
+
221
+ pid = getattr(proc, "pid", None)
222
+ key = pid if pid is not None else id(proc)
223
+
224
+ if key in seen_pids:
225
+ continue
226
+ seen_pids.add(key)
227
+
228
+ # Close pipes first to unblock readline()
207
229
  try:
208
- # Close pipes first to unblock readline()
209
- try:
210
- if p.stdout and not p.stdout.closed:
211
- p.stdout.close()
212
- if p.stderr and not p.stderr.closed:
213
- p.stderr.close()
214
- if p.stdin and not p.stdin.closed:
215
- p.stdin.close()
216
- except (OSError, ValueError):
217
- pass
230
+ if proc.stdout and not proc.stdout.closed:
231
+ proc.stdout.close()
232
+ if proc.stderr and not proc.stderr.closed:
233
+ proc.stderr.close()
234
+ if proc.stdin and not proc.stdin.closed:
235
+ proc.stdin.close()
236
+ except (OSError, ValueError):
237
+ pass
238
+
239
+ # Only attempt to kill processes that are still running
240
+ if proc.poll() is None:
241
+ # Let exceptions bubble up (tests expect this behavior)
242
+ _kill_process_group(proc)
243
+ killed_count += 1
244
+
245
+ # Track user-killed PIDs
246
+ if pid is not None:
247
+ try:
248
+ _USER_KILLED_PROCESSES.add(pid)
249
+ except Exception:
250
+ pass # Non-fatal bookkeeping
218
251
 
219
- if p.poll() is None:
220
- _kill_process_group(p)
221
- count += 1
222
- _USER_KILLED_PROCESSES.add(p.pid)
223
- finally:
224
- _unregister_process(p)
225
- return count
252
+ return killed_count
226
253
 
227
254
 
228
255
  def get_running_shell_process_count() -> int:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "code-puppy"
7
- version = "0.0.322"
7
+ version = "0.0.324"
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