code-puppy 0.0.135__py3-none-any.whl → 0.0.136__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 +15 -17
- code_puppy/agents/agent_manager.py +320 -9
- code_puppy/agents/base_agent.py +58 -2
- code_puppy/agents/runtime_manager.py +68 -42
- code_puppy/command_line/command_handler.py +82 -33
- code_puppy/command_line/mcp/__init__.py +10 -0
- code_puppy/command_line/mcp/add_command.py +183 -0
- code_puppy/command_line/mcp/base.py +35 -0
- code_puppy/command_line/mcp/handler.py +133 -0
- code_puppy/command_line/mcp/help_command.py +146 -0
- code_puppy/command_line/mcp/install_command.py +176 -0
- code_puppy/command_line/mcp/list_command.py +94 -0
- code_puppy/command_line/mcp/logs_command.py +126 -0
- code_puppy/command_line/mcp/remove_command.py +82 -0
- code_puppy/command_line/mcp/restart_command.py +92 -0
- code_puppy/command_line/mcp/search_command.py +117 -0
- code_puppy/command_line/mcp/start_all_command.py +126 -0
- code_puppy/command_line/mcp/start_command.py +98 -0
- code_puppy/command_line/mcp/status_command.py +185 -0
- code_puppy/command_line/mcp/stop_all_command.py +109 -0
- code_puppy/command_line/mcp/stop_command.py +79 -0
- code_puppy/command_line/mcp/test_command.py +107 -0
- code_puppy/command_line/mcp/utils.py +129 -0
- code_puppy/command_line/mcp/wizard_utils.py +259 -0
- code_puppy/command_line/model_picker_completion.py +21 -4
- code_puppy/command_line/prompt_toolkit_completion.py +9 -0
- code_puppy/main.py +23 -17
- code_puppy/mcp/__init__.py +42 -16
- code_puppy/mcp/async_lifecycle.py +51 -49
- code_puppy/mcp/blocking_startup.py +125 -113
- code_puppy/mcp/captured_stdio_server.py +63 -70
- code_puppy/mcp/circuit_breaker.py +63 -47
- code_puppy/mcp/config_wizard.py +169 -136
- code_puppy/mcp/dashboard.py +79 -71
- code_puppy/mcp/error_isolation.py +147 -100
- code_puppy/mcp/examples/retry_example.py +55 -42
- code_puppy/mcp/health_monitor.py +152 -141
- code_puppy/mcp/managed_server.py +100 -93
- code_puppy/mcp/manager.py +168 -156
- code_puppy/mcp/registry.py +148 -110
- code_puppy/mcp/retry_manager.py +63 -61
- code_puppy/mcp/server_registry_catalog.py +271 -225
- code_puppy/mcp/status_tracker.py +80 -80
- code_puppy/mcp/system_tools.py +47 -52
- code_puppy/messaging/message_queue.py +20 -13
- code_puppy/messaging/renderers.py +30 -15
- code_puppy/state_management.py +103 -0
- code_puppy/tui/app.py +64 -7
- code_puppy/tui/components/chat_view.py +3 -3
- code_puppy/tui/components/human_input_modal.py +12 -8
- code_puppy/tui/screens/__init__.py +2 -2
- code_puppy/tui/screens/mcp_install_wizard.py +208 -179
- code_puppy/tui/tests/test_agent_command.py +3 -3
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.136.dist-info}/METADATA +1 -1
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.136.dist-info}/RECORD +59 -41
- code_puppy/command_line/mcp_commands.py +0 -1789
- {code_puppy-0.0.135.data → code_puppy-0.0.136.data}/data/code_puppy/models.json +0 -0
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.136.dist-info}/WHEEL +0 -0
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.136.dist-info}/entry_points.txt +0 -0
- {code_puppy-0.0.135.dist-info → code_puppy-0.0.136.dist-info}/licenses/LICENSE +0 -0
|
@@ -201,45 +201,50 @@ class MessageQueue:
|
|
|
201
201
|
"""Create a human input request and return its unique ID."""
|
|
202
202
|
self._prompt_id_counter += 1
|
|
203
203
|
prompt_id = f"prompt_{self._prompt_id_counter}"
|
|
204
|
-
|
|
204
|
+
|
|
205
205
|
# Emit the human input request message
|
|
206
206
|
message = UIMessage(
|
|
207
207
|
type=MessageType.HUMAN_INPUT_REQUEST,
|
|
208
208
|
content=prompt_text,
|
|
209
|
-
metadata={"prompt_id": prompt_id}
|
|
209
|
+
metadata={"prompt_id": prompt_id},
|
|
210
210
|
)
|
|
211
211
|
self.emit(message)
|
|
212
|
-
|
|
212
|
+
|
|
213
213
|
return prompt_id
|
|
214
214
|
|
|
215
215
|
def wait_for_prompt_response(self, prompt_id: str, timeout: float = None) -> str:
|
|
216
216
|
"""Wait for a response to a human input request."""
|
|
217
217
|
import time
|
|
218
|
+
|
|
218
219
|
start_time = time.time()
|
|
219
|
-
|
|
220
|
+
|
|
220
221
|
# Check if we're in TUI mode - if so, try to yield control to the event loop
|
|
221
222
|
from code_puppy.state_management import is_tui_mode
|
|
223
|
+
|
|
222
224
|
sleep_interval = 0.05 if is_tui_mode() else 0.1
|
|
223
|
-
|
|
225
|
+
|
|
224
226
|
# Debug logging for TUI mode
|
|
225
227
|
if is_tui_mode():
|
|
226
228
|
print(f"[DEBUG] Waiting for prompt response: {prompt_id}")
|
|
227
|
-
|
|
229
|
+
|
|
228
230
|
while True:
|
|
229
231
|
if prompt_id in self._prompt_responses:
|
|
230
232
|
response = self._prompt_responses.pop(prompt_id)
|
|
231
233
|
if is_tui_mode():
|
|
232
234
|
print(f"[DEBUG] Got response for {prompt_id}: {response[:20]}...")
|
|
233
235
|
return response
|
|
234
|
-
|
|
236
|
+
|
|
235
237
|
if timeout and (time.time() - start_time) > timeout:
|
|
236
|
-
raise TimeoutError(
|
|
237
|
-
|
|
238
|
+
raise TimeoutError(
|
|
239
|
+
f"No response received for prompt {prompt_id} within {timeout} seconds"
|
|
240
|
+
)
|
|
241
|
+
|
|
238
242
|
time.sleep(sleep_interval)
|
|
239
243
|
|
|
240
244
|
def provide_prompt_response(self, prompt_id: str, response: str):
|
|
241
245
|
"""Provide a response to a human input request."""
|
|
242
246
|
from code_puppy.state_management import is_tui_mode
|
|
247
|
+
|
|
243
248
|
if is_tui_mode():
|
|
244
249
|
print(f"[DEBUG] Providing response for {prompt_id}: {response[:20]}...")
|
|
245
250
|
self._prompt_responses[prompt_id] = response
|
|
@@ -343,25 +348,27 @@ def emit_divider(content: str = "[dim]" + "─" * 100 + "\n" + "[/dim]", **metad
|
|
|
343
348
|
def emit_prompt(prompt_text: str, timeout: float = None) -> str:
|
|
344
349
|
"""Emit a human input request and wait for response."""
|
|
345
350
|
from code_puppy.state_management import is_tui_mode
|
|
346
|
-
|
|
351
|
+
|
|
347
352
|
# In interactive mode, use direct input instead of the queue system
|
|
348
353
|
if not is_tui_mode():
|
|
349
354
|
# Emit the prompt as a message for display
|
|
350
355
|
from code_puppy.messaging import emit_info
|
|
356
|
+
|
|
351
357
|
emit_info(f"[yellow]{prompt_text}[/yellow]")
|
|
352
|
-
|
|
358
|
+
|
|
353
359
|
# Get input directly
|
|
354
360
|
try:
|
|
355
361
|
# Try to use rich console for better formatting
|
|
356
362
|
from rich.console import Console
|
|
363
|
+
|
|
357
364
|
console = Console()
|
|
358
365
|
response = console.input("[cyan]>>> [/cyan]")
|
|
359
366
|
return response
|
|
360
|
-
except:
|
|
367
|
+
except Exception:
|
|
361
368
|
# Fallback to basic input
|
|
362
369
|
response = input(">>> ")
|
|
363
370
|
return response
|
|
364
|
-
|
|
371
|
+
|
|
365
372
|
# In TUI mode, use the queue system
|
|
366
373
|
queue = get_global_queue()
|
|
367
374
|
prompt_id = queue.create_prompt_request(prompt_text)
|
|
@@ -221,41 +221,53 @@ class TUIRenderer(MessageRenderer):
|
|
|
221
221
|
async def _handle_human_input_request(self, message: UIMessage):
|
|
222
222
|
"""Handle a human input request in TUI mode."""
|
|
223
223
|
try:
|
|
224
|
-
print(
|
|
225
|
-
|
|
224
|
+
print("[DEBUG] TUI renderer handling human input request")
|
|
225
|
+
|
|
226
226
|
# Check if tui_app is available
|
|
227
227
|
if not self.tui_app:
|
|
228
|
-
print(
|
|
229
|
-
prompt_id =
|
|
228
|
+
print("[DEBUG] No tui_app available, falling back to error response")
|
|
229
|
+
prompt_id = (
|
|
230
|
+
message.metadata.get("prompt_id") if message.metadata else None
|
|
231
|
+
)
|
|
230
232
|
if prompt_id:
|
|
231
233
|
from code_puppy.messaging import provide_prompt_response
|
|
234
|
+
|
|
232
235
|
provide_prompt_response(prompt_id, "")
|
|
233
236
|
return
|
|
234
|
-
|
|
237
|
+
|
|
235
238
|
prompt_id = message.metadata.get("prompt_id") if message.metadata else None
|
|
236
239
|
if not prompt_id:
|
|
237
|
-
print(
|
|
240
|
+
print("[DEBUG] No prompt_id in message metadata")
|
|
238
241
|
self.tui_app.add_error_message("Error: Invalid human input request")
|
|
239
242
|
return
|
|
240
243
|
|
|
241
244
|
# For now, use a simple fallback instead of modal to avoid crashes
|
|
242
|
-
print(
|
|
243
|
-
self.tui_app.add_system_message(
|
|
244
|
-
|
|
245
|
-
|
|
245
|
+
print("[DEBUG] Using fallback approach - showing prompt as message")
|
|
246
|
+
self.tui_app.add_system_message(
|
|
247
|
+
f"[yellow]INPUT NEEDED:[/yellow] {str(message.content)}"
|
|
248
|
+
)
|
|
249
|
+
self.tui_app.add_system_message(
|
|
250
|
+
"[dim]This would normally show a modal, but using fallback to prevent crashes[/dim]"
|
|
251
|
+
)
|
|
252
|
+
|
|
246
253
|
# Provide empty response for now to unblock the waiting thread
|
|
247
254
|
from code_puppy.messaging import provide_prompt_response
|
|
255
|
+
|
|
248
256
|
provide_prompt_response(prompt_id, "")
|
|
249
|
-
|
|
257
|
+
|
|
250
258
|
except Exception as e:
|
|
251
259
|
print(f"[DEBUG] Top-level exception in _handle_human_input_request: {e}")
|
|
252
260
|
import traceback
|
|
261
|
+
|
|
253
262
|
traceback.print_exc()
|
|
254
263
|
# Last resort - provide empty response to prevent hanging
|
|
255
264
|
try:
|
|
256
|
-
prompt_id =
|
|
265
|
+
prompt_id = (
|
|
266
|
+
message.metadata.get("prompt_id") if message.metadata else None
|
|
267
|
+
)
|
|
257
268
|
if prompt_id:
|
|
258
269
|
from code_puppy.messaging import provide_prompt_response
|
|
270
|
+
|
|
259
271
|
provide_prompt_response(prompt_id, "")
|
|
260
272
|
except Exception:
|
|
261
273
|
pass # Can't do anything more
|
|
@@ -374,7 +386,9 @@ class SynchronousInteractiveRenderer:
|
|
|
374
386
|
"""Handle a human input request in interactive mode."""
|
|
375
387
|
prompt_id = message.metadata.get("prompt_id") if message.metadata else None
|
|
376
388
|
if not prompt_id:
|
|
377
|
-
self.console.print(
|
|
389
|
+
self.console.print(
|
|
390
|
+
"[bold red]Error: Invalid human input request[/bold red]"
|
|
391
|
+
)
|
|
378
392
|
return
|
|
379
393
|
|
|
380
394
|
# Display the prompt
|
|
@@ -386,11 +400,12 @@ class SynchronousInteractiveRenderer:
|
|
|
386
400
|
try:
|
|
387
401
|
# Use basic input for now - could be enhanced with prompt_toolkit later
|
|
388
402
|
response = input(">>> ")
|
|
389
|
-
|
|
403
|
+
|
|
390
404
|
# Provide the response back to the queue
|
|
391
405
|
from .message_queue import provide_prompt_response
|
|
406
|
+
|
|
392
407
|
provide_prompt_response(prompt_id, response)
|
|
393
|
-
|
|
408
|
+
|
|
394
409
|
except (EOFError, KeyboardInterrupt):
|
|
395
410
|
# Handle Ctrl+C or Ctrl+D
|
|
396
411
|
provide_prompt_response(prompt_id, "")
|
code_puppy/state_management.py
CHANGED
|
@@ -1,18 +1,43 @@
|
|
|
1
1
|
from typing import Any, List
|
|
2
2
|
|
|
3
|
+
# Legacy global state - maintained for backward compatibility
|
|
3
4
|
_message_history: List[Any] = []
|
|
4
5
|
_compacted_message_hashes = set()
|
|
6
|
+
|
|
7
|
+
# Flag to control whether to use agent-specific history (True) or global history (False)
|
|
8
|
+
_use_agent_specific_history = True
|
|
5
9
|
_tui_mode: bool = False
|
|
6
10
|
_tui_app_instance: Any = None
|
|
7
11
|
|
|
8
12
|
|
|
9
13
|
def add_compacted_message_hash(message_hash: str) -> None:
|
|
10
14
|
"""Add a message hash to the set of compacted message hashes."""
|
|
15
|
+
if _use_agent_specific_history:
|
|
16
|
+
try:
|
|
17
|
+
from code_puppy.agents.agent_manager import (
|
|
18
|
+
add_current_agent_compacted_message_hash,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
add_current_agent_compacted_message_hash(message_hash)
|
|
22
|
+
return
|
|
23
|
+
except Exception:
|
|
24
|
+
# Fallback to global if agent system fails
|
|
25
|
+
pass
|
|
11
26
|
_compacted_message_hashes.add(message_hash)
|
|
12
27
|
|
|
13
28
|
|
|
14
29
|
def get_compacted_message_hashes():
|
|
15
30
|
"""Get the set of compacted message hashes."""
|
|
31
|
+
if _use_agent_specific_history:
|
|
32
|
+
try:
|
|
33
|
+
from code_puppy.agents.agent_manager import (
|
|
34
|
+
get_current_agent_compacted_message_hashes,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
return get_current_agent_compacted_message_hashes()
|
|
38
|
+
except Exception:
|
|
39
|
+
# Fallback to global if agent system fails
|
|
40
|
+
pass
|
|
16
41
|
return _compacted_message_hashes
|
|
17
42
|
|
|
18
43
|
|
|
@@ -64,27 +89,105 @@ def get_tui_mode() -> bool:
|
|
|
64
89
|
|
|
65
90
|
|
|
66
91
|
def get_message_history() -> List[Any]:
|
|
92
|
+
"""Get message history - uses agent-specific history if enabled, otherwise global."""
|
|
93
|
+
if _use_agent_specific_history:
|
|
94
|
+
try:
|
|
95
|
+
from code_puppy.agents.agent_manager import (
|
|
96
|
+
get_current_agent_message_history,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return get_current_agent_message_history()
|
|
100
|
+
except Exception:
|
|
101
|
+
# Fallback to global if agent system fails
|
|
102
|
+
return _message_history
|
|
67
103
|
return _message_history
|
|
68
104
|
|
|
69
105
|
|
|
70
106
|
def set_message_history(history: List[Any]) -> None:
|
|
107
|
+
"""Set message history - uses agent-specific history if enabled, otherwise global."""
|
|
108
|
+
if _use_agent_specific_history:
|
|
109
|
+
try:
|
|
110
|
+
from code_puppy.agents.agent_manager import (
|
|
111
|
+
set_current_agent_message_history,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
set_current_agent_message_history(history)
|
|
115
|
+
return
|
|
116
|
+
except Exception:
|
|
117
|
+
# Fallback to global if agent system fails
|
|
118
|
+
pass
|
|
71
119
|
global _message_history
|
|
72
120
|
_message_history = history
|
|
73
121
|
|
|
74
122
|
|
|
75
123
|
def clear_message_history() -> None:
|
|
124
|
+
"""Clear message history - uses agent-specific history if enabled, otherwise global."""
|
|
125
|
+
if _use_agent_specific_history:
|
|
126
|
+
try:
|
|
127
|
+
from code_puppy.agents.agent_manager import (
|
|
128
|
+
clear_current_agent_message_history,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
clear_current_agent_message_history()
|
|
132
|
+
return
|
|
133
|
+
except Exception:
|
|
134
|
+
# Fallback to global if agent system fails
|
|
135
|
+
pass
|
|
76
136
|
global _message_history
|
|
77
137
|
_message_history = []
|
|
78
138
|
|
|
79
139
|
|
|
80
140
|
def append_to_message_history(message: Any) -> None:
|
|
141
|
+
"""Append to message history - uses agent-specific history if enabled, otherwise global."""
|
|
142
|
+
if _use_agent_specific_history:
|
|
143
|
+
try:
|
|
144
|
+
from code_puppy.agents.agent_manager import (
|
|
145
|
+
append_to_current_agent_message_history,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
append_to_current_agent_message_history(message)
|
|
149
|
+
return
|
|
150
|
+
except Exception:
|
|
151
|
+
# Fallback to global if agent system fails
|
|
152
|
+
pass
|
|
81
153
|
_message_history.append(message)
|
|
82
154
|
|
|
83
155
|
|
|
84
156
|
def extend_message_history(history: List[Any]) -> None:
|
|
157
|
+
"""Extend message history - uses agent-specific history if enabled, otherwise global."""
|
|
158
|
+
if _use_agent_specific_history:
|
|
159
|
+
try:
|
|
160
|
+
from code_puppy.agents.agent_manager import (
|
|
161
|
+
extend_current_agent_message_history,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
extend_current_agent_message_history(history)
|
|
165
|
+
return
|
|
166
|
+
except Exception:
|
|
167
|
+
# Fallback to global if agent system fails
|
|
168
|
+
pass
|
|
85
169
|
_message_history.extend(history)
|
|
86
170
|
|
|
87
171
|
|
|
172
|
+
def set_use_agent_specific_history(enabled: bool) -> None:
|
|
173
|
+
"""Enable or disable agent-specific message history.
|
|
174
|
+
|
|
175
|
+
Args:
|
|
176
|
+
enabled: True to use per-agent history, False to use global history.
|
|
177
|
+
"""
|
|
178
|
+
global _use_agent_specific_history
|
|
179
|
+
_use_agent_specific_history = enabled
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def is_using_agent_specific_history() -> bool:
|
|
183
|
+
"""Check if agent-specific message history is enabled.
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
True if using per-agent history, False if using global history.
|
|
187
|
+
"""
|
|
188
|
+
return _use_agent_specific_history
|
|
189
|
+
|
|
190
|
+
|
|
88
191
|
def hash_message(message):
|
|
89
192
|
hashable_entities = []
|
|
90
193
|
for part in message.parts:
|
code_puppy/tui/app.py
CHANGED
|
@@ -46,7 +46,7 @@ from .. import state_management
|
|
|
46
46
|
# Import shared message classes
|
|
47
47
|
from .messages import CommandSelected, HistoryEntrySelected
|
|
48
48
|
from .models import ChatMessage, MessageType
|
|
49
|
-
from .screens import HelpScreen, SettingsScreen, ToolsScreen
|
|
49
|
+
from .screens import HelpScreen, MCPInstallWizardScreen, SettingsScreen, ToolsScreen
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
class CodePuppyTUI(App):
|
|
@@ -88,6 +88,7 @@ class CodePuppyTUI(App):
|
|
|
88
88
|
# Reactive variables for app state
|
|
89
89
|
current_model = reactive("")
|
|
90
90
|
puppy_name = reactive("")
|
|
91
|
+
current_agent = reactive("")
|
|
91
92
|
agent_busy = reactive(False)
|
|
92
93
|
|
|
93
94
|
def watch_agent_busy(self) -> None:
|
|
@@ -95,6 +96,35 @@ class CodePuppyTUI(App):
|
|
|
95
96
|
# Update the submit/cancel button state when agent_busy changes
|
|
96
97
|
self._update_submit_cancel_button(self.agent_busy)
|
|
97
98
|
|
|
99
|
+
def watch_current_agent(self) -> None:
|
|
100
|
+
"""Watch for changes to current_agent and update title."""
|
|
101
|
+
self._update_title()
|
|
102
|
+
|
|
103
|
+
def _update_title(self) -> None:
|
|
104
|
+
"""Update the application title to include current agent."""
|
|
105
|
+
if self.current_agent:
|
|
106
|
+
self.title = f"Code Puppy - {self.current_agent}"
|
|
107
|
+
self.sub_title = "TUI Mode"
|
|
108
|
+
else:
|
|
109
|
+
self.title = "Code Puppy - AI Code Assistant"
|
|
110
|
+
self.sub_title = "TUI Mode"
|
|
111
|
+
|
|
112
|
+
def _on_agent_reload(self, agent_id: str, agent_name: str) -> None:
|
|
113
|
+
"""Callback for when agent is reloaded/changed."""
|
|
114
|
+
# Get the updated agent configuration
|
|
115
|
+
from code_puppy.agents.agent_manager import get_current_agent_config
|
|
116
|
+
|
|
117
|
+
current_agent_config = get_current_agent_config()
|
|
118
|
+
new_agent_display = (
|
|
119
|
+
current_agent_config.display_name if current_agent_config else "code-puppy"
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# Update the reactive variable (this will trigger watch_current_agent)
|
|
123
|
+
self.current_agent = new_agent_display
|
|
124
|
+
|
|
125
|
+
# Add a system message to notify the user
|
|
126
|
+
self.add_system_message(f"🔄 Switched to agent: {new_agent_display}")
|
|
127
|
+
|
|
98
128
|
def __init__(self, initial_command: str = None, **kwargs):
|
|
99
129
|
super().__init__(**kwargs)
|
|
100
130
|
self.agent_manager = None
|
|
@@ -123,10 +153,26 @@ class CodePuppyTUI(App):
|
|
|
123
153
|
|
|
124
154
|
set_tui_app_instance(self)
|
|
125
155
|
|
|
156
|
+
# Register callback for agent reload events
|
|
157
|
+
from code_puppy.callbacks import register_callback
|
|
158
|
+
|
|
159
|
+
register_callback("agent_reload", self._on_agent_reload)
|
|
160
|
+
|
|
126
161
|
# Load configuration
|
|
127
162
|
self.current_model = get_model_name()
|
|
128
163
|
self.puppy_name = get_puppy_name()
|
|
129
164
|
|
|
165
|
+
# Get current agent information
|
|
166
|
+
from code_puppy.agents.agent_manager import get_current_agent_config
|
|
167
|
+
|
|
168
|
+
current_agent_config = get_current_agent_config()
|
|
169
|
+
self.current_agent = (
|
|
170
|
+
current_agent_config.display_name if current_agent_config else "code-puppy"
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
# Initial title update
|
|
174
|
+
self._update_title()
|
|
175
|
+
|
|
130
176
|
# Use runtime manager to ensure we always have the current agent
|
|
131
177
|
self.agent_manager = get_runtime_agent_manager()
|
|
132
178
|
|
|
@@ -143,7 +189,9 @@ class CodePuppyTUI(App):
|
|
|
143
189
|
|
|
144
190
|
# Get current agent and display info
|
|
145
191
|
get_code_generation_agent()
|
|
146
|
-
self.add_system_message(
|
|
192
|
+
self.add_system_message(
|
|
193
|
+
f"🐕 Loaded agent '{self.puppy_name}' with model '{self.current_model}'"
|
|
194
|
+
)
|
|
147
195
|
|
|
148
196
|
# Start the message renderer EARLY to catch startup messages
|
|
149
197
|
# Using call_after_refresh to start it as soon as possible after mount
|
|
@@ -632,16 +680,20 @@ class CodePuppyTUI(App):
|
|
|
632
680
|
|
|
633
681
|
def action_open_mcp_wizard(self) -> None:
|
|
634
682
|
"""Open the MCP Install Wizard."""
|
|
635
|
-
|
|
683
|
+
|
|
636
684
|
def handle_wizard_result(result):
|
|
637
685
|
if result and result.get("success"):
|
|
638
686
|
# Show success message
|
|
639
|
-
self.add_system_message(
|
|
640
|
-
|
|
687
|
+
self.add_system_message(
|
|
688
|
+
result.get("message", "MCP server installed successfully")
|
|
689
|
+
)
|
|
690
|
+
|
|
641
691
|
# If a server was installed, suggest starting it
|
|
642
692
|
if result.get("server_name"):
|
|
643
693
|
server_name = result["server_name"]
|
|
644
|
-
self.add_system_message(
|
|
694
|
+
self.add_system_message(
|
|
695
|
+
f"💡 Use '/mcp start {server_name}' to start the server"
|
|
696
|
+
)
|
|
645
697
|
elif (
|
|
646
698
|
result
|
|
647
699
|
and not result.get("success")
|
|
@@ -649,7 +701,7 @@ class CodePuppyTUI(App):
|
|
|
649
701
|
):
|
|
650
702
|
# Show error message (but not for cancellation)
|
|
651
703
|
self.add_error_message(result.get("message", "MCP installation failed"))
|
|
652
|
-
|
|
704
|
+
|
|
653
705
|
self.push_screen(MCPInstallWizardScreen(), handle_wizard_result)
|
|
654
706
|
|
|
655
707
|
def process_initial_command(self) -> None:
|
|
@@ -905,6 +957,11 @@ class CodePuppyTUI(App):
|
|
|
905
957
|
async def on_unmount(self):
|
|
906
958
|
"""Clean up when the app is unmounted."""
|
|
907
959
|
try:
|
|
960
|
+
# Unregister the agent reload callback
|
|
961
|
+
from code_puppy.callbacks import unregister_callback
|
|
962
|
+
|
|
963
|
+
unregister_callback("agent_reload", self._on_agent_reload)
|
|
964
|
+
|
|
908
965
|
await self.stop_message_renderer()
|
|
909
966
|
except Exception as e:
|
|
910
967
|
# Log unmount errors but don't crash during cleanup
|
|
@@ -11,7 +11,7 @@ from rich.syntax import Syntax
|
|
|
11
11
|
from rich.text import Text
|
|
12
12
|
from textual import on
|
|
13
13
|
from textual.containers import Vertical, VerticalScroll
|
|
14
|
-
from textual.widgets import Static
|
|
14
|
+
from textual.widgets import Static
|
|
15
15
|
|
|
16
16
|
from ..models import ChatMessage, MessageType
|
|
17
17
|
from .copy_button import CopyButton
|
|
@@ -342,14 +342,14 @@ class ChatView(VerticalScroll):
|
|
|
342
342
|
|
|
343
343
|
if message.type == MessageType.USER:
|
|
344
344
|
# Add user indicator and make it stand out
|
|
345
|
-
content_lines = message.content.split(
|
|
345
|
+
content_lines = message.content.split("\n")
|
|
346
346
|
if len(content_lines) > 1:
|
|
347
347
|
# Multi-line user message
|
|
348
348
|
formatted_content = f"╔══ USER ══╗\n{message.content}\n╚══════════╝"
|
|
349
349
|
else:
|
|
350
350
|
# Single line user message
|
|
351
351
|
formatted_content = f"▶ USER: {message.content}"
|
|
352
|
-
|
|
352
|
+
|
|
353
353
|
message_widget = Static(Text(formatted_content), classes=css_class)
|
|
354
354
|
# User messages are not collapsible - mount directly
|
|
355
355
|
self.mount(message_widget)
|
|
@@ -7,7 +7,7 @@ from textual.app import ComposeResult
|
|
|
7
7
|
from textual.containers import Container, Horizontal
|
|
8
8
|
from textual.events import Key
|
|
9
9
|
from textual.screen import ModalScreen
|
|
10
|
-
from textual.widgets import Button,
|
|
10
|
+
from textual.widgets import Button, Static, TextArea
|
|
11
11
|
|
|
12
12
|
try:
|
|
13
13
|
from .custom_widgets import CustomTextArea
|
|
@@ -109,13 +109,14 @@ class HumanInputModal(ModalScreen):
|
|
|
109
109
|
def on_mount(self) -> None:
|
|
110
110
|
"""Focus the input field when modal opens."""
|
|
111
111
|
try:
|
|
112
|
-
print(
|
|
112
|
+
print("[DEBUG] Modal on_mount called")
|
|
113
113
|
input_field = self.query_one("#response-input", CustomTextArea)
|
|
114
114
|
input_field.focus()
|
|
115
|
-
print(
|
|
115
|
+
print("[DEBUG] Modal input field focused")
|
|
116
116
|
except Exception as e:
|
|
117
117
|
print(f"[DEBUG] Modal on_mount exception: {e}")
|
|
118
118
|
import traceback
|
|
119
|
+
|
|
119
120
|
traceback.print_exc()
|
|
120
121
|
|
|
121
122
|
@on(Button.Pressed, "#submit-button")
|
|
@@ -123,7 +124,7 @@ class HumanInputModal(ModalScreen):
|
|
|
123
124
|
"""Handle submit button click."""
|
|
124
125
|
self._submit_response()
|
|
125
126
|
|
|
126
|
-
@on(Button.Pressed, "#cancel-button")
|
|
127
|
+
@on(Button.Pressed, "#cancel-button")
|
|
127
128
|
def on_cancel_clicked(self) -> None:
|
|
128
129
|
"""Handle cancel button click."""
|
|
129
130
|
self._cancel_response()
|
|
@@ -149,23 +150,26 @@ class HumanInputModal(ModalScreen):
|
|
|
149
150
|
input_field = self.query_one("#response-input", CustomTextArea)
|
|
150
151
|
self.response = input_field.text.strip()
|
|
151
152
|
print(f"[DEBUG] Modal submitting response: {self.response[:20]}...")
|
|
152
|
-
|
|
153
|
+
|
|
153
154
|
# Provide the response back to the message queue
|
|
154
155
|
from code_puppy.messaging import provide_prompt_response
|
|
156
|
+
|
|
155
157
|
provide_prompt_response(self.prompt_id, self.response)
|
|
156
|
-
|
|
158
|
+
|
|
157
159
|
# Close the modal using the same method as other modals
|
|
158
160
|
self.app.pop_screen()
|
|
159
161
|
except Exception as e:
|
|
160
162
|
print(f"[DEBUG] Modal error during submit: {e}")
|
|
161
163
|
# If something goes wrong, provide empty response
|
|
162
164
|
from code_puppy.messaging import provide_prompt_response
|
|
165
|
+
|
|
163
166
|
provide_prompt_response(self.prompt_id, "")
|
|
164
167
|
self.app.pop_screen()
|
|
165
168
|
|
|
166
169
|
def _cancel_response(self) -> None:
|
|
167
170
|
"""Cancel the input request."""
|
|
168
|
-
print(
|
|
171
|
+
print("[DEBUG] Modal cancelling response")
|
|
169
172
|
from code_puppy.messaging import provide_prompt_response
|
|
173
|
+
|
|
170
174
|
provide_prompt_response(self.prompt_id, "")
|
|
171
|
-
self.app.pop_screen()
|
|
175
|
+
self.app.pop_screen()
|
|
@@ -3,13 +3,13 @@ TUI screens package.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from .help import HelpScreen
|
|
6
|
+
from .mcp_install_wizard import MCPInstallWizardScreen
|
|
6
7
|
from .settings import SettingsScreen
|
|
7
8
|
from .tools import ToolsScreen
|
|
8
|
-
from .mcp_install_wizard import MCPInstallWizardScreen
|
|
9
9
|
|
|
10
10
|
__all__ = [
|
|
11
11
|
"HelpScreen",
|
|
12
|
-
"SettingsScreen",
|
|
12
|
+
"SettingsScreen",
|
|
13
13
|
"ToolsScreen",
|
|
14
14
|
"MCPInstallWizardScreen",
|
|
15
15
|
]
|