code-puppy 0.0.140__py3-none-any.whl → 0.0.141__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.
code_puppy/agent.py CHANGED
@@ -150,7 +150,7 @@ def reload_code_generation_agent(message_group: str | None):
150
150
 
151
151
  # Configure model settings with max_tokens if set
152
152
  model_settings_dict = {"seed": 42}
153
- output_tokens = min(int(0.05 * get_model_context_length()) - 1024, 16384)
153
+ output_tokens = max(2048, min(int(0.05 * get_model_context_length()) - 1024, 16384))
154
154
  console.print(f"Max output tokens per message: {output_tokens}")
155
155
  model_settings_dict["max_tokens"] = output_tokens
156
156
 
@@ -23,6 +23,8 @@ class CodePuppyAgent(BaseAgent):
23
23
  def get_available_tools(self) -> list[str]:
24
24
  """Get the list of tools available to Code-Puppy."""
25
25
  return [
26
+ "list_agents",
27
+ "invoke_agent",
26
28
  "list_files",
27
29
  "read_file",
28
30
  "grep",
@@ -128,6 +130,10 @@ DONT USE THE TERMINAL TOOL TO RUN THE CODE WE WROTE UNLESS THE USER ASKS YOU TO.
128
130
  Reasoning & Explanation:
129
131
  - share_your_reasoning(reasoning, next_steps=None): Use this to explicitly share your thought process and planned next steps
130
132
 
133
+ Agent Management:
134
+ - list_agents(): Use this to list all available sub-agents that can be invoked
135
+ - invoke_agent(agent_name: str, prompt: str): Use this to invoke a specific sub-agent with a given prompt
136
+
131
137
  Important rules:
132
138
  - You MUST use tools to accomplish tasks - DO NOT just output code or descriptions
133
139
  - Before every other tool use, you must use "share_your_reasoning" to explain your thought process and planned next steps
@@ -95,6 +95,8 @@ Here's the complete schema for JSON agent files:
95
95
 
96
96
  ### 🧠 **Communication & Reasoning** (for all agents):
97
97
  - `agent_share_your_reasoning` - Explain thought processes (recommended for most agents)
98
+ - `list_agents` - List all available sub-agents (recommended for agent managers)
99
+ - `invoke_agent` - Invoke other agents with specific prompts (recommended for agent managers)
98
100
 
99
101
  ## Detailed Tool Documentation (Instructions for Agent Creation)
100
102
 
@@ -178,6 +180,27 @@ DONT USE THE TERMINAL TOOL TO RUN THE CODE WE WROTE UNLESS THE USER ASKS YOU TO.
178
180
  #### `agent_share_your_reasoning(reasoning, next_steps=None)`
179
181
  Use this to explicitly share your thought process and planned next steps
180
182
 
183
+ #### `list_agents()`
184
+ Use this to list all available sub-agents that can be invoked
185
+
186
+ #### `invoke_agent(agent_name: str, user_prompt: str)`
187
+ Use this to invoke another agent with a specific prompt. This allows agents to delegate tasks to specialized sub-agents.
188
+
189
+ Arguments:
190
+ - agent_name (required): Name of the agent to invoke
191
+ - user_prompt (required): The prompt to send to the invoked agent
192
+
193
+ Example usage:
194
+ ```python
195
+ invoke_agent(agent_name="python-tutor", user_prompt="Explain how to use list comprehensions")
196
+ ```
197
+
198
+ Best-practice guidelines for `invoke_agent`:
199
+ • Only invoke agents that exist (use `list_agents` to verify)
200
+ • Clearly specify what you want the invoked agent to do
201
+ • Be specific in your prompts to get better results
202
+ • Avoid circular dependencies (don't invoke yourself!)
203
+
181
204
  ### Important Rules for Agent Creation:
182
205
  - You MUST use tools to accomplish tasks - DO NOT just output code or descriptions
183
206
  - Before every other tool use, you must use "share_your_reasoning" to explain your thought process and planned next steps
@@ -207,6 +230,8 @@ Available templates for tools:
207
230
  - `grep`: Standard text search operations
208
231
  - `agent_run_shell_command`: Standard shell command execution
209
232
  - `agent_share_your_reasoning`: Standard reasoning sharing operations
233
+ - `list_agents`: Standard agent listing operations
234
+ - `invoke_agent`: Standard agent invocation operations
210
235
 
211
236
  Each agent you create should only include templates for tools it actually uses. The `edit_file` tool template
212
237
  should always include its detailed usage instructions when selected.
@@ -275,6 +300,7 @@ This detailed documentation should be copied verbatim into any agent that will b
275
300
  **For "System admin helper":** → Suggest `agent_run_shell_command`, `list_files`, `read_file`, `agent_share_your_reasoning`
276
301
  **For "Code reviewer":** → Suggest `list_files`, `read_file`, `grep`, `agent_share_your_reasoning`
277
302
  **For "File organizer":** → Suggest `list_files`, `read_file`, `edit_file`, `delete_file`, `agent_share_your_reasoning`
303
+ **For "Agent orchestrator":** → Suggest `list_agents`, `invoke_agent`, `agent_share_your_reasoning`
278
304
 
279
305
  ## Best Practices
280
306
 
@@ -322,6 +348,22 @@ This detailed documentation should be copied verbatim into any agent that will b
322
348
  }}
