klaude-code 2.0.0__py3-none-any.whl → 2.0.2__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.
Files changed (50) hide show
  1. klaude_code/cli/cost_cmd.py +1 -1
  2. klaude_code/cli/runtime.py +1 -8
  3. klaude_code/command/debug_cmd.py +1 -1
  4. klaude_code/command/export_online_cmd.py +4 -4
  5. klaude_code/command/fork_session_cmd.py +6 -6
  6. klaude_code/command/help_cmd.py +1 -1
  7. klaude_code/command/model_cmd.py +1 -1
  8. klaude_code/command/registry.py +10 -1
  9. klaude_code/command/release_notes_cmd.py +1 -1
  10. klaude_code/command/resume_cmd.py +2 -2
  11. klaude_code/command/status_cmd.py +2 -2
  12. klaude_code/command/terminal_setup_cmd.py +2 -2
  13. klaude_code/command/thinking_cmd.py +1 -1
  14. klaude_code/config/assets/builtin_config.yaml +4 -0
  15. klaude_code/const.py +5 -3
  16. klaude_code/core/executor.py +15 -36
  17. klaude_code/core/reminders.py +55 -68
  18. klaude_code/core/tool/__init__.py +0 -2
  19. klaude_code/core/tool/file/edit_tool.py +3 -2
  20. klaude_code/core/tool/todo/todo_write_tool.py +1 -2
  21. klaude_code/core/tool/tool_registry.py +3 -3
  22. klaude_code/protocol/events.py +1 -0
  23. klaude_code/protocol/message.py +3 -11
  24. klaude_code/protocol/model.py +79 -13
  25. klaude_code/protocol/op.py +0 -13
  26. klaude_code/protocol/op_handler.py +0 -5
  27. klaude_code/protocol/sub_agent/explore.py +0 -15
  28. klaude_code/protocol/sub_agent/task.py +1 -1
  29. klaude_code/protocol/sub_agent/web.py +1 -17
  30. klaude_code/protocol/tools.py +0 -1
  31. klaude_code/ui/modes/exec/display.py +2 -3
  32. klaude_code/ui/modes/repl/display.py +1 -1
  33. klaude_code/ui/modes/repl/event_handler.py +2 -10
  34. klaude_code/ui/modes/repl/input_prompt_toolkit.py +5 -1
  35. klaude_code/ui/modes/repl/key_bindings.py +135 -1
  36. klaude_code/ui/modes/repl/renderer.py +2 -1
  37. klaude_code/ui/renderers/bash_syntax.py +36 -4
  38. klaude_code/ui/renderers/common.py +8 -6
  39. klaude_code/ui/renderers/developer.py +113 -97
  40. klaude_code/ui/renderers/metadata.py +28 -15
  41. klaude_code/ui/renderers/tools.py +17 -59
  42. klaude_code/ui/rich/markdown.py +69 -11
  43. klaude_code/ui/rich/theme.py +22 -17
  44. klaude_code/ui/terminal/selector.py +36 -17
  45. {klaude_code-2.0.0.dist-info → klaude_code-2.0.2.dist-info}/METADATA +1 -1
  46. {klaude_code-2.0.0.dist-info → klaude_code-2.0.2.dist-info}/RECORD +48 -50
  47. klaude_code/core/tool/file/move_tool.md +0 -41
  48. klaude_code/core/tool/file/move_tool.py +0 -435
  49. {klaude_code-2.0.0.dist-info → klaude_code-2.0.2.dist-info}/WHEEL +0 -0
  50. {klaude_code-2.0.0.dist-info → klaude_code-2.0.2.dist-info}/entry_points.txt +0 -0
@@ -7,6 +7,8 @@ from pygments.lexers import BashLexer # pyright: ignore[reportUnknownVariableTy
7
7
  from pygments.token import Token
8
8
  from rich.text import Text
9
9
 
10
+ from klaude_code.const import BASH_MULTILINE_STRING_TRUNCATE_MAX_LINES
11
+ from klaude_code.ui.renderers.common import truncate_head
10
12
  from klaude_code.ui.rich.theme import ThemeKey
11
13
 
12
14
  # Token types for bash syntax highlighting
