klaude-code 2.0.2__py3-none-any.whl → 2.1.1__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 (157) hide show
  1. klaude_code/app/__init__.py +12 -0
  2. klaude_code/app/runtime.py +215 -0
  3. klaude_code/cli/auth_cmd.py +2 -2
  4. klaude_code/cli/config_cmd.py +2 -2
  5. klaude_code/cli/cost_cmd.py +1 -1
  6. klaude_code/cli/debug.py +12 -36
  7. klaude_code/cli/list_model.py +3 -3
  8. klaude_code/cli/main.py +17 -60
  9. klaude_code/cli/self_update.py +2 -187
  10. klaude_code/cli/session_cmd.py +2 -2
  11. klaude_code/config/config.py +1 -1
  12. klaude_code/config/select_model.py +1 -1
  13. klaude_code/const.py +9 -1
  14. klaude_code/core/agent.py +9 -62
  15. klaude_code/core/agent_profile.py +291 -0
  16. klaude_code/core/executor.py +335 -230
  17. klaude_code/core/manager/llm_clients_builder.py +1 -1
  18. klaude_code/core/manager/sub_agent_manager.py +16 -29
  19. klaude_code/core/reminders.py +84 -103
  20. klaude_code/core/task.py +12 -20
  21. klaude_code/core/tool/__init__.py +5 -19
  22. klaude_code/core/tool/context.py +84 -0
  23. klaude_code/core/tool/file/apply_patch_tool.py +18 -21
  24. klaude_code/core/tool/file/edit_tool.py +39 -42
  25. klaude_code/core/tool/file/read_tool.py +14 -9
  26. klaude_code/core/tool/file/write_tool.py +12 -13
  27. klaude_code/core/tool/report_back_tool.py +4 -1
  28. klaude_code/core/tool/shell/bash_tool.py +6 -11
  29. klaude_code/core/tool/sub_agent_tool.py +8 -7
  30. klaude_code/core/tool/todo/todo_write_tool.py +3 -9
  31. klaude_code/core/tool/todo/update_plan_tool.py +3 -5
  32. klaude_code/core/tool/tool_abc.py +2 -1
  33. klaude_code/core/tool/tool_registry.py +2 -33
  34. klaude_code/core/tool/tool_runner.py +13 -10
  35. klaude_code/core/tool/web/mermaid_tool.py +3 -1
  36. klaude_code/core/tool/web/web_fetch_tool.py +5 -3
  37. klaude_code/core/tool/web/web_search_tool.py +5 -3
  38. klaude_code/core/turn.py +87 -30
  39. klaude_code/llm/anthropic/client.py +1 -1
  40. klaude_code/llm/bedrock/client.py +1 -1
  41. klaude_code/llm/claude/client.py +1 -1
  42. klaude_code/llm/codex/client.py +1 -1
  43. klaude_code/llm/google/client.py +1 -1
  44. klaude_code/llm/openai_compatible/client.py +1 -1
  45. klaude_code/llm/openai_compatible/tool_call_accumulator.py +1 -1
  46. klaude_code/llm/openrouter/client.py +1 -1
  47. klaude_code/llm/openrouter/reasoning.py +1 -1
  48. klaude_code/llm/responses/client.py +1 -1
  49. klaude_code/protocol/commands.py +1 -0
  50. klaude_code/protocol/events/__init__.py +57 -0
  51. klaude_code/protocol/events/base.py +18 -0
  52. klaude_code/protocol/events/chat.py +20 -0
  53. klaude_code/protocol/events/lifecycle.py +22 -0
  54. klaude_code/protocol/events/metadata.py +15 -0
  55. klaude_code/protocol/events/streaming.py +43 -0
  56. klaude_code/protocol/events/system.py +53 -0
  57. klaude_code/protocol/events/tools.py +27 -0
  58. klaude_code/protocol/op.py +5 -0
  59. klaude_code/protocol/tools.py +0 -1
  60. klaude_code/session/session.py +6 -7
  61. klaude_code/skill/assets/create-plan/SKILL.md +76 -0
  62. klaude_code/skill/loader.py +32 -88
  63. klaude_code/skill/manager.py +38 -0
  64. klaude_code/skill/system_skills.py +1 -1
  65. klaude_code/tui/__init__.py +8 -0
  66. klaude_code/{command → tui/command}/__init__.py +3 -0
  67. klaude_code/{command → tui/command}/clear_cmd.py +2 -1
  68. klaude_code/tui/command/copy_cmd.py +53 -0
  69. klaude_code/{command → tui/command}/debug_cmd.py +3 -2
  70. klaude_code/{command → tui/command}/export_cmd.py +2 -1
  71. klaude_code/{command → tui/command}/export_online_cmd.py +2 -1
  72. klaude_code/{command → tui/command}/fork_session_cmd.py +4 -3
  73. klaude_code/{command → tui/command}/help_cmd.py +2 -1
  74. klaude_code/{command → tui/command}/model_cmd.py +4 -3
  75. klaude_code/{command → tui/command}/model_select.py +2 -2
  76. klaude_code/{command → tui/command}/prompt_command.py +4 -3
  77. klaude_code/{command → tui/command}/refresh_cmd.py +3 -1
  78. klaude_code/{command → tui/command}/registry.py +6 -5
  79. klaude_code/{command → tui/command}/release_notes_cmd.py +2 -1
  80. klaude_code/{command → tui/command}/resume_cmd.py +4 -3
  81. klaude_code/{command → tui/command}/status_cmd.py +2 -1
  82. klaude_code/{command → tui/command}/terminal_setup_cmd.py +2 -1
  83. klaude_code/{command → tui/command}/thinking_cmd.py +3 -2
  84. klaude_code/tui/commands.py +164 -0
  85. klaude_code/{ui/renderers → tui/components}/assistant.py +3 -3
  86. klaude_code/{ui/renderers → tui/components}/bash_syntax.py +2 -2
  87. klaude_code/{ui/renderers → tui/components}/common.py +1 -1
  88. klaude_code/{ui/renderers → tui/components}/developer.py +4 -4
  89. klaude_code/{ui/renderers → tui/components}/diffs.py +2 -2
  90. klaude_code/{ui/renderers → tui/components}/errors.py +2 -2
  91. klaude_code/{ui/renderers → tui/components}/metadata.py +7 -7
  92. klaude_code/{ui → tui/components}/rich/markdown.py +9 -23
  93. klaude_code/{ui → tui/components}/rich/status.py +2 -2
  94. klaude_code/{ui → tui/components}/rich/theme.py +3 -1
  95. klaude_code/{ui/renderers → tui/components}/sub_agent.py +23 -43
  96. klaude_code/{ui/renderers → tui/components}/thinking.py +3 -3
  97. klaude_code/{ui/renderers → tui/components}/tools.py +13 -17
  98. klaude_code/{ui/renderers → tui/components}/user_input.py +3 -20
  99. klaude_code/tui/display.py +85 -0
  100. klaude_code/{ui/modes/repl → tui/input}/__init__.py +1 -1
  101. klaude_code/{ui/modes/repl → tui/input}/completers.py +1 -1
  102. klaude_code/{ui/modes/repl/input_prompt_toolkit.py → tui/input/prompt_toolkit.py} +6 -6
  103. klaude_code/tui/machine.py +608 -0
  104. klaude_code/tui/renderer.py +707 -0
  105. klaude_code/tui/runner.py +321 -0
  106. klaude_code/tui/terminal/__init__.py +56 -0
  107. klaude_code/{ui → tui}/terminal/color.py +1 -1
  108. klaude_code/{ui → tui}/terminal/control.py +1 -1
  109. klaude_code/{ui → tui}/terminal/notifier.py +1 -1
  110. klaude_code/ui/__init__.py +6 -50
  111. klaude_code/ui/core/display.py +3 -3
  112. klaude_code/ui/core/input.py +2 -1
  113. klaude_code/ui/{modes/debug/display.py → debug_mode.py} +1 -1
  114. klaude_code/ui/{modes/exec/display.py → exec_mode.py} +0 -2
  115. klaude_code/ui/terminal/__init__.py +6 -54
  116. klaude_code/ui/terminal/title.py +31 -0
  117. klaude_code/update.py +163 -0
  118. {klaude_code-2.0.2.dist-info → klaude_code-2.1.1.dist-info}/METADATA +1 -1
  119. klaude_code-2.1.1.dist-info/RECORD +233 -0
  120. klaude_code/cli/runtime.py +0 -518
  121. klaude_code/core/prompt.py +0 -108
  122. klaude_code/core/tool/skill/skill_tool.md +0 -24
  123. klaude_code/core/tool/skill/skill_tool.py +0 -87
  124. klaude_code/core/tool/tool_context.py +0 -148
  125. klaude_code/protocol/events.py +0 -195
  126. klaude_code/skill/assets/dev-docs/SKILL.md +0 -108
  127. klaude_code/trace/__init__.py +0 -21
  128. klaude_code/ui/core/stage_manager.py +0 -48
  129. klaude_code/ui/modes/__init__.py +0 -1
  130. klaude_code/ui/modes/debug/__init__.py +0 -1
  131. klaude_code/ui/modes/exec/__init__.py +0 -1
  132. klaude_code/ui/modes/repl/display.py +0 -61
  133. klaude_code/ui/modes/repl/event_handler.py +0 -629
  134. klaude_code/ui/modes/repl/renderer.py +0 -464
  135. klaude_code/ui/renderers/__init__.py +0 -0
  136. klaude_code/ui/utils/__init__.py +0 -1
  137. klaude_code-2.0.2.dist-info/RECORD +0 -227
  138. /klaude_code/{trace/log.py → log.py} +0 -0
  139. /klaude_code/{command → tui/command}/command_abc.py +0 -0
  140. /klaude_code/{command → tui/command}/prompt-commit.md +0 -0
  141. /klaude_code/{command → tui/command}/prompt-init.md +0 -0
  142. /klaude_code/{core/tool/skill → tui/components}/__init__.py +0 -0
  143. /klaude_code/{ui/renderers → tui/components}/mermaid_viewer.py +0 -0
  144. /klaude_code/{ui → tui/components}/rich/__init__.py +0 -0
  145. /klaude_code/{ui → tui/components}/rich/cjk_wrap.py +0 -0
  146. /klaude_code/{ui → tui/components}/rich/code_panel.py +0 -0
  147. /klaude_code/{ui → tui/components}/rich/live.py +0 -0
  148. /klaude_code/{ui → tui/components}/rich/quote.py +0 -0
  149. /klaude_code/{ui → tui/components}/rich/searchable_text.py +0 -0
  150. /klaude_code/{ui/modes/repl → tui/input}/clipboard.py +0 -0
  151. /klaude_code/{ui/modes/repl → tui/input}/key_bindings.py +0 -0
  152. /klaude_code/{ui → tui}/terminal/image.py +0 -0
  153. /klaude_code/{ui → tui}/terminal/progress_bar.py +0 -0
  154. /klaude_code/{ui → tui}/terminal/selector.py +0 -0
  155. /klaude_code/ui/{utils/common.py → common.py} +0 -0
  156. {klaude_code-2.0.2.dist-info → klaude_code-2.1.1.dist-info}/WHEEL +0 -0
  157. {klaude_code-2.0.2.dist-info → klaude_code-2.1.1.dist-info}/entry_points.txt +0 -0
