code-puppy 0.0.123__py3-none-any.whl → 0.0.125__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.
@@ -0,0 +1,211 @@
1
+ """Agent manager for handling different agent configurations."""
2
+
3
+ import importlib
4
+ import pkgutil
5
+ import uuid
6
+ from typing import Dict, Optional, Type, Union
7
+
8
+ from code_puppy.config import get_value, set_config_value
9
+ from .base_agent import BaseAgent
10
+ from .json_agent import JSONAgent, discover_json_agents
11
+ from ..callbacks import on_agent_reload
12
+ from ..messaging import emit_warning
13
+
14
+ # Registry of available agents (Python classes and JSON file paths)
15
+ _AGENT_REGISTRY: Dict[str, Union[Type[BaseAgent], str]] = {}
16
+ _CURRENT_AGENT_CONFIG: Optional[BaseAgent] = None
17
+
18
+
19
+ def _discover_agents(message_group_id: Optional[str] = None):
20
+ """Dynamically discover all agent classes and JSON agents."""
21
+ # Always clear the registry to force refresh
22
+ _AGENT_REGISTRY.clear()
23
+
24
+ # 1. Discover Python agent classes in the agents package
25
+ import code_puppy.agents as agents_package
26
+
27
+ # Iterate through all modules in the agents package
28
+ for _, modname, _ in pkgutil.iter_modules(agents_package.__path__):
29
+ if modname.startswith("_") or modname in [
30
+ "base_agent",
31
+ "json_agent",
32
+ "agent_manager",
33
+ ]:
34
+ continue
35
+
36
+ try:
37
+ # Import the module
38
+ module = importlib.import_module(f"code_puppy.agents.{modname}")
39
+
40
+ # Look for BaseAgent subclasses
41
+ for attr_name in dir(module):
42
+ attr = getattr(module, attr_name)
43
+ if (
44
+ isinstance(attr, type)
45
+ and issubclass(attr, BaseAgent)
46
+ and attr not in [BaseAgent, JSONAgent]
47
+ ):
48
+ # Create an instance to get the name
49
+ agent_instance = attr()
50
+ _AGENT_REGISTRY[agent_instance.name] = attr
51
+
52
+ except Exception as e:
53
+ # Skip problematic modules
54
+ emit_warning(
55
+ f"Warning: Could not load agent module {modname}: {e}",
56
+ message_group=message_group_id,
57
+ )
58
+ continue
59
+
60
+ # 2. Discover JSON agents in user directory
61
+ try:
62
+ json_agents = discover_json_agents()
63
+
64
+ # Add JSON agents to registry (store file path instead of class)
65
+ for agent_name, json_path in json_agents.items():
66
+ _AGENT_REGISTRY[agent_name] = json_path
67
+
68
+ except Exception as e:
69
+ emit_warning(
70
+ f"Warning: Could not discover JSON agents: {e}",
71
+ message_group=message_group_id,
72
+ )
73
+
74
+
75
+ def get_available_agents() -> Dict[str, str]:
76
+ """Get a dictionary of available agents with their display names.
77
+
78
+ Returns:
79
+ Dict mapping agent names to display names.
80
+ """
81
+ # Generate a message group ID for this operation
82
+ message_group_id = str(uuid.uuid4())
83
+ _discover_agents(message_group_id=message_group_id)
84
+
85
+ agents = {}
86
+ for name, agent_ref in _AGENT_REGISTRY.items():
87
+ try:
88
+ if isinstance(agent_ref, str): # JSON agent (file path)
89
+ agent_instance = JSONAgent(agent_ref)
90
+ else: # Python agent (class)
91
+ agent_instance = agent_ref()
92
+ agents[name] = agent_instance.display_name
93
+ except Exception:
94
+ agents[name] = name.title() # Fallback
95
+
96
+ return agents
97
+
98
+
99
+ def get_current_agent_name() -> str:
100
+ """Get the name of the currently active agent.
101
+
102
+ Returns:
103
+ The name of the current agent, defaults to 'code-puppy'.
104
+ """
105
+ return get_value("current_agent") or "code-puppy"
106
+
107
+
108
+ def set_current_agent(agent_name: str) -> bool:
109
+ """Set the current agent by name.
110
+
111
+ Args:
112
+ agent_name: The name of the agent to set as current.
113
+
114
+ Returns:
115
+ True if the agent was set successfully, False if agent not found.
116
+ """
117
+ # Generate a message group ID for agent switching
118
+ message_group_id = str(uuid.uuid4())
119
+ _discover_agents(message_group_id=message_group_id)
120
+ # Clear the cached config when switching agents
121
+ global _CURRENT_AGENT_CONFIG
122
+ _CURRENT_AGENT_CONFIG = None
123
+ agent_obj = load_agent_config(agent_name)
124
+ on_agent_reload(agent_obj.id, agent_name)
125
+ set_config_value("current_agent", agent_name)
126
+ return True
127
+
128
+
129
+ def get_current_agent_config() -> BaseAgent:
130
+ """Get the current agent configuration.
131
+
132
+ Returns:
133
+ The current agent configuration instance.
134
+ """
135
+ global _CURRENT_AGENT_CONFIG
136
+
137
+ _CURRENT_AGENT_CONFIG = load_agent_config(get_current_agent_name())
138
+
139
+ return _CURRENT_AGENT_CONFIG
140
+
141
+
142
+ def load_agent_config(agent_name: str) -> BaseAgent:
143
+ """Load an agent configuration by name.
144
+
145
+ Args:
146
+ agent_name: The name of the agent to load.
147
+
148
+ Returns:
149
+ The agent configuration instance.
150
+
151
+ Raises:
152
+ ValueError: If the agent is not found.
153
+ """
154
+ # Generate a message group ID for agent loading
155
+ message_group_id = str(uuid.uuid4())
156
+ _discover_agents(message_group_id=message_group_id)
157
+
158
+ if agent_name not in _AGENT_REGISTRY:
159
+ # Fallback to code-puppy if agent not found
160
+ if "code-puppy" in _AGENT_REGISTRY:
161
+ agent_name = "code-puppy"
162
+ else:
163
+ raise ValueError(
164
+ f"Agent '{agent_name}' not found and no fallback available"
165
+ )
166
+
167
+ agent_ref = _AGENT_REGISTRY[agent_name]
168
+ if isinstance(agent_ref, str): # JSON agent (file path)
169
+ return JSONAgent(agent_ref)
170
+ else: # Python agent (class)
171
+ return agent_ref()
172
+
173
+
174
+ def get_agent_descriptions() -> Dict[str, str]:
175
+ """Get descriptions for all available agents.
176
+
177
+ Returns:
178
+ Dict mapping agent names to their descriptions.
179
+ """
180
+ # Generate a message group ID for this operation
181
+ message_group_id = str(uuid.uuid4())
182
+ _discover_agents(message_group_id=message_group_id)
183
+
184
+ descriptions = {}
185
+ for name, agent_ref in _AGENT_REGISTRY.items():
186
+ try:
187
+ if isinstance(agent_ref, str): # JSON agent (file path)
188
+ agent_instance = JSONAgent(agent_ref)
189
+ else: # Python agent (class)
190
+ agent_instance = agent_ref()
191
+ descriptions[name] = agent_instance.description
192
+ except Exception:
193
+ descriptions[name] = "No description available"
194
+
195
+ return descriptions
196
+
197
+
198
+ def clear_agent_cache():
199
+ """Clear the cached agent configuration to force reload."""
200
+ global _CURRENT_AGENT_CONFIG
201
+ _CURRENT_AGENT_CONFIG = None
202
+
203
+
204
+ def refresh_agents():
205
+ """Refresh the agent discovery to pick up newly created agents.
206
+
207
+ This clears the agent registry cache and forces a rediscovery of all agents.
208
+ """
209
+ # Generate a message group ID for agent refreshing
210
+ message_group_id = str(uuid.uuid4())
211
+ _discover_agents(message_group_id=message_group_id)
@@ -0,0 +1,60 @@
1
+ """Base agent configuration class for defining agent properties."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any, Dict, List, Optional
5
+ import uuid
6
+
7
+
8
+ class BaseAgent(ABC):
9
+ """Base class for all agent configurations."""
10
+
11
+ def __init__(self):
12
+ self.id = str(uuid.uuid4())
13
+
14
+ @property
15
+ @abstractmethod
16
+ def name(self) -> str:
17
+ """Unique identifier for the agent."""
18
+ pass
19
+
20
+ @property
21
+ @abstractmethod
22
+ def display_name(self) -> str:
23
+ """Human-readable name for the agent."""
24
+ pass
25
+
26
+ @property
27
+ @abstractmethod
28
+ def description(self) -> str:
29
+ """Brief description of what this agent does."""
30
+ pass
31
+
32
+ @abstractmethod
33
+ def get_system_prompt(self) -> str:
34
+ """Get the system prompt for this agent."""
35
+ pass
36
+
37
+ @abstractmethod
38
+ def get_available_tools(self) -> List[str]:
39
+ """Get list of tool names that this agent should have access to.
40
+
41
+ Returns:
42
+ List of tool names to register for this agent.
43
+ """
44
+ pass
45
+
46
+ def get_tools_config(self) -> Optional[Dict[str, Any]]:
47
+ """Get tool configuration for this agent.
48
+
49
+ Returns:
50
+ Dict with tool configuration, or None to use default tools.
51
+ """
52
+ return None
53
+
54
+ def get_user_prompt(self) -> Optional[str]:
55
+ """Get custom user prompt for this agent.
56
+
57
+ Returns:
58
+ Custom prompt string, or None to use default.
59
+ """
60
+ return None
@@ -0,0 +1,129 @@
1
+ """JSON-based agent configuration system."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Dict, List, Optional
6
+
7
+ from .base_agent import BaseAgent
8
+
9
+
10
+ class JSONAgent(BaseAgent):
11
+ """Agent configured from a JSON file."""
12
+
13
+ def __init__(self, json_path: str):
14
+ """Initialize agent from JSON file.
15
+
16
+ Args:
17
+ json_path: Path to the JSON configuration file.
18
+ """
19
+ super().__init__()
20
+ self.json_path = json_path
21
+ self._config = self._load_config()
22
+ self._validate_config()
23
+
24
+ def _load_config(self) -> Dict:
25
+ """Load configuration from JSON file."""
26
+ try:
27
+ with open(self.json_path, "r", encoding="utf-8") as f:
28
+ return json.load(f)
29
+ except (json.JSONDecodeError, FileNotFoundError) as e:
30
+ raise ValueError(
31
+ f"Failed to load JSON agent config from {self.json_path}: {e}"
32
+ )
33
+
34
+ def _validate_config(self) -> None:
35
+ """Validate required fields in configuration."""
36
+ required_fields = ["name", "description", "system_prompt", "tools"]
37
+ for field in required_fields:
38
+ if field not in self._config:
39
+ raise ValueError(
40
+ f"Missing required field '{field}' in JSON agent config: {self.json_path}"
41
+ )
42
+
43
+ # Validate tools is a list
44
+ if not isinstance(self._config["tools"], list):
45
+ raise ValueError(
46
+ f"'tools' must be a list in JSON agent config: {self.json_path}"
47
+ )
48
+
49
+ # Validate system_prompt is string or list
50
+ system_prompt = self._config["system_prompt"]
51
+ if not isinstance(system_prompt, (str, list)):
52
+ raise ValueError(
53
+ f"'system_prompt' must be a string or list in JSON agent config: {self.json_path}"
54
+ )
55
+
56
+ @property
57
+ def name(self) -> str:
58
+ """Get agent name from JSON config."""
59
+ return self._config["name"]
60
+
61
+ @property
62
+ def display_name(self) -> str:
63
+ """Get display name from JSON config, fallback to name with emoji."""
64
+ return self._config.get("display_name", f"{self.name.title()} 🤖")
65
+
66
+ @property
67
+ def description(self) -> str:
68
+ """Get description from JSON config."""
69
+ return self._config["description"]
70
+
71
+ def get_system_prompt(self) -> str:
72
+ """Get system prompt from JSON config."""
73
+ system_prompt = self._config["system_prompt"]
74
+
75
+ # If it's a list, join with newlines
76
+ if isinstance(system_prompt, list):
77
+ return "\n".join(system_prompt)
78
+
79
+ return system_prompt
80
+
81
+ def get_available_tools(self) -> List[str]:
82
+ """Get available tools from JSON config."""
83
+ # Filter out any tools that don't exist in our registry
84
+ from code_puppy.tools import get_available_tool_names
85
+
86
+ available_tools = get_available_tool_names()
87
+
88
+ # Only return tools that are both requested and available
89
+ # Also filter out 'final_result' which is not in our registry
90
+ requested_tools = [
91
+ tool for tool in self._config["tools"] if tool in available_tools
92
+ ]
93
+
94
+ return requested_tools
95
+
96
+ def get_user_prompt(self) -> Optional[str]:
97
+ """Get custom user prompt from JSON config."""
98
+ return self._config.get("user_prompt")
99
+
100
+ def get_tools_config(self) -> Optional[Dict]:
101
+ """Get tool configuration from JSON config."""
102
+ return self._config.get("tools_config")
103
+
104
+
105
+ def discover_json_agents() -> Dict[str, str]:
106
+ """Discover JSON agent files in the user's agents directory.
107
+
108
+ Returns:
109
+ Dict mapping agent names to their JSON file paths.
110
+ """
111
+ from code_puppy.config import get_user_agents_directory
112
+
113
+ agents = {}
114
+ agents_dir = Path(get_user_agents_directory())
115
+
116
+ if not agents_dir.exists() or not agents_dir.is_dir():
117
+ return agents
118
+
119
+ # Find all .json files in the agents directory
120
+ for json_file in agents_dir.glob("*.json"):
121
+ try:
122
+ # Try to load and validate the agent
123
+ agent = JSONAgent(str(json_file))
124
+ agents[agent.name] = str(json_file)
125
+ except Exception:
126
+ # Skip invalid JSON agent files
127
+ continue
128
+
129
+ return agents
code_puppy/callbacks.py CHANGED
@@ -9,8 +9,12 @@ PhaseType = Literal[
9
9
  "invoke_agent",
10
10
  "agent_exception",
11
11
  "version_check",
12
+ "edit_file",
13
+ "delete_file",
14
+ "run_shell_command",
12
15
  "load_model_config",
13
16
  "load_prompt",
17
+ "agent_reload",
14
18
  ]
