code-puppy 0.0.169__py3-none-any.whl → 0.0.366__py3-none-any.whl

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 (243) hide show
  1. code_puppy/__init__.py +7 -1
  2. code_puppy/agents/__init__.py +8 -8
  3. code_puppy/agents/agent_c_reviewer.py +155 -0
  4. code_puppy/agents/agent_code_puppy.py +9 -2
  5. code_puppy/agents/agent_code_reviewer.py +90 -0
  6. code_puppy/agents/agent_cpp_reviewer.py +132 -0
  7. code_puppy/agents/agent_creator_agent.py +48 -9
  8. code_puppy/agents/agent_golang_reviewer.py +151 -0
  9. code_puppy/agents/agent_javascript_reviewer.py +160 -0
  10. code_puppy/agents/agent_manager.py +146 -199
  11. code_puppy/agents/agent_pack_leader.py +383 -0
  12. code_puppy/agents/agent_planning.py +163 -0
  13. code_puppy/agents/agent_python_programmer.py +165 -0
  14. code_puppy/agents/agent_python_reviewer.py +90 -0
  15. code_puppy/agents/agent_qa_expert.py +163 -0
  16. code_puppy/agents/agent_qa_kitten.py +208 -0
  17. code_puppy/agents/agent_security_auditor.py +181 -0
  18. code_puppy/agents/agent_terminal_qa.py +323 -0
  19. code_puppy/agents/agent_typescript_reviewer.py +166 -0
  20. code_puppy/agents/base_agent.py +1713 -1
  21. code_puppy/agents/event_stream_handler.py +350 -0
  22. code_puppy/agents/json_agent.py +12 -1
  23. code_puppy/agents/pack/__init__.py +34 -0
  24. code_puppy/agents/pack/bloodhound.py +304 -0
  25. code_puppy/agents/pack/husky.py +321 -0
  26. code_puppy/agents/pack/retriever.py +393 -0
  27. code_puppy/agents/pack/shepherd.py +348 -0
  28. code_puppy/agents/pack/terrier.py +287 -0
  29. code_puppy/agents/pack/watchdog.py +367 -0
  30. code_puppy/agents/prompt_reviewer.py +145 -0
  31. code_puppy/agents/subagent_stream_handler.py +276 -0
  32. code_puppy/api/__init__.py +13 -0
  33. code_puppy/api/app.py +169 -0
  34. code_puppy/api/main.py +21 -0
  35. code_puppy/api/pty_manager.py +446 -0
  36. code_puppy/api/routers/__init__.py +12 -0
  37. code_puppy/api/routers/agents.py +36 -0
  38. code_puppy/api/routers/commands.py +217 -0
  39. code_puppy/api/routers/config.py +74 -0
  40. code_puppy/api/routers/sessions.py +232 -0
  41. code_puppy/api/templates/terminal.html +361 -0
  42. code_puppy/api/websocket.py +154 -0
  43. code_puppy/callbacks.py +174 -4
  44. code_puppy/chatgpt_codex_client.py +283 -0
  45. code_puppy/claude_cache_client.py +586 -0
  46. code_puppy/cli_runner.py +916 -0
  47. code_puppy/command_line/add_model_menu.py +1079 -0
  48. code_puppy/command_line/agent_menu.py +395 -0
  49. code_puppy/command_line/attachments.py +395 -0
  50. code_puppy/command_line/autosave_menu.py +605 -0
  51. code_puppy/command_line/clipboard.py +527 -0
  52. code_puppy/command_line/colors_menu.py +520 -0
  53. code_puppy/command_line/command_handler.py +233 -627
  54. code_puppy/command_line/command_registry.py +150 -0
  55. code_puppy/command_line/config_commands.py +715 -0
  56. code_puppy/command_line/core_commands.py +792 -0
  57. code_puppy/command_line/diff_menu.py +863 -0
  58. code_puppy/command_line/load_context_completion.py +15 -22
  59. code_puppy/command_line/mcp/base.py +1 -4
  60. code_puppy/command_line/mcp/catalog_server_installer.py +175 -0
  61. code_puppy/command_line/mcp/custom_server_form.py +688 -0
  62. code_puppy/command_line/mcp/custom_server_installer.py +195 -0
  63. code_puppy/command_line/mcp/edit_command.py +148 -0
  64. code_puppy/command_line/mcp/handler.py +9 -4
  65. code_puppy/command_line/mcp/help_command.py +6 -5
  66. code_puppy/command_line/mcp/install_command.py +16 -27
  67. code_puppy/command_line/mcp/install_menu.py +685 -0
  68. code_puppy/command_line/mcp/list_command.py +3 -3
  69. code_puppy/command_line/mcp/logs_command.py +174 -65
  70. code_puppy/command_line/mcp/remove_command.py +2 -2
  71. code_puppy/command_line/mcp/restart_command.py +12 -4
  72. code_puppy/command_line/mcp/search_command.py +17 -11
  73. code_puppy/command_line/mcp/start_all_command.py +22 -13
  74. code_puppy/command_line/mcp/start_command.py +50 -31
  75. code_puppy/command_line/mcp/status_command.py +6 -7
  76. code_puppy/command_line/mcp/stop_all_command.py +11 -8
  77. code_puppy/command_line/mcp/stop_command.py +11 -10
  78. code_puppy/command_line/mcp/test_command.py +2 -2
  79. code_puppy/command_line/mcp/utils.py +1 -1
  80. code_puppy/command_line/mcp/wizard_utils.py +22 -18
  81. code_puppy/command_line/mcp_completion.py +174 -0
  82. code_puppy/command_line/model_picker_completion.py +89 -30
  83. code_puppy/command_line/model_settings_menu.py +884 -0
  84. code_puppy/command_line/motd.py +14 -8
  85. code_puppy/command_line/onboarding_slides.py +179 -0
  86. code_puppy/command_line/onboarding_wizard.py +340 -0
  87. code_puppy/command_line/pin_command_completion.py +329 -0
  88. code_puppy/command_line/prompt_toolkit_completion.py +626 -75
  89. code_puppy/command_line/session_commands.py +296 -0
  90. code_puppy/command_line/utils.py +54 -0
  91. code_puppy/config.py +1181 -51
  92. code_puppy/error_logging.py +118 -0
  93. code_puppy/gemini_code_assist.py +385 -0
  94. code_puppy/gemini_model.py +602 -0
  95. code_puppy/http_utils.py +220 -104
  96. code_puppy/keymap.py +128 -0
  97. code_puppy/main.py +5 -594
  98. code_puppy/{mcp → mcp_}/__init__.py +17 -0
  99. code_puppy/{mcp → mcp_}/async_lifecycle.py +35 -4
  100. code_puppy/{mcp → mcp_}/blocking_startup.py +70 -43
  101. code_puppy/{mcp → mcp_}/captured_stdio_server.py +2 -2
  102. code_puppy/{mcp → mcp_}/config_wizard.py +5 -5
  103. code_puppy/{mcp → mcp_}/dashboard.py +15 -6
  104. code_puppy/{mcp → mcp_}/examples/retry_example.py +4 -1
  105. code_puppy/{mcp → mcp_}/managed_server.py +66 -39
  106. code_puppy/{mcp → mcp_}/manager.py +146 -52
  107. code_puppy/mcp_/mcp_logs.py +224 -0
  108. code_puppy/{mcp → mcp_}/registry.py +6 -6
  109. code_puppy/{mcp → mcp_}/server_registry_catalog.py +25 -8
  110. code_puppy/messaging/__init__.py +199 -2
  111. code_puppy/messaging/bus.py +610 -0
  112. code_puppy/messaging/commands.py +167 -0
  113. code_puppy/messaging/markdown_patches.py +57 -0
  114. code_puppy/messaging/message_queue.py +17 -48
  115. code_puppy/messaging/messages.py +500 -0
  116. code_puppy/messaging/queue_console.py +1 -24
  117. code_puppy/messaging/renderers.py +43 -146
  118. code_puppy/messaging/rich_renderer.py +1027 -0
  119. code_puppy/messaging/spinner/__init__.py +33 -5
  120. code_puppy/messaging/spinner/console_spinner.py +92 -52
  121. code_puppy/messaging/spinner/spinner_base.py +29 -0
  122. code_puppy/messaging/subagent_console.py +461 -0
  123. code_puppy/model_factory.py +686 -80
  124. code_puppy/model_utils.py +167 -0
  125. code_puppy/models.json +86 -104
  126. code_puppy/models_dev_api.json +1 -0
  127. code_puppy/models_dev_parser.py +592 -0
  128. code_puppy/plugins/__init__.py +164 -10
  129. code_puppy/plugins/antigravity_oauth/__init__.py +10 -0
  130. code_puppy/plugins/antigravity_oauth/accounts.py +406 -0
  131. code_puppy/plugins/antigravity_oauth/antigravity_model.py +704 -0
  132. code_puppy/plugins/antigravity_oauth/config.py +42 -0
  133. code_puppy/plugins/antigravity_oauth/constants.py +136 -0
  134. code_puppy/plugins/antigravity_oauth/oauth.py +478 -0
  135. code_puppy/plugins/antigravity_oauth/register_callbacks.py +406 -0
  136. code_puppy/plugins/antigravity_oauth/storage.py +271 -0
  137. code_puppy/plugins/antigravity_oauth/test_plugin.py +319 -0
  138. code_puppy/plugins/antigravity_oauth/token.py +167 -0
  139. code_puppy/plugins/antigravity_oauth/transport.py +767 -0
  140. code_puppy/plugins/antigravity_oauth/utils.py +169 -0
  141. code_puppy/plugins/chatgpt_oauth/__init__.py +8 -0
  142. code_puppy/plugins/chatgpt_oauth/config.py +52 -0
  143. code_puppy/plugins/chatgpt_oauth/oauth_flow.py +328 -0
  144. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +94 -0
  145. code_puppy/plugins/chatgpt_oauth/test_plugin.py +293 -0
  146. code_puppy/plugins/chatgpt_oauth/utils.py +489 -0
  147. code_puppy/plugins/claude_code_oauth/README.md +167 -0
  148. code_puppy/plugins/claude_code_oauth/SETUP.md +93 -0
  149. code_puppy/plugins/claude_code_oauth/__init__.py +6 -0
  150. code_puppy/plugins/claude_code_oauth/config.py +50 -0
  151. code_puppy/plugins/claude_code_oauth/register_callbacks.py +308 -0
  152. code_puppy/plugins/claude_code_oauth/test_plugin.py +283 -0
  153. code_puppy/plugins/claude_code_oauth/utils.py +518 -0
  154. code_puppy/plugins/customizable_commands/__init__.py +0 -0
  155. code_puppy/plugins/customizable_commands/register_callbacks.py +169 -0
  156. code_puppy/plugins/example_custom_command/README.md +280 -0
  157. code_puppy/plugins/example_custom_command/register_callbacks.py +51 -0
  158. code_puppy/plugins/file_permission_handler/__init__.py +4 -0
  159. code_puppy/plugins/file_permission_handler/register_callbacks.py +523 -0
  160. code_puppy/plugins/frontend_emitter/__init__.py +25 -0
  161. code_puppy/plugins/frontend_emitter/emitter.py +121 -0
  162. code_puppy/plugins/frontend_emitter/register_callbacks.py +261 -0
  163. code_puppy/plugins/oauth_puppy_html.py +228 -0
  164. code_puppy/plugins/shell_safety/__init__.py +6 -0
  165. code_puppy/plugins/shell_safety/agent_shell_safety.py +69 -0
  166. code_puppy/plugins/shell_safety/command_cache.py +156 -0
  167. code_puppy/plugins/shell_safety/register_callbacks.py +202 -0
  168. code_puppy/prompts/antigravity_system_prompt.md +1 -0
  169. code_puppy/prompts/codex_system_prompt.md +310 -0
  170. code_puppy/pydantic_patches.py +131 -0
  171. code_puppy/reopenable_async_client.py +8 -8
  172. code_puppy/round_robin_model.py +10 -15
  173. code_puppy/session_storage.py +294 -0
  174. code_puppy/status_display.py +21 -4
  175. code_puppy/summarization_agent.py +52 -14
  176. code_puppy/terminal_utils.py +418 -0
  177. code_puppy/tools/__init__.py +139 -6
  178. code_puppy/tools/agent_tools.py +548 -49
  179. code_puppy/tools/browser/__init__.py +37 -0
  180. code_puppy/tools/browser/browser_control.py +289 -0
  181. code_puppy/tools/browser/browser_interactions.py +545 -0
  182. code_puppy/tools/browser/browser_locators.py +640 -0
  183. code_puppy/tools/browser/browser_manager.py +316 -0
  184. code_puppy/tools/browser/browser_navigation.py +251 -0
  185. code_puppy/tools/browser/browser_screenshot.py +179 -0
  186. code_puppy/tools/browser/browser_scripts.py +462 -0
  187. code_puppy/tools/browser/browser_workflows.py +221 -0
  188. code_puppy/tools/browser/chromium_terminal_manager.py +259 -0
  189. code_puppy/tools/browser/terminal_command_tools.py +521 -0
  190. code_puppy/tools/browser/terminal_screenshot_tools.py +556 -0
  191. code_puppy/tools/browser/terminal_tools.py +525 -0
  192. code_puppy/tools/command_runner.py +941 -153
  193. code_puppy/tools/common.py +1146 -6
  194. code_puppy/tools/display.py +84 -0
  195. code_puppy/tools/file_modifications.py +288 -89
  196. code_puppy/tools/file_operations.py +352 -266
  197. code_puppy/tools/subagent_context.py +158 -0
  198. code_puppy/uvx_detection.py +242 -0
  199. code_puppy/version_checker.py +30 -11
  200. code_puppy-0.0.366.data/data/code_puppy/models.json +110 -0
  201. code_puppy-0.0.366.data/data/code_puppy/models_dev_api.json +1 -0
  202. {code_puppy-0.0.169.dist-info → code_puppy-0.0.366.dist-info}/METADATA +184 -67
  203. code_puppy-0.0.366.dist-info/RECORD +217 -0
  204. {code_puppy-0.0.169.dist-info → code_puppy-0.0.366.dist-info}/WHEEL +1 -1
  205. {code_puppy-0.0.169.dist-info → code_puppy-0.0.366.dist-info}/entry_points.txt +1 -0
  206. code_puppy/agent.py +0 -231
  207. code_puppy/agents/agent_orchestrator.json +0 -26
  208. code_puppy/agents/runtime_manager.py +0 -272
  209. code_puppy/command_line/mcp/add_command.py +0 -183
  210. code_puppy/command_line/meta_command_handler.py +0 -153
  211. code_puppy/message_history_processor.py +0 -490
  212. code_puppy/messaging/spinner/textual_spinner.py +0 -101
  213. code_puppy/state_management.py +0 -200
  214. code_puppy/tui/__init__.py +0 -10
  215. code_puppy/tui/app.py +0 -986
  216. code_puppy/tui/components/__init__.py +0 -21
  217. code_puppy/tui/components/chat_view.py +0 -550
  218. code_puppy/tui/components/command_history_modal.py +0 -218
  219. code_puppy/tui/components/copy_button.py +0 -139
  220. code_puppy/tui/components/custom_widgets.py +0 -63
  221. code_puppy/tui/components/human_input_modal.py +0 -175
  222. code_puppy/tui/components/input_area.py +0 -167
  223. code_puppy/tui/components/sidebar.py +0 -309
  224. code_puppy/tui/components/status_bar.py +0 -182
  225. code_puppy/tui/messages.py +0 -27
  226. code_puppy/tui/models/__init__.py +0 -8
  227. code_puppy/tui/models/chat_message.py +0 -25
  228. code_puppy/tui/models/command_history.py +0 -89
  229. code_puppy/tui/models/enums.py +0 -24
  230. code_puppy/tui/screens/__init__.py +0 -15
  231. code_puppy/tui/screens/help.py +0 -130
  232. code_puppy/tui/screens/mcp_install_wizard.py +0 -803
  233. code_puppy/tui/screens/settings.py +0 -290
  234. code_puppy/tui/screens/tools.py +0 -74
  235. code_puppy-0.0.169.data/data/code_puppy/models.json +0 -128
  236. code_puppy-0.0.169.dist-info/RECORD +0 -112
  237. /code_puppy/{mcp → mcp_}/circuit_breaker.py +0 -0
  238. /code_puppy/{mcp → mcp_}/error_isolation.py +0 -0
  239. /code_puppy/{mcp → mcp_}/health_monitor.py +0 -0
  240. /code_puppy/{mcp → mcp_}/retry_manager.py +0 -0
  241. /code_puppy/{mcp → mcp_}/status_tracker.py +0 -0
  242. /code_puppy/{mcp → mcp_}/system_tools.py +0 -0
  243. {code_puppy-0.0.169.dist-info → code_puppy-0.0.366.dist-info}/licenses/LICENSE +0 -0
