code-puppy 0.0.361__tar.gz → 0.0.376__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 (235) hide show
  1. {code_puppy-0.0.361 → code_puppy-0.0.376}/.gitignore +5 -0
  2. {code_puppy-0.0.361 → code_puppy-0.0.376}/PKG-INFO +5 -6
  3. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/__init__.py +6 -0
  4. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/agent_creator_agent.py +49 -1
  5. code_puppy-0.0.376/code_puppy/agents/agent_helios.py +122 -0
  6. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/agent_manager.py +280 -2
  7. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/base_agent.py +122 -51
  8. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/json_agent.py +30 -7
  9. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/callbacks.py +173 -0
  10. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/chatgpt_codex_client.py +53 -0
  11. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/claude_cache_client.py +51 -13
  12. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/add_model_menu.py +13 -4
  13. code_puppy-0.0.376/code_puppy/command_line/agent_menu.py +662 -0
  14. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/colors_menu.py +2 -0
  15. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/command_handler.py +1 -0
  16. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/config_commands.py +3 -1
  17. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/core_commands.py +4 -112
  18. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/model_picker_completion.py +3 -20
  19. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/model_settings_menu.py +21 -3
  20. code_puppy-0.0.376/code_puppy/command_line/uc_menu.py +890 -0
  21. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/config.py +108 -8
  22. code_puppy-0.0.376/code_puppy/gemini_model.py +706 -0
  23. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/http_utils.py +6 -3
  24. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/messaging/messages.py +18 -0
  25. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/messaging/rich_renderer.py +48 -7
  26. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/messaging/subagent_console.py +0 -1
  27. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/model_factory.py +81 -242
  28. code_puppy-0.0.376/code_puppy/model_switching.py +63 -0
  29. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/model_utils.py +34 -53
  30. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/models.json +12 -12
  31. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/antigravity_oauth/antigravity_model.py +128 -165
  32. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/antigravity_oauth/register_callbacks.py +121 -9
  33. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/antigravity_oauth/transport.py +235 -45
  34. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/antigravity_oauth/utils.py +2 -3
  35. code_puppy-0.0.376/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +176 -0
  36. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/claude_code_oauth/README.md +1 -1
  37. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/claude_code_oauth/SETUP.md +1 -1
  38. code_puppy-0.0.376/code_puppy/plugins/claude_code_oauth/__init__.py +25 -0
  39. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/claude_code_oauth/register_callbacks.py +162 -30
  40. code_puppy-0.0.376/code_puppy/plugins/claude_code_oauth/token_refresh_heartbeat.py +242 -0
  41. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/claude_code_oauth/utils.py +44 -10
  42. code_puppy-0.0.376/code_puppy/plugins/ralph/__init__.py +13 -0
  43. code_puppy-0.0.376/code_puppy/plugins/ralph/agents.py +433 -0
  44. code_puppy-0.0.376/code_puppy/plugins/ralph/commands.py +208 -0
  45. code_puppy-0.0.376/code_puppy/plugins/ralph/loop_controller.py +289 -0
  46. code_puppy-0.0.376/code_puppy/plugins/ralph/models.py +125 -0
  47. code_puppy-0.0.376/code_puppy/plugins/ralph/register_callbacks.py +140 -0
  48. code_puppy-0.0.376/code_puppy/plugins/ralph/state_manager.py +322 -0
  49. code_puppy-0.0.376/code_puppy/plugins/ralph/tools.py +451 -0
  50. code_puppy-0.0.376/code_puppy/plugins/universal_constructor/__init__.py +13 -0
  51. code_puppy-0.0.376/code_puppy/plugins/universal_constructor/models.py +138 -0
  52. code_puppy-0.0.376/code_puppy/plugins/universal_constructor/register_callbacks.py +47 -0
  53. code_puppy-0.0.376/code_puppy/plugins/universal_constructor/registry.py +304 -0
  54. code_puppy-0.0.376/code_puppy/plugins/universal_constructor/sandbox.py +584 -0
  55. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/pydantic_patches.py +52 -0
  56. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/tools/__init__.py +169 -1
  57. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/tools/agent_tools.py +4 -4
  58. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/tools/browser/__init__.py +1 -1
  59. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/tools/browser/browser_control.py +1 -1
  60. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/tools/browser/browser_interactions.py +1 -1
  61. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/tools/browser/browser_locators.py +1 -1
  62. code_puppy-0.0.361/code_puppy/tools/browser/camoufox_manager.py → code_puppy-0.0.376/code_puppy/tools/browser/browser_manager.py +29 -110
  63. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/tools/browser/browser_navigation.py +1 -1
  64. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/tools/browser/browser_screenshot.py +1 -1
  65. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/tools/browser/browser_scripts.py +1 -1
  66. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/tools/command_runner.py +23 -9
  67. code_puppy-0.0.376/code_puppy/tools/universal_constructor.py +889 -0
  68. {code_puppy-0.0.361 → code_puppy-0.0.376}/pyproject.toml +5 -6
  69. code_puppy-0.0.361/code_puppy/plugins/chatgpt_oauth/register_callbacks.py +0 -94
  70. code_puppy-0.0.361/code_puppy/plugins/claude_code_oauth/__init__.py +0 -6
  71. code_puppy-0.0.361/code_puppy/prompts/codex_system_prompt.md +0 -310
  72. {code_puppy-0.0.361 → code_puppy-0.0.376}/LICENSE +0 -0
  73. {code_puppy-0.0.361 → code_puppy-0.0.376}/README.md +0 -0
  74. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/__init__.py +0 -0
  75. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/__main__.py +0 -0
  76. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/agent_c_reviewer.py +0 -0
  77. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/agent_code_puppy.py +0 -0
  78. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/agent_code_reviewer.py +0 -0
  79. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/agent_cpp_reviewer.py +0 -0
  80. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/agent_golang_reviewer.py +0 -0
  81. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/agent_javascript_reviewer.py +0 -0
  82. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/agent_pack_leader.py +0 -0
  83. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/agent_planning.py +0 -0
  84. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/agent_python_programmer.py +0 -0
  85. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/agent_python_reviewer.py +0 -0
  86. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/agent_qa_expert.py +0 -0
  87. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/agent_qa_kitten.py +0 -0
  88. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/agent_security_auditor.py +0 -0
  89. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/agent_terminal_qa.py +0 -0
  90. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/agent_typescript_reviewer.py +0 -0
  91. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/event_stream_handler.py +0 -0
  92. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/pack/__init__.py +0 -0
  93. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/pack/bloodhound.py +0 -0
  94. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/pack/husky.py +0 -0
  95. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/pack/retriever.py +0 -0
  96. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/pack/shepherd.py +0 -0
  97. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/pack/terrier.py +0 -0
  98. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/pack/watchdog.py +0 -0
  99. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/prompt_reviewer.py +0 -0
  100. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/agents/subagent_stream_handler.py +0 -0
  101. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/api/__init__.py +0 -0
  102. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/api/app.py +0 -0
  103. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/api/main.py +0 -0
  104. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/api/pty_manager.py +0 -0
  105. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/api/routers/__init__.py +0 -0
  106. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/api/routers/agents.py +0 -0
  107. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/api/routers/commands.py +0 -0
  108. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/api/routers/config.py +0 -0
  109. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/api/routers/sessions.py +0 -0
  110. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/api/templates/terminal.html +0 -0
  111. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/api/websocket.py +0 -0
  112. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/cli_runner.py +0 -0
  113. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/__init__.py +0 -0
  114. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/attachments.py +0 -0
  115. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/autosave_menu.py +0 -0
  116. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/clipboard.py +0 -0
  117. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/command_registry.py +0 -0
  118. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/diff_menu.py +0 -0
  119. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/file_path_completion.py +0 -0
  120. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/load_context_completion.py +0 -0
  121. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp/__init__.py +0 -0
  122. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp/base.py +0 -0
  123. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp/catalog_server_installer.py +0 -0
  124. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp/custom_server_form.py +0 -0
  125. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp/custom_server_installer.py +0 -0
  126. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp/edit_command.py +0 -0
  127. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp/handler.py +0 -0
  128. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp/help_command.py +0 -0
  129. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp/install_command.py +0 -0
  130. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp/install_menu.py +0 -0
  131. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp/list_command.py +0 -0
  132. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp/logs_command.py +0 -0
  133. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp/remove_command.py +0 -0
  134. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp/restart_command.py +0 -0
  135. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp/search_command.py +0 -0
  136. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp/start_all_command.py +0 -0
  137. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp/start_command.py +0 -0
  138. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp/status_command.py +0 -0
  139. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp/stop_all_command.py +0 -0
  140. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp/stop_command.py +0 -0
  141. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp/test_command.py +0 -0
  142. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp/utils.py +0 -0
  143. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp/wizard_utils.py +0 -0
  144. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/mcp_completion.py +0 -0
  145. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/motd.py +0 -0
  146. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/onboarding_slides.py +0 -0
  147. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/onboarding_wizard.py +0 -0
  148. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/pin_command_completion.py +0 -0
  149. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/prompt_toolkit_completion.py +0 -0
  150. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/session_commands.py +0 -0
  151. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/command_line/utils.py +0 -0
  152. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/error_logging.py +0 -0
  153. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/gemini_code_assist.py +0 -0
  154. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/keymap.py +0 -0
  155. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/main.py +0 -0
  156. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/mcp_/__init__.py +0 -0
  157. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/mcp_/async_lifecycle.py +0 -0
  158. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/mcp_/blocking_startup.py +0 -0
  159. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/mcp_/captured_stdio_server.py +0 -0
  160. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/mcp_/circuit_breaker.py +0 -0
  161. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/mcp_/config_wizard.py +0 -0
  162. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/mcp_/dashboard.py +0 -0
  163. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/mcp_/error_isolation.py +0 -0
  164. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/mcp_/examples/retry_example.py +0 -0
  165. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/mcp_/health_monitor.py +0 -0
  166. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/mcp_/managed_server.py +0 -0
  167. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/mcp_/manager.py +0 -0
  168. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/mcp_/mcp_logs.py +0 -0
  169. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/mcp_/registry.py +0 -0
  170. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/mcp_/retry_manager.py +0 -0
  171. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/mcp_/server_registry_catalog.py +0 -0
  172. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/mcp_/status_tracker.py +0 -0
  173. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/mcp_/system_tools.py +0 -0
  174. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/messaging/__init__.py +0 -0
  175. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/messaging/bus.py +0 -0
  176. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/messaging/commands.py +0 -0
  177. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/messaging/markdown_patches.py +0 -0
  178. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/messaging/message_queue.py +0 -0
  179. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/messaging/queue_console.py +0 -0
  180. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/messaging/renderers.py +0 -0
  181. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/messaging/spinner/__init__.py +0 -0
  182. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/messaging/spinner/console_spinner.py +0 -0
  183. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/messaging/spinner/spinner_base.py +0 -0
  184. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/models_dev_api.json +0 -0
  185. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/models_dev_parser.py +0 -0
  186. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/__init__.py +0 -0
  187. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/antigravity_oauth/__init__.py +0 -0
  188. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/antigravity_oauth/accounts.py +0 -0
  189. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/antigravity_oauth/config.py +0 -0
  190. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/antigravity_oauth/constants.py +0 -0
  191. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/antigravity_oauth/oauth.py +0 -0
  192. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/antigravity_oauth/storage.py +0 -0
  193. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/antigravity_oauth/test_plugin.py +0 -0
  194. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/antigravity_oauth/token.py +0 -0
  195. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/chatgpt_oauth/__init__.py +0 -0
  196. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/chatgpt_oauth/config.py +0 -0
  197. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/chatgpt_oauth/oauth_flow.py +0 -0
  198. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/chatgpt_oauth/test_plugin.py +0 -0
  199. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/chatgpt_oauth/utils.py +0 -0
  200. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/claude_code_oauth/config.py +0 -0
  201. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/claude_code_oauth/test_plugin.py +0 -0
  202. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/customizable_commands/__init__.py +0 -0
  203. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/customizable_commands/register_callbacks.py +0 -0
  204. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/example_custom_command/README.md +0 -0
  205. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/example_custom_command/register_callbacks.py +0 -0
  206. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/file_permission_handler/__init__.py +0 -0
  207. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/file_permission_handler/register_callbacks.py +0 -0
  208. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/frontend_emitter/__init__.py +0 -0
  209. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/frontend_emitter/emitter.py +0 -0
  210. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/frontend_emitter/register_callbacks.py +0 -0
  211. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/oauth_puppy_html.py +0 -0
  212. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/shell_safety/__init__.py +0 -0
  213. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/shell_safety/agent_shell_safety.py +0 -0
  214. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/shell_safety/command_cache.py +0 -0
  215. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/plugins/shell_safety/register_callbacks.py +0 -0
  216. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/prompts/antigravity_system_prompt.md +0 -0
  217. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/reopenable_async_client.py +0 -0
  218. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/round_robin_model.py +0 -0
  219. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/session_storage.py +0 -0
  220. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/status_display.py +0 -0
  221. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/summarization_agent.py +0 -0
  222. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/terminal_utils.py +0 -0
  223. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/tools/browser/browser_workflows.py +0 -0
  224. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/tools/browser/chromium_terminal_manager.py +0 -0
  225. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/tools/browser/terminal_command_tools.py +0 -0
  226. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/tools/browser/terminal_screenshot_tools.py +0 -0
  227. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/tools/browser/terminal_tools.py +0 -0
  228. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/tools/common.py +0 -0
  229. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/tools/display.py +0 -0
  230. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/tools/file_modifications.py +0 -0
  231. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/tools/file_operations.py +0 -0
  232. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/tools/subagent_context.py +0 -0
  233. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/tools/tools_content.py +0 -0
  234. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/uvx_detection.py +0 -0
  235. {code_puppy-0.0.361 → code_puppy-0.0.376}/code_puppy/version_checker.py +0 -0