15
19
  CallbackFunc = Callable[..., Any]
16
20
 
@@ -20,8 +24,12 @@ _callbacks: Dict[PhaseType, List[CallbackFunc]] = {
20
24
  "invoke_agent": [],
21
25
  "agent_exception": [],
22
26
  "version_check": [],
27
+ "edit_file": [],
28
+ "delete_file": [],
29
+ "run_shell_command": [],
23
30
  "load_model_config": [],
24
31
  "load_prompt": [],
32
+ "agent_reload": [],
25
33
  }
26
34
 
27
35
  logger = logging.getLogger(__name__)
@@ -148,5 +156,21 @@ def on_load_model_config(*args, **kwargs) -> List[Any]:
148
156
  return _trigger_callbacks_sync("load_model_config", *args, **kwargs)
149
157
 
150
158
 
159
+ def on_edit_file(*args, **kwargs) -> Any:
160
+ return _trigger_callbacks_sync("edit_file", *args, **kwargs)
161
+
162
+
163
+ def on_delete_file(*args, **kwargs) -> Any:
164
+ return _trigger_callbacks_sync("delete_file", *args, **kwargs)
165
+
166
+
167
+ def on_run_shell_command(*args, **kwargs) -> Any:
168
+ return _trigger_callbacks_sync("run_shell_command", *args, **kwargs)
169
+
170
+
171
+ def on_agent_reload(*args, **kwargs) -> Any:
172
+ return _trigger_callbacks_sync("agent_reload", *args, **kwargs)
173
+
174
+
151
175
  def on_load_prompt():
