klaude-code 1.2.1__py3-none-any.whl → 1.2.3__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/main.py +9 -4
- klaude_code/cli/runtime.py +42 -43
- klaude_code/command/__init__.py +7 -5
- klaude_code/command/clear_cmd.py +6 -29
- klaude_code/command/command_abc.py +44 -8
- klaude_code/command/diff_cmd.py +33 -27
- klaude_code/command/export_cmd.py +18 -26
- klaude_code/command/help_cmd.py +10 -8
- klaude_code/command/model_cmd.py +11 -40
- klaude_code/command/{prompt-update-dev-doc.md → prompt-dev-docs-update.md} +3 -2
- klaude_code/command/{prompt-dev-doc.md → prompt-dev-docs.md} +3 -2
- klaude_code/command/prompt-init.md +2 -5
- klaude_code/command/prompt_command.py +6 -6
- klaude_code/command/refresh_cmd.py +4 -5
- klaude_code/command/registry.py +16 -19
- klaude_code/command/terminal_setup_cmd.py +12 -11
- klaude_code/config/__init__.py +4 -0
- klaude_code/config/config.py +25 -26
- klaude_code/config/list_model.py +8 -3
- klaude_code/config/select_model.py +1 -1
- klaude_code/const/__init__.py +1 -1
- klaude_code/core/__init__.py +0 -3
- klaude_code/core/agent.py +25 -50
- klaude_code/core/executor.py +268 -101
- klaude_code/core/prompt.py +12 -12
- klaude_code/core/{prompt → prompts}/prompt-gemini.md +1 -1
- klaude_code/core/reminders.py +76 -95
- klaude_code/core/task.py +21 -14
- klaude_code/core/tool/__init__.py +45 -11
- klaude_code/core/tool/file/apply_patch.py +5 -1
- klaude_code/core/tool/file/apply_patch_tool.py +11 -13
- klaude_code/core/tool/file/edit_tool.py +27 -23
- klaude_code/core/tool/file/multi_edit_tool.py +15 -17
- klaude_code/core/tool/file/read_tool.py +41 -36
- klaude_code/core/tool/file/write_tool.py +13 -15
- klaude_code/core/tool/memory/memory_tool.py +85 -68
- klaude_code/core/tool/memory/skill_tool.py +10 -12
- klaude_code/core/tool/shell/bash_tool.py +24 -22
- klaude_code/core/tool/shell/command_safety.py +12 -1
- klaude_code/core/tool/sub_agent_tool.py +11 -12
- klaude_code/core/tool/todo/todo_write_tool.py +21 -28
- klaude_code/core/tool/todo/update_plan_tool.py +14 -24
- klaude_code/core/tool/tool_abc.py +3 -4
- klaude_code/core/tool/tool_context.py +7 -7
- klaude_code/core/tool/tool_registry.py +30 -47
- klaude_code/core/tool/tool_runner.py +35 -43
- klaude_code/core/tool/truncation.py +14 -20
- klaude_code/core/tool/web/mermaid_tool.py +12 -14
- klaude_code/core/tool/web/web_fetch_tool.py +15 -17
- klaude_code/core/turn.py +19 -7
- klaude_code/llm/__init__.py +3 -4
- klaude_code/llm/anthropic/client.py +30 -46
- klaude_code/llm/anthropic/input.py +4 -11
- klaude_code/llm/client.py +29 -8
- klaude_code/llm/input_common.py +66 -36
- klaude_code/llm/openai_compatible/client.py +42 -84
- klaude_code/llm/openai_compatible/input.py +11 -16
- klaude_code/llm/openai_compatible/tool_call_accumulator.py +2 -2
- klaude_code/llm/openrouter/client.py +40 -289
- klaude_code/llm/openrouter/input.py +13 -35
- klaude_code/llm/openrouter/reasoning_handler.py +209 -0
- klaude_code/llm/registry.py +5 -75
- klaude_code/llm/responses/client.py +34 -55
- klaude_code/llm/responses/input.py +24 -26
- klaude_code/llm/usage.py +109 -0
- klaude_code/protocol/__init__.py +4 -0
- klaude_code/protocol/events.py +3 -2
- klaude_code/protocol/{llm_parameter.py → llm_param.py} +12 -32
- klaude_code/protocol/model.py +49 -4
- klaude_code/protocol/op.py +18 -16
- klaude_code/protocol/op_handler.py +28 -0
- klaude_code/{core → protocol}/sub_agent.py +7 -0
- klaude_code/session/export.py +150 -70
- klaude_code/session/session.py +28 -14
- klaude_code/session/templates/export_session.html +180 -42
- klaude_code/trace/__init__.py +2 -2
- klaude_code/trace/log.py +11 -5
- klaude_code/ui/__init__.py +91 -8
- klaude_code/ui/core/__init__.py +1 -0
- klaude_code/ui/core/display.py +103 -0
- klaude_code/ui/core/input.py +71 -0
- klaude_code/ui/modes/__init__.py +1 -0
- klaude_code/ui/modes/debug/__init__.py +1 -0
- klaude_code/ui/{base/debug_event_display.py → modes/debug/display.py} +9 -5
- klaude_code/ui/modes/exec/__init__.py +1 -0
- klaude_code/ui/{base/exec_display.py → modes/exec/display.py} +28 -2
- klaude_code/ui/{repl → modes/repl}/__init__.py +5 -6
- klaude_code/ui/modes/repl/clipboard.py +152 -0
- klaude_code/ui/modes/repl/completers.py +429 -0
- klaude_code/ui/modes/repl/display.py +60 -0
- klaude_code/ui/modes/repl/event_handler.py +375 -0
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +198 -0
- klaude_code/ui/modes/repl/key_bindings.py +170 -0
- klaude_code/ui/{repl → modes/repl}/renderer.py +109 -132
- klaude_code/ui/renderers/assistant.py +21 -0
- klaude_code/ui/renderers/common.py +0 -16
- klaude_code/ui/renderers/developer.py +18 -18
- klaude_code/ui/renderers/diffs.py +36 -14
- klaude_code/ui/renderers/errors.py +1 -1
- klaude_code/ui/renderers/metadata.py +50 -27
- klaude_code/ui/renderers/sub_agent.py +43 -9
- klaude_code/ui/renderers/thinking.py +33 -1
- klaude_code/ui/renderers/tools.py +212 -20
- klaude_code/ui/renderers/user_input.py +19 -23
- klaude_code/ui/rich/__init__.py +1 -0
- klaude_code/ui/{rich_ext → rich}/searchable_text.py +3 -1
- klaude_code/ui/{renderers → rich}/status.py +29 -18
- klaude_code/ui/{base → rich}/theme.py +8 -2
- klaude_code/ui/terminal/__init__.py +1 -0
- klaude_code/ui/{base/terminal_color.py → terminal/color.py} +4 -1
- klaude_code/ui/{base/terminal_control.py → terminal/control.py} +1 -0
- klaude_code/ui/{base/terminal_notifier.py → terminal/notifier.py} +5 -2
- klaude_code/ui/utils/__init__.py +1 -0
- klaude_code/ui/{base/utils.py → utils/common.py} +35 -3
- {klaude_code-1.2.1.dist-info → klaude_code-1.2.3.dist-info}/METADATA +1 -1
- klaude_code-1.2.3.dist-info/RECORD +161 -0
- klaude_code/core/clipboard_manifest.py +0 -124
- klaude_code/llm/openrouter/tool_call_accumulator.py +0 -80
- klaude_code/ui/base/__init__.py +0 -1
- klaude_code/ui/base/display_abc.py +0 -36
- klaude_code/ui/base/input_abc.py +0 -20
- klaude_code/ui/repl/display.py +0 -36
- klaude_code/ui/repl/event_handler.py +0 -247
- klaude_code/ui/repl/input.py +0 -773
- klaude_code/ui/rich_ext/__init__.py +0 -1
- klaude_code-1.2.1.dist-info/RECORD +0 -151
- /klaude_code/core/{prompt → prompts}/prompt-claude-code.md +0 -0
- /klaude_code/core/{prompt → prompts}/prompt-codex.md +0 -0
- /klaude_code/core/{prompt → prompts}/prompt-subagent-explore.md +0 -0
- /klaude_code/core/{prompt → prompts}/prompt-subagent-oracle.md +0 -0
- /klaude_code/core/{prompt → prompts}/prompt-subagent-webfetch.md +0 -0
- /klaude_code/core/{prompt → prompts}/prompt-subagent.md +0 -0
- /klaude_code/ui/{base → core}/stage_manager.py +0 -0
- /klaude_code/ui/{rich_ext → rich}/live.py +0 -0
- /klaude_code/ui/{rich_ext → rich}/markdown.py +0 -0
- /klaude_code/ui/{rich_ext → rich}/quote.py +0 -0
- /klaude_code/ui/{base → terminal}/progress_bar.py +0 -0
- /klaude_code/ui/{base → utils}/debouncer.py +0 -0
- {klaude_code-1.2.1.dist-info → klaude_code-1.2.3.dist-info}/WHEEL +0 -0
- {klaude_code-1.2.1.dist-info → klaude_code-1.2.3.dist-info}/entry_points.txt +0 -0
klaude_code/core/executor.py
CHANGED
|
@@ -5,30 +5,90 @@ This module implements the submission_loop equivalent for klaude,
|
|
|
5
5
|
handling operations submitted from the CLI and coordinating with agents.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
8
10
|
import asyncio
|
|
9
11
|
from dataclasses import dataclass
|
|
10
|
-
from
|
|
12
|
+
from dataclasses import field as dataclass_field
|
|
11
13
|
|
|
12
|
-
from klaude_code.command import dispatch_command
|
|
14
|
+
from klaude_code.command import InputAction, InputActionType, dispatch_command
|
|
15
|
+
from klaude_code.config import Config, load_config
|
|
13
16
|
from klaude_code.core.agent import Agent, DefaultModelProfileProvider, ModelProfileProvider
|
|
14
|
-
from klaude_code.
|
|
15
|
-
from klaude_code.
|
|
16
|
-
from klaude_code.
|
|
17
|
-
from klaude_code.protocol import events, model
|
|
18
|
-
from klaude_code.protocol.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
InterruptOperation,
|
|
22
|
-
Operation,
|
|
23
|
-
Submission,
|
|
24
|
-
UserInputOperation,
|
|
25
|
-
)
|
|
17
|
+
from klaude_code.core.tool import current_run_subtask_callback
|
|
18
|
+
from klaude_code.llm.client import LLMClientABC
|
|
19
|
+
from klaude_code.llm.registry import create_llm_client
|
|
20
|
+
from klaude_code.protocol import commands, events, model, op
|
|
21
|
+
from klaude_code.protocol.op_handler import OperationHandler
|
|
22
|
+
from klaude_code.protocol.sub_agent import SubAgentResult, get_sub_agent_profile
|
|
23
|
+
from klaude_code.protocol.tools import SubAgentType
|
|
26
24
|
from klaude_code.session.session import Session
|
|
27
25
|
from klaude_code.trace import DebugType, log_debug
|
|
28
26
|
|
|
29
27
|
|
|
28
|
+
@dataclass
|
|
29
|
+
class LLMClients:
|
|
30
|
+
"""Container for LLM clients used by main agent and sub-agents."""
|
|
31
|
+
|
|
32
|
+
main: LLMClientABC
|
|
33
|
+
sub_clients: dict[SubAgentType, LLMClientABC] = dataclass_field(default_factory=lambda: {})
|
|
34
|
+
|
|
35
|
+
def get_client(self, sub_agent_type: SubAgentType | None = None) -> LLMClientABC:
|
|
36
|
+
"""Get client for given sub-agent type, or main client if None."""
|
|
37
|
+
if sub_agent_type is None:
|
|
38
|
+
return self.main
|
|
39
|
+
return self.sub_clients.get(sub_agent_type) or self.main
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def from_config(
|
|
43
|
+
cls,
|
|
44
|
+
config: Config,
|
|
45
|
+
model_override: str | None = None,
|
|
46
|
+
enabled_sub_agents: list[SubAgentType] | None = None,
|
|
47
|
+
) -> LLMClients:
|
|
48
|
+
"""Create LLMClients from application config.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
config: Application configuration
|
|
52
|
+
model_override: Optional model name to override the main model
|
|
53
|
+
enabled_sub_agents: List of sub-agent types to initialize clients for
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
LLMClients instance
|
|
57
|
+
"""
|
|
58
|
+
# Resolve main agent LLM config
|
|
59
|
+
if model_override:
|
|
60
|
+
llm_config = config.get_model_config(model_override)
|
|
61
|
+
else:
|
|
62
|
+
llm_config = config.get_main_model_config()
|
|
63
|
+
|
|
64
|
+
log_debug(
|
|
65
|
+
"Main LLM config",
|
|
66
|
+
llm_config.model_dump_json(exclude_none=True),
|
|
67
|
+
style="yellow",
|
|
68
|
+
debug_type=DebugType.LLM_CONFIG,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
main_client = create_llm_client(llm_config)
|
|
72
|
+
sub_clients: dict[SubAgentType, LLMClientABC] = {}
|
|
73
|
+
|
|
74
|
+
# Initialize sub-agent clients
|
|
75
|
+
for sub_agent_type in enabled_sub_agents or []:
|
|
76
|
+
model_name = config.subagent_models.get(sub_agent_type)
|
|
77
|
+
if not model_name:
|
|
78
|
+
continue
|
|
79
|
+
profile = get_sub_agent_profile(sub_agent_type)
|
|
80
|
+
if not profile.enabled_for_model(main_client.model_name):
|
|
81
|
+
continue
|
|
82
|
+
sub_llm_config = config.get_model_config(model_name)
|
|
83
|
+
sub_clients[sub_agent_type] = create_llm_client(sub_llm_config)
|
|
84
|
+
|
|
85
|
+
return cls(main=main_client, sub_clients=sub_clients)
|
|
86
|
+
|
|
87
|
+
|
|
30
88
|
@dataclass
|
|
31
89
|
class ActiveTask:
|
|
90
|
+
"""Track an in-flight task and its owning session."""
|
|
91
|
+
|
|
32
92
|
task: asyncio.Task[None]
|
|
33
93
|
session_id: str
|
|
34
94
|
|
|
@@ -39,6 +99,8 @@ class ExecutorContext:
|
|
|
39
99
|
|
|
40
100
|
This context is passed to operations when they execute, allowing them
|
|
41
101
|
to access shared resources like the event queue and active sessions.
|
|
102
|
+
|
|
103
|
+
Implements the OperationHandler protocol via structural subtyping.
|
|
42
104
|
"""
|
|
43
105
|
|
|
44
106
|
def __init__(
|
|
@@ -47,8 +109,8 @@ class ExecutorContext:
|
|
|
47
109
|
llm_clients: LLMClients,
|
|
48
110
|
model_profile_provider: ModelProfileProvider | None = None,
|
|
49
111
|
):
|
|
50
|
-
self.event_queue = event_queue
|
|
51
|
-
self.llm_clients = llm_clients
|
|
112
|
+
self.event_queue: asyncio.Queue[events.Event] = event_queue
|
|
113
|
+
self.llm_clients: LLMClients = llm_clients
|
|
52
114
|
self.model_profile_provider: ModelProfileProvider = model_profile_provider or DefaultModelProfileProvider()
|
|
53
115
|
|
|
54
116
|
# Track active agents by session ID
|
|
@@ -60,56 +122,68 @@ class ExecutorContext:
|
|
|
60
122
|
"""Emit an event to the UI display system."""
|
|
61
123
|
await self.event_queue.put(event)
|
|
62
124
|
|
|
63
|
-
async def
|
|
125
|
+
async def _ensure_agent(self, session_id: str) -> Agent:
|
|
126
|
+
"""Return an existing agent for the session or create a new one."""
|
|
127
|
+
|
|
128
|
+
agent = self.active_agents.get(session_id)
|
|
129
|
+
if agent is not None:
|
|
130
|
+
return agent
|
|
131
|
+
|
|
132
|
+
session = Session.load(session_id)
|
|
133
|
+
profile = self.model_profile_provider.build_profile(self.llm_clients.main)
|
|
134
|
+
agent = Agent(
|
|
135
|
+
session=session,
|
|
136
|
+
profile=profile,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
async for evt in agent.replay_history():
|
|
140
|
+
await self.emit_event(evt)
|
|
141
|
+
|
|
142
|
+
await self.emit_event(
|
|
143
|
+
events.WelcomeEvent(
|
|
144
|
+
work_dir=str(session.work_dir),
|
|
145
|
+
llm_config=self.llm_clients.main.get_llm_config(),
|
|
146
|
+
)
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
self.active_agents[session_id] = agent
|
|
150
|
+
log_debug(
|
|
151
|
+
f"Initialized agent for session: {session_id}",
|
|
152
|
+
style="cyan",
|
|
153
|
+
debug_type=DebugType.EXECUTION,
|
|
154
|
+
)
|
|
155
|
+
return agent
|
|
156
|
+
|
|
157
|
+
async def handle_init_agent(self, operation: op.InitAgentOperation) -> None:
|
|
64
158
|
"""Initialize an agent for a session and replay history to UI."""
|
|
65
159
|
if operation.session_id is None:
|
|
66
160
|
raise ValueError("session_id cannot be None")
|
|
67
161
|
|
|
68
|
-
|
|
69
|
-
session = Session.load(operation.session_id)
|
|
70
|
-
|
|
71
|
-
# Create agent if not exists
|
|
72
|
-
if operation.session_id not in self.active_agents:
|
|
73
|
-
profile = self.model_profile_provider.build_profile(self.llm_clients.main)
|
|
74
|
-
agent = Agent(
|
|
75
|
-
session=session,
|
|
76
|
-
profile=profile,
|
|
77
|
-
model_profile_provider=self.model_profile_provider,
|
|
78
|
-
)
|
|
79
|
-
async for evt in agent.replay_history():
|
|
80
|
-
await self.emit_event(evt)
|
|
81
|
-
await self.emit_event(
|
|
82
|
-
events.WelcomeEvent(
|
|
83
|
-
work_dir=str(session.work_dir),
|
|
84
|
-
llm_config=self.llm_clients.main.get_llm_config(),
|
|
85
|
-
)
|
|
86
|
-
)
|
|
87
|
-
self.active_agents[operation.session_id] = agent
|
|
88
|
-
log_debug(
|
|
89
|
-
f"Initialized agent for session: {operation.session_id}",
|
|
90
|
-
style="cyan",
|
|
91
|
-
debug_type=DebugType.EXECUTION,
|
|
92
|
-
)
|
|
162
|
+
await self._ensure_agent(operation.session_id)
|
|
93
163
|
|
|
94
|
-
async def handle_user_input(self, operation: UserInputOperation) -> None:
|
|
164
|
+
async def handle_user_input(self, operation: op.UserInputOperation) -> None:
|
|
95
165
|
"""Handle a user input operation by running it through an agent."""
|
|
96
166
|
|
|
97
167
|
if operation.session_id is None:
|
|
98
168
|
raise ValueError("session_id cannot be None")
|
|
99
169
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
agent = self.active_agents[operation.session_id]
|
|
170
|
+
session_id = operation.session_id
|
|
171
|
+
agent = await self._ensure_agent(session_id)
|
|
172
|
+
user_input = operation.input
|
|
105
173
|
|
|
106
174
|
# emit user input event
|
|
107
|
-
await self.emit_event(
|
|
175
|
+
await self.emit_event(
|
|
176
|
+
events.UserMessageEvent(content=user_input.text, session_id=session_id, images=user_input.images)
|
|
177
|
+
)
|
|
108
178
|
|
|
109
|
-
result = await dispatch_command(
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
179
|
+
result = await dispatch_command(user_input.text, agent)
|
|
180
|
+
|
|
181
|
+
actions: list[InputAction] = list(result.actions or [])
|
|
182
|
+
|
|
183
|
+
has_run_agent_action = any(action.type is InputActionType.RUN_AGENT for action in actions)
|
|
184
|
+
if not has_run_agent_action:
|
|
185
|
+
# No async agent task will run, append user message directly
|
|
186
|
+
agent.session.append_history([model.UserMessageItem(content=user_input.text, images=user_input.images)])
|
|
113
187
|
|
|
114
188
|
if result.events:
|
|
115
189
|
agent.session.append_history(
|
|
@@ -118,15 +192,82 @@ class ExecutorContext:
|
|
|
118
192
|
for evt in result.events:
|
|
119
193
|
await self.emit_event(evt)
|
|
120
194
|
|
|
121
|
-
|
|
122
|
-
|
|
195
|
+
for action in actions:
|
|
196
|
+
await self._run_input_action(action, operation, agent)
|
|
197
|
+
|
|
198
|
+
async def _run_input_action(self, action: InputAction, operation: op.UserInputOperation, agent: Agent) -> None:
|
|
199
|
+
if operation.session_id is None:
|
|
200
|
+
raise ValueError("session_id cannot be None for input actions")
|
|
201
|
+
|
|
202
|
+
session_id = operation.session_id
|
|
203
|
+
|
|
204
|
+
if action.type == InputActionType.RUN_AGENT:
|
|
205
|
+
task_input = model.UserInputPayload(text=action.text, images=operation.input.images)
|
|
206
|
+
|
|
207
|
+
existing_active = self.active_tasks.get(operation.id)
|
|
208
|
+
if existing_active is not None and not existing_active.task.done():
|
|
209
|
+
raise RuntimeError(f"Active task already registered for operation {operation.id}")
|
|
210
|
+
|
|
123
211
|
task: asyncio.Task[None] = asyncio.create_task(
|
|
124
|
-
self._run_agent_task(agent,
|
|
212
|
+
self._run_agent_task(agent, task_input, operation.id, session_id)
|
|
125
213
|
)
|
|
126
|
-
self.active_tasks[operation.id] = ActiveTask(task=task, session_id=
|
|
127
|
-
|
|
214
|
+
self.active_tasks[operation.id] = ActiveTask(task=task, session_id=session_id)
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
if action.type == InputActionType.CHANGE_MODEL:
|
|
218
|
+
if not action.model_name:
|
|
219
|
+
raise ValueError("ChangeModel action requires model_name")
|
|
220
|
+
|
|
221
|
+
await self._apply_model_change(agent, action.model_name)
|
|
222
|
+
return
|
|
223
|
+
|
|
224
|
+
if action.type == InputActionType.CLEAR:
|
|
225
|
+
await self._apply_clear(agent)
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
raise ValueError(f"Unsupported input action type: {action.type}")
|
|
229
|
+
|
|
230
|
+
async def _apply_model_change(self, agent: Agent, model_name: str) -> None:
|
|
231
|
+
config = load_config()
|
|
232
|
+
if config is None:
|
|
233
|
+
raise ValueError("Configuration must be initialized before changing model")
|
|
234
|
+
|
|
235
|
+
llm_config = config.get_model_config(model_name)
|
|
236
|
+
llm_client = create_llm_client(llm_config)
|
|
237
|
+
agent.set_model_profile(self.model_profile_provider.build_profile(llm_client))
|
|
238
|
+
|
|
239
|
+
developer_item = model.DeveloperMessageItem(
|
|
240
|
+
content=f"switched to model: {model_name}",
|
|
241
|
+
command_output=model.CommandOutput(command_name=commands.CommandName.MODEL),
|
|
242
|
+
)
|
|
243
|
+
agent.session.append_history([developer_item])
|
|
244
|
+
|
|
245
|
+
await self.emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
|
|
246
|
+
await self.emit_event(events.WelcomeEvent(llm_config=llm_config, work_dir=str(agent.session.work_dir)))
|
|
247
|
+
|
|
248
|
+
async def _apply_clear(self, agent: Agent) -> None:
|
|
249
|
+
old_session_id = agent.session.id
|
|
250
|
+
|
|
251
|
+
# Create a new session instance to replace the current one
|
|
252
|
+
new_session = Session(work_dir=agent.session.work_dir)
|
|
253
|
+
new_session.model_name = agent.session.model_name
|
|
128
254
|
|
|
129
|
-
|
|
255
|
+
# Replace the agent's session with the new one
|
|
256
|
+
agent.session = new_session
|
|
257
|
+
agent.session.save()
|
|
258
|
+
|
|
259
|
+
# Update the active_agents mapping
|
|
260
|
+
self.active_agents.pop(old_session_id, None)
|
|
261
|
+
self.active_agents[new_session.id] = agent
|
|
262
|
+
|
|
263
|
+
developer_item = model.DeveloperMessageItem(
|
|
264
|
+
content="started new conversation",
|
|
265
|
+
command_output=model.CommandOutput(command_name=commands.CommandName.CLEAR),
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
await self.emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
|
|
269
|
+
|
|
270
|
+
async def handle_interrupt(self, operation: op.InterruptOperation) -> None:
|
|
130
271
|
"""Handle an interrupt by invoking agent.cancel() and cancelling tasks."""
|
|
131
272
|
|
|
132
273
|
# Determine affected sessions
|
|
@@ -170,7 +311,9 @@ class ExecutorContext:
|
|
|
170
311
|
# Remove from active tasks immediately
|
|
171
312
|
self.active_tasks.pop(task_id, None)
|
|
172
313
|
|
|
173
|
-
async def _run_agent_task(
|
|
314
|
+
async def _run_agent_task(
|
|
315
|
+
self, agent: Agent, user_input: model.UserInputPayload, task_id: str, session_id: str
|
|
316
|
+
) -> None:
|
|
174
317
|
"""
|
|
175
318
|
Run an agent task and forward all events to the UI.
|
|
176
319
|
|
|
@@ -225,7 +368,11 @@ class ExecutorContext:
|
|
|
225
368
|
finally:
|
|
226
369
|
# Clean up the task from active tasks
|
|
227
370
|
self.active_tasks.pop(task_id, None)
|
|
228
|
-
log_debug(
|
|
371
|
+
log_debug(
|
|
372
|
+
f"Cleaned up agent task {task_id}",
|
|
373
|
+
style="cyan",
|
|
374
|
+
debug_type=DebugType.EXECUTION,
|
|
375
|
+
)
|
|
229
376
|
|
|
230
377
|
async def _run_subagent_task(self, parent_agent: Agent, state: model.SubAgentState) -> SubAgentResult:
|
|
231
378
|
"""Run a nested sub-agent task and return the final task_result text.
|
|
@@ -246,7 +393,6 @@ class ExecutorContext:
|
|
|
246
393
|
child_agent = Agent(
|
|
247
394
|
session=child_session,
|
|
248
395
|
profile=child_profile,
|
|
249
|
-
model_profile_provider=self.model_profile_provider,
|
|
250
396
|
)
|
|
251
397
|
|
|
252
398
|
log_debug(
|
|
@@ -258,7 +404,8 @@ class ExecutorContext:
|
|
|
258
404
|
try:
|
|
259
405
|
# Not emit the subtask's user input since task tool call is already rendered
|
|
260
406
|
result: str = ""
|
|
261
|
-
|
|
407
|
+
sub_agent_input = model.UserInputPayload(text=state.sub_agent_prompt, images=None)
|
|
408
|
+
async for event in child_agent.run_task(sub_agent_input):
|
|
262
409
|
# Capture TaskFinishEvent content for return
|
|
263
410
|
if isinstance(event, events.TaskFinishEvent):
|
|
264
411
|
result = event.task_result
|
|
@@ -279,7 +426,9 @@ class ExecutorContext:
|
|
|
279
426
|
debug_type=DebugType.EXECUTION,
|
|
280
427
|
)
|
|
281
428
|
return SubAgentResult(
|
|
282
|
-
task_result=f"Subagent task failed: [{e.__class__.__name__}] {str(e)}",
|
|
429
|
+
task_result=f"Subagent task failed: [{e.__class__.__name__}] {str(e)}",
|
|
430
|
+
session_id="",
|
|
431
|
+
error=True,
|
|
283
432
|
)
|
|
284
433
|
|
|
285
434
|
|
|
@@ -298,11 +447,11 @@ class Executor:
|
|
|
298
447
|
model_profile_provider: ModelProfileProvider | None = None,
|
|
299
448
|
):
|
|
300
449
|
self.context = ExecutorContext(event_queue, llm_clients, model_profile_provider)
|
|
301
|
-
self.submission_queue: asyncio.Queue[Submission] = asyncio.Queue()
|
|
302
|
-
|
|
303
|
-
self.
|
|
450
|
+
self.submission_queue: asyncio.Queue[op.Submission] = asyncio.Queue()
|
|
451
|
+
# Track completion events for all submissions (not just those with ActiveTask)
|
|
452
|
+
self._completion_events: dict[str, asyncio.Event] = {}
|
|
304
453
|
|
|
305
|
-
async def submit(self, operation: Operation) -> str:
|
|
454
|
+
async def submit(self, operation: op.Operation) -> str:
|
|
306
455
|
"""
|
|
307
456
|
Submit an operation to the executor for processing.
|
|
308
457
|
|
|
@@ -313,12 +462,11 @@ class Executor:
|
|
|
313
462
|
Unique submission ID for tracking
|
|
314
463
|
"""
|
|
315
464
|
|
|
316
|
-
submission = Submission(id=operation.id, operation=operation)
|
|
465
|
+
submission = op.Submission(id=operation.id, operation=operation)
|
|
317
466
|
await self.submission_queue.put(submission)
|
|
318
467
|
|
|
319
468
|
# Create completion event for tracking
|
|
320
|
-
|
|
321
|
-
self.task_completion_events[operation.id] = completion_event
|
|
469
|
+
self._completion_events[operation.id] = asyncio.Event()
|
|
322
470
|
|
|
323
471
|
log_debug(
|
|
324
472
|
f"Submitted operation {operation.type} with ID {operation.id}",
|
|
@@ -328,12 +476,17 @@ class Executor:
|
|
|
328
476
|
|
|
329
477
|
return operation.id
|
|
330
478
|
|
|
331
|
-
async def
|
|
479
|
+
async def wait_for(self, submission_id: str) -> None:
|
|
332
480
|
"""Wait for a specific submission to complete."""
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
self.
|
|
481
|
+
event = self._completion_events.get(submission_id)
|
|
482
|
+
if event is not None:
|
|
483
|
+
await event.wait()
|
|
484
|
+
self._completion_events.pop(submission_id, None)
|
|
485
|
+
|
|
486
|
+
async def submit_and_wait(self, operation: op.Operation) -> None:
|
|
487
|
+
"""Submit an operation and wait for it to complete."""
|
|
488
|
+
submission_id = await self.submit(operation)
|
|
489
|
+
await self.wait_for(submission_id)
|
|
337
490
|
|
|
338
491
|
async def start(self) -> None:
|
|
339
492
|
"""
|
|
@@ -342,17 +495,15 @@ class Executor:
|
|
|
342
495
|
This method runs continuously, processing submissions from the queue
|
|
343
496
|
until the executor is stopped.
|
|
344
497
|
"""
|
|
345
|
-
self.running = True
|
|
346
|
-
|
|
347
498
|
log_debug("Executor started", style="green", debug_type=DebugType.EXECUTION)
|
|
348
499
|
|
|
349
|
-
while
|
|
500
|
+
while True:
|
|
350
501
|
try:
|
|
351
502
|
# Wait for next submission
|
|
352
503
|
submission = await self.submission_queue.get()
|
|
353
504
|
|
|
354
505
|
# Check for end operation to gracefully exit
|
|
355
|
-
if isinstance(submission.operation, EndOperation):
|
|
506
|
+
if isinstance(submission.operation, op.EndOperation):
|
|
356
507
|
log_debug(
|
|
357
508
|
"Received EndOperation, stopping executor",
|
|
358
509
|
style="yellow",
|
|
@@ -369,15 +520,17 @@ class Executor:
|
|
|
369
520
|
|
|
370
521
|
except Exception as e:
|
|
371
522
|
# Handle unexpected errors
|
|
372
|
-
log_debug(
|
|
523
|
+
log_debug(
|
|
524
|
+
f"Executor error: {str(e)}",
|
|
525
|
+
style="red",
|
|
526
|
+
debug_type=DebugType.EXECUTION,
|
|
527
|
+
)
|
|
373
528
|
await self.context.emit_event(
|
|
374
529
|
events.ErrorEvent(error_message=f"Executor error: {str(e)}", can_retry=False)
|
|
375
530
|
)
|
|
376
531
|
|
|
377
532
|
async def stop(self) -> None:
|
|
378
533
|
"""Stop the executor and clean up resources."""
|
|
379
|
-
self.running = False
|
|
380
|
-
|
|
381
534
|
# Cancel all active tasks and collect them for awaiting
|
|
382
535
|
tasks_to_await: list[asyncio.Task[None]] = []
|
|
383
536
|
for active in self.context.active_tasks.values():
|
|
@@ -395,15 +548,19 @@ class Executor:
|
|
|
395
548
|
|
|
396
549
|
# Send EndOperation to wake up the start() loop
|
|
397
550
|
try:
|
|
398
|
-
end_operation = EndOperation()
|
|
399
|
-
submission = Submission(id=end_operation.id, operation=end_operation)
|
|
551
|
+
end_operation = op.EndOperation()
|
|
552
|
+
submission = op.Submission(id=end_operation.id, operation=end_operation)
|
|
400
553
|
await self.submission_queue.put(submission)
|
|
401
554
|
except Exception as e:
|
|
402
|
-
log_debug(
|
|
555
|
+
log_debug(
|
|
556
|
+
f"Failed to send EndOperation: {str(e)}",
|
|
557
|
+
style="red",
|
|
558
|
+
debug_type=DebugType.EXECUTION,
|
|
559
|
+
)
|
|
403
560
|
|
|
404
561
|
log_debug("Executor stopped", style="yellow", debug_type=DebugType.EXECUTION)
|
|
405
562
|
|
|
406
|
-
async def _handle_submission(self, submission: Submission) -> None:
|
|
563
|
+
async def _handle_submission(self, submission: op.Submission) -> None:
|
|
407
564
|
"""
|
|
408
565
|
Handle a single submission by executing its operation.
|
|
409
566
|
|
|
@@ -418,23 +575,28 @@ class Executor:
|
|
|
418
575
|
)
|
|
419
576
|
|
|
420
577
|
# Execute to spawn the agent task in context
|
|
421
|
-
await submission.operation.execute(self.context)
|
|
578
|
+
await submission.operation.execute(handler=self.context)
|
|
422
579
|
|
|
423
580
|
async def _await_agent_and_complete() -> None:
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
581
|
+
# Wait for the agent task tied to this submission id
|
|
582
|
+
active = self.context.active_tasks.get(submission.id)
|
|
583
|
+
if active is not None:
|
|
584
|
+
try:
|
|
428
585
|
await active.task
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
# Run in background so the submission loop can continue (e.g., to handle interrupts)
|
|
586
|
+
finally:
|
|
587
|
+
event = self._completion_events.get(submission.id)
|
|
588
|
+
if event is not None:
|
|
589
|
+
event.set()
|
|
435
590
|
|
|
591
|
+
# Run in background so the submission loop can continue (e.g., to handle interrupts)
|
|
436
592
|
asyncio.create_task(_await_agent_and_complete())
|
|
437
593
|
|
|
594
|
+
# For operations without ActiveTask (e.g., InitAgentOperation), signal completion immediately
|
|
595
|
+
if submission.id not in self.context.active_tasks:
|
|
596
|
+
event = self._completion_events.get(submission.id)
|
|
597
|
+
if event is not None:
|
|
598
|
+
event.set()
|
|
599
|
+
|
|
438
600
|
except Exception as e:
|
|
439
601
|
log_debug(
|
|
440
602
|
f"Failed to handle submission {submission.id}: {str(e)}",
|
|
@@ -445,6 +607,11 @@ class Executor:
|
|
|
445
607
|
events.ErrorEvent(error_message=f"Operation failed: {str(e)}", can_retry=False)
|
|
446
608
|
)
|
|
447
609
|
# Set completion event even on error to prevent wait_for_completion from hanging
|
|
448
|
-
|
|
449
|
-
if
|
|
450
|
-
|
|
610
|
+
event = self._completion_events.get(submission.id)
|
|
611
|
+
if event is not None:
|
|
612
|
+
event.set()
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
# Static type check: ExecutorContext must satisfy OperationHandler protocol.
|
|
616
|
+
# If this line causes a type error, ExecutorContext is missing required methods.
|
|
617
|
+
_: type[OperationHandler] = ExecutorContext # pyright: ignore[reportUnusedVariable]
|
klaude_code/core/prompt.py
CHANGED
|
@@ -13,20 +13,20 @@ COMMAND_DESCRIPTIONS: dict[str, str] = {
|
|
|
13
13
|
|
|
14
14
|
# Mapping from logical prompt keys to resource file paths under the core/prompt directory.
|
|
15
15
|
PROMPT_FILES: dict[str, str] = {
|
|
16
|
-
"main_codex": "
|
|
17
|
-
"main_claude": "
|
|
18
|
-
"main_gemini": "
|
|
16
|
+
"main_codex": "prompts/prompt-codex.md",
|
|
17
|
+
"main_claude": "prompts/prompt-claude-code.md",
|
|
18
|
+
"main_gemini": "prompts/prompt-gemini.md", # https://ai.google.dev/gemini-api/docs/prompting-strategies?hl=zh-cn#agentic-si-template
|
|
19
19
|
# Sub-agent prompts keyed by their name
|
|
20
|
-
"Task": "
|
|
21
|
-
"Oracle": "
|
|
22
|
-
"Explore": "
|
|
23
|
-
"WebFetchAgent": "
|
|
20
|
+
"Task": "prompts/prompt-subagent.md",
|
|
21
|
+
"Oracle": "prompts/prompt-subagent-oracle.md",
|
|
22
|
+
"Explore": "prompts/prompt-subagent-explore.md",
|
|
23
|
+
"WebFetchAgent": "prompts/prompt-subagent-webfetch.md",
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
@lru_cache(maxsize=None)
|
|
28
|
-
def get_system_prompt(model_name: str,
|
|
29
|
-
"""Get system prompt content for the given model and
|
|
28
|
+
def get_system_prompt(model_name: str, sub_agent_type: str | None = None) -> str:
|
|
29
|
+
"""Get system prompt content for the given model and sub-agent type."""
|
|
30
30
|
|
|
31
31
|
cwd = Path.cwd()
|
|
32
32
|
today = datetime.datetime.now().strftime("%Y-%m-%d")
|
|
@@ -37,7 +37,7 @@ def get_system_prompt(model_name: str, key: str = "main") -> str:
|
|
|
37
37
|
if shutil.which(command) is not None:
|
|
38
38
|
available_tools.append(f"{command}: {desc}")
|
|
39
39
|
|
|
40
|
-
if
|
|
40
|
+
if sub_agent_type is None:
|
|
41
41
|
match model_name:
|
|
42
42
|
case name if "gpt-5" in name:
|
|
43
43
|
file_key = "main_codex"
|
|
@@ -46,12 +46,12 @@ def get_system_prompt(model_name: str, key: str = "main") -> str:
|
|
|
46
46
|
case _:
|
|
47
47
|
file_key = "main_claude"
|
|
48
48
|
else:
|
|
49
|
-
file_key =
|
|
49
|
+
file_key = sub_agent_type
|
|
50
50
|
|
|
51
51
|
try:
|
|
52
52
|
prompt_path = PROMPT_FILES[file_key]
|
|
53
53
|
except KeyError as exc:
|
|
54
|
-
raise ValueError(f"Unknown prompt key: {
|
|
54
|
+
raise ValueError(f"Unknown prompt key: {file_key}") from exc
|
|
55
55
|
|
|
56
56
|
base_prompt = (
|
|
57
57
|
files(__package__)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
You are
|
|
1
|
+
You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
|
|
2
2
|
|
|
3
3
|
Before taking any action (either tool calls *or* responses to the user), you must proactively, methodically, and independently plan and reason about:
|
|
4
4
|
|