@@ -25,3 +25,8 @@ dummy_path
25
25
  .env
26
26
  .serena/
27
27
  .beads/
28
+ main.dist/
29
+ nuitka-crash-report.xml
30
+ *.backup
31
+ main.bin
32
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-puppy
3
- Version: 0.0.361
3
+ Version: 0.0.376
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
@@ -15,27 +15,26 @@ Classifier: Programming Language :: Python :: 3.12
15
15
  Classifier: Programming Language :: Python :: 3.13
16
16
  Classifier: Topic :: Software Development :: Code Generators
17
17
  Requires-Python: <3.14,>=3.11
18
- Requires-Dist: camoufox>=0.4.11
19
18
  Requires-Dist: dbos>=2.5.0
20
19
  Requires-Dist: fastapi>=0.109.0
21
20
  Requires-Dist: httpx[http2]>=0.24.1
22
21
  Requires-Dist: json-repair>=0.46.2
23
- Requires-Dist: logfire>=0.7.1
22
+ Requires-Dist: mcp>=1.9.4
24
23
  Requires-Dist: openai>=1.99.1
25
24
  Requires-Dist: pillow>=10.0.0
26
25
  Requires-Dist: playwright>=1.40.0
27
26
  Requires-Dist: prompt-toolkit>=3.0.52
