tunacode-cli 0.0.30__py3-none-any.whl → 0.0.32__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 +113 -232
- tunacode/cli/repl.py +40 -84
- tunacode/constants.py +10 -1
- tunacode/core/agents/__init__.py +0 -4
- tunacode/core/agents/main.py +345 -43
- tunacode/core/code_index.py +479 -0
- tunacode/core/setup/git_safety_setup.py +7 -9
- tunacode/core/tool_handler.py +18 -0
- tunacode/exceptions.py +13 -0
- tunacode/prompts/system.md +237 -28
- 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_cli-0.0.30.dist-info → tunacode_cli-0.0.32.dist-info}/METADATA +16 -7
- {tunacode_cli-0.0.30.dist-info → tunacode_cli-0.0.32.dist-info}/RECORD +23 -21
- {tunacode_cli-0.0.30.dist-info → tunacode_cli-0.0.32.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.30.dist-info → tunacode_cli-0.0.32.dist-info}/WHEEL +0 -0
- {tunacode_cli-0.0.30.dist-info → tunacode_cli-0.0.32.dist-info}/entry_points.txt +0 -0
- {tunacode_cli-0.0.30.dist-info → tunacode_cli-0.0.32.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
|
|
@@ -265,15 +228,12 @@ class ClearCommand(SimpleCommand):
|
|
|
265
228
|
class FixCommand(SimpleCommand):
|
|
266
229
|
"""Fix orphaned tool calls that cause API errors."""
|
|
267
230
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
category=CommandCategory.DEBUG,
|
|
275
|
-
)
|
|
276
|
-
)
|
|
231
|
+
spec = CommandSpec(
|
|
232
|
+
name="fix",
|
|
233
|
+
aliases=["/fix"],
|
|
234
|
+
description="Fix orphaned tool calls causing API errors",
|
|
235
|
+
category=CommandCategory.DEBUG,
|
|
236
|
+
)
|
|
277
237
|
|
|
278
238
|
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
279
239
|
from tunacode.core.agents.main import patch_tool_messages
|
|
@@ -298,17 +258,12 @@ class FixCommand(SimpleCommand):
|
|
|
298
258
|
class ParseToolsCommand(SimpleCommand):
|
|
299
259
|
"""Parse and execute JSON tool calls from the last response."""
|
|
300
260
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
"Parse JSON tool calls from last response when structured calling fails"
|
|
308
|
-
),
|
|
309
|
-
category=CommandCategory.DEBUG,
|
|
310
|
-
)
|
|
311
|
-
)
|
|
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
|
+
)
|
|
312
267
|
|
|
313
268
|
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
314
269
|
from tunacode.core.agents.main import extract_and_execute_tool_calls
|
|
@@ -349,15 +304,12 @@ class ParseToolsCommand(SimpleCommand):
|
|
|
349
304
|
class RefreshConfigCommand(SimpleCommand):
|
|
350
305
|
"""Refresh configuration from defaults."""
|
|
351
306
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
category=CommandCategory.SYSTEM,
|
|
359
|
-
)
|
|
360
|
-
)
|
|
307
|
+
spec = CommandSpec(
|
|
308
|
+
name="refresh",
|
|
309
|
+
aliases=["/refresh"],
|
|
310
|
+
description="Refresh configuration from defaults (useful after updates)",
|
|
311
|
+
category=CommandCategory.SYSTEM,
|
|
312
|
+
)
|
|
361
313
|
|
|
362
314
|
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
363
315
|
from tunacode.configuration.defaults import DEFAULT_USER_CONFIG
|
|
@@ -379,76 +331,17 @@ class RefreshConfigCommand(SimpleCommand):
|
|
|
379
331
|
await ui.success(f"Configuration refreshed - max iterations: {max_iterations}")
|
|
380
332
|
|
|
381
333
|
|
|
382
|
-
class TunaCodeCommand(SimpleCommand):
|
|
383
|
-
"""Use BM25 to inspect the codebase and read relevant files."""
|
|
384
|
-
|
|
385
|
-
def __init__(self):
|
|
386
|
-
super().__init__(
|
|
387
|
-
CommandSpec(
|
|
388
|
-
name="tunaCode",
|
|
389
|
-
aliases=["/tunaCode"],
|
|
390
|
-
description="Scan repo with BM25 and display key files",
|
|
391
|
-
category=CommandCategory.DEVELOPMENT,
|
|
392
|
-
)
|
|
393
|
-
)
|
|
394
|
-
|
|
395
|
-
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
396
|
-
from pathlib import Path
|
|
397
|
-
|
|
398
|
-
from tunacode.constants import UI_COLORS
|
|
399
|
-
from tunacode.utils.file_utils import DotDict
|
|
400
|
-
|
|
401
|
-
from ..tools.read_file import read_file
|
|
402
|
-
from ..utils.bm25 import BM25, tokenize
|
|
403
|
-
from ..utils.text_utils import ext_to_lang
|
|
404
|
-
|
|
405
|
-
colors = DotDict(UI_COLORS)
|
|
406
|
-
|
|
407
|
-
query = " ".join(args) if args else "overview"
|
|
408
|
-
await ui.info("Building BM25 index of repository")
|
|
409
|
-
|
|
410
|
-
docs: List[str] = []
|
|
411
|
-
paths: List[Path] = []
|
|
412
|
-
exts = {".py", ".js", ".ts", ".java", ".c", ".cpp", ".md", ".txt"}
|
|
413
|
-
for path in Path(".").rglob("*"):
|
|
414
|
-
if path.is_file() and path.suffix in exts:
|
|
415
|
-
try:
|
|
416
|
-
docs.append(path.read_text(encoding="utf-8"))
|
|
417
|
-
paths.append(path)
|
|
418
|
-
except Exception:
|
|
419
|
-
continue
|
|
420
|
-
|
|
421
|
-
if not docs:
|
|
422
|
-
await ui.error("No files found to index")
|
|
423
|
-
return
|
|
424
|
-
|
|
425
|
-
bm25 = BM25(docs)
|
|
426
|
-
scores = bm25.get_scores(tokenize(query))
|
|
427
|
-
ranked = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)[:5]
|
|
428
|
-
|
|
429
|
-
for idx in ranked:
|
|
430
|
-
file_path = paths[idx]
|
|
431
|
-
content = await read_file(str(file_path))
|
|
432
|
-
lang = ext_to_lang(str(file_path))
|
|
433
|
-
await ui.panel(
|
|
434
|
-
str(file_path),
|
|
435
|
-
f"```{lang}\n{content}\n```",
|
|
436
|
-
border_style=colors.muted,
|
|
437
|
-
)
|
|
438
|
-
|
|
439
|
-
|
|
440
334
|
class HelpCommand(SimpleCommand):
|
|
441
335
|
"""Show help information."""
|
|
442
336
|
|
|
337
|
+
spec = CommandSpec(
|
|
338
|
+
name="help",
|
|
339
|
+
aliases=["/help"],
|
|
340
|
+
description="Show help information",
|
|
341
|
+
category=CommandCategory.SYSTEM,
|
|
342
|
+
)
|
|
343
|
+
|
|
443
344
|
def __init__(self, command_registry=None):
|
|
444
|
-
super().__init__(
|
|
445
|
-
CommandSpec(
|
|
446
|
-
name="help",
|
|
447
|
-
aliases=["/help"],
|
|
448
|
-
description="Show help information",
|
|
449
|
-
category=CommandCategory.SYSTEM,
|
|
450
|
-
)
|
|
451
|
-
)
|
|
452
345
|
self._command_registry = command_registry
|
|
453
346
|
|
|
454
347
|
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
@@ -458,15 +351,12 @@ class HelpCommand(SimpleCommand):
|
|
|
458
351
|
class BranchCommand(SimpleCommand):
|
|
459
352
|
"""Create and switch to a new git branch."""
|
|
460
353
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
category=CommandCategory.DEVELOPMENT,
|
|
468
|
-
)
|
|
469
|
-
)
|
|
354
|
+
spec = CommandSpec(
|
|
355
|
+
name="branch",
|
|
356
|
+
aliases=["/branch"],
|
|
357
|
+
description="Create and switch to a new git branch",
|
|
358
|
+
category=CommandCategory.DEVELOPMENT,
|
|
359
|
+
)
|
|
470
360
|
|
|
471
361
|
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
472
362
|
import os
|
|
@@ -503,15 +393,14 @@ class BranchCommand(SimpleCommand):
|
|
|
503
393
|
class CompactCommand(SimpleCommand):
|
|
504
394
|
"""Compact conversation context."""
|
|
505
395
|
|
|
396
|
+
spec = CommandSpec(
|
|
397
|
+
name="compact",
|
|
398
|
+
aliases=["/compact"],
|
|
399
|
+
description="Summarize and compact the conversation history",
|
|
400
|
+
category=CommandCategory.SYSTEM,
|
|
401
|
+
)
|
|
402
|
+
|
|
506
403
|
def __init__(self, process_request_callback: Optional[ProcessRequestCallback] = None):
|
|
507
|
-
super().__init__(
|
|
508
|
-
CommandSpec(
|
|
509
|
-
name="compact",
|
|
510
|
-
aliases=["/compact"],
|
|
511
|
-
description="Summarize and compact the conversation history",
|
|
512
|
-
category=CommandCategory.SYSTEM,
|
|
513
|
-
)
|
|
514
|
-
)
|
|
515
404
|
self._process_request = process_request_callback
|
|
516
405
|
|
|
517
406
|
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
@@ -612,15 +501,12 @@ class CompactCommand(SimpleCommand):
|
|
|
612
501
|
class UpdateCommand(SimpleCommand):
|
|
613
502
|
"""Update TunaCode to the latest version."""
|
|
614
503
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
category=CommandCategory.SYSTEM,
|
|
622
|
-
)
|
|
623
|
-
)
|
|
504
|
+
spec = CommandSpec(
|
|
505
|
+
name="update",
|
|
506
|
+
aliases=["/update"],
|
|
507
|
+
description="Update TunaCode to the latest version",
|
|
508
|
+
category=CommandCategory.SYSTEM,
|
|
509
|
+
)
|
|
624
510
|
|
|
625
511
|
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
626
512
|
import shutil
|
|
@@ -706,19 +592,16 @@ class UpdateCommand(SimpleCommand):
|
|
|
706
592
|
class ModelCommand(SimpleCommand):
|
|
707
593
|
"""Manage model selection."""
|
|
708
594
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
category=CommandCategory.MODEL,
|
|
716
|
-
)
|
|
717
|
-
)
|
|
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
|
+
)
|
|
718
601
|
|
|
719
602
|
async def execute(self, args: CommandArgs, context: CommandContext) -> Optional[str]:
|
|
603
|
+
# No arguments - show current model
|
|
720
604
|
if not args:
|
|
721
|
-
# No arguments - show current model
|
|
722
605
|
current_model = context.state_manager.session.current_model
|
|
723
606
|
await ui.info(f"Current model: {current_model}")
|
|
724
607
|
await ui.muted("Usage: /model <provider:model-name> [default]")
|
|
@@ -748,10 +631,10 @@ class ModelCommand(SimpleCommand):
|
|
|
748
631
|
utils.user_configuration.set_default_model(model_name, context.state_manager)
|
|
749
632
|
await ui.muted("Updating default model")
|
|
750
633
|
return "restart"
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
634
|
+
|
|
635
|
+
# Show success message with the new model
|
|
636
|
+
await ui.success(f"Switched to model: {model_name}")
|
|
637
|
+
return None
|
|
755
638
|
|
|
756
639
|
|
|
757
640
|
@dataclass
|
|
@@ -833,7 +716,6 @@ class CommandRegistry:
|
|
|
833
716
|
YoloCommand,
|
|
834
717
|
DumpCommand,
|
|
835
718
|
ThoughtsCommand,
|
|
836
|
-
ArchitectCommand,
|
|
837
719
|
IterationsCommand,
|
|
838
720
|
ClearCommand,
|
|
839
721
|
FixCommand,
|
|
@@ -842,7 +724,6 @@ class CommandRegistry:
|
|
|
842
724
|
UpdateCommand,
|
|
843
725
|
HelpCommand,
|
|
844
726
|
BranchCommand,
|
|
845
|
-
# TunaCodeCommand, # TODO: Temporarily disabled
|
|
846
727
|
CompactCommand,
|
|
847
728
|
ModelCommand,
|
|
848
729
|
]
|
tunacode/cli/repl.py
CHANGED
|
@@ -18,7 +18,6 @@ from pydantic_ai.exceptions import UnexpectedModelBehavior
|
|
|
18
18
|
from tunacode.configuration.settings import ApplicationSettings
|
|
19
19
|
from tunacode.core.agents import main as agent
|
|
20
20
|
from tunacode.core.agents.main import patch_tool_messages
|
|
21
|
-
from tunacode.core.agents.orchestrator import OrchestratorAgent
|
|
22
21
|
from tunacode.core.tool_handler import ToolHandler
|
|
23
22
|
from tunacode.exceptions import AgentError, UserAbortError, ValidationError
|
|
24
23
|
from tunacode.ui import console as ui
|
|
@@ -179,93 +178,50 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
|
|
|
179
178
|
def tool_callback_with_state(part, node):
|
|
180
179
|
return _tool_handler(part, node, state_manager)
|
|
181
180
|
|
|
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
|
-
# Fallback: show that the request was processed
|
|
212
|
-
await ui.muted("Request completed")
|
|
213
|
-
|
|
214
|
-
# Always show files in context after orchestrator response
|
|
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
|
|
215
210
|
if state_manager.session.files_in_context:
|
|
211
|
+
# Extract just filenames from full paths for readability
|
|
216
212
|
filenames = [
|
|
217
213
|
Path(f).name for f in sorted(state_manager.session.files_in_context)
|
|
218
214
|
]
|
|
219
215
|
await ui.muted(f"\nFiles in context: {', '.join(filenames)}")
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
except ValueError as e:
|
|
230
|
-
await ui.error(str(e))
|
|
231
|
-
return
|
|
232
|
-
|
|
233
|
-
# Use normal agent processing
|
|
234
|
-
res = await agent.process_request(
|
|
235
|
-
state_manager.session.current_model,
|
|
236
|
-
text,
|
|
237
|
-
state_manager,
|
|
238
|
-
tool_callback=tool_callback_with_state,
|
|
239
|
-
)
|
|
240
|
-
if output:
|
|
241
|
-
if state_manager.session.show_thoughts:
|
|
242
|
-
new_msgs = state_manager.session.messages[start_idx:]
|
|
243
|
-
for msg in new_msgs:
|
|
244
|
-
if isinstance(msg, dict) and "thought" in msg:
|
|
245
|
-
await ui.muted(f"THOUGHT: {msg['thought']}")
|
|
246
|
-
# Check if result exists and has output
|
|
247
|
-
if (
|
|
248
|
-
hasattr(res, "result")
|
|
249
|
-
and res.result is not None
|
|
250
|
-
and hasattr(res.result, "output")
|
|
251
|
-
):
|
|
252
|
-
await ui.agent(res.result.output)
|
|
253
|
-
# Always show files in context after agent response
|
|
254
|
-
if state_manager.session.files_in_context:
|
|
255
|
-
# Extract just filenames from full paths for readability
|
|
256
|
-
filenames = [
|
|
257
|
-
Path(f).name for f in sorted(state_manager.session.files_in_context)
|
|
258
|
-
]
|
|
259
|
-
await ui.muted(f"\nFiles in context: {', '.join(filenames)}")
|
|
260
|
-
else:
|
|
261
|
-
# Fallback: show that the request was processed
|
|
262
|
-
await ui.muted("Request completed")
|
|
263
|
-
# Show files in context even for empty responses
|
|
264
|
-
if state_manager.session.files_in_context:
|
|
265
|
-
filenames = [
|
|
266
|
-
Path(f).name for f in sorted(state_manager.session.files_in_context)
|
|
267
|
-
]
|
|
268
|
-
await ui.muted(f"Files 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)}")
|
|
269
225
|
except CancelledError:
|
|
270
226
|
await ui.muted("Request cancelled")
|
|
271
227
|
except UserAbortError:
|
|
@@ -294,7 +250,7 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
|
|
|
294
250
|
await extract_and_execute_tool_calls(
|
|
295
251
|
part.content, tool_callback_with_state, state_manager
|
|
296
252
|
)
|
|
297
|
-
await ui.warning("
|
|
253
|
+
await ui.warning(" Recovered using JSON tool parsing")
|
|
298
254
|
return # Successfully recovered
|
|
299
255
|
except Exception:
|
|
300
256
|
pass # Fallback failed, continue with normal error handling
|