klaude-code 2.3.0__py3-none-any.whl → 2.4.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/cli/list_model.py +3 -3
- klaude_code/cli/main.py +2 -2
- klaude_code/config/assets/builtin_config.yaml +165 -307
- klaude_code/config/config.py +17 -17
- klaude_code/config/{select_model.py → model_matcher.py} +7 -7
- klaude_code/config/sub_agent_model_helper.py +1 -1
- klaude_code/config/thinking.py +2 -2
- klaude_code/core/executor.py +59 -52
- klaude_code/core/tool/file/diff_builder.py +25 -18
- klaude_code/llm/anthropic/client.py +5 -5
- klaude_code/llm/client.py +1 -1
- klaude_code/llm/codex/client.py +2 -2
- klaude_code/llm/google/client.py +6 -6
- klaude_code/llm/input_common.py +2 -2
- klaude_code/llm/openai_compatible/client.py +3 -3
- klaude_code/llm/openai_compatible/stream.py +1 -1
- klaude_code/llm/openrouter/client.py +4 -4
- klaude_code/llm/openrouter/input.py +1 -3
- klaude_code/llm/responses/client.py +5 -5
- klaude_code/protocol/events/__init__.py +7 -1
- klaude_code/protocol/events/chat.py +10 -0
- klaude_code/protocol/llm_param.py +1 -1
- klaude_code/protocol/model.py +0 -26
- klaude_code/protocol/op.py +0 -5
- klaude_code/session/session.py +4 -2
- klaude_code/tui/command/clear_cmd.py +0 -1
- klaude_code/tui/command/command_abc.py +6 -4
- klaude_code/tui/command/copy_cmd.py +10 -10
- klaude_code/tui/command/debug_cmd.py +11 -10
- klaude_code/tui/command/export_online_cmd.py +18 -23
- klaude_code/tui/command/fork_session_cmd.py +39 -43
- klaude_code/tui/command/model_cmd.py +5 -7
- klaude_code/tui/command/{model_select.py → model_picker.py} +3 -5
- klaude_code/tui/command/refresh_cmd.py +0 -1
- klaude_code/tui/command/registry.py +15 -21
- klaude_code/tui/command/resume_cmd.py +10 -16
- klaude_code/tui/command/status_cmd.py +8 -12
- klaude_code/tui/command/sub_agent_model_cmd.py +11 -16
- klaude_code/tui/command/terminal_setup_cmd.py +8 -11
- klaude_code/tui/command/thinking_cmd.py +4 -6
- klaude_code/tui/commands.py +5 -0
- klaude_code/tui/components/command_output.py +96 -0
- klaude_code/tui/components/developer.py +3 -110
- klaude_code/tui/components/welcome.py +2 -2
- klaude_code/tui/input/prompt_toolkit.py +6 -8
- klaude_code/tui/machine.py +5 -0
- klaude_code/tui/renderer.py +5 -5
- klaude_code/tui/runner.py +0 -6
- klaude_code/tui/terminal/selector.py +4 -4
- {klaude_code-2.3.0.dist-info → klaude_code-2.4.0.dist-info}/METADATA +21 -74
- {klaude_code-2.3.0.dist-info → klaude_code-2.4.0.dist-info}/RECORD +53 -52
- {klaude_code-2.3.0.dist-info → klaude_code-2.4.0.dist-info}/WHEEL +0 -0
- {klaude_code-2.3.0.dist-info → klaude_code-2.4.0.dist-info}/entry_points.txt +0 -0
klaude_code/protocol/model.py
CHANGED
|
@@ -5,7 +5,6 @@ from typing import Annotated, Any, Literal
|
|
|
5
5
|
from pydantic import BaseModel, Field, computed_field
|
|
6
6
|
|
|
7
7
|
from klaude_code.const import DEFAULT_MAX_TOKENS
|
|
8
|
-
from klaude_code.protocol.commands import CommandName
|
|
9
8
|
from klaude_code.protocol.tools import SubAgentType
|
|
10
9
|
|
|
11
10
|
RoleType = Literal["system", "developer", "user", "assistant", "tool"]
|
|
@@ -272,12 +271,6 @@ ToolResultUIExtra = Annotated[
|
|
|
272
271
|
]
|
|
273
272
|
|
|
274
273
|
|
|
275
|
-
class CommandOutput(BaseModel):
|
|
276
|
-
command_name: CommandName
|
|
277
|
-
ui_extra: ToolResultUIExtra | None = None
|
|
278
|
-
is_error: bool = False
|
|
279
|
-
|
|
280
|
-
|
|
281
274
|
class MemoryFileLoaded(BaseModel):
|
|
282
275
|
path: str
|
|
283
276
|
mentioned_patterns: list[str] = Field(default_factory=list)
|
|
@@ -319,11 +312,6 @@ class SkillActivatedUIItem(BaseModel):
|
|
|
319
312
|
name: str
|
|
320
313
|
|
|
321
314
|
|
|
322
|
-
class CommandOutputUIItem(BaseModel):
|
|
323
|
-
type: Literal["command_output"] = "command_output"
|
|
324
|
-
output: CommandOutput
|
|
325
|
-
|
|
326
|
-
|
|
327
315
|
type DeveloperUIItem = (
|
|
328
316
|
MemoryLoadedUIItem
|
|
329
317
|
| ExternalFileChangesUIItem
|
|
@@ -331,7 +319,6 @@ type DeveloperUIItem = (
|
|
|
331
319
|
| AtFileOpsUIItem
|
|
332
320
|
| UserImagesUIItem
|
|
333
321
|
| SkillActivatedUIItem
|
|
334
|
-
| CommandOutputUIItem
|
|
335
322
|
)
|
|
336
323
|
|
|
337
324
|
|
|
@@ -343,19 +330,6 @@ class DeveloperUIExtra(BaseModel):
|
|
|
343
330
|
items: list[DeveloperUIItem] = Field(default_factory=_empty_developer_ui_items)
|
|
344
331
|
|
|
345
332
|
|
|
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
|
-
|
|
359
333
|
class SubAgentState(BaseModel):
|
|
360
334
|
sub_agent_type: SubAgentType
|
|
361
335
|
sub_agent_desc: str
|
klaude_code/protocol/op.py
CHANGED
|
@@ -52,11 +52,6 @@ class RunAgentOperation(Operation):
|
|
|
52
52
|
type: OperationType = OperationType.RUN_AGENT
|
|
53
53
|
session_id: str
|
|
54
54
|
input: UserInputPayload
|
|
55
|
-
# Frontends may choose to render the user message themselves (e.g. TUI) to support
|
|
56
|
-
# event-only commands; in that case the core should skip emitting the UserMessageEvent.
|
|
57
|
-
emit_user_message_event: bool = True
|
|
58
|
-
# Frontends may choose to run without persisting input (e.g. some interactive commands).
|
|
59
|
-
persist_user_input: bool = True
|
|
60
55
|
|
|
61
56
|
async def execute(self, handler: OperationHandler) -> None:
|
|
62
57
|
await handler.handle_run_agent(self)
|
klaude_code/session/session.py
CHANGED
|
@@ -237,7 +237,7 @@ class Session(BaseModel):
|
|
|
237
237
|
Args:
|
|
238
238
|
new_id: Optional ID for the forked session.
|
|
239
239
|
until_index: If provided, only copy conversation history up to (but not including) this index.
|
|
240
|
-
If
|
|
240
|
+
If -1, copy all history.
|
|
241
241
|
"""
|
|
242
242
|
|
|
243
243
|
forked = Session.create(id=new_id, work_dir=self.work_dir)
|
|
@@ -250,7 +250,9 @@ class Session(BaseModel):
|
|
|
250
250
|
forked.todos = [todo.model_copy(deep=True) for todo in self.todos]
|
|
251
251
|
|
|
252
252
|
history_to_copy = (
|
|
253
|
-
self.conversation_history[:until_index]
|
|
253
|
+
self.conversation_history[:until_index]
|
|
254
|
+
if (until_index is not None and until_index >= 0)
|
|
255
|
+
else self.conversation_history
|
|
254
256
|
)
|
|
255
257
|
items = [it.model_copy(deep=True) for it in history_to_copy]
|
|
256
258
|
if items:
|
|
@@ -37,14 +37,16 @@ class CommandResult(BaseModel):
|
|
|
37
37
|
"""Result of a command execution."""
|
|
38
38
|
|
|
39
39
|
events: (
|
|
40
|
-
list[
|
|
40
|
+
list[
|
|
41
|
+
protocol_events.CommandOutputEvent
|
|
42
|
+
| protocol_events.ErrorEvent
|
|
43
|
+
| protocol_events.WelcomeEvent
|
|
44
|
+
| protocol_events.ReplayHistoryEvent
|
|
45
|
+
]
|
|
41
46
|
| None
|
|
42
47
|
) = None # List of UI events to display immediately
|
|
43
48
|
operations: list[op.Operation] | None = None
|
|
44
49
|
|
|
45
|
-
# Persistence controls: some slash commands are UI/control actions and should not be written to session history.
|
|
46
|
-
persist: bool = True
|
|
47
|
-
|
|
48
50
|
|
|
49
51
|
class CommandABC(ABC):
|
|
50
52
|
"""Abstract base class for slash commands."""
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from klaude_code.protocol import commands, events, message
|
|
1
|
+
from klaude_code.protocol import commands, events, message
|
|
2
2
|
from klaude_code.tui.input.clipboard import copy_to_clipboard
|
|
3
3
|
|
|
4
4
|
from .command_abc import Agent, CommandABC, CommandResult
|
|
@@ -20,10 +20,10 @@ class CopyCommand(CommandABC):
|
|
|
20
20
|
|
|
21
21
|
last = _get_last_assistant_text(agent.session.conversation_history)
|
|
22
22
|
if not last:
|
|
23
|
-
return
|
|
23
|
+
return _command_output(agent, "(no assistant message to copy)", self.name, is_error=True)
|
|
24
24
|
|
|
25
25
|
copy_to_clipboard(last)
|
|
26
|
-
return
|
|
26
|
+
return _command_output(agent, "Copied last assistant message to clipboard.", self.name)
|
|
27
27
|
|
|
28
28
|
|
|
29
29
|
def _get_last_assistant_text(history: list[message.HistoryEvent]) -> str:
|
|
@@ -37,16 +37,16 @@ def _get_last_assistant_text(history: list[message.HistoryEvent]) -> str:
|
|
|
37
37
|
return ""
|
|
38
38
|
|
|
39
39
|
|
|
40
|
-
def
|
|
40
|
+
def _command_output(
|
|
41
|
+
agent: Agent, content: str, command_name: commands.CommandName, *, is_error: bool = False
|
|
42
|
+
) -> CommandResult:
|
|
41
43
|
return CommandResult(
|
|
42
44
|
events=[
|
|
43
|
-
events.
|
|
45
|
+
events.CommandOutputEvent(
|
|
44
46
|
session_id=agent.session.id,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
),
|
|
47
|
+
command_name=command_name,
|
|
48
|
+
content=content,
|
|
49
|
+
is_error=is_error,
|
|
49
50
|
)
|
|
50
51
|
],
|
|
51
|
-
persist=False,
|
|
52
52
|
)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from klaude_code.log import DebugType, get_current_log_file, is_debug_enabled, set_debug_logging
|
|
2
|
-
from klaude_code.protocol import commands, events, message
|
|
2
|
+
from klaude_code.protocol import commands, events, message
|
|
3
3
|
|
|
4
4
|
from .command_abc import Agent, CommandABC, CommandResult
|
|
5
5
|
|
|
@@ -52,7 +52,7 @@ class DebugCommand(CommandABC):
|
|
|
52
52
|
# /debug (no args) - enable debug
|
|
53
53
|
if not raw:
|
|
54
54
|
set_debug_logging(True, write_to_file=True)
|
|
55
|
-
return self.
|
|
55
|
+
return self._command_output(agent, _format_status())
|
|
56
56
|
|
|
57
57
|
# /debug <filters> - enable with filters
|
|
58
58
|
try:
|
|
@@ -60,21 +60,22 @@ class DebugCommand(CommandABC):
|
|
|
60
60
|
if filters:
|
|
61
61
|
set_debug_logging(True, write_to_file=True, filters=filters)
|
|
62
62
|
filter_names = ", ".join(sorted(dt.value for dt in filters))
|
|
63
|
-
return self.
|
|
63
|
+
return self._command_output(agent, f"Filters: {filter_names}\n{_format_status()}")
|
|
64
64
|
except ValueError:
|
|
65
65
|
pass
|
|
66
66
|
|
|
67
|
-
return self.
|
|
67
|
+
return self._command_output(
|
|
68
|
+
agent, f"Invalid filter: {raw}\nValid: {', '.join(dt.value for dt in DebugType)}", is_error=True
|
|
69
|
+
)
|
|
68
70
|
|
|
69
|
-
def
|
|
71
|
+
def _command_output(self, agent: "Agent", content: str, *, is_error: bool = False) -> CommandResult:
|
|
70
72
|
return CommandResult(
|
|
71
73
|
events=[
|
|
72
|
-
events.
|
|
74
|
+
events.CommandOutputEvent(
|
|
73
75
|
session_id=agent.session.id,
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
),
|
|
76
|
+
command_name=self.name,
|
|
77
|
+
content=content,
|
|
78
|
+
is_error=is_error,
|
|
78
79
|
)
|
|
79
80
|
]
|
|
80
81
|
)
|
|
@@ -9,7 +9,7 @@ from pathlib import Path
|
|
|
9
9
|
from rich.console import Console
|
|
10
10
|
from rich.text import Text
|
|
11
11
|
|
|
12
|
-
from klaude_code.protocol import commands, events, message
|
|
12
|
+
from klaude_code.protocol import commands, events, message
|
|
13
13
|
from klaude_code.session.export import build_export_html
|
|
14
14
|
|
|
15
15
|
from .command_abc import Agent, CommandABC, CommandResult
|
|
@@ -39,54 +39,49 @@ class ExportOnlineCommand(CommandABC):
|
|
|
39
39
|
# Check if npx or surge is available
|
|
40
40
|
surge_cmd = self._get_surge_command()
|
|
41
41
|
if not surge_cmd:
|
|
42
|
-
event = events.
|
|
42
|
+
event = events.CommandOutputEvent(
|
|
43
43
|
session_id=agent.session.id,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
),
|
|
44
|
+
command_name=self.name,
|
|
45
|
+
content="surge.sh CLI not found. Install with: npm install -g surge",
|
|
46
|
+
is_error=True,
|
|
48
47
|
)
|
|
49
48
|
return CommandResult(events=[event])
|
|
50
49
|
|
|
51
50
|
try:
|
|
52
51
|
console = Console()
|
|
53
52
|
# Check login status inside status context since npx surge whoami can be slow
|
|
54
|
-
with console.status(Text("Checking surge.sh login status
|
|
53
|
+
with console.status(Text("Checking surge.sh login status...", style="dim"), spinner_style="dim"):
|
|
55
54
|
logged_in = self._is_surge_logged_in(surge_cmd)
|
|
56
55
|
|
|
57
56
|
if not logged_in:
|
|
58
57
|
login_cmd = " ".join([*surge_cmd, "login"])
|
|
59
|
-
event = events.
|
|
58
|
+
event = events.CommandOutputEvent(
|
|
60
59
|
session_id=agent.session.id,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
),
|
|
60
|
+
command_name=self.name,
|
|
61
|
+
content=f"Not logged in to surge.sh. Please run: {login_cmd}",
|
|
62
|
+
is_error=True,
|
|
65
63
|
)
|
|
66
64
|
return CommandResult(events=[event])
|
|
67
65
|
|
|
68
|
-
with console.status(Text("Deploying to surge.sh
|
|
66
|
+
with console.status(Text("Deploying to surge.sh...", style="dim"), spinner_style="dim"):
|
|
69
67
|
html_doc = self._build_html(agent)
|
|
70
68
|
domain = self._generate_domain()
|
|
71
69
|
url = self._deploy_to_surge(surge_cmd, html_doc, domain)
|
|
72
70
|
|
|
73
|
-
event = events.
|
|
71
|
+
event = events.CommandOutputEvent(
|
|
74
72
|
session_id=agent.session.id,
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
ui_extra=model.build_command_output_extra(self.name),
|
|
78
|
-
),
|
|
73
|
+
command_name=self.name,
|
|
74
|
+
content=f"Session deployed to: {url}",
|
|
79
75
|
)
|
|
80
76
|
return CommandResult(events=[event])
|
|
81
77
|
except Exception as exc:
|
|
82
78
|
import traceback
|
|
83
79
|
|
|
84
|
-
event = events.
|
|
80
|
+
event = events.CommandOutputEvent(
|
|
85
81
|
session_id=agent.session.id,
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
),
|
|
82
|
+
command_name=self.name,
|
|
83
|
+
content=f"Failed to deploy session: {exc}\n{traceback.format_exc()}",
|
|
84
|
+
is_error=True,
|
|
90
85
|
)
|
|
91
86
|
return CommandResult(events=[event])
|
|
92
87
|
|
|
@@ -31,7 +31,7 @@ FORK_SELECT_STYLE = Style(
|
|
|
31
31
|
class ForkPoint:
|
|
32
32
|
"""A fork point in conversation history."""
|
|
33
33
|
|
|
34
|
-
history_index: int
|
|
34
|
+
history_index: int # -1 means fork entire conversation
|
|
35
35
|
user_message: str
|
|
36
36
|
tool_call_stats: dict[str, int] # tool_name -> count
|
|
37
37
|
last_assistant_summary: str
|
|
@@ -94,7 +94,7 @@ def _build_fork_points(conversation_history: list[message.HistoryEvent]) -> list
|
|
|
94
94
|
if user_indices:
|
|
95
95
|
fork_points.append(
|
|
96
96
|
ForkPoint(
|
|
97
|
-
history_index
|
|
97
|
+
history_index=-1, # None means fork entire conversation
|
|
98
98
|
user_message="", # No specific message, this represents the end
|
|
99
99
|
tool_call_stats={},
|
|
100
100
|
last_assistant_summary="",
|
|
@@ -104,9 +104,9 @@ def _build_fork_points(conversation_history: list[message.HistoryEvent]) -> list
|
|
|
104
104
|
return fork_points
|
|
105
105
|
|
|
106
106
|
|
|
107
|
-
def _build_select_items(fork_points: list[ForkPoint]) -> list[SelectItem[int
|
|
107
|
+
def _build_select_items(fork_points: list[ForkPoint]) -> list[SelectItem[int]]:
|
|
108
108
|
"""Build SelectItem list from fork points."""
|
|
109
|
-
items: list[SelectItem[int
|
|
109
|
+
items: list[SelectItem[int]] = []
|
|
110
110
|
|
|
111
111
|
for i, fp in enumerate(fork_points):
|
|
112
112
|
is_first = i == 0
|
|
@@ -116,8 +116,8 @@ def _build_select_items(fork_points: list[ForkPoint]) -> list[SelectItem[int | N
|
|
|
116
116
|
title_parts: list[tuple[str, str]] = []
|
|
117
117
|
|
|
118
118
|
# First line: separator (with special markers for first/last fork points)
|
|
119
|
-
if is_first
|
|
120
|
-
|
|
119
|
+
if is_first:
|
|
120
|
+
pass
|
|
121
121
|
elif is_last:
|
|
122
122
|
title_parts.append(("class:separator", "----- fork from here (entire session) -----\n\n"))
|
|
123
123
|
else:
|
|
@@ -150,17 +150,16 @@ def _build_select_items(fork_points: list[ForkPoint]) -> list[SelectItem[int | N
|
|
|
150
150
|
return items
|
|
151
151
|
|
|
152
152
|
|
|
153
|
-
def _select_fork_point_sync(fork_points: list[ForkPoint]) -> int |
|
|
153
|
+
def _select_fork_point_sync(fork_points: list[ForkPoint]) -> int | Literal["cancelled"]:
|
|
154
154
|
"""Interactive fork point selection (sync version for asyncio.to_thread).
|
|
155
155
|
|
|
156
156
|
Returns:
|
|
157
|
-
- int: history index to fork at (exclusive)
|
|
158
|
-
- None: fork entire conversation
|
|
157
|
+
- int: history index to fork at (exclusive), -1 means fork entire conversation
|
|
159
158
|
- "cancelled": user cancelled selection
|
|
160
159
|
"""
|
|
161
160
|
items = _build_select_items(fork_points)
|
|
162
161
|
if not items:
|
|
163
|
-
return
|
|
162
|
+
return -1
|
|
164
163
|
|
|
165
164
|
# Default to the last option (fork entire conversation)
|
|
166
165
|
last_value = items[-1].value
|
|
@@ -204,14 +203,13 @@ class ForkSessionCommand(CommandABC):
|
|
|
204
203
|
del user_input # unused
|
|
205
204
|
|
|
206
205
|
if agent.session.messages_count == 0:
|
|
207
|
-
event = events.
|
|
206
|
+
event = events.CommandOutputEvent(
|
|
208
207
|
session_id=agent.session.id,
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
),
|
|
208
|
+
command_name=self.name,
|
|
209
|
+
content="(no messages to fork)",
|
|
210
|
+
is_error=True,
|
|
213
211
|
)
|
|
214
|
-
return CommandResult(events=[event]
|
|
212
|
+
return CommandResult(events=[event])
|
|
215
213
|
|
|
216
214
|
# Build fork points from conversation history
|
|
217
215
|
fork_points = _build_fork_points(agent.session.conversation_history)
|
|
@@ -224,51 +222,49 @@ class ForkSessionCommand(CommandABC):
|
|
|
224
222
|
resume_cmd = f"klaude --resume-by-id {new_session.id}"
|
|
225
223
|
copy_to_clipboard(resume_cmd)
|
|
226
224
|
|
|
227
|
-
event = events.
|
|
225
|
+
event = events.CommandOutputEvent(
|
|
228
226
|
session_id=agent.session.id,
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
self.name,
|
|
233
|
-
ui_extra=model.SessionIdUIExtra(session_id=new_session.id),
|
|
234
|
-
),
|
|
235
|
-
),
|
|
227
|
+
command_name=self.name,
|
|
228
|
+
content=f"Session forked successfully. New session id: {new_session.id}",
|
|
229
|
+
ui_extra=model.SessionIdUIExtra(session_id=new_session.id),
|
|
236
230
|
)
|
|
237
|
-
return CommandResult(events=[event]
|
|
231
|
+
return CommandResult(events=[event])
|
|
238
232
|
|
|
239
233
|
# Interactive selection
|
|
240
234
|
selected = await asyncio.to_thread(_select_fork_point_sync, fork_points)
|
|
241
235
|
|
|
242
236
|
if selected == "cancelled":
|
|
243
|
-
event = events.
|
|
237
|
+
event = events.CommandOutputEvent(
|
|
244
238
|
session_id=agent.session.id,
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
ui_extra=model.build_command_output_extra(self.name),
|
|
248
|
-
),
|
|
239
|
+
command_name=self.name,
|
|
240
|
+
content="(fork cancelled)",
|
|
249
241
|
)
|
|
250
|
-
return CommandResult(events=[event]
|
|
242
|
+
return CommandResult(events=[event])
|
|
243
|
+
|
|
244
|
+
# First option (empty session) is just for UI display, not a valid fork point
|
|
245
|
+
if selected == fork_points[0].history_index:
|
|
246
|
+
event = events.CommandOutputEvent(
|
|
247
|
+
session_id=agent.session.id,
|
|
248
|
+
command_name=self.name,
|
|
249
|
+
content="(cannot fork to empty session)",
|
|
250
|
+
is_error=True,
|
|
251
|
+
)
|
|
252
|
+
return CommandResult(events=[event])
|
|
251
253
|
|
|
252
254
|
# Perform the fork
|
|
253
255
|
new_session = agent.session.fork(until_index=selected)
|
|
254
256
|
await new_session.wait_for_flush()
|
|
255
257
|
|
|
256
258
|
# Build result message
|
|
257
|
-
fork_description = "entire conversation" if selected
|
|
259
|
+
fork_description = "entire conversation" if selected == -1 else f"up to message index {selected}"
|
|
258
260
|
|
|
259
261
|
resume_cmd = f"klaude --resume-by-id {new_session.id}"
|
|
260
262
|
copy_to_clipboard(resume_cmd)
|
|
261
263
|
|
|
262
|
-
event = events.
|
|
264
|
+
event = events.CommandOutputEvent(
|
|
263
265
|
session_id=agent.session.id,
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
),
|
|
268
|
-
ui_extra=model.build_command_output_extra(
|
|
269
|
-
self.name,
|
|
270
|
-
ui_extra=model.SessionIdUIExtra(session_id=new_session.id),
|
|
271
|
-
),
|
|
272
|
-
),
|
|
266
|
+
command_name=self.name,
|
|
267
|
+
content=f"Session forked ({fork_description}). New session id: {new_session.id}",
|
|
268
|
+
ui_extra=model.SessionIdUIExtra(session_id=new_session.id),
|
|
273
269
|
)
|
|
274
|
-
return CommandResult(events=[event]
|
|
270
|
+
return CommandResult(events=[event])
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
|
|
3
|
-
from klaude_code.protocol import commands, events, message,
|
|
3
|
+
from klaude_code.protocol import commands, events, message, op
|
|
4
4
|
|
|
5
5
|
from .command_abc import Agent, CommandABC, CommandResult
|
|
6
|
-
from .
|
|
6
|
+
from .model_picker import ModelSelectStatus, select_model_interactive
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class ModelCommand(CommandABC):
|
|
@@ -37,12 +37,10 @@ class ModelCommand(CommandABC):
|
|
|
37
37
|
if selected_model is None or selected_model == current_model:
|
|
38
38
|
return CommandResult(
|
|
39
39
|
events=[
|
|
40
|
-
events.
|
|
40
|
+
events.CommandOutputEvent(
|
|
41
41
|
session_id=agent.session.id,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
ui_extra=model.build_command_output_extra(self.name),
|
|
45
|
-
),
|
|
42
|
+
command_name=self.name,
|
|
43
|
+
content="(no change)",
|
|
46
44
|
)
|
|
47
45
|
]
|
|
48
46
|
)
|
|
@@ -7,7 +7,7 @@ from dataclasses import dataclass
|
|
|
7
7
|
from enum import Enum
|
|
8
8
|
|
|
9
9
|
from klaude_code.config.config import load_config
|
|
10
|
-
from klaude_code.config.
|
|
10
|
+
from klaude_code.config.model_matcher import match_model_from_config
|
|
11
11
|
from klaude_code.log import log
|
|
12
12
|
|
|
13
13
|
|
|
@@ -35,7 +35,7 @@ def select_model_interactive(
|
|
|
35
35
|
This function combines matching logic with interactive UI selection.
|
|
36
36
|
For CLI usage.
|
|
37
37
|
|
|
38
|
-
If keywords is provided, preferred is ignored and the model list is pre-filtered by
|
|
38
|
+
If keywords is provided, preferred is ignored and the model list is pre-filtered by model_id.
|
|
39
39
|
|
|
40
40
|
If preferred is provided:
|
|
41
41
|
- Exact match: return immediately
|
|
@@ -54,9 +54,7 @@ def select_model_interactive(
|
|
|
54
54
|
if keywords:
|
|
55
55
|
keywords_lower = [k.lower() for k in keywords]
|
|
56
56
|
filtered_models = [
|
|
57
|
-
m
|
|
58
|
-
for m in result.filtered_models
|
|
59
|
-
if any(kw in (m.model_params.model or "").lower() for kw in keywords_lower)
|
|
57
|
+
m for m in result.filtered_models if any(kw in (m.model_id or "").lower() for kw in keywords_lower)
|
|
60
58
|
]
|
|
61
59
|
if not filtered_models:
|
|
62
60
|
return ModelSelectResult(status=ModelSelectStatus.NO_MATCH)
|
|
@@ -2,7 +2,7 @@ from importlib.resources import files
|
|
|
2
2
|
from typing import TYPE_CHECKING
|
|
3
3
|
|
|
4
4
|
from klaude_code.log import log_debug
|
|
5
|
-
from klaude_code.protocol import commands, events, message,
|
|
5
|
+
from klaude_code.protocol import commands, events, message, op
|
|
6
6
|
|
|
7
7
|
from .command_abc import Agent, CommandResult
|
|
8
8
|
from .prompt_command import PromptCommand
|
|
@@ -179,30 +179,24 @@ async def dispatch_command(user_input: message.UserInputPayload, agent: Agent, *
|
|
|
179
179
|
result.operations = ops
|
|
180
180
|
return result
|
|
181
181
|
except Exception as e:
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
182
|
+
error_content = f"Command {command_identifier} error: [{e.__class__.__name__}] {e!s}"
|
|
183
|
+
if isinstance(command_identifier, commands.CommandName):
|
|
184
|
+
return CommandResult(
|
|
185
|
+
events=[
|
|
186
|
+
events.CommandOutputEvent(
|
|
187
|
+
session_id=agent.session.id,
|
|
188
|
+
command_name=command_identifier,
|
|
189
|
+
content=error_content,
|
|
190
|
+
is_error=True,
|
|
191
|
+
)
|
|
192
|
+
]
|
|
192
193
|
)
|
|
193
|
-
if command_output is not None
|
|
194
|
-
else None
|
|
195
|
-
)
|
|
196
194
|
return CommandResult(
|
|
197
195
|
events=[
|
|
198
|
-
events.
|
|
196
|
+
events.ErrorEvent(
|
|
199
197
|
session_id=agent.session.id,
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
f"Command {command_identifier} error: [{e.__class__.__name__}] {e!s}"
|
|
203
|
-
),
|
|
204
|
-
ui_extra=ui_extra,
|
|
205
|
-
),
|
|
198
|
+
error_message=error_content,
|
|
199
|
+
can_retry=False,
|
|
206
200
|
)
|
|
207
201
|
]
|
|
208
202
|
)
|
|
@@ -3,7 +3,7 @@ import asyncio
|
|
|
3
3
|
from prompt_toolkit.styles import Style
|
|
4
4
|
|
|
5
5
|
from klaude_code.log import log
|
|
6
|
-
from klaude_code.protocol import commands, events, message,
|
|
6
|
+
from klaude_code.protocol import commands, events, message, op
|
|
7
7
|
from klaude_code.session.selector import build_session_select_options, format_user_messages_display
|
|
8
8
|
from klaude_code.tui.terminal.selector import SelectItem, select_one
|
|
9
9
|
|
|
@@ -87,29 +87,23 @@ class ResumeCommand(CommandABC):
|
|
|
87
87
|
del user_input # unused
|
|
88
88
|
|
|
89
89
|
if agent.session.messages_count > 0:
|
|
90
|
-
event = events.
|
|
90
|
+
event = events.CommandOutputEvent(
|
|
91
91
|
session_id=agent.session.id,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
),
|
|
96
|
-
ui_extra=model.build_command_output_extra(self.name, is_error=True),
|
|
97
|
-
),
|
|
92
|
+
command_name=self.name,
|
|
93
|
+
content="Cannot resume: current session already has messages. Use `klaude -r` to start a new instance with session selection.",
|
|
94
|
+
is_error=True,
|
|
98
95
|
)
|
|
99
|
-
return CommandResult(events=[event]
|
|
96
|
+
return CommandResult(events=[event])
|
|
100
97
|
|
|
101
98
|
selected_session_id = await asyncio.to_thread(select_session_sync)
|
|
102
99
|
if selected_session_id is None:
|
|
103
|
-
event = events.
|
|
100
|
+
event = events.CommandOutputEvent(
|
|
104
101
|
session_id=agent.session.id,
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
ui_extra=model.build_command_output_extra(self.name),
|
|
108
|
-
),
|
|
102
|
+
command_name=self.name,
|
|
103
|
+
content="(no session selected)",
|
|
109
104
|
)
|
|
110
|
-
return CommandResult(events=[event]
|
|
105
|
+
return CommandResult(events=[event])
|
|
111
106
|
|
|
112
107
|
return CommandResult(
|
|
113
108
|
operations=[op.ResumeSessionOperation(target_session_id=selected_session_id)],
|
|
114
|
-
persist=False,
|
|
115
109
|
)
|
|
@@ -138,19 +138,15 @@ class StatusCommand(CommandABC):
|
|
|
138
138
|
session = agent.session
|
|
139
139
|
aggregated = accumulate_session_usage(session)
|
|
140
140
|
|
|
141
|
-
event = events.
|
|
141
|
+
event = events.CommandOutputEvent(
|
|
142
142
|
session_id=session.id,
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
task_count=aggregated.task_count,
|
|
150
|
-
by_model=aggregated.by_model,
|
|
151
|
-
),
|
|
152
|
-
),
|
|
143
|
+
command_name=self.name,
|
|
144
|
+
content=format_status_content(aggregated),
|
|
145
|
+
ui_extra=model.SessionStatusUIExtra(
|
|
146
|
+
usage=aggregated.total,
|
|
147
|
+
task_count=aggregated.task_count,
|
|
148
|
+
by_model=aggregated.by_model,
|
|
153
149
|
),
|
|
154
150
|
)
|
|
155
151
|
|
|
156
|
-
return CommandResult(events=[event]
|
|
152
|
+
return CommandResult(events=[event])
|