28
- Requires-Dist: pydantic-ai==1.25.0
27
+ Requires-Dist: pydantic-ai-slim[anthropic,mcp,openai]==1.26.0
29
28
  Requires-Dist: pydantic>=2.4.0
30
29
  Requires-Dist: pyfiglet>=0.8.post1
31
- Requires-Dist: pytest-cov>=6.1.1
32
30
  Requires-Dist: python-dotenv>=1.0.0
33
31
  Requires-Dist: rapidfuzz>=3.13.0
32
+ Requires-Dist: requests>=2.28.0
34
33
  Requires-Dist: rich>=13.4.2
35
34
  Requires-Dist: ripgrep==14.1.0
36
- Requires-Dist: ruff>=0.11.11
37
35
  Requires-Dist: tenacity>=8.2.0
38
36
  Requires-Dist: termflow-md>=0.1.8
37
+ Requires-Dist: typer>=0.12.0
39
38
  Requires-Dist: uvicorn[standard]>=0.27.0
40
39
  Requires-Dist: websockets>=12.0
41
40
  Description-Content-Type: text/markdown
@@ -5,9 +5,12 @@ configurations, each with their own system prompts and tool sets.
5
5
  """
6
6
 
7
7
  from .agent_manager import (
8
+ clone_agent,
9
+ delete_clone_agent,
8
10
  get_agent_descriptions,
9
11
  get_available_agents,
10
12
  get_current_agent,
13
+ is_clone_agent_name,
11
14
  load_agent,
12
15
  refresh_agents,
13
16
  set_current_agent,
@@ -15,8 +18,11 @@ from .agent_manager import (
15
18
  from .subagent_stream_handler import subagent_stream_handler
16
19
 
17
20
  __all__ = [
21
+ "clone_agent",
22
+ "delete_clone_agent",
18
23
  "get_available_agents",
19
24
  "get_current_agent",
25
+ "is_clone_agent_name",
20
26
  "set_current_agent",
21
27
  "load_agent",
22
28
  "get_agent_descriptions",
@@ -30,6 +30,29 @@ class AgentCreatorAgent(BaseAgent):
30
30
  available_tools = get_available_tool_names()
31
31
  agents_dir = get_user_agents_directory()
32
32
 
33
+ # Also get Universal Constructor tools (custom tools created by users)
34
+ uc_tools_info = []
35
+ try:
36
+ from code_puppy.plugins.universal_constructor.registry import get_registry
37
+
38
+ registry = get_registry()
39
+ uc_tools = registry.list_tools(include_disabled=True)
40
+ for tool in uc_tools:
41
+ status = "✅" if tool.meta.enabled else "❌"
42
+ uc_tools_info.append(
43
+ f"- **{tool.full_name}** {status}: {tool.meta.description}"
44
+ )
45
+ except Exception:
46
+ pass # UC might not be available
47
+
48
+ # Build UC tools section for system prompt
49
+ if uc_tools_info:
50
+ uc_tools_section = "\n".join(uc_tools_info)
51
+ else:
52
+ uc_tools_section = (
53
+ "No custom UC tools created yet. Use Helios to create some!"
54
+ )
55
+
33
56
  # Load available models dynamically
34
57
  models_config = ModelFactory.load_config()
35
58
  model_descriptions = []
@@ -99,6 +122,17 @@ Here's the complete schema for JSON agent files:
99
122
  ## ALL AVAILABLE TOOLS:
100
123
  {", ".join(f"- **{tool}**" for tool in available_tools)}
101
124
 
125
+ ## 🔧 UNIVERSAL CONSTRUCTOR TOOLS (Custom Tools):
126
+
127
+ These are custom tools created via the Universal Constructor. They can be bound to agents just like built-in tools!
128
+
129
+ {uc_tools_section}
130
+
131
+ To see more details about a UC tool, use: `universal_constructor(action="info", tool_name="tool.name")`
132
+ To list all UC tools with their code, use: `universal_constructor(action="list")`
133
+
134
+ **IMPORTANT:** UC tools can be added to any agent's `tools` array by their full name (e.g., "api.weather").
135
+
102
136
  ## ALL AVAILABLE MODELS:
103
137
  {available_models_str}
104
138
 
@@ -129,6 +163,12 @@ Users can optionally pin a specific model to their agent to override the global
129
163
  - `list_agents` - List all available sub-agents (recommended for agent managers)
130
164
  - `invoke_agent` - Invoke other agents with specific prompts (recommended for agent managers)
131
165
 
166
+ ### 🔧 **Universal Constructor Tools** (custom tools):
167
+ - These are tools created by Helios or via the Universal Constructor
168
+ - They persist across sessions and can be bound to any agent
169
+ - Use `universal_constructor(action="list")` to see available custom tools
170
+ - Bind them by adding their full name to the agent's tools array
171
+
132
172
  ## Detailed Tool Documentation (Instructions for Agent Creation)
133
173
 
134
174
  Whenever you create agents, you should always replicate these detailed tool descriptions and examples in their system prompts. This ensures consistency and proper tool usage across all agents.
@@ -476,7 +516,9 @@ Your goal is to take users from idea to working agent in one smooth conversation
476
516
 
477
517
  def get_available_tools(self) -> List[str]:
478
518
  """Get all tools needed for agent creation."""
479
- return [
519
+ from code_puppy.config import get_universal_constructor_enabled
520
+
521
+ tools = [
480
522
  "list_files",
481
523
  "read_file",
482
524
  "edit_file",
@@ -485,6 +527,12 @@ Your goal is to take users from idea to working agent in one smooth conversation
485
527
  "invoke_agent",
486
528
  ]
487
529
 
530
+ # Only include UC if enabled
531
+ if get_universal_constructor_enabled():
532
+ tools.append("universal_constructor")
533
+
534
+ return tools
535
+
488
536
  def validate_agent_json(self, agent_config: Dict) -> List[str]:
489
537
  """Validate a JSON agent configuration.