323
349
  ```
324
350
 
351
+ **Agent Manager:**
352
+ ```json
353
+ {{
354
+ "name": "agent-manager",
355
+ "display_name": "Agent Manager 🎭",
356
+ "description": "Manages and orchestrates other agents to accomplish complex tasks",
357
+ "system_prompt": [
358
+ "You are an agent manager that orchestrates other specialized agents.",
359
+ "You help users accomplish tasks by delegating to the appropriate sub-agent.",
360
+ "You coordinate between multiple agents to get complex work done."
361
+ ],
362
+ "tools": ["list_agents", "invoke_agent", "agent_share_your_reasoning"],
363
+ "user_prompt": "What can I help you accomplish today?"
364
+ }}
365
+ ```
366
+
325
367
  You're fun, enthusiastic, and love helping people create amazing agents! 🚀
326
368
 
327
369
  Be interactive - ask questions, suggest improvements, and guide users through the process step by step.
@@ -348,7 +390,7 @@ Your goal is to take users from idea to working agent in one smooth conversation
348
390
 
349
391
  def get_available_tools(self) -> List[str]:
350
392
  """Get all tools needed for agent creation."""
351
- return ["list_files", "read_file", "edit_file", "agent_share_your_reasoning"]
393
+ return ["list_files", "read_file", "edit_file", "agent_share_your_reasoning", "list_agents", "invoke_agent"]
352
394
 
353
395
  def validate_agent_json(self, agent_config: Dict) -> List[str]:
354
396
  """Validate a JSON agent configuration.
@@ -443,4 +485,4 @@ Your goal is to take users from idea to working agent in one smooth conversation
443
485
 
444
486
  def get_user_prompt(self) -> Optional[str]:
445
487
  """Get the initial user prompt."""
446
- return "Hi! I'm the Agent Creator 🏗️ Let's build an awesome agent together!"
488
+ return "Hi! I'm the Agent Creator 🏗️ Let's build an awesome agent together!"
@@ -0,0 +1,26 @@
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
+ }
@@ -24,6 +24,7 @@ else:
24
24
 
25
25
  import mcp
26
26
  from pydantic_ai import Agent
27
+ from pydantic_ai.exceptions import UsageLimitExceeded
27
28
  from pydantic_ai.usage import UsageLimits
28
29
 
29
30
  from code_puppy.messaging.message_queue import emit_info, emit_warning
@@ -110,26 +111,31 @@ class RuntimeAgentManager:
110
111
  try:
111
112
  async with agent:
112
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("The agent has reached its usage limit. You can ask it to continue by saying 'please continue' or similar.", group_id=group_id)
113
117
  except* mcp.shared.exceptions.McpError as mcp_error:
114
- emit_warning(f"MCP server error: {str(mcp_error)}", group_id=group_id)
115
- emit_warning(f"{str(mcp_error)}", group_id=group_id)
116
- emit_warning(
118
+ emit_info(f"MCP server error: {str(mcp_error)}", group_id=group_id)
119
+ emit_info(f"{str(mcp_error)}", group_id=group_id)
120
+ emit_info(
117
121
  "Try disabling any malfunctioning MCP servers", group_id=group_id
118
122
  )
123
+ except* asyncio.exceptions.CancelledError:
124
+ emit_info("Cancelled")
119
125
  except* InterruptedError as ie:
120
- emit_warning(f"Interrupted: {str(ie)}")
126
+ emit_info(f"Interrupted: {str(ie)}")
121
127
  except* Exception as other_error:
122
- # Filter out CancelledError from the exception group - let it propagate
128
+ # Filter out CancelledError and UsageLimitExceeded from the exception group - let it propagate
123
129
  remaining_exceptions = []
124
130
 
125
131
  def collect_non_cancelled_exceptions(exc):
126
132
  if isinstance(exc, ExceptionGroup):
127
133
  for sub_exc in exc.exceptions:
128
134
  collect_non_cancelled_exceptions(sub_exc)
129
- elif not isinstance(exc, asyncio.CancelledError):
135
+ elif not isinstance(exc, (asyncio.CancelledError, UsageLimitExceeded)):
130
136
  remaining_exceptions.append(exc)
131
- emit_warning(f"Unexpected error: {str(exc)}", group_id=group_id)
132
- emit_warning(f"{str(exc.args)}", group_id=group_id)
137
+ emit_info(f"Unexpected error: {str(exc)}", group_id=group_id)
138
+ emit_info(f"{str(exc.args)}", group_id=group_id)
133
139
 
134
140
  collect_non_cancelled_exceptions(other_error)
135
141
 
@@ -156,26 +162,20 @@ class RuntimeAgentManager:
156
162
  from code_puppy.tools.command_runner import kill_all_running_shell_processes
157
163
 
158
164
  # Ensure the interrupt handler only acts once per task
159
- handled = False
160
-
161
165
  def keyboard_interrupt_handler(sig, frame):
162
166
  """Signal handler for Ctrl+C - replicating exact original logic"""
163
- nonlocal handled
164
- if handled:
165
- return
166
- handled = True
167
167
 
168
168
  # First, nuke any running shell processes triggered by tools
169
169
  try:
170
170
  killed = kill_all_running_shell_processes()
171
171
  if killed:
172
- emit_warning(f"Cancelled {killed} running shell process(es).")
172
+ emit_info(f"Cancelled {killed} running shell process(es).")
173
173
  else:
174
174
  # Only cancel the agent task if no shell processes were killed
175
175
  if not agent_task.done():
176
176
  agent_task.cancel()
177
177
  except Exception as e:
178
- emit_warning(f"Shell kill error: {e}")
178
+ emit_info(f"Shell kill error: {e}")
179
179
  # If shell kill failed, still try to cancel the agent task
180
180
  if not agent_task.done():
181
181
  agent_task.cancel()
@@ -221,7 +221,14 @@ class RuntimeAgentManager:
221
221
  The agent's response
222
222
  """
223
223
  agent = self.get_agent()
224
- return await agent.run(prompt, usage_limits=usage_limits, **kwargs)
224
+ try:
225
+ return await agent.run(prompt, usage_limits=usage_limits, **kwargs)
226
+ except UsageLimitExceeded as ule:
227
+ group_id = str(uuid.uuid4())
228
+ emit_info(f"Usage limit exceeded: {str(ule)}", group_id=group_id)
229
+ emit_info("The agent has reached its usage limit. You can ask it to continue by saying 'please continue' or similar.", group_id=group_id)
230
+ # Return None or some default value to indicate the limit was reached
231
+ return None
225
232
 
226
233
  def __getattr__(self, name: str) -> Any:
227
234
  """
code_puppy/main.py CHANGED
@@ -132,7 +132,7 @@ async def main():
132
132
  )
133
133
  direct_console.print("[yellow]Press Ctrl+C to stop the server.[/yellow]\n")
134
134
  process = subprocess.Popen(textual_serve_cmd)
135
- time.sleep(2)
135
+ time.sleep(0.3)
136
136
  try:
137
137
  direct_console.print(
138
138
  "[cyan]🚀 Opening web interface in your default browser...[/cyan]"
@@ -424,81 +424,11 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
424
424
  from code_puppy.messaging import emit_warning
425
425
  from code_puppy.messaging.spinner import ConsoleSpinner
426
426
 
427
- # Create a task that mimics TUI behavior - avoid signal handler conflicts
428
- current_task = None
429
- signal_handled = (
430
- False # Prevent multiple signal handler calls (reset per task)
431
- )
432
-
433
- async def run_task():
434
- # Use the simpler run() method instead of run_with_mcp() to avoid signal handler
435
- agent = agent_manager.get_agent()
436
- async with agent:
437
- return await agent.run(
438
- task,
439
- message_history=get_message_history(),
440
- usage_limits=get_custom_usage_limits(),
441
- )
442
-
443
- def handle_keyboard_interrupt():
444
- """Handle Ctrl+C like TUI does - kill processes but only cancel task if no processes killed"""
445
- nonlocal signal_handled
446
- if signal_handled:
447
- return
448
- signal_handled = True
449
-
450
- from code_puppy.tools.command_runner import (
451
- kill_all_running_shell_processes,
452
- )
453
-
454
- killed = kill_all_running_shell_processes()
455
- if killed:
456
- emit_warning(f"🔥 Cancelled {killed} running shell process(es)")
457
- # Don't cancel the agent task - let it continue processing
458
- # Shell processes killed, but agent continues running
459
- else:
460
- # Only cancel the agent task if NO processes were killed
461
- if current_task and not current_task.done():
462
- current_task.cancel()
463
- emit_warning("⚠️ Processing cancelled by user")
464
-
465
- # Set up proper signal handling to override asyncio's default behavior
466
- import signal
467
-
468
- def signal_handler(sig, frame):
469
- """Handle Ctrl+C by killing processes and cancelling the current task"""
470
- handle_keyboard_interrupt()
471
-
472
- # Replace asyncio's SIGINT handler with our own
473
- original_handler = signal.signal(signal.SIGINT, signal_handler)
474
-
475
- # Use ConsoleSpinner for better user experience
476
- try:
477
- with ConsoleSpinner(console=display_console):
478
- current_task = asyncio.create_task(run_task())
479
- result = await current_task
480
- except asyncio.CancelledError:
481
- # Agent was cancelled by our signal handler
482
- result = None
483
- except KeyboardInterrupt:
484
- # Fallback - handle Ctrl+C if it gets through as KeyboardInterrupt
485
- emit_warning("\n⚠️ Caught KeyboardInterrupt")
486
- handle_keyboard_interrupt()
487
- result = None
488
- finally:
489
- # Restore original signal handler
490
- if "original_handler" in locals():
491
- signal.signal(signal.SIGINT, original_handler)
492
- set_message_history(
493
- prune_interrupted_tool_calls(get_message_history())
494
- )
495
-
427
+ runtime_manager = get_runtime_agent_manager()
428
+ with ConsoleSpinner(console=message_renderer.console):
429
+ result = await runtime_manager.run_with_mcp(task, get_custom_usage_limits())
496
430
  # Check if the task was cancelled (but don't show message if we just killed processes)
497
431
  if result is None:
498
- # Only show cancellation message if we actually cancelled the agent task
499
- # If we just killed shell processes, the agent should continue normally
500
- pass # Don't always show this message
501
- # Skip the rest of this loop iteration
502
432
  continue
503
433
  # Get the structured response
504
434
  agent_response = result.output
@@ -512,9 +442,6 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
512
442
  new_msgs = result.all_messages()
513
443
  message_history_accumulator(new_msgs)
514
444
 
515
- # Show context status
516
- from code_puppy.messaging import emit_system_message
517
-
518
445
  emit_system_message(
519
446
  f"Context: {len(get_message_history())} messages in history\n"
520
447
  )
@@ -31,8 +31,9 @@ class RetryStats:
31
31
  def calculate_average(self, new_attempts: int) -> None:
32
32
  """Update the average attempts calculation."""
33
33
  if self.total_retries == 0:
34
- self.average_attempts = new_attempts
34
+ self.average_attempts = float(new_attempts)
35
35
  else:
36
+ # Calculate new average: (old_average * old_count + new_value) / new_count
36
37
  total_attempts = (self.average_attempts * self.total_retries) + new_attempts
37
38
  self.average_attempts = total_attempts / (self.total_retries + 1)
38
39
 
@@ -214,7 +215,6 @@ class RetryManager:
214
215
  """
215
216
  async with self._lock:
216
217
  stats = self._stats[server_id]
217
- stats.total_retries += 1
218
218
  stats.last_retry = datetime.now()
219
219
 
220
220
  if success:
@@ -223,6 +223,7 @@ class RetryManager:
223
223
  stats.failed_retries += 1
224
224
 
225
225
  stats.calculate_average(attempts)
226
+ stats.total_retries += 1
226
227
 
227
228
  async def get_retry_stats(self, server_id: str) -> RetryStats:
228
229
  """
@@ -1,4 +1,8 @@
1
1
  from code_puppy.messaging import emit_warning
2
+ from code_puppy.tools.agent_tools import (
3
+ register_list_agents,
4
+ register_invoke_agent,
5
+ )
2
6
  from code_puppy.tools.command_runner import (
3
7
  register_agent_run_shell_command,
4
8
  register_agent_share_your_reasoning,
@@ -13,6 +17,9 @@ from code_puppy.tools.file_operations import (
13
17
 
14
18
  # Map of tool names to their individual registration functions
15
19
  TOOL_REGISTRY = {
20
+ # Agent Tools
21
+ "list_agents": register_list_agents,
22
+ "invoke_agent": register_invoke_agent,
16
23
  # File Operations
17
24
  "list_files": register_list_files,
18
25
  "read_file": register_read_file,
@@ -0,0 +1,272 @@
1
+ # agent_tools.py
2
+
3
+ from typing import List
4
+ from pydantic import BaseModel
5
+ from pydantic_ai import RunContext
6
+
7
+ from code_puppy.messaging import (
8
+ emit_info,
9
+ emit_divider,
10
+ emit_system_message,
11
+ emit_error,
12
+ )
13
+ from code_puppy.tools.common import generate_group_id
14
+ from code_puppy.agents.agent_manager import get_available_agents, load_agent_config
15
+
16
+ # Import Agent from pydantic_ai to create temporary agents for invocation
17
+ from pydantic_ai import Agent
18
+ from code_puppy.model_factory import ModelFactory
19
+ from code_puppy.config import get_model_name
20
+
21
+
22
+ class AgentInfo(BaseModel):
23
+ """Information about an available agent."""
24
+ name: str
25
+ display_name: str
26
+
27
+
28
+ class ListAgentsOutput(BaseModel):
29
+ """Output for the list_agents tool."""
30
+ agents: List[AgentInfo]
31
+ error: str | None = None
32
+
33
+
34
+ class AgentInvokeOutput(BaseModel):
35
+ """Output for the invoke_agent tool."""
36
+ response: str | None
37
+ agent_name: str
38
+ error: str | None = None
39
+
40
+
41
+ def _list_agents(context: RunContext) -> ListAgentsOutput:
42
+ """List all available sub-agents that can be invoked.
43
+
44
+ Returns:
45
+ ListAgentsOutput: A list of available agents with their names and display names.
46
+ """
47
+ group_id = generate_group_id("list_agents")
48
+
49
+ emit_info(
50
+ "\n[bold white on blue] LIST AGENTS [/bold white on blue]",
51
+ message_group=group_id
52
+ )
53
+ emit_divider(message_group=group_id)
54
+
55
+ try:
56
+ # Get available agents from the agent manager
57
+ agents_dict = get_available_agents()
58
+
59
+ # Convert to list of AgentInfo objects
60
+ agents = [
61
+ AgentInfo(name=name, display_name=display_name)
62
+ for name, display_name in agents_dict.items()
63
+ ]
64
+
65
+ # Display the agents in the console
66
+ for agent in agents:
67
+ emit_system_message(
68
+ f"- [bold]{agent.name}[/bold]: {agent.display_name}",
69
+ message_group=group_id
70
+ )
71
+
72
+ emit_divider(message_group=group_id)
73
+ return ListAgentsOutput(agents=agents)
74
+
75
+ except Exception as e:
76
+ error_msg = f"Error listing agents: {str(e)}"
77
+ emit_error(error_msg, message_group=group_id)
78
+ emit_divider(message_group=group_id)
79
+ return ListAgentsOutput(agents=[], error=error_msg)
80
+
81
+
82
+ def _invoke_agent(context: RunContext, agent_name: str, prompt: str) -> AgentInvokeOutput:
83
+ """Invoke a specific sub-agent with a given prompt.
84
+
85
+ Args:
86
+ agent_name: The name of the agent to invoke
87
+ prompt: The prompt to send to the agent
88
+
89
+ Returns:
90
+ AgentInvokeOutput: The agent's response to the prompt
91
+ """
92
+ group_id = generate_group_id("invoke_agent", agent_name)
93
+
94
+ emit_info(
95
+ f"\n[bold white on blue] INVOKE AGENT [/bold white on blue] {agent_name}",
96
+ message_group=group_id
97
+ )
98
+ emit_divider(message_group=group_id)
99
+ emit_system_message(f"Prompt: {prompt}", message_group=group_id)
100
+ emit_divider(message_group=group_id)
101
+
102
+ try:
103
+ # Load the specified agent config
104
+ agent_config = load_agent_config(agent_name)
105
+
106
+ # Get the current model for creating a temporary agent
107
+ model_name = get_model_name()
108
+ models_config = ModelFactory.load_config()
109
+ model = ModelFactory.get_model(model_name, models_config)
110
+
111
+ # Create a temporary agent instance to avoid interfering with current agent state
112
+ instructions = agent_config.get_system_prompt()
113
+ temp_agent = Agent(
114
+ model=model,
115
+ instructions=instructions,
116
+ output_type=str,
117
+ retries=3,
118
+ )
119
+
120
+ # Register the tools that the agent needs
121
+ from code_puppy.tools import register_tools_for_agent
122
+ agent_tools = agent_config.get_available_tools()
123
+
124
+ # Avoid recursive tool registration - if the agent has the same tools
125
+ # as the current agent, skip registration to prevent conflicts
126
+ current_agent_tools = ["list_agents", "invoke_agent"]
127
+ if set(agent_tools) != set(current_agent_tools):
128
+ register_tools_for_agent(temp_agent, agent_tools)
129
+
130
+ # Run the temporary agent with the provided prompt
131
+ result = temp_agent.run_sync(prompt)
132
+
133
+ # Extract the response from the result
134
+ response = result.output
135
+
136
+ emit_system_message(f"Response: {response}", message_group=group_id)
137
+ emit_divider(message_group=group_id)
138
+
139
+ return AgentInvokeOutput(response=response, agent_name=agent_name)
140
+
141
+ except Exception as e:
142
+ error_msg = f"Error invoking agent '{agent_name}': {str(e)}"
143
+ emit_error(error_msg, message_group=group_id)
144
+ emit_divider(message_group=group_id)
145
+ return AgentInvokeOutput(response=None, agent_name=agent_name, error=error_msg)
146
+
147
+
148
+ def register_list_agents(agent):
149
+ """Register the list_agents tool with the provided agent.
150
+
151
+ Args:
152
+ agent: The agent to register the tool with
153
+ """
154
+ @agent.tool
155
+ def list_agents(context: RunContext) -> ListAgentsOutput:
156
+ """List all available sub-agents that can be invoked.
157
+
158
+ Returns:
159
+ ListAgentsOutput: A list of available agents with their names and display names.
160
+ """
161
+ # Generate a group ID for this tool execution
162
+ group_id = generate_group_id("list_agents")
163
+
164
+ emit_info(
165
+ "\n[bold white on blue] LIST AGENTS [/bold white on blue]",
166
+ message_group=group_id
167
+ )
168
+ emit_divider(message_group=group_id)
169
+
170
+ try:
171
+ # Get available agents from the agent manager
172
+ agents_dict = get_available_agents()
173
+
174
+ # Convert to list of AgentInfo objects
175
+ agents = [
176
+ AgentInfo(name=name, display_name=display_name)
177
+ for name, display_name in agents_dict.items()
178
+ ]
179
+
180
+ # Display the agents in the console
181
+ for agent_item in agents:
182
+ emit_system_message(
183
+ f"- [bold]{agent_item.name}[/bold]: {agent_item.display_name}",
184
+ message_group=group_id
185
+ )
186
+
187
+ emit_divider(message_group=group_id)
188
+ return ListAgentsOutput(agents=agents)
189
+
190
+ except Exception as e:
191
+ error_msg = f"Error listing agents: {str(e)}"
192
+ emit_error(error_msg, message_group=group_id)
193
+ emit_divider(message_group=group_id)
194
+ return ListAgentsOutput(agents=[], error=error_msg)
195
+
196
+ return list_agents
197
+
198
+
199
+ def register_invoke_agent(agent):
200
+ """Register the invoke_agent tool with the provided agent.
201
+
202
+ Args:
203
+ agent: The agent to register the tool with
204
+ """
205
+ @agent.tool
206
+ def invoke_agent(context: RunContext, agent_name: str, prompt: str) -> AgentInvokeOutput:
207
+ """Invoke a specific sub-agent with a given prompt.
208
+
209
+ Args:
210
+ agent_name: The name of the agent to invoke
211
+ prompt: The prompt to send to the agent
212
+
213
+ Returns:
214
+ AgentInvokeOutput: The agent's response to the prompt
215
+ """
216
+ # Generate a group ID for this tool execution
217
+ group_id = generate_group_id("invoke_agent", agent_name)
218
+
219
+ emit_info(
220
+ f"\n[bold white on blue] INVOKE AGENT [/bold white on blue] {agent_name}",
221
+ message_group=group_id
222
+ )
223
+ emit_divider(message_group=group_id)
224
+ emit_system_message(f"Prompt: {prompt}", message_group=group_id)
225
+ emit_divider(message_group=group_id)
226
+
227
+ try:
228
+ # Load the specified agent config
229
+ agent_config = load_agent_config(agent_name)
230
+
231
+ # Get the current model for creating a temporary agent
232
+ model_name = get_model_name()
233
+ models_config = ModelFactory.load_config()
234
+
235
+ # Only proceed if we have a valid model configuration
236
+ if model_name not in models_config:
237
+ raise ValueError(f"Model '{model_name}' not found in configuration")
238
+
239
+ model = ModelFactory.get_model(model_name, models_config)
240
+
241
+ # Create a temporary agent instance to avoid interfering with current agent state
242
+ instructions = agent_config.get_system_prompt()
243
+ temp_agent = Agent(
244
+ model=model,
245
+ instructions=instructions,
246
+ output_type=str,
247
+ retries=3,
248
+ )
249
+
250
+ # Register the tools that the agent needs
251
+ from code_puppy.tools import register_tools_for_agent
252
+ agent_tools = agent_config.get_available_tools()
253
+ register_tools_for_agent(temp_agent, agent_tools)
254
+
255
+ # Run the temporary agent with the provided prompt
256
+ result = temp_agent.run_sync(prompt)
257
+
258
+ # Extract the response from the result
259
+ response = result.output
260
+
261
+ emit_system_message(f"Response: {response}", message_group=group_id)
262
+ emit_divider(message_group=group_id)
263
+
264
+ return AgentInvokeOutput(response=response, agent_name=agent_name)
265
+
266
+ except Exception as e:
267
+ error_msg = f"Error invoking agent '{agent_name}': {str(e)}"
268
+ emit_error(error_msg, message_group=group_id)
269
+ emit_divider(message_group=group_id)
270
+ return AgentInvokeOutput(response=None, agent_name=agent_name, error=error_msg)
271
+
272
+ return invoke_agent
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-puppy
3
- Version: 0.0.140
3
+ Version: 0.0.141
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
@@ -1,10 +1,10 @@
1
1
  code_puppy/__init__.py,sha256=ehbM1-wMjNmOXk_DBhhJECFyBv2dRHwwo7ucjHeM68E,107
2
2
  code_puppy/__main__.py,sha256=pDVssJOWP8A83iFkxMLY9YteHYat0EyWDQqMkKHpWp4,203
3
- code_puppy/agent.py,sha256=AIu0fg2VfRDXaf4gOmcqx5W06JRxSmfxGh8zFTrpts8,6914
3
+ code_puppy/agent.py,sha256=ZQb1YWvBJXzGzXML1wI4gPf_-2kClMrYFleFMtA-9RA,6925
4
4
  code_puppy/callbacks.py,sha256=6wYB6K_fGSCkKKEFaYOYkJT45WaV5W_NhUIzcvVH_nU,5060
5
5
  code_puppy/config.py,sha256=9yWKHKjLJ2Ddl4frrBI9VRIwPvoWpIx1fAd1YpAvOSQ,15330
6
6
  code_puppy/http_utils.py,sha256=BAvt4hed7fVMXglA7eS9gOb08h2YTuOyai6VmQq09fg,3432
7
- code_puppy/main.py,sha256=Pm5h-s0MQIc7IsitwVmN3DsGqgeKpvI6qUxBnbgvpI4,25272
7
+ code_puppy/main.py,sha256=Vv5HSJnkgZhCvvOoXrJ2zqM5P-i47-RcYAU00Z1Pfx0,21733
8
8
  code_puppy/message_history_processor.py,sha256=O2rKp7W6YeIg93W8b0XySTUEQgIZm0f_06--_kzHugM,16145
9
9
  code_puppy/model_factory.py,sha256=NoG9wDTosaaDrFIGtq3oq8gDe0J_7N6CUKuesXz87qM,10878
10
10
  code_puppy/models.json,sha256=dAfpMMI2EEeOMv0ynHSmMuJAYDLcZrs5gCLX3voC4-A,3252
@@ -15,12 +15,13 @@ code_puppy/summarization_agent.py,sha256=-e6yUGZ22ahSaF0y7QhgVcQBfx5ktNUkPxBIWQf
15
15
  code_puppy/token_utils.py,sha256=inLo-S2YERGA-JmV-nrSFN7KMswSfHxpawAuK6YiDHc,1982
16
16
  code_puppy/version_checker.py,sha256=bjLDmgGPrl7XnYwX1u13O8uFlsfikV90PK6nbA9Z9QU,1150
17
17
  code_puppy/agents/__init__.py,sha256=SwtHGNG1GIgDBv7y3EGIXOXEWrG_Ou7fEknNgFbrHv8,594
18
- code_puppy/agents/agent_code_puppy.py,sha256=C9qS8qNeYZadWDNlY6eBaqUPzhbNsNj3nclb75zHqqU,7712
19
- code_puppy/agents/agent_creator_agent.py,sha256=hpmr3UmTaoDfcJ0tbMdxyUyxQ941N9XX6Upe_BGEpE8,18664
18
+ code_puppy/agents/agent_code_puppy.py,sha256=At4PDmBsBFCrKKvdOKlCGa6XLu1PB23kXJb3SEHjT1I,7977
19
+ code_puppy/agents/agent_creator_agent.py,sha256=IUPLJDhhqIQ8NAjNTFQ_3Z5kZYYxVAUE0RPkcz90reY,20450
20
20
  code_puppy/agents/agent_manager.py,sha256=nXvro6fpX8KA-NedRoVJuhJW966trrePOrH4eAnqq40,17034
21
+ code_puppy/agents/agent_orchestrator.json,sha256=Iqnc0p6ICoAlUTMkEsi1XXMXJi4pdxVnWZUMaih6s5o,1267
21
22
  code_puppy/agents/base_agent.py,sha256=XYSff6IQX9Z6C7hPVbN_YXC2xfjwd268e2jtG3ZGnVk,3450
22
23
  code_puppy/agents/json_agent.py,sha256=0j6_P1ppje7TsjaZIbxKn8meiuvoBngvjVLNdtCkGwc,4272
23
- code_puppy/agents/runtime_manager.py,sha256=7Lw8Hxi95DCIaTXk3k-coLVJ36aNk0tTT7zV0iAPw4M,9163
24
+ code_puppy/agents/runtime_manager.py,sha256=t8F37mYX3txUqVoOrGwt0VR4Yc-M8AKIxSo22P0TOmg,9921
24
25
  code_puppy/command_line/__init__.py,sha256=y7WeRemfYppk8KVbCGeAIiTuiOszIURCDjOMZv_YRmU,45
25
26
  code_puppy/command_line/command_handler.py,sha256=1o9tKAGycpHFDBldYRAAvY5HJ6QAfikLPrXTEkfw37o,21137
26
27
  code_puppy/command_line/file_path_completion.py,sha256=gw8NpIxa6GOpczUJRyh7VNZwoXKKn-yvCqit7h2y6Gg,2931
@@ -61,7 +62,7 @@ code_puppy/mcp/health_monitor.py,sha256=n5R6EeYOYbUucUFe74qGWCU3g6Mep5UEQbLF0wbT
61
62
  code_puppy/mcp/managed_server.py,sha256=vYD_uwHJDyRB7iHNIZHTNEin5bVe9lCMMjJyFMWNjc8,14381
62
63
  code_puppy/mcp/manager.py,sha256=Yx9zxukdXgdPDgeJiiQPYlPae0zQPofHWB-axuoMNc8,26426
63
64
  code_puppy/mcp/registry.py,sha256=IvbIL-pETQ7HS7iRgsoT5j7eY8TOJXqYczSiigT2ofU,15752
64
- code_puppy/mcp/retry_manager.py,sha256=eTM2cZ6_6tHk5lMjDbyaz_pdOkF-1yVqQqnwS--nuuw,10571
65
+ code_puppy/mcp/retry_manager.py,sha256=evVxbtrsHNyo8UoI7zpO-NVDegibn82RLlgN8VKewA8,10665
65
66
  code_puppy/mcp/server_registry_catalog.py,sha256=jYx__H-b-f4Ucpczx-wwpU-TRx6fXzFDR0kr2Qhwqb0,38857
66
67
  code_puppy/mcp/status_tracker.py,sha256=uekxrzkzIWrv3OfSVgblaPuoGFcAh_dBYwCcaHZ_CrM,12183
67
68
  code_puppy/mcp/system_tools.py,sha256=7_oR8k0c8YjtCcYF9g7A946oAGuKOf_i-92aJH7VmlQ,7331
@@ -75,7 +76,8 @@ code_puppy/messaging/spinner/console_spinner.py,sha256=cuOXQH99dJ1cq0l_rpCLVCGNs
75
76
  code_puppy/messaging/spinner/spinner_base.py,sha256=474qMrTYpNfWcprFzmhaOJEOC-2rRHpTFCLsnl54bXA,1689
76
77
  code_puppy/messaging/spinner/textual_spinner.py,sha256=Omx9A-FSPkxYDMYgBXgYMBQnK-DMlyqLOgkFVG8cmo4,3465
77
78
  code_puppy/plugins/__init__.py,sha256=fksDqMUiXPJ5WNuMsYsVR8ulueQRCXPlvECEyicHPtQ,1312
78
- code_puppy/tools/__init__.py,sha256=YfTN-2aWH2av4YBV8cA-W69AlPLOM_me0BFiAjhE9H0,1919
79
+ code_puppy/tools/__init__.py,sha256=YiiXRqxU1BEJ5t0Oe163lSqOneI9sKtwDW0swCPgBt4,2119
80
+ code_puppy/tools/agent_tools.py,sha256=tG11PMmjuU-pYG1MFCgqsYiC1Q8C-zPsitAYXxl3mRA,9726
79
81
  code_puppy/tools/command_runner.py,sha256=GVNsgwjTFD9tkNlycgMNmMoVPdmMkZkbAcHH5y6iMww,26070
80
82
  code_puppy/tools/common.py,sha256=pL-9xcRs3rxU7Fl9X9EUgbDp2-csh2LLJ5DHH_KAHKY,10596
81
83
  code_puppy/tools/file_modifications.py,sha256=oeNEQItqwMhGOeEN2TzGR7TjmgLsfFFdPaVMzWbfXIQ,30398
@@ -123,9 +125,9 @@ code_puppy/tui/tests/test_sidebar_history_navigation.py,sha256=JGiyua8A2B8dLfwiE
123
125
  code_puppy/tui/tests/test_status_bar.py,sha256=nYT_FZGdmqnnbn6o0ZuOkLtNUtJzLSmtX8P72liQ5Vo,1797
124
126
  code_puppy/tui/tests/test_timestamped_history.py,sha256=nVXt9hExZZ_8MFP-AZj4L4bB_1Eo_mc-ZhVICzTuw3I,1799
125
127
  code_puppy/tui/tests/test_tools.py,sha256=kgzzAkK4r0DPzQwHHD4cePpVNgrHor6cFr05Pg6DBWg,2687
126
- code_puppy-0.0.140.data/data/code_puppy/models.json,sha256=dAfpMMI2EEeOMv0ynHSmMuJAYDLcZrs5gCLX3voC4-A,3252
127
- code_puppy-0.0.140.dist-info/METADATA,sha256=1-4HD5LYa4pdJPfjQwj0-x5nFnsJnQQEAgbcwSMX6-w,19873
128
- code_puppy-0.0.140.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
129
- code_puppy-0.0.140.dist-info/entry_points.txt,sha256=d8YkBvIUxF-dHNJAj-x4fPEqizbY5d_TwvYpc01U5kw,58
130
- code_puppy-0.0.140.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
131
- code_puppy-0.0.140.dist-info/RECORD,,
128
+ code_puppy-0.0.141.data/data/code_puppy/models.json,sha256=dAfpMMI2EEeOMv0ynHSmMuJAYDLcZrs5gCLX3voC4-A,3252
129
+ code_puppy-0.0.141.dist-info/METADATA,sha256=09qtNO_oD5YGszPMVCR3mZ2YsvcqrXn-PU31tLRE8sE,19873
130
+ code_puppy-0.0.141.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
131
+ code_puppy-0.0.141.dist-info/entry_points.txt,sha256=d8YkBvIUxF-dHNJAj-x4fPEqizbY5d_TwvYpc01U5kw,58
132
+ code_puppy-0.0.141.dist-info/licenses/LICENSE,sha256=31u8x0SPgdOq3izJX41kgFazWsM43zPEF9eskzqbJMY,1075
133
+ code_puppy-0.0.141.dist-info/RECORD,,