tunacode-cli 0.0.17__py3-none-any.whl → 0.0.18__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.
Potentially problematic release.
This version of tunacode-cli might be problematic. Click here for more details.
- tunacode/cli/commands.py +39 -41
- tunacode/cli/main.py +29 -26
- tunacode/cli/repl.py +35 -10
- tunacode/cli/textual_app.py +69 -66
- tunacode/cli/textual_bridge.py +33 -32
- tunacode/configuration/settings.py +2 -9
- tunacode/constants.py +2 -4
- tunacode/context.py +1 -1
- tunacode/core/agents/main.py +88 -62
- tunacode/core/setup/config_setup.py +79 -44
- tunacode/core/setup/coordinator.py +20 -13
- tunacode/core/setup/git_safety_setup.py +35 -49
- tunacode/core/state.py +2 -9
- tunacode/exceptions.py +0 -2
- tunacode/tools/__init__.py +10 -1
- tunacode/tools/base.py +1 -1
- tunacode/tools/bash.py +5 -5
- tunacode/tools/grep.py +210 -250
- tunacode/tools/read_file.py +2 -8
- tunacode/tools/run_command.py +4 -11
- tunacode/tools/update_file.py +2 -6
- tunacode/ui/completers.py +32 -31
- tunacode/ui/console.py +3 -3
- tunacode/ui/input.py +8 -5
- tunacode/ui/keybindings.py +1 -3
- tunacode/ui/lexers.py +16 -16
- tunacode/ui/output.py +2 -2
- tunacode/ui/panels.py +8 -8
- tunacode/ui/prompt_manager.py +19 -7
- tunacode/utils/import_cache.py +11 -0
- tunacode/utils/user_configuration.py +24 -2
- {tunacode_cli-0.0.17.dist-info → tunacode_cli-0.0.18.dist-info}/METADATA +43 -2
- tunacode_cli-0.0.18.dist-info/RECORD +68 -0
- tunacode_cli-0.0.17.dist-info/RECORD +0 -67
- {tunacode_cli-0.0.17.dist-info → tunacode_cli-0.0.18.dist-info}/WHEEL +0 -0
- {tunacode_cli-0.0.17.dist-info → tunacode_cli-0.0.18.dist-info}/entry_points.txt +0 -0
- {tunacode_cli-0.0.17.dist-info → tunacode_cli-0.0.18.dist-info}/licenses/LICENSE +0 -0
- {tunacode_cli-0.0.17.dist-info → tunacode_cli-0.0.18.dist-info}/top_level.txt +0 -0
tunacode/cli/textual_app.py
CHANGED
|
@@ -18,10 +18,9 @@ from textual.binding import Binding
|
|
|
18
18
|
from textual.containers import Container, Horizontal, Vertical, VerticalScroll
|
|
19
19
|
from textual.message import Message
|
|
20
20
|
from textual.reactive import reactive
|
|
21
|
-
from textual.widgets import
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
)
|
|
21
|
+
from textual.widgets import Button, Footer, Header, Input, Label
|
|
22
|
+
from textual.widgets import Markdown as MarkdownWidget
|
|
23
|
+
from textual.widgets import Static, TabbedContent, TabPane, TextArea
|
|
25
24
|
|
|
26
25
|
from tunacode.configuration.settings import ApplicationSettings
|
|
27
26
|
from tunacode.core.state import StateManager
|
|
@@ -31,52 +30,58 @@ from tunacode.utils.system import check_for_updates
|
|
|
31
30
|
|
|
32
31
|
class ChatMessage(Static):
|
|
33
32
|
"""A single chat message widget."""
|
|
34
|
-
|
|
33
|
+
|
|
35
34
|
def __init__(self, sender: str, content: str, message_type: str = "user"):
|
|
36
35
|
super().__init__()
|
|
37
36
|
self.sender = sender
|
|
38
37
|
self.content = content
|
|
39
38
|
self.message_type = message_type
|
|
40
|
-
|
|
39
|
+
|
|
41
40
|
def compose(self) -> ComposeResult:
|
|
42
41
|
"""Compose the chat message."""
|
|
43
42
|
if self.message_type == "user":
|
|
44
43
|
yield Static(f"[bold cyan]❯ You[/bold cyan]\n{self.content}", classes="user-message")
|
|
45
44
|
elif self.message_type == "agent":
|
|
46
|
-
yield Static(
|
|
45
|
+
yield Static(
|
|
46
|
+
f"[bold green]🤖 TunaCode[/bold green]\n{self.content}", classes="agent-message"
|
|
47
|
+
)
|
|
47
48
|
elif self.message_type == "system":
|
|
48
|
-
yield Static(
|
|
49
|
+
yield Static(
|
|
50
|
+
f"[bold yellow]⚠️ System[/bold yellow]\n{self.content}", classes="system-message"
|
|
51
|
+
)
|
|
49
52
|
elif self.message_type == "tool":
|
|
50
|
-
yield Static(
|
|
53
|
+
yield Static(
|
|
54
|
+
f"[bold magenta]🔧 Tool[/bold magenta]\n{self.content}", classes="tool-message"
|
|
55
|
+
)
|
|
51
56
|
|
|
52
57
|
|
|
53
58
|
class Sidebar(Container):
|
|
54
59
|
"""Sidebar with model info and commands."""
|
|
55
|
-
|
|
60
|
+
|
|
56
61
|
def __init__(self, state_manager: StateManager):
|
|
57
62
|
super().__init__()
|
|
58
63
|
self.state_manager = state_manager
|
|
59
|
-
|
|
64
|
+
|
|
60
65
|
def compose(self) -> ComposeResult:
|
|
61
66
|
"""Compose the sidebar."""
|
|
62
67
|
yield Static("[bold]TunaCode[/bold]", classes="sidebar-title")
|
|
63
68
|
yield Static(f"Model: {self.state_manager.session.current_model}", id="current-model")
|
|
64
69
|
yield Static("", classes="spacer")
|
|
65
|
-
|
|
70
|
+
|
|
66
71
|
yield Static("[bold]Commands[/bold]", classes="section-title")
|
|
67
72
|
yield Static("/help - Show help", classes="command-item")
|
|
68
73
|
yield Static("/clear - Clear chat", classes="command-item")
|
|
69
74
|
yield Static("/model - Switch model", classes="command-item")
|
|
70
75
|
yield Static("/yolo - Toggle confirmations", classes="command-item")
|
|
71
76
|
yield Static("", classes="spacer")
|
|
72
|
-
|
|
77
|
+
|
|
73
78
|
yield Static("[bold]Status[/bold]", classes="section-title")
|
|
74
79
|
yield Static("● Ready", id="status", classes="status-ready")
|
|
75
80
|
|
|
76
81
|
|
|
77
82
|
class ChatHistory(VerticalScroll):
|
|
78
83
|
"""Scrollable chat history container."""
|
|
79
|
-
|
|
84
|
+
|
|
80
85
|
def add_message(self, sender: str, content: str, message_type: str = "user") -> None:
|
|
81
86
|
"""Add a new message to the chat history."""
|
|
82
87
|
message = ChatMessage(sender, content, message_type)
|
|
@@ -86,20 +91,20 @@ class ChatHistory(VerticalScroll):
|
|
|
86
91
|
|
|
87
92
|
class InputArea(Container):
|
|
88
93
|
"""Input area with text area and send button."""
|
|
89
|
-
|
|
94
|
+
|
|
90
95
|
class SendMessage(Message):
|
|
91
96
|
"""Message sent when user submits input."""
|
|
92
|
-
|
|
97
|
+
|
|
93
98
|
def __init__(self, content: str) -> None:
|
|
94
99
|
self.content = content
|
|
95
100
|
super().__init__()
|
|
96
|
-
|
|
101
|
+
|
|
97
102
|
def compose(self) -> ComposeResult:
|
|
98
103
|
"""Compose the input area."""
|
|
99
104
|
with Horizontal():
|
|
100
105
|
yield TextArea(id="message-input")
|
|
101
106
|
yield Button("Send", id="send-button", variant="primary")
|
|
102
|
-
|
|
107
|
+
|
|
103
108
|
@on(Button.Pressed, "#send-button")
|
|
104
109
|
def send_message(self) -> None:
|
|
105
110
|
"""Send the current message."""
|
|
@@ -108,7 +113,7 @@ class InputArea(Container):
|
|
|
108
113
|
if content:
|
|
109
114
|
self.post_message(self.SendMessage(content))
|
|
110
115
|
text_area.clear()
|
|
111
|
-
|
|
116
|
+
|
|
112
117
|
def on_key(self, event) -> None:
|
|
113
118
|
"""Handle key events."""
|
|
114
119
|
if event.key == "ctrl+enter":
|
|
@@ -117,7 +122,7 @@ class InputArea(Container):
|
|
|
117
122
|
|
|
118
123
|
class TunaCodeApp(App):
|
|
119
124
|
"""Main TunaCode Textual application."""
|
|
120
|
-
|
|
125
|
+
|
|
121
126
|
CSS = """
|
|
122
127
|
Sidebar {
|
|
123
128
|
width: 30;
|
|
@@ -202,79 +207,75 @@ class TunaCodeApp(App):
|
|
|
202
207
|
margin-left: 1;
|
|
203
208
|
}
|
|
204
209
|
"""
|
|
205
|
-
|
|
210
|
+
|
|
206
211
|
BINDINGS = [
|
|
207
212
|
Binding("ctrl+c", "quit", "Quit"),
|
|
208
213
|
Binding("ctrl+l", "clear_chat", "Clear"),
|
|
209
214
|
Binding("f1", "help", "Help"),
|
|
210
215
|
Binding("f2", "model_info", "Model"),
|
|
211
216
|
]
|
|
212
|
-
|
|
217
|
+
|
|
213
218
|
def __init__(self, state_manager: StateManager):
|
|
214
219
|
super().__init__()
|
|
215
220
|
self.state_manager = state_manager
|
|
216
221
|
self.chat_history: Optional[ChatHistory] = None
|
|
217
222
|
self.sidebar: Optional[Sidebar] = None
|
|
218
223
|
self.input_area: Optional[InputArea] = None
|
|
219
|
-
|
|
224
|
+
|
|
220
225
|
def compose(self) -> ComposeResult:
|
|
221
226
|
"""Compose the main application layout."""
|
|
222
227
|
yield Header()
|
|
223
|
-
|
|
228
|
+
|
|
224
229
|
with Horizontal():
|
|
225
230
|
self.sidebar = Sidebar(self.state_manager)
|
|
226
231
|
yield self.sidebar
|
|
227
|
-
|
|
232
|
+
|
|
228
233
|
with Vertical():
|
|
229
234
|
self.chat_history = ChatHistory()
|
|
230
235
|
yield self.chat_history
|
|
231
|
-
|
|
236
|
+
|
|
232
237
|
self.input_area = InputArea()
|
|
233
238
|
yield self.input_area
|
|
234
|
-
|
|
239
|
+
|
|
235
240
|
yield Footer()
|
|
236
|
-
|
|
241
|
+
|
|
237
242
|
def on_mount(self) -> None:
|
|
238
243
|
"""Called when the app is mounted."""
|
|
239
244
|
# Add welcome messages
|
|
240
245
|
self.chat_history.add_message(
|
|
241
|
-
"System",
|
|
242
|
-
"Welcome to TunaCode v0.11 - Your AI-powered development assistant",
|
|
243
|
-
"system"
|
|
246
|
+
"System", "Welcome to TunaCode v0.11 - Your AI-powered development assistant", "system"
|
|
244
247
|
)
|
|
245
248
|
self.chat_history.add_message(
|
|
246
|
-
"System",
|
|
247
|
-
f"Current model: {self.state_manager.session.current_model}",
|
|
248
|
-
"system"
|
|
249
|
+
"System", f"Current model: {self.state_manager.session.current_model}", "system"
|
|
249
250
|
)
|
|
250
251
|
self.chat_history.add_message(
|
|
251
252
|
"System",
|
|
252
253
|
"⚠️ IMPORTANT: Always use git branches before making major changes\nType '/help' for available commands",
|
|
253
|
-
"system"
|
|
254
|
+
"system",
|
|
254
255
|
)
|
|
255
|
-
|
|
256
|
+
|
|
256
257
|
@on(InputArea.SendMessage)
|
|
257
258
|
async def handle_message(self, message: InputArea.SendMessage) -> None:
|
|
258
259
|
"""Handle incoming messages from the input area."""
|
|
259
260
|
content = message.content
|
|
260
|
-
|
|
261
|
+
|
|
261
262
|
# Add user message to chat
|
|
262
263
|
self.chat_history.add_message("You", content, "user")
|
|
263
|
-
|
|
264
|
+
|
|
264
265
|
# Update status
|
|
265
266
|
status_widget = self.sidebar.query_one("#status", Static)
|
|
266
267
|
status_widget.update("● Processing...")
|
|
267
268
|
status_widget.classes = "status-busy"
|
|
268
|
-
|
|
269
|
+
|
|
269
270
|
if content.startswith("/"):
|
|
270
271
|
await self.handle_command(content)
|
|
271
272
|
else:
|
|
272
273
|
await self.handle_user_input(content)
|
|
273
|
-
|
|
274
|
+
|
|
274
275
|
# Reset status
|
|
275
276
|
status_widget.update("● Ready")
|
|
276
277
|
status_widget.classes = "status-ready"
|
|
277
|
-
|
|
278
|
+
|
|
278
279
|
async def handle_command(self, command: str) -> None:
|
|
279
280
|
"""Handle slash commands."""
|
|
280
281
|
if command == "/help":
|
|
@@ -293,57 +294,58 @@ F1 - Help
|
|
|
293
294
|
F2 - Model info
|
|
294
295
|
Ctrl+Enter - Send message"""
|
|
295
296
|
self.chat_history.add_message("System", help_text, "system")
|
|
296
|
-
|
|
297
|
+
|
|
297
298
|
elif command == "/clear":
|
|
298
299
|
await self.action_clear_chat()
|
|
299
|
-
|
|
300
|
+
|
|
300
301
|
elif command == "/model":
|
|
301
302
|
model_info = f"Current model: {self.state_manager.session.current_model}"
|
|
302
303
|
self.chat_history.add_message("System", model_info, "system")
|
|
303
|
-
|
|
304
|
+
|
|
304
305
|
elif command == "/yolo":
|
|
305
306
|
# Toggle yolo mode
|
|
306
|
-
current_state = getattr(self.state_manager.session,
|
|
307
|
+
current_state = getattr(self.state_manager.session, "yolo_mode", False)
|
|
307
308
|
self.state_manager.session.yolo_mode = not current_state
|
|
308
309
|
new_state = "enabled" if not current_state else "disabled"
|
|
309
310
|
self.chat_history.add_message("System", f"Confirmation prompts {new_state}", "system")
|
|
310
|
-
|
|
311
|
+
|
|
311
312
|
elif command == "/quit":
|
|
312
313
|
await self.action_quit()
|
|
313
|
-
|
|
314
|
+
|
|
314
315
|
else:
|
|
315
316
|
self.chat_history.add_message("System", f"Unknown command: {command}", "system")
|
|
316
|
-
|
|
317
|
+
|
|
317
318
|
async def handle_user_input(self, text: str) -> None:
|
|
318
319
|
"""Handle regular user input."""
|
|
319
320
|
try:
|
|
320
321
|
# Use the bridge to process the input
|
|
321
|
-
if not hasattr(self,
|
|
322
|
+
if not hasattr(self, "_bridge"):
|
|
322
323
|
from tunacode.cli.textual_bridge import TextualAgentBridge
|
|
324
|
+
|
|
323
325
|
self._bridge = TextualAgentBridge(self.state_manager, self._bridge_message_callback)
|
|
324
|
-
|
|
326
|
+
|
|
325
327
|
# Process the request
|
|
326
328
|
response = await self._bridge.process_user_input(text)
|
|
327
|
-
|
|
329
|
+
|
|
328
330
|
# Add the agent's response to chat
|
|
329
331
|
self.chat_history.add_message("TunaCode", response, "agent")
|
|
330
|
-
|
|
332
|
+
|
|
331
333
|
except Exception as e:
|
|
332
334
|
self.chat_history.add_message("System", f"Error: {str(e)}", "system")
|
|
333
|
-
|
|
335
|
+
|
|
334
336
|
async def _bridge_message_callback(self, message_type: str, content: str) -> None:
|
|
335
337
|
"""Callback for bridge to send messages to the UI."""
|
|
336
338
|
self.chat_history.add_message("System", content, message_type)
|
|
337
|
-
|
|
339
|
+
|
|
338
340
|
def action_clear_chat(self) -> None:
|
|
339
341
|
"""Clear the chat history."""
|
|
340
342
|
self.chat_history.remove_children()
|
|
341
343
|
self.chat_history.add_message("System", "Chat cleared", "system")
|
|
342
|
-
|
|
344
|
+
|
|
343
345
|
def action_help(self) -> None:
|
|
344
346
|
"""Show help information."""
|
|
345
347
|
self.handle_command("/help")
|
|
346
|
-
|
|
348
|
+
|
|
347
349
|
def action_model_info(self) -> None:
|
|
348
350
|
"""Show model information."""
|
|
349
351
|
self.handle_command("/model")
|
|
@@ -358,29 +360,30 @@ async def run_textual_app(state_manager: StateManager) -> None:
|
|
|
358
360
|
def main():
|
|
359
361
|
"""Main entry point for the Textual app."""
|
|
360
362
|
import sys
|
|
361
|
-
|
|
363
|
+
|
|
362
364
|
# Handle command line arguments
|
|
363
365
|
version_flag = "--version" in sys.argv or "-v" in sys.argv
|
|
364
366
|
if version_flag:
|
|
365
367
|
from tunacode.constants import APP_VERSION
|
|
368
|
+
|
|
366
369
|
print(f"TunaCode v{APP_VERSION}")
|
|
367
370
|
return
|
|
368
|
-
|
|
371
|
+
|
|
369
372
|
# Initialize state manager
|
|
370
373
|
state_manager = StateManager()
|
|
371
374
|
app_settings = ApplicationSettings()
|
|
372
|
-
|
|
375
|
+
|
|
373
376
|
# Show banner
|
|
374
377
|
print("🐟 TunaCode - Modern AI Development Assistant")
|
|
375
378
|
print("=" * 50)
|
|
376
|
-
|
|
379
|
+
|
|
377
380
|
# Check for updates
|
|
378
381
|
has_update, latest_version = check_for_updates()
|
|
379
382
|
if has_update:
|
|
380
383
|
print(f"📦 Update available: v{latest_version}")
|
|
381
384
|
print("Run: pip install --upgrade tunacode-cli")
|
|
382
385
|
print()
|
|
383
|
-
|
|
386
|
+
|
|
384
387
|
# Parse CLI arguments for configuration
|
|
385
388
|
cli_config = {}
|
|
386
389
|
args = sys.argv[1:]
|
|
@@ -400,24 +403,24 @@ def main():
|
|
|
400
403
|
i += 1
|
|
401
404
|
else:
|
|
402
405
|
i += 1
|
|
403
|
-
|
|
406
|
+
|
|
404
407
|
async def run_app():
|
|
405
408
|
try:
|
|
406
409
|
# Run setup
|
|
407
410
|
run_setup = cli_config.get("setup", False)
|
|
408
411
|
await setup(run_setup, state_manager, cli_config)
|
|
409
|
-
|
|
412
|
+
|
|
410
413
|
# Run the Textual app
|
|
411
414
|
await run_textual_app(state_manager)
|
|
412
|
-
|
|
415
|
+
|
|
413
416
|
except KeyboardInterrupt:
|
|
414
417
|
print("\n👋 Goodbye!")
|
|
415
418
|
except Exception as e:
|
|
416
419
|
print(f"❌ Error: {e}")
|
|
417
|
-
|
|
420
|
+
|
|
418
421
|
# Run the async app
|
|
419
422
|
asyncio.run(run_app())
|
|
420
423
|
|
|
421
424
|
|
|
422
425
|
if __name__ == "__main__":
|
|
423
|
-
main()
|
|
426
|
+
main()
|
tunacode/cli/textual_bridge.py
CHANGED
|
@@ -6,90 +6,89 @@ with the new Textual-based interface while maintaining compatibility.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import asyncio
|
|
9
|
-
from typing import Callable, Optional
|
|
10
9
|
from asyncio.exceptions import CancelledError
|
|
10
|
+
from typing import Callable, Optional
|
|
11
11
|
|
|
12
12
|
from pydantic_ai.exceptions import UnexpectedModelBehavior
|
|
13
13
|
|
|
14
|
+
from tunacode.cli.commands import CommandRegistry
|
|
15
|
+
from tunacode.cli.repl import _parse_args
|
|
14
16
|
from tunacode.core.agents import main as agent
|
|
15
17
|
from tunacode.core.agents.main import patch_tool_messages
|
|
16
18
|
from tunacode.core.tool_handler import ToolHandler
|
|
17
19
|
from tunacode.exceptions import AgentError, UserAbortError, ValidationError
|
|
18
20
|
from tunacode.types import StateManager
|
|
19
|
-
from tunacode.cli.commands import CommandRegistry
|
|
20
|
-
from tunacode.cli.repl import _parse_args
|
|
21
21
|
from tunacode.ui.tool_ui import ToolUI
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
class TextualAgentBridge:
|
|
25
25
|
"""Bridge between Textual UI and existing agent logic."""
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
def __init__(self, state_manager: StateManager, message_callback: Callable):
|
|
28
28
|
self.state_manager = state_manager
|
|
29
29
|
self.message_callback = message_callback
|
|
30
30
|
self.tool_ui = ToolUI()
|
|
31
31
|
self.command_registry = CommandRegistry()
|
|
32
32
|
self.command_registry.register_all_default_commands()
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
async def process_user_input(self, text: str) -> str:
|
|
35
35
|
"""Process user input and return the agent's response."""
|
|
36
36
|
if text.startswith("/"):
|
|
37
37
|
return await self._handle_command(text)
|
|
38
38
|
else:
|
|
39
39
|
return await self._process_agent_request(text)
|
|
40
|
-
|
|
40
|
+
|
|
41
41
|
async def _handle_command(self, command: str) -> str:
|
|
42
42
|
"""Handle slash commands."""
|
|
43
43
|
try:
|
|
44
44
|
from tunacode.types import CommandContext
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
# Create command context
|
|
47
47
|
context = CommandContext(
|
|
48
|
-
state_manager=self.state_manager,
|
|
49
|
-
process_request=self._process_agent_request
|
|
48
|
+
state_manager=self.state_manager, process_request=self._process_agent_request
|
|
50
49
|
)
|
|
51
|
-
|
|
50
|
+
|
|
52
51
|
# Set the process_request callback for commands that need it
|
|
53
52
|
self.command_registry.set_process_request_callback(self._process_agent_request)
|
|
54
|
-
|
|
53
|
+
|
|
55
54
|
# Execute the command
|
|
56
55
|
result = await self.command_registry.execute(command, context)
|
|
57
|
-
|
|
56
|
+
|
|
58
57
|
if result == "restart":
|
|
59
58
|
return "Application restart requested."
|
|
60
59
|
elif result:
|
|
61
60
|
return str(result)
|
|
62
61
|
else:
|
|
63
62
|
return f"Command '{command}' executed successfully."
|
|
64
|
-
|
|
63
|
+
|
|
65
64
|
except ValidationError as e:
|
|
66
65
|
return f"Error: {str(e)}"
|
|
67
66
|
except Exception as e:
|
|
68
67
|
return f"Command error: {str(e)}"
|
|
69
|
-
|
|
68
|
+
|
|
70
69
|
async def _process_agent_request(self, text: str) -> str:
|
|
71
70
|
"""Process input using the agent."""
|
|
72
71
|
try:
|
|
73
72
|
# Expand @file references before sending to the agent
|
|
74
73
|
try:
|
|
75
74
|
from tunacode.utils.text_utils import expand_file_refs
|
|
75
|
+
|
|
76
76
|
text = expand_file_refs(text)
|
|
77
77
|
except ValueError as e:
|
|
78
78
|
return f"Error: {str(e)}"
|
|
79
|
-
|
|
79
|
+
|
|
80
80
|
# Notify UI about processing start
|
|
81
81
|
await self.message_callback("tool", "Processing request...")
|
|
82
|
-
|
|
82
|
+
|
|
83
83
|
# Create a tool callback that integrates with Textual
|
|
84
84
|
def tool_callback_with_state(part, node):
|
|
85
85
|
return self._tool_handler(part, node)
|
|
86
|
-
|
|
86
|
+
|
|
87
87
|
# Get or create agent instance
|
|
88
88
|
instance = agent.get_or_create_agent(
|
|
89
|
-
self.state_manager.session.current_model,
|
|
90
|
-
self.state_manager
|
|
89
|
+
self.state_manager.session.current_model, self.state_manager
|
|
91
90
|
)
|
|
92
|
-
|
|
91
|
+
|
|
93
92
|
# Process the request
|
|
94
93
|
async with instance.run_mcp_servers():
|
|
95
94
|
res = await agent.process_request(
|
|
@@ -98,12 +97,12 @@ class TextualAgentBridge:
|
|
|
98
97
|
self.state_manager,
|
|
99
98
|
tool_callback=tool_callback_with_state,
|
|
100
99
|
)
|
|
101
|
-
|
|
100
|
+
|
|
102
101
|
if res and res.result:
|
|
103
102
|
return res.result.output
|
|
104
103
|
else:
|
|
105
104
|
return "Request processed (no output)."
|
|
106
|
-
|
|
105
|
+
|
|
107
106
|
except CancelledError:
|
|
108
107
|
return "Request cancelled."
|
|
109
108
|
except UserAbortError:
|
|
@@ -117,29 +116,31 @@ class TextualAgentBridge:
|
|
|
117
116
|
agent_error = AgentError(f"Agent processing failed: {str(e)}")
|
|
118
117
|
agent_error.__cause__ = e
|
|
119
118
|
return f"Error: {str(e)}"
|
|
120
|
-
|
|
119
|
+
|
|
121
120
|
async def _tool_handler(self, part, node):
|
|
122
121
|
"""Handle tool execution with Textual UI integration."""
|
|
123
122
|
await self.message_callback("tool", f"Tool: {part.tool_name}")
|
|
124
|
-
|
|
123
|
+
|
|
125
124
|
try:
|
|
126
125
|
# Create tool handler with state
|
|
127
126
|
tool_handler = ToolHandler(self.state_manager)
|
|
128
127
|
args = _parse_args(part.args)
|
|
129
|
-
|
|
128
|
+
|
|
130
129
|
# Check if confirmation is needed
|
|
131
130
|
if tool_handler.should_confirm(part.tool_name):
|
|
132
131
|
# Create confirmation request
|
|
133
132
|
request = tool_handler.create_confirmation_request(part.tool_name, args)
|
|
134
|
-
|
|
133
|
+
|
|
135
134
|
# For now, show a simple confirmation in the UI
|
|
136
135
|
# In a full implementation, this would show a proper modal dialog
|
|
137
|
-
await self.message_callback(
|
|
138
|
-
|
|
136
|
+
await self.message_callback(
|
|
137
|
+
"system", f"Tool confirmation: {part.tool_name} with args: {args}"
|
|
138
|
+
)
|
|
139
|
+
|
|
139
140
|
# For demo purposes, auto-approve (in production, this should be interactive)
|
|
140
141
|
if not tool_handler.process_confirmation(True, part.tool_name):
|
|
141
142
|
raise UserAbortError("User aborted tool execution.")
|
|
142
|
-
|
|
143
|
+
|
|
143
144
|
except UserAbortError:
|
|
144
145
|
patch_tool_messages("Operation aborted by user.", self.state_manager)
|
|
145
146
|
raise
|
|
@@ -147,12 +148,12 @@ class TextualAgentBridge:
|
|
|
147
148
|
|
|
148
149
|
class TextualToolConfirmation:
|
|
149
150
|
"""Handle tool confirmations in Textual UI."""
|
|
150
|
-
|
|
151
|
+
|
|
151
152
|
def __init__(self, app_instance):
|
|
152
153
|
self.app = app_instance
|
|
153
|
-
|
|
154
|
+
|
|
154
155
|
async def show_confirmation(self, tool_name: str, args: dict) -> bool:
|
|
155
156
|
"""Show tool confirmation dialog and return user's choice."""
|
|
156
157
|
# This would show a modal dialog in the Textual app
|
|
157
158
|
# For now, return True (auto-approve)
|
|
158
|
-
return True
|
|
159
|
+
return True
|
|
@@ -7,15 +7,8 @@ Handles configuration paths, model registries, and application metadata.
|
|
|
7
7
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
|
-
from tunacode.constants import (
|
|
11
|
-
|
|
12
|
-
APP_VERSION,
|
|
13
|
-
CONFIG_FILE_NAME,
|
|
14
|
-
TOOL_READ_FILE,
|
|
15
|
-
TOOL_RUN_COMMAND,
|
|
16
|
-
TOOL_UPDATE_FILE,
|
|
17
|
-
TOOL_WRITE_FILE,
|
|
18
|
-
)
|
|
10
|
+
from tunacode.constants import (APP_NAME, APP_VERSION, CONFIG_FILE_NAME, TOOL_READ_FILE,
|
|
11
|
+
TOOL_RUN_COMMAND, TOOL_UPDATE_FILE, TOOL_WRITE_FILE)
|
|
19
12
|
from tunacode.types import ConfigFile, ConfigPath, ToolName
|
|
20
13
|
|
|
21
14
|
|
tunacode/constants.py
CHANGED
|
@@ -7,7 +7,7 @@ Centralizes all magic strings, UI text, error messages, and application constant
|
|
|
7
7
|
|
|
8
8
|
# Application info
|
|
9
9
|
APP_NAME = "TunaCode"
|
|
10
|
-
APP_VERSION = "0.0.
|
|
10
|
+
APP_VERSION = "0.0.18"
|
|
11
11
|
|
|
12
12
|
# File patterns
|
|
13
13
|
GUIDE_FILE_PATTERN = "{name}.md"
|
|
@@ -101,9 +101,7 @@ ERROR_INVALID_PROVIDER = "Invalid provider number"
|
|
|
101
101
|
ERROR_FILE_NOT_FOUND = "Error: File not found at '{filepath}'."
|
|
102
102
|
ERROR_FILE_TOO_LARGE = "Error: File '{filepath}' is too large (> 100KB)."
|
|
103
103
|
ERROR_FILE_DECODE = "Error reading file '{filepath}': Could not decode using UTF-8."
|
|
104
|
-
ERROR_FILE_DECODE_DETAILS =
|
|
105
|
-
"It might be a binary file or use a different encoding. {error}"
|
|
106
|
-
)
|
|
104
|
+
ERROR_FILE_DECODE_DETAILS = "It might be a binary file or use a different encoding. {error}"
|
|
107
105
|
ERROR_COMMAND_NOT_FOUND = "Error: Command not found or failed to execute:"
|
|
108
106
|
ERROR_COMMAND_EXECUTION = (
|
|
109
107
|
"Error: Command not found or failed to execute: {command}. Details: {error}"
|
tunacode/context.py
CHANGED
|
@@ -4,8 +4,8 @@ import subprocess
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Dict, List
|
|
6
6
|
|
|
7
|
-
from tunacode.utils.system import list_cwd
|
|
8
7
|
from tunacode.utils.ripgrep import ripgrep
|
|
8
|
+
from tunacode.utils.system import list_cwd
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
async def get_git_status() -> Dict[str, object]:
|