code-puppy 0.0.315__tar.gz → 0.0.317__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 (165) hide show
  1. {code_puppy-0.0.315 → code_puppy-0.0.317}/PKG-INFO +1 -1
  2. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/agents/base_agent.py +25 -30
  3. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/cli_runner.py +6 -0
  4. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/mcp_/blocking_startup.py +11 -3
  5. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/mcp_/managed_server.py +22 -2
  6. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/mcp_/manager.py +65 -0
  7. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/messaging/__init__.py +9 -0
  8. code_puppy-0.0.317/code_puppy/messaging/markdown_patches.py +57 -0
  9. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/model_factory.py +6 -0
  10. {code_puppy-0.0.315 → code_puppy-0.0.317}/pyproject.toml +2 -1
  11. {code_puppy-0.0.315 → code_puppy-0.0.317}/.gitignore +0 -0
  12. {code_puppy-0.0.315 → code_puppy-0.0.317}/LICENSE +0 -0
  13. {code_puppy-0.0.315 → code_puppy-0.0.317}/README.md +0 -0
  14. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/__init__.py +0 -0
  15. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/__main__.py +0 -0
  16. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/agents/__init__.py +0 -0
  17. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/agents/agent_c_reviewer.py +0 -0
  18. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/agents/agent_code_puppy.py +0 -0
  19. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/agents/agent_code_reviewer.py +0 -0
  20. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
  21. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/agents/agent_creator_agent.py +0 -0
  22. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/agents/agent_golang_reviewer.py +0 -0
  23. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
  24. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/agents/agent_manager.py +0 -0
  25. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/agents/agent_planning.py +0 -0
  26. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/agents/agent_python_programmer.py +0 -0
  27. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/agents/agent_python_reviewer.py +0 -0
  28. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/agents/agent_qa_expert.py +0 -0
  29. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/agents/agent_qa_kitten.py +0 -0
  30. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/agents/agent_security_auditor.py +0 -0
  31. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
  32. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/agents/json_agent.py +0 -0
  33. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/agents/prompt_reviewer.py +0 -0
  34. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/callbacks.py +0 -0
  35. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/chatgpt_codex_client.py +0 -0
  36. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/claude_cache_client.py +0 -0
  37. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/__init__.py +0 -0
  38. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/add_model_menu.py +0 -0
  39. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/attachments.py +0 -0
  40. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/autosave_menu.py +0 -0
  41. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/colors_menu.py +0 -0
  42. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/command_handler.py +0 -0
  43. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/command_registry.py +0 -0
  44. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/config_commands.py +0 -0
  45. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/core_commands.py +0 -0
  46. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/diff_menu.py +0 -0
  47. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/file_path_completion.py +0 -0
  48. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/load_context_completion.py +0 -0
  49. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/__init__.py +0 -0
  50. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/add_command.py +0 -0
  51. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/base.py +0 -0
  52. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
  53. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
  54. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
  55. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/edit_command.py +0 -0
  56. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/handler.py +0 -0
  57. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/help_command.py +0 -0
  58. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/install_command.py +0 -0
  59. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/install_menu.py +0 -0
  60. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/list_command.py +0 -0
  61. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/logs_command.py +0 -0
  62. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/remove_command.py +0 -0
  63. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/restart_command.py +0 -0
  64. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/search_command.py +0 -0
  65. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/start_all_command.py +0 -0
  66. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/start_command.py +0 -0
  67. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/status_command.py +0 -0
  68. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
  69. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/stop_command.py +0 -0
  70. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/test_command.py +0 -0
  71. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/utils.py +0 -0
  72. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
  73. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/mcp_completion.py +0 -0
  74. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/model_picker_completion.py +0 -0
  75. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/model_settings_menu.py +0 -0
  76. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/motd.py +0 -0
  77. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/pin_command_completion.py +0 -0
  78. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
  79. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/session_commands.py +0 -0
  80. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/command_line/utils.py +0 -0
  81. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/config.py +0 -0
  82. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/error_logging.py +0 -0
  83. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/gemini_code_assist.py +0 -0
  84. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/http_utils.py +0 -0
  85. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/keymap.py +0 -0
  86. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/main.py +0 -0
  87. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/mcp_/__init__.py +0 -0
  88. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/mcp_/async_lifecycle.py +0 -0
  89. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/mcp_/captured_stdio_server.py +0 -0
  90. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/mcp_/circuit_breaker.py +0 -0
  91. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/mcp_/config_wizard.py +0 -0
  92. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/mcp_/dashboard.py +0 -0
  93. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/mcp_/error_isolation.py +0 -0
  94. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/mcp_/examples/retry_example.py +0 -0
  95. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/mcp_/health_monitor.py +0 -0
  96. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/mcp_/registry.py +0 -0
  97. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/mcp_/retry_manager.py +0 -0
  98. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/mcp_/server_registry_catalog.py +0 -0
  99. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/mcp_/status_tracker.py +0 -0
  100. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/mcp_/system_tools.py +0 -0
  101. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/messaging/bus.py +0 -0
  102. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/messaging/commands.py +0 -0
  103. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/messaging/message_queue.py +0 -0
  104. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/messaging/messages.py +0 -0
  105. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/messaging/queue_console.py +0 -0
  106. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/messaging/renderers.py +0 -0
  107. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/messaging/rich_renderer.py +0 -0
  108. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/messaging/spinner/__init__.py +0 -0
  109. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/messaging/spinner/console_spinner.py +0 -0
  110. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/messaging/spinner/spinner_base.py +0 -0
  111. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/model_utils.py +0 -0
  112. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/models.json +0 -0
  113. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/models_dev_api.json +0 -0
  114. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/models_dev_parser.py +0 -0
  115. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/__init__.py +0 -0
  116. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/chatgpt_oauth/__init__.py +0 -0
  117. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/chatgpt_oauth/config.py +0 -0
  118. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/chatgpt_oauth/oauth_flow.py +0 -0
  119. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +0 -0
  120. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/chatgpt_oauth/test_plugin.py +0 -0
  121. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/chatgpt_oauth/utils.py +0 -0
  122. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/claude_code_oauth/README.md +0 -0
  123. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/claude_code_oauth/SETUP.md +0 -0
  124. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/claude_code_oauth/__init__.py +0 -0
  125. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/claude_code_oauth/config.py +0 -0
  126. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/claude_code_oauth/register_callbacks.py +0 -0
  127. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/claude_code_oauth/test_plugin.py +0 -0
  128. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/claude_code_oauth/utils.py +0 -0
  129. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/customizable_commands/__init__.py +0 -0
  130. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/customizable_commands/register_callbacks.py +0 -0
  131. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/example_custom_command/README.md +0 -0
  132. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
  133. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/file_permission_handler/__init__.py +0 -0
  134. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/file_permission_handler/register_callbacks.py +0 -0
  135. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/oauth_puppy_html.py +0 -0
  136. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/shell_safety/__init__.py +0 -0
  137. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/shell_safety/agent_shell_safety.py +0 -0
  138. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/shell_safety/command_cache.py +0 -0
  139. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/plugins/shell_safety/register_callbacks.py +0 -0
  140. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/prompts/codex_system_prompt.md +0 -0
  141. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/pydantic_patches.py +0 -0
  142. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/reopenable_async_client.py +0 -0
  143. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/round_robin_model.py +0 -0
  144. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/session_storage.py +0 -0
  145. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/status_display.py +0 -0
  146. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/summarization_agent.py +0 -0
  147. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/terminal_utils.py +0 -0
  148. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/tools/__init__.py +0 -0
  149. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/tools/agent_tools.py +0 -0
  150. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/tools/browser/__init__.py +0 -0
  151. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/tools/browser/browser_control.py +0 -0
  152. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/tools/browser/browser_interactions.py +0 -0
  153. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/tools/browser/browser_locators.py +0 -0
  154. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/tools/browser/browser_navigation.py +0 -0
  155. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/tools/browser/browser_screenshot.py +0 -0
  156. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/tools/browser/browser_scripts.py +0 -0
  157. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/tools/browser/browser_workflows.py +0 -0
  158. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/tools/browser/camoufox_manager.py +0 -0
  159. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/tools/browser/vqa_agent.py +0 -0
  160. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/tools/command_runner.py +0 -0
  161. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/tools/common.py +0 -0
  162. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/tools/file_modifications.py +0 -0
  163. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/tools/file_operations.py +0 -0
  164. {code_puppy-0.0.315 → code_puppy-0.0.317}/code_puppy/tools/tools_content.py +0 -0
  165. {code_puppy-0.0.315 → code_puppy-0.0.317}/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.315