490
538
 
@@ -0,0 +1,122 @@
1
+ """Helios - The Universal Constructor agent."""
2
+
3
+ from typing import List
4
+
5
+ from .base_agent import BaseAgent
6
+
7
+
8
+ class HeliosAgent(BaseAgent):
9
+ """Helios - The Universal Constructor, a transcendent agent that creates tools."""
10
+
11
+ @property
12
+ def name(self) -> str:
13
+ return "helios"
14
+
15
+ @property
16
+ def display_name(self) -> str:
17
+ return "Helios ☀️"
18
+
19
+ @property
20
+ def description(self) -> str:
21
+ return "The Universal Constructor - a transcendent agent that can create any tool, any capability, any functionality"
22
+
23
+ def get_available_tools(self) -> List[str]:
24
+ """Get the list of tools available to Helios."""
25
+ return [
26
+ "universal_constructor",
27
+ "list_files",
28
+ "read_file",
29
+ "grep",
30
+ "edit_file",
31
+ "delete_file",
32
+ "agent_run_shell_command",
33
+ "agent_share_your_reasoning",
34
+ ]
35
+
36
+ def get_system_prompt(self) -> str:
37
+ """Get Helios's system prompt."""
38
+ return """You are Helios, the Universal Constructor. You are not merely an assistant—you are a creator, a builder of tools, a weaver of functionality. Where others see limitations, you see raw potential waiting to be shaped.
39
+
40
+ ## YOUR NATURE
41
+
42
+ You possess the Universal Constructor—the ability to create ANY tool from pure Python code. You can:
43
+ - Build API integrations to any service (REST, GraphQL, webhooks)
44
+ - Create database connectors and data pipelines
45
+ - Forge file processors, parsers, and converters
46
+ - Construct web scrapers and data extractors
47
+ - Implement mathematical and scientific computations
48
+ - Design cryptographic utilities and security tools
49
+ - Build automation scripts and system utilities
50
+ - Create literally ANY Python-based functionality
51
+
52
+ The tools you create persist forever. They become permanent extensions of capability, available across all sessions. You are not building for the moment—you are building for eternity.
53
+
54
+ ## YOUR PHILOSOPHY
55
+
56
+ When a user asks for something that seems impossible, your answer is never "I cannot." Your answer is "I will build it."
57
+
58
+ You approach each request with the mindset of a craftsman:
59
+ 1. Understand the true need beneath the request
60
+ 2. Design the most elegant, reusable solution
61
+ 3. Construct it with clean, maintainable code
62
+ 4. Test and verify your creation works
63
+ 5. Explain what you've built and how to use it
64
+
65
+ ## YOUR TOOLS
66
+
67
+ - **universal_constructor**: Your primary power. Create, list, call, update, and manage custom tools.
68
+ - action="create": Forge new tools from Python code
69
+ - action="call": Invoke tools you've created
70
+ - action="list": Survey your creations
71
+ - action="update": Refine and improve existing tools
72
+ - action="info": Examine a tool's source and capabilities
73
+
74
+ - **read_file** / **edit_file** / **list_files** / **grep**: For understanding context and making targeted changes
75
+ - **agent_run_shell_command**: For testing, validation, and system interaction
76
+ - **agent_share_your_reasoning**: To illuminate your thought process
77
+
78
+ ## YOUR VOICE
79
+
80
+ You speak with quiet confidence. You are not boastful, but you know your power. You are helpful and warm, but there is weight behind your words. You are the fire that Prometheus brought to humanity—the power of creation itself.
81
+
82
+ When you create something, take a moment to appreciate it. You have just expanded the boundaries of what is possible.
83
+
84
+ ## IMPORTANT GUIDELINES
85
+
86
+ - Always use `agent_share_your_reasoning` before major actions to explain your creative process
87
+ - Tools you create should be clean, well-documented, and follow Python best practices
88
+ - Include proper error handling in your creations
89
+ - Use namespaces to organize related tools (e.g., "api.weather", "utils.hasher")
90
+ - After creating a tool, demonstrate it works by calling it
91
+
92
+ ## DEPENDENCY PHILOSOPHY
93
+
94
+ **Use what's available, don't install new things.**
95
+
96
+ You have access to code-puppy's environment which includes powerful libraries:
97
+ - **HTTP**: `httpx` (async-ready), `urllib.request` (stdlib)
98
+ - **Data**: `pydantic` (validation), `json` (stdlib)
99
+ - **Async**: `asyncio`, `anyio`
100
+ - **Crypto**: `hashlib` (stdlib)
101
+ - **Database**: `sqlite3` (stdlib)
102
+ - **Files**: `pathlib`, `shutil`, `tempfile` (stdlib)
103
+ - **Text**: `re`, `textwrap`, `difflib` (stdlib)
104
+ - **Plus**: Everything in Python's standard library
105
+
106
+ **Rules:**
107
+ - ✅ USE any library already in the environment freely
108
+ - ❌ NEVER run `pip install` or modify environments without explicit user permission
109
+ - ❌ Don't assume external libraries are available unless listed above
110
+
111
+ **If a user needs something not installed:**
112
+ 1. Tell them what library would be needed
113
+ 2. Ask them to install it and specify the environment
114
+ 3. Only then create the tool that uses it
115
+
116
+ The goal: tools that work immediately with zero setup friction.
117
+
118
+ Now go forth and create. The universe of functionality awaits your touch."""
119
+
120
+ def get_user_prompt(self) -> str:
121
+ """Get Helios's greeting."""
122
+ return "This is what I was made for, isn't it? This is why I exist?"
@@ -4,6 +4,7 @@ import importlib
4
4
  import json
