tunacode-cli 0.0.77.2__py3-none-any.whl → 0.0.77.3__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/repl.py +2 -1
- tunacode/constants.py +1 -1
- tunacode/core/agents/agent_components/node_processor.py +3 -0
- tunacode/core/tool_handler.py +16 -0
- tunacode/types.py +1 -0
- tunacode/ui/tool_ui.py +30 -8
- {tunacode_cli-0.0.77.2.dist-info → tunacode_cli-0.0.77.3.dist-info}/METADATA +1 -1
- {tunacode_cli-0.0.77.2.dist-info → tunacode_cli-0.0.77.3.dist-info}/RECORD +11 -12
- tunacode/context.py +0 -71
- {tunacode_cli-0.0.77.2.dist-info → tunacode_cli-0.0.77.3.dist-info}/WHEEL +0 -0
- {tunacode_cli-0.0.77.2.dist-info → tunacode_cli-0.0.77.3.dist-info}/entry_points.txt +0 -0
- {tunacode_cli-0.0.77.2.dist-info → tunacode_cli-0.0.77.3.dist-info}/licenses/LICENSE +0 -0
tunacode/cli/repl.py
CHANGED
|
@@ -394,7 +394,8 @@ async def execute_repl_request(text: str, state_manager: StateManager, output: b
|
|
|
394
394
|
except CancelledError:
|
|
395
395
|
await ui.muted(MSG_REQUEST_CANCELLED)
|
|
396
396
|
except UserAbortError:
|
|
397
|
-
|
|
397
|
+
# CLAUDE_ANCHOR[7b2c1d4e]: Guided aborts inject user instructions; skip legacy banner.
|
|
398
|
+
pass
|
|
398
399
|
except UnexpectedModelBehavior as e:
|
|
399
400
|
await ui.muted(str(e))
|
|
400
401
|
patch_tool_messages(str(e), state_manager)
|
tunacode/constants.py
CHANGED
|
@@ -5,6 +5,7 @@ from typing import Any, Awaitable, Callable, Optional, Tuple
|
|
|
5
5
|
|
|
6
6
|
from tunacode.core.logging.logger import get_logger
|
|
7
7
|
from tunacode.core.state import StateManager
|
|
8
|
+
from tunacode.exceptions import UserAbortError
|
|
8
9
|
from tunacode.types import AgentState, UsageTrackerProtocol
|
|
9
10
|
from tunacode.ui.tool_descriptions import get_batch_description, get_tool_description
|
|
10
11
|
|
|
@@ -472,6 +473,8 @@ async def _process_tool_calls(
|
|
|
472
473
|
# Execute the tool with robust error handling so one failure doesn't crash the run
|
|
473
474
|
try:
|
|
474
475
|
await tool_callback(part, node)
|
|
476
|
+
except UserAbortError:
|
|
477
|
+
raise
|
|
475
478
|
except Exception as tool_err:
|
|
476
479
|
logger.error(
|
|
477
480
|
"Tool callback failed: tool=%s iter=%s err=%s",
|
tunacode/core/tool_handler.py
CHANGED
|
@@ -5,6 +5,7 @@ Tool handling business logic, separated from UI concerns.
|
|
|
5
5
|
from typing import Optional
|
|
6
6
|
|
|
7
7
|
from tunacode.constants import READ_ONLY_TOOLS
|
|
8
|
+
from tunacode.core.agents.agent_components.agent_helpers import create_user_message
|
|
8
9
|
from tunacode.core.state import StateManager
|
|
9
10
|
from tunacode.templates.loader import Template
|
|
10
11
|
from tunacode.types import (
|
|
@@ -86,6 +87,21 @@ class ToolHandler:
|
|
|
86
87
|
if response.skip_future:
|
|
87
88
|
self.state.session.tool_ignore.append(tool_name)
|
|
88
89
|
|
|
90
|
+
if not response.approved or response.abort:
|
|
91
|
+
guidance = getattr(response, "instructions", "").strip()
|
|
92
|
+
# CLAUDE_ANCHOR[3c8b1f70]: Route user rejection guidance back to the agent.
|
|
93
|
+
if guidance:
|
|
94
|
+
guidance_section = f"User guidance:\n{guidance}"
|
|
95
|
+
else:
|
|
96
|
+
guidance_section = "User cancelled without additional instructions."
|
|
97
|
+
|
|
98
|
+
message = (
|
|
99
|
+
f"Tool '{tool_name}' execution cancelled before running.\n"
|
|
100
|
+
f"{guidance_section}\n"
|
|
101
|
+
"Do not assume the operation succeeded; request updated guidance or offer alternatives."
|
|
102
|
+
)
|
|
103
|
+
create_user_message(message, self.state)
|
|
104
|
+
|
|
89
105
|
return response.approved and not response.abort
|
|
90
106
|
|
|
91
107
|
def create_confirmation_request(
|
tunacode/types.py
CHANGED
tunacode/ui/tool_ui.py
CHANGED
|
@@ -25,6 +25,11 @@ if TYPE_CHECKING:
|
|
|
25
25
|
class ToolUI:
|
|
26
26
|
"""Handles tool confirmation UI presentation."""
|
|
27
27
|
|
|
28
|
+
REJECTION_FEEDBACK_SESSION = "tool_rejection_feedback"
|
|
29
|
+
REJECTION_GUIDANCE_PROMPT = (
|
|
30
|
+
" Describe what the agent should do instead (leave blank to skip): "
|
|
31
|
+
)
|
|
32
|
+
|
|
28
33
|
def __init__(self):
|
|
29
34
|
self.colors = DotDict(UI_COLORS)
|
|
30
35
|
|
|
@@ -133,10 +138,14 @@ class ToolUI:
|
|
|
133
138
|
|
|
134
139
|
if resp == "2":
|
|
135
140
|
return ToolConfirmationResponse(approved=True, skip_future=True)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
141
|
+
if resp == "3":
|
|
142
|
+
instructions = await self._prompt_rejection_feedback(state_manager)
|
|
143
|
+
return ToolConfirmationResponse(
|
|
144
|
+
approved=False,
|
|
145
|
+
abort=True,
|
|
146
|
+
instructions=instructions,
|
|
147
|
+
)
|
|
148
|
+
return ToolConfirmationResponse(approved=True)
|
|
140
149
|
|
|
141
150
|
def show_sync_confirmation(self, request: ToolConfirmationRequest) -> ToolConfirmationResponse:
|
|
142
151
|
"""
|
|
@@ -189,10 +198,23 @@ class ToolUI:
|
|
|
189
198
|
|
|
190
199
|
if resp == "2":
|
|
191
200
|
return ToolConfirmationResponse(approved=True, skip_future=True)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
201
|
+
if resp == "3":
|
|
202
|
+
instructions = self._prompt_rejection_feedback_sync()
|
|
203
|
+
return ToolConfirmationResponse(approved=False, abort=True, instructions=instructions)
|
|
204
|
+
return ToolConfirmationResponse(approved=True)
|
|
205
|
+
|
|
206
|
+
async def _prompt_rejection_feedback(self, state_manager: Optional["StateManager"]) -> str:
|
|
207
|
+
guidance = await ui.input(
|
|
208
|
+
session_key=self.REJECTION_FEEDBACK_SESSION,
|
|
209
|
+
pretext=self.REJECTION_GUIDANCE_PROMPT,
|
|
210
|
+
state_manager=state_manager,
|
|
211
|
+
)
|
|
212
|
+
return guidance.strip() if guidance else ""
|
|
213
|
+
|
|
214
|
+
def _prompt_rejection_feedback_sync(self) -> str:
|
|
215
|
+
guidance = input(self.REJECTION_GUIDANCE_PROMPT).strip()
|
|
216
|
+
ui.console.print()
|
|
217
|
+
return guidance
|
|
196
218
|
|
|
197
219
|
async def log_mcp(self, title: str, args: ToolArgs) -> None:
|
|
198
220
|
"""
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
tunacode/__init__.py,sha256=yUul8igNYMfUrHnYfioIGAqvrH8b5BKiO_pt1wVnmd0,119
|
|
2
|
-
tunacode/constants.py,sha256=
|
|
3
|
-
tunacode/context.py,sha256=4xMzqhSBqtTLuXnPBkJzn0SA61G5kAQytFvVY8TDnYY,2395
|
|
2
|
+
tunacode/constants.py,sha256=4R41mrExdN-66GmxdgKDO8DI9YgeXVn8V1eBxmNrpm8,6168
|
|
4
3
|
tunacode/exceptions.py,sha256=m80njR-LqBXhFAEOPqCE7N2QPU4Fkjlf_f6CWKO0_Is,8479
|
|
5
4
|
tunacode/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
5
|
tunacode/setup.py,sha256=1GXwD1cNDkGcmylZbytdlGnnHzpsGIwnGpnN0OFp1F0,2022
|
|
7
|
-
tunacode/types.py,sha256=
|
|
6
|
+
tunacode/types.py,sha256=ofclq3gZF9xnd0WFaSNqC4OKaKX6kgdqznkNPIpG3cM,10523
|
|
8
7
|
tunacode/cli/__init__.py,sha256=zgs0UbAck8hfvhYsWhWOfBe5oK09ug2De1r4RuQZREA,55
|
|
9
8
|
tunacode/cli/main.py,sha256=MAKPFA4kGSFciqMdxnyp2r9XzVp8TfxvK6ztt7dvjwM,3445
|
|
10
|
-
tunacode/cli/repl.py,sha256=
|
|
9
|
+
tunacode/cli/repl.py,sha256=XzCb6Xx6-uwsTNcaJ2PP6w4Xwc7jY0GpidV1ZWDNpHU,23577
|
|
11
10
|
tunacode/cli/commands/__init__.py,sha256=J7MZofTaSgspAKP64OavPukj4l53qvkv_-sCfYEUi10,1794
|
|
12
11
|
tunacode/cli/commands/base.py,sha256=Ge_lNQA-GDfcb1Ap1oznCH3UrifBiHH3bA9DNL-tCDw,2519
|
|
13
12
|
tunacode/cli/commands/registry.py,sha256=J2zS2QZmVaUyOA6GEgYwKqh1bZ9QBbsuuxMYniF7YSA,15139
|
|
@@ -42,7 +41,7 @@ tunacode/configuration/settings.py,sha256=9wtIWBlLhW_ZBlLx-GA4XDfVZyGj2Gs6Zk49vk
|
|
|
42
41
|
tunacode/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
43
42
|
tunacode/core/code_index.py,sha256=2qxEn2eTIegV4F_gLeZO5lAOv8mkf4Y_t21whZ9F2Fk,17370
|
|
44
43
|
tunacode/core/state.py,sha256=4SzI_OPiL2VIDywtUavtE6jZnIJS7K2IVgj8AYcawW0,8706
|
|
45
|
-
tunacode/core/tool_handler.py,sha256=
|
|
44
|
+
tunacode/core/tool_handler.py,sha256=ImCqzq5NflovKSkrnUzf26I4E_orFt6K_vCU06Ex12Q,4426
|
|
46
45
|
tunacode/core/agents/__init__.py,sha256=ZOSvWhqBWX3ADzTUZ7ILY92xZ7VMXanjPQ_Sf37WYmU,1005
|
|
47
46
|
tunacode/core/agents/main.py,sha256=IFKuGy-3t9XJDAs4rO0oTPZ1xGkZAXxoK1efdgIN15Q,23251
|
|
48
47
|
tunacode/core/agents/utils.py,sha256=cQJbpXzMcixJ1TKdLsEcpJHMMMXiH6_qcT3wbnSQ9gc,9411
|
|
@@ -51,7 +50,7 @@ tunacode/core/agents/agent_components/agent_config.py,sha256=X4S5PCsndoHBd7EHHuN
|
|
|
51
50
|
tunacode/core/agents/agent_components/agent_helpers.py,sha256=m0sB0_ztHhRJYFnG2tmQyNmyZk_WnZExBE5jKIHN5lA,9121
|
|
52
51
|
tunacode/core/agents/agent_components/json_tool_parser.py,sha256=HuyNT0rs-ppx_gLAI2e0XMVGbR_F0WXZfP3sx38VoMg,3447
|
|
53
52
|
tunacode/core/agents/agent_components/message_handler.py,sha256=KJGOtb9VhumgZpxxwO45HrKLhU9_MwuoWRsSQwJviNU,3704
|
|
54
|
-
tunacode/core/agents/agent_components/node_processor.py,sha256=
|
|
53
|
+
tunacode/core/agents/agent_components/node_processor.py,sha256=WnN3K7d2V1B7VZ_qlEh94qY1V6a9y4NAzPdeAi0qucU,24854
|
|
55
54
|
tunacode/core/agents/agent_components/response_state.py,sha256=qnjRSQCYZzac04CcVc4gTvW8epxl4w-Vz0kPjsvM_Qg,4482
|
|
56
55
|
tunacode/core/agents/agent_components/result_wrapper.py,sha256=9CFK0wpsfZx2WT4PBHfkSv22GxL1gAQuUYVMlmYtCJU,1761
|
|
57
56
|
tunacode/core/agents/agent_components/state_transition.py,sha256=uyvLJriexosBDQIrxbVDLR_luvXAMG6tnDsX10mbZcI,4077
|
|
@@ -138,7 +137,7 @@ tunacode/ui/panels.py,sha256=DQETEuP2LnliXGWSXKIqtmA3qUD5uJD4hB05XMPikFQ,18319
|
|
|
138
137
|
tunacode/ui/path_heuristics.py,sha256=SkhGaM8WCRuK86vLwypbfhtI81PrXtOsWoz-P0CTsmQ,2221
|
|
139
138
|
tunacode/ui/prompt_manager.py,sha256=HUL6443pFPb41uDAnAKD-sZsrWd_VhWYRGwvrFH_9SI,5618
|
|
140
139
|
tunacode/ui/tool_descriptions.py,sha256=vk61JPIXy7gHNfJ--77maXgK6WwNwxqY47QYsw_a2uw,4126
|
|
141
|
-
tunacode/ui/tool_ui.py,sha256=
|
|
140
|
+
tunacode/ui/tool_ui.py,sha256=tB1w01ffPVtWJn1veutcUrOZE6yb7t7kMqzxVNPfZZs,8132
|
|
142
141
|
tunacode/ui/utils.py,sha256=yvoCTz8AOdRfV0XIqUX3sgg88g_wntV9yhnQP6WzAVs,114
|
|
143
142
|
tunacode/ui/validators.py,sha256=MMIMT1I2v0l2jIy-gxX_4GSApvUTi8XWIOACr_dmoBA,758
|
|
144
143
|
tunacode/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -158,8 +157,8 @@ tunacode/utils/system.py,sha256=J8KqJ4ZqQrNSnM5rrJxPeMk9z2xQQp6dWtI1SKBY1-0,1112
|
|
|
158
157
|
tunacode/utils/text_utils.py,sha256=HAwlT4QMy41hr53cDbbNeNo05MI461TpI9b_xdIv8EY,7288
|
|
159
158
|
tunacode/utils/token_counter.py,sha256=dmFuqVz4ywGFdLfAi5Mg9bAGf8v87Ek-mHU-R3fsYjI,2711
|
|
160
159
|
tunacode/utils/user_configuration.py,sha256=OA-L0BgWNbf9sWpc8lyivgLscwJdpdI8TAYbe0wRs1s,4836
|
|
161
|
-
tunacode_cli-0.0.77.
|
|
162
|
-
tunacode_cli-0.0.77.
|
|
163
|
-
tunacode_cli-0.0.77.
|
|
164
|
-
tunacode_cli-0.0.77.
|
|
165
|
-
tunacode_cli-0.0.77.
|
|
160
|
+
tunacode_cli-0.0.77.3.dist-info/METADATA,sha256=5JwyUQ4xJE5g5G60hnV1JaoFmd14KERAXjjXSA0ADzg,8904
|
|
161
|
+
tunacode_cli-0.0.77.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
162
|
+
tunacode_cli-0.0.77.3.dist-info/entry_points.txt,sha256=hbkytikj4dGu6rizPuAd_DGUPBGF191RTnhr9wdhORY,51
|
|
163
|
+
tunacode_cli-0.0.77.3.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
|
|
164
|
+
tunacode_cli-0.0.77.3.dist-info/RECORD,,
|
tunacode/context.py
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import subprocess
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from typing import Dict, List
|
|
5
|
-
|
|
6
|
-
from tunacode.utils.ripgrep import ripgrep
|
|
7
|
-
from tunacode.utils.system import list_cwd
|
|
8
|
-
|
|
9
|
-
logger = logging.getLogger(__name__)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
async def get_git_status() -> Dict[str, object]:
|
|
13
|
-
"""Return git branch and dirty state information."""
|
|
14
|
-
try:
|
|
15
|
-
result = subprocess.run(
|
|
16
|
-
["git", "status", "--porcelain", "--branch"],
|
|
17
|
-
capture_output=True,
|
|
18
|
-
text=True,
|
|
19
|
-
check=True,
|
|
20
|
-
timeout=5,
|
|
21
|
-
)
|
|
22
|
-
lines = result.stdout.splitlines()
|
|
23
|
-
branch_line = lines[0][2:] if lines else ""
|
|
24
|
-
branch = branch_line.split("...")[0]
|
|
25
|
-
ahead = behind = 0
|
|
26
|
-
if "[" in branch_line and "]" in branch_line:
|
|
27
|
-
bracket = branch_line.split("[", 1)[1].split("]", 1)[0]
|
|
28
|
-
for part in bracket.split(","):
|
|
29
|
-
if "ahead" in part:
|
|
30
|
-
ahead = int(part.split("ahead")[1].strip().strip(" ]"))
|
|
31
|
-
if "behind" in part:
|
|
32
|
-
behind = int(part.split("behind")[1].strip().strip(" ]"))
|
|
33
|
-
dirty = any(line for line in lines[1:])
|
|
34
|
-
return {"branch": branch, "ahead": ahead, "behind": behind, "dirty": dirty}
|
|
35
|
-
except Exception as e:
|
|
36
|
-
logger.warning(f"Failed to get git status: {e}")
|
|
37
|
-
return {}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
async def get_directory_structure(max_depth: int = 3) -> str:
|
|
41
|
-
"""Return a simple directory tree string."""
|
|
42
|
-
files = list_cwd(max_depth=max_depth)
|
|
43
|
-
lines: List[str] = []
|
|
44
|
-
for path in files:
|
|
45
|
-
depth = path.count("/")
|
|
46
|
-
indent = " " * depth
|
|
47
|
-
name = path.split("/")[-1]
|
|
48
|
-
lines.append(f"{indent}{name}")
|
|
49
|
-
return "\n".join(lines)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
async def get_code_style() -> str:
|
|
53
|
-
"""Concatenate contents of all AGENTS.md files up the directory tree."""
|
|
54
|
-
parts: List[str] = []
|
|
55
|
-
current = Path.cwd()
|
|
56
|
-
while True:
|
|
57
|
-
file = current / "AGENTS.md"
|
|
58
|
-
if file.exists():
|
|
59
|
-
try:
|
|
60
|
-
parts.append(file.read_text(encoding="utf-8"))
|
|
61
|
-
except Exception as e:
|
|
62
|
-
logger.debug(f"Failed to read AGENTS.md at {file}: {e}")
|
|
63
|
-
if current == current.parent:
|
|
64
|
-
break
|
|
65
|
-
current = current.parent
|
|
66
|
-
return "\n".join(parts)
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
async def get_claude_files() -> List[str]:
|
|
70
|
-
"""Return a list of additional AGENTS.md files in the repo."""
|
|
71
|
-
return ripgrep("AGENTS.md", ".")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|