tunacode-cli 0.0.29__py3-none-any.whl → 0.0.31__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.
- api/auth.py +13 -0
- api/users.py +8 -0
- tunacode/cli/commands.py +115 -233
- tunacode/cli/repl.py +53 -63
- tunacode/cli/textual_bridge.py +4 -1
- tunacode/constants.py +10 -1
- tunacode/core/agents/__init__.py +0 -4
- tunacode/core/agents/main.py +454 -49
- tunacode/core/code_index.py +479 -0
- tunacode/core/setup/git_safety_setup.py +7 -9
- tunacode/core/state.py +5 -0
- tunacode/core/tool_handler.py +18 -0
- tunacode/exceptions.py +13 -0
- tunacode/prompts/system.md +269 -30
- tunacode/tools/glob.py +288 -0
- tunacode/tools/grep.py +168 -195
- tunacode/tools/list_dir.py +190 -0
- tunacode/tools/read_file.py +9 -3
- tunacode/tools/read_file_async_poc.py +188 -0
- tunacode/utils/text_utils.py +14 -5
- tunacode/utils/token_counter.py +23 -0
- {tunacode_cli-0.0.29.dist-info → tunacode_cli-0.0.31.dist-info}/METADATA +16 -7
- {tunacode_cli-0.0.29.dist-info → tunacode_cli-0.0.31.dist-info}/RECORD +27 -24
- {tunacode_cli-0.0.29.dist-info → tunacode_cli-0.0.31.dist-info}/top_level.txt +1 -0
- tunacode/core/agents/orchestrator.py +0 -213
- tunacode/core/agents/planner_schema.py +0 -9
- tunacode/core/agents/readonly.py +0 -65
- tunacode/core/llm/planner.py +0 -62
- {tunacode_cli-0.0.29.dist-info → tunacode_cli-0.0.31.dist-info}/WHEEL +0 -0
- {tunacode_cli-0.0.29.dist-info → tunacode_cli-0.0.31.dist-info}/entry_points.txt +0 -0
- {tunacode_cli-0.0.29.dist-info → tunacode_cli-0.0.31.dist-info}/licenses/LICENSE +0 -0
api/auth.py
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import jwt
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def authenticate(username, password):
|
|
5
|
+
# TODO: Add password hashing
|
|
6
|
+
if username == "admin" and password == "admin":
|
|
7
|
+
return generate_token(username)
|
|
8
|
+
return None
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def generate_token(username):
|
|
12
|
+
# TODO: Add expiration
|
|
13
|
+
return jwt.encode({"user": username}, "secret")
|
api/users.py
ADDED
tunacode/cli/commands.py
CHANGED
|
@@ -72,44 +72,45 @@ class CommandSpec:
|
|
|
72
72
|
|
|
73
73
|
|
|
74
74
|
class SimpleCommand(Command):
|
|
75
|
-
"""Base class for simple commands without complex logic.
|
|
75
|
+
"""Base class for simple commands without complex logic.
|
|
76
76
|
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
This class provides a standard implementation for commands that don't
|
|
78
|
+
require special initialization or complex behavior. It reads all
|
|
79
|
+
properties from a class-level CommandSpec attribute.
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
spec: CommandSpec
|
|
79
83
|
|
|
80
84
|
@property
|
|
81
85
|
def name(self) -> str:
|
|
82
86
|
"""The primary name of the command."""
|
|
83
|
-
return self.spec.name
|
|
87
|
+
return self.__class__.spec.name
|
|
84
88
|
|
|
85
89
|
@property
|
|
86
90
|
def aliases(self) -> CommandArgs:
|
|
87
91
|
"""Alternative names/aliases for the command."""
|
|
88
|
-
return self.spec.aliases
|
|
92
|
+
return self.__class__.spec.aliases
|
|
89
93
|
|
|
90
94
|
@property
|
|
91
95
|
def description(self) -> str:
|
|
92
96
|
"""Description of what the command does."""
|
|
93
|
-
return self.spec.description
|
|
97
|
+
return self.__class__.spec.description
|
|
94
98
|
|
|
95
99
|
@property
|
|
96
100
|
def category(self) -> CommandCategory:
|
|
97
101
|
"""Category this command belongs to."""
|
|
98
|
-
return self.spec.category
|
|
102
|
+
return self.__class__.spec.category
|
|
99
103
|
|
|
100
104
|
|
|
101
105
|
class YoloCommand(SimpleCommand):
|
|
102
106
|
"""Toggle YOLO mode (skip confirmations)."""
|
|
103
107
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
category=CommandCategory.DEVELOPMENT,
|
|
111
|
-
)
|
|
112
|
-
)
|
|
108
|
+
spec = CommandSpec(
|
|
109
|
+
name="yolo",
|
|
110
|
+
aliases=["/yolo"],
|
|
111
|
+
description="Toggle YOLO mode (skip tool confirmations)",
|
|
112
|
+
category=CommandCategory.DEVELOPMENT,
|
|
113
|
+
)
|
|
113
114
|
|
|
114
115
|
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
115
116
|
state = context.state_manager.session
|
|
@@ -123,15 +124,12 @@ class YoloCommand(SimpleCommand):
|
|
|
123
124
|
class DumpCommand(SimpleCommand):
|
|
124
125
|
"""Dump message history."""
|
|
125
126
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
category=CommandCategory.DEBUG,
|
|
133
|
-
)
|
|
134
|
-
)
|
|
127
|
+
spec = CommandSpec(
|
|
128
|
+
name="dump",
|
|
129
|
+
aliases=["/dump"],
|
|
130
|
+
description="Dump the current message history",
|
|
131
|
+
category=CommandCategory.DEBUG,
|
|
132
|
+
)
|
|
135
133
|
|
|
136
134
|
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
137
135
|
await ui.dump_messages(context.state_manager.session.messages)
|
|
@@ -140,78 +138,46 @@ class DumpCommand(SimpleCommand):
|
|
|
140
138
|
class ThoughtsCommand(SimpleCommand):
|
|
141
139
|
"""Toggle display of agent thoughts."""
|
|
142
140
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
category=CommandCategory.DEBUG,
|
|
150
|
-
)
|
|
151
|
-
)
|
|
141
|
+
spec = CommandSpec(
|
|
142
|
+
name="thoughts",
|
|
143
|
+
aliases=["/thoughts"],
|
|
144
|
+
description="Show or hide agent thought messages",
|
|
145
|
+
category=CommandCategory.DEBUG,
|
|
146
|
+
)
|
|
152
147
|
|
|
153
148
|
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
154
149
|
state = context.state_manager.session
|
|
155
|
-
if args:
|
|
156
|
-
arg = args[0].lower()
|
|
157
|
-
if arg in {"on", "1", "true"}:
|
|
158
|
-
state.show_thoughts = True
|
|
159
|
-
elif arg in {"off", "0", "false"}:
|
|
160
|
-
state.show_thoughts = False
|
|
161
|
-
else:
|
|
162
|
-
await ui.error("Usage: /thoughts [on|off]")
|
|
163
|
-
return
|
|
164
|
-
else:
|
|
165
|
-
state.show_thoughts = not state.show_thoughts
|
|
166
|
-
status = "ON" if state.show_thoughts else "OFF"
|
|
167
|
-
await ui.success(f"Thought display {status}")
|
|
168
|
-
|
|
169
150
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
name="architect",
|
|
177
|
-
aliases=["/architect"],
|
|
178
|
-
description="Toggle architect mode (task planning & orchestration)",
|
|
179
|
-
category=CommandCategory.DEBUG,
|
|
180
|
-
)
|
|
181
|
-
)
|
|
151
|
+
# No args - toggle
|
|
152
|
+
if not args:
|
|
153
|
+
state.show_thoughts = not state.show_thoughts
|
|
154
|
+
status = "ON" if state.show_thoughts else "OFF"
|
|
155
|
+
await ui.success(f"Thought display {status}")
|
|
156
|
+
return
|
|
182
157
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
elif arg in {"off", "0", "false"}:
|
|
190
|
-
state.architect_mode = False
|
|
191
|
-
else:
|
|
192
|
-
await ui.error("Usage: /architect [on|off]")
|
|
193
|
-
return
|
|
194
|
-
else:
|
|
195
|
-
state.architect_mode = not getattr(state, "architect_mode", False)
|
|
196
|
-
status = "ON" if state.architect_mode else "OFF"
|
|
197
|
-
if state.architect_mode:
|
|
198
|
-
await ui.success(f"Architect mode {status} - Requests will be planned before execution")
|
|
158
|
+
# Parse argument
|
|
159
|
+
arg = args[0].lower()
|
|
160
|
+
if arg in {"on", "1", "true"}:
|
|
161
|
+
state.show_thoughts = True
|
|
162
|
+
elif arg in {"off", "0", "false"}:
|
|
163
|
+
state.show_thoughts = False
|
|
199
164
|
else:
|
|
200
|
-
await ui.
|
|
165
|
+
await ui.error("Usage: /thoughts [on|off]")
|
|
166
|
+
return
|
|
167
|
+
|
|
168
|
+
status = "ON" if state.show_thoughts else "OFF"
|
|
169
|
+
await ui.success(f"Thought display {status}")
|
|
201
170
|
|
|
202
171
|
|
|
203
172
|
class IterationsCommand(SimpleCommand):
|
|
204
173
|
"""Configure maximum agent iterations for ReAct reasoning."""
|
|
205
174
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
category=CommandCategory.DEBUG,
|
|
213
|
-
)
|
|
214
|
-
)
|
|
175
|
+
spec = CommandSpec(
|
|
176
|
+
name="iterations",
|
|
177
|
+
aliases=["/iterations"],
|
|
178
|
+
description="Set maximum agent iterations for complex reasoning",
|
|
179
|
+
category=CommandCategory.DEBUG,
|
|
180
|
+
)
|
|
215
181
|
|
|
216
182
|
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
217
183
|
state = context.state_manager.session
|
|
@@ -240,15 +206,12 @@ class IterationsCommand(SimpleCommand):
|
|
|
240
206
|
class ClearCommand(SimpleCommand):
|
|
241
207
|
"""Clear screen and message history."""
|
|
242
208
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
category=CommandCategory.NAVIGATION,
|
|
250
|
-
)
|
|
251
|
-
)
|
|
209
|
+
spec = CommandSpec(
|
|
210
|
+
name="clear",
|
|
211
|
+
aliases=["/clear"],
|
|
212
|
+
description="Clear the screen and message history",
|
|
213
|
+
category=CommandCategory.NAVIGATION,
|
|
214
|
+
)
|
|
252
215
|
|
|
253
216
|
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
254
217
|
# Patch any orphaned tool calls before clearing
|
|
@@ -258,21 +221,19 @@ class ClearCommand(SimpleCommand):
|
|
|
258
221
|
|
|
259
222
|
await ui.clear()
|
|
260
223
|
context.state_manager.session.messages = []
|
|
261
|
-
|
|
224
|
+
context.state_manager.session.files_in_context.clear()
|
|
225
|
+
await ui.success("Message history and file context cleared")
|
|
262
226
|
|
|
263
227
|
|
|
264
228
|
class FixCommand(SimpleCommand):
|
|
265
229
|
"""Fix orphaned tool calls that cause API errors."""
|
|
266
230
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
category=CommandCategory.DEBUG,
|
|
274
|
-
)
|
|
275
|
-
)
|
|
231
|
+
spec = CommandSpec(
|
|
232
|
+
name="fix",
|
|
233
|
+
aliases=["/fix"],
|
|
234
|
+
description="Fix orphaned tool calls causing API errors",
|
|
235
|
+
category=CommandCategory.DEBUG,
|
|
236
|
+
)
|
|
276
237
|
|
|
277
238
|
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
278
239
|
from tunacode.core.agents.main import patch_tool_messages
|
|
@@ -297,17 +258,12 @@ class FixCommand(SimpleCommand):
|
|
|
297
258
|
class ParseToolsCommand(SimpleCommand):
|
|
298
259
|
"""Parse and execute JSON tool calls from the last response."""
|
|
299
260
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
"Parse JSON tool calls from last response when structured calling fails"
|
|
307
|
-
),
|
|
308
|
-
category=CommandCategory.DEBUG,
|
|
309
|
-
)
|
|
310
|
-
)
|
|
261
|
+
spec = CommandSpec(
|
|
262
|
+
name="parsetools",
|
|
263
|
+
aliases=["/parsetools"],
|
|
264
|
+
description=("Parse JSON tool calls from last response when structured calling fails"),
|
|
265
|
+
category=CommandCategory.DEBUG,
|
|
266
|
+
)
|
|
311
267
|
|
|
312
268
|
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
313
269
|
from tunacode.core.agents.main import extract_and_execute_tool_calls
|
|
@@ -348,15 +304,12 @@ class ParseToolsCommand(SimpleCommand):
|
|
|
348
304
|
class RefreshConfigCommand(SimpleCommand):
|
|
349
305
|
"""Refresh configuration from defaults."""
|
|
350
306
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
category=CommandCategory.SYSTEM,
|
|
358
|
-
)
|
|
359
|
-
)
|
|
307
|
+
spec = CommandSpec(
|
|
308
|
+
name="refresh",
|
|
309
|
+
aliases=["/refresh"],
|
|
310
|
+
description="Refresh configuration from defaults (useful after updates)",
|
|
311
|
+
category=CommandCategory.SYSTEM,
|
|
312
|
+
)
|
|
360
313
|
|
|
361
314
|
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
362
315
|
from tunacode.configuration.defaults import DEFAULT_USER_CONFIG
|
|
@@ -378,76 +331,17 @@ class RefreshConfigCommand(SimpleCommand):
|
|
|
378
331
|
await ui.success(f"Configuration refreshed - max iterations: {max_iterations}")
|
|
379
332
|
|
|
380
333
|
|
|
381
|
-
class TunaCodeCommand(SimpleCommand):
|
|
382
|
-
"""Use BM25 to inspect the codebase and read relevant files."""
|
|
383
|
-
|
|
384
|
-
def __init__(self):
|
|
385
|
-
super().__init__(
|
|
386
|
-
CommandSpec(
|
|
387
|
-
name="tunaCode",
|
|
388
|
-
aliases=["/tunaCode"],
|
|
389
|
-
description="Scan repo with BM25 and display key files",
|
|
390
|
-
category=CommandCategory.DEVELOPMENT,
|
|
391
|
-
)
|
|
392
|
-
)
|
|
393
|
-
|
|
394
|
-
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
395
|
-
from pathlib import Path
|
|
396
|
-
|
|
397
|
-
from tunacode.constants import UI_COLORS
|
|
398
|
-
from tunacode.utils.file_utils import DotDict
|
|
399
|
-
|
|
400
|
-
from ..tools.read_file import read_file
|
|
401
|
-
from ..utils.bm25 import BM25, tokenize
|
|
402
|
-
from ..utils.text_utils import ext_to_lang
|
|
403
|
-
|
|
404
|
-
colors = DotDict(UI_COLORS)
|
|
405
|
-
|
|
406
|
-
query = " ".join(args) if args else "overview"
|
|
407
|
-
await ui.info("Building BM25 index of repository")
|
|
408
|
-
|
|
409
|
-
docs: List[str] = []
|
|
410
|
-
paths: List[Path] = []
|
|
411
|
-
exts = {".py", ".js", ".ts", ".java", ".c", ".cpp", ".md", ".txt"}
|
|
412
|
-
for path in Path(".").rglob("*"):
|
|
413
|
-
if path.is_file() and path.suffix in exts:
|
|
414
|
-
try:
|
|
415
|
-
docs.append(path.read_text(encoding="utf-8"))
|
|
416
|
-
paths.append(path)
|
|
417
|
-
except Exception:
|
|
418
|
-
continue
|
|
419
|
-
|
|
420
|
-
if not docs:
|
|
421
|
-
await ui.error("No files found to index")
|
|
422
|
-
return
|
|
423
|
-
|
|
424
|
-
bm25 = BM25(docs)
|
|
425
|
-
scores = bm25.get_scores(tokenize(query))
|
|
426
|
-
ranked = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)[:5]
|
|
427
|
-
|
|
428
|
-
for idx in ranked:
|
|
429
|
-
file_path = paths[idx]
|
|
430
|
-
content = await read_file(str(file_path))
|
|
431
|
-
lang = ext_to_lang(str(file_path))
|
|
432
|
-
await ui.panel(
|
|
433
|
-
str(file_path),
|
|
434
|
-
f"```{lang}\n{content}\n```",
|
|
435
|
-
border_style=colors.muted,
|
|
436
|
-
)
|
|
437
|
-
|
|
438
|
-
|
|
439
334
|
class HelpCommand(SimpleCommand):
|
|
440
335
|
"""Show help information."""
|
|
441
336
|
|
|
337
|
+
spec = CommandSpec(
|
|
338
|
+
name="help",
|
|
339
|
+
aliases=["/help"],
|
|
340
|
+
description="Show help information",
|
|
341
|
+
category=CommandCategory.SYSTEM,
|
|
342
|
+
)
|
|
343
|
+
|
|
442
344
|
def __init__(self, command_registry=None):
|
|
443
|
-
super().__init__(
|
|
444
|
-
CommandSpec(
|
|
445
|
-
name="help",
|
|
446
|
-
aliases=["/help"],
|
|
447
|
-
description="Show help information",
|
|
448
|
-
category=CommandCategory.SYSTEM,
|
|
449
|
-
)
|
|
450
|
-
)
|
|
451
345
|
self._command_registry = command_registry
|
|
452
346
|
|
|
453
347
|
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
@@ -457,15 +351,12 @@ class HelpCommand(SimpleCommand):
|
|
|
457
351
|
class BranchCommand(SimpleCommand):
|
|
458
352
|
"""Create and switch to a new git branch."""
|
|
459
353
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
category=CommandCategory.DEVELOPMENT,
|
|
467
|
-
)
|
|
468
|
-
)
|
|
354
|
+
spec = CommandSpec(
|
|
355
|
+
name="branch",
|
|
356
|
+
aliases=["/branch"],
|
|
357
|
+
description="Create and switch to a new git branch",
|
|
358
|
+
category=CommandCategory.DEVELOPMENT,
|
|
359
|
+
)
|
|
469
360
|
|
|
470
361
|
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
471
362
|
import os
|
|
@@ -502,15 +393,14 @@ class BranchCommand(SimpleCommand):
|
|
|
502
393
|
class CompactCommand(SimpleCommand):
|
|
503
394
|
"""Compact conversation context."""
|
|
504
395
|
|
|
396
|
+
spec = CommandSpec(
|
|
397
|
+
name="compact",
|
|
398
|
+
aliases=["/compact"],
|
|
399
|
+
description="Summarize and compact the conversation history",
|
|
400
|
+
category=CommandCategory.SYSTEM,
|
|
401
|
+
)
|
|
402
|
+
|
|
505
403
|
def __init__(self, process_request_callback: Optional[ProcessRequestCallback] = None):
|
|
506
|
-
super().__init__(
|
|
507
|
-
CommandSpec(
|
|
508
|
-
name="compact",
|
|
509
|
-
aliases=["/compact"],
|
|
510
|
-
description="Summarize and compact the conversation history",
|
|
511
|
-
category=CommandCategory.SYSTEM,
|
|
512
|
-
)
|
|
513
|
-
)
|
|
514
404
|
self._process_request = process_request_callback
|
|
515
405
|
|
|
516
406
|
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
@@ -611,15 +501,12 @@ class CompactCommand(SimpleCommand):
|
|
|
611
501
|
class UpdateCommand(SimpleCommand):
|
|
612
502
|
"""Update TunaCode to the latest version."""
|
|
613
503
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
category=CommandCategory.SYSTEM,
|
|
621
|
-
)
|
|
622
|
-
)
|
|
504
|
+
spec = CommandSpec(
|
|
505
|
+
name="update",
|
|
506
|
+
aliases=["/update"],
|
|
507
|
+
description="Update TunaCode to the latest version",
|
|
508
|
+
category=CommandCategory.SYSTEM,
|
|
509
|
+
)
|
|
623
510
|
|
|
624
511
|
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
625
512
|
import shutil
|
|
@@ -705,19 +592,16 @@ class UpdateCommand(SimpleCommand):
|
|
|
705
592
|
class ModelCommand(SimpleCommand):
|
|
706
593
|
"""Manage model selection."""
|
|
707
594
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
category=CommandCategory.MODEL,
|
|
715
|
-
)
|
|
716
|
-
)
|
|
595
|
+
spec = CommandSpec(
|
|
596
|
+
name="model",
|
|
597
|
+
aliases=["/model"],
|
|
598
|
+
description="Switch model (e.g., /model gpt-4 or /model openai:gpt-4)",
|
|
599
|
+
category=CommandCategory.MODEL,
|
|
600
|
+
)
|
|
717
601
|
|
|
718
602
|
async def execute(self, args: CommandArgs, context: CommandContext) -> Optional[str]:
|
|
603
|
+
# No arguments - show current model
|
|
719
604
|
if not args:
|
|
720
|
-
# No arguments - show current model
|
|
721
605
|
current_model = context.state_manager.session.current_model
|
|
722
606
|
await ui.info(f"Current model: {current_model}")
|
|
723
607
|
await ui.muted("Usage: /model <provider:model-name> [default]")
|
|
@@ -747,10 +631,10 @@ class ModelCommand(SimpleCommand):
|
|
|
747
631
|
utils.user_configuration.set_default_model(model_name, context.state_manager)
|
|
748
632
|
await ui.muted("Updating default model")
|
|
749
633
|
return "restart"
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
634
|
+
|
|
635
|
+
# Show success message with the new model
|
|
636
|
+
await ui.success(f"Switched to model: {model_name}")
|
|
637
|
+
return None
|
|
754
638
|
|
|
755
639
|
|
|
756
640
|
@dataclass
|
|
@@ -832,7 +716,6 @@ class CommandRegistry:
|
|
|
832
716
|
YoloCommand,
|
|
833
717
|
DumpCommand,
|
|
834
718
|
ThoughtsCommand,
|
|
835
|
-
ArchitectCommand,
|
|
836
719
|
IterationsCommand,
|
|
837
720
|
ClearCommand,
|
|
838
721
|
FixCommand,
|
|
@@ -841,7 +724,6 @@ class CommandRegistry:
|
|
|
841
724
|
UpdateCommand,
|
|
842
725
|
HelpCommand,
|
|
843
726
|
BranchCommand,
|
|
844
|
-
# TunaCodeCommand, # TODO: Temporarily disabled
|
|
845
727
|
CompactCommand,
|
|
846
728
|
ModelCommand,
|
|
847
729
|
]
|
tunacode/cli/repl.py
CHANGED
|
@@ -9,6 +9,7 @@ import json
|
|
|
9
9
|
import os
|
|
10
10
|
import subprocess
|
|
11
11
|
from asyncio.exceptions import CancelledError
|
|
12
|
+
from pathlib import Path
|
|
12
13
|
|
|
13
14
|
from prompt_toolkit.application import run_in_terminal
|
|
14
15
|
from prompt_toolkit.application.current import get_app
|
|
@@ -17,7 +18,6 @@ from pydantic_ai.exceptions import UnexpectedModelBehavior
|
|
|
17
18
|
from tunacode.configuration.settings import ApplicationSettings
|
|
18
19
|
from tunacode.core.agents import main as agent
|
|
19
20
|
from tunacode.core.agents.main import patch_tool_messages
|
|
20
|
-
from tunacode.core.agents.orchestrator import OrchestratorAgent
|
|
21
21
|
from tunacode.core.tool_handler import ToolHandler
|
|
22
22
|
from tunacode.exceptions import AgentError, UserAbortError, ValidationError
|
|
23
23
|
from tunacode.ui import console as ui
|
|
@@ -164,6 +164,13 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
|
|
|
164
164
|
# Patch any orphaned tool calls from previous requests before proceeding
|
|
165
165
|
patch_tool_messages("Tool execution was interrupted", state_manager)
|
|
166
166
|
|
|
167
|
+
# Clear tracking for new request when thoughts are enabled
|
|
168
|
+
if state_manager.session.show_thoughts:
|
|
169
|
+
state_manager.session.tool_calls = []
|
|
170
|
+
# Don't clear files_in_context - keep it cumulative for the session
|
|
171
|
+
state_manager.session.iteration_count = 0
|
|
172
|
+
state_manager.session.current_iteration = 0
|
|
173
|
+
|
|
167
174
|
# Track message start for thoughts display
|
|
168
175
|
start_idx = len(state_manager.session.messages)
|
|
169
176
|
|
|
@@ -171,67 +178,50 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
|
|
|
171
178
|
def tool_callback_with_state(part, node):
|
|
172
179
|
return _tool_handler(part, node, state_manager)
|
|
173
180
|
|
|
174
|
-
#
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
)
|
|
219
|
-
if output:
|
|
220
|
-
if state_manager.session.show_thoughts:
|
|
221
|
-
new_msgs = state_manager.session.messages[start_idx:]
|
|
222
|
-
for msg in new_msgs:
|
|
223
|
-
if isinstance(msg, dict) and "thought" in msg:
|
|
224
|
-
await ui.muted(f"THOUGHT: {msg['thought']}")
|
|
225
|
-
# Check if result exists and has output
|
|
226
|
-
if (
|
|
227
|
-
hasattr(res, "result")
|
|
228
|
-
and res.result is not None
|
|
229
|
-
and hasattr(res.result, "output")
|
|
230
|
-
):
|
|
231
|
-
await ui.agent(res.result.output)
|
|
232
|
-
else:
|
|
233
|
-
# Fallback: show that the request was processed
|
|
234
|
-
await ui.muted("Request completed")
|
|
181
|
+
# Expand @file references before sending to the agent
|
|
182
|
+
try:
|
|
183
|
+
from tunacode.utils.text_utils import expand_file_refs
|
|
184
|
+
|
|
185
|
+
text, referenced_files = expand_file_refs(text)
|
|
186
|
+
# Track the referenced files
|
|
187
|
+
for file_path in referenced_files:
|
|
188
|
+
state_manager.session.files_in_context.add(file_path)
|
|
189
|
+
except ValueError as e:
|
|
190
|
+
await ui.error(str(e))
|
|
191
|
+
return
|
|
192
|
+
|
|
193
|
+
# Use normal agent processing
|
|
194
|
+
res = await agent.process_request(
|
|
195
|
+
state_manager.session.current_model,
|
|
196
|
+
text,
|
|
197
|
+
state_manager,
|
|
198
|
+
tool_callback=tool_callback_with_state,
|
|
199
|
+
)
|
|
200
|
+
if output:
|
|
201
|
+
if state_manager.session.show_thoughts:
|
|
202
|
+
new_msgs = state_manager.session.messages[start_idx:]
|
|
203
|
+
for msg in new_msgs:
|
|
204
|
+
if isinstance(msg, dict) and "thought" in msg:
|
|
205
|
+
await ui.muted(f"THOUGHT: {msg['thought']}")
|
|
206
|
+
# Check if result exists and has output
|
|
207
|
+
if hasattr(res, "result") and res.result is not None and hasattr(res.result, "output"):
|
|
208
|
+
await ui.agent(res.result.output)
|
|
209
|
+
# Always show files in context after agent response
|
|
210
|
+
if state_manager.session.files_in_context:
|
|
211
|
+
# Extract just filenames from full paths for readability
|
|
212
|
+
filenames = [
|
|
213
|
+
Path(f).name for f in sorted(state_manager.session.files_in_context)
|
|
214
|
+
]
|
|
215
|
+
await ui.muted(f"\nFiles in context: {', '.join(filenames)}")
|
|
216
|
+
else:
|
|
217
|
+
# Fallback: show that the request was processed
|
|
218
|
+
await ui.muted("Request completed")
|
|
219
|
+
# Show files in context even for empty responses
|
|
220
|
+
if state_manager.session.files_in_context:
|
|
221
|
+
filenames = [
|
|
222
|
+
Path(f).name for f in sorted(state_manager.session.files_in_context)
|
|
223
|
+
]
|
|
224
|
+
await ui.muted(f"Files in context: {', '.join(filenames)}")
|
|
235
225
|
except CancelledError:
|
|
236
226
|
await ui.muted("Request cancelled")
|
|
237
227
|
except UserAbortError:
|
|
@@ -260,7 +250,7 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
|
|
|
260
250
|
await extract_and_execute_tool_calls(
|
|
261
251
|
part.content, tool_callback_with_state, state_manager
|
|
262
252
|
)
|
|
263
|
-
await ui.warning("
|
|
253
|
+
await ui.warning(" Recovered using JSON tool parsing")
|
|
264
254
|
return # Successfully recovered
|
|
265
255
|
except Exception:
|
|
266
256
|
pass # Fallback failed, continue with normal error handling
|