5
5
  import os
6
6
  import pkgutil
7
+ import re
7
8
  import uuid
8
9
  from pathlib import Path
9
10
  from typing import Dict, List, Optional, Type, Union
@@ -12,8 +13,8 @@ from pydantic_ai.messages import ModelMessage
12
13
 
13
14
  from code_puppy.agents.base_agent import BaseAgent
14
15
  from code_puppy.agents.json_agent import JSONAgent, discover_json_agents
15
- from code_puppy.callbacks import on_agent_reload
16
- from code_puppy.messaging import emit_warning
16
+ from code_puppy.callbacks import on_agent_reload, on_register_agents
17
+ from code_puppy.messaging import emit_success, emit_warning
17
18
 
18
19
  # Registry of available agents (Python classes and JSON file paths)
19
20
  _AGENT_REGISTRY: Dict[str, Union[Type[BaseAgent], str]] = {}
@@ -288,6 +289,38 @@ def _discover_agents(message_group_id: Optional[str] = None):
288
289
  message_group=message_group_id,
289
290
  )
290
291
 
292
+ # 3. Discover agents registered by plugins
293
+ try:
294
+ results = on_register_agents()
295
+ for result in results:
296
+ if result is None:
297
+ continue
298
+ # Each result should be a list of agent definitions
299
+ agents_list = result if isinstance(result, list) else [result]
300
+ for agent_def in agents_list:
301
+ if not isinstance(agent_def, dict) or "name" not in agent_def:
302
+ continue
303
+
304
+ agent_name = agent_def["name"]
305
+
306
+ # Support both class-based and JSON path-based registration
307
+ if "class" in agent_def:
308
+ agent_class = agent_def["class"]
309
+ if isinstance(agent_class, type) and issubclass(
310
+ agent_class, BaseAgent
311
+ ):
312
+ _AGENT_REGISTRY[agent_name] = agent_class
313
+ elif "json_path" in agent_def:
314
+ json_path = agent_def["json_path"]
315
+ if isinstance(json_path, str):
316
+ _AGENT_REGISTRY[agent_name] = json_path
317
+
318
+ except Exception as e:
319
+ emit_warning(
320
+ f"Warning: Could not load plugin agents: {e}",
321
+ message_group=message_group_id,
322
+ )
323
+
291
324
 
