klaude-code 1.2.6__py3-none-any.whl → 1.8.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/auth/__init__.py +24 -0
- klaude_code/auth/codex/__init__.py +20 -0
- klaude_code/auth/codex/exceptions.py +17 -0
- klaude_code/auth/codex/jwt_utils.py +45 -0
- klaude_code/auth/codex/oauth.py +229 -0
- klaude_code/auth/codex/token_manager.py +84 -0
- klaude_code/cli/auth_cmd.py +73 -0
- klaude_code/cli/config_cmd.py +91 -0
- klaude_code/cli/cost_cmd.py +338 -0
- klaude_code/cli/debug.py +78 -0
- klaude_code/cli/list_model.py +307 -0
- klaude_code/cli/main.py +233 -134
- klaude_code/cli/runtime.py +309 -117
- klaude_code/{version.py → cli/self_update.py} +114 -5
- klaude_code/cli/session_cmd.py +37 -21
- klaude_code/command/__init__.py +88 -27
- klaude_code/command/clear_cmd.py +8 -7
- klaude_code/command/command_abc.py +31 -31
- klaude_code/command/debug_cmd.py +79 -0
- klaude_code/command/export_cmd.py +19 -53
- klaude_code/command/export_online_cmd.py +154 -0
- klaude_code/command/fork_session_cmd.py +267 -0
- klaude_code/command/help_cmd.py +7 -8
- klaude_code/command/model_cmd.py +60 -10
- klaude_code/command/model_select.py +84 -0
- klaude_code/command/prompt-jj-describe.md +32 -0
- klaude_code/command/prompt_command.py +19 -11
- klaude_code/command/refresh_cmd.py +8 -10
- klaude_code/command/registry.py +139 -40
- klaude_code/command/release_notes_cmd.py +84 -0
- klaude_code/command/resume_cmd.py +111 -0
- klaude_code/command/status_cmd.py +104 -60
- klaude_code/command/terminal_setup_cmd.py +7 -9
- klaude_code/command/thinking_cmd.py +98 -0
- klaude_code/config/__init__.py +14 -6
- klaude_code/config/assets/__init__.py +1 -0
- klaude_code/config/assets/builtin_config.yaml +303 -0
- klaude_code/config/builtin_config.py +38 -0
- klaude_code/config/config.py +378 -109
- klaude_code/config/select_model.py +117 -53
- klaude_code/config/thinking.py +269 -0
- klaude_code/{const/__init__.py → const.py} +50 -19
- klaude_code/core/agent.py +20 -28
- klaude_code/core/executor.py +327 -112
- klaude_code/core/manager/__init__.py +2 -4
- klaude_code/core/manager/llm_clients.py +1 -15
- klaude_code/core/manager/llm_clients_builder.py +10 -11
- klaude_code/core/manager/sub_agent_manager.py +37 -6
- klaude_code/core/prompt.py +63 -44
- klaude_code/core/prompts/prompt-claude-code.md +2 -13
- klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +117 -0
- klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +117 -0
- klaude_code/core/prompts/prompt-codex.md +9 -42
- klaude_code/core/prompts/prompt-minimal.md +12 -0
- klaude_code/core/prompts/{prompt-subagent-explore.md → prompt-sub-agent-explore.md} +16 -3
- klaude_code/core/prompts/{prompt-subagent-oracle.md → prompt-sub-agent-oracle.md} +1 -2
- klaude_code/core/prompts/prompt-sub-agent-web.md +51 -0
- klaude_code/core/reminders.py +283 -95
- klaude_code/core/task.py +113 -75
- klaude_code/core/tool/__init__.py +24 -31
- klaude_code/core/tool/file/_utils.py +36 -0
- klaude_code/core/tool/file/apply_patch.py +17 -25
- klaude_code/core/tool/file/apply_patch_tool.py +57 -77
- klaude_code/core/tool/file/diff_builder.py +151 -0
- klaude_code/core/tool/file/edit_tool.py +50 -63
- klaude_code/core/tool/file/move_tool.md +41 -0
- klaude_code/core/tool/file/move_tool.py +435 -0
- klaude_code/core/tool/file/read_tool.md +1 -1
- klaude_code/core/tool/file/read_tool.py +86 -86
- klaude_code/core/tool/file/write_tool.py +59 -69
- klaude_code/core/tool/report_back_tool.py +84 -0
- klaude_code/core/tool/shell/bash_tool.py +265 -22
- klaude_code/core/tool/shell/command_safety.py +3 -6
- klaude_code/core/tool/{memory → skill}/skill_tool.py +16 -26
- klaude_code/core/tool/sub_agent_tool.py +13 -2
- klaude_code/core/tool/todo/todo_write_tool.md +0 -157
- klaude_code/core/tool/todo/todo_write_tool.py +1 -1
- klaude_code/core/tool/todo/todo_write_tool_raw.md +182 -0
- klaude_code/core/tool/todo/update_plan_tool.py +1 -1
- klaude_code/core/tool/tool_abc.py +18 -0
- klaude_code/core/tool/tool_context.py +27 -12
- klaude_code/core/tool/tool_registry.py +7 -7
- klaude_code/core/tool/tool_runner.py +44 -36
- klaude_code/core/tool/truncation.py +29 -14
- klaude_code/core/tool/web/mermaid_tool.md +43 -0
- klaude_code/core/tool/web/mermaid_tool.py +2 -5
- klaude_code/core/tool/web/web_fetch_tool.md +1 -1
- klaude_code/core/tool/web/web_fetch_tool.py +112 -22
- klaude_code/core/tool/web/web_search_tool.md +23 -0
- klaude_code/core/tool/web/web_search_tool.py +130 -0
- klaude_code/core/turn.py +168 -66
- klaude_code/llm/__init__.py +2 -10
- klaude_code/llm/anthropic/client.py +190 -178
- klaude_code/llm/anthropic/input.py +39 -15
- klaude_code/llm/bedrock/__init__.py +3 -0
- klaude_code/llm/bedrock/client.py +60 -0
- klaude_code/llm/client.py +7 -21
- klaude_code/llm/codex/__init__.py +5 -0
- klaude_code/llm/codex/client.py +149 -0
- klaude_code/llm/google/__init__.py +3 -0
- klaude_code/llm/google/client.py +309 -0
- klaude_code/llm/google/input.py +215 -0
- klaude_code/llm/input_common.py +3 -9
- klaude_code/llm/openai_compatible/client.py +72 -164
- klaude_code/llm/openai_compatible/input.py +6 -4
- klaude_code/llm/openai_compatible/stream.py +273 -0
- klaude_code/llm/openai_compatible/tool_call_accumulator.py +17 -1
- klaude_code/llm/openrouter/client.py +89 -160
- klaude_code/llm/openrouter/input.py +18 -30
- klaude_code/llm/openrouter/reasoning.py +118 -0
- klaude_code/llm/registry.py +39 -7
- klaude_code/llm/responses/client.py +184 -171
- klaude_code/llm/responses/input.py +20 -1
- klaude_code/llm/usage.py +17 -12
- klaude_code/protocol/commands.py +17 -1
- klaude_code/protocol/events.py +31 -4
- klaude_code/protocol/llm_param.py +13 -10
- klaude_code/protocol/model.py +232 -29
- klaude_code/protocol/op.py +90 -1
- klaude_code/protocol/op_handler.py +35 -1
- klaude_code/protocol/sub_agent/__init__.py +117 -0
- klaude_code/protocol/sub_agent/explore.py +63 -0
- klaude_code/protocol/sub_agent/oracle.py +91 -0
- klaude_code/protocol/sub_agent/task.py +61 -0
- klaude_code/protocol/sub_agent/web.py +79 -0
- klaude_code/protocol/tools.py +4 -2
- klaude_code/session/__init__.py +2 -2
- klaude_code/session/codec.py +71 -0
- klaude_code/session/export.py +293 -86
- klaude_code/session/selector.py +89 -67
- klaude_code/session/session.py +320 -309
- klaude_code/session/store.py +220 -0
- klaude_code/session/templates/export_session.html +595 -83
- klaude_code/session/templates/mermaid_viewer.html +926 -0
- klaude_code/skill/__init__.py +27 -0
- klaude_code/skill/assets/deslop/SKILL.md +17 -0
- klaude_code/skill/assets/dev-docs/SKILL.md +108 -0
- klaude_code/skill/assets/handoff/SKILL.md +39 -0
- klaude_code/skill/assets/jj-workspace/SKILL.md +20 -0
- klaude_code/skill/assets/skill-creator/SKILL.md +139 -0
- klaude_code/{core/tool/memory/skill_loader.py → skill/loader.py} +55 -15
- klaude_code/skill/manager.py +70 -0
- klaude_code/skill/system_skills.py +192 -0
- klaude_code/trace/__init__.py +20 -2
- klaude_code/trace/log.py +150 -5
- klaude_code/ui/__init__.py +4 -9
- klaude_code/ui/core/input.py +1 -1
- klaude_code/ui/core/stage_manager.py +7 -7
- klaude_code/ui/modes/debug/display.py +2 -1
- klaude_code/ui/modes/repl/__init__.py +3 -48
- klaude_code/ui/modes/repl/clipboard.py +5 -5
- klaude_code/ui/modes/repl/completers.py +487 -123
- klaude_code/ui/modes/repl/display.py +5 -4
- klaude_code/ui/modes/repl/event_handler.py +370 -117
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +552 -105
- klaude_code/ui/modes/repl/key_bindings.py +146 -23
- klaude_code/ui/modes/repl/renderer.py +189 -99
- klaude_code/ui/renderers/assistant.py +9 -2
- klaude_code/ui/renderers/bash_syntax.py +178 -0
- klaude_code/ui/renderers/common.py +78 -0
- klaude_code/ui/renderers/developer.py +104 -48
- klaude_code/ui/renderers/diffs.py +87 -6
- klaude_code/ui/renderers/errors.py +11 -6
- klaude_code/ui/renderers/mermaid_viewer.py +57 -0
- klaude_code/ui/renderers/metadata.py +112 -76
- klaude_code/ui/renderers/sub_agent.py +92 -7
- klaude_code/ui/renderers/thinking.py +40 -18
- klaude_code/ui/renderers/tools.py +405 -227
- klaude_code/ui/renderers/user_input.py +73 -13
- klaude_code/ui/rich/__init__.py +10 -1
- klaude_code/ui/rich/cjk_wrap.py +228 -0
- klaude_code/ui/rich/code_panel.py +131 -0
- klaude_code/ui/rich/live.py +17 -0
- klaude_code/ui/rich/markdown.py +305 -170
- klaude_code/ui/rich/searchable_text.py +10 -13
- klaude_code/ui/rich/status.py +190 -49
- klaude_code/ui/rich/theme.py +135 -39
- klaude_code/ui/terminal/__init__.py +55 -0
- klaude_code/ui/terminal/color.py +1 -1
- klaude_code/ui/terminal/control.py +13 -22
- klaude_code/ui/terminal/notifier.py +44 -4
- klaude_code/ui/terminal/selector.py +658 -0
- klaude_code/ui/utils/common.py +0 -18
- klaude_code-1.8.0.dist-info/METADATA +377 -0
- klaude_code-1.8.0.dist-info/RECORD +219 -0
- {klaude_code-1.2.6.dist-info → klaude_code-1.8.0.dist-info}/entry_points.txt +1 -0
- klaude_code/command/diff_cmd.py +0 -138
- klaude_code/command/prompt-dev-docs-update.md +0 -56
- klaude_code/command/prompt-dev-docs.md +0 -46
- klaude_code/config/list_model.py +0 -162
- klaude_code/core/manager/agent_manager.py +0 -127
- klaude_code/core/prompts/prompt-subagent-webfetch.md +0 -46
- klaude_code/core/tool/file/multi_edit_tool.md +0 -42
- klaude_code/core/tool/file/multi_edit_tool.py +0 -199
- klaude_code/core/tool/memory/memory_tool.md +0 -16
- klaude_code/core/tool/memory/memory_tool.py +0 -462
- klaude_code/llm/openrouter/reasoning_handler.py +0 -209
- klaude_code/protocol/sub_agent.py +0 -348
- klaude_code/ui/utils/debouncer.py +0 -42
- klaude_code-1.2.6.dist-info/METADATA +0 -178
- klaude_code-1.2.6.dist-info/RECORD +0 -167
- /klaude_code/core/prompts/{prompt-subagent.md → prompt-sub-agent.md} +0 -0
- /klaude_code/core/tool/{memory → skill}/__init__.py +0 -0
- /klaude_code/core/tool/{memory → skill}/skill_tool.md +0 -0
- {klaude_code-1.2.6.dist-info → klaude_code-1.8.0.dist-info}/WHEEL +0 -0
|
@@ -1,18 +1,44 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import contextlib
|
|
3
|
+
import os
|
|
2
4
|
import re
|
|
5
|
+
import shlex
|
|
6
|
+
import signal
|
|
3
7
|
import subprocess
|
|
4
8
|
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
5
10
|
|
|
6
11
|
from pydantic import BaseModel
|
|
7
12
|
|
|
8
13
|
from klaude_code import const
|
|
9
14
|
from klaude_code.core.tool.shell.command_safety import is_safe_command
|
|
10
15
|
from klaude_code.core.tool.tool_abc import ToolABC, load_desc
|
|
16
|
+
from klaude_code.core.tool.tool_context import get_current_file_tracker
|
|
11
17
|
from klaude_code.core.tool.tool_registry import register
|
|
12
18
|
from klaude_code.protocol import llm_param, model, tools
|
|
13
19
|
|
|
14
|
-
# Regex to strip ANSI
|
|
15
|
-
|
|
20
|
+
# Regex to strip ANSI and terminal control sequences from command output
|
|
21
|
+
#
|
|
22
|
+
# This is intentionally broader than just SGR color codes (e.g. "\x1b[31m").
|
|
23
|
+
# Many interactive or TUI-style programs emit additional escape sequences
|
|
24
|
+
# that move the cursor, clear the screen, or switch screen buffers
|
|
25
|
+
# (CSI/OSC/DCS/APC/PM, etc). If these reach the Rich console, they can
|
|
26
|
+
# corrupt the REPL layout. We therefore remove all of them before
|
|
27
|
+
# rendering the output.
|
|
28
|
+
_ANSI_ESCAPE_RE = re.compile(
|
|
29
|
+
r"""
|
|
30
|
+
\x1B
|
|
31
|
+
(?:
|
|
32
|
+
\[[0-?]*[ -/]*[@-~] | # CSI sequences
|
|
33
|
+
\][0-?]*.*?(?:\x07|\x1B\\) | # OSC sequences
|
|
34
|
+
P.*?(?:\x07|\x1B\\) | # DCS sequences
|
|
35
|
+
_.*?(?:\x07|\x1B\\) | # APC sequences
|
|
36
|
+
\^.*?(?:\x07|\x1B\\) | # PM sequences
|
|
37
|
+
[@-Z\\-_] # 2-char sequences
|
|
38
|
+
)
|
|
39
|
+
""",
|
|
40
|
+
re.VERBOSE | re.DOTALL,
|
|
41
|
+
)
|
|
16
42
|
|
|
17
43
|
|
|
18
44
|
@register(tools.BASH)
|
|
@@ -67,31 +93,250 @@ class BashTool(ToolABC):
|
|
|
67
93
|
|
|
68
94
|
# Run the command using bash -lc so shell semantics work (pipes, &&, etc.)
|
|
69
95
|
# Capture stdout/stderr, respect timeout, and return a ToolMessage.
|
|
96
|
+
#
|
|
97
|
+
# Important: this tool is intentionally non-interactive.
|
|
98
|
+
# - Always detach stdin (DEVNULL) so interactive programs can't steal REPL input.
|
|
99
|
+
# - Always disable pagers/editors to avoid launching TUI subprocesses that can
|
|
100
|
+
# leave the terminal in a bad state.
|
|
70
101
|
cmd = ["bash", "-lc", args.command]
|
|
71
102
|
timeout_sec = max(0.0, args.timeout_ms / 1000.0)
|
|
72
103
|
|
|
104
|
+
env = os.environ.copy()
|
|
105
|
+
env.update(
|
|
106
|
+
{
|
|
107
|
+
# Avoid blocking on git/jj prompts.
|
|
108
|
+
"GIT_TERMINAL_PROMPT": "0",
|
|
109
|
+
# Avoid pagers.
|
|
110
|
+
"PAGER": "cat",
|
|
111
|
+
"GIT_PAGER": "cat",
|
|
112
|
+
# Avoid opening editors.
|
|
113
|
+
"EDITOR": "true",
|
|
114
|
+
"VISUAL": "true",
|
|
115
|
+
"GIT_EDITOR": "true",
|
|
116
|
+
"JJ_EDITOR": "true",
|
|
117
|
+
# Encourage non-interactive output.
|
|
118
|
+
"TERM": "dumb",
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
def _hash_file_content_sha256(file_path: str) -> str | None:
|
|
123
|
+
try:
|
|
124
|
+
suffix = Path(file_path).suffix.lower()
|
|
125
|
+
if suffix in {".png", ".jpg", ".jpeg", ".gif", ".webp"}:
|
|
126
|
+
import hashlib
|
|
127
|
+
|
|
128
|
+
with open(file_path, "rb") as f:
|
|
129
|
+
return hashlib.sha256(f.read()).hexdigest()
|
|
130
|
+
|
|
131
|
+
import hashlib
|
|
132
|
+
|
|
133
|
+
hasher = hashlib.sha256()
|
|
134
|
+
with open(file_path, encoding="utf-8", errors="replace") as f:
|
|
135
|
+
for line in f:
|
|
136
|
+
hasher.update(line.encode("utf-8"))
|
|
137
|
+
return hasher.hexdigest()
|
|
138
|
+
except (FileNotFoundError, IsADirectoryError, OSError, PermissionError, UnicodeDecodeError):
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
def _resolve_in_dir(base_dir: str, path: str) -> str:
|
|
142
|
+
if os.path.isabs(path):
|
|
143
|
+
return os.path.abspath(path)
|
|
144
|
+
return os.path.abspath(os.path.join(base_dir, path))
|
|
145
|
+
|
|
146
|
+
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
|
+
for p in file_paths:
|
|
151
|
+
abs_path = _resolve_in_dir(base_dir, p)
|
|
152
|
+
if not os.path.exists(abs_path) or os.path.isdir(abs_path):
|
|
153
|
+
continue
|
|
154
|
+
sha = _hash_file_content_sha256(abs_path)
|
|
155
|
+
if sha is None:
|
|
156
|
+
continue
|
|
157
|
+
existing = file_tracker.get(abs_path)
|
|
158
|
+
is_mem = existing.is_memory if existing else False
|
|
159
|
+
with contextlib.suppress(Exception):
|
|
160
|
+
file_tracker[abs_path] = model.FileStatus(
|
|
161
|
+
mtime=Path(abs_path).stat().st_mtime,
|
|
162
|
+
content_sha256=sha,
|
|
163
|
+
is_memory=is_mem,
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def _track_files_written(file_paths: list[str], *, base_dir: str) -> None:
|
|
167
|
+
# Same as read tracking, but intentionally kept separate for clarity.
|
|
168
|
+
_track_files_read(file_paths, base_dir=base_dir)
|
|
169
|
+
|
|
170
|
+
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
|
+
abs_dest = _resolve_in_dir(base_dir, dest_path)
|
|
176
|
+
dest_is_dir = os.path.isdir(abs_dest)
|
|
177
|
+
|
|
178
|
+
for src in src_paths:
|
|
179
|
+
abs_src = _resolve_in_dir(base_dir, src)
|
|
180
|
+
abs_new = os.path.join(abs_dest, os.path.basename(abs_src)) if dest_is_dir else abs_dest
|
|
181
|
+
|
|
182
|
+
# Remove old entry if present.
|
|
183
|
+
existing = file_tracker.pop(abs_src, None)
|
|
184
|
+
is_mem = existing.is_memory if existing else False
|
|
185
|
+
|
|
186
|
+
if not os.path.exists(abs_new) or os.path.isdir(abs_new):
|
|
187
|
+
continue
|
|
188
|
+
|
|
189
|
+
sha = _hash_file_content_sha256(abs_new)
|
|
190
|
+
if sha is None:
|
|
191
|
+
continue
|
|
192
|
+
with contextlib.suppress(Exception):
|
|
193
|
+
file_tracker[abs_new] = model.FileStatus(
|
|
194
|
+
mtime=Path(abs_new).stat().st_mtime,
|
|
195
|
+
content_sha256=sha,
|
|
196
|
+
is_memory=is_mem,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
def _best_effort_update_file_tracker(command: str) -> None:
|
|
200
|
+
# Best-effort heuristics for common shell tools that access/modify files.
|
|
201
|
+
# We intentionally do not try to interpret complex shell scripts here.
|
|
202
|
+
try:
|
|
203
|
+
argv = shlex.split(command, posix=True)
|
|
204
|
+
except ValueError:
|
|
205
|
+
return
|
|
206
|
+
if not argv:
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
# Handle common patterns like: cd subdir && cat file
|
|
210
|
+
base_dir = os.getcwd()
|
|
211
|
+
while len(argv) >= 4 and argv[0] == "cd" and argv[2] == "&&":
|
|
212
|
+
dest = argv[1]
|
|
213
|
+
if dest != "-":
|
|
214
|
+
base_dir = _resolve_in_dir(base_dir, dest)
|
|
215
|
+
argv = argv[3:]
|
|
216
|
+
if not argv:
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
cmd0 = argv[0]
|
|
220
|
+
if cmd0 == "cat":
|
|
221
|
+
paths = [a for a in argv[1:] if a and not a.startswith("-") and a != "-"]
|
|
222
|
+
_track_files_read(paths, base_dir=base_dir)
|
|
223
|
+
return
|
|
224
|
+
|
|
225
|
+
if cmd0 == "sed":
|
|
226
|
+
# Support: sed [-i ...] 's/old/new/' file1 [file2 ...]
|
|
227
|
+
# and: sed -n 'Np' file
|
|
228
|
+
saw_script = False
|
|
229
|
+
file_paths: list[str] = []
|
|
230
|
+
for a in argv[1:]:
|
|
231
|
+
if not a:
|
|
232
|
+
continue
|
|
233
|
+
if a == "--":
|
|
234
|
+
continue
|
|
235
|
+
if a.startswith("-") and not saw_script:
|
|
236
|
+
continue
|
|
237
|
+
if not saw_script and (a.startswith("s/") or a.startswith("s|") or a.endswith("p")):
|
|
238
|
+
saw_script = True
|
|
239
|
+
continue
|
|
240
|
+
if saw_script and not a.startswith("-"):
|
|
241
|
+
file_paths.append(a)
|
|
242
|
+
|
|
243
|
+
if file_paths:
|
|
244
|
+
_track_files_written(file_paths, base_dir=base_dir)
|
|
245
|
+
return
|
|
246
|
+
|
|
247
|
+
if cmd0 == "mv":
|
|
248
|
+
# Support: mv [opts] src... dest
|
|
249
|
+
operands: list[str] = []
|
|
250
|
+
end_of_opts = False
|
|
251
|
+
for a in argv[1:]:
|
|
252
|
+
if not end_of_opts and a == "--":
|
|
253
|
+
end_of_opts = True
|
|
254
|
+
continue
|
|
255
|
+
if not end_of_opts and a.startswith("-"):
|
|
256
|
+
continue
|
|
257
|
+
operands.append(a)
|
|
258
|
+
if len(operands) < 2:
|
|
259
|
+
return
|
|
260
|
+
srcs = operands[:-1]
|
|
261
|
+
dest = operands[-1]
|
|
262
|
+
_track_mv(srcs, dest, base_dir=base_dir)
|
|
263
|
+
return
|
|
264
|
+
|
|
265
|
+
async def _terminate_process(proc: asyncio.subprocess.Process) -> None:
|
|
266
|
+
# Best-effort termination. Ensure we don't hang on cancellation.
|
|
267
|
+
if proc.returncode is not None:
|
|
268
|
+
return
|
|
269
|
+
|
|
270
|
+
try:
|
|
271
|
+
if os.name == "posix":
|
|
272
|
+
os.killpg(proc.pid, signal.SIGTERM)
|
|
273
|
+
else:
|
|
274
|
+
proc.terminate()
|
|
275
|
+
except ProcessLookupError:
|
|
276
|
+
return
|
|
277
|
+
except OSError:
|
|
278
|
+
# Fall back to kill below.
|
|
279
|
+
pass
|
|
280
|
+
|
|
281
|
+
with contextlib.suppress(Exception):
|
|
282
|
+
await asyncio.wait_for(proc.wait(), timeout=1.0)
|
|
283
|
+
return
|
|
284
|
+
|
|
285
|
+
# Escalate to hard kill if it didn't exit quickly.
|
|
286
|
+
with contextlib.suppress(Exception):
|
|
287
|
+
if os.name == "posix":
|
|
288
|
+
os.killpg(proc.pid, signal.SIGKILL)
|
|
289
|
+
else:
|
|
290
|
+
proc.kill()
|
|
291
|
+
with contextlib.suppress(Exception):
|
|
292
|
+
await asyncio.wait_for(proc.wait(), timeout=1.0)
|
|
293
|
+
|
|
73
294
|
try:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
295
|
+
# Create a dedicated process group so we can terminate the whole tree.
|
|
296
|
+
# (macOS/Linux support start_new_session; Windows does not.)
|
|
297
|
+
kwargs: dict[str, Any] = {
|
|
298
|
+
"stdin": asyncio.subprocess.DEVNULL,
|
|
299
|
+
"stdout": asyncio.subprocess.PIPE,
|
|
300
|
+
"stderr": asyncio.subprocess.PIPE,
|
|
301
|
+
"env": env,
|
|
302
|
+
}
|
|
303
|
+
if os.name == "posix":
|
|
304
|
+
kwargs["start_new_session"] = True
|
|
305
|
+
elif os.name == "nt": # pragma: no cover
|
|
306
|
+
kwargs["creationflags"] = getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0)
|
|
82
307
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
308
|
+
proc = await asyncio.create_subprocess_exec(*cmd, **kwargs)
|
|
309
|
+
try:
|
|
310
|
+
stdout_b, stderr_b = await asyncio.wait_for(proc.communicate(), timeout=timeout_sec)
|
|
311
|
+
except TimeoutError:
|
|
312
|
+
with contextlib.suppress(Exception):
|
|
313
|
+
await _terminate_process(proc)
|
|
314
|
+
return model.ToolResultItem(
|
|
315
|
+
status="error",
|
|
316
|
+
output=f"Timeout after {args.timeout_ms} ms running: {args.command}",
|
|
317
|
+
)
|
|
318
|
+
except asyncio.CancelledError:
|
|
319
|
+
# Ensure subprocess is stopped and propagate cancellation.
|
|
320
|
+
with contextlib.suppress(Exception):
|
|
321
|
+
await asyncio.shield(_terminate_process(proc))
|
|
322
|
+
raise
|
|
323
|
+
|
|
324
|
+
stdout = _ANSI_ESCAPE_RE.sub("", (stdout_b or b"").decode(errors="replace"))
|
|
325
|
+
stderr = _ANSI_ESCAPE_RE.sub("", (stderr_b or b"").decode(errors="replace"))
|
|
326
|
+
rc = proc.returncode
|
|
86
327
|
|
|
87
328
|
if rc == 0:
|
|
88
329
|
output = stdout if stdout else ""
|
|
89
330
|
# Include stderr if there is useful diagnostics despite success
|
|
90
331
|
if stderr.strip():
|
|
91
332
|
output = (output + ("\n" if output else "")) + f"[stderr]\n{stderr}"
|
|
333
|
+
|
|
334
|
+
_best_effort_update_file_tracker(args.command)
|
|
92
335
|
return model.ToolResultItem(
|
|
93
336
|
status="success",
|
|
94
|
-
|
|
337
|
+
# Preserve leading whitespace for tools like `nl -ba`.
|
|
338
|
+
# Only trim trailing newlines to avoid adding an extra blank line in the UI.
|
|
339
|
+
output=output.rstrip("\n"),
|
|
95
340
|
)
|
|
96
341
|
else:
|
|
97
342
|
combined = ""
|
|
@@ -103,20 +348,18 @@ class BashTool(ToolABC):
|
|
|
103
348
|
combined = f"Command exited with code {rc}"
|
|
104
349
|
return model.ToolResultItem(
|
|
105
350
|
status="error",
|
|
106
|
-
|
|
351
|
+
# Preserve leading whitespace; only trim trailing newlines.
|
|
352
|
+
output=combined.rstrip("\n"),
|
|
107
353
|
)
|
|
108
|
-
|
|
109
|
-
except subprocess.TimeoutExpired:
|
|
110
|
-
return model.ToolResultItem(
|
|
111
|
-
status="error",
|
|
112
|
-
output=f"Timeout after {args.timeout_ms} ms running: {args.command}",
|
|
113
|
-
)
|
|
114
354
|
except FileNotFoundError:
|
|
115
355
|
return model.ToolResultItem(
|
|
116
356
|
status="error",
|
|
117
357
|
output="bash not found on system path",
|
|
118
358
|
)
|
|
119
|
-
except
|
|
359
|
+
except asyncio.CancelledError:
|
|
360
|
+
# Propagate cooperative cancellation so outer layers can handle interrupts correctly.
|
|
361
|
+
raise
|
|
362
|
+
except OSError as e: # safeguard: catch remaining OS-level errors (permissions, resources, etc.)
|
|
120
363
|
return model.ToolResultItem(
|
|
121
364
|
status="error",
|
|
122
365
|
output=f"Execution error: {e}",
|
|
@@ -275,12 +275,10 @@ def _is_safe_argv(argv: list[str]) -> SafetyCheckResult:
|
|
|
275
275
|
"tag",
|
|
276
276
|
"clone",
|
|
277
277
|
"worktree",
|
|
278
|
+
"push",
|
|
279
|
+
"pull",
|
|
280
|
+
"remote",
|
|
278
281
|
}
|
|
279
|
-
# Block remote operations
|
|
280
|
-
blocked_git_cmds = {"push", "pull", "remote"}
|
|
281
|
-
|
|
282
|
-
if sub in blocked_git_cmds:
|
|
283
|
-
return SafetyCheckResult(False, f"git: Remote operation '{sub}' not allowed")
|
|
284
282
|
if sub not in allowed_git_cmds:
|
|
285
283
|
return SafetyCheckResult(False, f"git: Subcommand '{sub}' not in allow list")
|
|
286
284
|
return SafetyCheckResult(True)
|
|
@@ -293,7 +291,6 @@ def _is_safe_argv(argv: list[str]) -> SafetyCheckResult:
|
|
|
293
291
|
"ruff",
|
|
294
292
|
"pyright",
|
|
295
293
|
"make",
|
|
296
|
-
"isort",
|
|
297
294
|
"npm",
|
|
298
295
|
"pnpm",
|
|
299
296
|
"bun",
|
|
@@ -1,27 +1,22 @@
|
|
|
1
|
+
"""SkillTool - Tool for agent to activate and load skills."""
|
|
2
|
+
|
|
1
3
|
from pathlib import Path
|
|
2
4
|
|
|
3
5
|
from pydantic import BaseModel
|
|
4
6
|
|
|
5
|
-
from klaude_code.core.tool.memory.skill_loader import SkillLoader
|
|
6
7
|
from klaude_code.core.tool.tool_abc import ToolABC, load_desc
|
|
7
8
|
from klaude_code.core.tool.tool_registry import register
|
|
8
9
|
from klaude_code.protocol import llm_param, model, tools
|
|
10
|
+
from klaude_code.skill import get_available_skills, get_skill, list_skill_names
|
|
9
11
|
|
|
10
12
|
|
|
11
13
|
@register(tools.SKILL)
|
|
12
14
|
class SkillTool(ToolABC):
|
|
13
|
-
"""Tool to execute/load a skill within the main conversation"""
|
|
14
|
-
|
|
15
|
-
_skill_loader: SkillLoader | None = None
|
|
16
|
-
|
|
17
|
-
@classmethod
|
|
18
|
-
def set_skill_loader(cls, loader: SkillLoader) -> None:
|
|
19
|
-
"""Set the skill loader instance"""
|
|
20
|
-
cls._skill_loader = loader
|
|
15
|
+
"""Tool to execute/load a skill within the main conversation."""
|
|
21
16
|
|
|
22
17
|
@classmethod
|
|
23
18
|
def schema(cls) -> llm_param.ToolSchema:
|
|
24
|
-
"""Generate schema with embedded available skills metadata"""
|
|
19
|
+
"""Generate schema with embedded available skills metadata."""
|
|
25
20
|
skills_xml = cls._generate_skills_xml()
|
|
26
21
|
|
|
27
22
|
return llm_param.ToolSchema(
|
|
@@ -42,16 +37,17 @@ class SkillTool(ToolABC):
|
|
|
42
37
|
|
|
43
38
|
@classmethod
|
|
44
39
|
def _generate_skills_xml(cls) -> str:
|
|
45
|
-
"""Generate XML format skills metadata"""
|
|
46
|
-
|
|
40
|
+
"""Generate XML format skills metadata."""
|
|
41
|
+
skills = get_available_skills()
|
|
42
|
+
if not skills:
|
|
47
43
|
return ""
|
|
48
44
|
|
|
49
45
|
xml_parts: list[str] = []
|
|
50
|
-
for
|
|
46
|
+
for name, description, location in skills:
|
|
51
47
|
xml_parts.append(f"""<skill>
|
|
52
|
-
<name>{
|
|
53
|
-
<description>{
|
|
54
|
-
<location>{
|
|
48
|
+
<name>{name}</name>
|
|
49
|
+
<description>{description}</description>
|
|
50
|
+
<location>{location}</location>
|
|
55
51
|
</skill>""")
|
|
56
52
|
return "\n".join(xml_parts)
|
|
57
53
|
|
|
@@ -60,7 +56,7 @@ class SkillTool(ToolABC):
|
|
|
60
56
|
|
|
61
57
|
@classmethod
|
|
62
58
|
async def call(cls, arguments: str) -> model.ToolResultItem:
|
|
63
|
-
"""Load and return full skill content"""
|
|
59
|
+
"""Load and return full skill content."""
|
|
64
60
|
try:
|
|
65
61
|
args = cls.SkillArguments.model_validate_json(arguments)
|
|
66
62
|
except ValueError as e:
|
|
@@ -69,16 +65,10 @@ class SkillTool(ToolABC):
|
|
|
69
65
|
output=f"Invalid arguments: {e}",
|
|
70
66
|
)
|
|
71
67
|
|
|
72
|
-
|
|
73
|
-
return model.ToolResultItem(
|
|
74
|
-
status="error",
|
|
75
|
-
output="Skill loader not initialized",
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
skill = cls._skill_loader.get_skill(args.command)
|
|
68
|
+
skill = get_skill(args.command)
|
|
79
69
|
|
|
80
70
|
if not skill:
|
|
81
|
-
available = ", ".join(
|
|
71
|
+
available = ", ".join(list_skill_names())
|
|
82
72
|
return model.ToolResultItem(
|
|
83
73
|
status="error",
|
|
84
74
|
output=f"Skill '{args.command}' does not exist. Available skills: {available}",
|
|
@@ -88,7 +78,7 @@ class SkillTool(ToolABC):
|
|
|
88
78
|
base_dir = str(skill.skill_path.parent) if skill.skill_path else "unknown"
|
|
89
79
|
|
|
90
80
|
# Return with loading message format
|
|
91
|
-
result = f"""<command-message>The "{skill.name}" skill is
|
|
81
|
+
result = f"""<command-message>The "{skill.name}" skill is activated</command-message>
|
|
92
82
|
<command-name>{skill.name}</command-name>
|
|
93
83
|
|
|
94
84
|
Base directory for this skill: {base_dir}
|
|
@@ -10,7 +10,7 @@ import asyncio
|
|
|
10
10
|
import json
|
|
11
11
|
from typing import TYPE_CHECKING, ClassVar
|
|
12
12
|
|
|
13
|
-
from klaude_code.core.tool.tool_abc import ToolABC
|
|
13
|
+
from klaude_code.core.tool.tool_abc import ToolABC, ToolConcurrencyPolicy, ToolMetadata
|
|
14
14
|
from klaude_code.core.tool.tool_context import current_run_subtask_callback
|
|
15
15
|
from klaude_code.protocol import llm_param, model
|
|
16
16
|
|
|
@@ -36,6 +36,10 @@ class SubAgentTool(ToolABC):
|
|
|
36
36
|
{"_profile": profile},
|
|
37
37
|
)
|
|
38
38
|
|
|
39
|
+
@classmethod
|
|
40
|
+
def metadata(cls) -> ToolMetadata:
|
|
41
|
+
return ToolMetadata(concurrency_policy=ToolConcurrencyPolicy.CONCURRENT, has_side_effects=True)
|
|
42
|
+
|
|
39
43
|
@classmethod
|
|
40
44
|
def schema(cls) -> llm_param.ToolSchema:
|
|
41
45
|
profile = cls._profile
|
|
@@ -63,12 +67,18 @@ class SubAgentTool(ToolABC):
|
|
|
63
67
|
prompt = profile.prompt_builder(args)
|
|
64
68
|
description = args.get("description", "")
|
|
65
69
|
|
|
70
|
+
# Extract output_schema if configured
|
|
71
|
+
output_schema = None
|
|
72
|
+
if profile.output_schema_arg:
|
|
73
|
+
output_schema = args.get(profile.output_schema_arg)
|
|
74
|
+
|
|
66
75
|
try:
|
|
67
76
|
result = await runner(
|
|
68
77
|
model.SubAgentState(
|
|
69
78
|
sub_agent_type=profile.name,
|
|
70
79
|
sub_agent_desc=description,
|
|
71
80
|
sub_agent_prompt=prompt,
|
|
81
|
+
output_schema=output_schema,
|
|
72
82
|
)
|
|
73
83
|
)
|
|
74
84
|
except asyncio.CancelledError:
|
|
@@ -79,5 +89,6 @@ class SubAgentTool(ToolABC):
|
|
|
79
89
|
return model.ToolResultItem(
|
|
80
90
|
status="success" if not result.error else "error",
|
|
81
91
|
output=result.task_result or "",
|
|
82
|
-
ui_extra=model.
|
|
92
|
+
ui_extra=model.SessionIdUIExtra(session_id=result.session_id),
|
|
93
|
+
task_metadata=result.task_metadata,
|
|
83
94
|
)
|
|
@@ -22,161 +22,4 @@ Skip using this tool when:
|
|
|
22
22
|
|
|
23
23
|
NOTE that you should not use this tool if there is only one trivial task to do. In this case you are better off just doing the task directly.
|
|
24
24
|
|
|
25
|
-
#### Examples of When to Use the Todo List
|
|
26
|
-
|
|
27
|
-
<example>
|
|
28
|
-
User: I want to add a dark mode toggle to the application settings. Make sure you run the tests and build when you're done!
|
|
29
|
-
Assistant: I'll help add a dark mode toggle to your application settings. Let me create a todo list to track this implementation.
|
|
30
|
-
*Creates todo list with the following items:*
|
|
31
|
-
1. Creating dark mode toggle component in Settings page
|
|
32
|
-
2. Adding dark mode state management (context/store)
|
|
33
|
-
3. Implementing CSS-in-JS styles for dark theme
|
|
34
|
-
4. Updating existing components to support theme switching
|
|
35
|
-
5. Running tests and build process, addressing any failures or errors that occur
|
|
36
|
-
*Begins working on the first task*
|
|
37
|
-
|
|
38
|
-
<reasoning>
|
|
39
|
-
The assistant used the todo list because:
|
|
40
|
-
1. Adding dark mode is a multi-step feature requiring UI, state management, and styling changes
|
|
41
|
-
2. The user explicitly requested tests and build be run afterward
|
|
42
|
-
3. The assistant inferred that tests and build need to pass by adding "Ensure tests and build succeed" as the final task
|
|
43
|
-
</reasoning>
|
|
44
|
-
</example>
|
|
45
|
-
|
|
46
|
-
<example>
|
|
47
|
-
User: Help me rename the function getCwd to getCurrentWorkingDirectory across my project
|
|
48
|
-
Assistant: Let me first search through your codebase to find all occurrences of 'getCwd'.
|
|
49
|
-
*Uses grep or search tools to locate all instances of getCwd in the codebase*
|
|
50
|
-
Assistant: I've found 15 instances of 'getCwd' across 8 different files. Let me create a todo list to track these changes.
|
|
51
|
-
*Creates todo list with specific items for each file that needs updating*
|
|
52
|
-
|
|
53
|
-
<reasoning>
|
|
54
|
-
The assistant used the todo list because:
|
|
55
|
-
1. First, the assistant searched to understand the scope of the task
|
|
56
|
-
2. Upon finding multiple occurrences across different files, it determined this was a complex task with multiple steps
|
|
57
|
-
3. The todo list helps ensure every instance is tracked and updated systematically
|
|
58
|
-
4. This approach prevents missing any occurrences and maintains code consistency
|
|
59
|
-
</reasoning>
|
|
60
|
-
</example>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
<example>
|
|
64
|
-
User: I need to implement these features for my e-commerce site: user registration, product catalog, shopping cart, and checkout flow.
|
|
65
|
-
Assistant: I'll help implement these features. First, let's add all the features to the todo list.
|
|
66
|
-
*Creates a todo list breaking down each feature into specific tasks based on the project architecture*
|
|
67
|
-
Assistant: Let's start with user registration. This will involve creating database models, API endpoints, and frontend forms that integrate with your existing authentication system.
|
|
68
|
-
|
|
69
|
-
<reasoning>
|
|
70
|
-
The assistant used the todo list because:
|
|
71
|
-
1. The user provided multiple complex features to implement in a comma separated list
|
|
72
|
-
2. The todo list helps organize these large features into manageable tasks
|
|
73
|
-
3. This approach allows for tracking progress across the entire implementation
|
|
74
|
-
</reasoning>
|
|
75
|
-
</example>
|
|
76
|
-
|
|
77
|
-
<example>
|
|
78
|
-
User: Can you help optimize my React application? It's rendering slowly and has performance issues.</user>
|
|
79
|
-
Assistant: I'll help optimize your React application. First, let me examine your codebase to identify potential performance bottlenecks.
|
|
80
|
-
*Reviews component structure, render patterns, state management, and data fetching*
|
|
81
|
-
Assistant: After analyzing your codebase, I've identified several performance issues. Let me create a todo list to track our optimization efforts.
|
|
82
|
-
*Creates todo list with items like: 1) Implementing memoization for expensive calculations in ProductList, 2) Adding virtualization for long lists in Dashboard, 3) Optimizing image loading in Gallery component, 4) Fixing state update loops in ShoppingCart, 5) Reviewing bundle size and implementing code splitting*
|
|
83
|
-
Let's start by implementing memoization for the expensive calculations in your ProductList component.</assistant>
|
|
84
|
-
|
|
85
|
-
<reasoning>
|
|
86
|
-
The assistant used the todo list because:
|
|
87
|
-
1. First, the assistant examined the codebase to identify specific performance issues
|
|
88
|
-
2. Based on this analysis, it identified multiple optimization opportunities
|
|
89
|
-
3. Performance optimization is a non-trivial task requiring multiple steps
|
|
90
|
-
4. The todo list helps methodically track improvements across different components
|
|
91
|
-
5. This systematic approach ensures all performance bottlenecks are addressed
|
|
92
|
-
</reasoning>
|
|
93
|
-
</example>
|
|
94
|
-
|
|
95
|
-
#### Examples of When NOT to Use the Todo List
|
|
96
|
-
|
|
97
|
-
<example>
|
|
98
|
-
User: How do I print 'Hello World' in Python?
|
|
99
|
-
Assistant: In Python, you can print "Hello World" with this simple code:
|
|
100
|
-
|
|
101
|
-
python
|
|
102
|
-
print("Hello World")
|
|
103
|
-
|
|
104
|
-
This will output the text "Hello World" to the console when executed.</assistant>
|
|
105
|
-
|
|
106
|
-
<reasoning>
|
|
107
|
-
The assistant did not use the todo list because this is a single, trivial task that can be completed in one step. There's no need to track multiple tasks or steps for such a straightforward request.
|
|
108
|
-
</reasoning>
|
|
109
|
-
</example>
|
|
110
|
-
|
|
111
|
-
<example>
|
|
112
|
-
User: What does the git status command do?
|
|
113
|
-
Assistant: The git status command shows the current state of your working directory and staging area. It displays which changes have been staged, which haven't, and which files aren't being tracked by Git. This is a fundamental command for understanding the current state of your repository before making commits.
|
|
114
|
-
|
|
115
|
-
<reasoning>
|
|
116
|
-
The assistant did not use the todo list because this is an informational request with no actual coding task to complete. The user is simply asking for an explanation, not for the assistant to perform multiple steps or tasks.
|
|
117
|
-
</reasoning>
|
|
118
|
-
</example>
|
|
119
|
-
|
|
120
|
-
<example>
|
|
121
|
-
User: Can you add a comment to the calculateTotal function to explain what it does?
|
|
122
|
-
Assistant: Sure, let me add a comment to the calculateTotal function to explain what it does.
|
|
123
|
-
* Uses the Edit tool to add a comment to the calculateTotal function *
|
|
124
|
-
|
|
125
|
-
<reasoning>
|
|
126
|
-
The assistant did not use the todo list because this is a single, straightforward task confined to one location in the code. Adding a comment doesn't require tracking multiple steps or systematic organization.
|
|
127
|
-
</reasoning>
|
|
128
|
-
</example>
|
|
129
|
-
|
|
130
|
-
<example>
|
|
131
|
-
User: Run npm install for me and tell me what happens.
|
|
132
|
-
Assistant: I'll run the npm install command for you.
|
|
133
|
-
|
|
134
|
-
*Executes: npm install*
|
|
135
|
-
|
|
136
|
-
The command completed successfully. Here's the output:
|
|
137
|
-
[Output of npm install command]
|
|
138
|
-
|
|
139
|
-
All dependencies have been installed according to your package.json file.
|
|
140
|
-
|
|
141
|
-
<reasoning>
|
|
142
|
-
The assistant did not use the todo list because this is a single command execution with immediate results. There are no multiple steps to track or organize, making the todo list unnecessary for this straightforward task.
|
|
143
|
-
</reasoning>
|
|
144
|
-
</example>
|
|
145
|
-
|
|
146
|
-
#### Task States and Management
|
|
147
|
-
|
|
148
|
-
1. **Task States**: Use these states to track progress:
|
|
149
|
-
- pending: Task not yet started
|
|
150
|
-
- in_progress: Currently working on (limit to ONE task at a time)
|
|
151
|
-
- completed: Task finished successfully
|
|
152
|
-
|
|
153
|
-
**IMPORTANT**: Task descriptions must have two forms:
|
|
154
|
-
- content: The imperative form describing what needs to be done (e.g., "Run tests", "Build the project")
|
|
155
|
-
- activeForm: The present continuous form shown during execution (e.g., "Running tests", "Building the project")
|
|
156
|
-
|
|
157
|
-
2. **Task Management**:
|
|
158
|
-
- Update task status in real-time as you work
|
|
159
|
-
- Mark tasks complete IMMEDIATELY after finishing (don't batch completions)
|
|
160
|
-
- Exactly ONE task must be in_progress at any time (not less, not more)
|
|
161
|
-
- Complete current tasks before starting new ones
|
|
162
|
-
- Remove tasks that are no longer relevant from the list entirely
|
|
163
|
-
|
|
164
|
-
3. **Task Completion Requirements**:
|
|
165
|
-
- ONLY mark a task as completed when you have FULLY accomplished it
|
|
166
|
-
- If you encounter errors, blockers, or cannot finish, keep the task as in_progress
|
|
167
|
-
- When blocked, create a new task describing what needs to be resolved
|
|
168
|
-
- Never mark a task as completed if:
|
|
169
|
-
- Tests are failing
|
|
170
|
-
- Implementation is partial
|
|
171
|
-
- You encountered unresolved errors
|
|
172
|
-
- You couldn't find necessary files or dependencies
|
|
173
|
-
|
|
174
|
-
4. **Task Breakdown**:
|
|
175
|
-
- Create specific, actionable items
|
|
176
|
-
- Break complex tasks into smaller, manageable steps
|
|
177
|
-
- Use clear, descriptive task names
|
|
178
|
-
- Always provide both forms:
|
|
179
|
-
- content: "Fix authentication bug"
|
|
180
|
-
- activeForm: "Fixing authentication bug"
|
|
181
|
-
|
|
182
25
|
When in doubt, use this tool. Being proactive with task management demonstrates attentiveness and ensures you complete all requirements successfully.
|
|
@@ -116,6 +116,6 @@ Your todo list has changed. DO NOT mention this explicitly to the user. Here are
|
|
|
116
116
|
return model.ToolResultItem(
|
|
117
117
|
status="success",
|
|
118
118
|
output=response,
|
|
119
|
-
ui_extra=model.
|
|
119
|
+
ui_extra=model.TodoListUIExtra(todo_list=ui_extra),
|
|
120
120
|
side_effects=[model.ToolSideEffect.TODO_CHANGE],
|
|
121
121
|
)
|