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

Files changed (90) hide show
  1. tunacode/cli/commands/__init__.py +0 -2
  2. tunacode/cli/commands/implementations/__init__.py +0 -3
  3. tunacode/cli/commands/implementations/debug.py +2 -2
  4. tunacode/cli/commands/implementations/development.py +10 -8
  5. tunacode/cli/commands/implementations/model.py +357 -29
  6. tunacode/cli/commands/implementations/system.py +3 -2
  7. tunacode/cli/commands/implementations/template.py +0 -2
  8. tunacode/cli/commands/registry.py +8 -7
  9. tunacode/cli/commands/slash/loader.py +2 -1
  10. tunacode/cli/commands/slash/validator.py +2 -1
  11. tunacode/cli/main.py +19 -1
  12. tunacode/cli/repl.py +90 -229
  13. tunacode/cli/repl_components/command_parser.py +2 -1
  14. tunacode/cli/repl_components/error_recovery.py +8 -5
  15. tunacode/cli/repl_components/output_display.py +1 -10
  16. tunacode/cli/repl_components/tool_executor.py +1 -13
  17. tunacode/configuration/defaults.py +2 -2
  18. tunacode/configuration/key_descriptions.py +284 -0
  19. tunacode/configuration/settings.py +0 -1
  20. tunacode/constants.py +6 -42
  21. tunacode/core/agents/__init__.py +43 -2
  22. tunacode/core/agents/agent_components/__init__.py +7 -0
  23. tunacode/core/agents/agent_components/agent_config.py +162 -158
  24. tunacode/core/agents/agent_components/agent_helpers.py +31 -2
  25. tunacode/core/agents/agent_components/node_processor.py +180 -146
  26. tunacode/core/agents/agent_components/response_state.py +123 -6
  27. tunacode/core/agents/agent_components/state_transition.py +116 -0
  28. tunacode/core/agents/agent_components/streaming.py +296 -0
  29. tunacode/core/agents/agent_components/task_completion.py +19 -6
  30. tunacode/core/agents/agent_components/tool_buffer.py +21 -1
  31. tunacode/core/agents/agent_components/tool_executor.py +10 -0
  32. tunacode/core/agents/main.py +522 -370
  33. tunacode/core/agents/main_legact.py +538 -0
  34. tunacode/core/agents/prompts.py +66 -0
  35. tunacode/core/agents/utils.py +29 -122
  36. tunacode/core/setup/__init__.py +0 -2
  37. tunacode/core/setup/config_setup.py +88 -227
  38. tunacode/core/setup/config_wizard.py +230 -0
  39. tunacode/core/setup/coordinator.py +2 -1
  40. tunacode/core/state.py +16 -64
  41. tunacode/core/token_usage/usage_tracker.py +3 -1
  42. tunacode/core/tool_authorization.py +352 -0
  43. tunacode/core/tool_handler.py +67 -60
  44. tunacode/prompts/system.xml +751 -0
  45. tunacode/services/mcp.py +97 -1
  46. tunacode/setup.py +0 -23
  47. tunacode/tools/base.py +54 -1
  48. tunacode/tools/bash.py +14 -0
  49. tunacode/tools/glob.py +4 -2
  50. tunacode/tools/grep.py +7 -17
  51. tunacode/tools/prompts/glob_prompt.xml +1 -1
  52. tunacode/tools/prompts/grep_prompt.xml +1 -0
  53. tunacode/tools/prompts/list_dir_prompt.xml +1 -1
  54. tunacode/tools/prompts/react_prompt.xml +23 -0
  55. tunacode/tools/prompts/read_file_prompt.xml +1 -1
  56. tunacode/tools/react.py +153 -0
  57. tunacode/tools/run_command.py +15 -0
  58. tunacode/types.py +14 -79
  59. tunacode/ui/completers.py +434 -50
  60. tunacode/ui/config_dashboard.py +585 -0
  61. tunacode/ui/console.py +63 -11
  62. tunacode/ui/input.py +8 -3
  63. tunacode/ui/keybindings.py +0 -18
  64. tunacode/ui/model_selector.py +395 -0
  65. tunacode/ui/output.py +40 -19
  66. tunacode/ui/panels.py +173 -49
  67. tunacode/ui/path_heuristics.py +91 -0
  68. tunacode/ui/prompt_manager.py +1 -20
  69. tunacode/ui/tool_ui.py +30 -8
  70. tunacode/utils/api_key_validation.py +93 -0
  71. tunacode/utils/config_comparator.py +340 -0
  72. tunacode/utils/models_registry.py +593 -0
  73. tunacode/utils/text_utils.py +18 -1
  74. {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/METADATA +80 -12
  75. {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/RECORD +78 -74
  76. tunacode/cli/commands/implementations/plan.py +0 -50
  77. tunacode/cli/commands/implementations/todo.py +0 -217
  78. tunacode/context.py +0 -71
  79. tunacode/core/setup/git_safety_setup.py +0 -186
  80. tunacode/prompts/system.md +0 -359
  81. tunacode/prompts/system.md.bak +0 -487
  82. tunacode/tools/exit_plan_mode.py +0 -273
  83. tunacode/tools/present_plan.py +0 -288
  84. tunacode/tools/prompts/exit_plan_mode_prompt.xml +0 -25
  85. tunacode/tools/prompts/present_plan_prompt.xml +0 -20
  86. tunacode/tools/prompts/todo_prompt.xml +0 -96
  87. tunacode/tools/todo.py +0 -456
  88. {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/WHEEL +0 -0
  89. {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/entry_points.txt +0 -0
  90. {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/licenses/LICENSE +0 -0
tunacode/cli/repl.py CHANGED
@@ -13,8 +13,8 @@ from pydantic_ai.exceptions import UnexpectedModelBehavior
13
13
 
14
14
  from tunacode.configuration.models import ModelRegistry
15
15
  from tunacode.constants import DEFAULT_CONTEXT_WINDOW
16
- from tunacode.core.agents import main as agent
17
- from tunacode.core.agents.main import patch_tool_messages
16
+ from tunacode.core import agents as agent
17
+ from tunacode.core.agents import patch_tool_messages
18
18
  from tunacode.core.token_usage.api_response_parser import ApiResponseParser
19
19
  from tunacode.core.token_usage.cost_calculator import CostCalculator
20
20
  from tunacode.core.token_usage.usage_tracker import UsageTracker
@@ -41,237 +41,69 @@ DEFAULT_SHELL = "bash"
41
41
  logger = logging.getLogger(__name__)
42
42
 
43
43
 
44
- def _transform_to_implementation_request(original_request: str) -> str:
45
- """
46
- Transform a planning request into an implementation request.
47
-
48
- This ensures that after plan approval, the agent understands it should
49
- implement rather than plan again.
50
- """
51
- request = original_request.lower()
52
-
53
- if "plan" in request:
54
- request = request.replace("plan a ", "create a ")
55
- request = request.replace("plan an ", "create an ")
56
- request = request.replace("plan to ", "")
57
- request = request.replace("plan ", "create ")
58
-
59
- # Add clear implementation instruction
60
- implementation_request = f"{request}\n\nIMPORTANT: Actually implement and create the file(s) - do not just plan or outline. The plan has been approved, now execute the implementation."
61
-
62
- return implementation_request
63
-
64
-
65
- async def _display_plan(plan_doc) -> None:
66
- """Display the plan in a formatted way."""
67
- if not plan_doc:
68
- await ui.error("⚠️ Error: No plan document found to display")
69
- return
70
-
71
- output = [f"[bold cyan]🎯 {plan_doc.title}[/bold cyan]", ""]
72
-
73
- if plan_doc.overview:
74
- output.extend([f"[bold]📝 Overview:[/bold] {plan_doc.overview}", ""])
75
-
76
- sections = [
77
- ("📝 Files to Modify:", plan_doc.files_to_modify, "•"),
78
- ("📄 Files to Create:", plan_doc.files_to_create, "•"),
79
- ("🧪 Testing Approach:", plan_doc.tests, "•"),
80
- ("✅ Success Criteria:", plan_doc.success_criteria, "•"),
81
- ("⚠️ Risks & Considerations:", plan_doc.risks, "•"),
82
- ("❓ Open Questions:", plan_doc.open_questions, "•"),
83
- ("📚 References:", plan_doc.references, "•"),
84
- ]
85
-
86
- for title, items, prefix in sections:
87
- if items:
88
- output.append(f"[bold]{title}[/bold]")
89
- output.extend(f" {prefix} {item}" for item in items)
90
- output.append("")
91
-
92
- output.append("[bold]🔧 Implementation Steps:[/bold]")
93
- output.extend(f" {i}. {step}" for i, step in enumerate(plan_doc.steps, 1))
94
- output.append("")
95
-
96
- if plan_doc.rollback:
97
- output.extend([f"[bold]🔄 Rollback Plan:[/bold] {plan_doc.rollback}", ""])
98
-
99
- await ui.panel("📋 IMPLEMENTATION PLAN", "\n".join(output), border_style="cyan")
100
-
101
-
102
- async def _detect_and_handle_text_plan(state_manager, agent_response, original_request):
103
- """Detect if agent presented a plan in text format and handle it."""
104
- try:
105
- # Extract response text
106
- response_text = ""
107
- if hasattr(agent_response, "messages") and agent_response.messages:
108
- msg = agent_response.messages[-1]
109
- response_text = str(getattr(msg, "content", getattr(msg, "text", msg)))
110
- elif hasattr(agent_response, "result"):
111
- response_text = str(getattr(agent_response.result, "output", agent_response.result))
112
- else:
113
- response_text = str(agent_response)
114
-
115
- if "TUNACODE_TASK_COMPLETE" in response_text:
116
- await ui.warning(
117
- "⚠️ Agent failed to call present_plan tool. Please provide clearer instructions."
118
- )
119
- return
120
-
121
- if "present_plan(" in response_text:
122
- await ui.error(
123
- "❌ Agent showed present_plan as text instead of EXECUTING it as a tool!"
124
- )
125
- await ui.info("Try again with: 'Execute the present_plan tool to create a plan for...'")
126
- return
127
-
128
- # Check for plan indicators
129
- plan_indicators = {
130
- "plan for",
131
- "implementation plan",
132
- "here's a plan",
133
- "i'll create a plan",
134
- "plan to",
135
- "outline for",
136
- "overview:",
137
- "steps:",
138
- }
139
- has_plan = any(ind in response_text.lower() for ind in plan_indicators)
140
- has_structure = (
141
- any(x in response_text for x in ["1.", "2.", "•"]) and response_text.count("\n") > 5
142
- )
143
-
144
- if has_plan and has_structure:
145
- await ui.info("📋 Plan detected in text format - extracting for review")
146
- from tunacode.types import PlanDoc, PlanPhase
147
-
148
- plan_doc = PlanDoc(
149
- title="Implementation Plan",
150
- overview="Automated plan extraction from text",
151
- steps=["Review and implement the described functionality"],
152
- files_to_modify=[],
153
- files_to_create=[],
154
- success_criteria=[],
155
- )
156
-
157
- state_manager.session.plan_phase = PlanPhase.PLAN_READY
158
- state_manager.session.current_plan = plan_doc
159
- await _handle_plan_approval(state_manager, original_request)
160
-
161
- except Exception as e:
162
- logger.error(f"Error detecting text plan: {e}")
44
+ _command_registry = CommandRegistry()
45
+ _command_registry.register_all_default_commands()
163
46
 
164
47
 
165
- async def _handle_plan_approval(state_manager, original_request=None):
166
- """Handle plan approval when a plan has been presented via present_plan tool."""
48
+ async def _handle_command(command: str, state_manager: StateManager) -> CommandResult:
49
+ """Handles a command string using the command registry."""
50
+ context = CommandContext(state_manager=state_manager, process_request=execute_repl_request)
167
51
  try:
168
- import time
169
-
170
- from tunacode.types import PlanPhase
171
- from tunacode.ui.keybindings import create_key_bindings
172
-
173
- state_manager.session.plan_phase = PlanPhase.REVIEW_DECISION
174
- plan_doc = state_manager.session.current_plan
175
- state_manager.exit_plan_mode(plan_doc)
52
+ _command_registry.set_process_request_callback(execute_repl_request)
53
+ return await _command_registry.execute(command, context)
54
+ except ValidationError as e:
55
+ await ui.error(str(e))
56
+ return None
176
57
 
177
- await ui.info("📋 Plan has been prepared and Plan Mode exited")
178
- await _display_plan(plan_doc)
179
58
 
180
- content = (
181
- "[bold cyan]The implementation plan has been presented.[/bold cyan]\n\n"
182
- "[yellow]Choose your action:[/yellow]\n\n"
183
- " [bold green]a[/bold green] → Approve and proceed\n"
184
- " [bold yellow]m[/bold yellow] → Modify the plan\n"
185
- " [bold red]r[/bold red] → Reject and recreate\n"
186
- )
187
- await ui.panel("🎯 Plan Review", content, border_style="cyan")
59
+ def _extract_feedback_from_last_message(state_manager: StateManager) -> str | None:
60
+ """Extract user guidance feedback from recent messages in session.messages.
188
61
 
189
- kb = create_key_bindings(state_manager)
190
- while True:
191
- try:
192
- response = await ui.input(
193
- "plan_approval", " → Your choice [a/m/r]: ", kb, state_manager
194
- )
195
- response = response.strip().lower()
196
- state_manager.session.approval_abort_pressed = False
197
- state_manager.session.approval_last_abort_time = 0.0
198
- break
199
- except UserAbortError:
200
- current_time = time.time()
201
- abort_pressed = getattr(state_manager.session, "approval_abort_pressed", False)
202
- last_abort = getattr(state_manager.session, "approval_last_abort_time", 0.0)
62
+ When option 3 is selected with feedback, a message is added with format:
63
+ "Tool '...' execution cancelled before running.\nUser guidance:\n{guidance}\n..."
203
64
 
204
- if current_time - last_abort > 3.0:
205
- abort_pressed = False
65
+ Note: patch_tool_messages() adds "Operation aborted by user." AFTER the feedback,
66
+ so we check the last few messages, not just the last one.
206
67
 
207
- if abort_pressed:
208
- await ui.info("🔄 Returning to Plan Mode")
209
- state_manager.enter_plan_mode()
210
- state_manager.session.approval_abort_pressed = False
211
- return
212
-
213
- state_manager.session.approval_abort_pressed = True
214
- state_manager.session.approval_last_abort_time = current_time
215
- await ui.warning("Hit ESC or Ctrl+C again to return to Plan Mode")
216
-
217
- actions = {
218
- "a": (
219
- "✅ Plan approved - proceeding with implementation",
220
- lambda: state_manager.approve_plan(),
221
- ),
222
- "m": (
223
- "📝 Returning to Plan Mode for modifications",
224
- lambda: state_manager.enter_plan_mode(),
225
- ),
226
- "r": (
227
- "🔄 Plan rejected - returning to Plan Mode",
228
- lambda: state_manager.enter_plan_mode(),
229
- ),
230
- }
231
-
232
- if response in actions or response in ["approve", "modify", "reject"]:
233
- key = response[0] if len(response) > 1 else response
234
- msg, action = actions.get(key, (None, None))
235
- if msg:
236
- await ui.info(msg) if key == "a" else await ui.warning(msg)
237
- action()
238
- if key == "a" and original_request:
239
- await ui.info("🚀 Executing implementation...")
240
- await process_request(
241
- _transform_to_implementation_request(original_request),
242
- state_manager,
243
- output=True,
244
- )
245
- else:
246
- await ui.warning("⚠️ Invalid choice - please enter a, m, or r")
68
+ Args:
69
+ state_manager: State manager containing session messages
247
70
 
248
- state_manager.session.plan_phase = None
71
+ Returns:
72
+ The guidance text if found, None otherwise
73
+ """
74
+ if not state_manager.session.messages:
75
+ return None
249
76
 
250
- except Exception as e:
251
- logger.error(f"Error in plan approval: {e}")
252
- state_manager.session.plan_phase = None
77
+ # Check last 3 messages since patch_tool_messages() adds a message after feedback
78
+ messages_to_check = state_manager.session.messages[-3:]
253
79
 
80
+ for msg in reversed(messages_to_check):
81
+ # Extract content from message parts
82
+ if not hasattr(msg, "parts"):
83
+ continue
254
84
 
255
- _command_registry = CommandRegistry()
256
- _command_registry.register_all_default_commands()
85
+ for part in msg.parts:
86
+ if hasattr(part, "content") and isinstance(part.content, str):
87
+ content = part.content
257
88
 
89
+ # Look for "User guidance:" pattern
90
+ if "User guidance:" in content:
91
+ lines = content.split("\n")
92
+ for i, line in enumerate(lines):
93
+ if "User guidance:" in line and i + 1 < len(lines):
94
+ guidance = lines[i + 1].strip()
95
+ # Only return non-empty guidance
96
+ cancelled_msg = "User cancelled without additional instructions."
97
+ if guidance and guidance != cancelled_msg:
98
+ return guidance
258
99
 
259
- async def _handle_command(command: str, state_manager: StateManager) -> CommandResult:
260
- """Handles a command string using the command registry."""
261
- context = CommandContext(state_manager=state_manager, process_request=process_request)
262
- try:
263
- _command_registry.set_process_request_callback(process_request)
264
- return await _command_registry.execute(command, context)
265
- except ValidationError as e:
266
- await ui.error(str(e))
267
- return None
100
+ return None
268
101
 
269
102
 
270
- async def process_request(text: str, state_manager: StateManager, output: bool = True):
103
+ async def execute_repl_request(text: str, state_manager: StateManager, output: bool = True):
271
104
  """Process input using the agent, handling cancellation safely."""
272
105
  import uuid
273
106
 
274
- from tunacode.types import PlanPhase
275
107
  from tunacode.utils.text_utils import expand_file_refs
276
108
 
277
109
  state_manager.session.request_id = str(uuid.uuid4())
@@ -319,7 +151,9 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
319
151
  if enable_streaming:
320
152
  await ui.spinner(False, state_manager.session.spinner, state_manager)
321
153
  state_manager.session.is_streaming_active = True
322
- streaming_panel = ui.StreamingAgentPanel()
154
+ streaming_panel = ui.StreamingAgentPanel(
155
+ debug=bool(state_manager.session.show_thoughts)
156
+ )
323
157
  await streaming_panel.start()
324
158
  state_manager.session.streaming_panel = streaming_panel
325
159
 
@@ -336,6 +170,20 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
336
170
  await streaming_panel.stop()
337
171
  state_manager.session.streaming_panel = None
338
172
  state_manager.session.is_streaming_active = False
173
+ # Emit source-side streaming diagnostics if thoughts are enabled
174
+ if state_manager.session.show_thoughts:
175
+ try:
176
+ raw = getattr(state_manager.session, "_debug_raw_stream_accum", "") or ""
177
+ events = getattr(state_manager.session, "_debug_events", []) or []
178
+ raw_first5 = repr(raw[:5])
179
+ await ui.muted(
180
+ f"[debug] raw_stream_first5={raw_first5} total_len={len(raw)}"
181
+ )
182
+ for line in events:
183
+ await ui.muted(line)
184
+ except Exception:
185
+ # Don't let diagnostics break normal flow
186
+ pass
339
187
  else:
340
188
  res = await agent.process_request(
341
189
  text,
@@ -345,17 +193,6 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
345
193
  usage_tracker=usage_tracker,
346
194
  )
347
195
 
348
- # Handle plan approval or detection
349
- if (
350
- hasattr(state_manager.session, "plan_phase")
351
- and state_manager.session.plan_phase == PlanPhase.PLAN_READY
352
- ):
353
- await _handle_plan_approval(state_manager, text)
354
- elif state_manager.is_plan_mode() and not getattr(
355
- state_manager.session, "_continuing_from_plan", False
356
- ):
357
- await _detect_and_handle_text_plan(state_manager, res, text)
358
-
359
196
  if output:
360
197
  if state_manager.session.show_thoughts:
361
198
  for msg in state_manager.session.messages[start_idx:]:
@@ -377,7 +214,24 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
377
214
  except CancelledError:
378
215
  await ui.muted(MSG_REQUEST_CANCELLED)
379
216
  except UserAbortError:
380
- await ui.muted(MSG_OPERATION_ABORTED)
217
+ # CLAUDE_ANCHOR[7b2c1d4e]: Guided aborts inject user instructions; skip legacy banner.
218
+ # Check if there's feedback to process immediately
219
+ feedback = _extract_feedback_from_last_message(state_manager)
220
+ if feedback:
221
+ # Process the feedback as a new request immediately
222
+ # Stop spinner first to clean up state before recursive call
223
+ await ui.spinner(False, state_manager.session.spinner, state_manager)
224
+ # Clear current_task so recursive call can set its own
225
+ state_manager.session.current_task = None
226
+ try:
227
+ await execute_repl_request(feedback, state_manager, output=output)
228
+ except Exception:
229
+ # If recursive call fails, don't let it bubble up - just continue
230
+ pass
231
+ # Return early to skip the finally block's cleanup (already done above)
232
+ return
233
+ # No feedback, just abort normally
234
+ pass
381
235
  except UnexpectedModelBehavior as e:
382
236
  await ui.muted(str(e))
383
237
  patch_tool_messages(str(e), state_manager)
@@ -395,6 +249,10 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
395
249
  )
396
250
 
397
251
 
252
+ # Backwards compatibility: exported name expected by external integrations/tests
253
+ process_request = execute_repl_request
254
+
255
+
398
256
  async def warm_code_index():
399
257
  """Pre-warm the code index in background for faster directory operations."""
400
258
  try:
@@ -434,9 +292,12 @@ async def repl(state_manager: StateManager):
434
292
  if state_manager.session.session_total_usage:
435
293
  session_cost = float(state_manager.session.session_total_usage.get("cost", 0.0) or 0.0)
436
294
 
295
+ # Subtle, unified styling - mostly muted with minimal accent on cost
437
296
  await ui.muted(f"• Model: {state_manager.session.current_model} • {context}")
438
297
  if session_cost > 0:
439
- await ui.muted(f"• Session Cost: ${session_cost:.4f}")
298
+ await ui.print(
299
+ f"[dim]• Session Cost:[/dim] [dim #00d7ff]${session_cost:.4f}[/dim #00d7ff]"
300
+ )
440
301
 
441
302
  # Always show context
442
303
  await show_context()
@@ -516,7 +377,7 @@ async def repl(state_manager: StateManager):
516
377
  state_manager.session.operation_cancelled = False
517
378
 
518
379
  state_manager.session.current_task = get_app().create_background_task(
519
- process_request(line, state_manager)
380
+ execute_repl_request(line, state_manager)
520
381
  )
521
382
  await state_manager.session.current_task
522
383
 
@@ -31,7 +31,8 @@ def parse_args(args) -> ToolArgs:
31
31
  dict: The parsed arguments.
32
32
 
33
33
  Raises:
34
- ValidationError: If 'args' is not a string or dictionary, or if the string is not valid JSON.
34
+ ValidationError: If 'args' is not a string or dictionary, or if the string
35
+ is not valid JSON.
35
36
  """
36
37
  if isinstance(args, str):
37
38
  try:
@@ -6,6 +6,7 @@ Error recovery utilities for the REPL.
6
6
 
7
7
  import logging
8
8
 
9
+ import tunacode.core.agents as agent_api
9
10
  from tunacode.types import StateManager
10
11
  from tunacode.ui import console as ui
11
12
 
@@ -64,7 +65,8 @@ async def attempt_json_args_recovery(e: Exception, state_manager: StateManager)
64
65
 
65
66
  await ui.warning(f"Warning: {MSG_JSON_ARGS_RECOVERY}")
66
67
  logger.info(
67
- f"Successfully recovered tool {part.tool_name} with split JSON args",
68
+ f"Successfully recovered tool {part.tool_name} with "
69
+ f"split JSON args",
68
70
  extra={
69
71
  "original_args": part.args,
70
72
  "recovered_args": json_objects[0],
@@ -78,7 +80,8 @@ async def attempt_json_args_recovery(e: Exception, state_manager: StateManager)
78
80
 
79
81
  except Exception as recovery_exc:
80
82
  logger.error(
81
- f"Error during JSON args recovery for tool {getattr(part, 'tool_name', 'unknown')}",
83
+ f"Error during JSON args recovery for tool "
84
+ f"{getattr(part, 'tool_name', 'unknown')}",
82
85
  exc_info=True,
83
86
  extra={"recovery_exception": str(recovery_exc)},
84
87
  )
@@ -126,17 +129,17 @@ async def attempt_tool_recovery(e: Exception, state_manager: StateManager) -> bo
126
129
  },
127
130
  )
128
131
  await ui.muted(
129
- f"⚠️ Model response error. Attempting to recover by parsing tools from text: {str(e)[:100]}..."
132
+ f"⚠️ Model response error. Attempting to recover by parsing tools "
133
+ f"from text: {str(e)[:100]}..."
130
134
  )
131
135
 
132
136
  try:
133
- from tunacode.core.agents.main import extract_and_execute_tool_calls
134
137
 
135
138
  def tool_callback_with_state(tool_part, _node):
136
139
  return tool_handler(tool_part, state_manager)
137
140
 
138
141
  # This function now returns the number of tools found
139
- tools_found = await extract_and_execute_tool_calls(
142
+ tools_found = await agent_api.extract_and_execute_tool_calls(
140
143
  content_to_parse, tool_callback_with_state, state_manager
141
144
  )
142
145
 
@@ -30,19 +30,10 @@ async def display_agent_output(res, enable_streaming: bool, state_manager=None)
30
30
  if '"tool_uses"' in output:
31
31
  return
32
32
 
33
- # In plan mode, don't display any agent text output at all
34
- # The plan will be displayed via the present_plan tool
35
- if state_manager and state_manager.is_plan_mode():
36
- return
37
-
38
- # Filter out plan mode system prompts and tool definitions
33
+ # Filter out system prompts and tool definitions
39
34
  if any(
40
35
  phrase in output
41
36
  for phrase in [
42
- "PLAN MODE - TOOL EXECUTION ONLY",
43
- "🔧 PLAN MODE",
44
- "TOOL EXECUTION ONLY 🔧",
45
- "planning assistant that ONLY communicates",
46
37
  "namespace functions {",
47
38
  "namespace multi_tool_use {",
48
39
  "You are trained on data up to",
@@ -9,7 +9,7 @@ from asyncio.exceptions import CancelledError
9
9
 
10
10
  from prompt_toolkit.application import run_in_terminal
11
11
 
12
- from tunacode.core.agents.main import patch_tool_messages
12
+ from tunacode.core.agents import patch_tool_messages
13
13
  from tunacode.core.tool_handler import ToolHandler
14
14
  from tunacode.exceptions import UserAbortError
15
15
  from tunacode.types import StateManager
@@ -59,18 +59,6 @@ async def tool_handler(part, state_manager: StateManager):
59
59
  args = parse_args(part.args)
60
60
 
61
61
  def confirm_func():
62
- # Check if tool is blocked in plan mode first
63
- if tool_handler_instance.is_tool_blocked_in_plan_mode(part.tool_name):
64
- from tunacode.constants import READ_ONLY_TOOLS
65
-
66
- error_msg = (
67
- f"🔍 Plan Mode: Tool '{part.tool_name}' is not available in Plan Mode.\n"
68
- f"Only read-only tools are allowed: {', '.join(READ_ONLY_TOOLS)}\n"
69
- f"Use 'exit_plan_mode' tool to present your plan and exit Plan Mode."
70
- )
71
- print(f"\n❌ {error_msg}\n")
72
- return True # Abort the tool
73
-
74
62
  if not tool_handler_instance.should_confirm(part.tool_name):
75
63
  return False
76
64
  request = tool_handler_instance.create_confirmation_request(part.tool_name, args)
@@ -5,7 +5,7 @@ Default configuration values for the TunaCode CLI.
5
5
  Provides sensible defaults for user configuration and environment variables.
6
6
  """
7
7
 
8
- from tunacode.constants import GUIDE_FILE_NAME, ToolName
8
+ from tunacode.constants import GUIDE_FILE_NAME
9
9
  from tunacode.types import UserConfig
10
10
 
11
11
  DEFAULT_USER_CONFIG: UserConfig = {
@@ -19,7 +19,7 @@ DEFAULT_USER_CONFIG: UserConfig = {
19
19
  "settings": {
20
20
  "max_retries": 10,
21
21
  "max_iterations": 40,
22
- "tool_ignore": [ToolName.READ_FILE],
22
+ "tool_ignore": [],
23
23
  "guide_file": GUIDE_FILE_NAME,
24
24
  "fallback_response": True,
25
25
  "fallback_verbosity": "normal", # Options: minimal, normal, detailed