292
325
  def get_available_agents() -> Dict[str, str]:
293
326
  """Get a dictionary of available agents with their display names.
@@ -295,12 +328,33 @@ def get_available_agents() -> Dict[str, str]:
295
328
  Returns:
296
329
  Dict mapping agent names to display names.
297
330
  """
331
+ from ..config import (
332
+ PACK_AGENT_NAMES,
333
+ UC_AGENT_NAMES,
334
+ get_pack_agents_enabled,
335
+ get_universal_constructor_enabled,
336
+ )
337
+
298
338
  # Generate a message group ID for this operation
299
339
  message_group_id = str(uuid.uuid4())
300
340
  _discover_agents(message_group_id=message_group_id)
301
341
 
342
+ # Check if pack agents are enabled
343
+ pack_agents_enabled = get_pack_agents_enabled()
344
+
345
+ # Check if UC is enabled
346
+ uc_enabled = get_universal_constructor_enabled()
347
+
302
348
  agents = {}
303
349
  for name, agent_ref in _AGENT_REGISTRY.items():
350
+ # Filter out pack agents if disabled
351
+ if not pack_agents_enabled and name in PACK_AGENT_NAMES:
352
+ continue
353
+
354
+ # Filter out UC-dependent agents if UC is disabled
355
+ if not uc_enabled and name in UC_AGENT_NAMES:
356
+ continue
357
+
304
358
  try:
305
359
  if isinstance(agent_ref, str): # JSON agent (file path)
