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 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
- await ui.muted(MSG_OPERATION_ABORTED)
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
@@ -9,7 +9,7 @@ from enum import Enum
9
9
 
10
10
  # Application info
11
11
  APP_NAME = "TunaCode"
12
- APP_VERSION = "0.0.77.2"
12
+ APP_VERSION = "0.0.77.3"
13
13
 
14
14
 
15
15
  # File patterns
@@ -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",
@@ -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
@@ -129,6 +129,7 @@ class ToolConfirmationResponse:
129
129
  approved: bool
130
130
  skip_future: bool = False
131
131
  abort: bool = False
132
+ instructions: str = ""
132
133
 
133
134
 
134
135
  # =============================================================================
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
- elif resp == "3":
137
- return ToolConfirmationResponse(approved=False, abort=True)
138
- else:
139
- return ToolConfirmationResponse(approved=True)
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
- elif resp == "3":
193
- return ToolConfirmationResponse(approved=False, abort=True)
194
- else:
195
- return ToolConfirmationResponse(approved=True)
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tunacode-cli
3
- Version: 0.0.77.2
3
+ Version: 0.0.77.3
4
4
  Summary: Your agentic CLI developer.
5
5
  Project-URL: Homepage, https://tunacode.xyz/
6
6
  Project-URL: Repository, https://github.com/alchemiststudiosDOTai/tunacode
@@ -1,13 +1,12 @@
1
1
  tunacode/__init__.py,sha256=yUul8igNYMfUrHnYfioIGAqvrH8b5BKiO_pt1wVnmd0,119
2
- tunacode/constants.py,sha256=uOp0kTgQk6GFZXnx1gpSusnIlg72TxI11G-i5iMzmv0,6168
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=xNpDRjIRYg4qGNbl3EG8B13CWAWBoob9ekVm8_6dvnc,10496
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=QBw05NNypxuTYoV51FVoTfujJfiOTCIYzAr_1uoi0YA,23515
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=42yUfnq5jgk-0LK93JoJgtsXfVDTf-7hNXyKEfH2FM0,3626
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=oOi3Yuccpdmnlza6vZRr_wB2_OczV0OLcqvs0a3BJcA,24734
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=MVmBLXx6OTJVFLl58SpoW0KoStOrbAY9sc6XXMKgWtQ,7216
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.2.dist-info/METADATA,sha256=45BWpB2EYn2kWKFd3av6YyPnPsbGrEK4P4MLuO1w0Pg,8904
162
- tunacode_cli-0.0.77.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
163
- tunacode_cli-0.0.77.2.dist-info/entry_points.txt,sha256=hbkytikj4dGu6rizPuAd_DGUPBGF191RTnhr9wdhORY,51
164
- tunacode_cli-0.0.77.2.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
165
- tunacode_cli-0.0.77.2.dist-info/RECORD,,
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", ".")