klaude-code 2.0.1__py3-none-any.whl → 2.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- klaude_code/cli/runtime.py +1 -8
- klaude_code/command/debug_cmd.py +1 -1
- klaude_code/command/export_online_cmd.py +4 -4
- klaude_code/command/fork_session_cmd.py +6 -6
- klaude_code/command/help_cmd.py +1 -1
- klaude_code/command/model_cmd.py +1 -1
- klaude_code/command/registry.py +10 -1
- klaude_code/command/release_notes_cmd.py +1 -1
- klaude_code/command/resume_cmd.py +2 -2
- klaude_code/command/status_cmd.py +2 -2
- klaude_code/command/terminal_setup_cmd.py +2 -2
- klaude_code/command/thinking_cmd.py +1 -1
- klaude_code/const.py +1 -0
- klaude_code/core/executor.py +15 -7
- klaude_code/core/reminders.py +55 -68
- klaude_code/core/tool/__init__.py +0 -2
- klaude_code/core/tool/file/edit_tool.py +3 -2
- klaude_code/core/tool/tool_registry.py +3 -3
- klaude_code/protocol/events.py +1 -0
- klaude_code/protocol/message.py +3 -11
- klaude_code/protocol/model.py +78 -9
- klaude_code/protocol/sub_agent/explore.py +0 -15
- klaude_code/protocol/sub_agent/task.py +1 -1
- klaude_code/protocol/sub_agent/web.py +1 -17
- klaude_code/protocol/tools.py +0 -1
- klaude_code/ui/modes/exec/display.py +2 -3
- klaude_code/ui/modes/repl/display.py +1 -1
- klaude_code/ui/modes/repl/event_handler.py +0 -5
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +5 -1
- klaude_code/ui/modes/repl/renderer.py +2 -1
- klaude_code/ui/renderers/developer.py +113 -97
- klaude_code/ui/renderers/metadata.py +28 -15
- klaude_code/ui/renderers/tools.py +2 -39
- klaude_code/ui/rich/markdown.py +69 -11
- klaude_code/ui/rich/theme.py +9 -4
- klaude_code/ui/terminal/selector.py +36 -17
- {klaude_code-2.0.1.dist-info → klaude_code-2.0.2.dist-info}/METADATA +1 -1
- {klaude_code-2.0.1.dist-info → klaude_code-2.0.2.dist-info}/RECORD +40 -42
- klaude_code/core/tool/file/move_tool.md +0 -41
- klaude_code/core/tool/file/move_tool.py +0 -435
- {klaude_code-2.0.1.dist-info → klaude_code-2.0.2.dist-info}/WHEEL +0 -0
- {klaude_code-2.0.1.dist-info → klaude_code-2.0.2.dist-info}/entry_points.txt +0 -0
klaude_code/protocol/message.py
CHANGED
|
@@ -12,8 +12,7 @@ from typing import Annotated, Literal
|
|
|
12
12
|
from pydantic import BaseModel, Field, field_validator
|
|
13
13
|
|
|
14
14
|
from klaude_code.protocol.model import (
|
|
15
|
-
|
|
16
|
-
CommandOutput,
|
|
15
|
+
DeveloperUIExtra,
|
|
17
16
|
StopReason,
|
|
18
17
|
TaskMetadata,
|
|
19
18
|
TaskMetadataItem,
|
|
@@ -137,15 +136,8 @@ class DeveloperMessage(MessageBase):
|
|
|
137
136
|
role: Literal["developer"] = "developer"
|
|
138
137
|
parts: list[Part]
|
|
139
138
|
|
|
140
|
-
#
|
|
141
|
-
|
|
142
|
-
memory_mentioned: dict[str, list[str]] | None = None # memory_path -> list of @ patterns mentioned in it
|
|
143
|
-
external_file_changes: list[str] | None = None
|
|
144
|
-
todo_use: bool | None = None
|
|
145
|
-
at_files: list[AtPatternParseResult] | None = None
|
|
146
|
-
command_output: CommandOutput | None = None
|
|
147
|
-
user_image_count: int | None = None
|
|
148
|
-
skill_name: str | None = None # Skill name activated via $skill syntax
|
|
139
|
+
# Structured UI-only metadata (never sent to the LLM).
|
|
140
|
+
ui_extra: DeveloperUIExtra | None = None
|
|
149
141
|
|
|
150
142
|
|
|
151
143
|
class UserMessage(MessageBase):
|
klaude_code/protocol/model.py
CHANGED
|
@@ -272,21 +272,90 @@ ToolResultUIExtra = Annotated[
|
|
|
272
272
|
]
|
|
273
273
|
|
|
274
274
|
|
|
275
|
-
class AtPatternParseResult(BaseModel):
|
|
276
|
-
path: str
|
|
277
|
-
tool_name: str
|
|
278
|
-
result: str
|
|
279
|
-
tool_args: str
|
|
280
|
-
operation: Literal["Read", "List"]
|
|
281
|
-
mentioned_in: str | None = None # Parent file that referenced this file
|
|
282
|
-
|
|
283
|
-
|
|
284
275
|
class CommandOutput(BaseModel):
|
|
285
276
|
command_name: CommandName
|
|
286
277
|
ui_extra: ToolResultUIExtra | None = None
|
|
287
278
|
is_error: bool = False
|
|
288
279
|
|
|
289
280
|
|
|
281
|
+
class MemoryFileLoaded(BaseModel):
|
|
282
|
+
path: str
|
|
283
|
+
mentioned_patterns: list[str] = Field(default_factory=list)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class MemoryLoadedUIItem(BaseModel):
|
|
287
|
+
type: Literal["memory_loaded"] = "memory_loaded"
|
|
288
|
+
files: list[MemoryFileLoaded]
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class ExternalFileChangesUIItem(BaseModel):
|
|
292
|
+
type: Literal["external_file_changes"] = "external_file_changes"
|
|
293
|
+
paths: list[str]
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
class TodoReminderUIItem(BaseModel):
|
|
297
|
+
type: Literal["todo_reminder"] = "todo_reminder"
|
|
298
|
+
reason: Literal["empty", "not_used_recently"]
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
class AtFileOp(BaseModel):
|
|
302
|
+
operation: Literal["Read", "List"]
|
|
303
|
+
path: str
|
|
304
|
+
mentioned_in: str | None = None
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class AtFileOpsUIItem(BaseModel):
|
|
308
|
+
type: Literal["at_file_ops"] = "at_file_ops"
|
|
309
|
+
ops: list[AtFileOp]
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
class UserImagesUIItem(BaseModel):
|
|
313
|
+
type: Literal["user_images"] = "user_images"
|
|
314
|
+
count: int
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class SkillActivatedUIItem(BaseModel):
|
|
318
|
+
type: Literal["skill_activated"] = "skill_activated"
|
|
319
|
+
name: str
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
class CommandOutputUIItem(BaseModel):
|
|
323
|
+
type: Literal["command_output"] = "command_output"
|
|
324
|
+
output: CommandOutput
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
type DeveloperUIItem = (
|
|
328
|
+
MemoryLoadedUIItem
|
|
329
|
+
| ExternalFileChangesUIItem
|
|
330
|
+
| TodoReminderUIItem
|
|
331
|
+
| AtFileOpsUIItem
|
|
332
|
+
| UserImagesUIItem
|
|
333
|
+
| SkillActivatedUIItem
|
|
334
|
+
| CommandOutputUIItem
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def _empty_developer_ui_items() -> list[DeveloperUIItem]:
|
|
339
|
+
return []
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
class DeveloperUIExtra(BaseModel):
|
|
343
|
+
items: list[DeveloperUIItem] = Field(default_factory=_empty_developer_ui_items)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def build_command_output_extra(
|
|
347
|
+
command_name: CommandName,
|
|
348
|
+
*,
|
|
349
|
+
ui_extra: ToolResultUIExtra | None = None,
|
|
350
|
+
is_error: bool = False,
|
|
351
|
+
) -> DeveloperUIExtra:
|
|
352
|
+
return DeveloperUIExtra(
|
|
353
|
+
items=[
|
|
354
|
+
CommandOutputUIItem(output=CommandOutput(command_name=command_name, ui_extra=ui_extra, is_error=is_error))
|
|
355
|
+
]
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
|
|
290
359
|
class SubAgentState(BaseModel):
|
|
291
360
|
sub_agent_type: SubAgentType
|
|
292
361
|
sub_agent_desc: str
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
3
|
from klaude_code.protocol import tools
|
|
6
4
|
from klaude_code.protocol.sub_agent import SubAgentProfile, register_sub_agent
|
|
7
5
|
|
|
@@ -37,11 +35,6 @@ EXPLORE_PARAMETERS = {
|
|
|
37
35
|
"type": "string",
|
|
38
36
|
"description": "The task for the agent to perform",
|
|
39
37
|
},
|
|
40
|
-
"thoroughness": {
|
|
41
|
-
"type": "string",
|
|
42
|
-
"enum": ["quick", "medium", "very thorough"],
|
|
43
|
-
"description": "Controls how deep the sub-agent should search the repo",
|
|
44
|
-
},
|
|
45
38
|
"output_format": {
|
|
46
39
|
"type": "object",
|
|
47
40
|
"description": "Optional JSON Schema for sub-agent structured output",
|
|
@@ -52,13 +45,6 @@ EXPLORE_PARAMETERS = {
|
|
|
52
45
|
}
|
|
53
46
|
|
|
54
47
|
|
|
55
|
-
def _explore_prompt_builder(args: dict[str, Any]) -> str:
|
|
56
|
-
"""Build the Explore prompt from tool arguments."""
|
|
57
|
-
prompt = args.get("prompt", "").strip()
|
|
58
|
-
thoroughness = args.get("thoroughness", "medium")
|
|
59
|
-
return f"{prompt}\nthoroughness: {thoroughness}"
|
|
60
|
-
|
|
61
|
-
|
|
62
48
|
register_sub_agent(
|
|
63
49
|
SubAgentProfile(
|
|
64
50
|
name="Explore",
|
|
@@ -66,7 +52,6 @@ register_sub_agent(
|
|
|
66
52
|
parameters=EXPLORE_PARAMETERS,
|
|
67
53
|
prompt_file="prompts/prompt-sub-agent-explore.md",
|
|
68
54
|
tool_set=(tools.BASH, tools.READ),
|
|
69
|
-
prompt_builder=_explore_prompt_builder,
|
|
70
55
|
active_form="Exploring",
|
|
71
56
|
output_schema_arg="output_format",
|
|
72
57
|
)
|
|
@@ -64,7 +64,7 @@ register_sub_agent(
|
|
|
64
64
|
description=TASK_DESCRIPTION,
|
|
65
65
|
parameters=TASK_PARAMETERS,
|
|
66
66
|
prompt_file="prompts/prompt-sub-agent.md",
|
|
67
|
-
tool_set=(tools.BASH, tools.READ, tools.EDIT, tools.WRITE
|
|
67
|
+
tool_set=(tools.BASH, tools.READ, tools.EDIT, tools.WRITE),
|
|
68
68
|
active_form="Tasking",
|
|
69
69
|
output_schema_arg="output_format",
|
|
70
70
|
)
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
3
|
from klaude_code.protocol import tools
|
|
6
4
|
from klaude_code.protocol.sub_agent import SubAgentProfile, register_sub_agent
|
|
7
5
|
|
|
@@ -21,7 +19,7 @@ Capabilities:
|
|
|
21
19
|
How to use:
|
|
22
20
|
- Write a clear prompt describing what information you need - the agent will search and fetch as needed
|
|
23
21
|
- Account for "Today's date" in <env>. For example, if <env> says "Today's date: 2025-07-01", and the user wants the latest docs, do not use 2024 in the search query. Use 2025.
|
|
24
|
-
-
|
|
22
|
+
- Provide the url if you already know the target page
|
|
25
23
|
- Use `output_format` (JSON Schema) to get structured data back from the agent
|
|
26
24
|
|
|
27
25
|
What you receive:
|
|
@@ -48,10 +46,6 @@ WEB_AGENT_PARAMETERS = {
|
|
|
48
46
|
"type": "string",
|
|
49
47
|
"description": "A short (3-5 word) description of the task",
|
|
50
48
|
},
|
|
51
|
-
"url": {
|
|
52
|
-
"type": "string",
|
|
53
|
-
"description": "The URL to fetch and analyze. If not provided, the agent will search the web first",
|
|
54
|
-
},
|
|
55
49
|
"prompt": {
|
|
56
50
|
"type": "string",
|
|
57
51
|
"description": "Instructions for searching, analyzing, or extracting content from the web page",
|
|
@@ -66,15 +60,6 @@ WEB_AGENT_PARAMETERS = {
|
|
|
66
60
|
}
|
|
67
61
|
|
|
68
62
|
|
|
69
|
-
def _web_agent_prompt_builder(args: dict[str, Any]) -> str:
|
|
70
|
-
"""Build the WebAgent prompt from tool arguments."""
|
|
71
|
-
url = args.get("url", "")
|
|
72
|
-
prompt = args.get("prompt", "")
|
|
73
|
-
if url:
|
|
74
|
-
return f"URL to fetch: {url}\nTask: {prompt}"
|
|
75
|
-
return prompt
|
|
76
|
-
|
|
77
|
-
|
|
78
63
|
register_sub_agent(
|
|
79
64
|
SubAgentProfile(
|
|
80
65
|
name="WebAgent",
|
|
@@ -82,7 +67,6 @@ register_sub_agent(
|
|
|
82
67
|
parameters=WEB_AGENT_PARAMETERS,
|
|
83
68
|
prompt_file="prompts/prompt-sub-agent-web.md",
|
|
84
69
|
tool_set=(tools.BASH, tools.READ, tools.WEB_FETCH, tools.WEB_SEARCH, tools.WRITE),
|
|
85
|
-
prompt_builder=_web_agent_prompt_builder,
|
|
86
70
|
active_form="Surfing",
|
|
87
71
|
output_schema_arg="output_format",
|
|
88
72
|
)
|
klaude_code/protocol/tools.py
CHANGED
|
@@ -14,12 +14,11 @@ class ExecDisplay(DisplayABC):
|
|
|
14
14
|
"""Only handle TaskFinishEvent."""
|
|
15
15
|
match event:
|
|
16
16
|
case events.TaskStartEvent():
|
|
17
|
-
|
|
17
|
+
pass
|
|
18
18
|
case events.ErrorEvent() as e:
|
|
19
|
-
emit_osc94(OSC94States.
|
|
19
|
+
emit_osc94(OSC94States.ERROR)
|
|
20
20
|
print(f"Error: {e.error_message}")
|
|
21
21
|
case events.TaskFinishEvent() as e:
|
|
22
|
-
emit_osc94(OSC94States.HIDDEN)
|
|
23
22
|
# Print the task result when task finishes
|
|
24
23
|
if e.task_result.strip():
|
|
25
24
|
print(e.task_result)
|
|
@@ -19,7 +19,7 @@ class REPLDisplay(DisplayABC):
|
|
|
19
19
|
- Syntax-highlighted code blocks and diffs
|
|
20
20
|
- Animated spinners for in-progress operations
|
|
21
21
|
- Tool call and result visualization
|
|
22
|
-
- OSC94
|
|
22
|
+
- OSC94 error indicator (for supported terminals)
|
|
23
23
|
- Desktop notifications on task completion
|
|
24
24
|
|
|
25
25
|
This is the primary display mode for interactive klaude-code sessions.
|
|
@@ -416,14 +416,12 @@ class DisplayEventHandler:
|
|
|
416
416
|
self._sub_agent_thinking_headers[event.session_id] = SubAgentThinkingHeaderState()
|
|
417
417
|
self.renderer.spinner_start()
|
|
418
418
|
self.renderer.display_task_start(event)
|
|
419
|
-
emit_osc94(OSC94States.INDETERMINATE)
|
|
420
419
|
|
|
421
420
|
def _on_developer_message(self, event: events.DeveloperMessageEvent) -> None:
|
|
422
421
|
self.renderer.display_developer_message(event)
|
|
423
422
|
self.renderer.display_command_output(event)
|
|
424
423
|
|
|
425
424
|
def _on_turn_start(self, event: events.TurnStartEvent) -> None:
|
|
426
|
-
emit_osc94(OSC94States.INDETERMINATE)
|
|
427
425
|
self.renderer.display_turn_start(event)
|
|
428
426
|
self.spinner_status.clear_for_new_turn()
|
|
429
427
|
self.spinner_status.set_reasoning_status(None)
|
|
@@ -533,7 +531,6 @@ class DisplayEventHandler:
|
|
|
533
531
|
self.renderer.display_task_finish(event)
|
|
534
532
|
if not self.renderer.is_sub_agent_session(event.session_id):
|
|
535
533
|
r_status.clear_task_start()
|
|
536
|
-
emit_osc94(OSC94States.HIDDEN)
|
|
537
534
|
self.spinner_status.reset()
|
|
538
535
|
self.renderer.spinner_stop()
|
|
539
536
|
self.renderer.console.print(Rule(characters="─", style=ThemeKey.LINES))
|
|
@@ -548,7 +545,6 @@ class DisplayEventHandler:
|
|
|
548
545
|
self.spinner_status.reset()
|
|
549
546
|
r_status.clear_task_start()
|
|
550
547
|
await self.stage_manager.transition_to(Stage.WAITING)
|
|
551
|
-
emit_osc94(OSC94States.HIDDEN)
|
|
552
548
|
self.renderer.display_interrupt()
|
|
553
549
|
|
|
554
550
|
async def _on_error(self, event: events.ErrorEvent) -> None:
|
|
@@ -560,7 +556,6 @@ class DisplayEventHandler:
|
|
|
560
556
|
self.spinner_status.reset()
|
|
561
557
|
|
|
562
558
|
async def _on_end(self, event: events.EndEvent) -> None:
|
|
563
|
-
emit_osc94(OSC94States.HIDDEN)
|
|
564
559
|
await self.stage_manager.transition_to(Stage.WAITING)
|
|
565
560
|
self.renderer.spinner_stop()
|
|
566
561
|
self.spinner_status.reset()
|
|
@@ -314,7 +314,7 @@ class PromptToolkitInput(InputProviderABC):
|
|
|
314
314
|
"question": "bold",
|
|
315
315
|
"msg": "",
|
|
316
316
|
"meta": "fg:ansibrightblack",
|
|
317
|
-
"frame.border": "fg:ansibrightblack",
|
|
317
|
+
"frame.border": "fg:ansibrightblack dim",
|
|
318
318
|
"search_prefix": "fg:ansibrightblack",
|
|
319
319
|
"search_placeholder": "fg:ansibrightblack italic",
|
|
320
320
|
"search_input": "",
|
|
@@ -604,6 +604,10 @@ class PromptToolkitInput(InputProviderABC):
|
|
|
604
604
|
(symbol_style, " ctrl-l "),
|
|
605
605
|
(text_style, " "),
|
|
606
606
|
(text_style, "models"),
|
|
607
|
+
(text_style, " "),
|
|
608
|
+
(symbol_style, " ctrl-t "),
|
|
609
|
+
(text_style, " "),
|
|
610
|
+
(text_style, "think"),
|
|
607
611
|
]
|
|
608
612
|
)
|
|
609
613
|
|
|
@@ -257,7 +257,7 @@ class REPLRenderer:
|
|
|
257
257
|
self.print(r_developer.render_developer_message(e))
|
|
258
258
|
|
|
259
259
|
def display_command_output(self, e: events.DeveloperMessageEvent) -> None:
|
|
260
|
-
if not e.item
|
|
260
|
+
if not r_developer.get_command_output(e.item):
|
|
261
261
|
return
|
|
262
262
|
with self.session_print_context(e.session_id):
|
|
263
263
|
self.print(r_developer.render_command_output(e))
|
|
@@ -458,6 +458,7 @@ class REPLRenderer:
|
|
|
458
458
|
return
|
|
459
459
|
with contextlib.suppress(Exception):
|
|
460
460
|
# Avoid cursor restore when stopping right before prompt_toolkit.
|
|
461
|
+
# This will leave a blank line before prompt input
|
|
461
462
|
self._bottom_live.transient = False
|
|
462
463
|
self._bottom_live.stop()
|
|
463
464
|
self._bottom_live = None
|
|
@@ -12,15 +12,19 @@ from klaude_code.ui.rich.theme import ThemeKey
|
|
|
12
12
|
REMINDER_BULLET = " ⧉"
|
|
13
13
|
|
|
14
14
|
|
|
15
|
+
def get_command_output(item: message.DeveloperMessage) -> model.CommandOutput | None:
|
|
16
|
+
if not item.ui_extra:
|
|
17
|
+
return None
|
|
18
|
+
for ui_item in item.ui_extra.items:
|
|
19
|
+
if isinstance(ui_item, model.CommandOutputUIItem):
|
|
20
|
+
return ui_item.output
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
|
|
15
24
|
def need_render_developer_message(e: events.DeveloperMessageEvent) -> bool:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
or e.item.todo_use
|
|
20
|
-
or e.item.at_files
|
|
21
|
-
or e.item.user_image_count
|
|
22
|
-
or e.item.skill_name
|
|
23
|
-
)
|
|
25
|
+
if not e.item.ui_extra:
|
|
26
|
+
return False
|
|
27
|
+
return any(not isinstance(ui_item, model.CommandOutputUIItem) for ui_item in e.item.ui_extra.items)
|
|
24
28
|
|
|
25
29
|
|
|
26
30
|
def render_developer_message(e: events.DeveloperMessageEvent) -> RenderableType:
|
|
@@ -31,112 +35,124 @@ def render_developer_message(e: events.DeveloperMessageEvent) -> RenderableType:
|
|
|
31
35
|
"""
|
|
32
36
|
parts: list[RenderableType] = []
|
|
33
37
|
|
|
34
|
-
if
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
38
|
+
if e.item.ui_extra:
|
|
39
|
+
for ui_item in e.item.ui_extra.items:
|
|
40
|
+
match ui_item:
|
|
41
|
+
case model.MemoryLoadedUIItem() as item:
|
|
42
|
+
grid = create_grid()
|
|
43
|
+
grid.add_row(
|
|
44
|
+
Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
|
|
45
|
+
Text.assemble(
|
|
46
|
+
("Load memory ", ThemeKey.REMINDER),
|
|
47
|
+
Text(", ", ThemeKey.REMINDER).join(
|
|
48
|
+
render_path(mem.path, ThemeKey.REMINDER_BOLD) for mem in item.files
|
|
49
|
+
),
|
|
50
|
+
),
|
|
51
|
+
)
|
|
52
|
+
parts.append(grid)
|
|
53
|
+
case model.ExternalFileChangesUIItem() as item:
|
|
54
|
+
grid = create_grid()
|
|
55
|
+
for file_path in item.paths:
|
|
56
|
+
grid.add_row(
|
|
57
|
+
Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
|
|
58
|
+
Text.assemble(
|
|
59
|
+
("Read ", ThemeKey.REMINDER),
|
|
60
|
+
render_path(file_path, ThemeKey.REMINDER_BOLD),
|
|
61
|
+
(" after external changes", ThemeKey.REMINDER),
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
parts.append(grid)
|
|
65
|
+
case model.TodoReminderUIItem() as item:
|
|
66
|
+
match item.reason:
|
|
67
|
+
case "not_used_recently":
|
|
68
|
+
text = "Todo hasn't been updated recently"
|
|
69
|
+
case "empty":
|
|
70
|
+
text = "Todo list is empty"
|
|
71
|
+
case _:
|
|
72
|
+
text = "Todo reminder"
|
|
73
|
+
grid = create_grid()
|
|
74
|
+
grid.add_row(
|
|
75
|
+
Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
|
|
76
|
+
Text(text, ThemeKey.REMINDER),
|
|
77
|
+
)
|
|
78
|
+
parts.append(grid)
|
|
79
|
+
case model.AtFileOpsUIItem() as item:
|
|
80
|
+
grid = create_grid()
|
|
81
|
+
grouped: dict[tuple[str, str | None], list[str]] = {}
|
|
82
|
+
for op in item.ops:
|
|
83
|
+
key = (op.operation, op.mentioned_in)
|
|
84
|
+
grouped.setdefault(key, []).append(op.path)
|
|
85
|
+
|
|
86
|
+
for (operation, mentioned_in), paths in grouped.items():
|
|
87
|
+
path_texts = Text(", ", ThemeKey.REMINDER).join(
|
|
88
|
+
render_path(p, ThemeKey.REMINDER_BOLD) for p in paths
|
|
89
|
+
)
|
|
90
|
+
if mentioned_in:
|
|
91
|
+
grid.add_row(
|
|
92
|
+
Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
|
|
93
|
+
Text.assemble(
|
|
94
|
+
(f"{operation} ", ThemeKey.REMINDER),
|
|
95
|
+
path_texts,
|
|
96
|
+
(" mentioned in ", ThemeKey.REMINDER),
|
|
97
|
+
render_path(mentioned_in, ThemeKey.REMINDER_BOLD),
|
|
98
|
+
),
|
|
99
|
+
)
|
|
100
|
+
else:
|
|
101
|
+
grid.add_row(
|
|
102
|
+
Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
|
|
103
|
+
Text.assemble(
|
|
104
|
+
(f"{operation} ", ThemeKey.REMINDER),
|
|
105
|
+
path_texts,
|
|
106
|
+
),
|
|
107
|
+
)
|
|
108
|
+
parts.append(grid)
|
|
109
|
+
case model.UserImagesUIItem() as item:
|
|
110
|
+
grid = create_grid()
|
|
111
|
+
count = item.count
|
|
112
|
+
grid.add_row(
|
|
113
|
+
Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
|
|
114
|
+
Text(
|
|
115
|
+
f"Attached {count} image{'s' if count > 1 else ''}",
|
|
116
|
+
style=ThemeKey.REMINDER,
|
|
117
|
+
),
|
|
118
|
+
)
|
|
119
|
+
parts.append(grid)
|
|
120
|
+
case model.SkillActivatedUIItem() as item:
|
|
121
|
+
grid = create_grid()
|
|
122
|
+
grid.add_row(
|
|
123
|
+
Text(REMINDER_BULLET, style=ThemeKey.REMINDER),
|
|
124
|
+
Text.assemble(
|
|
125
|
+
("Activated skill ", ThemeKey.REMINDER),
|
|
126
|
+
(item.name, ThemeKey.REMINDER_BOLD),
|
|
127
|
+
),
|
|
128
|
+
)
|
|
129
|
+
parts.append(grid)
|
|
130
|
+
case model.CommandOutputUIItem():
|
|
131
|
+
# Rendered via render_command_output
|
|
132
|
+
pass
|
|
118
133
|
|
|
119
134
|
return Group(*parts) if parts else Text("")
|
|
120
135
|
|
|
121
136
|
|
|
122
137
|
def render_command_output(e: events.DeveloperMessageEvent) -> RenderableType:
|
|
123
138
|
"""Render developer command output content."""
|
|
124
|
-
|
|
139
|
+
command_output = get_command_output(e.item)
|
|
140
|
+
if not command_output:
|
|
125
141
|
return Text("")
|
|
126
142
|
|
|
127
143
|
content = message.join_text_parts(e.item.parts)
|
|
128
|
-
match
|
|
144
|
+
match command_output.command_name:
|
|
129
145
|
case commands.CommandName.HELP:
|
|
130
146
|
return Padding.indent(Text.from_markup(content or ""), level=2)
|
|
131
147
|
case commands.CommandName.STATUS:
|
|
132
|
-
return _render_status_output(
|
|
148
|
+
return _render_status_output(command_output)
|
|
133
149
|
case commands.CommandName.RELEASE_NOTES:
|
|
134
150
|
return Padding.indent(NoInsetMarkdown(content or ""), level=2)
|
|
135
151
|
case commands.CommandName.FORK_SESSION:
|
|
136
|
-
return _render_fork_session_output(
|
|
152
|
+
return _render_fork_session_output(command_output)
|
|
137
153
|
case _:
|
|
138
154
|
content = content or "(no content)"
|
|
139
|
-
style = ThemeKey.TOOL_RESULT if not
|
|
155
|
+
style = ThemeKey.TOOL_RESULT if not command_output.is_error else ThemeKey.ERROR
|
|
140
156
|
return Padding.indent(truncate_middle(content, base_style=style), level=2)
|
|
141
157
|
|
|
142
158
|
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
from importlib.metadata import PackageNotFoundError, version
|
|
2
2
|
|
|
3
|
-
from rich import box
|
|
4
3
|
from rich.console import Group, RenderableType
|
|
5
4
|
from rich.padding import Padding
|
|
6
|
-
from rich.panel import Panel
|
|
7
5
|
from rich.text import Text
|
|
8
6
|
|
|
9
7
|
from klaude_code.const import DEFAULT_MAX_TOKENS
|
|
10
8
|
from klaude_code.protocol import events, model
|
|
11
9
|
from klaude_code.trace import is_debug_enabled
|
|
12
10
|
from klaude_code.ui.renderers.common import create_grid
|
|
11
|
+
from klaude_code.ui.rich.quote import Quote
|
|
13
12
|
from klaude_code.ui.rich.theme import ThemeKey
|
|
14
13
|
from klaude_code.ui.utils.common import format_model_params, format_number
|
|
15
14
|
|
|
@@ -199,17 +198,29 @@ def render_task_metadata(e: events.TaskMetadataEvent) -> RenderableType:
|
|
|
199
198
|
|
|
200
199
|
|
|
201
200
|
def render_welcome(e: events.WelcomeEvent) -> RenderableType:
|
|
202
|
-
"""Render the welcome panel with model info and settings.
|
|
201
|
+
"""Render the welcome panel with model info and settings.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
e: The welcome event.
|
|
205
|
+
"""
|
|
203
206
|
debug_mode = is_debug_enabled()
|
|
204
207
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
(
|
|
211
|
-
("
|
|
212
|
-
(
|
|
208
|
+
panel_content = Text()
|
|
209
|
+
|
|
210
|
+
if e.show_klaude_code_info:
|
|
211
|
+
# First line: Klaude Code version
|
|
212
|
+
klaude_code_style = ThemeKey.WELCOME_DEBUG_TITLE if debug_mode else ThemeKey.WELCOME_HIGHLIGHT_BOLD
|
|
213
|
+
panel_content.append_text(Text("Klaude Code", style=klaude_code_style))
|
|
214
|
+
panel_content.append_text(Text(f" v{_get_version()}", style=ThemeKey.WELCOME_INFO))
|
|
215
|
+
panel_content.append_text(Text("\n"))
|
|
216
|
+
|
|
217
|
+
# Model line: model @ provider · params...
|
|
218
|
+
panel_content.append_text(
|
|
219
|
+
Text.assemble(
|
|
220
|
+
(str(e.llm_config.model), ThemeKey.WELCOME_HIGHLIGHT),
|
|
221
|
+
(" @ ", ThemeKey.WELCOME_INFO),
|
|
222
|
+
(e.llm_config.provider_name, ThemeKey.WELCOME_INFO),
|
|
223
|
+
)
|
|
213
224
|
)
|
|
214
225
|
|
|
215
226
|
# Use format_model_params for consistent formatting
|
|
@@ -228,7 +239,9 @@ def render_welcome(e: events.WelcomeEvent) -> RenderableType:
|
|
|
228
239
|
)
|
|
229
240
|
|
|
230
241
|
border_style = ThemeKey.WELCOME_DEBUG_BORDER if debug_mode else ThemeKey.LINES
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
"",
|
|
234
|
-
|
|
242
|
+
|
|
243
|
+
if e.show_klaude_code_info:
|
|
244
|
+
groups = ["", Quote(panel_content, style=border_style, prefix="▌ "), ""]
|
|
245
|
+
else:
|
|
246
|
+
groups = [Quote(panel_content, style=border_style, prefix="▌ "), ""]
|
|
247
|
+
return Group(*groups)
|