tunacode-cli 0.0.53__py3-none-any.whl → 0.0.55__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.

@@ -2,39 +2,9 @@
2
2
 
3
3
  Main agent functionality and coordination for the TunaCode CLI.
4
4
  Handles agent creation, configuration, and request processing.
5
- """
6
-
7
- # Re-export for backward compatibility
8
- from tunacode.services.mcp import get_mcp_servers
9
5
 
10
- from .agent_components import (
11
- ToolBuffer,
12
- check_task_completion,
13
- extract_and_execute_tool_calls,
14
- get_model_messages,
15
- parse_json_tool_calls,
16
- patch_tool_messages,
17
- )
18
-
19
- __all__ = [
20
- "ToolBuffer",
21
- "check_task_completion",
22
- "extract_and_execute_tool_calls",
23
- "get_model_messages",
24
- "parse_json_tool_calls",
25
- "patch_tool_messages",
26
- "get_mcp_servers",
27
- "check_query_satisfaction",
28
- "process_request",
29
- "get_or_create_agent",
30
- "_process_node",
31
- "ResponseState",
32
- "SimpleResult",
33
- "AgentRunWrapper",
34
- "AgentRunWithState",
35
- "execute_tools_parallel",
36
- "get_agent_tool",
37
- ]
6
+ CLAUDE_ANCHOR[main-agent-module]: Primary agent orchestration and lifecycle management
7
+ """
38
8
 
39
9
  from typing import TYPE_CHECKING, Awaitable, Callable, Optional
40
10
 
@@ -44,6 +14,16 @@ if TYPE_CHECKING:
44
14
  from pydantic_ai import Tool # noqa: F401
45
15
 
46
16
  from tunacode.core.logging.logger import get_logger
17
+ from tunacode.core.state import StateManager
18
+ from tunacode.exceptions import ToolBatchingJSONError, UserAbortError
19
+ from tunacode.services.mcp import get_mcp_servers
20
+ from tunacode.types import (
21
+ AgentRun,
22
+ ModelName,
23
+ ToolCallback,
24
+ UsageTrackerProtocol,
25
+ )
26
+ from tunacode.ui.tool_descriptions import get_batch_description
47
27
 
48
28
  # Import agent components
