klaude-code 2.5.2__py3-none-any.whl → 2.6.0__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/auth/__init__.py +10 -0
- klaude_code/auth/env.py +77 -0
- klaude_code/cli/auth_cmd.py +89 -21
- klaude_code/cli/config_cmd.py +5 -5
- klaude_code/cli/cost_cmd.py +167 -68
- klaude_code/cli/main.py +51 -27
- klaude_code/cli/self_update.py +7 -7
- klaude_code/config/assets/builtin_config.yaml +45 -24
- klaude_code/config/builtin_config.py +23 -9
- klaude_code/config/config.py +19 -9
- klaude_code/config/model_matcher.py +1 -1
- klaude_code/const.py +2 -1
- klaude_code/core/tool/file/edit_tool.py +1 -1
- klaude_code/core/tool/file/read_tool.py +2 -2
- klaude_code/core/tool/file/write_tool.py +1 -1
- klaude_code/core/turn.py +21 -4
- klaude_code/llm/anthropic/client.py +75 -50
- klaude_code/llm/anthropic/input.py +20 -9
- klaude_code/llm/google/client.py +235 -148
- klaude_code/llm/google/input.py +44 -36
- klaude_code/llm/openai_compatible/stream.py +114 -100
- klaude_code/llm/openrouter/client.py +1 -0
- klaude_code/llm/openrouter/reasoning.py +4 -29
- klaude_code/llm/partial_message.py +2 -32
- klaude_code/llm/responses/client.py +99 -81
- klaude_code/llm/responses/input.py +11 -25
- klaude_code/llm/stream_parts.py +94 -0
- klaude_code/log.py +57 -0
- klaude_code/protocol/events.py +214 -0
- klaude_code/protocol/sub_agent/image_gen.py +0 -4
- klaude_code/session/session.py +51 -18
- klaude_code/tui/command/fork_session_cmd.py +14 -23
- klaude_code/tui/command/model_picker.py +2 -17
- klaude_code/tui/command/resume_cmd.py +2 -18
- klaude_code/tui/command/sub_agent_model_cmd.py +5 -19
- klaude_code/tui/command/thinking_cmd.py +2 -14
- klaude_code/tui/commands.py +0 -5
- klaude_code/tui/components/common.py +1 -1
- klaude_code/tui/components/metadata.py +21 -21
- klaude_code/tui/components/rich/quote.py +36 -8
- klaude_code/tui/components/rich/theme.py +2 -0
- klaude_code/tui/components/sub_agent.py +6 -0
- klaude_code/tui/display.py +11 -1
- klaude_code/tui/input/completers.py +11 -7
- klaude_code/tui/input/prompt_toolkit.py +3 -1
- klaude_code/tui/machine.py +108 -56
- klaude_code/tui/renderer.py +4 -65
- klaude_code/tui/terminal/selector.py +174 -31
- {klaude_code-2.5.2.dist-info → klaude_code-2.6.0.dist-info}/METADATA +23 -31
- {klaude_code-2.5.2.dist-info → klaude_code-2.6.0.dist-info}/RECORD +52 -58
- klaude_code/cli/session_cmd.py +0 -96
- klaude_code/protocol/events/__init__.py +0 -63
- klaude_code/protocol/events/base.py +0 -18
- klaude_code/protocol/events/chat.py +0 -30
- klaude_code/protocol/events/lifecycle.py +0 -23
- klaude_code/protocol/events/metadata.py +0 -16
- klaude_code/protocol/events/streaming.py +0 -43
- klaude_code/protocol/events/system.py +0 -56
- klaude_code/protocol/events/tools.py +0 -27
- {klaude_code-2.5.2.dist-info → klaude_code-2.6.0.dist-info}/WHEEL +0 -0
- {klaude_code-2.5.2.dist-info → klaude_code-2.6.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from typing import TYPE_CHECKING, Any, Self
|
|
2
2
|
|
|
3
|
+
from rich.cells import cell_len
|
|
3
4
|
from rich.console import Console, ConsoleOptions, RenderResult
|
|
5
|
+
from rich.measure import Measurement
|
|
4
6
|
from rich.segment import Segment
|
|
5
7
|
from rich.style import Style
|
|
6
8
|
|
|
@@ -16,10 +18,20 @@ class Quote:
|
|
|
16
18
|
self.prefix = prefix
|
|
17
19
|
self.style = style
|
|
18
20
|
|
|
21
|
+
def __rich_measure__(self, console: Console, options: ConsoleOptions) -> Measurement:
|
|
22
|
+
prefix_width = cell_len(self.prefix)
|
|
23
|
+
available_width = max(1, options.max_width - prefix_width)
|
|
24
|
+
content_measurement = Measurement.get(console, options.update(width=available_width), self.content)
|
|
25
|
+
|
|
26
|
+
minimum = min(options.max_width, content_measurement.minimum + prefix_width)
|
|
27
|
+
maximum = min(options.max_width, content_measurement.maximum + prefix_width)
|
|
28
|
+
return Measurement(minimum, maximum)
|
|
29
|
+
|
|
19
30
|
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
|
|
20
31
|
# Reduce width to leave space for prefix
|
|
21
|
-
prefix_width =
|
|
22
|
-
|
|
32
|
+
prefix_width = cell_len(self.prefix)
|
|
33
|
+
available_width = max(1, options.max_width - prefix_width)
|
|
34
|
+
render_options = options.update(width=available_width)
|
|
23
35
|
|
|
24
36
|
# Get style
|
|
25
37
|
quote_style = console.get_style(self.style) if isinstance(self.style, str) else self.style
|
|
@@ -29,7 +41,9 @@ class Quote:
|
|
|
29
41
|
new_line = Segment("\n")
|
|
30
42
|
|
|
31
43
|
# Render content as lines
|
|
32
|
-
|
|
44
|
+
# Avoid padding to full width.
|
|
45
|
+
# Trailing spaces can cause terminals to reflow wrapped lines on resize.
|
|
46
|
+
lines = console.render_lines(self.content, render_options, pad=False)
|
|
33
47
|
|
|
34
48
|
for line in lines:
|
|
35
49
|
yield prefix_segment
|
|
@@ -57,6 +71,19 @@ class TreeQuote:
|
|
|
57
71
|
self.style = style
|
|
58
72
|
self.style_first = style_first
|
|
59
73
|
|
|
74
|
+
def __rich_measure__(self, console: Console, options: ConsoleOptions) -> Measurement:
|
|
75
|
+
prefix_width = max(
|
|
76
|
+
cell_len(self.prefix_middle),
|
|
77
|
+
cell_len(self.prefix_last),
|
|
78
|
+
cell_len(self.prefix_first) if self.prefix_first is not None else 0,
|
|
79
|
+
)
|
|
80
|
+
available_width = max(1, options.max_width - prefix_width)
|
|
81
|
+
content_measurement = Measurement.get(console, options.update(width=available_width), self.content)
|
|
82
|
+
|
|
83
|
+
minimum = min(options.max_width, content_measurement.minimum + prefix_width)
|
|
84
|
+
maximum = min(options.max_width, content_measurement.maximum + prefix_width)
|
|
85
|
+
return Measurement(minimum, maximum)
|
|
86
|
+
|
|
60
87
|
@classmethod
|
|
61
88
|
def for_tool_call(cls, content: "RenderableType", *, mark: str, style: str, style_first: str) -> Self:
|
|
62
89
|
"""Create a tree quote for tool call display.
|
|
@@ -85,17 +112,18 @@ class TreeQuote:
|
|
|
85
112
|
def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
|
|
86
113
|
# Reduce width to leave space for prefix
|
|
87
114
|
prefix_width = max(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
115
|
+
cell_len(self.prefix_middle),
|
|
116
|
+
cell_len(self.prefix_last),
|
|
117
|
+
cell_len(self.prefix_first) if self.prefix_first is not None else 0,
|
|
91
118
|
)
|
|
92
|
-
|
|
119
|
+
available_width = max(1, options.max_width - prefix_width)
|
|
120
|
+
render_options = options.update(width=available_width)
|
|
93
121
|
|
|
94
122
|
quote_style = console.get_style(self.style) if isinstance(self.style, str) else self.style
|
|
95
123
|
first_style = console.get_style(self.style_first) if isinstance(self.style_first, str) else self.style_first
|
|
96
124
|
|
|
97
125
|
new_line = Segment("\n")
|
|
98
|
-
lines = console.render_lines(self.content, render_options)
|
|
126
|
+
lines = console.render_lines(self.content, render_options, pad=False)
|
|
99
127
|
line_count = len(lines)
|
|
100
128
|
|
|
101
129
|
for idx, line in enumerate(lines):
|
|
@@ -133,6 +133,7 @@ class ThemeKey(str, Enum):
|
|
|
133
133
|
METADATA = "metadata"
|
|
134
134
|
METADATA_DIM = "metadata.dim"
|
|
135
135
|
METADATA_BOLD = "metadata.bold"
|
|
136
|
+
METADATA_ITALIC = "metadata.italic"
|
|
136
137
|
# SPINNER_STATUS
|
|
137
138
|
STATUS_SPINNER = "spinner.status"
|
|
138
139
|
STATUS_TEXT = "spinner.status.text"
|
|
@@ -259,6 +260,7 @@ def get_theme(theme: str | None = None) -> Themes:
|
|
|
259
260
|
ThemeKey.METADATA.value: palette.lavender,
|
|
260
261
|
ThemeKey.METADATA_DIM.value: "dim " + palette.lavender,
|
|
261
262
|
ThemeKey.METADATA_BOLD.value: "bold " + palette.lavender,
|
|
263
|
+
ThemeKey.METADATA_ITALIC.value: "italic " + palette.lavender,
|
|
262
264
|
# STATUS
|
|
263
265
|
ThemeKey.STATUS_SPINNER.value: palette.blue,
|
|
264
266
|
ThemeKey.STATUS_TEXT.value: palette.blue,
|
|
@@ -135,6 +135,7 @@ def build_sub_agent_state_from_tool_call(e: events.ToolCallEvent) -> model.SubAg
|
|
|
135
135
|
description = profile.name
|
|
136
136
|
prompt = ""
|
|
137
137
|
output_schema: dict[str, Any] | None = None
|
|
138
|
+
generation: dict[str, Any] | None = None
|
|
138
139
|
resume: str | None = None
|
|
139
140
|
if e.arguments:
|
|
140
141
|
try:
|
|
@@ -155,10 +156,15 @@ def build_sub_agent_state_from_tool_call(e: events.ToolCallEvent) -> model.SubAg
|
|
|
155
156
|
schema_value = payload.get(profile.output_schema_arg)
|
|
156
157
|
if isinstance(schema_value, dict):
|
|
157
158
|
output_schema = cast(dict[str, Any], schema_value)
|
|
159
|
+
# Extract generation config for ImageGen
|
|
160
|
+
generation_value = payload.get("generation")
|
|
161
|
+
if isinstance(generation_value, dict):
|
|
162
|
+
generation = cast(dict[str, Any], generation_value)
|
|
158
163
|
return model.SubAgentState(
|
|
159
164
|
sub_agent_type=profile.name,
|
|
160
165
|
sub_agent_desc=description,
|
|
161
166
|
sub_agent_prompt=prompt,
|
|
162
167
|
resume=resume,
|
|
163
168
|
output_schema=output_schema,
|
|
169
|
+
generation=generation,
|
|
164
170
|
)
|
klaude_code/tui/display.py
CHANGED
|
@@ -30,8 +30,18 @@ class TUIDisplay(DisplayABC):
|
|
|
30
30
|
|
|
31
31
|
@override
|
|
32
32
|
async def consume_event(self, event: events.Event) -> None:
|
|
33
|
+
if isinstance(event, events.ReplayHistoryEvent):
|
|
34
|
+
await self._renderer.execute(self._machine.begin_replay())
|
|
35
|
+
for item in event.events:
|
|
36
|
+
commands = self._machine.transition_replay(item)
|
|
37
|
+
if commands:
|
|
38
|
+
await self._renderer.execute(commands)
|
|
39
|
+
await self._renderer.execute(self._machine.end_replay())
|
|
40
|
+
return
|
|
41
|
+
|
|
33
42
|
commands = self._machine.transition(event)
|
|
34
|
-
|
|
43
|
+
if commands:
|
|
44
|
+
await self._renderer.execute(commands)
|
|
35
45
|
|
|
36
46
|
@override
|
|
37
47
|
async def start(self) -> None:
|
|
@@ -313,11 +313,13 @@ class _AtFilesCompleter(Completer):
|
|
|
313
313
|
if not suggestions:
|
|
314
314
|
return [] # type: ignore[reportUnknownVariableType]
|
|
315
315
|
start_position = token_start_in_input - len(text_before)
|
|
316
|
-
|
|
316
|
+
suggestions_to_show = suggestions[: self._max_results]
|
|
317
|
+
align_width = self._display_align_width(suggestions_to_show)
|
|
318
|
+
for s in suggestions_to_show:
|
|
317
319
|
yield Completion(
|
|
318
320
|
text=self._format_completion_text(s, is_quoted=is_quoted),
|
|
319
321
|
start_position=start_position,
|
|
320
|
-
display=self._format_display_label(s,
|
|
322
|
+
display=self._format_display_label(s, align_width),
|
|
321
323
|
display_meta=s,
|
|
322
324
|
)
|
|
323
325
|
return [] # type: ignore[reportUnknownVariableType]
|
|
@@ -329,12 +331,14 @@ class _AtFilesCompleter(Completer):
|
|
|
329
331
|
|
|
330
332
|
# Prepare Completion objects. Replace from the '@' character.
|
|
331
333
|
start_position = token_start_in_input - len(text_before) # negative
|
|
332
|
-
|
|
334
|
+
suggestions_to_show = suggestions[: self._max_results]
|
|
335
|
+
align_width = self._display_align_width(suggestions_to_show)
|
|
336
|
+
for s in suggestions_to_show:
|
|
333
337
|
# Insert formatted text (with quoting when needed) so that subsequent typing does not keep triggering
|
|
334
338
|
yield Completion(
|
|
335
339
|
text=self._format_completion_text(s, is_quoted=is_quoted),
|
|
336
340
|
start_position=start_position,
|
|
337
|
-
display=self._format_display_label(s,
|
|
341
|
+
display=self._format_display_label(s, align_width),
|
|
338
342
|
display_meta=s,
|
|
339
343
|
)
|
|
340
344
|
|
|
@@ -543,9 +547,9 @@ class _AtFilesCompleter(Completer):
|
|
|
543
547
|
Keep this unstyled so that the completion menu's selection style can
|
|
544
548
|
fully override the selected row.
|
|
545
549
|
"""
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
return
|
|
550
|
+
name = self._display_name(suggestion)
|
|
551
|
+
# Pad to align_width + extra padding for visual separation from meta
|
|
552
|
+
return name.ljust(align_width + 6)
|
|
549
553
|
|
|
550
554
|
def _display_align_width(self, suggestions: list[str]) -> int:
|
|
551
555
|
"""Calculate alignment width for display labels."""
|
|
@@ -315,9 +315,11 @@ class PromptToolkitInput(InputProviderABC):
|
|
|
315
315
|
"msg": "",
|
|
316
316
|
"meta": "fg:ansibrightblack",
|
|
317
317
|
"frame.border": "fg:ansibrightblack dim",
|
|
318
|
-
"search_prefix": "
|
|
318
|
+
"search_prefix": "ansibrightblack",
|
|
319
319
|
"search_placeholder": "fg:ansibrightblack italic",
|
|
320
320
|
"search_input": "",
|
|
321
|
+
"search_success": "noinherit fg:ansigreen",
|
|
322
|
+
"search_none": "noinherit fg:ansired",
|
|
321
323
|
# Empty bottom-toolbar style
|
|
322
324
|
"bottom-toolbar": "bg:default fg:default noreverse",
|
|
323
325
|
"bottom-toolbar.text": "bg:default fg:default noreverse",
|
klaude_code/tui/machine.py
CHANGED
|
@@ -19,6 +19,7 @@ from klaude_code.tui.commands import (
|
|
|
19
19
|
EmitTmuxSignal,
|
|
20
20
|
EndAssistantStream,
|
|
21
21
|
EndThinkingStream,
|
|
22
|
+
PrintBlankLine,
|
|
22
23
|
PrintRuleLine,
|
|
23
24
|
RenderAssistantImage,
|
|
24
25
|
RenderCommand,
|
|
@@ -26,7 +27,6 @@ from klaude_code.tui.commands import (
|
|
|
26
27
|
RenderDeveloperMessage,
|
|
27
28
|
RenderError,
|
|
28
29
|
RenderInterrupt,
|
|
29
|
-
RenderReplayHistory,
|
|
30
30
|
RenderTaskFinish,
|
|
31
31
|
RenderTaskMetadata,
|
|
32
32
|
RenderTaskStart,
|
|
@@ -243,7 +243,24 @@ class SpinnerStatusState:
|
|
|
243
243
|
return Text(self._toast_status, style=ThemeKey.STATUS_TOAST)
|
|
244
244
|
|
|
245
245
|
activity_text = self._activity.get_activity_text()
|
|
246
|
-
|
|
246
|
+
todo_status = self._todo_status
|
|
247
|
+
reasoning_status = self._reasoning_status
|
|
248
|
+
|
|
249
|
+
if todo_status is not None:
|
|
250
|
+
base_status = todo_status
|
|
251
|
+
extra_reasoning = None if reasoning_status in (None, STATUS_THINKING_TEXT) else reasoning_status
|
|
252
|
+
else:
|
|
253
|
+
base_status = reasoning_status
|
|
254
|
+
extra_reasoning = None
|
|
255
|
+
|
|
256
|
+
if extra_reasoning is not None:
|
|
257
|
+
if activity_text is None:
|
|
258
|
+
activity_text = Text(extra_reasoning, style=ThemeKey.STATUS_TEXT_BOLD_ITALIC)
|
|
259
|
+
else:
|
|
260
|
+
prefixed = Text(extra_reasoning, style=ThemeKey.STATUS_TEXT_BOLD_ITALIC)
|
|
261
|
+
prefixed.append(" , ")
|
|
262
|
+
prefixed.append_text(activity_text)
|
|
263
|
+
activity_text = prefixed
|
|
247
264
|
|
|
248
265
|
if base_status:
|
|
249
266
|
# Default "Thinking ..." uses normal style; custom headers use bold italic
|
|
@@ -306,6 +323,7 @@ class _SessionState:
|
|
|
306
323
|
@property
|
|
307
324
|
def should_extract_reasoning_header(self) -> bool:
|
|
308
325
|
"""Gemini and GPT-5 models use markdown bold headers in thinking."""
|
|
326
|
+
return False # Temporarily disabled for all models
|
|
309
327
|
if self.model_id is None:
|
|
310
328
|
return False
|
|
311
329
|
model_lower = self.model_id.lower()
|
|
@@ -364,17 +382,25 @@ class DisplayStateMachine:
|
|
|
364
382
|
self._spinner.set_toast_status(None)
|
|
365
383
|
return self._spinner_update_commands()
|
|
366
384
|
|
|
385
|
+
def begin_replay(self) -> list[RenderCommand]:
|
|
386
|
+
self._spinner.reset()
|
|
387
|
+
return [SpinnerStop(), PrintBlankLine()]
|
|
388
|
+
|
|
389
|
+
def end_replay(self) -> list[RenderCommand]:
|
|
390
|
+
return [SpinnerStop()]
|
|
391
|
+
|
|
392
|
+
def transition_replay(self, event: events.Event) -> list[RenderCommand]:
|
|
393
|
+
return self._transition(event, is_replay=True)
|
|
394
|
+
|
|
367
395
|
def transition(self, event: events.Event) -> list[RenderCommand]:
|
|
396
|
+
return self._transition(event, is_replay=False)
|
|
397
|
+
|
|
398
|
+
def _transition(self, event: events.Event, *, is_replay: bool) -> list[RenderCommand]:
|
|
368
399
|
session_id = getattr(event, "session_id", "__app__")
|
|
369
400
|
s = self._session(session_id)
|
|
370
401
|
cmds: list[RenderCommand] = []
|
|
371
402
|
|
|
372
403
|
match event:
|
|
373
|
-
case events.ReplayHistoryEvent() as e:
|
|
374
|
-
cmds.append(RenderReplayHistory(e))
|
|
375
|
-
cmds.append(SpinnerStop())
|
|
376
|
-
return cmds
|
|
377
|
-
|
|
378
404
|
case events.WelcomeEvent() as e:
|
|
379
405
|
cmds.append(RenderWelcome(e))
|
|
380
406
|
return cmds
|
|
@@ -390,13 +416,16 @@ class DisplayStateMachine:
|
|
|
390
416
|
s.model_id = e.model_id
|
|
391
417
|
if not s.is_sub_agent:
|
|
392
418
|
self._set_primary_if_needed(e.session_id)
|
|
393
|
-
|
|
419
|
+
if not is_replay:
|
|
420
|
+
cmds.append(TaskClockStart())
|
|
394
421
|
else:
|
|
395
422
|
s.sub_agent_thinking_header = SubAgentThinkingHeaderState()
|
|
396
423
|
|
|
397
|
-
|
|
424
|
+
if not is_replay:
|
|
425
|
+
cmds.append(SpinnerStart())
|
|
398
426
|
cmds.append(RenderTaskStart(e))
|
|
399
|
-
|
|
427
|
+
if not is_replay:
|
|
428
|
+
cmds.extend(self._spinner_update_commands())
|
|
400
429
|
return cmds
|
|
401
430
|
|
|
402
431
|
case events.DeveloperMessageEvent() as e:
|
|
@@ -409,9 +438,10 @@ class DisplayStateMachine:
|
|
|
409
438
|
|
|
410
439
|
case events.TurnStartEvent() as e:
|
|
411
440
|
cmds.append(RenderTurnStart(e))
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
441
|
+
if not is_replay:
|
|
442
|
+
self._spinner.clear_for_new_turn()
|
|
443
|
+
self._spinner.set_reasoning_status(None)
|
|
444
|
+
cmds.extend(self._spinner_update_commands())
|
|
415
445
|
return cmds
|
|
416
446
|
|
|
417
447
|
case events.ThinkingStartEvent() as e:
|
|
@@ -423,9 +453,11 @@ class DisplayStateMachine:
|
|
|
423
453
|
s.thinking_tail = ""
|
|
424
454
|
# Ensure the status reflects that reasoning has started even
|
|
425
455
|
# before we receive any deltas (or a bold header).
|
|
426
|
-
|
|
456
|
+
if not is_replay:
|
|
457
|
+
self._spinner.set_reasoning_status(STATUS_THINKING_TEXT)
|
|
427
458
|
cmds.append(StartThinkingStream(session_id=e.session_id))
|
|
428
|
-
|
|
459
|
+
if not is_replay:
|
|
460
|
+
cmds.extend(self._spinner_update_commands())
|
|
429
461
|
return cmds
|
|
430
462
|
|
|
431
463
|
case events.ThinkingDeltaEvent() as e:
|
|
@@ -445,7 +477,7 @@ class DisplayStateMachine:
|
|
|
445
477
|
|
|
446
478
|
# Update reasoning status for spinner (based on bounded tail).
|
|
447
479
|
# Only extract headers for models that use markdown bold headers in thinking.
|
|
448
|
-
if s.should_extract_reasoning_header:
|
|
480
|
+
if not is_replay and s.should_extract_reasoning_header:
|
|
449
481
|
s.thinking_tail = (s.thinking_tail + e.content)[-8192:]
|
|
450
482
|
header = extract_last_bold_header(normalize_thinking_content(s.thinking_tail))
|
|
451
483
|
if header:
|
|
@@ -460,26 +492,31 @@ class DisplayStateMachine:
|
|
|
460
492
|
if not self._is_primary(e.session_id):
|
|
461
493
|
return []
|
|
462
494
|
s.thinking_stream_active = False
|
|
463
|
-
|
|
495
|
+
if not is_replay:
|
|
496
|
+
self._spinner.clear_default_reasoning_status()
|
|
464
497
|
cmds.append(EndThinkingStream(session_id=e.session_id))
|
|
465
|
-
|
|
466
|
-
|
|
498
|
+
if not is_replay:
|
|
499
|
+
cmds.append(SpinnerStart())
|
|
500
|
+
cmds.extend(self._spinner_update_commands())
|
|
467
501
|
return cmds
|
|
468
502
|
|
|
469
503
|
case events.AssistantTextStartEvent() as e:
|
|
470
504
|
if s.is_sub_agent:
|
|
471
|
-
|
|
472
|
-
|
|
505
|
+
if not is_replay:
|
|
506
|
+
self._spinner.set_composing(True)
|
|
507
|
+
cmds.extend(self._spinner_update_commands())
|
|
473
508
|
return cmds
|
|
474
509
|
if not self._is_primary(e.session_id):
|
|
475
510
|
return []
|
|
476
511
|
|
|
477
512
|
s.assistant_stream_active = True
|
|
478
513
|
s.assistant_char_count = 0
|
|
479
|
-
|
|
480
|
-
|
|
514
|
+
if not is_replay:
|
|
515
|
+
self._spinner.set_composing(True)
|
|
516
|
+
self._spinner.clear_tool_calls()
|
|
481
517
|
cmds.append(StartAssistantStream(session_id=e.session_id))
|
|
482
|
-
|
|
518
|
+
if not is_replay:
|
|
519
|
+
cmds.extend(self._spinner_update_commands())
|
|
483
520
|
return cmds
|
|
484
521
|
|
|
485
522
|
case events.AssistantTextDeltaEvent() as e:
|
|
@@ -489,24 +526,29 @@ class DisplayStateMachine:
|
|
|
489
526
|
return []
|
|
490
527
|
|
|
491
528
|
s.assistant_char_count += len(e.content)
|
|
492
|
-
|
|
529
|
+
if not is_replay:
|
|
530
|
+
self._spinner.set_buffer_length(s.assistant_char_count)
|
|
493
531
|
cmds.append(AppendAssistant(session_id=e.session_id, content=e.content))
|
|
494
|
-
|
|
532
|
+
if not is_replay:
|
|
533
|
+
cmds.extend(self._spinner_update_commands())
|
|
495
534
|
return cmds
|
|
496
535
|
|
|
497
536
|
case events.AssistantTextEndEvent() as e:
|
|
498
537
|
if s.is_sub_agent:
|
|
499
|
-
|
|
500
|
-
|
|
538
|
+
if not is_replay:
|
|
539
|
+
self._spinner.set_composing(False)
|
|
540
|
+
cmds.extend(self._spinner_update_commands())
|
|
501
541
|
return cmds
|
|
502
542
|
if not self._is_primary(e.session_id):
|
|
503
543
|
return []
|
|
504
544
|
|
|
505
545
|
s.assistant_stream_active = False
|
|
506
|
-
|
|
546
|
+
if not is_replay:
|
|
547
|
+
self._spinner.set_composing(False)
|
|
507
548
|
cmds.append(EndAssistantStream(session_id=e.session_id))
|
|
508
|
-
|
|
509
|
-
|
|
549
|
+
if not is_replay:
|
|
550
|
+
cmds.append(SpinnerStart())
|
|
551
|
+
cmds.extend(self._spinner_update_commands())
|
|
510
552
|
return cmds
|
|
511
553
|
|
|
512
554
|
case events.AssistantImageDeltaEvent() as e:
|
|
@@ -518,9 +560,10 @@ class DisplayStateMachine:
|
|
|
518
560
|
return []
|
|
519
561
|
if not self._is_primary(e.session_id):
|
|
520
562
|
return []
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
563
|
+
if not is_replay:
|
|
564
|
+
self._spinner.set_composing(False)
|
|
565
|
+
cmds.append(SpinnerStart())
|
|
566
|
+
cmds.extend(self._spinner_update_commands())
|
|
524
567
|
return cmds
|
|
525
568
|
|
|
526
569
|
case events.ToolCallStartEvent() as e:
|
|
@@ -535,18 +578,20 @@ class DisplayStateMachine:
|
|
|
535
578
|
primary.thinking_stream_active = False
|
|
536
579
|
cmds.append(EndThinkingStream(session_id=primary.session_id))
|
|
537
580
|
|
|
538
|
-
|
|
581
|
+
if not is_replay:
|
|
582
|
+
self._spinner.set_composing(False)
|
|
539
583
|
|
|
540
584
|
# Skip activity state for fast tools on non-streaming models (e.g., Gemini)
|
|
541
585
|
# to avoid flash-and-disappear effect
|
|
542
|
-
if not s.should_skip_tool_activity(e.tool_name):
|
|
586
|
+
if not is_replay and not s.should_skip_tool_activity(e.tool_name):
|
|
543
587
|
tool_active_form = get_tool_active_form(e.tool_name)
|
|
544
588
|
if is_sub_agent_tool(e.tool_name):
|
|
545
589
|
self._spinner.add_sub_agent_tool_call(e.tool_call_id, tool_active_form)
|
|
546
590
|
else:
|
|
547
591
|
self._spinner.add_tool_call(tool_active_form)
|
|
548
592
|
|
|
549
|
-
|
|
593
|
+
if not is_replay:
|
|
594
|
+
cmds.extend(self._spinner_update_commands())
|
|
550
595
|
return cmds
|
|
551
596
|
|
|
552
597
|
case events.ToolCallEvent() as e:
|
|
@@ -565,7 +610,7 @@ class DisplayStateMachine:
|
|
|
565
610
|
return cmds
|
|
566
611
|
|
|
567
612
|
case events.ToolResultEvent() as e:
|
|
568
|
-
if is_sub_agent_tool(e.tool_name):
|
|
613
|
+
if not is_replay and is_sub_agent_tool(e.tool_name):
|
|
569
614
|
self._spinner.finish_sub_agent_tool_call(e.tool_call_id, get_tool_active_form(e.tool_name))
|
|
570
615
|
cmds.extend(self._spinner_update_commands())
|
|
571
616
|
|
|
@@ -583,9 +628,10 @@ class DisplayStateMachine:
|
|
|
583
628
|
|
|
584
629
|
case events.TodoChangeEvent() as e:
|
|
585
630
|
todo_text = _extract_active_form_text(e)
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
631
|
+
if not is_replay:
|
|
632
|
+
self._spinner.set_todo_status(todo_text)
|
|
633
|
+
self._spinner.clear_for_new_turn()
|
|
634
|
+
cmds.extend(self._spinner_update_commands())
|
|
589
635
|
return cmds
|
|
590
636
|
|
|
591
637
|
case events.UsageEvent() as e:
|
|
@@ -595,7 +641,7 @@ class DisplayStateMachine:
|
|
|
595
641
|
if not self._is_primary(e.session_id):
|
|
596
642
|
return []
|
|
597
643
|
context_percent = e.usage.context_usage_percent
|
|
598
|
-
if context_percent is not None:
|
|
644
|
+
if not is_replay and context_percent is not None:
|
|
599
645
|
self._spinner.set_context_percent(context_percent)
|
|
600
646
|
cmds.extend(self._spinner_update_commands())
|
|
601
647
|
return cmds
|
|
@@ -606,37 +652,43 @@ class DisplayStateMachine:
|
|
|
606
652
|
case events.TaskFinishEvent() as e:
|
|
607
653
|
cmds.append(RenderTaskFinish(e))
|
|
608
654
|
if not s.is_sub_agent:
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
655
|
+
if not is_replay:
|
|
656
|
+
cmds.append(TaskClockClear())
|
|
657
|
+
self._spinner.reset()
|
|
658
|
+
cmds.append(SpinnerStop())
|
|
659
|
+
cmds.append(PrintRuleLine())
|
|
660
|
+
cmds.append(EmitTmuxSignal())
|
|
614
661
|
else:
|
|
615
662
|
s.sub_agent_thinking_header = None
|
|
616
663
|
return cmds
|
|
617
664
|
|
|
618
665
|
case events.InterruptEvent() as e:
|
|
619
|
-
|
|
620
|
-
|
|
666
|
+
if not is_replay:
|
|
667
|
+
self._spinner.reset()
|
|
668
|
+
cmds.append(SpinnerStop())
|
|
621
669
|
cmds.append(EndThinkingStream(session_id=e.session_id))
|
|
622
670
|
cmds.append(EndAssistantStream(session_id=e.session_id))
|
|
623
|
-
|
|
671
|
+
if not is_replay:
|
|
672
|
+
cmds.append(TaskClockClear())
|
|
624
673
|
cmds.append(RenderInterrupt(session_id=e.session_id))
|
|
625
674
|
return cmds
|
|
626
675
|
|
|
627
676
|
case events.ErrorEvent() as e:
|
|
628
|
-
|
|
677
|
+
if not is_replay:
|
|
678
|
+
cmds.append(EmitOsc94Error())
|
|
629
679
|
cmds.append(RenderError(e))
|
|
630
|
-
if not e.can_retry:
|
|
680
|
+
if not is_replay and not e.can_retry:
|
|
631
681
|
self._spinner.reset()
|
|
632
682
|
cmds.append(SpinnerStop())
|
|
633
|
-
|
|
683
|
+
if not is_replay:
|
|
684
|
+
cmds.extend(self._spinner_update_commands())
|
|
634
685
|
return cmds
|
|
635
686
|
|
|
636
687
|
case events.EndEvent():
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
688
|
+
if not is_replay:
|
|
689
|
+
self._spinner.reset()
|
|
690
|
+
cmds.append(SpinnerStop())
|
|
691
|
+
cmds.append(TaskClockClear())
|
|
640
692
|
return cmds
|
|
641
693
|
|
|
642
694
|
case _:
|
klaude_code/tui/renderer.py
CHANGED
|
@@ -35,7 +35,6 @@ from klaude_code.tui.commands import (
|
|
|
35
35
|
RenderDeveloperMessage,
|
|
36
36
|
RenderError,
|
|
37
37
|
RenderInterrupt,
|
|
38
|
-
RenderReplayHistory,
|
|
39
38
|
RenderTaskFinish,
|
|
40
39
|
RenderTaskMetadata,
|
|
41
40
|
RenderTaskStart,
|
|
@@ -64,7 +63,7 @@ from klaude_code.tui.components import thinking as c_thinking
|
|
|
64
63
|
from klaude_code.tui.components import tools as c_tools
|
|
65
64
|
from klaude_code.tui.components import user_input as c_user_input
|
|
66
65
|
from klaude_code.tui.components import welcome as c_welcome
|
|
67
|
-
from klaude_code.tui.components.common import truncate_head
|
|
66
|
+
from klaude_code.tui.components.common import truncate_head
|
|
68
67
|
from klaude_code.tui.components.rich import status as r_status
|
|
69
68
|
from klaude_code.tui.components.rich.live import CropAboveLive, SingleLine
|
|
70
69
|
from klaude_code.tui.components.rich.markdown import MarkdownStream, ThinkingMarkdown
|
|
@@ -437,67 +436,10 @@ class TUICommandRenderer:
|
|
|
437
436
|
Text.assemble(
|
|
438
437
|
(c_thinking.THINKING_MESSAGE_MARK, ThemeKey.THINKING),
|
|
439
438
|
" ",
|
|
440
|
-
(stripped, ThemeKey.
|
|
439
|
+
(stripped, ThemeKey.THINKING),
|
|
441
440
|
)
|
|
442
441
|
)
|
|
443
442
|
|
|
444
|
-
async def replay_history(self, history_events: events.ReplayHistoryEvent) -> None:
|
|
445
|
-
tool_call_dict: dict[str, events.ToolCallEvent] = {}
|
|
446
|
-
self.print()
|
|
447
|
-
for event in history_events.events:
|
|
448
|
-
event_session_id = getattr(event, "session_id", history_events.session_id)
|
|
449
|
-
is_sub_agent = self.is_sub_agent_session(event_session_id)
|
|
450
|
-
|
|
451
|
-
with self.session_print_context(event_session_id):
|
|
452
|
-
match event:
|
|
453
|
-
case events.TaskStartEvent() as e:
|
|
454
|
-
self.display_task_start(e)
|
|
455
|
-
case events.TurnStartEvent():
|
|
456
|
-
self.print()
|
|
457
|
-
case events.AssistantImageDeltaEvent() as e:
|
|
458
|
-
self.display_image(e.file_path)
|
|
459
|
-
case events.ResponseCompleteEvent() as e:
|
|
460
|
-
if is_sub_agent:
|
|
461
|
-
if self._should_display_sub_agent_thinking_header(event_session_id) and e.thinking_text:
|
|
462
|
-
header = c_thinking.extract_last_bold_header(
|
|
463
|
-
c_thinking.normalize_thinking_content(e.thinking_text)
|
|
464
|
-
)
|
|
465
|
-
if header:
|
|
466
|
-
self.display_thinking_header(header)
|
|
467
|
-
continue
|
|
468
|
-
if e.thinking_text:
|
|
469
|
-
self.display_thinking(e.thinking_text)
|
|
470
|
-
renderable = c_assistant.render_assistant_message(e.content, code_theme=self.themes.code_theme)
|
|
471
|
-
if renderable is not None:
|
|
472
|
-
self.print(renderable)
|
|
473
|
-
self.print()
|
|
474
|
-
case events.DeveloperMessageEvent() as e:
|
|
475
|
-
self.display_developer_message(e)
|
|
476
|
-
case events.UserMessageEvent() as e:
|
|
477
|
-
if is_sub_agent:
|
|
478
|
-
continue
|
|
479
|
-
self.print(c_user_input.render_user_input(e.content))
|
|
480
|
-
case events.ToolCallEvent() as e:
|
|
481
|
-
tool_call_dict[e.tool_call_id] = e
|
|
482
|
-
case events.ToolResultEvent() as e:
|
|
483
|
-
tool_call_event = tool_call_dict.get(e.tool_call_id)
|
|
484
|
-
if tool_call_event is not None:
|
|
485
|
-
self.display_tool_call(tool_call_event)
|
|
486
|
-
tool_call_dict.pop(e.tool_call_id, None)
|
|
487
|
-
if is_sub_agent:
|
|
488
|
-
continue
|
|
489
|
-
self.display_tool_call_result(e)
|
|
490
|
-
case events.TaskMetadataEvent() as e:
|
|
491
|
-
self.print(c_metadata.render_task_metadata(e))
|
|
492
|
-
self.print()
|
|
493
|
-
case events.InterruptEvent():
|
|
494
|
-
self.print()
|
|
495
|
-
self.print(c_user_input.render_interrupt())
|
|
496
|
-
case events.ErrorEvent() as e:
|
|
497
|
-
self.display_error(e)
|
|
498
|
-
case events.TaskFinishEvent() as e:
|
|
499
|
-
self.display_task_finish(e)
|
|
500
|
-
|
|
501
443
|
def display_developer_message(self, e: events.DeveloperMessageEvent) -> None:
|
|
502
444
|
if not c_developer.need_render_developer_message(e):
|
|
503
445
|
return
|
|
@@ -578,9 +520,9 @@ class TUICommandRenderer:
|
|
|
578
520
|
def display_error(self, event: events.ErrorEvent) -> None:
|
|
579
521
|
if event.session_id:
|
|
580
522
|
with self.session_print_context(event.session_id):
|
|
581
|
-
self.print(c_errors.render_error(
|
|
523
|
+
self.print(c_errors.render_error(Text(event.error_message)))
|
|
582
524
|
else:
|
|
583
|
-
self.print(c_errors.render_error(
|
|
525
|
+
self.print(c_errors.render_error(Text(event.error_message)))
|
|
584
526
|
|
|
585
527
|
# ---------------------------------------------------------------------
|
|
586
528
|
# Notifications
|
|
@@ -615,9 +557,6 @@ class TUICommandRenderer:
|
|
|
615
557
|
async def execute(self, commands: list[RenderCommand]) -> None:
|
|
616
558
|
for cmd in commands:
|
|
617
559
|
match cmd:
|
|
618
|
-
case RenderReplayHistory(event=event):
|
|
619
|
-
await self.replay_history(event)
|
|
620
|
-
self.spinner_stop()
|
|
621
560
|
case RenderWelcome(event=event):
|
|
622
561
|
self.display_welcome(event)
|
|
623
562
|
case RenderUserMessage(event=event):
|