klaude-code 1.9.0__py3-none-any.whl → 2.0.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/base.py +2 -6
- klaude_code/cli/auth_cmd.py +4 -4
- klaude_code/cli/list_model.py +1 -1
- klaude_code/cli/main.py +1 -1
- klaude_code/cli/runtime.py +7 -5
- klaude_code/cli/self_update.py +1 -1
- klaude_code/cli/session_cmd.py +1 -1
- klaude_code/command/clear_cmd.py +6 -2
- klaude_code/command/command_abc.py +2 -2
- klaude_code/command/debug_cmd.py +4 -4
- klaude_code/command/export_cmd.py +2 -2
- klaude_code/command/export_online_cmd.py +12 -12
- klaude_code/command/fork_session_cmd.py +29 -23
- klaude_code/command/help_cmd.py +4 -4
- klaude_code/command/model_cmd.py +4 -4
- klaude_code/command/model_select.py +1 -1
- klaude_code/command/prompt-commit.md +11 -2
- klaude_code/command/prompt_command.py +3 -3
- klaude_code/command/refresh_cmd.py +2 -2
- klaude_code/command/registry.py +7 -5
- klaude_code/command/release_notes_cmd.py +4 -4
- klaude_code/command/resume_cmd.py +15 -11
- klaude_code/command/status_cmd.py +4 -4
- klaude_code/command/terminal_setup_cmd.py +8 -8
- klaude_code/command/thinking_cmd.py +4 -4
- klaude_code/config/assets/builtin_config.yaml +16 -0
- klaude_code/config/builtin_config.py +16 -5
- klaude_code/config/config.py +7 -2
- klaude_code/const.py +146 -91
- klaude_code/core/agent.py +3 -12
- klaude_code/core/executor.py +21 -13
- klaude_code/core/manager/sub_agent_manager.py +71 -7
- klaude_code/core/prompts/prompt-sub-agent-image-gen.md +1 -0
- klaude_code/core/prompts/prompt-sub-agent-web.md +27 -1
- klaude_code/core/reminders.py +88 -69
- klaude_code/core/task.py +44 -45
- klaude_code/core/tool/file/apply_patch_tool.py +9 -9
- klaude_code/core/tool/file/diff_builder.py +3 -5
- klaude_code/core/tool/file/edit_tool.py +23 -23
- klaude_code/core/tool/file/move_tool.py +43 -43
- klaude_code/core/tool/file/read_tool.py +44 -39
- klaude_code/core/tool/file/write_tool.py +14 -14
- klaude_code/core/tool/report_back_tool.py +4 -4
- klaude_code/core/tool/shell/bash_tool.py +23 -23
- klaude_code/core/tool/skill/skill_tool.py +7 -7
- klaude_code/core/tool/sub_agent_tool.py +38 -9
- klaude_code/core/tool/todo/todo_write_tool.py +8 -8
- klaude_code/core/tool/todo/update_plan_tool.py +6 -6
- klaude_code/core/tool/tool_abc.py +2 -2
- klaude_code/core/tool/tool_context.py +27 -0
- klaude_code/core/tool/tool_runner.py +88 -42
- klaude_code/core/tool/truncation.py +38 -20
- klaude_code/core/tool/web/mermaid_tool.py +6 -7
- klaude_code/core/tool/web/web_fetch_tool.py +68 -30
- klaude_code/core/tool/web/web_search_tool.py +15 -17
- klaude_code/core/turn.py +120 -73
- klaude_code/llm/anthropic/client.py +79 -44
- klaude_code/llm/anthropic/input.py +116 -108
- klaude_code/llm/bedrock/client.py +8 -5
- klaude_code/llm/claude/client.py +18 -8
- klaude_code/llm/client.py +4 -3
- klaude_code/llm/codex/client.py +15 -9
- klaude_code/llm/google/client.py +122 -60
- klaude_code/llm/google/input.py +94 -108
- klaude_code/llm/image.py +123 -0
- klaude_code/llm/input_common.py +136 -189
- klaude_code/llm/openai_compatible/client.py +17 -7
- klaude_code/llm/openai_compatible/input.py +36 -66
- klaude_code/llm/openai_compatible/stream.py +119 -67
- klaude_code/llm/openai_compatible/tool_call_accumulator.py +23 -11
- klaude_code/llm/openrouter/client.py +34 -9
- klaude_code/llm/openrouter/input.py +63 -64
- klaude_code/llm/openrouter/reasoning.py +22 -24
- klaude_code/llm/registry.py +20 -17
- klaude_code/llm/responses/client.py +107 -45
- klaude_code/llm/responses/input.py +115 -98
- klaude_code/llm/usage.py +52 -25
- klaude_code/protocol/__init__.py +1 -0
- klaude_code/protocol/events.py +16 -12
- klaude_code/protocol/llm_param.py +20 -2
- klaude_code/protocol/message.py +250 -0
- klaude_code/protocol/model.py +94 -281
- klaude_code/protocol/op.py +2 -2
- klaude_code/protocol/sub_agent/__init__.py +1 -0
- klaude_code/protocol/sub_agent/explore.py +10 -0
- klaude_code/protocol/sub_agent/image_gen.py +119 -0
- klaude_code/protocol/sub_agent/task.py +10 -0
- klaude_code/protocol/sub_agent/web.py +10 -0
- klaude_code/session/codec.py +6 -6
- klaude_code/session/export.py +261 -62
- klaude_code/session/selector.py +7 -24
- klaude_code/session/session.py +126 -54
- klaude_code/session/store.py +5 -32
- klaude_code/session/templates/export_session.html +1 -1
- klaude_code/session/templates/mermaid_viewer.html +1 -1
- klaude_code/trace/log.py +11 -6
- klaude_code/ui/core/input.py +1 -1
- klaude_code/ui/core/stage_manager.py +1 -8
- klaude_code/ui/modes/debug/display.py +2 -2
- klaude_code/ui/modes/repl/clipboard.py +2 -2
- klaude_code/ui/modes/repl/completers.py +18 -10
- klaude_code/ui/modes/repl/event_handler.py +136 -127
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +1 -1
- klaude_code/ui/modes/repl/key_bindings.py +1 -1
- klaude_code/ui/modes/repl/renderer.py +107 -15
- klaude_code/ui/renderers/assistant.py +2 -2
- klaude_code/ui/renderers/common.py +65 -7
- klaude_code/ui/renderers/developer.py +7 -6
- klaude_code/ui/renderers/diffs.py +11 -11
- klaude_code/ui/renderers/mermaid_viewer.py +49 -2
- klaude_code/ui/renderers/metadata.py +33 -5
- klaude_code/ui/renderers/sub_agent.py +57 -16
- klaude_code/ui/renderers/thinking.py +37 -2
- klaude_code/ui/renderers/tools.py +180 -165
- klaude_code/ui/rich/live.py +3 -1
- klaude_code/ui/rich/markdown.py +39 -7
- klaude_code/ui/rich/quote.py +76 -1
- klaude_code/ui/rich/status.py +14 -8
- klaude_code/ui/rich/theme.py +8 -2
- klaude_code/ui/terminal/image.py +34 -0
- klaude_code/ui/terminal/notifier.py +2 -1
- klaude_code/ui/terminal/progress_bar.py +4 -4
- klaude_code/ui/terminal/selector.py +22 -4
- klaude_code/ui/utils/common.py +11 -2
- {klaude_code-1.9.0.dist-info → klaude_code-2.0.0.dist-info}/METADATA +4 -2
- klaude_code-2.0.0.dist-info/RECORD +229 -0
- klaude_code-1.9.0.dist-info/RECORD +0 -224
- {klaude_code-1.9.0.dist-info → klaude_code-2.0.0.dist-info}/WHEEL +0 -0
- {klaude_code-1.9.0.dist-info → klaude_code-2.0.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from collections.abc import Callable
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
|
|
5
6
|
from rich.rule import Rule
|
|
6
7
|
from rich.text import Text
|
|
7
8
|
|
|
8
|
-
from klaude_code import
|
|
9
|
+
from klaude_code.const import MARKDOWN_LEFT_MARGIN, MARKDOWN_STREAM_LIVE_REPAINT_ENABLED, STATUS_DEFAULT_TEXT
|
|
9
10
|
from klaude_code.protocol import events
|
|
10
11
|
from klaude_code.ui.core.stage_manager import Stage, StageManager
|
|
11
12
|
from klaude_code.ui.modes.repl.renderer import REPLRenderer
|
|
12
13
|
from klaude_code.ui.renderers.assistant import ASSISTANT_MESSAGE_MARK
|
|
13
|
-
from klaude_code.ui.renderers.thinking import
|
|
14
|
+
from klaude_code.ui.renderers.thinking import (
|
|
15
|
+
THINKING_MESSAGE_MARK,
|
|
16
|
+
extract_last_bold_header,
|
|
17
|
+
normalize_thinking_content,
|
|
18
|
+
)
|
|
19
|
+
from klaude_code.ui.renderers.tools import get_tool_active_form
|
|
14
20
|
from klaude_code.ui.rich import status as r_status
|
|
15
21
|
from klaude_code.ui.rich.markdown import MarkdownStream, ThinkingMarkdown
|
|
16
22
|
from klaude_code.ui.rich.theme import ThemeKey
|
|
@@ -18,39 +24,24 @@ from klaude_code.ui.terminal.notifier import Notification, NotificationType, Ter
|
|
|
18
24
|
from klaude_code.ui.terminal.progress_bar import OSC94States, emit_osc94
|
|
19
25
|
|
|
20
26
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
of a line (ignoring leading whitespace). This avoids picking up incidental
|
|
26
|
-
emphasis inside paragraphs.
|
|
27
|
-
|
|
28
|
-
Returns None if no complete bold segment is available yet.
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
last: str | None = None
|
|
32
|
-
i = 0
|
|
33
|
-
while True:
|
|
34
|
-
start = text.find("**", i)
|
|
35
|
-
if start < 0:
|
|
36
|
-
break
|
|
37
|
-
|
|
38
|
-
line_start = text.rfind("\n", 0, start) + 1
|
|
39
|
-
if text[line_start:start].strip():
|
|
40
|
-
i = start + 2
|
|
41
|
-
continue
|
|
42
|
-
|
|
43
|
-
end = text.find("**", start + 2)
|
|
44
|
-
if end < 0:
|
|
45
|
-
break
|
|
27
|
+
@dataclass
|
|
28
|
+
class SubAgentThinkingHeaderState:
|
|
29
|
+
buffer: str = ""
|
|
30
|
+
last_header: str | None = None
|
|
46
31
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
last = inner
|
|
32
|
+
def append_and_extract_new_header(self, content: str) -> str | None:
|
|
33
|
+
self.buffer += content
|
|
50
34
|
|
|
51
|
-
|
|
35
|
+
# Sub-agent thinking does not need full streaming; keep a bounded tail.
|
|
36
|
+
max_chars = 8192
|
|
37
|
+
if len(self.buffer) > max_chars:
|
|
38
|
+
self.buffer = self.buffer[-max_chars:]
|
|
52
39
|
|
|
53
|
-
|
|
40
|
+
header = extract_last_bold_header(normalize_thinking_content(self.buffer))
|
|
41
|
+
if header and header != self.last_header:
|
|
42
|
+
self.last_header = header
|
|
43
|
+
return header
|
|
44
|
+
return None
|
|
54
45
|
|
|
55
46
|
|
|
56
47
|
@dataclass
|
|
@@ -107,6 +98,31 @@ class StreamState:
|
|
|
107
98
|
"""End the current streaming session."""
|
|
108
99
|
self._active = None
|
|
109
100
|
|
|
101
|
+
def render(self, *, transform: Callable[[str], str] | None = None, final: bool = False) -> bool:
|
|
102
|
+
"""Render the current buffer to the markdown stream.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
bool: True if an active stream was rendered.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
if self._active is None:
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
text = self._active.buffer
|
|
112
|
+
if transform is not None:
|
|
113
|
+
text = transform(text)
|
|
114
|
+
self._active.mdstream.update(text, final=final)
|
|
115
|
+
|
|
116
|
+
if final:
|
|
117
|
+
self.finish()
|
|
118
|
+
|
|
119
|
+
return True
|
|
120
|
+
|
|
121
|
+
def finalize(self, *, transform: Callable[[str], str] | None = None) -> bool:
|
|
122
|
+
"""Finalize rendering and end the current streaming session."""
|
|
123
|
+
|
|
124
|
+
return self.render(transform=transform, final=True)
|
|
125
|
+
|
|
110
126
|
|
|
111
127
|
class ActivityState:
|
|
112
128
|
"""Represents the current activity state for spinner display.
|
|
@@ -185,7 +201,7 @@ class SpinnerStatusState:
|
|
|
185
201
|
- context_percent: Context usage percentage, updated during task execution
|
|
186
202
|
|
|
187
203
|
Display logic:
|
|
188
|
-
- If activity: show base + activity (if base exists) or activity + "
|
|
204
|
+
- If activity: show base + activity (if base exists) or activity + "…"
|
|
189
205
|
- Elif base_status: show base_status
|
|
190
206
|
- Else: show "Thinking …"
|
|
191
207
|
- Context percent is appended at the end if available
|
|
@@ -260,7 +276,7 @@ class SpinnerStatusState:
|
|
|
260
276
|
activity_text.append(" …")
|
|
261
277
|
result = activity_text
|
|
262
278
|
else:
|
|
263
|
-
result = Text(
|
|
279
|
+
result = Text(STATUS_DEFAULT_TEXT, style=ThemeKey.STATUS_TEXT)
|
|
264
280
|
|
|
265
281
|
return result
|
|
266
282
|
|
|
@@ -295,6 +311,7 @@ class DisplayEventHandler:
|
|
|
295
311
|
self.notifier = notifier
|
|
296
312
|
self.assistant_stream = StreamState()
|
|
297
313
|
self.thinking_stream = StreamState()
|
|
314
|
+
self._sub_agent_thinking_headers: dict[str, SubAgentThinkingHeaderState] = {}
|
|
298
315
|
self.spinner_status = SpinnerStatusState()
|
|
299
316
|
|
|
300
317
|
self.stage_manager = StageManager(
|
|
@@ -302,6 +319,31 @@ class DisplayEventHandler:
|
|
|
302
319
|
finish_thinking=self._finish_thinking_stream,
|
|
303
320
|
)
|
|
304
321
|
|
|
322
|
+
def _new_thinking_mdstream(self) -> MarkdownStream:
|
|
323
|
+
return MarkdownStream(
|
|
324
|
+
mdargs={
|
|
325
|
+
"code_theme": self.renderer.themes.code_theme,
|
|
326
|
+
"style": ThemeKey.THINKING,
|
|
327
|
+
},
|
|
328
|
+
theme=self.renderer.themes.thinking_markdown_theme,
|
|
329
|
+
console=self.renderer.console,
|
|
330
|
+
live_sink=self.renderer.set_stream_renderable if MARKDOWN_STREAM_LIVE_REPAINT_ENABLED else None,
|
|
331
|
+
mark=THINKING_MESSAGE_MARK,
|
|
332
|
+
mark_style=ThemeKey.THINKING,
|
|
333
|
+
left_margin=MARKDOWN_LEFT_MARGIN,
|
|
334
|
+
markdown_class=ThinkingMarkdown,
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
def _new_assistant_mdstream(self) -> MarkdownStream:
|
|
338
|
+
return MarkdownStream(
|
|
339
|
+
mdargs={"code_theme": self.renderer.themes.code_theme},
|
|
340
|
+
theme=self.renderer.themes.markdown_theme,
|
|
341
|
+
console=self.renderer.console,
|
|
342
|
+
live_sink=self.renderer.set_stream_renderable if MARKDOWN_STREAM_LIVE_REPAINT_ENABLED else None,
|
|
343
|
+
mark=ASSISTANT_MESSAGE_MARK,
|
|
344
|
+
left_margin=MARKDOWN_LEFT_MARGIN,
|
|
345
|
+
)
|
|
346
|
+
|
|
305
347
|
async def consume_event(self, event: events.Event) -> None:
|
|
306
348
|
match event:
|
|
307
349
|
case events.ReplayHistoryEvent() as e:
|
|
@@ -316,16 +358,16 @@ class DisplayEventHandler:
|
|
|
316
358
|
self._on_developer_message(e)
|
|
317
359
|
case events.TurnStartEvent() as e:
|
|
318
360
|
self._on_turn_start(e)
|
|
319
|
-
case events.ThinkingEvent() as e:
|
|
320
|
-
await self._on_thinking(e)
|
|
321
361
|
case events.ThinkingDeltaEvent() as e:
|
|
322
362
|
await self._on_thinking_delta(e)
|
|
323
|
-
case events.
|
|
363
|
+
case events.AssistantTextDeltaEvent() as e:
|
|
324
364
|
await self._on_assistant_delta(e)
|
|
365
|
+
case events.AssistantImageDeltaEvent() as e:
|
|
366
|
+
await self._on_assistant_image_delta(e)
|
|
325
367
|
case events.AssistantMessageEvent() as e:
|
|
326
368
|
await self._on_assistant_message(e)
|
|
327
369
|
case events.TurnToolCallStartEvent() as e:
|
|
328
|
-
self._on_tool_call_start(e)
|
|
370
|
+
await self._on_tool_call_start(e)
|
|
329
371
|
case events.ToolCallEvent() as e:
|
|
330
372
|
await self._on_tool_call(e)
|
|
331
373
|
case events.ToolResultEvent() as e:
|
|
@@ -350,8 +392,8 @@ class DisplayEventHandler:
|
|
|
350
392
|
await self._on_end(e)
|
|
351
393
|
|
|
352
394
|
async def stop(self) -> None:
|
|
353
|
-
|
|
354
|
-
|
|
395
|
+
self._flush_assistant_buffer()
|
|
396
|
+
self._flush_thinking_buffer()
|
|
355
397
|
|
|
356
398
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
357
399
|
# Private event handlers
|
|
@@ -370,6 +412,8 @@ class DisplayEventHandler:
|
|
|
370
412
|
def _on_task_start(self, event: events.TaskStartEvent) -> None:
|
|
371
413
|
if event.sub_agent_state is None:
|
|
372
414
|
r_status.set_task_start()
|
|
415
|
+
else:
|
|
416
|
+
self._sub_agent_thinking_headers[event.session_id] = SubAgentThinkingHeaderState()
|
|
373
417
|
self.renderer.spinner_start()
|
|
374
418
|
self.renderer.display_task_start(event)
|
|
375
419
|
emit_osc94(OSC94States.INDETERMINATE)
|
|
@@ -385,41 +429,20 @@ class DisplayEventHandler:
|
|
|
385
429
|
self.spinner_status.set_reasoning_status(None)
|
|
386
430
|
self._update_spinner()
|
|
387
431
|
|
|
388
|
-
async def _on_thinking(self, event: events.ThinkingEvent) -> None:
|
|
389
|
-
if self.renderer.is_sub_agent_session(event.session_id):
|
|
390
|
-
return
|
|
391
|
-
# If streaming was active, finalize it
|
|
392
|
-
if self.thinking_stream.is_active:
|
|
393
|
-
await self._finish_thinking_stream()
|
|
394
|
-
else:
|
|
395
|
-
# Non-streaming path (history replay or models without delta support)
|
|
396
|
-
reasoning_status = extract_last_bold_header(normalize_thinking_content(event.content))
|
|
397
|
-
if reasoning_status:
|
|
398
|
-
self.spinner_status.set_reasoning_status(reasoning_status)
|
|
399
|
-
self._update_spinner()
|
|
400
|
-
await self.stage_manager.enter_thinking_stage()
|
|
401
|
-
self.renderer.display_thinking(event.content)
|
|
402
|
-
|
|
403
432
|
async def _on_thinking_delta(self, event: events.ThinkingDeltaEvent) -> None:
|
|
404
433
|
if self.renderer.is_sub_agent_session(event.session_id):
|
|
434
|
+
if not self.renderer.should_display_sub_agent_thinking_header(event.session_id):
|
|
435
|
+
return
|
|
436
|
+
state = self._sub_agent_thinking_headers.setdefault(event.session_id, SubAgentThinkingHeaderState())
|
|
437
|
+
header = state.append_and_extract_new_header(event.content)
|
|
438
|
+
if header:
|
|
439
|
+
with self.renderer.session_print_context(event.session_id):
|
|
440
|
+
self.renderer.display_thinking_header(header)
|
|
405
441
|
return
|
|
406
442
|
|
|
407
443
|
first_delta = not self.thinking_stream.is_active
|
|
408
444
|
if first_delta:
|
|
409
|
-
|
|
410
|
-
mdargs={
|
|
411
|
-
"code_theme": self.renderer.themes.code_theme,
|
|
412
|
-
"style": ThemeKey.THINKING,
|
|
413
|
-
},
|
|
414
|
-
theme=self.renderer.themes.thinking_markdown_theme,
|
|
415
|
-
console=self.renderer.console,
|
|
416
|
-
live_sink=self.renderer.set_stream_renderable if const.MARKDOWN_STREAM_LIVE_REPAINT_ENABLED else None,
|
|
417
|
-
mark=THINKING_MESSAGE_MARK,
|
|
418
|
-
mark_style=ThemeKey.THINKING,
|
|
419
|
-
left_margin=const.MARKDOWN_LEFT_MARGIN,
|
|
420
|
-
markdown_class=ThinkingMarkdown,
|
|
421
|
-
)
|
|
422
|
-
self.thinking_stream.start(mdstream)
|
|
445
|
+
self.thinking_stream.start(self._new_thinking_mdstream())
|
|
423
446
|
|
|
424
447
|
self.thinking_stream.append(event.content)
|
|
425
448
|
|
|
@@ -428,62 +451,52 @@ class DisplayEventHandler:
|
|
|
428
451
|
self.spinner_status.set_reasoning_status(reasoning_status)
|
|
429
452
|
self._update_spinner()
|
|
430
453
|
|
|
431
|
-
if first_delta
|
|
432
|
-
self.thinking_stream.
|
|
454
|
+
if first_delta:
|
|
455
|
+
self.thinking_stream.render(transform=normalize_thinking_content)
|
|
433
456
|
|
|
434
457
|
await self.stage_manager.enter_thinking_stage()
|
|
435
|
-
|
|
458
|
+
self._flush_thinking_buffer()
|
|
436
459
|
|
|
437
|
-
async def _on_assistant_delta(self, event: events.
|
|
460
|
+
async def _on_assistant_delta(self, event: events.AssistantTextDeltaEvent) -> None:
|
|
438
461
|
if self.renderer.is_sub_agent_session(event.session_id):
|
|
439
462
|
self.spinner_status.set_composing(True)
|
|
440
463
|
self._update_spinner()
|
|
441
464
|
return
|
|
465
|
+
|
|
442
466
|
if len(event.content.strip()) == 0 and self.stage_manager.current_stage != Stage.ASSISTANT:
|
|
467
|
+
await self.stage_manager.transition_to(Stage.WAITING)
|
|
443
468
|
return
|
|
469
|
+
|
|
470
|
+
await self.stage_manager.transition_to(Stage.ASSISTANT)
|
|
444
471
|
first_delta = not self.assistant_stream.is_active
|
|
445
472
|
if first_delta:
|
|
446
473
|
self.spinner_status.set_composing(True)
|
|
447
474
|
self.spinner_status.clear_tool_calls()
|
|
448
475
|
self._update_spinner()
|
|
449
|
-
|
|
450
|
-
mdargs={"code_theme": self.renderer.themes.code_theme},
|
|
451
|
-
theme=self.renderer.themes.markdown_theme,
|
|
452
|
-
console=self.renderer.console,
|
|
453
|
-
live_sink=self.renderer.set_stream_renderable if const.MARKDOWN_STREAM_LIVE_REPAINT_ENABLED else None,
|
|
454
|
-
mark=ASSISTANT_MESSAGE_MARK,
|
|
455
|
-
left_margin=const.MARKDOWN_LEFT_MARGIN,
|
|
456
|
-
)
|
|
457
|
-
self.assistant_stream.start(mdstream)
|
|
476
|
+
self.assistant_stream.start(self._new_assistant_mdstream())
|
|
458
477
|
self.assistant_stream.append(event.content)
|
|
459
478
|
self.spinner_status.set_buffer_length(len(self.assistant_stream.buffer))
|
|
460
479
|
if not first_delta:
|
|
461
480
|
self._update_spinner()
|
|
462
|
-
if first_delta
|
|
463
|
-
self.assistant_stream.
|
|
464
|
-
|
|
465
|
-
await self._flush_assistant_buffer(self.assistant_stream)
|
|
481
|
+
if first_delta:
|
|
482
|
+
self.assistant_stream.render()
|
|
483
|
+
self._flush_assistant_buffer()
|
|
466
484
|
|
|
467
485
|
async def _on_assistant_message(self, event: events.AssistantMessageEvent) -> None:
|
|
468
486
|
if self.renderer.is_sub_agent_session(event.session_id):
|
|
469
487
|
return
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
mdstream = self.assistant_stream.mdstream
|
|
473
|
-
assert mdstream is not None
|
|
474
|
-
mdstream.update(event.content.strip(), final=True)
|
|
475
|
-
else:
|
|
476
|
-
self.renderer.display_assistant_message(event.content)
|
|
477
|
-
self.assistant_stream.finish()
|
|
488
|
+
|
|
489
|
+
await self.stage_manager.transition_to(Stage.WAITING)
|
|
478
490
|
self.spinner_status.set_composing(False)
|
|
479
491
|
self._update_spinner()
|
|
480
|
-
await self.stage_manager.transition_to(Stage.WAITING)
|
|
481
|
-
self.renderer.print()
|
|
482
492
|
self.renderer.spinner_start()
|
|
483
493
|
|
|
484
|
-
def
|
|
485
|
-
|
|
494
|
+
async def _on_assistant_image_delta(self, event: events.AssistantImageDeltaEvent) -> None:
|
|
495
|
+
await self.stage_manager.transition_to(Stage.ASSISTANT)
|
|
496
|
+
self.renderer.display_image(event.file_path)
|
|
486
497
|
|
|
498
|
+
async def _on_tool_call_start(self, event: events.TurnToolCallStartEvent) -> None:
|
|
499
|
+
self._flush_assistant_buffer()
|
|
487
500
|
self.spinner_status.set_composing(False)
|
|
488
501
|
self.spinner_status.add_tool_call(get_tool_active_form(event.tool_name))
|
|
489
502
|
self._update_spinner()
|
|
@@ -494,18 +507,18 @@ class DisplayEventHandler:
|
|
|
494
507
|
self.renderer.display_tool_call(event)
|
|
495
508
|
|
|
496
509
|
async def _on_tool_result(self, event: events.ToolResultEvent) -> None:
|
|
497
|
-
|
|
510
|
+
is_sub_agent = self.renderer.is_sub_agent_session(event.session_id)
|
|
511
|
+
if is_sub_agent and event.status == "success":
|
|
498
512
|
return
|
|
499
513
|
await self.stage_manager.transition_to(Stage.TOOL_RESULT)
|
|
500
514
|
with self.renderer.session_print_context(event.session_id):
|
|
501
|
-
self.renderer.display_tool_call_result(event)
|
|
515
|
+
self.renderer.display_tool_call_result(event, is_sub_agent=is_sub_agent)
|
|
502
516
|
|
|
503
517
|
def _on_task_metadata(self, event: events.TaskMetadataEvent) -> None:
|
|
504
518
|
self.renderer.display_task_metadata(event)
|
|
505
519
|
|
|
506
520
|
def _on_todo_change(self, event: events.TodoChangeEvent) -> None:
|
|
507
|
-
|
|
508
|
-
self.spinner_status.set_todo_status(active_form_status_text if active_form_status_text else None)
|
|
521
|
+
self.spinner_status.set_todo_status(self._extract_active_form_text(event))
|
|
509
522
|
# Clear tool calls when todo changes, as the tool execution has advanced
|
|
510
523
|
self.spinner_status.clear_for_new_turn()
|
|
511
524
|
self._update_spinner()
|
|
@@ -525,6 +538,8 @@ class DisplayEventHandler:
|
|
|
525
538
|
self.renderer.spinner_stop()
|
|
526
539
|
self.renderer.console.print(Rule(characters="─", style=ThemeKey.LINES))
|
|
527
540
|
emit_tmux_signal() # Signal test harness if KLAUDE_TEST_SIGNAL is set
|
|
541
|
+
else:
|
|
542
|
+
self._sub_agent_thinking_headers.pop(event.session_id, None)
|
|
528
543
|
await self.stage_manager.transition_to(Stage.WAITING)
|
|
529
544
|
self._maybe_notify_task_finish(event)
|
|
530
545
|
|
|
@@ -555,13 +570,6 @@ class DisplayEventHandler:
|
|
|
555
570
|
# Private helper methods
|
|
556
571
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
557
572
|
|
|
558
|
-
async def _finish_assistant_stream(self) -> None:
|
|
559
|
-
if self.assistant_stream.is_active:
|
|
560
|
-
mdstream = self.assistant_stream.mdstream
|
|
561
|
-
assert mdstream is not None
|
|
562
|
-
mdstream.update(self.assistant_stream.buffer, final=True)
|
|
563
|
-
self.assistant_stream.finish()
|
|
564
|
-
|
|
565
573
|
def _update_spinner(self) -> None:
|
|
566
574
|
"""Update spinner text from current status state."""
|
|
567
575
|
status_text = self.spinner_status.get_status()
|
|
@@ -571,27 +579,23 @@ class DisplayEventHandler:
|
|
|
571
579
|
right_text,
|
|
572
580
|
)
|
|
573
581
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
mdstream = state.mdstream
|
|
577
|
-
assert mdstream is not None
|
|
578
|
-
mdstream.update(state.buffer)
|
|
582
|
+
def _flush_thinking_buffer(self) -> None:
|
|
583
|
+
self.thinking_stream.render(transform=normalize_thinking_content)
|
|
579
584
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
mdstream = state.mdstream
|
|
583
|
-
assert mdstream is not None
|
|
584
|
-
mdstream.update(normalize_thinking_content(state.buffer))
|
|
585
|
+
def _flush_assistant_buffer(self) -> None:
|
|
586
|
+
self.assistant_stream.render()
|
|
585
587
|
|
|
586
588
|
async def _finish_thinking_stream(self) -> None:
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
assert mdstream is not None
|
|
590
|
-
mdstream.update(normalize_thinking_content(self.thinking_stream.buffer), final=True)
|
|
591
|
-
self.thinking_stream.finish()
|
|
589
|
+
finalized = self.thinking_stream.finalize(transform=normalize_thinking_content)
|
|
590
|
+
if finalized:
|
|
592
591
|
self.renderer.print()
|
|
593
592
|
self.renderer.spinner_start()
|
|
594
593
|
|
|
594
|
+
async def _finish_assistant_stream(self) -> None:
|
|
595
|
+
finalized = self.assistant_stream.finalize()
|
|
596
|
+
if finalized:
|
|
597
|
+
self.renderer.print()
|
|
598
|
+
|
|
595
599
|
def _maybe_notify_task_finish(self, event: events.TaskFinishEvent) -> None:
|
|
596
600
|
if self.notifier is None:
|
|
597
601
|
return
|
|
@@ -617,12 +621,17 @@ class DisplayEventHandler:
|
|
|
617
621
|
return squashed[:197] + "…"
|
|
618
622
|
return squashed
|
|
619
623
|
|
|
620
|
-
def _extract_active_form_text(self, todo_event: events.TodoChangeEvent) -> str:
|
|
621
|
-
status_text =
|
|
624
|
+
def _extract_active_form_text(self, todo_event: events.TodoChangeEvent) -> str | None:
|
|
625
|
+
status_text: str | None = None
|
|
622
626
|
for todo in todo_event.todos:
|
|
623
627
|
if todo.status == "in_progress":
|
|
624
628
|
if len(todo.active_form) > 0:
|
|
625
629
|
status_text = todo.active_form
|
|
626
630
|
if len(todo.content) > 0:
|
|
627
631
|
status_text = todo.content
|
|
628
|
-
|
|
632
|
+
|
|
633
|
+
if status_text is None:
|
|
634
|
+
return None
|
|
635
|
+
|
|
636
|
+
normalized = status_text.replace("\n", " ").strip()
|
|
637
|
+
return normalized if normalized else None
|
|
@@ -34,7 +34,7 @@ from klaude_code.config.thinking import (
|
|
|
34
34
|
)
|
|
35
35
|
from klaude_code.protocol import llm_param
|
|
36
36
|
from klaude_code.protocol.commands import CommandInfo
|
|
37
|
-
from klaude_code.protocol.
|
|
37
|
+
from klaude_code.protocol.message import UserInputPayload
|
|
38
38
|
from klaude_code.ui.core.input import InputProviderABC
|
|
39
39
|
from klaude_code.ui.modes.repl.clipboard import capture_clipboard_tag, copy_to_clipboard, extract_images_from_text
|
|
40
40
|
from klaude_code.ui.modes.repl.completers import AT_TOKEN_PATTERN, create_repl_completer
|
|
@@ -198,7 +198,7 @@ def create_key_bindings(
|
|
|
198
198
|
"""Ensure completions refresh on backspace when editing an @token.
|
|
199
199
|
|
|
200
200
|
We delete the character before cursor (default behavior), then explicitly
|
|
201
|
-
trigger completion refresh if the caret is still within an
|
|
201
|
+
trigger completion refresh if the caret is still within an @… token.
|
|
202
202
|
"""
|
|
203
203
|
buf = event.current_buffer # type: ignore
|
|
204
204
|
# Handle selection: cut selection if present, otherwise delete one character
|