klaude-code 2.0.2__py3-none-any.whl → 2.1.1__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 +9 -1
- klaude_code/core/agent.py +9 -62
- klaude_code/core/agent_profile.py +291 -0
- klaude_code/core/executor.py +335 -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 +84 -103
- 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 +39 -42
- 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/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 +87 -30
- 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/commands.py +1 -0
- 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 +27 -0
- klaude_code/protocol/op.py +5 -0
- klaude_code/protocol/tools.py +0 -1
- klaude_code/session/session.py +6 -7
- klaude_code/skill/assets/create-plan/SKILL.md +76 -0
- klaude_code/skill/loader.py +32 -88
- klaude_code/skill/manager.py +38 -0
- klaude_code/skill/system_skills.py +1 -1
- klaude_code/tui/__init__.py +8 -0
- klaude_code/{command → tui/command}/__init__.py +3 -0
- klaude_code/{command → tui/command}/clear_cmd.py +2 -1
- klaude_code/tui/command/copy_cmd.py +53 -0
- klaude_code/{command → tui/command}/debug_cmd.py +3 -2
- klaude_code/{command → tui/command}/export_cmd.py +2 -1
- klaude_code/{command → tui/command}/export_online_cmd.py +2 -1
- klaude_code/{command → tui/command}/fork_session_cmd.py +4 -3
- klaude_code/{command → tui/command}/help_cmd.py +2 -1
- klaude_code/{command → tui/command}/model_cmd.py +4 -3
- 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 +6 -5
- klaude_code/{command → tui/command}/release_notes_cmd.py +2 -1
- klaude_code/{command → tui/command}/resume_cmd.py +4 -3
- klaude_code/{command → tui/command}/status_cmd.py +2 -1
- klaude_code/{command → tui/command}/terminal_setup_cmd.py +2 -1
- klaude_code/{command → tui/command}/thinking_cmd.py +3 -2
- 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/{ui/renderers → tui/components}/developer.py +4 -4
- 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 +7 -7
- klaude_code/{ui → tui/components}/rich/markdown.py +9 -23
- klaude_code/{ui → tui/components}/rich/status.py +2 -2
- klaude_code/{ui → tui/components}/rich/theme.py +3 -1
- 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 +13 -17
- 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} +6 -6
- klaude_code/tui/machine.py +608 -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/__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} +0 -2
- 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.2.dist-info → klaude_code-2.1.1.dist-info}/METADATA +1 -1
- klaude_code-2.1.1.dist-info/RECORD +233 -0
- klaude_code/cli/runtime.py +0 -518
- klaude_code/core/prompt.py +0 -108
- klaude_code/core/tool/skill/skill_tool.md +0 -24
- klaude_code/core/tool/skill/skill_tool.py +0 -87
- klaude_code/core/tool/tool_context.py +0 -148
- klaude_code/protocol/events.py +0 -195
- 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 -629
- klaude_code/ui/modes/repl/renderer.py +0 -464
- klaude_code/ui/renderers/__init__.py +0 -0
- klaude_code/ui/utils/__init__.py +0 -1
- klaude_code-2.0.2.dist-info/RECORD +0 -227
- /klaude_code/{trace/log.py → log.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/{core/tool/skill → 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 → tui}/terminal/selector.py +0 -0
- /klaude_code/ui/{utils/common.py → common.py} +0 -0
- {klaude_code-2.0.2.dist-info → klaude_code-2.1.1.dist-info}/WHEEL +0 -0
- {klaude_code-2.0.2.dist-info → klaude_code-2.1.1.dist-info}/entry_points.txt +0 -0
|
@@ -9,10 +9,10 @@ from pathlib import Path
|
|
|
9
9
|
from pydantic import BaseModel, Field
|
|
10
10
|
|
|
11
11
|
from klaude_code.const import DIFF_DEFAULT_CONTEXT_LINES
|
|
12
|
+
from klaude_code.core.tool.context import ToolContext
|
|
12
13
|
from klaude_code.core.tool.file._utils import file_exists, hash_text_sha256, is_directory, read_text, write_text
|
|
13
14
|
from klaude_code.core.tool.file.diff_builder import build_structured_diff
|
|
14
15
|
from klaude_code.core.tool.tool_abc import ToolABC, load_desc
|
|
15
|
-
from klaude_code.core.tool.tool_context import get_current_file_tracker
|
|
16
16
|
from klaude_code.core.tool.tool_registry import register
|
|
17
17
|
from klaude_code.protocol import llm_param, message, model, tools
|
|
18
18
|
|
|
@@ -86,7 +86,7 @@ class EditTool(ToolABC):
|
|
|
86
86
|
return content.replace(old_string, new_string, 1)
|
|
87
87
|
|
|
88
88
|
@classmethod
|
|
89
|
-
async def call(cls, arguments: str) -> message.ToolResultMessage:
|
|
89
|
+
async def call(cls, arguments: str, context: ToolContext) -> message.ToolResultMessage:
|
|
90
90
|
try:
|
|
91
91
|
args = EditTool.EditArguments.model_validate_json(arguments)
|
|
92
92
|
except ValueError as e: # pragma: no cover - defensive
|
|
@@ -111,7 +111,7 @@ class EditTool(ToolABC):
|
|
|
111
111
|
)
|
|
112
112
|
|
|
113
113
|
# FileTracker checks (only for editing existing files)
|
|
114
|
-
file_tracker =
|
|
114
|
+
file_tracker = context.file_tracker
|
|
115
115
|
tracked_status: model.FileStatus | None = None
|
|
116
116
|
if not file_exists(file_path):
|
|
117
117
|
# We require reading before editing
|
|
@@ -119,13 +119,12 @@ class EditTool(ToolABC):
|
|
|
119
119
|
status="error",
|
|
120
120
|
output_text=("File has not been read yet. Read it first before writing to it."),
|
|
121
121
|
)
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
)
|
|
122
|
+
tracked_status = file_tracker.get(file_path)
|
|
123
|
+
if tracked_status is None:
|
|
124
|
+
return message.ToolResultMessage(
|
|
125
|
+
status="error",
|
|
126
|
+
output_text=("File has not been read yet. Read it first before writing to it."),
|
|
127
|
+
)
|
|
129
128
|
|
|
130
129
|
# Edit existing file: validate and apply
|
|
131
130
|
try:
|
|
@@ -137,29 +136,28 @@ class EditTool(ToolABC):
|
|
|
137
136
|
)
|
|
138
137
|
|
|
139
138
|
# Re-check external modifications using content hash when available.
|
|
140
|
-
if tracked_status is not None:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
)
|
|
139
|
+
if tracked_status.content_sha256 is not None:
|
|
140
|
+
current_sha256 = hash_text_sha256(before)
|
|
141
|
+
if current_sha256 != tracked_status.content_sha256:
|
|
142
|
+
return message.ToolResultMessage(
|
|
143
|
+
status="error",
|
|
144
|
+
output_text=(
|
|
145
|
+
"File has been modified externally. Either by user or a linter. Read it first before writing to it."
|
|
146
|
+
),
|
|
147
|
+
)
|
|
148
|
+
else:
|
|
149
|
+
# Backward-compat: old sessions only stored mtime.
|
|
150
|
+
try:
|
|
151
|
+
current_mtime = Path(file_path).stat().st_mtime
|
|
152
|
+
except OSError:
|
|
153
|
+
current_mtime = tracked_status.mtime
|
|
154
|
+
if current_mtime != tracked_status.mtime:
|
|
155
|
+
return message.ToolResultMessage(
|
|
156
|
+
status="error",
|
|
157
|
+
output_text=(
|
|
158
|
+
"File has been modified externally. Either by user or a linter. Read it first before writing to it."
|
|
159
|
+
),
|
|
160
|
+
)
|
|
163
161
|
|
|
164
162
|
err = cls.valid(
|
|
165
163
|
content=before,
|
|
@@ -205,15 +203,14 @@ class EditTool(ToolABC):
|
|
|
205
203
|
ui_extra = build_structured_diff(before, after, file_path=file_path)
|
|
206
204
|
|
|
207
205
|
# Update tracker with new mtime and content hash
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
)
|
|
206
|
+
with contextlib.suppress(Exception):
|
|
207
|
+
existing = file_tracker.get(file_path)
|
|
208
|
+
is_mem = existing.is_memory if existing else False
|
|
209
|
+
file_tracker[file_path] = model.FileStatus(
|
|
210
|
+
mtime=Path(file_path).stat().st_mtime,
|
|
211
|
+
content_sha256=hash_text_sha256(after),
|
|
212
|
+
is_memory=is_mem,
|
|
213
|
+
)
|
|
217
214
|
|
|
218
215
|
# Build output message
|
|
219
216
|
if args.replace_all:
|
|
@@ -17,9 +17,9 @@ from klaude_code.const import (
|
|
|
17
17
|
READ_MAX_CHARS,
|
|
18
18
|
READ_MAX_IMAGE_BYTES,
|
|
19
19
|
)
|
|
20
|
+
from klaude_code.core.tool.context import FileTracker, ToolContext
|
|
20
21
|
from klaude_code.core.tool.file._utils import file_exists, is_directory
|
|
21
22
|
from klaude_code.core.tool.tool_abc import ToolABC, load_desc
|
|
22
|
-
from klaude_code.core.tool.tool_context import get_current_file_tracker
|
|
23
23
|
from klaude_code.core.tool.tool_registry import register
|
|
24
24
|
from klaude_code.protocol import llm_param, message, model, tools
|
|
25
25
|
|
|
@@ -121,8 +121,13 @@ def _read_segment(options: ReadOptions) -> ReadSegmentResult:
|
|
|
121
121
|
)
|
|
122
122
|
|
|
123
123
|
|
|
124
|
-
def _track_file_access(
|
|
125
|
-
file_tracker
|
|
124
|
+
def _track_file_access(
|
|
125
|
+
file_tracker: FileTracker | None,
|
|
126
|
+
file_path: str,
|
|
127
|
+
*,
|
|
128
|
+
content_sha256: str | None = None,
|
|
129
|
+
is_memory: bool = False,
|
|
130
|
+
) -> None:
|
|
126
131
|
if file_tracker is None or not file_exists(file_path) or is_directory(file_path):
|
|
127
132
|
return
|
|
128
133
|
with contextlib.suppress(Exception):
|
|
@@ -182,12 +187,12 @@ class ReadTool(ToolABC):
|
|
|
182
187
|
)
|
|
183
188
|
|
|
184
189
|
@classmethod
|
|
185
|
-
async def call(cls, arguments: str) -> message.ToolResultMessage:
|
|
190
|
+
async def call(cls, arguments: str, context: ToolContext) -> message.ToolResultMessage:
|
|
186
191
|
try:
|
|
187
192
|
args = ReadTool.ReadArguments.model_validate_json(arguments)
|
|
188
193
|
except Exception as e: # pragma: no cover - defensive
|
|
189
194
|
return message.ToolResultMessage(status="error", output_text=f"Invalid arguments: {e}")
|
|
190
|
-
return await cls.call_with_args(args)
|
|
195
|
+
return await cls.call_with_args(args, context)
|
|
191
196
|
|
|
192
197
|
@classmethod
|
|
193
198
|
def _effective_limits(cls) -> tuple[int | None, int | None, int | None]:
|
|
@@ -198,7 +203,7 @@ class ReadTool(ToolABC):
|
|
|
198
203
|
)
|
|
199
204
|
|
|
200
205
|
@classmethod
|
|
201
|
-
async def call_with_args(cls, args: ReadTool.ReadArguments) -> message.ToolResultMessage:
|
|
206
|
+
async def call_with_args(cls, args: ReadTool.ReadArguments, context: ToolContext) -> message.ToolResultMessage:
|
|
202
207
|
file_path = os.path.abspath(args.file_path)
|
|
203
208
|
char_per_line, line_cap, max_chars = cls._effective_limits()
|
|
204
209
|
|
|
@@ -271,7 +276,7 @@ class ReadTool(ToolABC):
|
|
|
271
276
|
output_text=f"<tool_use_error>Failed to read image file: {exc}</tool_use_error>",
|
|
272
277
|
)
|
|
273
278
|
|
|
274
|
-
_track_file_access(file_path, content_sha256=hashlib.sha256(image_bytes).hexdigest())
|
|
279
|
+
_track_file_access(context.file_tracker, file_path, content_sha256=hashlib.sha256(image_bytes).hexdigest())
|
|
275
280
|
size_kb = size_bytes / 1024.0 if size_bytes else 0.0
|
|
276
281
|
output_text = f"[image] {Path(file_path).name} ({size_kb:.1f}KB)"
|
|
277
282
|
image_part = message.ImageURLPart(url=data_url, id=None)
|
|
@@ -308,7 +313,7 @@ class ReadTool(ToolABC):
|
|
|
308
313
|
|
|
309
314
|
if offset > max(read_result.total_lines, 0):
|
|
310
315
|
warn = f"<system-reminder>Warning: the file exists but is shorter than the provided offset ({offset}). The file has {read_result.total_lines} lines.</system-reminder>"
|
|
311
|
-
_track_file_access(file_path, content_sha256=read_result.content_sha256)
|
|
316
|
+
_track_file_access(context.file_tracker, file_path, content_sha256=read_result.content_sha256)
|
|
312
317
|
return message.ToolResultMessage(status="success", output_text=warn)
|
|
313
318
|
|
|
314
319
|
lines_out: list[str] = [_format_numbered_line(no, content) for no, content in read_result.selected_lines]
|
|
@@ -326,6 +331,6 @@ class ReadTool(ToolABC):
|
|
|
326
331
|
)
|
|
327
332
|
|
|
328
333
|
read_result_str = "\n".join(lines_out)
|
|
329
|
-
_track_file_access(file_path, content_sha256=read_result.content_sha256)
|
|
334
|
+
_track_file_access(context.file_tracker, file_path, content_sha256=read_result.content_sha256)
|
|
330
335
|
|
|
331
336
|
return message.ToolResultMessage(status="success", output_text=read_result_str)
|
|
@@ -7,10 +7,10 @@ from pathlib import Path
|
|
|
7
7
|
|
|
8
8
|
from pydantic import BaseModel
|
|
9
9
|
|
|
10
|
+
from klaude_code.core.tool.context import ToolContext
|
|
10
11
|
from klaude_code.core.tool.file._utils import file_exists, hash_text_sha256, is_directory, read_text, write_text
|
|
11
12
|
from klaude_code.core.tool.file.diff_builder import build_structured_diff
|
|
12
13
|
from klaude_code.core.tool.tool_abc import ToolABC, load_desc
|
|
13
|
-
from klaude_code.core.tool.tool_context import get_current_file_tracker
|
|
14
14
|
from klaude_code.core.tool.tool_registry import register
|
|
15
15
|
from klaude_code.protocol import llm_param, message, model, tools
|
|
16
16
|
|
|
@@ -46,7 +46,7 @@ class WriteTool(ToolABC):
|
|
|
46
46
|
)
|
|
47
47
|
|
|
48
48
|
@classmethod
|
|
49
|
-
async def call(cls, arguments: str) -> message.ToolResultMessage:
|
|
49
|
+
async def call(cls, arguments: str, context: ToolContext) -> message.ToolResultMessage:
|
|
50
50
|
try:
|
|
51
51
|
args = WriteArguments.model_validate_json(arguments)
|
|
52
52
|
except ValueError as e: # pragma: no cover - defensive
|
|
@@ -60,12 +60,12 @@ class WriteTool(ToolABC):
|
|
|
60
60
|
output_text="<tool_use_error>Illegal operation on a directory. write</tool_use_error>",
|
|
61
61
|
)
|
|
62
62
|
|
|
63
|
-
file_tracker =
|
|
63
|
+
file_tracker = context.file_tracker
|
|
64
64
|
exists = file_exists(file_path)
|
|
65
65
|
tracked_status: model.FileStatus | None = None
|
|
66
66
|
|
|
67
67
|
if exists:
|
|
68
|
-
tracked_status = file_tracker.get(file_path)
|
|
68
|
+
tracked_status = file_tracker.get(file_path)
|
|
69
69
|
if tracked_status is None:
|
|
70
70
|
return message.ToolResultMessage(
|
|
71
71
|
status="error",
|
|
@@ -114,15 +114,14 @@ class WriteTool(ToolABC):
|
|
|
114
114
|
except (OSError, UnicodeError) as e: # pragma: no cover
|
|
115
115
|
return message.ToolResultMessage(status="error", output_text=f"<tool_use_error>{e}</tool_use_error>")
|
|
116
116
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
)
|
|
117
|
+
with contextlib.suppress(Exception):
|
|
118
|
+
existing = file_tracker.get(file_path)
|
|
119
|
+
is_mem = existing.is_memory if existing else False
|
|
120
|
+
file_tracker[file_path] = model.FileStatus(
|
|
121
|
+
mtime=Path(file_path).stat().st_mtime,
|
|
122
|
+
content_sha256=hash_text_sha256(args.content),
|
|
123
|
+
is_memory=is_mem,
|
|
124
|
+
)
|
|
126
125
|
|
|
127
126
|
# For markdown files, use MarkdownDocUIExtra to render content as markdown
|
|
128
127
|
# Otherwise, build diff between previous and new content
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import Any, ClassVar, cast
|
|
4
4
|
|
|
5
|
+
from klaude_code.core.tool.context import ToolContext
|
|
5
6
|
from klaude_code.protocol import llm_param, message, tools
|
|
6
7
|
|
|
7
8
|
|
|
@@ -72,7 +73,9 @@ class ReportBackTool:
|
|
|
72
73
|
)
|
|
73
74
|
|
|
74
75
|
@classmethod
|
|
75
|
-
async def call(cls, arguments: str) -> message.ToolResultMessage:
|
|
76
|
+
async def call(cls, arguments: str, context: ToolContext) -> message.ToolResultMessage:
|
|
77
|
+
del arguments
|
|
78
|
+
del context
|
|
76
79
|
"""Execute the report_back tool.
|
|
77
80
|
|
|
78
81
|
The actual handling of report_back results is done by TurnExecutor.
|
|
@@ -11,9 +11,9 @@ from typing import Any
|
|
|
11
11
|
from pydantic import BaseModel
|
|
12
12
|
|
|
13
13
|
from klaude_code.const import BASH_DEFAULT_TIMEOUT_MS, BASH_TERMINATE_TIMEOUT_SEC
|
|
14
|
+
from klaude_code.core.tool.context import ToolContext
|
|
14
15
|
from klaude_code.core.tool.shell.command_safety import is_safe_command
|
|
15
16
|
from klaude_code.core.tool.tool_abc import ToolABC, load_desc
|
|
16
|
-
from klaude_code.core.tool.tool_context import get_current_file_tracker
|
|
17
17
|
from klaude_code.core.tool.tool_registry import register
|
|
18
18
|
from klaude_code.protocol import llm_param, message, model, tools
|
|
19
19
|
|
|
@@ -71,7 +71,7 @@ class BashTool(ToolABC):
|
|
|
71
71
|
timeout_ms: int = BASH_DEFAULT_TIMEOUT_MS
|
|
72
72
|
|
|
73
73
|
@classmethod
|
|
74
|
-
async def call(cls, arguments: str) -> message.ToolResultMessage:
|
|
74
|
+
async def call(cls, arguments: str, context: ToolContext) -> message.ToolResultMessage:
|
|
75
75
|
try:
|
|
76
76
|
args = BashTool.BashArguments.model_validate_json(arguments)
|
|
77
77
|
except ValueError as e:
|
|
@@ -79,10 +79,10 @@ class BashTool(ToolABC):
|
|
|
79
79
|
status="error",
|
|
80
80
|
output_text=f"Invalid arguments: {e}",
|
|
81
81
|
)
|
|
82
|
-
return await cls.call_with_args(args)
|
|
82
|
+
return await cls.call_with_args(args, context)
|
|
83
83
|
|
|
84
84
|
@classmethod
|
|
85
|
-
async def call_with_args(cls, args: BashArguments) -> message.ToolResultMessage:
|
|
85
|
+
async def call_with_args(cls, args: BashArguments, context: ToolContext) -> message.ToolResultMessage:
|
|
86
86
|
# Safety check: only execute commands proven as "known safe"
|
|
87
87
|
result = is_safe_command(args.command)
|
|
88
88
|
if not result.is_safe:
|
|
@@ -119,6 +119,8 @@ class BashTool(ToolABC):
|
|
|
119
119
|
}
|
|
120
120
|
)
|
|
121
121
|
|
|
122
|
+
file_tracker = context.file_tracker
|
|
123
|
+
|
|
122
124
|
def _hash_file_content_sha256(file_path: str) -> str | None:
|
|
123
125
|
try:
|
|
124
126
|
suffix = Path(file_path).suffix.lower()
|
|
@@ -144,9 +146,6 @@ class BashTool(ToolABC):
|
|
|
144
146
|
return os.path.abspath(os.path.join(base_dir, path))
|
|
145
147
|
|
|
146
148
|
def _track_files_read(file_paths: list[str], *, base_dir: str) -> None:
|
|
147
|
-
file_tracker = get_current_file_tracker()
|
|
148
|
-
if file_tracker is None:
|
|
149
|
-
return
|
|
150
149
|
for p in file_paths:
|
|
151
150
|
abs_path = _resolve_in_dir(base_dir, p)
|
|
152
151
|
if not os.path.exists(abs_path) or os.path.isdir(abs_path):
|
|
@@ -168,10 +167,6 @@ class BashTool(ToolABC):
|
|
|
168
167
|
_track_files_read(file_paths, base_dir=base_dir)
|
|
169
168
|
|
|
170
169
|
def _track_mv(src_paths: list[str], dest_path: str, *, base_dir: str) -> None:
|
|
171
|
-
file_tracker = get_current_file_tracker()
|
|
172
|
-
if file_tracker is None:
|
|
173
|
-
return
|
|
174
|
-
|
|
175
170
|
abs_dest = _resolve_in_dir(base_dir, dest_path)
|
|
176
171
|
dest_is_dir = os.path.isdir(abs_dest)
|
|
177
172
|
|
|
@@ -10,8 +10,8 @@ import asyncio
|
|
|
10
10
|
import json
|
|
11
11
|
from typing import TYPE_CHECKING, Any, ClassVar, cast
|
|
12
12
|
|
|
13
|
+
from klaude_code.core.tool.context import ToolContext
|
|
13
14
|
from klaude_code.core.tool.tool_abc import ToolABC, ToolConcurrencyPolicy, ToolMetadata
|
|
14
|
-
from klaude_code.core.tool.tool_context import current_run_subtask_callback, current_sub_agent_resume_claims
|
|
15
15
|
from klaude_code.protocol import llm_param, message, model
|
|
16
16
|
from klaude_code.session.session import Session
|
|
17
17
|
|
|
@@ -52,7 +52,7 @@ class SubAgentTool(ToolABC):
|
|
|
52
52
|
)
|
|
53
53
|
|
|
54
54
|
@classmethod
|
|
55
|
-
async def call(cls, arguments: str) -> message.ToolResultMessage:
|
|
55
|
+
async def call(cls, arguments: str, context: ToolContext) -> message.ToolResultMessage:
|
|
56
56
|
profile = cls._profile
|
|
57
57
|
|
|
58
58
|
try:
|
|
@@ -60,7 +60,7 @@ class SubAgentTool(ToolABC):
|
|
|
60
60
|
except json.JSONDecodeError as e:
|
|
61
61
|
return message.ToolResultMessage(status="error", output_text=f"Invalid JSON arguments: {e}")
|
|
62
62
|
|
|
63
|
-
runner =
|
|
63
|
+
runner = context.run_subtask
|
|
64
64
|
if runner is None:
|
|
65
65
|
return message.ToolResultMessage(status="error", output_text="No subtask runner available in this context")
|
|
66
66
|
|
|
@@ -76,9 +76,10 @@ class SubAgentTool(ToolABC):
|
|
|
76
76
|
except ValueError as exc:
|
|
77
77
|
return message.ToolResultMessage(status="error", output_text=str(exc))
|
|
78
78
|
|
|
79
|
-
claims =
|
|
79
|
+
claims = context.sub_agent_resume_claims
|
|
80
80
|
if claims is not None:
|
|
81
|
-
|
|
81
|
+
ok = await claims.claim(resume_session_id)
|
|
82
|
+
if not ok:
|
|
82
83
|
return message.ToolResultMessage(
|
|
83
84
|
status="error",
|
|
84
85
|
output_text=(
|
|
@@ -87,7 +88,6 @@ class SubAgentTool(ToolABC):
|
|
|
87
88
|
"Merge into a single call or resume in a later turn."
|
|
88
89
|
),
|
|
89
90
|
)
|
|
90
|
-
claims.add(resume_session_id)
|
|
91
91
|
|
|
92
92
|
generation = args.get("generation")
|
|
93
93
|
generation_dict: dict[str, Any] | None = (
|
|
@@ -108,7 +108,8 @@ class SubAgentTool(ToolABC):
|
|
|
108
108
|
resume=resume_session_id,
|
|
109
109
|
output_schema=output_schema,
|
|
110
110
|
generation=generation_dict,
|
|
111
|
-
)
|
|
111
|
+
),
|
|
112
|
+
context.record_sub_agent_session_id,
|
|
112
113
|
)
|
|
113
114
|
except asyncio.CancelledError:
|
|
114
115
|
raise
|
|
@@ -2,8 +2,8 @@ from pathlib import Path
|
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel
|
|
4
4
|
|
|
5
|
+
from klaude_code.core.tool.context import ToolContext
|
|
5
6
|
from klaude_code.core.tool.tool_abc import ToolABC, load_desc
|
|
6
|
-
from klaude_code.core.tool.tool_context import get_current_todo_context
|
|
7
7
|
from klaude_code.core.tool.tool_registry import register
|
|
8
8
|
from klaude_code.protocol import llm_param, message, model, tools
|
|
9
9
|
|
|
@@ -76,7 +76,7 @@ class TodoWriteTool(ToolABC):
|
|
|
76
76
|
)
|
|
77
77
|
|
|
78
78
|
@classmethod
|
|
79
|
-
async def call(cls, arguments: str) -> message.ToolResultMessage:
|
|
79
|
+
async def call(cls, arguments: str, context: ToolContext) -> message.ToolResultMessage:
|
|
80
80
|
try:
|
|
81
81
|
args = TodoWriteArguments.model_validate_json(arguments)
|
|
82
82
|
except ValueError as e:
|
|
@@ -85,13 +85,7 @@ class TodoWriteTool(ToolABC):
|
|
|
85
85
|
output_text=f"Invalid arguments: {e}",
|
|
86
86
|
)
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
todo_context = get_current_todo_context()
|
|
90
|
-
if todo_context is None:
|
|
91
|
-
return message.ToolResultMessage(
|
|
92
|
-
status="error",
|
|
93
|
-
output_text="No active session found",
|
|
94
|
-
)
|
|
88
|
+
todo_context = context.todo_context
|
|
95
89
|
|
|
96
90
|
# Get current todos before updating (for comparison)
|
|
97
91
|
old_todos = todo_context.get_todos()
|
|
@@ -6,8 +6,8 @@ from pathlib import Path
|
|
|
6
6
|
|
|
7
7
|
from pydantic import BaseModel, field_validator
|
|
8
8
|
|
|
9
|
+
from klaude_code.core.tool.context import ToolContext
|
|
9
10
|
from klaude_code.core.tool.tool_abc import ToolABC, load_desc
|
|
10
|
-
from klaude_code.core.tool.tool_context import get_current_todo_context
|
|
11
11
|
from klaude_code.core.tool.tool_registry import register
|
|
12
12
|
from klaude_code.protocol import llm_param, message, model, tools
|
|
13
13
|
|
|
@@ -79,15 +79,13 @@ class UpdatePlanTool(ToolABC):
|
|
|
79
79
|
)
|
|
80
80
|
|
|
81
81
|
@classmethod
|
|
82
|
-
async def call(cls, arguments: str) -> message.ToolResultMessage:
|
|
82
|
+
async def call(cls, arguments: str, context: ToolContext) -> message.ToolResultMessage:
|
|
83
83
|
try:
|
|
84
84
|
args = UpdatePlanArguments.model_validate_json(arguments)
|
|
85
85
|
except ValueError as exc:
|
|
86
86
|
return message.ToolResultMessage(status="error", output_text=f"Invalid arguments: {exc}")
|
|
87
87
|
|
|
88
|
-
todo_context =
|
|
89
|
-
if todo_context is None:
|
|
90
|
-
return message.ToolResultMessage(status="error", output_text="No active session found")
|
|
88
|
+
todo_context = context.todo_context
|
|
91
89
|
|
|
92
90
|
new_todos = [model.TodoItem(content=item.step, status=item.status) for item in args.plan]
|
|
93
91
|
old_todos = todo_context.get_todos()
|
|
@@ -4,6 +4,7 @@ from dataclasses import dataclass
|
|
|
4
4
|
from enum import Enum
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
|
+
from klaude_code.core.tool.context import ToolContext
|
|
7
8
|
from klaude_code.protocol import llm_param, message
|
|
8
9
|
|
|
9
10
|
|
|
@@ -27,7 +28,7 @@ class ToolABC(ABC):
|
|
|
27
28
|
|
|
28
29
|
@classmethod
|
|
29
30
|
@abstractmethod
|
|
30
|
-
async def call(cls, arguments: str) -> message.ToolResultMessage:
|
|
31
|
+
async def call(cls, arguments: str, context: ToolContext) -> message.ToolResultMessage:
|
|
31
32
|
raise NotImplementedError
|
|
32
33
|
|
|
33
34
|
|
|
@@ -3,8 +3,8 @@ from typing import TypeVar
|
|
|
3
3
|
|
|
4
4
|
from klaude_code.core.tool.sub_agent_tool import SubAgentTool
|
|
5
5
|
from klaude_code.core.tool.tool_abc import ToolABC
|
|
6
|
-
from klaude_code.protocol import llm_param
|
|
7
|
-
from klaude_code.protocol.sub_agent import
|
|
6
|
+
from klaude_code.protocol import llm_param
|
|
7
|
+
from klaude_code.protocol.sub_agent import iter_sub_agent_profiles
|
|
8
8
|
|
|
9
9
|
_REGISTRY: dict[str, type[ToolABC]] = {}
|
|
10
10
|
|
|
@@ -45,34 +45,3 @@ def get_tool_schemas(tool_names: list[str]) -> list[llm_param.ToolSchema]:
|
|
|
45
45
|
def get_registry() -> dict[str, type[ToolABC]]:
|
|
46
46
|
"""Get the global tool registry."""
|
|
47
47
|
return _REGISTRY
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def load_agent_tools(
|
|
51
|
-
model_name: str, sub_agent_type: tools.SubAgentType | None = None, *, vanilla: bool = False
|
|
52
|
-
) -> list[llm_param.ToolSchema]:
|
|
53
|
-
"""Get tools for an agent based on model and agent type.
|
|
54
|
-
|
|
55
|
-
Args:
|
|
56
|
-
model_name: The model name.
|
|
57
|
-
sub_agent_type: If None, returns main agent tools. Otherwise returns sub-agent tools.
|
|
58
|
-
vanilla: If True, returns minimal vanilla tools (ignores sub_agent_type).
|
|
59
|
-
"""
|
|
60
|
-
if vanilla:
|
|
61
|
-
return get_tool_schemas([tools.BASH, tools.EDIT, tools.WRITE, tools.READ])
|
|
62
|
-
|
|
63
|
-
if sub_agent_type is not None:
|
|
64
|
-
profile = get_sub_agent_profile(sub_agent_type)
|
|
65
|
-
return get_tool_schemas(list(profile.tool_set))
|
|
66
|
-
|
|
67
|
-
# Main agent tools
|
|
68
|
-
if "gpt-5" in model_name:
|
|
69
|
-
tool_names = [tools.BASH, tools.READ, tools.APPLY_PATCH, tools.UPDATE_PLAN]
|
|
70
|
-
elif "gemini-3" in model_name:
|
|
71
|
-
tool_names = [tools.BASH, tools.READ, tools.EDIT, tools.WRITE]
|
|
72
|
-
else:
|
|
73
|
-
tool_names = [tools.BASH, tools.READ, tools.EDIT, tools.WRITE, tools.TODO_WRITE]
|
|
74
|
-
|
|
75
|
-
tool_names.extend(sub_agent_tool_names(enabled_only=True, model_name=model_name))
|
|
76
|
-
tool_names.extend([tools.SKILL, tools.MERMAID])
|
|
77
|
-
# tool_names.extend([tools.MEMORY])
|
|
78
|
-
return get_tool_schemas(tool_names)
|
|
@@ -3,9 +3,9 @@ from collections.abc import AsyncGenerator, Callable, Iterable, Sequence
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
|
|
5
5
|
from klaude_code.const import CANCEL_OUTPUT
|
|
6
|
+
from klaude_code.core.tool.context import ToolContext
|
|
6
7
|
from klaude_code.core.tool.report_back_tool import ReportBackTool
|
|
7
8
|
from klaude_code.core.tool.tool_abc import ToolABC, ToolConcurrencyPolicy
|
|
8
|
-
from klaude_code.core.tool.tool_context import current_sub_agent_session_id_recorder
|
|
9
9
|
from klaude_code.core.tool.truncation import truncate_tool_output
|
|
10
10
|
from klaude_code.protocol import message, model, tools
|
|
11
11
|
|
|
@@ -18,19 +18,24 @@ class ToolCallRequest:
|
|
|
18
18
|
arguments_json: str
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
async def run_tool(
|
|
21
|
+
async def run_tool(
|
|
22
|
+
tool_call: ToolCallRequest,
|
|
23
|
+
registry: dict[str, type[ToolABC]],
|
|
24
|
+
context: ToolContext,
|
|
25
|
+
) -> message.ToolResultMessage:
|
|
22
26
|
"""Execute a tool call and return the result.
|
|
23
27
|
|
|
24
28
|
Args:
|
|
25
29
|
tool_call: The tool call to execute.
|
|
26
30
|
registry: The tool registry mapping tool names to tool classes.
|
|
31
|
+
context: The explicit tool execution context.
|
|
27
32
|
|
|
28
33
|
Returns:
|
|
29
34
|
The result of the tool execution.
|
|
30
35
|
"""
|
|
31
36
|
# Special handling for report_back tool (not registered in global registry)
|
|
32
37
|
if tool_call.tool_name == tools.REPORT_BACK:
|
|
33
|
-
tool_result = await ReportBackTool.call(tool_call.arguments_json)
|
|
38
|
+
tool_result = await ReportBackTool.call(tool_call.arguments_json, context)
|
|
34
39
|
tool_result.call_id = tool_call.call_id
|
|
35
40
|
tool_result.tool_name = tool_call.tool_name
|
|
36
41
|
return tool_result
|
|
@@ -43,7 +48,7 @@ async def run_tool(tool_call: ToolCallRequest, registry: dict[str, type[ToolABC]
|
|
|
43
48
|
tool_name=tool_call.tool_name,
|
|
44
49
|
)
|
|
45
50
|
try:
|
|
46
|
-
tool_result = await registry[tool_call.tool_name].call(tool_call.arguments_json)
|
|
51
|
+
tool_result = await registry[tool_call.tool_name].call(tool_call.arguments_json, context)
|
|
47
52
|
tool_result.call_id = tool_call.call_id
|
|
48
53
|
tool_result.tool_name = tool_call.tool_name
|
|
49
54
|
if tool_result.output_text:
|
|
@@ -109,9 +114,11 @@ class ToolExecutor:
|
|
|
109
114
|
def __init__(
|
|
110
115
|
self,
|
|
111
116
|
*,
|
|
117
|
+
context: ToolContext,
|
|
112
118
|
registry: dict[str, type[ToolABC]],
|
|
113
119
|
append_history: Callable[[Sequence[message.HistoryEvent]], None],
|
|
114
120
|
) -> None:
|
|
121
|
+
self._context = context
|
|
115
122
|
self._registry = registry
|
|
116
123
|
self._append_history = append_history
|
|
117
124
|
|
|
@@ -268,15 +275,11 @@ class ToolExecutor:
|
|
|
268
275
|
|
|
269
276
|
async def _run_single_tool_call(self, tool_call: ToolCallRequest) -> list[ToolExecutorEvent]:
|
|
270
277
|
def _record_sub_agent_session_id(session_id: str) -> None:
|
|
271
|
-
# Keep the first recorded id if multiple writes happen.
|
|
272
278
|
if tool_call.call_id not in self._sub_agent_session_ids:
|
|
273
279
|
self._sub_agent_session_ids[tool_call.call_id] = session_id
|
|
274
280
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
tool_result: message.ToolResultMessage = await run_tool(tool_call, self._registry)
|
|
278
|
-
finally:
|
|
279
|
-
current_sub_agent_session_id_recorder.reset(recorder_token)
|
|
281
|
+
call_context = self._context.with_record_sub_agent_session_id(_record_sub_agent_session_id)
|
|
282
|
+
tool_result: message.ToolResultMessage = await run_tool(tool_call, self._registry, call_context)
|
|
280
283
|
|
|
281
284
|
self._append_history([tool_result])
|
|
282
285
|
|
|
@@ -8,6 +8,7 @@ from pathlib import Path
|
|
|
8
8
|
from pydantic import BaseModel, Field
|
|
9
9
|
|
|
10
10
|
from klaude_code.const import MERMAID_LIVE_PREFIX
|
|
11
|
+
from klaude_code.core.tool.context import ToolContext
|
|
11
12
|
from klaude_code.core.tool.tool_abc import ToolABC, load_desc
|
|
12
13
|
from klaude_code.core.tool.tool_registry import register
|
|
13
14
|
from klaude_code.protocol import llm_param, message, model, tools
|
|
@@ -40,7 +41,8 @@ class MermaidTool(ToolABC):
|
|
|
40
41
|
)
|
|
41
42
|
|
|
42
43
|
@classmethod
|
|
43
|
-
async def call(cls, arguments: str) -> message.ToolResultMessage:
|
|
44
|
+
async def call(cls, arguments: str, context: ToolContext) -> message.ToolResultMessage:
|
|
45
|
+
del context
|
|
44
46
|
try:
|
|
45
47
|
args = cls.MermaidArguments.model_validate_json(arguments)
|
|
46
48
|
except Exception as exc: # pragma: no cover - defensive
|
|
@@ -16,6 +16,7 @@ from klaude_code.const import (
|
|
|
16
16
|
WEB_FETCH_DEFAULT_TIMEOUT_SEC,
|
|
17
17
|
WEB_FETCH_USER_AGENT,
|
|
18
18
|
)
|
|
19
|
+
from klaude_code.core.tool.context import ToolContext
|
|
19
20
|
from klaude_code.core.tool.tool_abc import ToolABC, ToolConcurrencyPolicy, ToolMetadata, load_desc
|
|
20
21
|
from klaude_code.core.tool.tool_registry import register
|
|
21
22
|
from klaude_code.protocol import llm_param, message, tools
|
|
@@ -213,7 +214,7 @@ class WebFetchTool(ToolABC):
|
|
|
213
214
|
url: str
|
|
214
215
|
|
|
215
216
|
@classmethod
|
|
216
|
-
async def call(cls, arguments: str) -> message.ToolResultMessage:
|
|
217
|
+
async def call(cls, arguments: str, context: ToolContext) -> message.ToolResultMessage:
|
|
217
218
|
try:
|
|
218
219
|
args = WebFetchTool.WebFetchArguments.model_validate_json(arguments)
|
|
219
220
|
except ValueError as e:
|
|
@@ -221,10 +222,11 @@ class WebFetchTool(ToolABC):
|
|
|
221
222
|
status="error",
|
|
222
223
|
output_text=f"Invalid arguments: {e}",
|
|
223
224
|
)
|
|
224
|
-
return await cls.call_with_args(args)
|
|
225
|
+
return await cls.call_with_args(args, context)
|
|
225
226
|
|
|
226
227
|
@classmethod
|
|
227
|
-
async def call_with_args(cls, args: WebFetchArguments) -> message.ToolResultMessage:
|
|
228
|
+
async def call_with_args(cls, args: WebFetchArguments, context: ToolContext) -> message.ToolResultMessage:
|
|
229
|
+
del context
|
|
228
230
|
url = args.url
|
|
229
231
|
|
|
230
232
|
# Basic URL validation
|