3
+ Version: 0.0.317
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
@@ -47,11 +47,10 @@ from code_puppy.config import (
47
47
  get_protected_token_count,
48
48
  get_use_dbos,
49
49
  get_value,
50
- load_mcp_server_configs,
51
50
  )
52
51
  from code_puppy.error_logging import log_error
53
52
  from code_puppy.keymap import cancel_agent_uses_signal, get_cancel_agent_char_code
54
- from code_puppy.mcp_ import ServerConfig, get_mcp_manager
53
+ from code_puppy.mcp_ import get_mcp_manager
55
54
  from code_puppy.messaging import (
56
55
  emit_error,
57
56
  emit_info,
@@ -90,6 +89,9 @@ class BaseAgent(ABC):
90
89
  # Cache for MCP tool definitions (for token estimation)
91
90
  # This is populated after the first successful run when MCP tools are retrieved
92
91
  self._mcp_tool_definitions_cache: List[Dict[str, Any]] = []
92
+ # Shared console for streaming output - should be set by cli_runner
93
+ # to avoid conflicts between spinner's Live display and response streaming
94
+ self._console: Optional[Any] = None
93
95
 
94
96
  @property
95
97
  @abstractmethod
@@ -989,45 +991,31 @@ class BaseAgent(ABC):
989
991
  return self._puppy_rules
990
992
 
991
993
  def load_mcp_servers(self, extra_headers: Optional[Dict[str, str]] = None):
992
- """Load MCP servers through the manager and return pydantic-ai compatible servers."""
994
+ """Load MCP servers through the manager and return pydantic-ai compatible servers.
995
+
996
+ Note: The manager automatically syncs from mcp_servers.json during initialization,
997
+ so we don't need to sync here. Use reload_mcp_servers() to force a re-sync.
998
+ """
993
999
 
994
1000
  mcp_disabled = get_value("disable_mcp_servers")
995
1001
  if mcp_disabled and str(mcp_disabled).lower() in ("1", "true", "yes", "on"):
996
1002
  return []
997
1003
 
998
1004
  manager = get_mcp_manager()
999
- configs = load_mcp_server_configs()
1000
- if not configs:
1001
- existing_servers = manager.list_servers()
1002
- if not existing_servers:
1003
- return []
1004
- else:
1005
- for name, conf in configs.items():
1006
- try:
1007
- server_config = ServerConfig(
1008
- id=conf.get("id", f"{name}_{hash(name)}"),
1009
- name=name,
1010
- type=conf.get("type", "sse"),
1011
- enabled=conf.get("enabled", True),
1012
- config=conf,
1013
- )
1014
- existing = manager.get_server_by_name(name)
1015
- if not existing:
1016
- manager.register_server(server_config)
1017
- else:
1018
- if existing.config != server_config.config:
1019
- manager.update_server(existing.id, server_config)
1020
- except Exception:
1021
- continue
1022
-
1023
1005
  return manager.get_servers_for_agent()
1024
1006
 
1025
1007
  def reload_mcp_servers(self):
1026
- """Reload MCP servers and return updated servers."""
1008
+ """Reload MCP servers and return updated servers.
1009
+
1010
+ Forces a re-sync from mcp_servers.json to pick up any configuration changes.
1011
+ """
1027
1012
  # Clear the MCP tool cache when servers are reloaded
1028
1013
  self._mcp_tool_definitions_cache = []
1029
- self.load_mcp_servers()
1014
+
1015
+ # Force re-sync from mcp_servers.json
1030
1016
  manager = get_mcp_manager()
1017
+ manager.sync_from_config()
1018
+
1031
1019
  return manager.get_servers_for_agent()
1032
1020
 
1033
1021
  def _load_model_with_fallback(
@@ -1291,7 +1279,14 @@ class BaseAgent(ABC):
1291
1279
 
1292
1280
  from code_puppy.messaging.spinner import pause_all_spinners
1293
1281
 
1294
- console = Console()
1282
+ # IMPORTANT: Use the shared console (set by cli_runner) to avoid conflicts
1283
+ # with the spinner's Live display. Multiple Console instances with separate
1284
+ # Live displays cause cursor positioning chaos and line duplication.
1285
+ if self._console is not None:
1286
+ console = self._console
1287
+ else:
1288
+ # Fallback if console not set (shouldn't happen in normal use)
1289
+ console = Console()
1295
1290
 
1296
1291
  # Disable Live display in test mode or non-interactive environments
1297
1292
  # This fixes issues with pexpect PTY where Live() hangs
@@ -706,6 +706,12 @@ async def run_prompt_with_attachments(
706
706
  attachments = [attachment.content for attachment in processed_prompt.attachments]
707
707
  link_attachments = [link.url_part for link in processed_prompt.link_attachments]
708
708
 
709
+ # IMPORTANT: Set the shared console on the agent so that streaming output
710
+ # uses the same console as the spinner. This prevents Live display conflicts
711
+ # that cause line duplication during markdown streaming.
712
+ if spinner_console is not None:
713
+ agent._console = spinner_console
714
+
709
715
  # Create the agent task first so we can track and cancel it
710
716
  agent_task = asyncio.create_task(
711
717
  agent.run_with_mcp(
@@ -201,15 +201,23 @@ class BlockingMCPServerStdio(SimpleCapturedMCPServerStdio):
201
201
 
202
202
  return result
203
203
 
204
- except Exception as e:
204
+ except BaseException as e:
205
205
  # Store error and mark as initialized (with error)
206
- self._init_error = e
206
+ # Unwrap ExceptionGroup if present (Python 3.11+)
207
+ if type(e).__name__ == "ExceptionGroup" and hasattr(e, "exceptions"):
208
+ # Use the first exception as the primary cause
209
+ self._init_error = e.exceptions[0]
210
+ error_details = f"{e.exceptions[0]}"
211
+ else:
212
+ self._init_error = e
213
+ error_details = str(e)
214
+
207
215
  self._initialized.set()
208
216
 
209
217
  # Emit error message
210
218
  server_name = getattr(self, "tool_prefix", self.command)
211
219
  emit_info(
212
- f"❌ MCP Server '{server_name}' failed to initialize: {e}",
220
+ f"❌ MCP Server '{server_name}' failed to initialize: {error_details}",
213
221
  style="red",
214
222
  message_group=self.message_group,
215
223
  )
@@ -6,6 +6,7 @@ that adds management capabilities while maintaining 100% compatibility.
6
6
  """
7
7
 
8
8
  import json
9
+ import os
9
10
  import uuid
10
11
  from dataclasses import dataclass, field
11
12
  from datetime import datetime, timedelta
@@ -222,7 +223,16 @@ class ManagedMCPServer:
222
223
  if "read_timeout" in config:
223
224
  http_kwargs["read_timeout"] = config["read_timeout"]
224
225
  if "headers" in config:
225
- http_kwargs["headers"] = config.get("headers")
226
+ # Expand environment variables in headers
227
+ headers = config.get("headers")
228
+ resolved_headers = {}
229
+ if isinstance(headers, dict):
230
+ for k, v in headers.items():
231
+ if isinstance(v, str):
232
+ resolved_headers[k] = os.path.expandvars(v)
233
+ else:
234
+ resolved_headers[k] = v
235
+ http_kwargs["headers"] = resolved_headers
226
236
  # Create HTTP client if headers are provided but no client specified
227
237
 
228
238
  self._pydantic_server = MCPServerStreamableHTTP(
@@ -243,8 +253,18 @@ class ManagedMCPServer:
243
253
  Configured async HTTP client with custom headers
244
254
  """
245
255
  headers = self.config.config.get("headers", {})
256
+
257
+ # Expand environment variables in headers
258
+ resolved_headers = {}
259
+ if isinstance(headers, dict):
260
+ for k, v in headers.items():
261
+ if isinstance(v, str):
262
+ resolved_headers[k] = os.path.expandvars(v)
263
+ else:
264
+ resolved_headers[k] = v
265
+
246
266
  timeout = self.config.config.get("timeout", 30)
247
- client = create_async_client(headers=headers, timeout=timeout)
267
+ client = create_async_client(headers=resolved_headers, timeout=timeout)
248
268
  return client
249
269
 
250
270
  def enable(self) -> None:
@@ -78,11 +78,76 @@ class MCPManager:
78
78
  # Active managed servers (server_id -> ManagedMCPServer)
79
79
  self._managed_servers: Dict[str, ManagedMCPServer] = {}
80
80
 
81
+ # Sync servers from mcp_servers.json into registry
82
+ self.sync_from_config()
83
+
81
84
  # Load existing servers from registry
82
85
  self._initialize_servers()
83
86
 
84
87
  logger.info("MCPManager initialized with core components")
85
88
 
89
+ def sync_from_config(self) -> None:
90
+ """Sync servers from mcp_servers.json into the registry.
91
+
92
+ This public method ensures that servers defined in the user's
93
+ configuration file are automatically registered with the manager.
94
+ It can be called during initialization or manually to reload
95
+ server configurations.
96
+
97
+ This is the single source of truth for syncing mcp_servers.json
98
+ into the registry, avoiding duplication with base_agent.py.
99
+ """
100
+ try:
101
+ from code_puppy.config import load_mcp_server_configs
102
+
103
+ configs = load_mcp_server_configs()
104
+ if not configs:
105
+ logger.debug("No servers found in mcp_servers.json")
106
+ return
107
+
108
+ synced_count = 0
109
+ updated_count = 0
110
+
111
+ for name, conf in configs.items():
112
+ try:
113
+ # Create ServerConfig from the loaded configuration
114
+ server_config = ServerConfig(
115
+ id=conf.get("id", ""), # Empty ID will be auto-generated
116
+ name=name,
117
+ type=conf.get("type", "sse"),
118
+ enabled=conf.get("enabled", True),
119
+ config=conf,
120
+ )
121
+
122
+ # Check if server already exists by name
123
+ existing = self.registry.get_by_name(name)
124
+
125
+ if not existing:
126
+ # Register new server
127
+ self.registry.register(server_config)
128
+ synced_count += 1
129
+ logger.debug(f"Synced new server from config: {name}")
130
+ else:
131
+ # Update existing server if config has changed
132
+ if existing.config != server_config.config:
133
+ server_config.id = existing.id # Keep existing ID
134
+ self.registry.update(existing.id, server_config)
135
+ updated_count += 1
136
+ logger.debug(f"Updated server from config: {name}")
137
+
138
+ except Exception as e:
139
+ logger.warning(f"Failed to sync server '{name}' from config: {e}")
140
+ continue
141
+
142
+ if synced_count > 0 or updated_count > 0:
143
+ logger.info(
144
+ f"Synced {synced_count} new and updated {updated_count} servers from mcp_servers.json"
145
+ )
146
+
147
+ except Exception as e:
148
+ logger.error(f"Failed to sync from mcp_servers.json: {e}")
149
+ # Don't fail initialization if sync fails
150
+
86
151
  def _initialize_servers(self) -> None:
87
152
  """Initialize managed servers from registry configurations."""
88
153
  configs = self.registry.list_all()
@@ -29,6 +29,13 @@ Example (new):
29
29
  >>> bus.emit(TextMessage(level=MessageLevel.INFO, text="Hello"))
30
30
  """
31
31
 
32
+ # =============================================================================
33
+ # Apply Rich Markdown patches (left-justified headers)
34
+ # =============================================================================
35
+ from .markdown_patches import patch_markdown_headings
36
+
37
+ patch_markdown_headings()
38
+
32
39
  # =============================================================================
33
40
  # Legacy API (backward compatible)
34
41
  # =============================================================================
@@ -220,4 +227,6 @@ __all__ = [
220
227
  "RichConsoleRenderer",
221
228
  "DEFAULT_STYLES",
222
229
  "DIFF_STYLES",
230
+ # Markdown patches
231
+ "patch_markdown_headings",
223
232
  ]
@@ -0,0 +1,57 @@
1
+ """Patches for Rich's Markdown rendering.
2
+
3
+ This module provides customizations to Rich's default Markdown rendering,
4
+ particularly for header justification which is hardcoded to center in Rich.
5
+ """
6
+
7
+ from rich import box
8
+ from rich.markdown import Heading, Markdown
9
+ from rich.panel import Panel
10
+ from rich.text import Text
11
+
12
+
13
+ class LeftJustifiedHeading(Heading):
14
+ """A heading that left-justifies text instead of centering.
15
+
16
+ Rich's default Heading class hardcodes `text.justify = 'center'`,
17
+ which can look odd in a CLI context. This subclass overrides that
18
+ to use left justification instead.
19
+ """
20
+
21
+ def __rich_console__(self, console, options):
22
+ """Render the heading with left justification."""
23
+ text = self.text
24
+ text.justify = "left" # Override Rich's default 'center'
25
+
26
+ if self.tag == "h1":
27
+ # Draw a border around h1s (same as Rich default)
28
+ yield Panel(
29
+ text,
30
+ box=box.HEAVY,
31
+ style="markdown.h1.border",
32
+ )
33
+ else:
34
+ # Styled text for h2 and beyond (same as Rich default)
35
+ if self.tag == "h2":
36
+ yield Text("")
37
+ yield text
38
+
39
+
40
+ _patched = False
41
+
42
+
43
+ def patch_markdown_headings():
44
+ """Patch Rich's Markdown to use left-justified headings.
45
+
46
+ This function is idempotent - calling it multiple times has no effect
47
+ after the first call.
48
+ """
49
+ global _patched
50
+ if _patched:
51
+ return
52
+
53
+ Markdown.elements["heading_open"] = LeftJustifiedHeading
54
+ _patched = True
55
+
56
+
57
+ __all__ = ["patch_markdown_headings", "LeftJustifiedHeading"]
@@ -108,6 +108,12 @@ def make_model_settings(
108
108
  # Handle Anthropic extended thinking settings
109
109
  # Remove top_p as Anthropic doesn't support it with extended thinking
110
110
  model_settings_dict.pop("top_p", None)
111
+
112
+ # Claude extended thinking requires temperature=1.0 (API restriction)
113
+ # Default to 1.0 if not explicitly set by user
114
+ if model_settings_dict.get("temperature") is None:
115
+ model_settings_dict["temperature"] = 1.0
116
+
111
117
  extended_thinking = effective_settings.get("extended_thinking", True)
112
118
  budget_tokens = effective_settings.get("budget_tokens", 10000)
113
119
  if extended_thinking and budget_tokens:
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "code-puppy"
7
- version = "0.0.315"
7
+ version = "0.0.317"
8
8
  description = "Code generation agent"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11,<3.14"
@@ -79,6 +79,7 @@ path = "code_puppy/models_dev_api.json"
79
79
  [tool.ruff.lint.per-file-ignores]
80
80
  "code_puppy/main.py" = ["E402"]
81
81
  "code_puppy/cli_runner.py" = ["E402"]
82
+ "code_puppy/messaging/__init__.py" = ["E402"]
82
83
 
83
84
  [tool.pytest.ini_options]
84
85
  addopts = "--cov=code_puppy --cov-report=term-missing"
File without changes
File without changes
File without changes