klaude-code 2.0.1__py3-none-any.whl → 2.1.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/app/__init__.py +12 -0
- klaude_code/app/runtime.py +215 -0
- klaude_code/cli/auth_cmd.py +2 -2
- klaude_code/cli/config_cmd.py +2 -2
- klaude_code/cli/cost_cmd.py +1 -1
- klaude_code/cli/debug.py +12 -36
- klaude_code/cli/list_model.py +3 -3
- klaude_code/cli/main.py +17 -60
- klaude_code/cli/self_update.py +2 -187
- klaude_code/cli/session_cmd.py +2 -2
- klaude_code/config/config.py +1 -1
- klaude_code/config/select_model.py +1 -1
- klaude_code/const.py +10 -1
- klaude_code/core/agent.py +9 -62
- klaude_code/core/agent_profile.py +284 -0
- klaude_code/core/executor.py +343 -230
- klaude_code/core/manager/llm_clients_builder.py +1 -1
- klaude_code/core/manager/sub_agent_manager.py +16 -29
- klaude_code/core/reminders.py +107 -155
- klaude_code/core/task.py +12 -20
- klaude_code/core/tool/__init__.py +5 -19
- klaude_code/core/tool/context.py +84 -0
- klaude_code/core/tool/file/apply_patch_tool.py +18 -21
- klaude_code/core/tool/file/edit_tool.py +42 -44
- klaude_code/core/tool/file/read_tool.py +14 -9
- klaude_code/core/tool/file/write_tool.py +12 -13
- klaude_code/core/tool/report_back_tool.py +4 -1
- klaude_code/core/tool/shell/bash_tool.py +6 -11
- klaude_code/core/tool/skill/skill_tool.py +3 -1
- klaude_code/core/tool/sub_agent_tool.py +8 -7
- klaude_code/core/tool/todo/todo_write_tool.py +3 -9
- klaude_code/core/tool/todo/update_plan_tool.py +3 -5
- klaude_code/core/tool/tool_abc.py +2 -1
- klaude_code/core/tool/tool_registry.py +2 -33
- klaude_code/core/tool/tool_runner.py +13 -10
- klaude_code/core/tool/web/mermaid_tool.py +3 -1
- klaude_code/core/tool/web/web_fetch_tool.py +5 -3
- klaude_code/core/tool/web/web_search_tool.py +5 -3
- klaude_code/core/turn.py +86 -26
- klaude_code/llm/anthropic/client.py +1 -1
- klaude_code/llm/bedrock/client.py +1 -1
- klaude_code/llm/claude/client.py +1 -1
- klaude_code/llm/codex/client.py +1 -1
- klaude_code/llm/google/client.py +1 -1
- klaude_code/llm/openai_compatible/client.py +1 -1
- klaude_code/llm/openai_compatible/tool_call_accumulator.py +1 -1
- klaude_code/llm/openrouter/client.py +1 -1
- klaude_code/llm/openrouter/reasoning.py +1 -1
- klaude_code/llm/responses/client.py +1 -1
- klaude_code/protocol/events/__init__.py +57 -0
- klaude_code/protocol/events/base.py +18 -0
- klaude_code/protocol/events/chat.py +20 -0
- klaude_code/protocol/events/lifecycle.py +22 -0
- klaude_code/protocol/events/metadata.py +15 -0
- klaude_code/protocol/events/streaming.py +43 -0
- klaude_code/protocol/events/system.py +53 -0
- klaude_code/protocol/events/tools.py +23 -0
- klaude_code/protocol/message.py +3 -11
- klaude_code/protocol/model.py +78 -9
- klaude_code/protocol/op.py +5 -0
- 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/session/session.py +6 -5
- klaude_code/skill/assets/create-plan/SKILL.md +76 -0
- klaude_code/skill/loader.py +1 -1
- klaude_code/skill/system_skills.py +1 -1
- klaude_code/tui/__init__.py +8 -0
- klaude_code/{command → tui/command}/clear_cmd.py +2 -1
- klaude_code/{command → tui/command}/debug_cmd.py +4 -3
- klaude_code/{command → tui/command}/export_cmd.py +2 -1
- klaude_code/{command → tui/command}/export_online_cmd.py +6 -5
- klaude_code/{command → tui/command}/fork_session_cmd.py +10 -9
- klaude_code/{command → tui/command}/help_cmd.py +3 -2
- klaude_code/{command → tui/command}/model_cmd.py +5 -4
- klaude_code/{command → tui/command}/model_select.py +2 -2
- klaude_code/{command → tui/command}/prompt_command.py +4 -3
- klaude_code/{command → tui/command}/refresh_cmd.py +3 -1
- klaude_code/{command → tui/command}/registry.py +16 -6
- klaude_code/{command → tui/command}/release_notes_cmd.py +3 -2
- klaude_code/{command → tui/command}/resume_cmd.py +6 -5
- klaude_code/{command → tui/command}/status_cmd.py +4 -3
- klaude_code/{command → tui/command}/terminal_setup_cmd.py +4 -3
- klaude_code/{command → tui/command}/thinking_cmd.py +4 -3
- klaude_code/tui/commands.py +164 -0
- klaude_code/{ui/renderers → tui/components}/assistant.py +3 -3
- klaude_code/{ui/renderers → tui/components}/bash_syntax.py +2 -2
- klaude_code/{ui/renderers → tui/components}/common.py +1 -1
- klaude_code/tui/components/developer.py +231 -0
- klaude_code/{ui/renderers → tui/components}/diffs.py +2 -2
- klaude_code/{ui/renderers → tui/components}/errors.py +2 -2
- klaude_code/{ui/renderers → tui/components}/metadata.py +34 -21
- klaude_code/{ui → tui/components}/rich/markdown.py +78 -34
- klaude_code/{ui → tui/components}/rich/status.py +2 -2
- klaude_code/{ui → tui/components}/rich/theme.py +12 -5
- klaude_code/{ui/renderers → tui/components}/sub_agent.py +23 -43
- klaude_code/{ui/renderers → tui/components}/thinking.py +3 -3
- klaude_code/{ui/renderers → tui/components}/tools.py +11 -48
- klaude_code/{ui/renderers → tui/components}/user_input.py +3 -20
- klaude_code/tui/display.py +85 -0
- klaude_code/{ui/modes/repl → tui/input}/__init__.py +1 -1
- klaude_code/{ui/modes/repl → tui/input}/completers.py +1 -1
- klaude_code/{ui/modes/repl/input_prompt_toolkit.py → tui/input/prompt_toolkit.py} +11 -7
- klaude_code/tui/machine.py +606 -0
- klaude_code/tui/renderer.py +707 -0
- klaude_code/tui/runner.py +321 -0
- klaude_code/tui/terminal/__init__.py +56 -0
- klaude_code/{ui → tui}/terminal/color.py +1 -1
- klaude_code/{ui → tui}/terminal/control.py +1 -1
- klaude_code/{ui → tui}/terminal/notifier.py +1 -1
- klaude_code/{ui → tui}/terminal/selector.py +36 -17
- klaude_code/ui/__init__.py +6 -50
- klaude_code/ui/core/display.py +3 -3
- klaude_code/ui/core/input.py +2 -1
- klaude_code/ui/{modes/debug/display.py → debug_mode.py} +1 -1
- klaude_code/ui/{modes/exec/display.py → exec_mode.py} +1 -4
- klaude_code/ui/terminal/__init__.py +6 -54
- klaude_code/ui/terminal/title.py +31 -0
- klaude_code/update.py +163 -0
- {klaude_code-2.0.1.dist-info → klaude_code-2.1.0.dist-info}/METADATA +1 -1
- klaude_code-2.1.0.dist-info/RECORD +235 -0
- klaude_code/cli/runtime.py +0 -525
- klaude_code/core/prompt.py +0 -108
- klaude_code/core/tool/file/move_tool.md +0 -41
- klaude_code/core/tool/file/move_tool.py +0 -435
- klaude_code/core/tool/tool_context.py +0 -148
- klaude_code/protocol/events.py +0 -194
- klaude_code/skill/assets/dev-docs/SKILL.md +0 -108
- klaude_code/trace/__init__.py +0 -21
- klaude_code/ui/core/stage_manager.py +0 -48
- klaude_code/ui/modes/__init__.py +0 -1
- klaude_code/ui/modes/debug/__init__.py +0 -1
- klaude_code/ui/modes/exec/__init__.py +0 -1
- klaude_code/ui/modes/repl/display.py +0 -61
- klaude_code/ui/modes/repl/event_handler.py +0 -634
- klaude_code/ui/modes/repl/renderer.py +0 -463
- klaude_code/ui/renderers/developer.py +0 -215
- klaude_code/ui/utils/__init__.py +0 -1
- klaude_code-2.0.1.dist-info/RECORD +0 -229
- /klaude_code/{trace/log.py → log.py} +0 -0
- /klaude_code/{command → tui/command}/__init__.py +0 -0
- /klaude_code/{command → tui/command}/command_abc.py +0 -0
- /klaude_code/{command → tui/command}/prompt-commit.md +0 -0
- /klaude_code/{command → tui/command}/prompt-init.md +0 -0
- /klaude_code/{ui/renderers → tui/components}/__init__.py +0 -0
- /klaude_code/{ui/renderers → tui/components}/mermaid_viewer.py +0 -0
- /klaude_code/{ui → tui/components}/rich/__init__.py +0 -0
- /klaude_code/{ui → tui/components}/rich/cjk_wrap.py +0 -0
- /klaude_code/{ui → tui/components}/rich/code_panel.py +0 -0
- /klaude_code/{ui → tui/components}/rich/live.py +0 -0
- /klaude_code/{ui → tui/components}/rich/quote.py +0 -0
- /klaude_code/{ui → tui/components}/rich/searchable_text.py +0 -0
- /klaude_code/{ui/modes/repl → tui/input}/clipboard.py +0 -0
- /klaude_code/{ui/modes/repl → tui/input}/key_bindings.py +0 -0
- /klaude_code/{ui → tui}/terminal/image.py +0 -0
- /klaude_code/{ui → tui}/terminal/progress_bar.py +0 -0
- /klaude_code/ui/{utils/common.py → common.py} +0 -0
- {klaude_code-2.0.1.dist-info → klaude_code-2.1.0.dist-info}/WHEEL +0 -0
- {klaude_code-2.0.1.dist-info → klaude_code-2.1.0.dist-info}/entry_points.txt +0 -0
|
@@ -6,9 +6,9 @@ from klaude_code.config import Config
|
|
|
6
6
|
from klaude_code.core.manager.llm_clients import LLMClients
|
|
7
7
|
from klaude_code.llm.client import LLMClientABC
|
|
8
8
|
from klaude_code.llm.registry import create_llm_client
|
|
9
|
+
from klaude_code.log import DebugType, log_debug
|
|
9
10
|
from klaude_code.protocol.sub_agent import iter_sub_agent_profiles
|
|
10
11
|
from klaude_code.protocol.tools import SubAgentType
|
|
11
|
-
from klaude_code.trace import DebugType, log_debug
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
def build_llm_clients(
|
|
@@ -9,15 +9,15 @@ from __future__ import annotations
|
|
|
9
9
|
|
|
10
10
|
import asyncio
|
|
11
11
|
import json
|
|
12
|
+
from collections.abc import Callable
|
|
12
13
|
|
|
13
|
-
from klaude_code.core.agent import Agent
|
|
14
|
+
from klaude_code.core.agent import Agent
|
|
15
|
+
from klaude_code.core.agent_profile import ModelProfileProvider
|
|
14
16
|
from klaude_code.core.manager.llm_clients import LLMClients
|
|
15
|
-
from klaude_code.
|
|
16
|
-
from klaude_code.core.tool.tool_context import record_sub_agent_session_id
|
|
17
|
+
from klaude_code.log import DebugType, log_debug
|
|
17
18
|
from klaude_code.protocol import events, message, model
|
|
18
19
|
from klaude_code.protocol.sub_agent import SubAgentResult
|
|
19
20
|
from klaude_code.session.session import Session
|
|
20
|
-
from klaude_code.trace import DebugType, log_debug
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
class SubAgentManager:
|
|
@@ -38,7 +38,13 @@ class SubAgentManager:
|
|
|
38
38
|
|
|
39
39
|
await self._event_queue.put(event)
|
|
40
40
|
|
|
41
|
-
async def run_sub_agent(
|
|
41
|
+
async def run_sub_agent(
|
|
42
|
+
self,
|
|
43
|
+
parent_agent: Agent,
|
|
44
|
+
state: model.SubAgentState,
|
|
45
|
+
*,
|
|
46
|
+
record_session_id: Callable[[str], None] | None = None,
|
|
47
|
+
) -> SubAgentResult:
|
|
42
48
|
"""Run a nested sub-agent task and return its result."""
|
|
43
49
|
|
|
44
50
|
parent_session = parent_agent.session
|
|
@@ -77,9 +83,8 @@ class SubAgentManager:
|
|
|
77
83
|
error=True,
|
|
78
84
|
)
|
|
79
85
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
record_sub_agent_session_id(child_session.id)
|
|
86
|
+
if record_session_id is not None:
|
|
87
|
+
record_session_id(child_session.id)
|
|
83
88
|
|
|
84
89
|
# Update persisted sub-agent state to reflect the current invocation.
|
|
85
90
|
child_session.sub_agent_state.sub_agent_desc = state.sub_agent_desc
|
|
@@ -92,33 +97,15 @@ class SubAgentManager:
|
|
|
92
97
|
child_session = Session(work_dir=parent_session.work_dir)
|
|
93
98
|
child_session.sub_agent_state = state
|
|
94
99
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
record_sub_agent_session_id(child_session.id)
|
|
100
|
+
if record_session_id is not None:
|
|
101
|
+
record_session_id(child_session.id)
|
|
98
102
|
|
|
99
103
|
child_profile = self._model_profile_provider.build_profile(
|
|
100
104
|
self._llm_clients.get_client(state.sub_agent_type),
|
|
101
105
|
state.sub_agent_type,
|
|
106
|
+
output_schema=state.output_schema,
|
|
102
107
|
)
|
|
103
108
|
|
|
104
|
-
# Inject report_back tool if output_schema is provided
|
|
105
|
-
if state.output_schema:
|
|
106
|
-
report_back_tool_class = ReportBackTool.for_schema(state.output_schema)
|
|
107
|
-
report_back_prompt = """\
|
|
108
|
-
|
|
109
|
-
# Structured Output
|
|
110
|
-
You have a `report_back` tool available. When you complete the task,\
|
|
111
|
-
you MUST call `report_back` with the structured result matching the required schema.\
|
|
112
|
-
Only the content passed to `report_back` will be returned to user.\
|
|
113
|
-
"""
|
|
114
|
-
base_prompt = child_profile.system_prompt or ""
|
|
115
|
-
child_profile = AgentProfile(
|
|
116
|
-
llm_client=child_profile.llm_client,
|
|
117
|
-
system_prompt=base_prompt + report_back_prompt,
|
|
118
|
-
tools=[*child_profile.tools, report_back_tool_class.schema()],
|
|
119
|
-
reminders=child_profile.reminders,
|
|
120
|
-
)
|
|
121
|
-
|
|
122
109
|
child_agent = Agent(session=child_session, profile=child_profile)
|
|
123
110
|
|
|
124
111
|
log_debug(
|
klaude_code/core/reminders.py
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
import hashlib
|
|
2
2
|
import re
|
|
3
3
|
import shlex
|
|
4
|
-
from collections.abc import Awaitable, Callable
|
|
5
4
|
from dataclasses import dataclass
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
|
|
8
7
|
from pydantic import BaseModel
|
|
9
8
|
|
|
10
9
|
from klaude_code.const import MEMORY_FILE_NAMES, REMINDER_COOLDOWN_TURNS, TODO_REMINDER_TOOL_CALL_THRESHOLD
|
|
11
|
-
from klaude_code.core.tool import BashTool, ReadTool,
|
|
10
|
+
from klaude_code.core.tool import BashTool, ReadTool, build_todo_context
|
|
11
|
+
from klaude_code.core.tool.context import ToolContext
|
|
12
12
|
from klaude_code.core.tool.file._utils import hash_text_sha256
|
|
13
13
|
from klaude_code.protocol import message, model, tools
|
|
14
14
|
from klaude_code.session import Session
|
|
15
15
|
from klaude_code.skill import get_skill
|
|
16
16
|
|
|
17
|
-
type Reminder = Callable[[Session], Awaitable[message.DeveloperMessage | None]]
|
|
18
|
-
|
|
19
|
-
|
|
20
17
|
# Match @ preceded by whitespace, start of line, or → (ReadTool line number arrow)
|
|
21
18
|
AT_FILE_PATTERN = re.compile(r'(?:(?<!\S)|(?<=\u2192))@("(?P<quoted>[^\"]+)"|(?P<plain>\S+))')
|
|
22
19
|
|
|
@@ -71,10 +68,13 @@ def get_at_patterns_with_source(session: Session) -> list[AtPatternSource]:
|
|
|
71
68
|
patterns.append(AtPatternSource(pattern=path_str, mentioned_in=None))
|
|
72
69
|
break
|
|
73
70
|
|
|
74
|
-
if isinstance(item, message.DeveloperMessage) and item.
|
|
75
|
-
for
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
if isinstance(item, message.DeveloperMessage) and item.ui_extra:
|
|
72
|
+
for ui_item in item.ui_extra.items:
|
|
73
|
+
if not isinstance(ui_item, model.MemoryLoadedUIItem):
|
|
74
|
+
continue
|
|
75
|
+
for mem in ui_item.files:
|
|
76
|
+
for pattern in mem.mentioned_patterns:
|
|
77
|
+
patterns.append(AtPatternSource(pattern=pattern, mentioned_in=mem.path))
|
|
78
78
|
return patterns
|
|
79
79
|
|
|
80
80
|
|
|
@@ -113,7 +113,8 @@ def _is_tracked_file_unchanged(session: Session, path: str) -> bool:
|
|
|
113
113
|
async def _load_at_file_recursive(
|
|
114
114
|
session: Session,
|
|
115
115
|
pattern: str,
|
|
116
|
-
|
|
116
|
+
at_ops: list[model.AtFileOp],
|
|
117
|
+
formatted_blocks: list[str],
|
|
117
118
|
collected_images: list[message.ImageURLPart],
|
|
118
119
|
visited: set[str],
|
|
119
120
|
base_dir: Path | None = None,
|
|
@@ -127,53 +128,59 @@ async def _load_at_file_recursive(
|
|
|
127
128
|
return
|
|
128
129
|
visited.add(path_str)
|
|
129
130
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
131
|
+
tool_context = ToolContext(
|
|
132
|
+
file_tracker=session.file_tracker,
|
|
133
|
+
todo_context=build_todo_context(session),
|
|
134
|
+
session_id=session.id,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
if path.exists() and path.is_file():
|
|
138
|
+
if _is_tracked_file_unchanged(session, path_str):
|
|
139
|
+
return
|
|
140
|
+
args = ReadTool.ReadArguments(file_path=path_str)
|
|
141
|
+
tool_result = await ReadTool.call_with_args(args, tool_context)
|
|
142
|
+
images = [part for part in tool_result.parts if isinstance(part, message.ImageURLPart)]
|
|
143
|
+
|
|
144
|
+
tool_args = args.model_dump_json(exclude_none=True)
|
|
145
|
+
formatted_blocks.append(
|
|
146
|
+
f"""Called the {tools.READ} tool with the following input: {tool_args}
|
|
147
|
+
Result of calling the {tools.READ} tool:
|
|
148
|
+
{tool_result.output_text}
|
|
149
|
+
"""
|
|
150
|
+
)
|
|
151
|
+
at_ops.append(model.AtFileOp(operation="Read", path=path_str, mentioned_in=mentioned_in))
|
|
152
|
+
if images:
|
|
153
|
+
collected_images.extend(images)
|
|
154
|
+
|
|
155
|
+
# Recursively parse @ references from ReadTool output
|
|
156
|
+
output = tool_result.output_text
|
|
157
|
+
if "@" in output:
|
|
158
|
+
for match in AT_FILE_PATTERN.finditer(output):
|
|
159
|
+
nested = match.group("quoted") or match.group("plain")
|
|
160
|
+
if nested:
|
|
161
|
+
await _load_at_file_recursive(
|
|
162
|
+
session,
|
|
163
|
+
nested,
|
|
164
|
+
at_ops,
|
|
165
|
+
formatted_blocks,
|
|
166
|
+
collected_images,
|
|
167
|
+
visited,
|
|
168
|
+
base_dir=path.parent,
|
|
169
|
+
mentioned_in=path_str,
|
|
170
|
+
)
|
|
171
|
+
elif path.exists() and path.is_dir():
|
|
172
|
+
quoted_path = shlex.quote(path_str)
|
|
173
|
+
args = BashTool.BashArguments(command=f"ls {quoted_path}")
|
|
174
|
+
tool_result = await BashTool.call_with_args(args, tool_context)
|
|
175
|
+
|
|
176
|
+
tool_args = args.model_dump_json(exclude_none=True)
|
|
177
|
+
formatted_blocks.append(
|
|
178
|
+
f"""Called the {tools.BASH} tool with the following input: {tool_args}
|
|
179
|
+
Result of calling the {tools.BASH} tool:
|
|
180
|
+
{tool_result.output_text}
|
|
181
|
+
"""
|
|
182
|
+
)
|
|
183
|
+
at_ops.append(model.AtFileOp(operation="List", path=path_str + "/", mentioned_in=mentioned_in))
|
|
177
184
|
|
|
178
185
|
|
|
179
186
|
async def at_file_reader_reminder(
|
|
@@ -184,7 +191,8 @@ async def at_file_reader_reminder(
|
|
|
184
191
|
if not at_pattern_sources:
|
|
185
192
|
return None
|
|
186
193
|
|
|
187
|
-
|
|
194
|
+
at_ops: list[model.AtFileOp] = []
|
|
195
|
+
formatted_blocks: list[str] = []
|
|
188
196
|
collected_images: list[message.ImageURLPart] = []
|
|
189
197
|
visited: set[str] = set()
|
|
190
198
|
|
|
@@ -192,30 +200,23 @@ async def at_file_reader_reminder(
|
|
|
192
200
|
await _load_at_file_recursive(
|
|
193
201
|
session,
|
|
194
202
|
source.pattern,
|
|
195
|
-
|
|
203
|
+
at_ops,
|
|
204
|
+
formatted_blocks,
|
|
196
205
|
collected_images,
|
|
197
206
|
visited,
|
|
198
207
|
mentioned_in=source.mentioned_in,
|
|
199
208
|
)
|
|
200
209
|
|
|
201
|
-
if len(
|
|
210
|
+
if len(formatted_blocks) == 0:
|
|
202
211
|
return None
|
|
203
212
|
|
|
204
|
-
at_files_str = "\n\n".join(
|
|
205
|
-
[
|
|
206
|
-
f"""Called the {result.tool_name} tool with the following input: {result.tool_args}
|
|
207
|
-
Result of calling the {result.tool_name} tool:
|
|
208
|
-
{result.result}
|
|
209
|
-
"""
|
|
210
|
-
for result in at_files.values()
|
|
211
|
-
]
|
|
212
|
-
)
|
|
213
|
+
at_files_str = "\n\n".join(formatted_blocks)
|
|
213
214
|
return message.DeveloperMessage(
|
|
214
215
|
parts=message.parts_from_text_and_images(
|
|
215
216
|
f"""<system-reminder>{at_files_str}\n</system-reminder>""",
|
|
216
217
|
collected_images or None,
|
|
217
218
|
),
|
|
218
|
-
|
|
219
|
+
ui_extra=model.DeveloperUIExtra(items=[model.AtFileOpsUIItem(ops=at_ops)]),
|
|
219
220
|
)
|
|
220
221
|
|
|
221
222
|
|
|
@@ -299,7 +300,7 @@ Here are the existing contents of your todo list:
|
|
|
299
300
|
|
|
300
301
|
{model.todo_list_str(session.todos)}</system-reminder>"""
|
|
301
302
|
),
|
|
302
|
-
|
|
303
|
+
ui_extra=model.DeveloperUIExtra(items=[model.TodoReminderUIItem(reason="not_used_recently")]),
|
|
303
304
|
)
|
|
304
305
|
|
|
305
306
|
if session.need_todo_not_used_cooldown_counter > 0:
|
|
@@ -327,18 +328,20 @@ async def file_changed_externally_reminder(
|
|
|
327
328
|
changed = current_mtime != status.mtime
|
|
328
329
|
|
|
329
330
|
if changed:
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
331
|
+
tool_context = ToolContext(
|
|
332
|
+
file_tracker=session.file_tracker,
|
|
333
|
+
todo_context=build_todo_context(session),
|
|
334
|
+
session_id=session.id,
|
|
335
|
+
)
|
|
336
|
+
tool_result = await ReadTool.call_with_args(
|
|
337
|
+
ReadTool.ReadArguments(file_path=path),
|
|
338
|
+
tool_context,
|
|
339
|
+
) # This tool will update file tracker
|
|
340
|
+
if tool_result.status == "success":
|
|
341
|
+
images = [part for part in tool_result.parts if isinstance(part, message.ImageURLPart)]
|
|
342
|
+
changed_files.append((path, tool_result.output_text, images or None))
|
|
343
|
+
if images:
|
|
344
|
+
collected_images.extend(images)
|
|
342
345
|
except (
|
|
343
346
|
FileNotFoundError,
|
|
344
347
|
IsADirectoryError,
|
|
@@ -357,10 +360,12 @@ async def file_changed_externally_reminder(
|
|
|
357
360
|
)
|
|
358
361
|
return message.DeveloperMessage(
|
|
359
362
|
parts=message.parts_from_text_and_images(
|
|
360
|
-
f"""<system-reminder>{changed_files_str}""",
|
|
363
|
+
f"""<system-reminder>{changed_files_str}</system-reminder>""",
|
|
361
364
|
collected_images or None,
|
|
362
365
|
),
|
|
363
|
-
|
|
366
|
+
ui_extra=model.DeveloperUIExtra(
|
|
367
|
+
items=[model.ExternalFileChangesUIItem(paths=[file_path for file_path, _, _ in changed_files])]
|
|
368
|
+
),
|
|
364
369
|
)
|
|
365
370
|
|
|
366
371
|
return None
|
|
@@ -423,10 +428,8 @@ async def image_reminder(session: Session) -> message.DeveloperMessage | None:
|
|
|
423
428
|
return None
|
|
424
429
|
|
|
425
430
|
return message.DeveloperMessage(
|
|
426
|
-
parts=
|
|
427
|
-
|
|
428
|
-
),
|
|
429
|
-
user_image_count=image_count,
|
|
431
|
+
parts=[],
|
|
432
|
+
ui_extra=model.DeveloperUIExtra(items=[model.UserImagesUIItem(count=image_count)]),
|
|
430
433
|
)
|
|
431
434
|
|
|
432
435
|
|
|
@@ -456,7 +459,7 @@ async def skill_reminder(session: Session) -> message.DeveloperMessage | None:
|
|
|
456
459
|
|
|
457
460
|
return message.DeveloperMessage(
|
|
458
461
|
parts=message.text_parts_from_str(content),
|
|
459
|
-
|
|
462
|
+
ui_extra=model.DeveloperUIExtra(items=[model.SkillActivatedUIItem(name=skill.name)]),
|
|
460
463
|
)
|
|
461
464
|
|
|
462
465
|
|
|
@@ -487,7 +490,7 @@ async def memory_reminder(session: Session) -> message.DeveloperMessage | None:
|
|
|
487
490
|
path_str = str(memory_path)
|
|
488
491
|
if memory_path.exists() and memory_path.is_file() and not _is_memory_loaded(session, path_str):
|
|
489
492
|
try:
|
|
490
|
-
text = memory_path.read_text()
|
|
493
|
+
text = memory_path.read_text(encoding="utf-8", errors="replace")
|
|
491
494
|
_mark_memory_loaded(session, path_str)
|
|
492
495
|
memories.append(Memory(path=path_str, instruction=instruction, content=text))
|
|
493
496
|
except (PermissionError, UnicodeDecodeError, OSError):
|
|
@@ -496,32 +499,19 @@ async def memory_reminder(session: Session) -> message.DeveloperMessage | None:
|
|
|
496
499
|
memories_str = "\n\n".join(
|
|
497
500
|
[f"Contents of {memory.path} ({memory.instruction}):\n\n{memory.content}" for memory in memories]
|
|
498
501
|
)
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
if patterns:
|
|
504
|
-
memory_mentioned[memory.path] = patterns
|
|
505
|
-
|
|
502
|
+
loaded_files = [
|
|
503
|
+
model.MemoryFileLoaded(path=memory.path, mentioned_patterns=_extract_at_patterns(memory.content))
|
|
504
|
+
for memory in memories
|
|
505
|
+
]
|
|
506
506
|
return message.DeveloperMessage(
|
|
507
507
|
parts=message.text_parts_from_str(
|
|
508
|
-
f"""<system-reminder>
|
|
508
|
+
f"""<system-reminder>
|
|
509
|
+
Loaded memory files. Follow these instructions. Do not mention them to the user unless explicitly asked.
|
|
509
510
|
|
|
510
|
-
# claudeMd
|
|
511
|
-
Codebase and user instructions are shown below. Be sure to adhere to these instructions. IMPORTANT: These instructions OVERRIDE any default behavior and you MUST follow them exactly as written.
|
|
512
511
|
{memories_str}
|
|
513
|
-
|
|
514
|
-
#important-instruction-reminders
|
|
515
|
-
Do what has been asked; nothing more, nothing less.
|
|
516
|
-
NEVER create files unless they're absolutely necessary for achieving your goal.
|
|
517
|
-
ALWAYS prefer editing an existing file to creating a new one.
|
|
518
|
-
NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.
|
|
519
|
-
|
|
520
|
-
IMPORTANT: this context may or may not be relevant to your tasks. You should not respond to this context unless it is highly relevant to your task.
|
|
521
512
|
</system-reminder>"""
|
|
522
513
|
),
|
|
523
|
-
|
|
524
|
-
memory_mentioned=memory_mentioned or None,
|
|
514
|
+
ui_extra=model.DeveloperUIExtra(items=[model.MemoryLoadedUIItem(files=loaded_files)]),
|
|
525
515
|
)
|
|
526
516
|
return None
|
|
527
517
|
|
|
@@ -572,7 +562,7 @@ async def last_path_memory_reminder(
|
|
|
572
562
|
continue
|
|
573
563
|
if mem_path.exists() and mem_path.is_file():
|
|
574
564
|
try:
|
|
575
|
-
text = mem_path.read_text()
|
|
565
|
+
text = mem_path.read_text(encoding="utf-8", errors="replace")
|
|
576
566
|
except (PermissionError, UnicodeDecodeError, OSError):
|
|
577
567
|
continue
|
|
578
568
|
_mark_memory_loaded(session, mem_path_str)
|
|
@@ -589,20 +579,16 @@ async def last_path_memory_reminder(
|
|
|
589
579
|
memories_str = "\n\n".join(
|
|
590
580
|
[f"Contents of {memory.path} ({memory.instruction}):\n\n{memory.content}" for memory in memories]
|
|
591
581
|
)
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
if patterns:
|
|
597
|
-
memory_mentioned[memory.path] = patterns
|
|
598
|
-
|
|
582
|
+
loaded_files = [
|
|
583
|
+
model.MemoryFileLoaded(path=memory.path, mentioned_patterns=_extract_at_patterns(memory.content))
|
|
584
|
+
for memory in memories
|
|
585
|
+
]
|
|
599
586
|
return message.DeveloperMessage(
|
|
600
587
|
parts=message.text_parts_from_str(
|
|
601
588
|
f"""<system-reminder>{memories_str}
|
|
602
589
|
</system-reminder>"""
|
|
603
590
|
),
|
|
604
|
-
|
|
605
|
-
memory_mentioned=memory_mentioned or None,
|
|
591
|
+
ui_extra=model.DeveloperUIExtra(items=[model.MemoryLoadedUIItem(files=loaded_files)]),
|
|
606
592
|
)
|
|
607
593
|
|
|
608
594
|
|
|
@@ -616,37 +602,3 @@ ALL_REMINDERS = [
|
|
|
616
602
|
image_reminder,
|
|
617
603
|
skill_reminder,
|
|
618
604
|
]
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
def load_agent_reminders(
|
|
622
|
-
model_name: str, sub_agent_type: str | None = None, *, vanilla: bool = False
|
|
623
|
-
) -> list[Reminder]:
|
|
624
|
-
"""Get reminders for an agent based on model and agent type.
|
|
625
|
-
|
|
626
|
-
Args:
|
|
627
|
-
model_name: The model name.
|
|
628
|
-
sub_agent_type: If None, returns main agent reminders. Otherwise returns sub-agent reminders.
|
|
629
|
-
vanilla: If True, returns minimal vanilla reminders (ignores sub_agent_type).
|
|
630
|
-
"""
|
|
631
|
-
if vanilla:
|
|
632
|
-
return [at_file_reader_reminder]
|
|
633
|
-
|
|
634
|
-
reminders: list[Reminder] = []
|
|
635
|
-
|
|
636
|
-
# Only main agent (not sub-agent) gets todo reminders, and not for GPT-5
|
|
637
|
-
if sub_agent_type is None and "gpt-5" not in model_name:
|
|
638
|
-
reminders.append(empty_todo_reminder)
|
|
639
|
-
reminders.append(todo_not_used_recently_reminder)
|
|
640
|
-
|
|
641
|
-
reminders.extend(
|
|
642
|
-
[
|
|
643
|
-
memory_reminder,
|
|
644
|
-
at_file_reader_reminder,
|
|
645
|
-
last_path_memory_reminder,
|
|
646
|
-
file_changed_externally_reminder,
|
|
647
|
-
image_reminder,
|
|
648
|
-
skill_reminder,
|
|
649
|
-
]
|
|
650
|
-
)
|
|
651
|
-
|
|
652
|
-
return reminders
|
klaude_code/core/task.py
CHANGED
|
@@ -4,17 +4,14 @@ import asyncio
|
|
|
4
4
|
import time
|
|
5
5
|
from collections.abc import AsyncGenerator, Callable, Sequence
|
|
6
6
|
from dataclasses import dataclass
|
|
7
|
-
from typing import TYPE_CHECKING
|
|
8
7
|
|
|
9
8
|
from klaude_code.const import INITIAL_RETRY_DELAY_S, MAX_FAILED_TURN_RETRIES, MAX_RETRY_DELAY_S
|
|
10
|
-
from klaude_code.core.
|
|
9
|
+
from klaude_code.core.agent_profile import AgentProfile, Reminder
|
|
11
10
|
from klaude_code.core.tool import FileTracker, TodoContext, ToolABC
|
|
11
|
+
from klaude_code.core.tool.context import RunSubtask
|
|
12
12
|
from klaude_code.core.turn import TurnError, TurnExecutionContext, TurnExecutor
|
|
13
|
+
from klaude_code.log import DebugType, log_debug
|
|
13
14
|
from klaude_code.protocol import events, message, model
|
|
14
|
-
from klaude_code.trace import DebugType, log_debug
|
|
15
|
-
|
|
16
|
-
if TYPE_CHECKING:
|
|
17
|
-
from klaude_code.core.agent import AgentProfile
|
|
18
15
|
|
|
19
16
|
|
|
20
17
|
class MetadataAccumulator:
|
|
@@ -101,6 +98,7 @@ class SessionContext:
|
|
|
101
98
|
append_history: Callable[[Sequence[message.HistoryEvent]], None]
|
|
102
99
|
file_tracker: FileTracker
|
|
103
100
|
todo_context: TodoContext
|
|
101
|
+
run_subtask: RunSubtask | None
|
|
104
102
|
|
|
105
103
|
|
|
106
104
|
@dataclass
|
|
@@ -189,26 +187,20 @@ class TaskExecutor:
|
|
|
189
187
|
self._current_turn = turn
|
|
190
188
|
|
|
191
189
|
try:
|
|
192
|
-
async for
|
|
193
|
-
match
|
|
194
|
-
case events.
|
|
190
|
+
async for e in turn.run():
|
|
191
|
+
match e:
|
|
192
|
+
case events.ResponseCompleteEvent() as am:
|
|
195
193
|
yield am
|
|
196
|
-
case events.
|
|
197
|
-
metadata_accumulator.add(e.
|
|
198
|
-
|
|
199
|
-
context_percent = e.metadata.context_usage_percent
|
|
200
|
-
if context_percent is not None:
|
|
201
|
-
yield events.ContextUsageEvent(
|
|
202
|
-
session_id=session_ctx.session_id,
|
|
203
|
-
context_percent=context_percent,
|
|
204
|
-
)
|
|
194
|
+
case events.UsageEvent() as e:
|
|
195
|
+
metadata_accumulator.add(e.usage)
|
|
196
|
+
yield e
|
|
205
197
|
case events.ToolResultEvent() as e:
|
|
206
198
|
# Collect sub-agent task metadata from tool results
|
|
207
199
|
if e.task_metadata is not None:
|
|
208
200
|
metadata_accumulator.add_sub_agent_metadata(e.task_metadata)
|
|
209
|
-
yield
|
|
201
|
+
yield e
|
|
210
202
|
case _:
|
|
211
|
-
yield
|
|
203
|
+
yield e
|
|
212
204
|
|
|
213
205
|
turn_succeeded = True
|
|
214
206
|
break
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
from .context import FileTracker, RunSubtask, SubAgentResumeClaims, TodoContext, ToolContext, build_todo_context
|
|
1
2
|
from .file.apply_patch import DiffError, process_patch
|
|
2
3
|
from .file.apply_patch_tool import ApplyPatchTool
|
|
3
4
|
from .file.edit_tool import EditTool
|
|
4
|
-
from .file.move_tool import MoveTool
|
|
5
5
|
from .file.read_tool import ReadTool
|
|
6
6
|
from .file.write_tool import WriteTool
|
|
7
7
|
from .report_back_tool import ReportBackTool
|
|
@@ -12,17 +12,7 @@ from .sub_agent_tool import SubAgentTool
|
|
|
12
12
|
from .todo.todo_write_tool import TodoWriteTool
|
|
13
13
|
from .todo.update_plan_tool import UpdatePlanTool
|
|
14
14
|
from .tool_abc import ToolABC
|
|
15
|
-
from .
|
|
16
|
-
FileTracker,
|
|
17
|
-
TodoContext,
|
|
18
|
-
ToolContextToken,
|
|
19
|
-
build_todo_context,
|
|
20
|
-
current_run_subtask_callback,
|
|
21
|
-
reset_tool_context,
|
|
22
|
-
set_tool_context_from_session,
|
|
23
|
-
tool_context,
|
|
24
|
-
)
|
|
25
|
-
from .tool_registry import get_registry, get_tool_schemas, load_agent_tools
|
|
15
|
+
from .tool_registry import get_registry, get_tool_schemas
|
|
26
16
|
from .tool_runner import run_tool
|
|
27
17
|
from .truncation import SimpleTruncationStrategy, TruncationStrategy, get_truncation_strategy, set_truncation_strategy
|
|
28
18
|
from .web.mermaid_tool import MermaidTool
|
|
@@ -36,33 +26,29 @@ __all__ = [
|
|
|
36
26
|
"EditTool",
|
|
37
27
|
"FileTracker",
|
|
38
28
|
"MermaidTool",
|
|
39
|
-
"MoveTool",
|
|
40
29
|
"ReadTool",
|
|
41
30
|
"ReportBackTool",
|
|
31
|
+
"RunSubtask",
|
|
42
32
|
"SafetyCheckResult",
|
|
43
33
|
"SimpleTruncationStrategy",
|
|
44
34
|
"SkillTool",
|
|
35
|
+
"SubAgentResumeClaims",
|
|
45
36
|
"SubAgentTool",
|
|
46
37
|
"TodoContext",
|
|
47
38
|
"TodoWriteTool",
|
|
48
39
|
"ToolABC",
|
|
49
|
-
"
|
|
40
|
+
"ToolContext",
|
|
50
41
|
"TruncationStrategy",
|
|
51
42
|
"UpdatePlanTool",
|
|
52
43
|
"WebFetchTool",
|
|
53
44
|
"WebSearchTool",
|
|
54
45
|
"WriteTool",
|
|
55
46
|
"build_todo_context",
|
|
56
|
-
"current_run_subtask_callback",
|
|
57
47
|
"get_registry",
|
|
58
48
|
"get_tool_schemas",
|
|
59
49
|
"get_truncation_strategy",
|
|
60
50
|
"is_safe_command",
|
|
61
|
-
"load_agent_tools",
|
|
62
51
|
"process_patch",
|
|
63
|
-
"reset_tool_context",
|
|
64
52
|
"run_tool",
|
|
65
|
-
"set_tool_context_from_session",
|
|
66
53
|
"set_truncation_strategy",
|
|
67
|
-
"tool_context",
|
|
68
54
|
]
|