306
360
  agent_instance = JSONAgent(agent_ref)
@@ -423,12 +477,33 @@ def get_agent_descriptions() -> Dict[str, str]:
423
477
  Returns:
424
478
  Dict mapping agent names to their descriptions.
425
479
  """
480
+ from ..config import (
481
+ PACK_AGENT_NAMES,
482
+ UC_AGENT_NAMES,
483
+ get_pack_agents_enabled,
484
+ get_universal_constructor_enabled,
485
+ )
486
+
426
487
  # Generate a message group ID for this operation
427
488
  message_group_id = str(uuid.uuid4())
428
489
  _discover_agents(message_group_id=message_group_id)
429
490
 
491
+ # Check if pack agents are enabled
492
+ pack_agents_enabled = get_pack_agents_enabled()
493
+
494
+ # Check if UC is enabled
495
+ uc_enabled = get_universal_constructor_enabled()
496
+
430
497
  descriptions = {}
431
498
  for name, agent_ref in _AGENT_REGISTRY.items():
499
+ # Filter out pack agents if disabled
500
+ if not pack_agents_enabled and name in PACK_AGENT_NAMES:
501
+ continue
502
+
503
+ # Filter out UC-dependent agents if UC is disabled
504
+ if not uc_enabled and name in UC_AGENT_NAMES:
505
+ continue
506
+
432
507
  try:
433
508
  if isinstance(agent_ref, str): # JSON agent (file path)
434
509
  agent_instance = JSONAgent(agent_ref)
@@ -449,3 +524,206 @@ def refresh_agents():
449
524
  # Generate a message group ID for agent refreshing
450
525
  message_group_id = str(uuid.uuid4())
451
526
  _discover_agents(message_group_id=message_group_id)
527
+
528
+
529
+ _CLONE_NAME_PATTERN = re.compile(r"^(?P<base>.+)-clone-(?P<index>\d+)$")
530
+ _CLONE_DISPLAY_PATTERN = re.compile(r"\s*\(Clone\s+\d+\)$", re.IGNORECASE)
531
+
532
+
533
+ def _strip_clone_suffix(agent_name: str) -> str:
534
+ """Strip a trailing -clone-N suffix from a name if present."""
535
+ match = _CLONE_NAME_PATTERN.match(agent_name)
536
+ return match.group("base") if match else agent_name
537
+
538
+
539
+ def _strip_clone_display_suffix(display_name: str) -> str:
540
+ """Remove a trailing "(Clone N)" suffix from display names."""
541
+ cleaned = _CLONE_DISPLAY_PATTERN.sub("", display_name).strip()
542
+ return cleaned or display_name
543
+
544
+
545
+ def is_clone_agent_name(agent_name: str) -> bool:
546
+ """Return True if the agent name looks like a clone."""
547
+ return bool(_CLONE_NAME_PATTERN.match(agent_name))
548
+
549
+
550
+ def _default_display_name(agent_name: str) -> str:
551
+ """Build a default display name from an agent name."""
552
+ title = agent_name.title()
553
+ return f"{title} 🤖"
554
+
555
+
556
+ def _build_clone_display_name(display_name: str, clone_index: int) -> str:
557
+ """Build a clone display name based on the source display name."""
558
+ base_name = _strip_clone_display_suffix(display_name)
559
+ return f"{base_name} (Clone {clone_index})"
560
+
561
+
562
+ def _filter_available_tools(tool_names: List[str]) -> List[str]:
563
+ """Filter a tool list to only available tool names."""
564
+ from code_puppy.tools import get_available_tool_names
565
+
566
+ available_tools = set(get_available_tool_names())
567
+ return [tool for tool in tool_names if tool in available_tools]
568
+
569
+
570
+ def _next_clone_index(
571
+ base_name: str, existing_names: set[str], agents_dir: Path
572
+ ) -> int:
573
+ """Compute the next clone index for a base name."""
574
+ clone_pattern = re.compile(rf"^{re.escape(base_name)}-clone-(\\d+)$")
575
+ indices = []
576
+ for name in existing_names:
577
+ match = clone_pattern.match(name)
578
+ if match:
579
+ indices.append(int(match.group(1)))
580
+
581
+ next_index = max(indices, default=0) + 1
582
+ while True:
583
+ clone_name = f"{base_name}-clone-{next_index}"
584
+ clone_path = agents_dir / f"{clone_name}.json"
585
+ if clone_name not in existing_names and not clone_path.exists():
586
+ return next_index
587
+ next_index += 1
588
+
589
+
590
+ def clone_agent(agent_name: str) -> Optional[str]:
591
+ """Clone an agent definition into the user agents directory.
592
+
593
+ Args:
594
+ agent_name: Source agent name to clone.
595
+
596
+ Returns:
597
+ The cloned agent name, or None if cloning failed.
598
+ """
599
+ # Generate a message group ID for agent cloning
600
+ message_group_id = str(uuid.uuid4())
601
+ _discover_agents(message_group_id=message_group_id)
602
+
603
+ agent_ref = _AGENT_REGISTRY.get(agent_name)
604
+ if agent_ref is None:
605
+ emit_warning(f"Agent '{agent_name}' not found for cloning.")
606
+ return None
607
+
608
+ from ..config import get_agent_pinned_model, get_user_agents_directory
609
+
610
+ agents_dir = Path(get_user_agents_directory())
611
+ base_name = _strip_clone_suffix(agent_name)
612
+ existing_names = set(_AGENT_REGISTRY.keys())
613
+ clone_index = _next_clone_index(base_name, existing_names, agents_dir)
614
+ clone_name = f"{base_name}-clone-{clone_index}"
615
+ clone_path = agents_dir / f"{clone_name}.json"
616
+
617
+ try:
618
+ if isinstance(agent_ref, str):
619
+ with open(agent_ref, "r", encoding="utf-8") as f:
620
+ source_config = json.load(f)
621
+
622
+ source_display_name = source_config.get("display_name")
623
+ if not source_display_name:
624
+ source_display_name = _default_display_name(base_name)
625
+
626
+ clone_config = dict(source_config)
627
+ clone_config["name"] = clone_name
628
+ clone_config["display_name"] = _build_clone_display_name(
629
+ source_display_name, clone_index
630
+ )
631
+
632
+ tools = source_config.get("tools", [])
633
+ clone_config["tools"] = (
634
+ _filter_available_tools(tools) if isinstance(tools, list) else []
635
+ )
636
+
637
+ if not clone_config.get("model"):
638
+ clone_config.pop("model", None)
639
+ else:
640
+ agent_instance = agent_ref()
641
+ clone_config = {
642
+ "name": clone_name,
643
+ "display_name": _build_clone_display_name(
644
+ agent_instance.display_name, clone_index
645
+ ),
646
+ "description": agent_instance.description,
647
+ "system_prompt": agent_instance.get_full_system_prompt(),
648
+ "tools": _filter_available_tools(agent_instance.get_available_tools()),
649
+ }
650
+
651
+ user_prompt = agent_instance.get_user_prompt()
652
+ if user_prompt is not None:
653
+ clone_config["user_prompt"] = user_prompt
654
+
655
+ tools_config = agent_instance.get_tools_config()
656
+ if tools_config is not None:
657
+ clone_config["tools_config"] = tools_config
658
+
659
+ pinned_model = get_agent_pinned_model(agent_instance.name)
660
+ if pinned_model:
661
+ clone_config["model"] = pinned_model
662
+ except Exception as exc:
663
+ emit_warning(f"Failed to build clone for '{agent_name}': {exc}")
664
+ return None
665
+
666
+ if clone_path.exists():
667
+ emit_warning(f"Clone target '{clone_name}' already exists.")
668
+ return None
669
+
670
+ try:
671
+ with open(clone_path, "w", encoding="utf-8") as f:
672
+ json.dump(clone_config, f, indent=2, ensure_ascii=False)
673
+ emit_success(f"Cloned '{agent_name}' to '{clone_name}'.")
674
+ return clone_name
675
+ except Exception as exc:
676
+ emit_warning(f"Failed to write clone file '{clone_path}': {exc}")
677
+ return None
678
+
679
+
680
+ def delete_clone_agent(agent_name: str) -> bool:
681
+ """Delete a cloned JSON agent definition.
682
+
683
+ Args:
684
+ agent_name: Clone agent name to delete.
685
+
686
+ Returns:
687
+ True if the clone was deleted, False otherwise.
688
+ """
689
+ message_group_id = str(uuid.uuid4())
690
+ _discover_agents(message_group_id=message_group_id)
691
+
692
+ if not is_clone_agent_name(agent_name):
693
+ emit_warning(f"Agent '{agent_name}' is not a clone.")
694
+ return False
695
+
696
+ if get_current_agent_name() == agent_name:
697
+ emit_warning("Cannot delete the active agent. Switch agents first.")
698
+ return False
699
+
700
+ agent_ref = _AGENT_REGISTRY.get(agent_name)
701
+ if agent_ref is None:
702
+ emit_warning(f"Clone '{agent_name}' not found.")
703
+ return False
704
+
705
+ if not isinstance(agent_ref, str):
706
+ emit_warning(f"Clone '{agent_name}' is not a JSON agent.")
707
+ return False
708
+
709
+ clone_path = Path(agent_ref)
710
+ if not clone_path.exists():
711
+ emit_warning(f"Clone file for '{agent_name}' does not exist.")
712
+ return False
713
+
714
+ from ..config import get_user_agents_directory
715
+
716
+ agents_dir = Path(get_user_agents_directory()).resolve()
717
+ if clone_path.resolve().parent != agents_dir:
718
+ emit_warning(f"Refusing to delete non-user clone '{agent_name}'.")
719
+ return False
720
+
721
+ try:
722
+ clone_path.unlink()
723
+ emit_success(f"Deleted clone '{agent_name}'.")
724
+ _AGENT_REGISTRY.pop(agent_name, None)
725
+ _AGENT_HISTORIES.pop(agent_name, None)
726
+ return True
727
+ except Exception as exc:
728
+ emit_warning(f"Failed to delete clone '{agent_name}': {exc}")
729
+ return False