@@ -1,464 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import contextlib
4
- from collections.abc import Iterator
5
- from contextlib import contextmanager
6
- from dataclasses import dataclass
7
- from typing import Any
8
-
9
- from rich.console import Console, Group, RenderableType
10
- from rich.padding import Padding
11
- from rich.spinner import Spinner
12
- from rich.style import Style, StyleType
13
- from rich.text import Text
14
-
15
- from klaude_code.const import (
16
- MARKDOWN_STREAM_LIVE_REPAINT_ENABLED,
17
- STATUS_DEFAULT_TEXT,
18
- STREAM_MAX_HEIGHT_SHRINK_RESET_LINES,
19
- )
20
- from klaude_code.protocol import events, model, tools
21
- from klaude_code.ui.renderers import assistant as r_assistant
22
- from klaude_code.ui.renderers import developer as r_developer
23
- from klaude_code.ui.renderers import errors as r_errors
24
- from klaude_code.ui.renderers import mermaid_viewer as r_mermaid_viewer
25
- from klaude_code.ui.renderers import metadata as r_metadata
26
- from klaude_code.ui.renderers import sub_agent as r_sub_agent
27
- from klaude_code.ui.renderers import thinking as r_thinking
28
- from klaude_code.ui.renderers import tools as r_tools
29
- from klaude_code.ui.renderers import user_input as r_user_input
30
- from klaude_code.ui.renderers.common import truncate_head, truncate_middle
31
- from klaude_code.ui.rich import status as r_status
32
- from klaude_code.ui.rich.live import CropAboveLive, SingleLine
33
- from klaude_code.ui.rich.quote import Quote
34
- from klaude_code.ui.rich.status import BreathingSpinner, ShimmerStatusText
35
- from klaude_code.ui.rich.theme import ThemeKey, get_theme
36
-
37
-
38
- @dataclass
39
- class SessionStatus:
40
- color: Style | None = None
41
- color_index: int | None = None
42
- sub_agent_state: model.SubAgentState | None = None
43
-
44
-
45
- class REPLRenderer:
46
- """Render REPL content via a Rich console."""
47
-
48
- def __init__(self, theme: str | None = None):
49
- self.themes = get_theme(theme)
50
- self.console: Console = Console(theme=self.themes.app_theme)
51
- self.console.push_theme(self.themes.markdown_theme)
52
- self._bottom_live: CropAboveLive | None = None
53
- self._stream_renderable: RenderableType | None = None
54
- self._stream_max_height: int = 0
55
- self._stream_last_height: int = 0
56
- self._stream_last_width: int = 0
57
- self._spinner_visible: bool = False
58
-
59
- self._status_text: ShimmerStatusText = ShimmerStatusText(STATUS_DEFAULT_TEXT)
60
- self._status_spinner: Spinner = BreathingSpinner(
61
- r_status.spinner_name(),
62
- text=SingleLine(self._status_text),
63
- style=ThemeKey.STATUS_SPINNER,
64
- )
65
-
66
- self.session_map: dict[str, SessionStatus] = {}
67
- self.current_sub_agent_color: Style | None = None
68
- self.sub_agent_color_index = 0
69
-
70
- def register_session(self, session_id: str, sub_agent_state: model.SubAgentState | None = None) -> None:
71
- session_status = SessionStatus(
72
- sub_agent_state=sub_agent_state,
73
- )
74
- if sub_agent_state is not None:
75
- color, color_index = self.pick_sub_agent_color()
76
- session_status.color = color
77
- session_status.color_index = color_index
78
- self.session_map[session_id] = session_status
79
-
80
- def is_sub_agent_session(self, session_id: str) -> bool:
81
- return session_id in self.session_map and self.session_map[session_id].sub_agent_state is not None
82
-
83
- def should_display_sub_agent_thinking_header(self, session_id: str) -> bool:
84
- # Hardcoded: only show sub-agent thinking headers for ImageGen.
85
- status = self.session_map.get(session_id)
86
- if status is None or status.sub_agent_state is None:
87
- return False
88
- return status.sub_agent_state.sub_agent_type == "ImageGen"
89
-
90
- def _advance_sub_agent_color_index(self) -> None:
91
- palette_size = len(self.themes.sub_agent_colors)
92
- if palette_size == 0:
93
- self.sub_agent_color_index = 0
94
- return
95
- self.sub_agent_color_index = (self.sub_agent_color_index + 1) % palette_size
96
-
97
- def pick_sub_agent_color(self) -> tuple[Style, int]:
98
- self._advance_sub_agent_color_index()
99
- palette = self.themes.sub_agent_colors
100
- if not palette:
101
- return Style(), 0
102
- return palette[self.sub_agent_color_index], self.sub_agent_color_index
103
-
104
- def get_session_sub_agent_color(self, session_id: str) -> Style:
105
- status = self.session_map.get(session_id)
106
- if status and status.color:
107
- return status.color
108
- return Style()
109
-
110
- def get_session_sub_agent_background(self, session_id: str) -> Style:
111
- status = self.session_map.get(session_id)
112
- backgrounds = self.themes.sub_agent_backgrounds
113
- if status and status.color_index is not None and backgrounds:
114
- return backgrounds[status.color_index]
115
- return Style()
116
-
117
- @contextmanager
118
- def session_print_context(self, session_id: str) -> Iterator[None]:
119
- """Temporarily switch to sub-agent quote style."""
120
- if session_id in self.session_map and self.session_map[session_id].color:
121
- self.current_sub_agent_color = self.session_map[session_id].color
122
- try:
123
- yield
124
- finally:
125
- self.current_sub_agent_color = None
126
-
127
- def print(self, *objects: Any, style: StyleType | None = None, end: str = "\n") -> None:
128
- if self.current_sub_agent_color:
129
- if objects:
130
- content = objects[0] if len(objects) == 1 else objects
131
- self.console.print(Quote(content, style=self.current_sub_agent_color), overflow="ellipsis")
132
- return
133
- self.console.print(*objects, style=style, end=end, overflow="ellipsis")
134
-
135
- def display_tool_call(self, e: events.ToolCallEvent) -> None:
136
- if r_tools.is_sub_agent_tool(e.tool_name):
137
- return
138
- renderable = r_tools.render_tool_call(e)
139
- if renderable is not None:
140
- self.print(renderable)
141
-
142
- def display_tool_call_result(self, e: events.ToolResultEvent, *, is_sub_agent: bool = False) -> None:
143
- if r_tools.is_sub_agent_tool(e.tool_name):
144
- return
145
- # Sub-agent errors: show only first 2 lines
146
- if is_sub_agent and e.status == "error":
147
- error_msg = truncate_head(e.result)
148
- self.print(r_errors.render_tool_error(error_msg))
149
- return
150
- if not is_sub_agent and e.tool_name == tools.MERMAID and isinstance(e.ui_extra, model.MermaidLinkUIExtra):
151
- image_path = r_mermaid_viewer.download_mermaid_png(
152
- link=e.ui_extra.link,
153
- tool_call_id=e.tool_call_id,
154
- session_id=e.session_id,
155
- )
156
- if image_path is not None:
157
- self.display_image(str(image_path), height=None)
158
-
159
- renderable = r_tools.render_tool_result(e, code_theme=self.themes.code_theme, session_id=e.session_id)
160
- else:
161
- renderable = r_tools.render_tool_result(e, code_theme=self.themes.code_theme, session_id=e.session_id)
162
- if renderable is not None:
163
- self.print(renderable)
164
-
165
- def display_thinking(self, content: str) -> None:
166
- renderable = r_thinking.render_thinking(
167
- content,
168
- code_theme=self.themes.code_theme,
169
- style=ThemeKey.THINKING,
170
- )
171
- if renderable is not None:
172
- self.console.push_theme(theme=self.themes.thinking_markdown_theme)
173
- self.print(renderable)
174
- self.console.pop_theme()
175
- self.print()
176
-
177
- def display_thinking_header(self, header: str) -> None:
178
- """Display a single thinking header line.
179
-
180
- Used by sub-agent sessions to avoid verbose thinking streaming.
181
- """
182
-
183
- stripped = header.strip()
184
- if not stripped:
185
- return
186
- self.print(
187
- Text.assemble(
188
- (r_thinking.THINKING_MESSAGE_MARK, ThemeKey.THINKING),
189
- " ",
190
- (stripped, ThemeKey.THINKING_BOLD),
191
- )
192
- )
193
-
194
- async def replay_history(self, history_events: events.ReplayHistoryEvent) -> None:
195
- tool_call_dict: dict[str, events.ToolCallEvent] = {}
196
- self.print()
197
- for event in history_events.events:
198
- event_session_id = getattr(event, "session_id", history_events.session_id)
199
- is_sub_agent = self.is_sub_agent_session(event_session_id)
200
-
201
- with self.session_print_context(event_session_id):
202
- match event:
203
- case events.TaskStartEvent() as e:
204
- self.display_task_start(e)
205
- case events.TurnStartEvent():
206
- self.print()
207
- case events.AssistantImageDeltaEvent() as e:
208
- self.display_image(e.file_path)
209
- case events.AssistantMessageEvent() as e:
210
- if is_sub_agent:
211
- if self.should_display_sub_agent_thinking_header(event_session_id) and e.thinking_text:
212
- header = r_thinking.extract_last_bold_header(
213
- r_thinking.normalize_thinking_content(e.thinking_text)
214
- )
215
- if header:
216
- self.display_thinking_header(header)
217
- continue
218
- if e.thinking_text:
219
- self.display_thinking(e.thinking_text)
220
- renderable = r_assistant.render_assistant_message(e.content, code_theme=self.themes.code_theme)
221
- if renderable is not None:
222
- self.print(renderable)
223
- self.print()
224
- case events.DeveloperMessageEvent() as e:
225
- self.display_developer_message(e)
226
- self.display_command_output(e)
227
- case events.UserMessageEvent() as e:
228
- if is_sub_agent:
229
- continue
230
- self.print(r_user_input.render_user_input(e.content))
231
- case events.ToolCallEvent() as e:
232
- tool_call_dict[e.tool_call_id] = e
233
- case events.ToolResultEvent() as e:
234
- tool_call_event = tool_call_dict.get(e.tool_call_id)
235
- if tool_call_event is not None:
236
- self.display_tool_call(tool_call_event)
237
- tool_call_dict.pop(e.tool_call_id, None)
238
- if is_sub_agent:
239
- continue
240
- self.display_tool_call_result(e)
241
- case events.TaskMetadataEvent() as e:
242
- self.print()
243
- self.print(r_metadata.render_task_metadata(e))
244
- self.print()
245
- case events.InterruptEvent():
246
- self.print()
247
- self.print(r_user_input.render_interrupt())
248
- case events.ErrorEvent() as e:
249
- self.display_error(e)
250
- case events.TaskFinishEvent() as e:
251
- self.display_task_finish(e)
252
-
253
- def display_developer_message(self, e: events.DeveloperMessageEvent) -> None:
254
- if not r_developer.need_render_developer_message(e):
255
- return
256
- with self.session_print_context(e.session_id):
257
- self.print(r_developer.render_developer_message(e))
258
-
259
- def display_command_output(self, e: events.DeveloperMessageEvent) -> None:
260
- if not r_developer.get_command_output(e.item):
261
- return
262
- with self.session_print_context(e.session_id):
263
- self.print(r_developer.render_command_output(e))
264
- self.print()
265
-
266
- def display_welcome(self, event: events.WelcomeEvent) -> None:
267
- self.print(r_metadata.render_welcome(event))
268
-
269
- def display_user_message(self, event: events.UserMessageEvent) -> None:
270
- self.print(r_user_input.render_user_input(event.content))
271
-
272
- def display_task_start(self, event: events.TaskStartEvent) -> None:
273
- self.register_session(event.session_id, event.sub_agent_state)
274
- if event.sub_agent_state is not None:
275
- with self.session_print_context(event.session_id):
276
- self.print(
277
- r_sub_agent.render_sub_agent_call(
278
- event.sub_agent_state,
279
- self.get_session_sub_agent_color(event.session_id),
280
- )
281
- )
282
-
283
- def display_turn_start(self, event: events.TurnStartEvent) -> None:
284
- if not self.is_sub_agent_session(event.session_id):
285
- self.print()
286
-
287
- def display_assistant_message(self, content: str) -> None:
288
- renderable = r_assistant.render_assistant_message(content, code_theme=self.themes.code_theme)
289
- if renderable is not None:
290
- self.print(renderable)
291
- self.print()
292
-
293
- def display_image(self, file_path: str, height: int | None = 40) -> None:
294
- """Display an image in the terminal.
295
-
296
- Args:
297
- file_path: Path to the image file.
298
- height: Height in terminal lines for displaying the image.
299
- """
300
- from klaude_code.ui.terminal.image import print_kitty_image
301
-
302
- # Suspend the Live status bar while emitting raw terminal output to avoid
303
- # interleaving refreshes with Kitty graphics escape sequences.
304
- had_live = self._bottom_live is not None
305
- was_spinner_visible = self._spinner_visible
306
- has_stream = MARKDOWN_STREAM_LIVE_REPAINT_ENABLED and self._stream_renderable is not None
307
- resume_live = had_live and (was_spinner_visible or has_stream)
308
-
309
- if self._bottom_live is not None:
310
- with contextlib.suppress(Exception):
311
- self._bottom_live.stop()
312
- self._bottom_live = None
313
-
314
- try:
315
- print_kitty_image(file_path, height=height, file=self.console.file)
316
- finally:
317
- if resume_live:
318
- if was_spinner_visible:
319
- self.spinner_start()
320
- else:
321
- self._ensure_bottom_live_started()
322
- self._refresh_bottom_live()
323
-
324
- def display_task_metadata(self, event: events.TaskMetadataEvent) -> None:
325
- with self.session_print_context(event.session_id):
326
- self.print(r_metadata.render_task_metadata(event))
327
- self.print()
328
-
329
- def display_task_finish(self, event: events.TaskFinishEvent) -> None:
330
- if self.is_sub_agent_session(event.session_id):
331
- session_status = self.session_map.get(event.session_id)
332
- description = (
333
- session_status.sub_agent_state.sub_agent_desc
334
- if session_status and session_status.sub_agent_state
335
- else None
336
- )
337
- panel_style = self.get_session_sub_agent_background(event.session_id)
338
- with self.session_print_context(event.session_id):
339
- self.print(
340
- r_sub_agent.render_sub_agent_result(
341
- event.task_result,
342
- code_theme=self.themes.code_theme,
343
- has_structured_output=event.has_structured_output,
344
- description=description,
345
- panel_style=panel_style,
346
- )
347
- )
348
-
349
- def display_interrupt(self) -> None:
350
- self.print(r_user_input.render_interrupt())
351
-
352
- def display_error(self, event: events.ErrorEvent) -> None:
353
- if event.session_id:
354
- with self.session_print_context(event.session_id):
355
- self.print(r_errors.render_error(truncate_middle(event.error_message)))
356
- else:
357
- self.print(r_errors.render_error(truncate_middle(event.error_message)))
358
-
359
- # -------------------------------------------------------------------------
360
- # Spinner control methods
361
- # -------------------------------------------------------------------------
362
-
363
- def spinner_start(self) -> None:
364
- """Start the spinner animation."""
365
- self._spinner_visible = True
366
- self._ensure_bottom_live_started()
367
- self._refresh_bottom_live()
368
-
369
- def spinner_stop(self) -> None:
370
- """Stop the spinner animation."""
371
- self._spinner_visible = False
372
- self._refresh_bottom_live()
373
-
374
- def spinner_update(self, status_text: str | Text, right_text: RenderableType | None = None) -> None:
375
- """Update the spinner status text with optional right-aligned text."""
376
- self._status_text = ShimmerStatusText(status_text, right_text)
377
- self._status_spinner.update(text=SingleLine(self._status_text), style=ThemeKey.STATUS_SPINNER)
378
- self._refresh_bottom_live()
379
-
380
- def spinner_renderable(self) -> Spinner:
381
- """Return the spinner's renderable for embedding in other components."""
382
- return self._status_spinner
383
-
384
- def set_stream_renderable(self, renderable: RenderableType | None) -> None:
385
- """Set the current streaming renderable displayed above the status line."""
386
-
387
- if renderable is None:
388
- self._stream_renderable = None
389
- self._stream_max_height = 0
390
- self._stream_last_height = 0
391
- self._stream_last_width = 0
392
- self._refresh_bottom_live()
393
- return
394
-
395
- self._ensure_bottom_live_started()
396
- self._stream_renderable = renderable
397
-
398
- height = len(self.console.render_lines(renderable, self.console.options, pad=False))
399
- self._stream_last_height = height
400
- self._stream_last_width = self.console.size.width
401
-
402
- if self._stream_max_height - height > STREAM_MAX_HEIGHT_SHRINK_RESET_LINES:
403
- self._stream_max_height = height
404
- else:
405
- self._stream_max_height = max(self._stream_max_height, height)
406
- self._refresh_bottom_live()
407
-
408
- def _ensure_bottom_live_started(self) -> None:
409
- if self._bottom_live is not None:
410
- return
411
- self._bottom_live = CropAboveLive(
412
- Text(""),
413
- console=self.console,
414
- refresh_per_second=30,
415
- transient=True,
416
- redirect_stdout=False,
417
- redirect_stderr=False,
418
- )
419
- self._bottom_live.start()
420
-
421
- def _bottom_renderable(self) -> RenderableType:
422
- stream_part: RenderableType = Group()
423
- gap_part: RenderableType = Group()
424
-
425
- if MARKDOWN_STREAM_LIVE_REPAINT_ENABLED:
426
- stream = self._stream_renderable
427
- if stream is not None:
428
- current_width = self.console.size.width
429
- if self._stream_last_width != current_width:
430
- height = len(self.console.render_lines(stream, self.console.options, pad=False))
431
- self._stream_last_height = height
432
- self._stream_last_width = current_width
433
-
434
- if self._stream_max_height - height > STREAM_MAX_HEIGHT_SHRINK_RESET_LINES:
435
- self._stream_max_height = height
436
- else:
437
- self._stream_max_height = max(self._stream_max_height, height)
438
- else:
439
- height = self._stream_last_height
440
-
441
- pad_lines = max(self._stream_max_height - height, 0)
442
- if pad_lines:
443
- stream = Padding(stream, (0, 0, pad_lines, 0))
444
- stream_part = stream
445
-
446
- gap_part = Text("") if self._spinner_visible else Group()
447
-
448
- status_part: RenderableType = SingleLine(self._status_spinner) if self._spinner_visible else Group()
449
- return Group(stream_part, gap_part, status_part)
450
-
451
- def _refresh_bottom_live(self) -> None:
452
- if self._bottom_live is None:
453
- return
454
- self._bottom_live.update(self._bottom_renderable(), refresh=True)
455
-
456
- def stop_bottom_live(self) -> None:
457
- if self._bottom_live is None:
458
- return
459
- with contextlib.suppress(Exception):
460
- # Avoid cursor restore when stopping right before prompt_toolkit.
461
- # This will leave a blank line before prompt input
462
- self._bottom_live.transient = False
463
- self._bottom_live.stop()
464
- self._bottom_live = None
File without changes
@@ -1 +0,0 @@
1
- # UI utilities