152
176
  return _trigger_callbacks_sync("load_prompt")
@@ -13,7 +13,7 @@ COMMANDS_HELP = """
13
13
  [bold magenta]Commands Help[/bold magenta]
14
14
  /help, /h Show this help message
15
15
  /cd <dir> Change directory or show directories
16
-
16
+ /agent <name> Switch to a different agent or show available agents
17
17
  /exit, /quit Exit interactive mode
18
18
  /generate-pr-description [@dir] Generate comprehensive PR description
19
19
  /m <model> Set active model
@@ -22,7 +22,7 @@ COMMANDS_HELP = """
22
22
  /compact Summarize and compact current chat history
23
23
  /dump_context <name> Save current message history to file
24
24
  /load_context <name> Load message history from file
25
- /set Set puppy config key-values (e.g., /set yolo_mode true)
25
+ /set Set puppy config key-values (e.g., /set yolo_mode true, /set compaction_strategy truncation)
26
26
  /tools Show available tools and capabilities
27
27
  /<unknown> Show unknown command warning
28
28
  """
@@ -47,9 +47,12 @@ def handle_command(command: str):
47
47
  return True
48
48
 
49
49
  if command.strip().startswith("/compact"):
50
+ from code_puppy.config import get_compaction_strategy
50
51
  from code_puppy.message_history_processor import (
51
52
  estimate_tokens_for_message,
52
53
  summarize_messages,
54
+ truncation,
55
+ get_protected_token_count,
53
56
  )
54
57
  from code_puppy.messaging import (
55
58
  emit_error,
@@ -66,13 +69,23 @@ def handle_command(command: str):
66
69
  return True
67
70
 
68
71
  before_tokens = sum(estimate_tokens_for_message(m) for m in history)
72
+ compaction_strategy = get_compaction_strategy()
69
73
  emit_info(
70
- f"🤔 Compacting {len(history)} messages... (~{before_tokens} tokens)"
74
+ f"🤔 Compacting {len(history)} messages using {compaction_strategy} strategy... (~{before_tokens} tokens)"
71
75
  )
72
76
 
73
- compacted, _ = summarize_messages(history, with_protection=False)
77
+ if compaction_strategy == "truncation":
78
+ protected_tokens = get_protected_token_count()
79
+ compacted = truncation(history, protected_tokens)
80
+ summarized_messages = [] # No summarization in truncation mode
81
+ else:
82
+ # Default to summarization
83
+ compacted, summarized_messages = summarize_messages(
84
+ history, with_protection=False
85
+ )
86
+
74
87
  if not compacted:
75
- emit_error("Summarization failed. History unchanged.")
88
+ emit_error("Compaction failed. History unchanged.")
76
89
  return True
77
90
 
78
91
  set_message_history(compacted)
@@ -83,8 +96,14 @@ def handle_command(command: str):
83
96
  if before_tokens > 0
84
97
  else 0
85
98
  )
99
+
100
+ strategy_info = (
101
+ f"using {compaction_strategy} strategy"
102
+ if compaction_strategy == "truncation"
103
+ else "via summarization"
104
+ )
86
105
  emit_success(
87
- f"✨ Done! History: {len(history)} → {len(compacted)} messages\n"
106
+ f"✨ Done! History: {len(history)} → {len(compacted)} messages {strategy_info}\n"
88
107
  f"🏦 Tokens: {before_tokens:,} → {after_tokens:,} ({reduction_pct:.1f}% reduction)"
89
108
  )
90
109
  return True
@@ -119,25 +138,34 @@ def handle_command(command: str):
119
138
  get_owner_name,
120
139
  get_protected_token_count,
121
140
  get_puppy_name,
122
- get_summarization_threshold,
141
+ get_compaction_threshold,
123
142
  get_yolo_mode,
124
143
  )
144
+ from code_puppy.agents import get_current_agent_config
145
+
146
+ from code_puppy.config import get_compaction_strategy
125
147
 
126
148
  puppy_name = get_puppy_name()
127
149
  owner_name = get_owner_name()
128
150
  model = get_active_model()
129
151
  yolo_mode = get_yolo_mode()
130
152
  protected_tokens = get_protected_token_count()
131
- summary_threshold = get_summarization_threshold()
153
+ compaction_threshold = get_compaction_threshold()
154
+ compaction_strategy = get_compaction_strategy()
155
+
156
+ # Get current agent info
157
+ current_agent = get_current_agent_config()
132
158
 
133
159
  status_msg = f"""[bold magenta]🐶 Puppy Status[/bold magenta]