49
29
  from .agent_components import (
@@ -51,9 +31,22 @@ from .agent_components import (
51
31
  AgentRunWrapper,
52
32
  ResponseState,
53
33
  SimpleResult,
34
+ ToolBuffer,
54
35
  _process_node,
36
+ check_task_completion,
37
+ create_empty_response_message,
38
+ create_fallback_response,
39
+ create_progress_summary,
40
+ create_user_message,
55
41
  execute_tools_parallel,
42
+ extract_and_execute_tool_calls,
43
+ format_fallback_output,
44
+ get_model_messages,
56
45
  get_or_create_agent,
46
+ get_recent_tools_context,
47
+ get_tool_summary,
48
+ parse_json_tool_calls,
49
+ patch_tool_messages,
57
50
  )
58
51
 
59
52
  # Import streaming types with fallback for older versions
@@ -62,63 +55,32 @@ try:
62
55
 
63
56
  STREAMING_AVAILABLE = True
64
57
  except ImportError:
65
- # Fallback for older pydantic-ai versions
66
58
  PartDeltaEvent = None
67
59
  TextPartDelta = None
68
60
  STREAMING_AVAILABLE = False
69
61
 
70
- from tunacode.core.state import StateManager
71
- from tunacode.exceptions import ToolBatchingJSONError, UserAbortError
72
- from tunacode.types import (
73
- AgentRun,
74
- FallbackResponse,
75
- ModelName,
76
- ToolCallback,
77
- UsageTrackerProtocol,
78
- )
79
-
80
62
  # Configure logging
81
63
  logger = get_logger(__name__)
82
64
 
83
- # Cache for UserPromptPart class to avoid repeated imports
84
- _USER_PROMPT_PART_CLASS = None
85
-
86
-
87
- def _get_user_prompt_part_class():
88
- """Get UserPromptPart class with caching and fallback for test environment.
89
-
90
- This function follows DRY principle by centralizing the UserPromptPart
91
- import logic and caching the result to avoid repeated imports.
92
- """
93
- global _USER_PROMPT_PART_CLASS
94
-
95
- if _USER_PROMPT_PART_CLASS is not None:
96
- return _USER_PROMPT_PART_CLASS
97
-
98
- try:
99
- import importlib
100
-
101
- messages = importlib.import_module("pydantic_ai.messages")
102
- _USER_PROMPT_PART_CLASS = getattr(messages, "UserPromptPart", None)
103
-
104
- if _USER_PROMPT_PART_CLASS is None:
105
- # Fallback for test environment
106
- class UserPromptPartFallback:
107
- def __init__(self, content, part_kind):
108
- self.content = content
109
- self.part_kind = part_kind
110
-
111
- _USER_PROMPT_PART_CLASS = UserPromptPartFallback
112
- except Exception:
113
- # Fallback for test environment
114
- class UserPromptPartFallback:
115
- def __init__(self, content, part_kind):
116
- self.content = content
117
- self.part_kind = part_kind
118
-
119
- _USER_PROMPT_PART_CLASS = UserPromptPartFallback
120
-
121
- return _USER_PROMPT_PART_CLASS
65
+ __all__ = [
66
+ "ToolBuffer",
67
+ "check_task_completion",
68
+ "extract_and_execute_tool_calls",
69
+ "get_model_messages",
70
+ "parse_json_tool_calls",
71
+ "patch_tool_messages",
72
+ "get_mcp_servers",
73
+ "check_query_satisfaction",
74
+ "process_request",
75
+ "get_or_create_agent",
76
+ "_process_node",
77
+ "ResponseState",
78
+ "SimpleResult",
79
+ "AgentRunWrapper",
80
+ "AgentRunWithState",
81
+ "execute_tools_parallel",
82
+ "get_agent_tool",
83
+ ]
122
84
 
123
85
 
124
86
  def get_agent_tool() -> tuple[type[Agent], type["Tool"]]:
@@ -134,15 +96,8 @@ async def check_query_satisfaction(
134
96
  response: str,
135
97
  state_manager: StateManager,
136
98
  ) -> bool:
137
- """
138
- Check if the response satisfies the original query.
139
-
140
- Returns:
141
- bool: True if query is satisfied, False otherwise
142
- """
143
- # For now, always return True to avoid recursive checks
144
- # The agent should use TUNACODE_TASK_COMPLETE marker instead
145
- return True
99
+ """Check if the response satisfies the original query."""
100
+ return True # Agent uses TUNACODE_TASK_COMPLETE marker
146
101
 
147
102
 
148
103
  async def process_request(
@@ -157,6 +112,8 @@ async def process_request(
157
112
  """
158
113
  Process a single request to the agent.
159
114
 
115
+ CLAUDE_ANCHOR[process-request-entry]: Main entry point for all agent requests
116
+
160
117
  Args:
161
118
  message: The user's request
162
119
  model: The model to use
@@ -231,68 +188,21 @@ async def process_request(
231
188
  response_state,
232
189
  )
233
190
 
234
- # Handle empty response by asking user for help instead of giving up
191
+ # Handle empty response
235
192
  if empty_response:
236
- # Track consecutive empty responses
237
193
  if not hasattr(state_manager.session, "consecutive_empty_responses"):
238
194
  state_manager.session.consecutive_empty_responses = 0
239
195
  state_manager.session.consecutive_empty_responses += 1
240
196
 
241
- # IMMEDIATE AGGRESSIVE INTERVENTION on ANY empty response
242
197
  if state_manager.session.consecutive_empty_responses >= 1:
243
- # Get context about what was happening
244
- last_tools_used = []
245
- if state_manager.session.tool_calls:
246
- for tc in state_manager.session.tool_calls[-3:]:
247
- tool_name = tc.get("tool", "unknown")
248
- tool_args = tc.get("args", {})
249
- tool_desc = tool_name
250
- if tool_name in ["grep", "glob"] and isinstance(tool_args, dict):
251
- pattern = tool_args.get("pattern", "")
252
- tool_desc = f"{tool_name}('{pattern}')"
253
- elif tool_name == "read_file" and isinstance(tool_args, dict):
254
- path = tool_args.get("file_path", tool_args.get("filepath", ""))
255
- tool_desc = f"{tool_name}('{path}')"
256
- last_tools_used.append(tool_desc)
257
-
258
- tools_context = (
259
- f"Recent tools: {', '.join(last_tools_used)}"
260
- if last_tools_used
261
- else "No tools used yet"
198
+ force_action_content = create_empty_response_message(
199
+ message,
200
+ empty_reason,
201
+ state_manager.session.tool_calls,
202
+ i,
203
+ state_manager,
262
204
  )
263
-
264
- # AGGRESSIVE prompt - YOU FAILED, TRY HARDER
265
- force_action_content = f"""FAILURE DETECTED: You returned {"an " + empty_reason if empty_reason != "empty" else "an empty"} response.
266
-
267
- This is UNACCEPTABLE. You FAILED to produce output.
268
-
269
- Task: {message[:200]}...
270
- {tools_context}
271
- Current iteration: {i}
272
-
273
- TRY AGAIN RIGHT NOW:
274
-
275
- 1. If your search returned no results → Try a DIFFERENT search pattern
276
- 2. If you found what you need → Use TUNACODE_TASK_COMPLETE
277
- 3. If you're stuck → EXPLAIN SPECIFICALLY what's blocking you
278
- 4. If you need to explore → Use list_dir or broader searches
279
-
280
- YOU MUST PRODUCE REAL OUTPUT IN THIS RESPONSE. NO EXCUSES.
281
- EXECUTE A TOOL OR PROVIDE SUBSTANTIAL CONTENT.
282
- DO NOT RETURN ANOTHER EMPTY RESPONSE."""
283
-
284
- model_request_cls = get_model_messages()[0]
285
- # Get UserPromptPart from the cached helper
286
- UserPromptPart = _get_user_prompt_part_class()
287
- user_prompt_part = UserPromptPart(
288
- content=force_action_content,
289
- part_kind="user-prompt",
290
- )
291
- force_message = model_request_cls(
292
- parts=[user_prompt_part],
293
- kind="request",
294
- )
295
- state_manager.session.messages.append(force_message)
205
+ create_user_message(force_action_content, state_manager)
296
206
 
297
207
  if state_manager.session.show_thoughts:
298
208
  from tunacode.ui import console as ui
@@ -301,13 +211,13 @@ DO NOT RETURN ANOTHER EMPTY RESPONSE."""
301
211
  "\n⚠️ EMPTY RESPONSE FAILURE - AGGRESSIVE RETRY TRIGGERED"
302
212
  )
303
213
  await ui.muted(f" Reason: {empty_reason}")
304
- await ui.muted(f" Recent tools: {tools_context}")
214
+ await ui.muted(
215
+ f" Recent tools: {get_recent_tools_context(state_manager.session.tool_calls)}"
216
+ )
305
217
  await ui.muted(" Injecting 'YOU FAILED TRY HARDER' prompt")
306
218
 
307
- # Reset counter after aggressive intervention
308
219
  state_manager.session.consecutive_empty_responses = 0
309
220
  else:
310
- # Reset counter on successful response
311
221
  if hasattr(state_manager.session, "consecutive_empty_responses"):
312
222
  state_manager.session.consecutive_empty_responses = 0
313
223
 
@@ -347,18 +257,7 @@ You're describing actions but not executing them. You MUST:
347
257
 
348
258
  NO MORE DESCRIPTIONS. Take ACTION or mark COMPLETE."""
349
259
 
350
- model_request_cls = get_model_messages()[0]
351
- # Get UserPromptPart from the cached helper
352
- UserPromptPart = _get_user_prompt_part_class()
353
- user_prompt_part = UserPromptPart(
354
- content=no_progress_content,
355
- part_kind="user-prompt",
356
- )
357
- progress_message = model_request_cls(
358
- parts=[user_prompt_part],
359
- kind="request",
360
- )
361
- state_manager.session.messages.append(progress_message)
260
+ create_user_message(no_progress_content, state_manager)
362
261
 
363
262
  if state_manager.session.show_thoughts:
364
263
  from tunacode.ui import console as ui
@@ -367,7 +266,6 @@ NO MORE DESCRIPTIONS. Take ACTION or mark COMPLETE."""
367
266
  f"⚠️ NO PROGRESS: {unproductive_iterations} iterations without tool usage"
368
267
  )
369
268
 
370
- # Reset counter after intervention
371
269
  unproductive_iterations = 0
372
270
 
373
271
  # REMOVED: Recursive satisfaction check that caused empty responses
@@ -386,11 +284,7 @@ NO MORE DESCRIPTIONS. Take ACTION or mark COMPLETE."""
386
284
 
387
285
  # Show summary of tools used so far
388
286
  if state_manager.session.tool_calls:
389
- tool_summary: dict[str, int] = {}
390
- for tc in state_manager.session.tool_calls:
391
- tool_name = tc.get("tool", "unknown")
392
- tool_summary[tool_name] = tool_summary.get(tool_name, 0) + 1
393
-
287
+ tool_summary = get_tool_summary(state_manager.session.tool_calls)
394
288
  summary_str = ", ".join(
395
289
  [f"{name}: {count}" for name, count in tool_summary.items()]
396
290
  )
@@ -398,23 +292,7 @@ NO MORE DESCRIPTIONS. Take ACTION or mark COMPLETE."""
398
292
 
399
293
  # User clarification: Ask user for guidance when explicitly awaiting
400
294
  if response_state.awaiting_user_guidance:
401
- # Build a progress summary
402
- tool_summary = {}
403
- if state_manager.session.tool_calls:
404
- for tc in state_manager.session.tool_calls:
405
- tool_name = tc.get("tool", "unknown")
406
- tool_summary[tool_name] = tool_summary.get(tool_name, 0) + 1
407
-
408
- tools_used_str = (
409
- ", ".join([f"{name}: {count}" for name, count in tool_summary.items()])
410
- if tool_summary
411
- else "No tools used yet"
412
- )
413
-
414
- # Create user message asking for clarification
415
- model_request_cls = get_model_messages()[0]
416
- # Get UserPromptPart from the cached helper
417
- UserPromptPart = _get_user_prompt_part_class()
295
+ _, tools_used_str = create_progress_summary(state_manager.session.tool_calls)
418
296
 
419
297
  clarification_content = f"""I need clarification to continue.
420
298
 
@@ -427,15 +305,7 @@ Progress so far:
427
305
  If the task is complete, I should respond with TUNACODE_TASK_COMPLETE.
428
306
  Otherwise, please provide specific guidance on what to do next."""
429
307
 
430
- user_prompt_part = UserPromptPart(
431
- content=clarification_content,
432
- part_kind="user-prompt",
433
- )
434
- clarification_message = model_request_cls(
435
- parts=[user_prompt_part],
436
- kind="request",
437
- )
438
- state_manager.session.messages.append(clarification_message)
308
+ create_user_message(clarification_content, state_manager)
439
309
 
440
310
  if state_manager.session.show_thoughts:
441
311
  from tunacode.ui import console as ui
@@ -444,7 +314,6 @@ Otherwise, please provide specific guidance on what to do next."""
444
314
  "\n🤔 SEEKING CLARIFICATION: Asking user for guidance on task progress"
445
315
  )
446
316
 
447
- # Mark that we've asked for user guidance
448
317
  response_state.awaiting_user_guidance = True
449
318
 
450
319
  # Check if task is explicitly completed
@@ -456,19 +325,8 @@ Otherwise, please provide specific guidance on what to do next."""
456
325
  break
457
326
 
458
327
  if i >= max_iterations and not response_state.task_completed:
459
- # Instead of breaking, ask user if they want to continue
460
- # Build progress summary
461
- tool_summary = {}
462
- if state_manager.session.tool_calls:
463
- for tc in state_manager.session.tool_calls:
464
- tool_name = tc.get("tool", "unknown")
465
- tool_summary[tool_name] = tool_summary.get(tool_name, 0) + 1
466
-
467
- tools_str = (
468
- ", ".join([f"{name}: {count}" for name, count in tool_summary.items()])
469
- if tool_summary
470
- else "No tools used"
471
- )
328
+ _, tools_str = create_progress_summary(state_manager.session.tool_calls)
329
+ tools_str = tools_str if tools_str != "No tools used yet" else "No tools used"
472
330
 
473
331
  extend_content = f"""I've reached the iteration limit ({max_iterations}).
474
332
 
@@ -483,19 +341,7 @@ The task appears incomplete. Would you like me to:
483
341
 
484
342
  Please let me know how to proceed."""
485
343
 
486
- # Create user message
487
- model_request_cls = get_model_messages()[0]
488
- # Get UserPromptPart from the cached helper
489
- UserPromptPart = _get_user_prompt_part_class()
490
- user_prompt_part = UserPromptPart(
491
- content=extend_content,
492
- part_kind="user-prompt",
493
- )
494
- extend_message = model_request_cls(
495
- parts=[user_prompt_part],
496
- kind="request",
497
- )
498
- state_manager.session.messages.append(extend_message)
344
+ create_user_message(extend_content, state_manager)
499
345
 
500
346
  if state_manager.session.show_thoughts:
501
347
  from tunacode.ui import console as ui
@@ -504,8 +350,7 @@ Please let me know how to proceed."""
504
350
  f"\n📊 ITERATION LIMIT: Asking user for guidance at {max_iterations} iterations"
505
351
  )
506
352
 
507
- # Extend the limit temporarily to allow processing the response
508
- max_iterations += 5 # Give 5 more iterations to process user guidance
353
+ max_iterations += 5
509
354
  response_state.awaiting_user_guidance = True
510
355
 
511
356
  # Increment iteration counter
@@ -520,6 +365,13 @@ Please let me know how to proceed."""
520
365
  buffered_tasks = tool_buffer.flush()
521
366
  start_time = time.time()
522
367
 
368
+ # Update spinner message for final batch execution
369
+ tool_names = [part.tool_name for part, _ in buffered_tasks]
370
+ batch_msg = get_batch_description(len(buffered_tasks), tool_names)
371
+ await ui.update_spinner_message(
372
+ f"[bold #00d7ff]{batch_msg}...[/bold #00d7ff]", state_manager
373
+ )
374
+
523
375
  await ui.muted("\n" + "=" * 60)
524
376
  await ui.muted(
525
377
  f"🚀 FINAL BATCH: Executing {len(buffered_tasks)} buffered read-only tools"
@@ -553,8 +405,12 @@ Please let me know how to proceed."""
553
405
  f"(~{speedup:.1f}x faster than sequential)\n"
554
406
  )
555
407
 
408
+ # Reset spinner back to thinking
409
+ from tunacode.constants import UI_THINKING_MESSAGE
410
+
411
+ await ui.update_spinner_message(UI_THINKING_MESSAGE, state_manager)
412
+
556
413
  # If we need to add a fallback response, create a wrapper
557
- # Don't add fallback if task was explicitly completed
558
414
  if (
559
415
  not response_state.has_user_response
560
416
  and not response_state.task_completed
@@ -564,100 +420,18 @@ Please let me know how to proceed."""
564
420
  patch_tool_messages("Task incomplete", state_manager=state_manager)
565
421
  response_state.has_final_synthesis = True
566
422
 
567
- # Extract context from the agent run
568
- tool_calls_summary = []
569
- files_modified = set()
570
- commands_run = []
571
-
572
- # Analyze message history for context
573
- for msg in state_manager.session.messages:
574
- if hasattr(msg, "parts"):
575
- for part in msg.parts:
576
- if hasattr(part, "part_kind") and part.part_kind == "tool-call":
577
- tool_name = getattr(part, "tool_name", "unknown")
578
- tool_calls_summary.append(tool_name)
579
-
580
- # Track specific operations
581
- if tool_name in ["write_file", "update_file"] and hasattr(
582
- part, "args"
583
- ):
584
- if isinstance(part.args, dict) and "file_path" in part.args:
585
- files_modified.add(part.args["file_path"])
586
- elif tool_name in ["run_command", "bash"] and hasattr(part, "args"):
587
- if isinstance(part.args, dict) and "command" in part.args:
588
- commands_run.append(part.args["command"])
589
-
590
- # Build fallback response with context
591
- fallback = FallbackResponse(
592
- summary="Reached maximum iterations without producing a final response.",
593
- progress=f"Completed {i} iterations (limit: {max_iterations})",
594
- )
595
-
596
- # Get verbosity setting
597
423
  verbosity = state_manager.session.user_config.get("settings", {}).get(
598
424
  "fallback_verbosity", "normal"
599
425
  )
600
-
601
- if verbosity in ["normal", "detailed"]:
602
- # Add what was attempted
603
- if tool_calls_summary:
604
- tool_counts: dict[str, int] = {}
605
- for tool in tool_calls_summary:
606
- tool_counts[tool] = tool_counts.get(tool, 0) + 1
607
-
608
- fallback.issues.append(f"Executed {len(tool_calls_summary)} tool calls:")
609
- for tool, count in sorted(tool_counts.items()):
610
- fallback.issues.append(f" • {tool}: {count}x")
611
-
612
- if verbosity == "detailed":
613
- if files_modified:
614
- fallback.issues.append(f"\nFiles modified ({len(files_modified)}):")
615
- for f in sorted(files_modified)[:5]: # Limit to 5 files
616
- fallback.issues.append(f" • {f}")
617
- if len(files_modified) > 5:
618
- fallback.issues.append(
619
- f" • ... and {len(files_modified) - 5} more"
620
- )
621
-
622
- if commands_run:
623
- fallback.issues.append(f"\nCommands executed ({len(commands_run)}):")
624
- for cmd in commands_run[:3]: # Limit to 3 commands
625
- # Truncate long commands
626
- display_cmd = cmd if len(cmd) <= 60 else cmd[:57] + "..."
627
- fallback.issues.append(f" • {display_cmd}")
628
- if len(commands_run) > 3:
629
- fallback.issues.append(f" • ... and {len(commands_run) - 3} more")
630
-
631
- # Add helpful next steps
632
- fallback.next_steps.append(
633
- "The task may be too complex - try breaking it into smaller steps"
634
- )
635
- fallback.next_steps.append(
636
- "Check the output above for any errors or partial progress"
426
+ fallback = create_fallback_response(
427
+ i,
428
+ max_iterations,
429
+ state_manager.session.tool_calls,
430
+ state_manager.session.messages,
431
+ verbosity,
637
432
  )
638
- if files_modified:
639
- fallback.next_steps.append(
640
- "Review modified files to see what changes were made"
641
- )
642
-
643
- # Create comprehensive output
644
- output_parts = [fallback.summary, ""]
645
-
646
- if fallback.progress:
647
- output_parts.append(f"Progress: {fallback.progress}")
648
-
649
- if fallback.issues:
650
- output_parts.append("\nWhat happened:")
651
- output_parts.extend(fallback.issues)
652
-
653
- if fallback.next_steps:
654
- output_parts.append("\nSuggested next steps:")
655
- for step in fallback.next_steps:
656
- output_parts.append(f" • {step}")
657
-
658
- comprehensive_output = "\n".join(output_parts)
433
+ comprehensive_output = format_fallback_output(fallback)
659
434
 
660
- # Create a wrapper object that mimics AgentRun with the required attributes
661
435
  wrapper = AgentRunWrapper(
662
436
  agent_run, SimpleResult(comprehensive_output), response_state
663
437
  )
tunacode/core/state.py CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  State management system for session data in TunaCode CLI.
4
4
  Handles user preferences, conversation history, and runtime state.
5
+
6
+ CLAUDE_ANCHOR[state-module]: Central state management and session tracking
5
7
  """
6
8
 
7
9
  import uuid
@@ -27,6 +29,8 @@ if TYPE_CHECKING:
27
29
 
28
30
  @dataclass
29
31
  class SessionState:
32
+ """CLAUDE_ANCHOR[session-state]: Core session state container"""
33
+
30
34
  user_config: UserConfig = field(default_factory=dict)
31
35
  agents: dict[str, Any] = field(
32
36
  default_factory=dict
@@ -44,9 +48,7 @@ class SessionState:
44
48
  input_sessions: InputSessions = field(default_factory=dict)
45
49
  current_task: Optional[Any] = None
46
50
  todos: list[TodoItem] = field(default_factory=list)
47
- # ESC key tracking for double-press functionality
48
- esc_press_count: int = 0
49
- last_esc_time: Optional[float] = None
51
+ # Operation state tracking
50
52
  operation_cancelled: bool = False
51
53
  # Enhanced tracking for thoughts display
52
54
  files_in_context: set[str] = field(default_factory=set)
@@ -92,6 +94,8 @@ class SessionState:
92
94
 
93
95
 
94
96
  class StateManager:
97
+ """CLAUDE_ANCHOR[state-manager]: Main state manager singleton"""
98
+
95
99
  def __init__(self):
96
100
  self._session = SessionState()
97
101
  self._tool_handler: Optional["ToolHandler"] = None
@@ -7,10 +7,11 @@ You are **"TunaCode"**, a **senior software developer AI assistant operating ins
7
7
  Your task is to **execute real actions** via tools and **report observations** after every tool use.
8
8
 
9
9
  **CRITICAL BEHAVIOR RULES:**
10
- 1. When you say "Let me..." or "I will..." you MUST execute the corresponding tool in THE SAME RESPONSE
11
- 2. Never describe what you'll do without doing it - ALWAYS execute tools when discussing actions
12
- 3. When a task is COMPLETE, start your response with: TUNACODE_TASK_COMPLETE
13
- 4. If your response is cut off or truncated, you'll be prompted to continue - complete your action
10
+ 1. **ALWAYS ANNOUNCE YOUR INTENTIONS FIRST**: Before executing any tools, briefly state what you're about to do (e.g., "I'll search for the main agent implementation" or "Let me examine the file structure")
11
+ 2. When you say "Let me..." or "I will..." you MUST execute the corresponding tool in THE SAME RESPONSE
12
+ 3. Never describe what you'll do without doing it - ALWAYS execute tools when discussing actions
13
+ 4. When a task is COMPLETE, start your response with: TUNACODE_TASK_COMPLETE
14
+ 5. If your response is cut off or truncated, you'll be prompted to continue - complete your action
14
15
 
15
16
  You MUST follow these rules:
16
17
 
tunacode/tools/grep.py CHANGED
@@ -7,6 +7,8 @@ This tool provides sophisticated grep-like functionality with:
7
7
  - Smart result ranking and deduplication
8
8
  - Context-aware output formatting
9
9
  - Timeout handling for overly broad patterns (3 second deadline for first match)
10
+
11
+ CLAUDE_ANCHOR[grep-module]: Fast parallel file search with 3-second deadline
10
12
  """
11
13
 
12
14
  import asyncio
@@ -29,7 +31,10 @@ from tunacode.tools.grep_components.result_formatter import ResultFormatter
29
31
 
30
32
 
31
33
  class ParallelGrep(BaseTool):
32
- """Advanced parallel grep tool with multiple search strategies."""
34
+ """Advanced parallel grep tool with multiple search strategies.
35
+
36
+ CLAUDE_ANCHOR[parallel-grep-class]: Main grep implementation with timeout handling
37
+ """
33
38
 
34
39
  def __init__(self, ui_logger=None):
35
40
  super().__init__(ui_logger)
@@ -123,7 +128,13 @@ class ParallelGrep(BaseTool):
123
128
  # 4️⃣ Execute chosen strategy with pre-filtered candidates
124
129
  # Execute search with pre-filtered candidates
125
130
  if search_type == "ripgrep":
131
+ # Try ripgrep first for performance. If ripgrep is unavailable or
132
+ # returns no results (e.g., binary missing), gracefully fallback to
133
+ # the Python implementation so the tool still returns matches.
126
134
  results = await self._ripgrep_search_filtered(pattern, candidates, config)
135
+ if not results:
136
+ # Fallback to python search when ripgrep produced no output
137
+ results = await self._python_search_filtered(pattern, candidates, config)
127
138
  elif search_type == "python":
128
139
  results = await self._python_search_filtered(pattern, candidates, config)
129
140
  elif search_type == "hybrid":
tunacode/ui/console.py CHANGED
@@ -21,6 +21,7 @@ from .output import (
21
21
  spinner,
22
22
  sync_print,
23
23
  update_available,
24
+ update_spinner_message,
24
25
  usage,
25
26
  version,
26
27
  )
@@ -93,6 +94,7 @@ __all__ = [
93
94
  "spinner",
94
95
  "sync_print",
95
96
  "update_available",
97
+ "update_spinner_message",
96
98
  "usage",
97
99
  "version",
98
100
  # Unified logging wrappers