tunacode-cli 0.0.54__py3-none-any.whl → 0.0.56__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.
- tunacode/cli/commands/__init__.py +2 -0
- tunacode/cli/commands/implementations/plan.py +50 -0
- tunacode/cli/commands/registry.py +7 -1
- tunacode/cli/repl.py +358 -8
- tunacode/cli/repl_components/output_display.py +18 -1
- tunacode/cli/repl_components/tool_executor.py +15 -4
- tunacode/constants.py +4 -2
- tunacode/core/agents/agent_components/__init__.py +20 -0
- tunacode/core/agents/agent_components/agent_config.py +134 -7
- tunacode/core/agents/agent_components/agent_helpers.py +219 -0
- tunacode/core/agents/agent_components/node_processor.py +82 -115
- tunacode/core/agents/agent_components/truncation_checker.py +81 -0
- tunacode/core/agents/main.py +86 -312
- tunacode/core/state.py +51 -3
- tunacode/core/tool_handler.py +20 -0
- tunacode/prompts/system.md +5 -4
- tunacode/tools/exit_plan_mode.py +191 -0
- tunacode/tools/grep.py +12 -1
- tunacode/tools/present_plan.py +208 -0
- tunacode/types.py +57 -0
- tunacode/ui/console.py +2 -0
- tunacode/ui/input.py +13 -2
- tunacode/ui/keybindings.py +26 -38
- tunacode/ui/output.py +39 -4
- tunacode/ui/panels.py +79 -2
- tunacode/ui/prompt_manager.py +19 -2
- tunacode/ui/tool_descriptions.py +115 -0
- tunacode/ui/tool_ui.py +3 -2
- tunacode/utils/message_utils.py +14 -4
- {tunacode_cli-0.0.54.dist-info → tunacode_cli-0.0.56.dist-info}/METADATA +4 -3
- {tunacode_cli-0.0.54.dist-info → tunacode_cli-0.0.56.dist-info}/RECORD +35 -29
- {tunacode_cli-0.0.54.dist-info → tunacode_cli-0.0.56.dist-info}/WHEEL +0 -0
- {tunacode_cli-0.0.54.dist-info → tunacode_cli-0.0.56.dist-info}/entry_points.txt +0 -0
- {tunacode_cli-0.0.54.dist-info → tunacode_cli-0.0.56.dist-info}/licenses/LICENSE +0 -0
- {tunacode_cli-0.0.54.dist-info → tunacode_cli-0.0.56.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""Truncation detection utilities for agent responses."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def check_for_truncation(combined_content: str) -> bool:
|
|
5
|
+
"""Check if content appears to be truncated.
|
|
6
|
+
|
|
7
|
+
Args:
|
|
8
|
+
combined_content: The text content to check for truncation
|
|
9
|
+
|
|
10
|
+
Returns:
|
|
11
|
+
bool: True if the content appears truncated, False otherwise
|
|
12
|
+
"""
|
|
13
|
+
if not combined_content:
|
|
14
|
+
return False
|
|
15
|
+
|
|
16
|
+
# Truncation indicators:
|
|
17
|
+
# 1. Ends with "..." or "…" (but not part of a complete sentence)
|
|
18
|
+
# 2. Ends mid-word (no punctuation, space, or complete word)
|
|
19
|
+
# 3. Contains incomplete markdown/code blocks
|
|
20
|
+
# 4. Ends with incomplete parentheses/brackets
|
|
21
|
+
|
|
22
|
+
# Check for ellipsis at end suggesting truncation
|
|
23
|
+
if combined_content.endswith(("...", "…")) and not combined_content.endswith(("....", "….")):
|
|
24
|
+
return True
|
|
25
|
+
|
|
26
|
+
# Check for mid-word truncation (ends with letters but no punctuation)
|
|
27
|
+
if combined_content and combined_content[-1].isalpha():
|
|
28
|
+
# Look for incomplete words by checking if last "word" seems cut off
|
|
29
|
+
words = combined_content.split()
|
|
30
|
+
if words:
|
|
31
|
+
last_word = words[-1]
|
|
32
|
+
# Common complete word endings vs likely truncations
|
|
33
|
+
complete_endings = (
|
|
34
|
+
"ing",
|
|
35
|
+
"ed",
|
|
36
|
+
"ly",
|
|
37
|
+
"er",
|
|
38
|
+
"est",
|
|
39
|
+
"tion",
|
|
40
|
+
"ment",
|
|
41
|
+
"ness",
|
|
42
|
+
"ity",
|
|
43
|
+
"ous",
|
|
44
|
+
"ive",
|
|
45
|
+
"able",
|
|
46
|
+
"ible",
|
|
47
|
+
)
|
|
48
|
+
incomplete_patterns = (
|
|
49
|
+
"referen",
|
|
50
|
+
"inte",
|
|
51
|
+
"proces",
|
|
52
|
+
"analy",
|
|
53
|
+
"deve",
|
|
54
|
+
"imple",
|
|
55
|
+
"execu",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if any(last_word.lower().endswith(pattern) for pattern in incomplete_patterns):
|
|
59
|
+
return True
|
|
60
|
+
elif len(last_word) > 2 and not any(
|
|
61
|
+
last_word.lower().endswith(end) for end in complete_endings
|
|
62
|
+
):
|
|
63
|
+
# Likely truncated if doesn't end with common suffix
|
|
64
|
+
return True
|
|
65
|
+
|
|
66
|
+
# Check for unclosed markdown code blocks
|
|
67
|
+
code_block_count = combined_content.count("```")
|
|
68
|
+
if code_block_count % 2 != 0:
|
|
69
|
+
return True
|
|
70
|
+
|
|
71
|
+
# Check for unclosed brackets/parentheses (more opens than closes)
|
|
72
|
+
open_brackets = (
|
|
73
|
+
combined_content.count("[") + combined_content.count("(") + combined_content.count("{")
|
|
74
|
+
)
|
|
75
|
+
close_brackets = (
|
|
76
|
+
combined_content.count("]") + combined_content.count(")") + combined_content.count("}")
|
|
77
|
+
)
|
|
78
|
+
if open_brackets > close_brackets:
|
|
79
|
+
return True
|
|
80
|
+
|
|
81
|
+
return False
|
tunacode/core/agents/main.py
CHANGED
|
@@ -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
|
-
|
|
11
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
""
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
""
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
460
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
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
|
-
|
|
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
|
)
|