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 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
@@ -0,0 +1,8 @@
1
+ from .auth import authenticate
2
+
3
+
4
+ class UserManager:
5
+ def login(self, username, password):
6
+ token = authenticate(username, password)
7
+ # TODO: Store session
8
+ return token
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
- def __init__(self, spec: CommandSpec):
78
- self.spec = spec
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
- def __init__(self):
105
- super().__init__(
106
- CommandSpec(
107
- name="yolo",
108
- aliases=["/yolo"],
109
- description="Toggle YOLO mode (skip tool confirmations)",
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
- def __init__(self):
127
- super().__init__(
128
- CommandSpec(
129
- name="dump",
130
- aliases=["/dump"],
131
- description="Dump the current message history",
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
- def __init__(self):
144
- super().__init__(
145
- CommandSpec(
146
- name="thoughts",
147
- aliases=["/thoughts"],
148
- description="Show or hide agent thought messages",
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
- class ArchitectCommand(SimpleCommand):
171
- """Toggle architect mode for task planning and orchestration."""
172
-
173
- def __init__(self):
174
- super().__init__(
175
- CommandSpec(
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
- async def execute(self, args: List[str], context: CommandContext) -> None:
184
- state = context.state_manager.session
185
- if args:
186
- arg = args[0].lower()
187
- if arg in {"on", "1", "true"}:
188
- state.architect_mode = True
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.success(f"Architect mode {status} - Using direct execution")
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
- def __init__(self):
207
- super().__init__(
208
- CommandSpec(
209
- name="iterations",
210
- aliases=["/iterations"],
211
- description="Set maximum agent iterations for complex reasoning",
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
- def __init__(self):
244
- super().__init__(
245
- CommandSpec(
246
- name="clear",
247
- aliases=["/clear"],
248
- description="Clear the screen and message history",
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
- await ui.success("Message history cleared")
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
- def __init__(self):
268
- super().__init__(
269
- CommandSpec(
270
- name="fix",
271
- aliases=["/fix"],
272
- description="Fix orphaned tool calls causing API errors",
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
- def __init__(self):
301
- super().__init__(
302
- CommandSpec(
303
- name="parsetools",
304
- aliases=["/parsetools"],
305
- description=(
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
- def __init__(self):
352
- super().__init__(
353
- CommandSpec(
354
- name="refresh",
355
- aliases=["/refresh"],
356
- description="Refresh configuration from defaults (useful after updates)",
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
- def __init__(self):
461
- super().__init__(
462
- CommandSpec(
463
- name="branch",
464
- aliases=["/branch"],
465
- description="Create and switch to a new git branch",
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
- def __init__(self):
615
- super().__init__(
616
- CommandSpec(
617
- name="update",
618
- aliases=["/update"],
619
- description="Update TunaCode to the latest version",
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
- def __init__(self):
709
- super().__init__(
710
- CommandSpec(
711
- name="model",
712
- aliases=["/model"],
713
- description="Switch model (e.g., /model gpt-4 or /model openai:gpt-4)",
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
- else:
751
- # Show success message with the new model
752
- await ui.success(f"Switched to model: {model_name}")
753
- return None
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
- # Check if architect mode is enabled
175
- if getattr(state_manager.session, "architect_mode", False):
176
- # Expand @file references before sending to the orchestrator
177
- try:
178
- from tunacode.utils.text_utils import expand_file_refs
179
-
180
- text = expand_file_refs(text)
181
- except ValueError as e:
182
- await ui.error(str(e))
183
- return
184
- # Use orchestrator for planning and execution
185
- orchestrator = OrchestratorAgent(state_manager)
186
- results = await orchestrator.run(text, state_manager.session.current_model)
187
-
188
- if output:
189
- # Process results from all sub-agents
190
- for res in results:
191
- # Check if result exists and has output
192
- if (
193
- hasattr(res, "result")
194
- and res.result is not None
195
- and hasattr(res.result, "output")
196
- ):
197
- await ui.agent(res.result.output)
198
-
199
- if not results:
200
- # Fallback: show that the request was processed
201
- await ui.muted("Request completed")
202
- else:
203
- # Expand @file references before sending to the agent
204
- try:
205
- from tunacode.utils.text_utils import expand_file_refs
206
-
207
- text = expand_file_refs(text)
208
- except ValueError as e:
209
- await ui.error(str(e))
210
- return
211
-
212
- # Use normal agent processing
213
- res = await agent.process_request(
214
- state_manager.session.current_model,
215
- text,
216
- state_manager,
217
- tool_callback=tool_callback_with_state,
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("🔧 Recovered using JSON tool parsing")
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