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.
- klaude_code/cli/cost_cmd.py +1 -1
- klaude_code/cli/runtime.py +1 -8
- klaude_code/command/debug_cmd.py +1 -1
- klaude_code/command/export_online_cmd.py +4 -4
- klaude_code/command/fork_session_cmd.py +6 -6
- klaude_code/command/help_cmd.py +1 -1
- klaude_code/command/model_cmd.py +1 -1
- klaude_code/command/registry.py +10 -1
- klaude_code/command/release_notes_cmd.py +1 -1
- klaude_code/command/resume_cmd.py +2 -2
- klaude_code/command/status_cmd.py +2 -2
- klaude_code/command/terminal_setup_cmd.py +2 -2
- klaude_code/command/thinking_cmd.py +1 -1
- klaude_code/config/assets/builtin_config.yaml +4 -0
- klaude_code/const.py +5 -3
- klaude_code/core/executor.py +15 -36
- klaude_code/core/reminders.py +55 -68
- klaude_code/core/tool/__init__.py +0 -2
- klaude_code/core/tool/file/edit_tool.py +3 -2
- klaude_code/core/tool/todo/todo_write_tool.py +1 -2
- klaude_code/core/tool/tool_registry.py +3 -3
- klaude_code/protocol/events.py +1 -0
- klaude_code/protocol/message.py +3 -11
- klaude_code/protocol/model.py +79 -13
- klaude_code/protocol/op.py +0 -13
- klaude_code/protocol/op_handler.py +0 -5
- klaude_code/protocol/sub_agent/explore.py +0 -15
- klaude_code/protocol/sub_agent/task.py +1 -1
- klaude_code/protocol/sub_agent/web.py +1 -17
- klaude_code/protocol/tools.py +0 -1
- klaude_code/ui/modes/exec/display.py +2 -3
- klaude_code/ui/modes/repl/display.py +1 -1
- klaude_code/ui/modes/repl/event_handler.py +2 -10
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +5 -1
- klaude_code/ui/modes/repl/key_bindings.py +135 -1
- klaude_code/ui/modes/repl/renderer.py +2 -1
- klaude_code/ui/renderers/bash_syntax.py +36 -4
- klaude_code/ui/renderers/common.py +8 -6
- klaude_code/ui/renderers/developer.py +113 -97
- klaude_code/ui/renderers/metadata.py +28 -15
- klaude_code/ui/renderers/tools.py +17 -59
- klaude_code/ui/rich/markdown.py +69 -11
- klaude_code/ui/rich/theme.py +22 -17
- klaude_code/ui/terminal/selector.py +36 -17
- {klaude_code-2.0.0.dist-info → klaude_code-2.0.2.dist-info}/METADATA +1 -1
- {klaude_code-2.0.0.dist-info → klaude_code-2.0.2.dist-info}/RECORD +48 -50
- klaude_code/core/tool/file/move_tool.md +0 -41
- klaude_code/core/tool/file/move_tool.py +0 -435
- {klaude_code-2.0.0.dist-info → klaude_code-2.0.2.dist-info}/WHEEL +0 -0
- {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
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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=
|
|
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"
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
(
|
|
211
|
-
("
|
|
212
|
-
(
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
"",
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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="
|
|
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(
|
|
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
|
|
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()
|