134
160
 
135
161
  [bold]puppy_name:[/bold] [cyan]{puppy_name}[/cyan]
136
162
  [bold]owner_name:[/bold] [cyan]{owner_name}[/cyan]
163
+ [bold]current_agent:[/bold] [magenta]{current_agent.display_name}[/magenta]
137
164
  [bold]model:[/bold] [green]{model}[/green]
138
165
  [bold]YOLO_MODE:[/bold] {"[red]ON[/red]" if yolo_mode else "[yellow]off[/yellow]"}
139
166
  [bold]protected_tokens:[/bold] [cyan]{protected_tokens:,}[/cyan] recent tokens preserved
140
- [bold]summary_threshold:[/bold] [cyan]{summary_threshold:.1%}[/cyan] context usage triggers summarization
167
+ [bold]compaction_threshold:[/bold] [cyan]{compaction_threshold:.1%}[/cyan] context usage triggers compaction
168
+ [bold]compaction_strategy:[/bold] [cyan]{compaction_strategy}[/cyan] (summarization or truncation)
141
169
 
142
170
  """
143
171
  emit_info(status_msg)
@@ -162,8 +190,11 @@ def handle_command(command: str):
162
190
  key = tokens[1]
163
191
  value = ""
164
192
  else:
193
+ config_keys = get_config_keys()
194
+ if "compaction_strategy" not in config_keys:
195
+ config_keys.append("compaction_strategy")
165
196
  emit_warning(
166
- f"Usage: /set KEY=VALUE or /set KEY VALUE\nConfig keys: {', '.join(get_config_keys())}"
197
+ f"Usage: /set KEY=VALUE or /set KEY VALUE\nConfig keys: {', '.join(config_keys)}\n[dim]Note: compaction_strategy can be 'summarization' or 'truncation'[/dim]"
167
198
  )
168
199
  return True
169
200
  if key:
@@ -181,6 +212,91 @@ def handle_command(command: str):
181
212
  emit_info(markdown_content)
182
213
  return True
183
214
 
215
+ if command.startswith("/agent"):
216
+ # Handle agent switching
217
+ from code_puppy.agents import (
218
+ get_available_agents,
219
+ get_current_agent_config,
220
+ set_current_agent,
221
+ get_agent_descriptions,
222
+ )
223
+ from code_puppy.agent import get_code_generation_agent
224
+
225
+ tokens = command.split()
226
+
227
+ if len(tokens) == 1:
228
+ # Show current agent and available agents
229
+ current_agent = get_current_agent_config()
230
+ available_agents = get_available_agents()
231
+ descriptions = get_agent_descriptions()
232
+
233
+ # Generate a group ID for all messages in this command
234
+ import uuid
235
+
236
+ group_id = str(uuid.uuid4())
237
+
238
+ emit_info(
239
+ f"[bold green]Current Agent:[/bold green] {current_agent.display_name}",
240
+ message_group=group_id,
241
+ )
242
+ emit_info(
243
+ f"[dim]{current_agent.description}[/dim]\n", message_group=group_id
244
+ )
245
+
246
+ emit_info(
247
+ "[bold magenta]Available Agents:[/bold magenta]", message_group=group_id
248
+ )
249
+ for name, display_name in available_agents.items():
250
+ description = descriptions.get(name, "No description")
251
+ current_marker = (
252
+ " [green]← current[/green]" if name == current_agent.name else ""
253
+ )
254
+ emit_info(
255
+ f" [cyan]{name:<12}[/cyan] {display_name}{current_marker}",
256
+ message_group=group_id,
257
+ )
258
+ emit_info(f" [dim]{description}[/dim]", message_group=group_id)
259
+
260
+ emit_info(
261
+ "\n[yellow]Usage:[/yellow] /agent <agent-name>", message_group=group_id
262
+ )
263
+ return True
264
+
265
+ elif len(tokens) == 2:
266
+ agent_name = tokens[1].lower()
267
+
268
+ # Generate a group ID for all messages in this command
269
+ import uuid
270
+
271
+ group_id = str(uuid.uuid4())
272
+
273
+ if set_current_agent(agent_name):
274
+ # Reload the agent with new configuration
275
+ get_code_generation_agent(force_reload=True)
276
+ new_agent = get_current_agent_config()
277
+ emit_success(
278
+ f"Switched to agent: {new_agent.display_name}",
279
+ message_group=group_id,
280
+ )
281
+ emit_info(f"[dim]{new_agent.description}[/dim]", message_group=group_id)
282
+ return True
283
+ else:
284
+ # Generate a group ID for all messages in this command
285
+ import uuid
286
+
287
+ group_id = str(uuid.uuid4())
288
+
289
+ available_agents = get_available_agents()
290
+ emit_error(f"Agent '{agent_name}' not found", message_group=group_id)
291
+ emit_warning(
292
+ f"Available agents: {', '.join(available_agents.keys())}",
293
+ message_group=group_id,
294
+ )
295
+ return True
296
+ else:
297
+ emit_warning("Usage: /agent [agent-name]")
298
+ return True
299
+
184
300
  if command.startswith("/m"):
185
301
  # Try setting model and show confirmation
186
302
  new_input = update_model_in_input(command)