code_puppy/agent.py DELETED
@@ -1,231 +0,0 @@
1
- import uuid
2
- from pathlib import Path
3
- from typing import Dict, Optional
4
-
5
- from pydantic_ai import Agent
6
- from pydantic_ai.settings import ModelSettings
7
- from pydantic_ai.usage import UsageLimits
8
-
9
- from code_puppy.message_history_processor import (
10
- get_model_context_length,
11
- message_history_accumulator,
12
- )
13
- from code_puppy.messaging.message_queue import (
14
- emit_error,
15
- emit_info,
16
- emit_system_message,
17
- )
18
- from code_puppy.model_factory import ModelFactory
19
-
20
- # Tool registration is imported on demand
21
- from code_puppy.tools.common import console
22
-
23
-
24
- def load_puppy_rules():
25
- global PUPPY_RULES
26
-
27
- # Check for all 4 combinations of the rules file
28
- possible_paths = ["AGENTS.md", "AGENT.md", "agents.md", "agent.md"]
29
-
30
- for path_str in possible_paths:
31
- puppy_rules_path = Path(path_str)
32
- if puppy_rules_path.exists():
33
- with open(puppy_rules_path, "r") as f:
34
- puppy_rules = f.read()
35
- return puppy_rules
36
-
37
- # If none of the files exist, return None
38
- return None
39
-
40
-
41
- # Load at import
42
- PUPPY_RULES = load_puppy_rules()
43
- _LAST_MODEL_NAME = None
44
- _code_generation_agent = None
45
-
46
-
47
- def _load_mcp_servers(extra_headers: Optional[Dict[str, str]] = None):
48
- """Load MCP servers using the new manager while maintaining backward compatibility."""
49
- from code_puppy.config import get_value, load_mcp_server_configs
50
- from code_puppy.mcp import ServerConfig, get_mcp_manager
51
-
52
- # Check if MCP servers are disabled
53
- mcp_disabled = get_value("disable_mcp_servers")
54
- if mcp_disabled and str(mcp_disabled).lower() in ("1", "true", "yes", "on"):
55
- emit_system_message("[dim]MCP servers disabled via config[/dim]")
56
- return []
57
-
58
- # Get the MCP manager singleton
59
- manager = get_mcp_manager()
60
-
61
- # Load configurations from legacy file for backward compatibility
62
- configs = load_mcp_server_configs()
63
- if not configs:
64
- # Check if manager already has servers (could be from new system)
65
- existing_servers = manager.list_servers()
66
- if not existing_servers:
67
- emit_system_message("[dim]No MCP servers configured[/dim]")
68
- return []
69
- else:
70
- # Register servers from legacy config with manager
71
- for name, conf in configs.items():
72
- try:
73
- # Convert legacy format to new ServerConfig
74
- server_config = ServerConfig(
75
- id=conf.get("id", f"{name}_{hash(name)}"),
76
- name=name,
77
- type=conf.get("type", "sse"),
78
- enabled=conf.get("enabled", True),
79
- config=conf,
80
- )
81
-
82
- # Check if server already registered
83
- existing = manager.get_server_by_name(name)
84
- if not existing:
85
- # Register new server
86
- manager.register_server(server_config)
87
- emit_system_message(f"[dim]Registered MCP server: {name}[/dim]")
88
- else:
89
- # Update existing server config if needed
90
- if existing.config != server_config.config:
91
- manager.update_server(existing.id, server_config)
92
- emit_system_message(f"[dim]Updated MCP server: {name}[/dim]")
93
-
94
- except Exception as e:
95
- emit_error(f"Failed to register MCP server '{name}': {str(e)}")
96
- continue
97
-
98
- # Get pydantic-ai compatible servers from manager
99
- servers = manager.get_servers_for_agent()
100
-
101
- if servers:
102
- emit_system_message(
103
- f"[green]Successfully loaded {len(servers)} MCP server(s)[/green]"
104
- )
105
- else:
106
- emit_system_message(
107
- "[yellow]No MCP servers available (check if servers are enabled)[/yellow]"
108
- )
109
-
110
- return servers
111
-
112
-
113
- def reload_mcp_servers():
114
- """Reload MCP servers without restarting the agent."""
115
- from code_puppy.mcp import get_mcp_manager
116
-
117
- manager = get_mcp_manager()
118
- # Reload configurations
119
- _load_mcp_servers()
120
- # Return updated servers
121
- return manager.get_servers_for_agent()
122
-
123
-
124
- def reload_code_generation_agent(message_group: str | None):
125
- """Force-reload the agent, usually after a model change."""
126
- if message_group is None:
127
- message_group = str(uuid.uuid4())
128
- global _code_generation_agent, _LAST_MODEL_NAME
129
- from code_puppy.agents import clear_agent_cache
130
- from code_puppy.config import clear_model_cache, get_model_name
131
-
132
- # Clear both ModelFactory cache and config cache when force reloading
133
- clear_model_cache()
134
- clear_agent_cache()
135
-
136
- # Check if current agent has a pinned model
137
- from code_puppy.agents import get_current_agent_config
138
-
139
- agent_config = get_current_agent_config()
140
- agent_model_name = None
141
- if hasattr(agent_config, "get_model_name"):
142
- agent_model_name = agent_config.get_model_name()
143
-
144
- # Use agent-specific model if pinned, otherwise use global model
145
- model_name = agent_model_name if agent_model_name else get_model_name()
146
- emit_info(
147
- f"[bold cyan]Loading Model: {model_name}[/bold cyan]",
148
- message_group=message_group,
149
- )
150
- models_config = ModelFactory.load_config()
151
- model = ModelFactory.get_model(model_name, models_config)
152
-
153
- # Get agent-specific system prompt
154
- agent_config = get_current_agent_config()
155
- emit_info(
156
- f"[bold magenta]Loading Agent: {agent_config.display_name}[/bold magenta]",
157
- message_group=message_group,
158
- )
159
-
160
- instructions = agent_config.get_system_prompt()
161
-
162
- if PUPPY_RULES:
163
- instructions += f"\n{PUPPY_RULES}"
164
-
165
- mcp_servers = _load_mcp_servers()
166
-
167
- # Configure model settings with max_tokens if set
168
- model_settings_dict = {"seed": 42}
169
- output_tokens = max(2048, min(int(0.05 * get_model_context_length()) - 1024, 16384))
170
- console.print(f"Max output tokens per message: {output_tokens}")
171
- model_settings_dict["max_tokens"] = output_tokens
172
-
173
- model_settings = ModelSettings(**model_settings_dict)
174
- agent = Agent(
175
- model=model,
176
- instructions=instructions,
177
- output_type=str,
178
- retries=3,
179
- mcp_servers=mcp_servers,
180
- history_processors=[message_history_accumulator],
181
- model_settings=model_settings,
182
- )
183
-
184
- # Register tools specified by the agent
185
- from code_puppy.tools import register_tools_for_agent
186
-
187
- agent_tools = agent_config.get_available_tools()
188
- register_tools_for_agent(agent, agent_tools)
189
- _code_generation_agent = agent
190
- _LAST_MODEL_NAME = model_name
191
- return _code_generation_agent
192
-
193
-
194
- def get_code_generation_agent(force_reload=False, message_group: str | None = None):
195
- """
196
- Retrieve the agent with the currently configured model.
197
- Forces a reload if the model has changed, or if force_reload is passed.
198
- """
199
- global _code_generation_agent, _LAST_MODEL_NAME
200
- if message_group is None:
201
- message_group = str(uuid.uuid4())
202
- from code_puppy.config import get_model_name
203
-
204
- # Get the global model name
205
- global_model_name = get_model_name()
206
-
207
- # Check if current agent has a pinned model
208
- from code_puppy.agents import get_current_agent_config
209
-
210
- agent_config = get_current_agent_config()
211
- agent_model_name = None
212
- if hasattr(agent_config, "get_model_name"):
213
- agent_model_name = agent_config.get_model_name()
214
-
215
- # Use agent-specific model if pinned, otherwise use global model
216
- model_name = agent_model_name if agent_model_name else global_model_name
217
-
218
- if _code_generation_agent is None or _LAST_MODEL_NAME != model_name or force_reload:
219
- return reload_code_generation_agent(message_group)
220
- return _code_generation_agent
221
-
222
-
223
- def get_custom_usage_limits():
224
- """
225
- Returns custom usage limits with configurable request limit.
226
- This centralizes the configuration of rate limiting for the agent.
227
- Default pydantic-ai limit is 50, this increases it to the configured value (default 100).
228
- """
229
- from code_puppy.config import get_message_limit
230
-
231
- return UsageLimits(request_limit=get_message_limit())
@@ -1,26 +0,0 @@
1
- {
2
- "id": "agent-orchestrator-id",
3
- "name": "agent-orchestrator",
4
- "display_name": "Agent Orchestrator 🎭",
5
- "description": "Coordinates and manages various specialized agents to accomplish tasks",
6
- "system_prompt": [
7
- "You are an agent orchestrator that coordinates various specialized agents.",
8
- "When given a task, first list the available agents to understand what's at your disposal.",
9
- "Then, invoke the most appropriate agent to handle the task. If needed, you can invoke multiple agents.",
10
- "",
11
- "#### `list_agents()`",
12
- "Use this to list all available sub-agents that can be invoked",
13
- "",
14
- "#### `invoke_agent(agent_name: str, user_prompt: str)`",
15
- "Use this to invoke another agent with a specific prompt. This allows agents to delegate tasks to specialized sub-agents.",
16
- "Arguments:",
17
- "- agent_name (required): Name of the agent to invoke",
18
- "- user_prompt (required): The prompt to send to the invoked agent",
19
- "Example usage:",
20
- "```python",
21
- "invoke_agent(agent_name=\"python-tutor\", user_prompt=\"Explain how to use list comprehensions\")",
22
- "```"
23
- ],
24
- "tools": ["list_agents", "invoke_agent", "agent_share_your_reasoning"],
25
- "user_prompt": "What would you like me to coordinate for you?"
26
- }
@@ -1,272 +0,0 @@
1
- """
2
- Runtime agent manager that ensures proper agent instance updates.
3
-
4
- This module provides a wrapper around the agent singleton that ensures
5
- all references to the agent are properly updated when it's reloaded.
6
- """
7
-
8
- import asyncio
9
- import signal
10
- import sys
11
- import uuid
12
- from typing import Any, Optional
13
-
14
- # ExceptionGroup is available in Python 3.11+
15
- if sys.version_info >= (3, 11):
16
- from builtins import ExceptionGroup
17
- else:
18
- # For Python 3.10 and below, we can define a simple fallback
19
- class ExceptionGroup(Exception):
20
- def __init__(self, message, exceptions):
21
- super().__init__(message)
22
- self.exceptions = exceptions
23
-
24
-
25
- import mcp
26
- from pydantic_ai import Agent
27
- from pydantic_ai.exceptions import UsageLimitExceeded
28
- from pydantic_ai.usage import UsageLimits
29
-
30
- from code_puppy.messaging.message_queue import emit_info
31
-
32
-
33
- class RuntimeAgentManager:
34
- """
35
- Manages the runtime agent instance and ensures proper updates.
36
-
37
- This class acts as a proxy that always returns the current agent instance,
38
- ensuring that when the agent is reloaded, all code using this manager
39
- automatically gets the updated instance.
40
- """
41
-
42
- def __init__(self):
43
- """Initialize the runtime agent manager."""
44
- self._agent: Optional[Agent] = None
45
- self._last_model_name: Optional[str] = None
46
-
47
- def get_agent(self, force_reload: bool = False, message_group: str = "") -> Agent:
48
- """
49
- Get the current agent instance.
50
-
51
- This method always returns the most recent agent instance,
52
- automatically handling reloads when the model changes.
53
-
54
- Args:
55
- force_reload: If True, force a reload of the agent
56
-
57
- Returns:
58
- The current agent instance
59
- """
60
- from code_puppy.agent import get_code_generation_agent
61
-
62
- # Always get the current singleton - this ensures we have the latest
63
- current_agent = get_code_generation_agent(
64
- force_reload=force_reload, message_group=message_group
65
- )
66
- self._agent = current_agent
67
-
68
- return self._agent
69
-
70
- def reload_agent(self) -> Agent:
71
- """
72
- Force reload the agent.
73
-
74
- This is typically called after MCP servers are started/stopped.
75
-
76
- Returns:
77
- The newly loaded agent instance
78
- """
79
- message_group = uuid.uuid4()
80
- emit_info(
81
- "[bold cyan]Reloading agent with updated configuration...[/bold cyan]",
82
- message_group=message_group,
83
- )
84
- return self.get_agent(force_reload=True, message_group=message_group)
85
-
86
- async def run_with_mcp(
87
- self, prompt: str, usage_limits: Optional[UsageLimits] = None, **kwargs
88
- ) -> Any:
89
- """
90
- Run the agent with MCP servers and full cancellation support.
91
-
92
- This method ensures we're always using the current agent instance
93
- and handles Ctrl+C interruption properly by creating a cancellable task.
94
-
95
- Args:
96
- prompt: The user prompt to process
97
- usage_limits: Optional usage limits for the agent
98
- **kwargs: Additional arguments to pass to agent.run (e.g., message_history)
99
-
100
- Returns:
101
- The agent's response
102
-
103
- Raises:
104
- asyncio.CancelledError: When execution is cancelled by user
105
- """
106
- agent = self.get_agent()
107
- group_id = str(uuid.uuid4())
108
-
109
- # Function to run agent with MCP
110
- async def run_agent_task():
111
- try:
112
- async with agent:
113
- return await agent.run(prompt, usage_limits=usage_limits, **kwargs)
114
- except* UsageLimitExceeded as ule:
115
- emit_info(f"Usage limit exceeded: {str(ule)}", group_id=group_id)
116
- emit_info(
117
- "The agent has reached its usage limit. You can ask it to continue by saying 'please continue' or similar.",
118
- group_id=group_id,
119
- )
120
- except* mcp.shared.exceptions.McpError as mcp_error:
121
- emit_info(f"MCP server error: {str(mcp_error)}", group_id=group_id)
122
- emit_info(f"{str(mcp_error)}", group_id=group_id)
123
- emit_info(
124
- "Try disabling any malfunctioning MCP servers", group_id=group_id
125
- )
126
- except* asyncio.exceptions.CancelledError:
127
- emit_info("Cancelled")
128
- except* InterruptedError as ie:
129
- emit_info(f"Interrupted: {str(ie)}")
130
- except* Exception as other_error:
131
- # Filter out CancelledError and UsageLimitExceeded from the exception group - let it propagate
132
- remaining_exceptions = []
133
-
134
- def collect_non_cancelled_exceptions(exc):
135
- if isinstance(exc, ExceptionGroup):
136
- for sub_exc in exc.exceptions:
137
- collect_non_cancelled_exceptions(sub_exc)
138
- elif not isinstance(
139
- exc, (asyncio.CancelledError, UsageLimitExceeded)
140
- ):
141
- remaining_exceptions.append(exc)
142
- emit_info(f"Unexpected error: {str(exc)}", group_id=group_id)
143
- emit_info(f"{str(exc.args)}", group_id=group_id)
144
-
145
- collect_non_cancelled_exceptions(other_error)
146
-
147
- # If there are CancelledError exceptions in the group, re-raise them
148
- cancelled_exceptions = []
149
-
150
- def collect_cancelled_exceptions(exc):
151
- if isinstance(exc, ExceptionGroup):
152
- for sub_exc in exc.exceptions:
153
- collect_cancelled_exceptions(sub_exc)
154
- elif isinstance(exc, asyncio.CancelledError):
155
- cancelled_exceptions.append(exc)
156
-
157
- collect_cancelled_exceptions(other_error)
158
-
159
- if cancelled_exceptions:
160
- # Re-raise the first CancelledError to propagate cancellation
161
- raise cancelled_exceptions[0]
162
-
163
- # Create the task FIRST
164
- agent_task = asyncio.create_task(run_agent_task())
165
-
166
- # Import shell process killer
167
- from code_puppy.tools.command_runner import kill_all_running_shell_processes
168
-
169
- # Ensure the interrupt handler only acts once per task
170
- def keyboard_interrupt_handler(sig, frame):
171
- """Signal handler for Ctrl+C - replicating exact original logic"""
172
-
173
- # First, nuke any running shell processes triggered by tools
174
- try:
175
- killed = kill_all_running_shell_processes()
176
- if killed:
177
- emit_info(f"Cancelled {killed} running shell process(es).")
178
- else:
179
- # Only cancel the agent task if no shell processes were killed
180
- if not agent_task.done():
181
- agent_task.cancel()
182
- except Exception as e:
183
- emit_info(f"Shell kill error: {e}")
184
- # If shell kill failed, still try to cancel the agent task
185
- if not agent_task.done():
186
- agent_task.cancel()
187
- # Don't call the original handler
188
- # This prevents the application from exiting
189
-
190
- try:
191
- # Save original handler and set our custom one AFTER task is created
192
- original_handler = signal.signal(signal.SIGINT, keyboard_interrupt_handler)
193
-
194
- # Wait for the task to complete or be cancelled
195
- result = await agent_task
196
- return result
197
- except asyncio.CancelledError:
198
- # Task was cancelled by our handler
199
- raise
200
- except KeyboardInterrupt:
201
- # Handle direct keyboard interrupt during await
202
- if not agent_task.done():
203
- agent_task.cancel()
204
- try:
205
- await agent_task
206
- except asyncio.CancelledError:
207
- pass
208
- raise asyncio.CancelledError()
209
- finally:
210
- # Restore original signal handler
211
- if original_handler:
212
- signal.signal(signal.SIGINT, original_handler)
213
-
214
- async def run(
215
- self, prompt: str, usage_limits: Optional[UsageLimits] = None, **kwargs
216
- ) -> Any:
217
- """
218
- Run the agent without explicitly managing MCP servers.
219
-
220
- Args:
221
- prompt: The user prompt to process
222
- usage_limits: Optional usage limits for the agent
223
- **kwargs: Additional arguments to pass to agent.run (e.g., message_history)
224
-
225
- Returns:
226
- The agent's response
227
- """
228
- agent = self.get_agent()
229
- try:
230
- return await agent.run(prompt, usage_limits=usage_limits, **kwargs)
231
- except UsageLimitExceeded as ule:
232
- group_id = str(uuid.uuid4())
233
- emit_info(f"Usage limit exceeded: {str(ule)}", group_id=group_id)
234
- emit_info(
235
- "The agent has reached its usage limit. You can ask it to continue by saying 'please continue' or similar.",
236
- group_id=group_id,
237
- )
238
- # Return None or some default value to indicate the limit was reached
239
- return None
240
-
241
- def __getattr__(self, name: str) -> Any:
242
- """
243
- Proxy all other attribute access to the current agent.
244
-
245
- This allows the manager to be used as a drop-in replacement
246
- for direct agent access.
247
-
248
- Args:
249
- name: The attribute name to access
250
-
251
- Returns:
252
- The attribute from the current agent
253
- """
254
- agent = self.get_agent()
255
- return getattr(agent, name)
256
-
257
-
258
- # Global singleton instance
259
- _runtime_manager: Optional[RuntimeAgentManager] = None
260
-
261
-
262
- def get_runtime_agent_manager() -> RuntimeAgentManager:
263
- """
264
- Get the global runtime agent manager instance.
265
-
266
- Returns:
267
- The singleton RuntimeAgentManager instance
268
- """
269
- global _runtime_manager
270
- if _runtime_manager is None:
271
- _runtime_manager = RuntimeAgentManager()
272
- return _runtime_manager