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.
- 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 +24 -0
- code_puppy/command_line/command_handler.py +126 -10
- code_puppy/config.py +68 -10
- code_puppy/main.py +13 -4
- code_puppy/message_history_processor.py +54 -7
- code_puppy/tools/__init__.py +60 -7
- code_puppy/tools/command_runner.py +100 -1
- code_puppy/tools/file_modifications.py +179 -11
- code_puppy/tools/file_operations.py +171 -1
- code_puppy/tui/app.py +22 -160
- code_puppy/tui/components/status_bar.py +4 -4
- code_puppy/tui/screens/settings.py +53 -18
- code_puppy/tui/tests/test_agent_command.py +72 -0
- code_puppy-0.0.125.dist-info/METADATA +634 -0
- {code_puppy-0.0.123.dist-info → code_puppy-0.0.125.dist-info}/RECORD +26 -20
- code_puppy-0.0.123.dist-info/METADATA +0 -192
- {code_puppy-0.0.123.data → code_puppy-0.0.125.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.123.dist-info → code_puppy-0.0.125.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.123.dist-info → code_puppy-0.0.125.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.123.dist-info → code_puppy-0.0.125.dist-info}/licenses/LICENSE +0 -0
|
@@ -101,15 +101,15 @@ class StatusBar(Static):
|
|
|
101
101
|
token_color = "green"
|
|
102
102
|
if self.token_count > 0 and self.token_capacity > 0:
|
|
103
103
|
# Import here to avoid circular import
|
|
104
|
-
from code_puppy.config import
|
|
104
|
+
from code_puppy.config import get_compaction_threshold
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
get_compaction_threshold = get_compaction_threshold()
|
|
107
107
|
|
|
108
|
-
if self.token_proportion >
|
|
108
|
+
if self.token_proportion > get_compaction_threshold:
|
|
109
109
|
token_color = "red"
|
|
110
110
|
token_status = f"🔴 {self.token_count}/{self.token_capacity} ({self.token_proportion:.1%})"
|
|
111
111
|
elif self.token_proportion > (
|
|
112
|
-
|
|
112
|
+
get_compaction_threshold - 0.15
|
|
113
113
|
): # 15% before summarization threshold
|
|
114
114
|
token_color = "yellow"
|
|
115
115
|
token_status = f"🟡 {self.token_count}/{self.token_capacity} ({self.token_proportion:.1%})"
|
|
@@ -100,9 +100,20 @@ class SettingsScreen(ModalScreen):
|
|
|
100
100
|
)
|
|
101
101
|
|
|
102
102
|
with Container(classes="setting-row"):
|
|
103
|
-
yield Static("
|
|
103
|
+
yield Static("Compaction Strategy:", classes="setting-label")
|
|
104
|
+
yield Select(
|
|
105
|
+
[
|
|
106
|
+
("Summarization", "summarization"),
|
|
107
|
+
("Truncation", "truncation"),
|
|
108
|
+
],
|
|
109
|
+
id="compaction-strategy-select",
|
|
110
|
+
classes="setting-input",
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
with Container(classes="setting-row"):
|
|
114
|
+
yield Static("Compaction Threshold:", classes="setting-label")
|
|
104
115
|
yield Input(
|
|
105
|
-
id="
|
|
116
|
+
id="compaction-threshold-input",
|
|
106
117
|
classes="setting-input",
|
|
107
118
|
placeholder="e.g., 0.85",
|
|
108
119
|
)
|
|
@@ -118,7 +129,8 @@ class SettingsScreen(ModalScreen):
|
|
|
118
129
|
get_owner_name,
|
|
119
130
|
get_protected_token_count,
|
|
120
131
|
get_puppy_name,
|
|
121
|
-
|
|
132
|
+
get_compaction_strategy,
|
|
133
|
+
get_compaction_threshold,
|
|
122
134
|
)
|
|
123
135
|
|
|
124
136
|
# Load current values
|
|
@@ -126,12 +138,18 @@ class SettingsScreen(ModalScreen):
|
|
|
126
138
|
owner_name_input = self.query_one("#owner-name-input", Input)
|
|
127
139
|
model_select = self.query_one("#model-select", Select)
|
|
128
140
|
protected_tokens_input = self.query_one("#protected-tokens-input", Input)
|
|
129
|
-
|
|
141
|
+
compaction_threshold_input = self.query_one(
|
|
142
|
+
"#compaction-threshold-input", Input
|
|
143
|
+
)
|
|
144
|
+
compaction_strategy_select = self.query_one(
|
|
145
|
+
"#compaction-strategy-select", Select
|
|
146
|
+
)
|
|
130
147
|
|
|
131
148
|
puppy_name_input.value = get_puppy_name() or ""
|
|
132
149
|
owner_name_input.value = get_owner_name() or ""
|
|
133
150
|
protected_tokens_input.value = str(get_protected_token_count())
|
|
134
|
-
|
|
151
|
+
compaction_threshold_input.value = str(get_compaction_threshold())
|
|
152
|
+
compaction_strategy_select.value = get_compaction_strategy()
|
|
135
153
|
|
|
136
154
|
# Load available models
|
|
137
155
|
self.load_model_options(model_select)
|
|
@@ -146,9 +164,7 @@ class SettingsScreen(ModalScreen):
|
|
|
146
164
|
"""Load available models into the model select widget."""
|
|
147
165
|
try:
|
|
148
166
|
# Use the same method that interactive mode uses to load models
|
|
149
|
-
import os
|
|
150
167
|
|
|
151
|
-
from code_puppy.config import CONFIG_DIR
|
|
152
168
|
from code_puppy.model_factory import ModelFactory
|
|
153
169
|
|
|
154
170
|
# Load models using the same path and method as interactive mode
|
|
@@ -171,7 +187,11 @@ class SettingsScreen(ModalScreen):
|
|
|
171
187
|
@on(Button.Pressed, "#save-button")
|
|
172
188
|
def save_settings(self) -> None:
|
|
173
189
|
"""Save the modified settings."""
|
|
174
|
-
from code_puppy.config import
|
|
190
|
+
from code_puppy.config import (
|
|
191
|
+
set_config_value,
|
|
192
|
+
set_model_name,
|
|
193
|
+
get_model_context_length,
|
|
194
|
+
)
|
|
175
195
|
|
|
176
196
|
try:
|
|
177
197
|
# Get values from inputs
|
|
@@ -182,8 +202,8 @@ class SettingsScreen(ModalScreen):
|
|
|
182
202
|
protected_tokens = self.query_one(
|
|
183
203
|
"#protected-tokens-input", Input
|
|
184
204
|
).value.strip()
|
|
185
|
-
|
|
186
|
-
"#
|
|
205
|
+
compaction_threshold = self.query_one(
|
|
206
|
+
"#compaction-threshold-input", Input
|
|
187
207
|
).value.strip()
|
|
188
208
|
|
|
189
209
|
# Validate and save
|
|
@@ -201,31 +221,46 @@ class SettingsScreen(ModalScreen):
|
|
|
201
221
|
# Validate and save protected tokens
|
|
202
222
|
if protected_tokens.isdigit():
|
|
203
223
|
tokens_value = int(protected_tokens)
|
|
224
|
+
model_context_length = get_model_context_length()
|
|
225
|
+
max_protected_tokens = int(model_context_length * 0.75)
|
|
226
|
+
|
|
204
227
|
if tokens_value >= 1000: # Minimum validation
|
|
205
|
-
|
|
228
|
+
if tokens_value <= max_protected_tokens: # Maximum validation
|
|
229
|
+
set_config_value("protected_token_count", protected_tokens)
|
|
230
|
+
else:
|
|
231
|
+
raise ValueError(
|
|
232
|
+
f"Protected tokens must not exceed 75% of model context length ({max_protected_tokens} tokens for current model)"
|
|
233
|
+
)
|
|
206
234
|
else:
|
|
207
235
|
raise ValueError("Protected tokens must be at least 1000")
|
|
208
236
|
elif protected_tokens: # If not empty but not digit
|
|
209
237
|
raise ValueError("Protected tokens must be a valid number")
|
|
210
238
|
|
|
211
|
-
# Validate and save
|
|
212
|
-
if
|
|
239
|
+
# Validate and save compaction threshold
|
|
240
|
+
if compaction_threshold:
|
|
213
241
|
try:
|
|
214
|
-
threshold_value = float(
|
|
215
|
-
if 0.
|
|
216
|
-
set_config_value("
|
|
242
|
+
threshold_value = float(compaction_threshold)
|
|
243
|
+
if 0.8 <= threshold_value <= 0.95: # Same bounds as config function
|
|
244
|
+
set_config_value("compaction_threshold", compaction_threshold)
|
|
217
245
|
else:
|
|
218
246
|
raise ValueError(
|
|
219
|
-
"
|
|
247
|
+
"Compaction threshold must be between 0.8 and 0.95"
|
|
220
248
|
)
|
|
221
249
|
except ValueError as ve:
|
|
222
250
|
if "must be between" in str(ve):
|
|
223
251
|
raise ve
|
|
224
252
|
else:
|
|
225
253
|
raise ValueError(
|
|
226
|
-
"
|
|
254
|
+
"Compaction threshold must be a valid decimal number"
|
|
227
255
|
)
|
|
228
256
|
|
|
257
|
+
# Save compaction strategy
|
|
258
|
+
compaction_strategy = self.query_one(
|
|
259
|
+
"#compaction-strategy-select", Select
|
|
260
|
+
).value
|
|
261
|
+
if compaction_strategy in ["summarization", "truncation"]:
|
|
262
|
+
set_config_value("compaction_strategy", compaction_strategy)
|
|
263
|
+
|
|
229
264
|
# Return success message with model change info
|
|
230
265
|
message = "Settings saved successfully!"
|
|
231
266
|
if selected_model:
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Tests for the /agent command handling in TUI mode."""
|
|
2
|
+
|
|
3
|
+
from unittest.mock import patch, MagicMock
|
|
4
|
+
|
|
5
|
+
from code_puppy.tui.app import CodePuppyTUI
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestTUIAgentCommand:
|
|
9
|
+
"""Test the TUI's handling of /agent commands."""
|
|
10
|
+
|
|
11
|
+
@patch("code_puppy.tui.app.get_code_generation_agent")
|
|
12
|
+
@patch("code_puppy.tui.app.handle_command")
|
|
13
|
+
def test_tui_handles_agent_command(self, mock_handle_command, mock_get_agent):
|
|
14
|
+
"""Test that TUI properly delegates /agent commands to command handler."""
|
|
15
|
+
# Create a TUI app instance
|
|
16
|
+
app = CodePuppyTUI()
|
|
17
|
+
|
|
18
|
+
# Mock the agent
|
|
19
|
+
mock_agent_instance = MagicMock()
|
|
20
|
+
mock_get_agent.return_value = mock_agent_instance
|
|
21
|
+
|
|
22
|
+
# Mock handle_command to simulate successful processing
|
|
23
|
+
mock_handle_command.return_value = True
|
|
24
|
+
|
|
25
|
+
# Simulate processing an /agent command
|
|
26
|
+
message = "/agent code-puppy"
|
|
27
|
+
app.agent = mock_agent_instance
|
|
28
|
+
|
|
29
|
+
# Call the method that processes messages
|
|
30
|
+
# We'll need to mock some UI elements to avoid complex setup
|
|
31
|
+
with (
|
|
32
|
+
patch.object(app, "add_user_message"),
|
|
33
|
+
patch.object(app, "_update_submit_cancel_button"),
|
|
34
|
+
patch.object(app, "start_agent_progress"),
|
|
35
|
+
patch.object(app, "stop_agent_progress"),
|
|
36
|
+
patch.object(app, "refresh_history_display"),
|
|
37
|
+
):
|
|
38
|
+
import asyncio
|
|
39
|
+
|
|
40
|
+
# Create an event loop for the async test
|
|
41
|
+
loop = asyncio.get_event_loop()
|
|
42
|
+
loop.run_until_complete(app.process_message(message))
|
|
43
|
+
|
|
44
|
+
# Verify that handle_command was called with the correct argument
|
|
45
|
+
mock_handle_command.assert_called_once_with(message)
|
|
46
|
+
|
|
47
|
+
# Verify that get_code_generation_agent was called to refresh the agent instance
|
|
48
|
+
mock_get_agent.assert_called()
|
|
49
|
+
|
|
50
|
+
@patch("code_puppy.tui.app.get_code_generation_agent")
|
|
51
|
+
def test_tui_refreshes_agent_after_command(self, mock_get_agent):
|
|
52
|
+
"""Test that TUI refreshes its agent instance after processing /agent command."""
|
|
53
|
+
# Create a TUI app instance
|
|
54
|
+
app = CodePuppyTUI()
|
|
55
|
+
|
|
56
|
+
# Set initial agent
|
|
57
|
+
initial_agent = MagicMock()
|
|
58
|
+
app.agent = initial_agent
|
|
59
|
+
|
|
60
|
+
# Mock get_code_generation_agent to return a new agent instance
|
|
61
|
+
new_agent = MagicMock()
|
|
62
|
+
mock_get_agent.return_value = new_agent
|
|
63
|
+
|
|
64
|
+
# Simulate that an /agent command was processed
|
|
65
|
+
with patch("code_puppy.tui.app.handle_command"):
|
|
66
|
+
import asyncio
|
|
67
|
+
|
|
68
|
+
loop = asyncio.get_event_loop()
|
|
69
|
+
loop.run_until_complete(app.process_message("/agent code-puppy"))
|
|
70
|
+
|
|
71
|
+
# Verify that the agent was refreshed
|
|
72
|
+
mock_get_agent.assert_called()
|