@@ -110,13 +112,34 @@ def _append_heredoc(result: Text, token_value: str) -> None:
110
112
  # Extra content on first line (e.g., "> file.py")
111
113
  if extra:
112
114
  result.append(extra, style=ThemeKey.BASH_ARGUMENT)
113
- # Body content
114
- result.append(body, style=ThemeKey.BASH_STRING)
115
+
116
+ # Body content (truncate to keep tool call rendering compact)
117
+ body_inner = body.strip("\n")
118
+ result.append("\n")
119
+ if body_inner:
120
+ body_text = truncate_head(
121
+ body_inner,
122
+ max_lines=BASH_MULTILINE_STRING_TRUNCATE_MAX_LINES,
123
+ base_style=ThemeKey.BASH_STRING,
124
+ truncated_style=ThemeKey.TOOL_RESULT_TRUNCATED,
125
+ )
126
+ result.append_text(body_text)
127
+ result.append("\n")
128
+
115
129
  # End delimiter
116
130
  result.append(end_delimiter, style=ThemeKey.BASH_HEREDOC_DELIMITER)
117
131
  else:
118
132
  # Fallback: couldn't parse heredoc structure
119
- result.append(token_value, style=ThemeKey.BASH_STRING)
133
+ if "\n" in token_value and len(token_value.splitlines()) > BASH_MULTILINE_STRING_TRUNCATE_MAX_LINES:
134
+ truncated = truncate_head(
135
+ token_value,
136
+ max_lines=BASH_MULTILINE_STRING_TRUNCATE_MAX_LINES,
137
+ base_style=ThemeKey.BASH_STRING,
138
+ truncated_style=ThemeKey.TOOL_RESULT_TRUNCATED,
139
+ )
140
+ result.append_text(truncated)
141
+ else:
142
+ result.append(token_value, style=ThemeKey.BASH_STRING)
120
143
 
121
144
 
122
145
  def highlight_bash_command(command: str) -> Text:
@@ -145,7 +168,16 @@ def highlight_bash_command(command: str) -> Text:
145
168
  if token_value.startswith("<<"):
146
169
  _append_heredoc(result, token_value)
147
170
  else:
148
- result.append(token_value, style=ThemeKey.BASH_STRING)
171
+ if "\n" in token_value and len(token_value.splitlines()) > BASH_MULTILINE_STRING_TRUNCATE_MAX_LINES:
172
+ truncated = truncate_head(
173
+ token_value,
174
+ max_lines=BASH_MULTILINE_STRING_TRUNCATE_MAX_LINES,
175
+ base_style=ThemeKey.BASH_STRING,
176
+ truncated_style=ThemeKey.TOOL_RESULT_TRUNCATED,
177
+ )
178
+ result.append_text(truncated)
179
+ else:
180
+ result.append(token_value, style=ThemeKey.BASH_STRING)
149
181
  expect_subcommand = False
150
182
  elif token_type in _OPERATOR_TOKENS:
151
183
  result.append(token_value, style=ThemeKey.BASH_OPERATOR)
@@ -1,3 +1,5 @@
1
+ from typing import Literal
2
+
1
3
  from rich.style import Style
2
4
  from rich.table import Table
3
5
  from rich.text import Text
