klaude-code 2.5.3__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 +87 -8
- klaude_code/cli/config_cmd.py +5 -5
- klaude_code/cli/cost_cmd.py +159 -60
- klaude_code/cli/main.py +52 -61
- klaude_code/cli/self_update.py +7 -7
- klaude_code/config/builtin_config.py +23 -9
- klaude_code/config/config.py +19 -9
- klaude_code/core/turn.py +7 -8
- klaude_code/llm/google/client.py +12 -0
- klaude_code/llm/openai_compatible/stream.py +5 -1
- klaude_code/llm/openrouter/client.py +1 -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/commands.py +0 -5
- klaude_code/tui/components/metadata.py +4 -5
- 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/machine.py +89 -55
- klaude_code/tui/renderer.py +1 -62
- {klaude_code-2.5.3.dist-info → klaude_code-2.6.0.dist-info}/METADATA +23 -31
- {klaude_code-2.5.3.dist-info → klaude_code-2.6.0.dist-info}/RECORD +27 -34
- klaude_code/cli/session_cmd.py +0 -87
- 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.3.dist-info → klaude_code-2.6.0.dist-info}/WHEEL +0 -0
- {klaude_code-2.5.3.dist-info → klaude_code-2.6.0.dist-info}/entry_points.txt +0 -0
klaude_code/session/session.py
CHANGED
|
@@ -304,24 +304,57 @@ class Session(BaseModel):
|
|
|
304
304
|
yield events.TurnStartEvent(session_id=self.id)
|
|
305
305
|
match it:
|
|
306
306
|
case message.AssistantMessage() as am:
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
last_assistant_content = message.format_saved_images(
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
307
|
+
all_images = [part for part in am.parts if isinstance(part, message.ImageFilePart)]
|
|
308
|
+
full_content = message.join_text_parts(am.parts)
|
|
309
|
+
last_assistant_content = message.format_saved_images(all_images, full_content)
|
|
310
|
+
|
|
311
|
+
# Reconstruct streaming boundaries from saved parts.
|
|
312
|
+
# This allows replay to reuse the same TUI state machine as live events.
|
|
313
|
+
thinking_open = False
|
|
314
|
+
assistant_open = False
|
|
315
|
+
|
|
316
|
+
for part in am.parts:
|
|
317
|
+
if isinstance(part, message.ThinkingTextPart):
|
|
318
|
+
if assistant_open:
|
|
319
|
+
assistant_open = False
|
|
320
|
+
yield events.AssistantTextEndEvent(response_id=am.response_id, session_id=self.id)
|
|
321
|
+
if not thinking_open:
|
|
322
|
+
thinking_open = True
|
|
323
|
+
yield events.ThinkingStartEvent(response_id=am.response_id, session_id=self.id)
|
|
324
|
+
if part.text:
|
|
325
|
+
yield events.ThinkingDeltaEvent(
|
|
326
|
+
content=part.text,
|
|
327
|
+
response_id=am.response_id,
|
|
328
|
+
session_id=self.id,
|
|
329
|
+
)
|
|
330
|
+
continue
|
|
331
|
+
|
|
332
|
+
if thinking_open:
|
|
333
|
+
thinking_open = False
|
|
334
|
+
yield events.ThinkingEndEvent(response_id=am.response_id, session_id=self.id)
|
|
335
|
+
|
|
336
|
+
if isinstance(part, message.TextPart):
|
|
337
|
+
if not assistant_open:
|
|
338
|
+
assistant_open = True
|
|
339
|
+
yield events.AssistantTextStartEvent(response_id=am.response_id, session_id=self.id)
|
|
340
|
+
if part.text:
|
|
341
|
+
yield events.AssistantTextDeltaEvent(
|
|
342
|
+
content=part.text,
|
|
343
|
+
response_id=am.response_id,
|
|
344
|
+
session_id=self.id,
|
|
345
|
+
)
|
|
346
|
+
elif isinstance(part, message.ImageFilePart):
|
|
347
|
+
yield events.AssistantImageDeltaEvent(
|
|
348
|
+
file_path=part.file_path,
|
|
349
|
+
response_id=am.response_id,
|
|
350
|
+
session_id=self.id,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
if thinking_open:
|
|
354
|
+
yield events.ThinkingEndEvent(response_id=am.response_id, session_id=self.id)
|
|
355
|
+
if assistant_open:
|
|
356
|
+
yield events.AssistantTextEndEvent(response_id=am.response_id, session_id=self.id)
|
|
357
|
+
|
|
325
358
|
for part in am.parts:
|
|
326
359
|
if not isinstance(part, message.ToolCallPart):
|
|
327
360
|
continue
|
klaude_code/tui/commands.py
CHANGED
|
@@ -13,11 +13,6 @@ class RenderCommand:
|
|
|
13
13
|
pass
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
@dataclass(frozen=True, slots=True)
|
|
17
|
-
class RenderReplayHistory(RenderCommand):
|
|
18
|
-
event: events.ReplayHistoryEvent
|
|
19
|
-
|
|
20
|
-
|
|
21
16
|
@dataclass(frozen=True, slots=True)
|
|
22
17
|
class RenderWelcome(RenderCommand):
|
|
23
18
|
event: events.WelcomeEvent
|
|
@@ -30,13 +30,12 @@ def _render_task_metadata_block(
|
|
|
30
30
|
currency = metadata.usage.currency if metadata.usage else "USD"
|
|
31
31
|
currency_symbol = "¥" if currency == "CNY" else "$"
|
|
32
32
|
|
|
33
|
-
# Second column: model
|
|
33
|
+
# Second column: provider/model description / tokens / cost / …
|
|
34
34
|
content = Text()
|
|
35
|
-
content.append_text(Text(metadata.model_name, style=ThemeKey.METADATA_BOLD))
|
|
36
35
|
if metadata.provider is not None:
|
|
37
|
-
content.append_text(Text("
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
content.append_text(Text(metadata.provider.lower().replace(" ", "-"), style=ThemeKey.METADATA))
|
|
37
|
+
content.append_text(Text("/", style=ThemeKey.METADATA_DIM))
|
|
38
|
+
content.append_text(Text(metadata.model_name, style=ThemeKey.METADATA_BOLD))
|
|
40
39
|
if metadata.description:
|
|
41
40
|
content.append_text(Text(" ", style=ThemeKey.METADATA)).append_text(
|
|
42
41
|
Text(metadata.description, style=ThemeKey.METADATA_ITALIC)
|
|
@@ -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."""
|
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,
|
|
@@ -382,17 +382,25 @@ class DisplayStateMachine:
|
|
|
382
382
|
self._spinner.set_toast_status(None)
|
|
383
383
|
return self._spinner_update_commands()
|
|
384
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
|
+
|
|
385
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]:
|
|
386
399
|
session_id = getattr(event, "session_id", "__app__")
|
|
387
400
|
s = self._session(session_id)
|
|
388
401
|
cmds: list[RenderCommand] = []
|
|
389
402
|
|
|
390
403
|
match event:
|
|
391
|
-
case events.ReplayHistoryEvent() as e:
|
|
392
|
-
cmds.append(RenderReplayHistory(e))
|
|
393
|
-
cmds.append(SpinnerStop())
|
|
394
|
-
return cmds
|
|
395
|
-
|
|
396
404
|
case events.WelcomeEvent() as e:
|
|
397
405
|
cmds.append(RenderWelcome(e))
|
|
398
406
|
return cmds
|
|
@@ -408,13 +416,16 @@ class DisplayStateMachine:
|
|
|
408
416
|
s.model_id = e.model_id
|
|
409
417
|
if not s.is_sub_agent:
|
|
410
418
|
self._set_primary_if_needed(e.session_id)
|
|
411
|
-
|
|
419
|
+
if not is_replay:
|
|
420
|
+
cmds.append(TaskClockStart())
|
|
412
421
|
else:
|
|
413
422
|
s.sub_agent_thinking_header = SubAgentThinkingHeaderState()
|
|
414
423
|
|
|
415
|
-
|
|
424
|
+
if not is_replay:
|
|
425
|
+
cmds.append(SpinnerStart())
|
|
416
426
|
cmds.append(RenderTaskStart(e))
|
|
417
|
-
|
|
427
|
+
if not is_replay:
|
|
428
|
+
cmds.extend(self._spinner_update_commands())
|
|
418
429
|
return cmds
|
|
419
430
|
|
|
420
431
|
case events.DeveloperMessageEvent() as e:
|
|
@@ -427,9 +438,10 @@ class DisplayStateMachine:
|
|
|
427
438
|
|
|
428
439
|
case events.TurnStartEvent() as e:
|
|
429
440
|
cmds.append(RenderTurnStart(e))
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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())
|
|
433
445
|
return cmds
|
|
434
446
|
|
|
435
447
|
case events.ThinkingStartEvent() as e:
|
|
@@ -441,9 +453,11 @@ class DisplayStateMachine:
|
|
|
441
453
|
s.thinking_tail = ""
|
|
442
454
|
# Ensure the status reflects that reasoning has started even
|
|
443
455
|
# before we receive any deltas (or a bold header).
|
|
444
|
-
|
|
456
|
+
if not is_replay:
|
|
457
|
+
self._spinner.set_reasoning_status(STATUS_THINKING_TEXT)
|
|
445
458
|
cmds.append(StartThinkingStream(session_id=e.session_id))
|
|
446
|
-
|
|
459
|
+
if not is_replay:
|
|
460
|
+
cmds.extend(self._spinner_update_commands())
|
|
447
461
|
return cmds
|
|
448
462
|
|
|
449
463
|
case events.ThinkingDeltaEvent() as e:
|
|
@@ -463,7 +477,7 @@ class DisplayStateMachine:
|
|
|
463
477
|
|
|
464
478
|
# Update reasoning status for spinner (based on bounded tail).
|
|
465
479
|
# Only extract headers for models that use markdown bold headers in thinking.
|
|
466
|
-
if s.should_extract_reasoning_header:
|
|
480
|
+
if not is_replay and s.should_extract_reasoning_header:
|
|
467
481
|
s.thinking_tail = (s.thinking_tail + e.content)[-8192:]
|
|
468
482
|
header = extract_last_bold_header(normalize_thinking_content(s.thinking_tail))
|
|
469
483
|
if header:
|
|
@@ -478,26 +492,31 @@ class DisplayStateMachine:
|
|
|
478
492
|
if not self._is_primary(e.session_id):
|
|
479
493
|
return []
|
|
480
494
|
s.thinking_stream_active = False
|
|
481
|
-
|
|
495
|
+
if not is_replay:
|
|
496
|
+
self._spinner.clear_default_reasoning_status()
|
|
482
497
|
cmds.append(EndThinkingStream(session_id=e.session_id))
|
|
483
|
-
|
|
484
|
-
|
|
498
|
+
if not is_replay:
|
|
499
|
+
cmds.append(SpinnerStart())
|
|
500
|
+
cmds.extend(self._spinner_update_commands())
|
|
485
501
|
return cmds
|
|
486
502
|
|
|
487
503
|
case events.AssistantTextStartEvent() as e:
|
|
488
504
|
if s.is_sub_agent:
|
|
489
|
-
|
|
490
|
-
|
|
505
|
+
if not is_replay:
|
|
506
|
+
self._spinner.set_composing(True)
|
|
507
|
+
cmds.extend(self._spinner_update_commands())
|
|
491
508
|
return cmds
|
|
492
509
|
if not self._is_primary(e.session_id):
|
|
493
510
|
return []
|
|
494
511
|
|
|
495
512
|
s.assistant_stream_active = True
|
|
496
513
|
s.assistant_char_count = 0
|
|
497
|
-
|
|
498
|
-
|
|
514
|
+
if not is_replay:
|
|
515
|
+
self._spinner.set_composing(True)
|
|
516
|
+
self._spinner.clear_tool_calls()
|
|
499
517
|
cmds.append(StartAssistantStream(session_id=e.session_id))
|
|
500
|
-
|
|
518
|
+
if not is_replay:
|
|
519
|
+
cmds.extend(self._spinner_update_commands())
|
|
501
520
|
return cmds
|
|
502
521
|
|
|
503
522
|
case events.AssistantTextDeltaEvent() as e:
|
|
@@ -507,24 +526,29 @@ class DisplayStateMachine:
|
|
|
507
526
|
return []
|
|
508
527
|
|
|
509
528
|
s.assistant_char_count += len(e.content)
|
|
510
|
-
|
|
529
|
+
if not is_replay:
|
|
530
|
+
self._spinner.set_buffer_length(s.assistant_char_count)
|
|
511
531
|
cmds.append(AppendAssistant(session_id=e.session_id, content=e.content))
|
|
512
|
-
|
|
532
|
+
if not is_replay:
|
|
533
|
+
cmds.extend(self._spinner_update_commands())
|
|
513
534
|
return cmds
|
|
514
535
|
|
|
515
536
|
case events.AssistantTextEndEvent() as e:
|
|
516
537
|
if s.is_sub_agent:
|
|
517
|
-
|
|
518
|
-
|
|
538
|
+
if not is_replay:
|
|
539
|
+
self._spinner.set_composing(False)
|
|
540
|
+
cmds.extend(self._spinner_update_commands())
|
|
519
541
|
return cmds
|
|
520
542
|
if not self._is_primary(e.session_id):
|
|
521
543
|
return []
|
|
522
544
|
|
|
523
545
|
s.assistant_stream_active = False
|
|
524
|
-
|
|
546
|
+
if not is_replay:
|
|
547
|
+
self._spinner.set_composing(False)
|
|
525
548
|
cmds.append(EndAssistantStream(session_id=e.session_id))
|
|
526
|
-
|
|
527
|
-
|
|
549
|
+
if not is_replay:
|
|
550
|
+
cmds.append(SpinnerStart())
|
|
551
|
+
cmds.extend(self._spinner_update_commands())
|
|
528
552
|
return cmds
|
|
529
553
|
|
|
530
554
|
case events.AssistantImageDeltaEvent() as e:
|
|
@@ -536,9 +560,10 @@ class DisplayStateMachine:
|
|
|
536
560
|
return []
|
|
537
561
|
if not self._is_primary(e.session_id):
|
|
538
562
|
return []
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
563
|
+
if not is_replay:
|
|
564
|
+
self._spinner.set_composing(False)
|
|
565
|
+
cmds.append(SpinnerStart())
|
|
566
|
+
cmds.extend(self._spinner_update_commands())
|
|
542
567
|
return cmds
|
|
543
568
|
|
|
544
569
|
case events.ToolCallStartEvent() as e:
|
|
@@ -553,18 +578,20 @@ class DisplayStateMachine:
|
|
|
553
578
|
primary.thinking_stream_active = False
|
|
554
579
|
cmds.append(EndThinkingStream(session_id=primary.session_id))
|
|
555
580
|
|
|
556
|
-
|
|
581
|
+
if not is_replay:
|
|
582
|
+
self._spinner.set_composing(False)
|
|
557
583
|
|
|
558
584
|
# Skip activity state for fast tools on non-streaming models (e.g., Gemini)
|
|
559
585
|
# to avoid flash-and-disappear effect
|
|
560
|
-
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):
|
|
561
587
|
tool_active_form = get_tool_active_form(e.tool_name)
|
|
562
588
|
if is_sub_agent_tool(e.tool_name):
|
|
563
589
|
self._spinner.add_sub_agent_tool_call(e.tool_call_id, tool_active_form)
|
|
564
590
|
else:
|
|
565
591
|
self._spinner.add_tool_call(tool_active_form)
|
|
566
592
|
|
|
567
|
-
|
|
593
|
+
if not is_replay:
|
|
594
|
+
cmds.extend(self._spinner_update_commands())
|
|
568
595
|
return cmds
|
|
569
596
|
|
|
570
597
|
case events.ToolCallEvent() as e:
|
|
@@ -583,7 +610,7 @@ class DisplayStateMachine:
|
|
|
583
610
|
return cmds
|
|
584
611
|
|
|
585
612
|
case events.ToolResultEvent() as e:
|
|
586
|
-
if is_sub_agent_tool(e.tool_name):
|
|
613
|
+
if not is_replay and is_sub_agent_tool(e.tool_name):
|
|
587
614
|
self._spinner.finish_sub_agent_tool_call(e.tool_call_id, get_tool_active_form(e.tool_name))
|
|
588
615
|
cmds.extend(self._spinner_update_commands())
|
|
589
616
|
|
|
@@ -601,9 +628,10 @@ class DisplayStateMachine:
|
|
|
601
628
|
|
|
602
629
|
case events.TodoChangeEvent() as e:
|
|
603
630
|
todo_text = _extract_active_form_text(e)
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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())
|
|
607
635
|
return cmds
|
|
608
636
|
|
|
609
637
|
case events.UsageEvent() as e:
|
|
@@ -613,7 +641,7 @@ class DisplayStateMachine:
|
|
|
613
641
|
if not self._is_primary(e.session_id):
|
|
614
642
|
return []
|
|
615
643
|
context_percent = e.usage.context_usage_percent
|
|
616
|
-
if context_percent is not None:
|
|
644
|
+
if not is_replay and context_percent is not None:
|
|
617
645
|
self._spinner.set_context_percent(context_percent)
|
|
618
646
|
cmds.extend(self._spinner_update_commands())
|
|
619
647
|
return cmds
|
|
@@ -624,37 +652,43 @@ class DisplayStateMachine:
|
|
|
624
652
|
case events.TaskFinishEvent() as e:
|
|
625
653
|
cmds.append(RenderTaskFinish(e))
|
|
626
654
|
if not s.is_sub_agent:
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
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())
|
|
632
661
|
else:
|
|
633
662
|
s.sub_agent_thinking_header = None
|
|
634
663
|
return cmds
|
|
635
664
|
|
|
636
665
|
case events.InterruptEvent() as e:
|
|
637
|
-
|
|
638
|
-
|
|
666
|
+
if not is_replay:
|
|
667
|
+
self._spinner.reset()
|
|
668
|
+
cmds.append(SpinnerStop())
|
|
639
669
|
cmds.append(EndThinkingStream(session_id=e.session_id))
|
|
640
670
|
cmds.append(EndAssistantStream(session_id=e.session_id))
|
|
641
|
-
|
|
671
|
+
if not is_replay:
|
|
672
|
+
cmds.append(TaskClockClear())
|
|
642
673
|
cmds.append(RenderInterrupt(session_id=e.session_id))
|
|
643
674
|
return cmds
|
|
644
675
|
|
|
645
676
|
case events.ErrorEvent() as e:
|
|
646
|
-
|
|
677
|
+
if not is_replay:
|
|
678
|
+
cmds.append(EmitOsc94Error())
|
|
647
679
|
cmds.append(RenderError(e))
|
|
648
|
-
if not e.can_retry:
|
|
680
|
+
if not is_replay and not e.can_retry:
|
|
649
681
|
self._spinner.reset()
|
|
650
682
|
cmds.append(SpinnerStop())
|
|
651
|
-
|
|
683
|
+
if not is_replay:
|
|
684
|
+
cmds.extend(self._spinner_update_commands())
|
|
652
685
|
return cmds
|
|
653
686
|
|
|
654
687
|
case events.EndEvent():
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
688
|
+
if not is_replay:
|
|
689
|
+
self._spinner.reset()
|
|
690
|
+
cmds.append(SpinnerStop())
|
|
691
|
+
cmds.append(TaskClockClear())
|
|
658
692
|
return cmds
|
|
659
693
|
|
|
660
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,
|
|
@@ -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
|
|
@@ -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):
|