code-puppy 0.0.124__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.
- code_puppy/agent.py +20 -4
- code_puppy/agents/__init__.py +25 -0
- code_puppy/{agent_prompts.py → agents/agent_code_puppy.py} +46 -13
- code_puppy/agents/agent_creator_agent.py +446 -0
- code_puppy/agents/agent_manager.py +211 -0
- code_puppy/agents/base_agent.py +60 -0
- code_puppy/agents/json_agent.py +129 -0
- code_puppy/callbacks.py +6 -0
- code_puppy/command_line/command_handler.py +91 -1
- code_puppy/config.py +12 -0
- code_puppy/main.py +6 -1
- code_puppy/tools/__init__.py +60 -7
- code_puppy/tools/command_runner.py +97 -0
- code_puppy/tools/file_modifications.py +176 -11
- code_puppy/tools/file_operations.py +171 -1
- code_puppy/tui/app.py +14 -158
- code_puppy/tui/tests/test_agent_command.py +72 -0
- code_puppy-0.0.125.dist-info/METADATA +634 -0
- {code_puppy-0.0.124.dist-info → code_puppy-0.0.125.dist-info}/RECORD +23 -17
- code_puppy-0.0.124.dist-info/METADATA +0 -192
- {code_puppy-0.0.124.data → code_puppy-0.0.125.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.124.dist-info → code_puppy-0.0.125.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.124.dist-info → code_puppy-0.0.125.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.124.dist-info → code_puppy-0.0.125.dist-info}/licenses/LICENSE +0 -0
|
@@ -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
|
@@ -14,6 +14,7 @@ PhaseType = Literal[
|
|
|
14
14
|
"run_shell_command",
|
|
15
15
|
"load_model_config",
|
|
16
16
|
"load_prompt",
|
|
17
|
+
"agent_reload",
|
|
17
18
|
]
|
|
18
19
|
CallbackFunc = Callable[..., Any]
|
|
19
20
|
|
|
@@ -28,6 +29,7 @@ _callbacks: Dict[PhaseType, List[CallbackFunc]] = {
|
|
|
28
29
|
"run_shell_command": [],
|
|
29
30
|
"load_model_config": [],
|
|
30
31
|
"load_prompt": [],
|
|
32
|
+
"agent_reload": [],
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
logger = logging.getLogger(__name__)
|
|
@@ -166,5 +168,9 @@ def on_run_shell_command(*args, **kwargs) -> Any:
|
|
|
166
168
|
return _trigger_callbacks_sync("run_shell_command", *args, **kwargs)
|
|
167
169
|
|
|
168
170
|
|
|
171
|
+
def on_agent_reload(*args, **kwargs) -> Any:
|
|
172
|
+
return _trigger_callbacks_sync("agent_reload", *args, **kwargs)
|
|
173
|
+
|
|
174
|
+
|
|
169
175
|
def on_load_prompt():
|
|
170
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
|
|
@@ -141,6 +141,7 @@ def handle_command(command: str):
|
|
|
141
141
|
get_compaction_threshold,
|
|
142
142
|
get_yolo_mode,
|
|
143
143
|
)
|
|
144
|
+
from code_puppy.agents import get_current_agent_config
|
|
144
145
|
|
|
145
146
|
from code_puppy.config import get_compaction_strategy
|
|
146
147
|
|
|
@@ -152,10 +153,14 @@ def handle_command(command: str):
|
|
|
152
153
|
compaction_threshold = get_compaction_threshold()
|
|
153
154
|
compaction_strategy = get_compaction_strategy()
|
|
154
155
|
|
|
156
|
+
# Get current agent info
|
|
157
|
+
current_agent = get_current_agent_config()
|
|
158
|
+
|
|
155
159
|
status_msg = f"""[bold magenta]🐶 Puppy Status[/bold magenta]
|
|
156
160
|
|
|
157
161
|
[bold]puppy_name:[/bold] [cyan]{puppy_name}[/cyan]
|
|
158
162
|
[bold]owner_name:[/bold] [cyan]{owner_name}[/cyan]
|
|
163
|
+
[bold]current_agent:[/bold] [magenta]{current_agent.display_name}[/magenta]
|
|
159
164
|
[bold]model:[/bold] [green]{model}[/green]
|
|
160
165
|
[bold]YOLO_MODE:[/bold] {"[red]ON[/red]" if yolo_mode else "[yellow]off[/yellow]"}
|
|
161
166
|
[bold]protected_tokens:[/bold] [cyan]{protected_tokens:,}[/cyan] recent tokens preserved
|
|
@@ -207,6 +212,91 @@ def handle_command(command: str):
|
|
|
207
212
|
emit_info(markdown_content)
|
|
208
213
|
return True
|
|
209
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
|
+
|
|
210
300
|
if command.startswith("/m"):
|
|
211
301
|
# Try setting model and show confirmation
|
|
212
302
|
new_input = update_model_in_input(command)
|
code_puppy/config.py
CHANGED
|
@@ -9,6 +9,7 @@ MCP_SERVERS_FILE = os.path.join(CONFIG_DIR, "mcp_servers.json")
|
|
|
9
9
|
COMMAND_HISTORY_FILE = os.path.join(CONFIG_DIR, "command_history.txt")
|
|
10
10
|
MODELS_FILE = os.path.join(CONFIG_DIR, "models.json")
|
|
11
11
|
EXTRA_MODELS_FILE = os.path.join(CONFIG_DIR, "extra_models.json")
|
|
12
|
+
AGENTS_DIR = os.path.join(CONFIG_DIR, "agents")
|
|
12
13
|
|
|
13
14
|
DEFAULT_SECTION = "puppy"
|
|
14
15
|
REQUIRED_KEYS = ["puppy_name", "owner_name"]
|
|
@@ -303,6 +304,17 @@ def normalize_command_history():
|
|
|
303
304
|
direct_console.print(f"[bold red]{error_msg}[/bold red]")
|
|
304
305
|
|
|
305
306
|
|
|
307
|
+
def get_user_agents_directory() -> str:
|
|
308
|
+
"""Get the user's agents directory path.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
Path to the user's Code Puppy agents directory.
|
|
312
|
+
"""
|
|
313
|
+
# Ensure the agents directory exists
|
|
314
|
+
os.makedirs(AGENTS_DIR, exist_ok=True)
|
|
315
|
+
return AGENTS_DIR
|
|
316
|
+
|
|
317
|
+
|
|
306
318
|
def initialize_command_history_file():
|
|
307
319
|
"""Create the command history file if it doesn't exist.
|
|
308
320
|
Handles migration from the old history file location for backward compatibility.
|
code_puppy/main.py
CHANGED
|
@@ -373,8 +373,13 @@ async def interactive_mode(message_renderer, initial_command: str = None) -> Non
|
|
|
373
373
|
|
|
374
374
|
while True:
|
|
375
375
|
from code_puppy.messaging import emit_info
|
|
376
|
+
from code_puppy.agents.agent_manager import get_current_agent_config
|
|
376
377
|
|
|
377
|
-
|
|
378
|
+
# Get the custom prompt from the current agent, or use default
|
|
379
|
+
current_agent = get_current_agent_config()
|
|
380
|
+
user_prompt = current_agent.get_user_prompt() or "Enter your coding task:"
|
|
381
|
+
|
|
382
|
+
emit_info(f"[bold blue]{user_prompt}[/bold blue]")
|
|
378
383
|
|
|
379
384
|
try:
|
|
380
385
|
# Use prompt_toolkit for enhanced input with path completion
|
code_puppy/tools/__init__.py
CHANGED
|
@@ -1,10 +1,63 @@
|
|
|
1
|
-
from code_puppy.
|
|
2
|
-
from code_puppy.tools.
|
|
3
|
-
|
|
1
|
+
from code_puppy.messaging import emit_warning
|
|
2
|
+
from code_puppy.tools.command_runner import (
|
|
3
|
+
register_agent_run_shell_command,
|
|
4
|
+
register_agent_share_your_reasoning,
|
|
5
|
+
)
|
|
6
|
+
from code_puppy.tools.file_modifications import register_edit_file, register_delete_file
|
|
7
|
+
from code_puppy.tools.file_operations import (
|
|
8
|
+
register_list_files,
|
|
9
|
+
register_read_file,
|
|
10
|
+
register_grep,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Map of tool names to their individual registration functions
|
|
15
|
+
TOOL_REGISTRY = {
|
|
16
|
+
# File Operations
|
|
17
|
+
"list_files": register_list_files,
|
|
18
|
+
"read_file": register_read_file,
|
|
19
|
+
"grep": register_grep,
|
|
20
|
+
# File Modifications
|
|
21
|
+
"edit_file": register_edit_file,
|
|
22
|
+
"delete_file": register_delete_file,
|
|
23
|
+
# Command Runner
|
|
24
|
+
"agent_run_shell_command": register_agent_run_shell_command,
|
|
25
|
+
"agent_share_your_reasoning": register_agent_share_your_reasoning,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def register_tools_for_agent(agent, tool_names: list[str]):
|
|
30
|
+
"""Register specific tools for an agent based on tool names.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
agent: The agent to register tools to.
|
|
34
|
+
tool_names: List of tool names to register.
|
|
35
|
+
"""
|
|
36
|
+
for tool_name in tool_names:
|
|
37
|
+
if tool_name not in TOOL_REGISTRY:
|
|
38
|
+
# Skip unknown tools with a warning instead of failing
|
|
39
|
+
emit_warning(f"Warning: Unknown tool '{tool_name}' requested, skipping...")
|
|
40
|
+
continue
|
|
41
|
+
|
|
42
|
+
# Register the individual tool
|
|
43
|
+
register_func = TOOL_REGISTRY[tool_name]
|
|
44
|
+
register_func(agent)
|
|
4
45
|
|
|
5
46
|
|
|
6
47
|
def register_all_tools(agent):
|
|
7
|
-
"""Register all available tools to the provided agent.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
48
|
+
"""Register all available tools to the provided agent.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
agent: The agent to register tools to.
|
|
52
|
+
"""
|
|
53
|
+
all_tools = list(TOOL_REGISTRY.keys())
|
|
54
|
+
register_tools_for_agent(agent, all_tools)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_available_tool_names() -> list[str]:
|
|
58
|
+
"""Get list of all available tool names.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
List of all tool names that can be registered.
|
|
62
|
+
"""
|
|
63
|
+
return list(TOOL_REGISTRY.keys())
|