@@ -12,10 +14,10 @@ from klaude_code.const import (
12
14
  from klaude_code.ui.rich.theme import ThemeKey
13
15
 
14
16
 
15
- def create_grid() -> Table:
17
+ def create_grid(*, overflow: Literal["fold", "crop", "ellipsis", "ignore"] = "fold") -> Table:
16
18
  grid = Table.grid(padding=(0, 1))
17
19
  grid.add_column(no_wrap=True)
18
- grid.add_column(overflow="fold")
20
+ grid.add_column(overflow=overflow)
19
21
  return grid
20
22
 
21
23
 
@@ -36,7 +38,7 @@ def truncate_middle(
36
38
  if max_lines <= 0:
37
39
  truncated_lines = text.split("\n")
38
40
  remaining = max(0, len(truncated_lines))
39
- return Text(f"… (more {remaining} lines)", style=ThemeKey.TOOL_RESULT_TRUNCATED)
41
+ return Text(f" … (more {remaining} lines)", style=ThemeKey.TOOL_RESULT_TRUNCATED)
40
42
 
41
43
  lines = text.split("\n")
42
44
  truncated_lines = 0
@@ -65,7 +67,7 @@ def truncate_middle(
65
67
  out.append(line[:max_line_length])
66
68
  out.append_text(
67
69
  Text(
68
- f"… (more {extra_chars} characters in this line)",
70
+ f" … (more {extra_chars} characters in this line)",
69
71
  style=ThemeKey.TOOL_RESULT_TRUNCATED,
70
72
  )
71
73
  )
@@ -82,7 +84,7 @@ def truncate_middle(
82
84
  out.append("\n")
83
85
 
84
86
  if truncated_lines > 0:
85
- out.append_text(Text(f" (more {truncated_lines} lines)\n", style=ThemeKey.TOOL_RESULT_TRUNCATED))
87
+ out.append_text(Text(f" (more {truncated_lines} lines)\n", style=ThemeKey.TOOL_RESULT_TRUNCATED))
86
88
 
87
89
  for idx, line in enumerate(tail_lines):
88
90
  append_line(out, line)
@@ -139,6 +141,6 @@ def truncate_head(
139
141
  out.append("\n")
140
142
 
141
143
  remaining = len(lines) - max_lines
142
- out.append_text(Text(f"… more {remaining} lines", style=truncated_style or ThemeKey.TOOL_RESULT_TRUNCATED))
144
+ out.append_text(Text(f" (more {remaining} lines)", style=truncated_style or ThemeKey.TOOL_RESULT_TRUNCATED))
143
145
 
144
146
  return out
@@ -12,15 +12,19 @@ from klaude_code.ui.rich.theme import ThemeKey
12
12
  REMINDER_BULLET = " ⧉"
13
13
 
14
14
 
15
+ def get_command_output(item: message.DeveloperMessage) -> model.CommandOutput | None:
16
+ if not item.ui_extra:
17
+ return None
18
+ for ui_item in item.ui_extra.items:
19
+ if isinstance(ui_item, model.CommandOutputUIItem):
20
+ return ui_item.output
21
+ return None
22
+
23
+
15
24
  def need_render_developer_message(e: events.DeveloperMessageEvent) -> bool:
16
- return bool(
17
- e.item.memory_paths
18
- or e.item.external_file_changes
19
- or e.item.todo_use
20
- or e.item.at_files
21
- or e.item.user_image_count
22
- or e.item.skill_name
23
- )
25
+ if not e.item.ui_extra:
26
+ return False
27
+ return any(not isinstance(ui_item, model.CommandOutputUIItem) for ui_item in e.item.ui_extra.items)
24
28
 
25
29
 
26
30
  def render_developer_message(e: events.DeveloperMessageEvent) -> RenderableType:
@@ -31,112 +35,124 @@ def render_developer_message(e: events.DeveloperMessageEvent) -> RenderableType:
31
35
  """
32
36
  parts: list[RenderableType] = []
33
37
 
34
- if mp := e.item.memory_paths:
35
- grid = create_grid()
36
- grid.add_row(
37
- Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
38
- Text.assemble(
39
- ("Load memory ", ThemeKey.REMINDER),
40
- Text(", ", ThemeKey.REMINDER).join(
41
- render_path(memory_path, ThemeKey.REMINDER_BOLD) for memory_path in mp
42
- ),
43
- ),
44
- )
45
- parts.append(grid)
46
-
47
- if fc := e.item.external_file_changes:
48
- grid = create_grid()
49
- for file_path in fc:
50
- grid.add_row(
51
- Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
52
- Text.assemble(
53
- ("Read ", ThemeKey.REMINDER),
54
- render_path(file_path, ThemeKey.REMINDER_BOLD),
55
- (" after external changes", ThemeKey.REMINDER),
56
- ),
57
- )
58
- parts.append(grid)
59
-
60
- if e.item.todo_use:
61
- grid = create_grid()
62
- grid.add_row(
63
- Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
64
- Text("Todo hasn't been updated recently", ThemeKey.REMINDER),
65
- )
66
- parts.append(grid)
67
-
68
- if e.item.at_files:
69
- grid = create_grid()
70
- # Group at_files by (operation, mentioned_in)
71
- grouped: dict[tuple[str, str | None], list[str]] = {}
72
- for at_file in e.item.at_files:
73
- key = (at_file.operation, at_file.mentioned_in)
74
- if key not in grouped:
75
- grouped[key] = []
76
- grouped[key].append(at_file.path)
77
-
78
- for (operation, mentioned_in), paths in grouped.items():
79
- path_texts = Text(", ", ThemeKey.REMINDER).join(render_path(p, ThemeKey.REMINDER_BOLD) for p in paths)
80
- if mentioned_in:
81
- grid.add_row(
82
- Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
83
- Text.assemble(
84
- (f"{operation} ", ThemeKey.REMINDER),
85
- path_texts,
86
- (" mentioned in ", ThemeKey.REMINDER),
87
- render_path(mentioned_in, ThemeKey.REMINDER_BOLD),
88
- ),
89
- )
90
- else:
91
- grid.add_row(
92
- Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
93
- Text.assemble(
94
- (f"{operation} ", ThemeKey.REMINDER),
95
- path_texts,
96
- ),
97
- )
98
- parts.append(grid)
99
-
100
- if uic := e.item.user_image_count:
101
- grid = create_grid()
102
- grid.add_row(
103
- Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
104
- Text(f"Attached {uic} image{'s' if uic > 1 else ''}", style=ThemeKey.REMINDER),
105
- )
106
- parts.append(grid)
107
-
108
- if sn := e.item.skill_name:
109
- grid = create_grid()
110
- grid.add_row(
111
- Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
112
- Text.assemble(
113
- ("Activated skill ", ThemeKey.REMINDER),
114
- (sn, ThemeKey.REMINDER_BOLD),
115
- ),
116
- )
117
- parts.append(grid)
38
+ if e.item.ui_extra:
39
+ for ui_item in e.item.ui_extra.items:
40
+ match ui_item:
41
+ case model.MemoryLoadedUIItem() as item:
42
+ grid = create_grid()
43
+ grid.add_row(
44
+ Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
45
+ Text.assemble(
46
+ ("Load memory ", ThemeKey.REMINDER),
47
+ Text(", ", ThemeKey.REMINDER).join(
48
+ render_path(mem.path, ThemeKey.REMINDER_BOLD) for mem in item.files
49
+ ),
50
+ ),
51
+ )
52
+ parts.append(grid)
53
+ case model.ExternalFileChangesUIItem() as item:
54
+ grid = create_grid()
55
+ for file_path in item.paths:
56
+ grid.add_row(
57
+ Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
58
+ Text.assemble(
59
+ ("Read ", ThemeKey.REMINDER),
60
+ render_path(file_path, ThemeKey.REMINDER_BOLD),
61
+ (" after external changes", ThemeKey.REMINDER),
62
+ ),
63
+ )
64
+ parts.append(grid)
65
+ case model.TodoReminderUIItem() as item:
66
+ match item.reason:
67
+ case "not_used_recently":
68
+ text = "Todo hasn't been updated recently"
69
+ case "empty":
70
+ text = "Todo list is empty"
71
+ case _:
72
+ text = "Todo reminder"
73
+ grid = create_grid()
74
+ grid.add_row(
75
+ Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
76
+ Text(text, ThemeKey.REMINDER),
77
+ )
78
+ parts.append(grid)
79
+ case model.AtFileOpsUIItem() as item:
80
+ grid = create_grid()
81
+ grouped: dict[tuple[str, str | None], list[str]] = {}
82
+ for op in item.ops:
83
+ key = (op.operation, op.mentioned_in)
84
+ grouped.setdefault(key, []).append(op.path)
85
+
86
+ for (operation, mentioned_in), paths in grouped.items():
87
+ path_texts = Text(", ", ThemeKey.REMINDER).join(
88
+ render_path(p, ThemeKey.REMINDER_BOLD) for p in paths
89
+ )
90
+ if mentioned_in:
91
+ grid.add_row(
92
+ Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
93
+ Text.assemble(
94
+ (f"{operation} ", ThemeKey.REMINDER),
95
+ path_texts,
96
+ (" mentioned in ", ThemeKey.REMINDER),
97
+ render_path(mentioned_in, ThemeKey.REMINDER_BOLD),
98
+ ),
99
+ )
100
+ else:
101
+ grid.add_row(
102
+ Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
103
+ Text.assemble(
104
+ (f"{operation} ", ThemeKey.REMINDER),
105
+ path_texts,
106
+ ),
107
+ )
108
+ parts.append(grid)
109
+ case model.UserImagesUIItem() as item:
110
+ grid = create_grid()
111
+ count = item.count
112
+ grid.add_row(
113
+ Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
114
+ Text(
115
+ f"Attached {count} image{'s' if count > 1 else ''}",
116
+ style=ThemeKey.REMINDER,
117
+ ),
118
+ )
119
+ parts.append(grid)
120
+ case model.SkillActivatedUIItem() as item:
121
+ grid = create_grid()
122
+ grid.add_row(
123
+ Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
124
+ Text.assemble(
125
+ ("Activated skill ", ThemeKey.REMINDER),
126
+ (item.name, ThemeKey.REMINDER_BOLD),
127
+ ),
128
+ )
129
+ parts.append(grid)
130
+ case model.CommandOutputUIItem():
131
+ # Rendered via render_command_output
132
+ pass
118
133
 
119
134
  return Group(*parts) if parts else Text("")
120
135
 
121
136
 
122
137
  def render_command_output(e: events.DeveloperMessageEvent) -> RenderableType:
123
138
  """Render developer command output content."""
124
- if not e.item.command_output:
139
+ command_output = get_command_output(e.item)
140
+ if not command_output:
125
141
  return Text("")
126
142
 
127
143
  content = message.join_text_parts(e.item.parts)
128
- match e.item.command_output.command_name:
144
+ match command_output.command_name:
129
145
  case commands.CommandName.HELP:
130
146
  return Padding.indent(Text.from_markup(content or ""), level=2)
131
147
  case commands.CommandName.STATUS:
132
- return _render_status_output(e.item.command_output)
148
+ return _render_status_output(command_output)
133
149
  case commands.CommandName.RELEASE_NOTES:
134
150
  return Padding.indent(NoInsetMarkdown(content or ""), level=2)
135
151
  case commands.CommandName.FORK_SESSION:
136
- return _render_fork_session_output(e.item.command_output)
152
+ return _render_fork_session_output(command_output)
137
153
  case _:
138
154
  content = content or "(no content)"
139
- style = ThemeKey.TOOL_RESULT if not e.item.command_output.is_error else ThemeKey.ERROR
155
+ style = ThemeKey.TOOL_RESULT if not command_output.is_error else ThemeKey.ERROR
140
156
  return Padding.indent(truncate_middle(content, base_style=style), level=2)
141
157
 
142
158
 
@@ -1,15 +1,14 @@
1
1
  from importlib.metadata import PackageNotFoundError, version
2
2
 
3
- from rich import box
4
3
  from rich.console import Group, RenderableType
5
4
  from rich.padding import Padding
6
- from rich.panel import Panel
7
5
  from rich.text import Text
8
6
 
9
7
  from klaude_code.const import DEFAULT_MAX_TOKENS
10
8
  from klaude_code.protocol import events, model
11
9
  from klaude_code.trace import is_debug_enabled
12
10
  from klaude_code.ui.renderers.common import create_grid
11
+ from klaude_code.ui.rich.quote import Quote
13
12
  from klaude_code.ui.rich.theme import ThemeKey
14
13
  from klaude_code.ui.utils.common import format_model_params, format_number
15
14
 
@@ -199,17 +198,29 @@ def render_task_metadata(e: events.TaskMetadataEvent) -> RenderableType:
199
198
 
200
199
 
201
200
  def render_welcome(e: events.WelcomeEvent) -> RenderableType:
202
- """Render the welcome panel with model info and settings."""
201
+ """Render the welcome panel with model info and settings.
202
+
203
+ Args:
204
+ e: The welcome event.
205
+ """
203
206
  debug_mode = is_debug_enabled()
204
207
 
205
- # First line: Klaude Code version
206
- klaude_code_style = ThemeKey.WELCOME_DEBUG_TITLE if debug_mode else ThemeKey.WELCOME_HIGHLIGHT_BOLD
207
- panel_content = Text.assemble(
208
- ("Klaude Code", klaude_code_style),
209
- (f" v{_get_version()}\n", ThemeKey.WELCOME_INFO),
210
- (str(e.llm_config.model), ThemeKey.WELCOME_HIGHLIGHT),
211
- (" @ ", ThemeKey.WELCOME_INFO),
212
- (e.llm_config.provider_name, ThemeKey.WELCOME_INFO),
208
+ panel_content = Text()
209
+
210
+ if e.show_klaude_code_info:
211
+ # First line: Klaude Code version
212
+ klaude_code_style = ThemeKey.WELCOME_DEBUG_TITLE if debug_mode else ThemeKey.WELCOME_HIGHLIGHT_BOLD
213
+ panel_content.append_text(Text("Klaude Code", style=klaude_code_style))
214
+ panel_content.append_text(Text(f" v{_get_version()}", style=ThemeKey.WELCOME_INFO))
215
+ panel_content.append_text(Text("\n"))
216
+
217
+ # Model line: model @ provider · params...
218
+ panel_content.append_text(
219
+ Text.assemble(
220
+ (str(e.llm_config.model), ThemeKey.WELCOME_HIGHLIGHT),
221
+ (" @ ", ThemeKey.WELCOME_INFO),
222
+ (e.llm_config.provider_name, ThemeKey.WELCOME_INFO),
223
+ )
213
224
  )
214
225
 
215
226
  # Use format_model_params for consistent formatting
@@ -228,7 +239,9 @@ def render_welcome(e: events.WelcomeEvent) -> RenderableType:
228
239
  )
229
240
 
230
241
  border_style = ThemeKey.WELCOME_DEBUG_BORDER if debug_mode else ThemeKey.LINES
231
- return Group(
232
- Panel.fit(panel_content, border_style=border_style, box=box.ROUNDED),
233
- "", # empty line
234
- )
242
+
243
+ if e.show_klaude_code_info:
244
+ groups = ["", Quote(panel_content, style=border_style, prefix="▌ "), ""]
245
+ else:
246
+ groups = [Quote(panel_content, style=border_style, prefix="▌ "), ""]
247
+ return Group(*groups)
@@ -33,7 +33,6 @@ MARK_PLAN = "◈"
33
33
  MARK_READ = "→"
34
34
  MARK_EDIT = "±"
35
35
  MARK_WRITE = "+"
36
- MARK_MOVE = "±"
37
36
  MARK_MERMAID = "⧉"
38
37
  MARK_WEB_FETCH = "→"
39
38
  MARK_WEB_SEARCH = "✱"
@@ -68,9 +67,7 @@ def _render_tool_call_tree(
68
67
  tool_name: str,
69
68
  details: RenderableType | None,
70
69
  ) -> RenderableType:
71
- # Keep the original 2-column layout (tool name on the left, details on the right),
72
- # but move the tool mark into the tree prefix so it can connect to the tool result.
73
- grid = create_grid()
70
+ grid = create_grid(overflow="ellipsis")
74
71
  grid.add_row(
75
72
  Text(tool_name, style=ThemeKey.TOOL_NAME),
76
73
  details if details is not None else Text(""),
@@ -138,15 +135,14 @@ def render_bash_tool_call(arguments: str) -> RenderableType:
138
135
  command = payload.get("command")
139
136
  timeout_ms = payload.get("timeout_ms")
140
137
 
141
- # Build the command display with optional timeout suffix
142
138
  if isinstance(command, str) and command.strip():
143
139
  cmd_str = command.strip()
144
- line_count = len(cmd_str.splitlines())
145
-
146
140
  highlighted = highlight_bash_command(cmd_str)
141
+ highlighted.stylize(ThemeKey.CODE_BACKGROUND)
142
+
143
+ display_line_count = len(highlighted.plain.splitlines())
147
144
 
148
- # For commands > threshold lines, use CodePanel for better display
149
- if line_count > BASH_OUTPUT_PANEL_THRESHOLD:
145
+ if display_line_count > BASH_OUTPUT_PANEL_THRESHOLD:
150
146
  code_panel = CodePanel(highlighted, border_style=ThemeKey.LINES)
151
147
  if isinstance(timeout_ms, int):
152
148
  if timeout_ms >= 1000 and timeout_ms % 1000 == 0:
@@ -281,36 +277,6 @@ def render_write_tool_call(arguments: str) -> RenderableType:
281
277
  return _render_tool_call_tree(mark=MARK_WRITE, tool_name=tool_name, details=details)
282
278
 
283
279
 
284
- def render_move_tool_call(arguments: str) -> RenderableType:
285
- tool_name = "Move"
286
-
287
- try:
288
- payload = json.loads(arguments)
289
- except json.JSONDecodeError:
290
- details = Text(
291
- arguments.strip()[:INVALID_TOOL_CALL_MAX_LENGTH],
292
- style=ThemeKey.INVALID_TOOL_CALL_ARGS,
293
- )
294
- return _render_tool_call_tree(mark=MARK_MOVE, tool_name=tool_name, details=details)
295
-
296
- source_path = payload.get("source_file_path", "")
297
- target_path = payload.get("target_file_path", "")
298
- start_line = payload.get("start_line", "")
299
- end_line = payload.get("end_line", "")
300
-
301
- # Build display: source:start-end -> target
302
- parts = Text()
303
- if source_path:
304
- parts.append_text(render_path(source_path, ThemeKey.TOOL_PARAM_FILE_PATH))
305
- if start_line and end_line:
306
- parts.append(f":{start_line}-{end_line}", style=ThemeKey.TOOL_PARAM)
307
- parts.append(" -> ", style=ThemeKey.TOOL_PARAM)
308
- if target_path:
309
- parts.append_text(render_path(target_path, ThemeKey.TOOL_PARAM_FILE_PATH))
310
-
311
- return _render_tool_call_tree(mark=MARK_MOVE, tool_name=tool_name, details=parts)
312
-
313
-
314
280
  def render_apply_patch_tool_call(arguments: str) -> RenderableType:
315
281
  tool_name = "Apply Patch"
316
282
 
@@ -386,15 +352,17 @@ def render_todo(tr: events.ToolResultEvent) -> RenderableType:
386
352
  def render_generic_tool_result(result: str, *, is_error: bool = False) -> RenderableType:
387
353
  """Render a generic tool result as truncated text."""
388
354
  style = ThemeKey.ERROR if is_error else ThemeKey.TOOL_RESULT
389
- return truncate_middle(result, base_style=style)
355
+ text = truncate_middle(result, base_style=style)
356
+ # Tool results should not reflow/wrap; use ellipsis when exceeding terminal width.
357
+ text.no_wrap = True
358
+ text.overflow = "ellipsis"
359
+ return text
390
360
 
391
361
 
392
362
  def _extract_mermaid_link(
393
363
  ui_extra: model.ToolResultUIExtra | None,
394
364
  ) -> model.MermaidLinkUIExtra | None:
395
- if isinstance(ui_extra, model.MermaidLinkUIExtra):
396
- return ui_extra
397
- return None
365
+ return ui_extra if isinstance(ui_extra, model.MermaidLinkUIExtra) else None
398
366
 
399
367
 
400
368
  def render_mermaid_tool_call(arguments: str) -> RenderableType:
@@ -443,7 +411,7 @@ def _render_mermaid_viewer_link(
443
411
  ) -> RenderableType:
444
412
  viewer_path = r_mermaid_viewer.build_viewer(code=link_info.code, link=link_info.link, tool_call_id=tr.tool_call_id)
445
413
  if viewer_path is None:
446
- return Text(link_info.link, style=ThemeKey.TOOL_RESULT_MERMAID, overflow="fold")
414
+ return Text(link_info.link, style=ThemeKey.TOOL_RESULT_MERMAID, overflow="ellipsis", no_wrap=True)
447
415
 
448
416
  display_path = str(viewer_path)
449
417
 
@@ -534,9 +502,7 @@ def render_mermaid_tool_result(
534
502
  def _extract_truncation(
535
503
  ui_extra: model.ToolResultUIExtra | None,
536
504
  ) -> model.TruncationUIExtra | None:
537
- if isinstance(ui_extra, model.TruncationUIExtra):
538
- return ui_extra
539
- return None
505
+ return ui_extra if isinstance(ui_extra, model.TruncationUIExtra) else None
540
506
 
541
507
 
542
508
  def render_truncation_info(ui_extra: model.TruncationUIExtra) -> RenderableType:
@@ -548,6 +514,8 @@ def render_truncation_info(ui_extra: model.TruncationUIExtra) -> RenderableType:
548
514
  (ui_extra.saved_file_path, ThemeKey.TOOL_RESULT_TRUNCATED),
549
515
  (f", {truncated_kb:.1f}KB truncated", ThemeKey.TOOL_RESULT_TRUNCATED),
550
516
  )
517
+ text.no_wrap = True
518
+ text.overflow = "ellipsis"
551
519
  return text
552
520
 
553
521
 
@@ -564,7 +532,6 @@ def render_report_back_tool_call() -> RenderableType:
564
532
  _TOOL_ACTIVE_FORM: dict[str, str] = {
565
533
  tools.BASH: "Bashing",
566
534
  tools.APPLY_PATCH: "Patching",
567
- tools.MOVE: "Moving",
568
535
  tools.EDIT: "Editing",
569
536
  tools.READ: "Reading",
570
537
  tools.WRITE: "Writing",
@@ -612,8 +579,6 @@ def render_tool_call(e: events.ToolCallEvent) -> RenderableType | None:
612
579
  return render_edit_tool_call(e.arguments)
613
580
  case tools.WRITE:
614
581
  return render_write_tool_call(e.arguments)
615
- case tools.MOVE:
616
- return render_move_tool_call(e.arguments)
617
582
  case tools.BASH:
618
583
  return render_bash_tool_call(e.arguments)
619
584
  case tools.APPLY_PATCH:
@@ -685,7 +650,7 @@ def render_tool_result(
685
650
 
686
651
  # Handle error case
687
652
  if e.status == "error" and e.ui_extra is None:
688
- return wrap(truncate_middle(e.result, base_style=ThemeKey.ERROR))
653
+ return wrap(render_generic_tool_result(e.result, is_error=True))
689
654
 
690
655
  # Render multiple ui blocks if present
691
656
  if isinstance(e.ui_extra, model.MultiUIExtra) and e.ui_extra.items:
@@ -694,7 +659,7 @@ def render_tool_result(
694
659
  if isinstance(item, model.MarkdownDocUIExtra):
695
660
  rendered.append(render_markdown_doc(item, code_theme=code_theme))
696
661
  elif isinstance(item, model.DiffUIExtra):
697
- show_file_name = e.tool_name in (tools.APPLY_PATCH, tools.MOVE)
662
+ show_file_name = e.tool_name == tools.APPLY_PATCH
698
663
  rendered.append(r_diffs.render_structured_diff(item, show_file_name=show_file_name))
699
664
  return wrap(Group(*rendered)) if rendered else None
700
665
 
@@ -721,11 +686,6 @@ def render_tool_result(
721
686
  if md_ui:
722
687
  return wrap(render_markdown_doc(md_ui, code_theme=code_theme))
723
688
  return wrap(r_diffs.render_structured_diff(diff_ui) if diff_ui else Text(""))
724
- case tools.MOVE:
725
- # Same-file move returns single DiffUIExtra, cross-file returns MultiUIExtra (handled above)
726
- if diff_ui:
727
- return wrap(r_diffs.render_structured_diff(diff_ui, show_file_name=True))
728
- return None
729
689
  case tools.APPLY_PATCH:
730
690
  if md_ui:
731
691
  return wrap(render_markdown_doc(md_ui, code_theme=code_theme))
@@ -737,8 +697,6 @@ def render_tool_result(
737
697
  case tools.MERMAID:
738
698
  return wrap(render_mermaid_tool_result(e, session_id=session_id))
739
699
  case tools.BASH:
740
- if e.result.startswith("diff --git"):
741
- return wrap(r_diffs.render_diff_panel(e.result, show_file_name=True))
742
700
  return _render_fallback()
743
701
  case _:
744
702
  return _render_fallback()