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.
Files changed (205) hide show
  1. klaude_code/auth/__init__.py +24 -0
  2. klaude_code/auth/codex/__init__.py +20 -0
  3. klaude_code/auth/codex/exceptions.py +17 -0
  4. klaude_code/auth/codex/jwt_utils.py +45 -0
  5. klaude_code/auth/codex/oauth.py +229 -0
  6. klaude_code/auth/codex/token_manager.py +84 -0
  7. klaude_code/cli/auth_cmd.py +73 -0
  8. klaude_code/cli/config_cmd.py +91 -0
  9. klaude_code/cli/cost_cmd.py +338 -0
  10. klaude_code/cli/debug.py +78 -0
  11. klaude_code/cli/list_model.py +307 -0
  12. klaude_code/cli/main.py +233 -134
  13. klaude_code/cli/runtime.py +309 -117
  14. klaude_code/{version.py → cli/self_update.py} +114 -5
  15. klaude_code/cli/session_cmd.py +37 -21
  16. klaude_code/command/__init__.py +88 -27
  17. klaude_code/command/clear_cmd.py +8 -7
  18. klaude_code/command/command_abc.py +31 -31
  19. klaude_code/command/debug_cmd.py +79 -0
  20. klaude_code/command/export_cmd.py +19 -53
  21. klaude_code/command/export_online_cmd.py +154 -0
  22. klaude_code/command/fork_session_cmd.py +267 -0
  23. klaude_code/command/help_cmd.py +7 -8
  24. klaude_code/command/model_cmd.py +60 -10
  25. klaude_code/command/model_select.py +84 -0
  26. klaude_code/command/prompt-jj-describe.md +32 -0
  27. klaude_code/command/prompt_command.py +19 -11
  28. klaude_code/command/refresh_cmd.py +8 -10
  29. klaude_code/command/registry.py +139 -40
  30. klaude_code/command/release_notes_cmd.py +84 -0
  31. klaude_code/command/resume_cmd.py +111 -0
  32. klaude_code/command/status_cmd.py +104 -60
  33. klaude_code/command/terminal_setup_cmd.py +7 -9
  34. klaude_code/command/thinking_cmd.py +98 -0
  35. klaude_code/config/__init__.py +14 -6
  36. klaude_code/config/assets/__init__.py +1 -0
  37. klaude_code/config/assets/builtin_config.yaml +303 -0
  38. klaude_code/config/builtin_config.py +38 -0
  39. klaude_code/config/config.py +378 -109
  40. klaude_code/config/select_model.py +117 -53
  41. klaude_code/config/thinking.py +269 -0
  42. klaude_code/{const/__init__.py → const.py} +50 -19
  43. klaude_code/core/agent.py +20 -28
  44. klaude_code/core/executor.py +327 -112
  45. klaude_code/core/manager/__init__.py +2 -4
  46. klaude_code/core/manager/llm_clients.py +1 -15
  47. klaude_code/core/manager/llm_clients_builder.py +10 -11
  48. klaude_code/core/manager/sub_agent_manager.py +37 -6
  49. klaude_code/core/prompt.py +63 -44
  50. klaude_code/core/prompts/prompt-claude-code.md +2 -13
  51. klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +117 -0
  52. klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +117 -0
  53. klaude_code/core/prompts/prompt-codex.md +9 -42
  54. klaude_code/core/prompts/prompt-minimal.md +12 -0
  55. klaude_code/core/prompts/{prompt-subagent-explore.md → prompt-sub-agent-explore.md} +16 -3
  56. klaude_code/core/prompts/{prompt-subagent-oracle.md → prompt-sub-agent-oracle.md} +1 -2
  57. klaude_code/core/prompts/prompt-sub-agent-web.md +51 -0
  58. klaude_code/core/reminders.py +283 -95
  59. klaude_code/core/task.py +113 -75
  60. klaude_code/core/tool/__init__.py +24 -31
  61. klaude_code/core/tool/file/_utils.py +36 -0
  62. klaude_code/core/tool/file/apply_patch.py +17 -25
  63. klaude_code/core/tool/file/apply_patch_tool.py +57 -77
  64. klaude_code/core/tool/file/diff_builder.py +151 -0
  65. klaude_code/core/tool/file/edit_tool.py +50 -63
  66. klaude_code/core/tool/file/move_tool.md +41 -0
  67. klaude_code/core/tool/file/move_tool.py +435 -0
  68. klaude_code/core/tool/file/read_tool.md +1 -1
  69. klaude_code/core/tool/file/read_tool.py +86 -86
  70. klaude_code/core/tool/file/write_tool.py +59 -69
  71. klaude_code/core/tool/report_back_tool.py +84 -0
  72. klaude_code/core/tool/shell/bash_tool.py +265 -22
  73. klaude_code/core/tool/shell/command_safety.py +3 -6
  74. klaude_code/core/tool/{memory → skill}/skill_tool.py +16 -26
  75. klaude_code/core/tool/sub_agent_tool.py +13 -2
  76. klaude_code/core/tool/todo/todo_write_tool.md +0 -157
  77. klaude_code/core/tool/todo/todo_write_tool.py +1 -1
  78. klaude_code/core/tool/todo/todo_write_tool_raw.md +182 -0
  79. klaude_code/core/tool/todo/update_plan_tool.py +1 -1
  80. klaude_code/core/tool/tool_abc.py +18 -0
  81. klaude_code/core/tool/tool_context.py +27 -12
  82. klaude_code/core/tool/tool_registry.py +7 -7
  83. klaude_code/core/tool/tool_runner.py +44 -36
  84. klaude_code/core/tool/truncation.py +29 -14
  85. klaude_code/core/tool/web/mermaid_tool.md +43 -0
  86. klaude_code/core/tool/web/mermaid_tool.py +2 -5
  87. klaude_code/core/tool/web/web_fetch_tool.md +1 -1
  88. klaude_code/core/tool/web/web_fetch_tool.py +112 -22
  89. klaude_code/core/tool/web/web_search_tool.md +23 -0
  90. klaude_code/core/tool/web/web_search_tool.py +130 -0
  91. klaude_code/core/turn.py +168 -66
  92. klaude_code/llm/__init__.py +2 -10
  93. klaude_code/llm/anthropic/client.py +190 -178
  94. klaude_code/llm/anthropic/input.py +39 -15
  95. klaude_code/llm/bedrock/__init__.py +3 -0
  96. klaude_code/llm/bedrock/client.py +60 -0
  97. klaude_code/llm/client.py +7 -21
  98. klaude_code/llm/codex/__init__.py +5 -0
  99. klaude_code/llm/codex/client.py +149 -0
  100. klaude_code/llm/google/__init__.py +3 -0
  101. klaude_code/llm/google/client.py +309 -0
  102. klaude_code/llm/google/input.py +215 -0
  103. klaude_code/llm/input_common.py +3 -9
  104. klaude_code/llm/openai_compatible/client.py +72 -164
  105. klaude_code/llm/openai_compatible/input.py +6 -4
  106. klaude_code/llm/openai_compatible/stream.py +273 -0
  107. klaude_code/llm/openai_compatible/tool_call_accumulator.py +17 -1
  108. klaude_code/llm/openrouter/client.py +89 -160
  109. klaude_code/llm/openrouter/input.py +18 -30
  110. klaude_code/llm/openrouter/reasoning.py +118 -0
  111. klaude_code/llm/registry.py +39 -7
  112. klaude_code/llm/responses/client.py +184 -171
  113. klaude_code/llm/responses/input.py +20 -1
  114. klaude_code/llm/usage.py +17 -12
  115. klaude_code/protocol/commands.py +17 -1
  116. klaude_code/protocol/events.py +31 -4
  117. klaude_code/protocol/llm_param.py +13 -10
  118. klaude_code/protocol/model.py +232 -29
  119. klaude_code/protocol/op.py +90 -1
  120. klaude_code/protocol/op_handler.py +35 -1
  121. klaude_code/protocol/sub_agent/__init__.py +117 -0
  122. klaude_code/protocol/sub_agent/explore.py +63 -0
  123. klaude_code/protocol/sub_agent/oracle.py +91 -0
  124. klaude_code/protocol/sub_agent/task.py +61 -0
  125. klaude_code/protocol/sub_agent/web.py +79 -0
  126. klaude_code/protocol/tools.py +4 -2
  127. klaude_code/session/__init__.py +2 -2
  128. klaude_code/session/codec.py +71 -0
  129. klaude_code/session/export.py +293 -86
  130. klaude_code/session/selector.py +89 -67
  131. klaude_code/session/session.py +320 -309
  132. klaude_code/session/store.py +220 -0
  133. klaude_code/session/templates/export_session.html +595 -83
  134. klaude_code/session/templates/mermaid_viewer.html +926 -0
  135. klaude_code/skill/__init__.py +27 -0
  136. klaude_code/skill/assets/deslop/SKILL.md +17 -0
  137. klaude_code/skill/assets/dev-docs/SKILL.md +108 -0
  138. klaude_code/skill/assets/handoff/SKILL.md +39 -0
  139. klaude_code/skill/assets/jj-workspace/SKILL.md +20 -0
  140. klaude_code/skill/assets/skill-creator/SKILL.md +139 -0
  141. klaude_code/{core/tool/memory/skill_loader.py → skill/loader.py} +55 -15
  142. klaude_code/skill/manager.py +70 -0
  143. klaude_code/skill/system_skills.py +192 -0
  144. klaude_code/trace/__init__.py +20 -2
  145. klaude_code/trace/log.py +150 -5
  146. klaude_code/ui/__init__.py +4 -9
  147. klaude_code/ui/core/input.py +1 -1
  148. klaude_code/ui/core/stage_manager.py +7 -7
  149. klaude_code/ui/modes/debug/display.py +2 -1
  150. klaude_code/ui/modes/repl/__init__.py +3 -48
  151. klaude_code/ui/modes/repl/clipboard.py +5 -5
  152. klaude_code/ui/modes/repl/completers.py +487 -123
  153. klaude_code/ui/modes/repl/display.py +5 -4
  154. klaude_code/ui/modes/repl/event_handler.py +370 -117
  155. klaude_code/ui/modes/repl/input_prompt_toolkit.py +552 -105
  156. klaude_code/ui/modes/repl/key_bindings.py +146 -23
  157. klaude_code/ui/modes/repl/renderer.py +189 -99
  158. klaude_code/ui/renderers/assistant.py +9 -2
  159. klaude_code/ui/renderers/bash_syntax.py +178 -0
  160. klaude_code/ui/renderers/common.py +78 -0
  161. klaude_code/ui/renderers/developer.py +104 -48
  162. klaude_code/ui/renderers/diffs.py +87 -6
  163. klaude_code/ui/renderers/errors.py +11 -6
  164. klaude_code/ui/renderers/mermaid_viewer.py +57 -0
  165. klaude_code/ui/renderers/metadata.py +112 -76
  166. klaude_code/ui/renderers/sub_agent.py +92 -7
  167. klaude_code/ui/renderers/thinking.py +40 -18
  168. klaude_code/ui/renderers/tools.py +405 -227
  169. klaude_code/ui/renderers/user_input.py +73 -13
  170. klaude_code/ui/rich/__init__.py +10 -1
  171. klaude_code/ui/rich/cjk_wrap.py +228 -0
  172. klaude_code/ui/rich/code_panel.py +131 -0
  173. klaude_code/ui/rich/live.py +17 -0
  174. klaude_code/ui/rich/markdown.py +305 -170
  175. klaude_code/ui/rich/searchable_text.py +10 -13
  176. klaude_code/ui/rich/status.py +190 -49
  177. klaude_code/ui/rich/theme.py +135 -39
  178. klaude_code/ui/terminal/__init__.py +55 -0
  179. klaude_code/ui/terminal/color.py +1 -1
  180. klaude_code/ui/terminal/control.py +13 -22
  181. klaude_code/ui/terminal/notifier.py +44 -4
  182. klaude_code/ui/terminal/selector.py +658 -0
  183. klaude_code/ui/utils/common.py +0 -18
  184. klaude_code-1.8.0.dist-info/METADATA +377 -0
  185. klaude_code-1.8.0.dist-info/RECORD +219 -0
  186. {klaude_code-1.2.6.dist-info → klaude_code-1.8.0.dist-info}/entry_points.txt +1 -0
  187. klaude_code/command/diff_cmd.py +0 -138
  188. klaude_code/command/prompt-dev-docs-update.md +0 -56
  189. klaude_code/command/prompt-dev-docs.md +0 -46
  190. klaude_code/config/list_model.py +0 -162
  191. klaude_code/core/manager/agent_manager.py +0 -127
  192. klaude_code/core/prompts/prompt-subagent-webfetch.md +0 -46
  193. klaude_code/core/tool/file/multi_edit_tool.md +0 -42
  194. klaude_code/core/tool/file/multi_edit_tool.py +0 -199
  195. klaude_code/core/tool/memory/memory_tool.md +0 -16
  196. klaude_code/core/tool/memory/memory_tool.py +0 -462
  197. klaude_code/llm/openrouter/reasoning_handler.py +0 -209
  198. klaude_code/protocol/sub_agent.py +0 -348
  199. klaude_code/ui/utils/debouncer.py +0 -42
  200. klaude_code-1.2.6.dist-info/METADATA +0 -178
  201. klaude_code-1.2.6.dist-info/RECORD +0 -167
  202. /klaude_code/core/prompts/{prompt-subagent.md → prompt-sub-agent.md} +0 -0
  203. /klaude_code/core/tool/{memory → skill}/__init__.py +0 -0
  204. /klaude_code/core/tool/{memory → skill}/skill_tool.md +0 -0
  205. {klaude_code-1.2.6.dist-info → klaude_code-1.8.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,182 @@
1
+ Use this tool to create and manage a structured task list for your current coding session. This helps you track progress, organize complex tasks, and demonstrate thoroughness to the user.
2
+ It also helps the user understand the progress of the task and overall progress of their requests.
3
+
4
+ #### When to Use This Tool
5
+ Use this tool proactively in these scenarios:
6
+
7
+ 1. Complex multi-step tasks - When a task requires 3 or more distinct steps or actions
8
+ 2. Non-trivial and complex tasks - Tasks that require careful planning or multiple operations
9
+ 3. User explicitly requests todo list - When the user directly asks you to use the todo list
10
+ 4. User provides multiple tasks - When users provide a list of things to be done (numbered or comma-separated)
11
+ 5. After receiving new instructions - Immediately capture user requirements as todos
12
+ 6. When you start working on a task - Mark it as in_progress BEFORE beginning work. Ideally you should only have one todo as in_progress at a time
13
+ 7. After completing a task - Mark it as completed and add any new follow-up tasks discovered during implementation
14
+
15
+ #### When NOT to Use This Tool
16
+
17
+ Skip using this tool when:
18
+ 1. There is only a single, straightforward task
19
+ 2. The task is trivial and tracking it provides no organizational benefit
20
+ 3. The task can be completed in less than 3 trivial steps
21
+ 4. The task is purely conversational or informational
22
+
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
+
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
+ When in doubt, use this tool. Being proactive with task management demonstrates attentiveness and ensures you complete all requirements successfully.
@@ -99,6 +99,6 @@ class UpdatePlanTool(ToolABC):
99
99
  return model.ToolResultItem(
100
100
  status="success",
101
101
  output="Plan updated",
102
- ui_extra=model.ToolResultUIExtra(type=model.ToolResultUIExtraType.TODO_LIST, todo_list=ui_extra),
102
+ ui_extra=model.TodoListUIExtra(todo_list=ui_extra),
103
103
  side_effects=[model.ToolSideEffect.TODO_CHANGE],
104
104
  )
@@ -1,5 +1,7 @@
1
1
  import string
2
2
  from abc import ABC, abstractmethod
3
+ from dataclasses import dataclass
4
+ from enum import Enum
3
5
  from pathlib import Path
4
6
 
5
7
  from klaude_code.protocol import llm_param, model
@@ -14,6 +16,10 @@ def load_desc(path: Path, substitutions: dict[str, str] | None = None) -> str:
14
16
 
15
17
 
16
18
  class ToolABC(ABC):
19
+ @classmethod
20
+ def metadata(cls) -> "ToolMetadata":
21
+ return ToolMetadata()
22
+
17
23
  @classmethod
18
24
  @abstractmethod
19
25
  def schema(cls) -> llm_param.ToolSchema:
@@ -23,3 +29,15 @@ class ToolABC(ABC):
23
29
  @abstractmethod
24
30
  async def call(cls, arguments: str) -> model.ToolResultItem:
25
31
  raise NotImplementedError
32
+
33
+
34
+ class ToolConcurrencyPolicy(str, Enum):
35
+ SEQUENTIAL = "sequential"
36
+ CONCURRENT = "concurrent"
37
+
38
+
39
+ @dataclass(frozen=True)
40
+ class ToolMetadata:
41
+ concurrency_policy: ToolConcurrencyPolicy = ToolConcurrencyPolicy.SEQUENTIAL
42
+ has_side_effects: bool = False
43
+ requires_tool_context: bool = True
@@ -9,6 +9,8 @@ from klaude_code.protocol import model
9
9
  from klaude_code.protocol.sub_agent import SubAgentResult
10
10
  from klaude_code.session.session import Session
11
11
 
12
+ type FileTracker = MutableMapping[str, model.FileStatus]
13
+
12
14
 
13
15
  @dataclass
14
16
  class TodoContext:
@@ -22,6 +24,19 @@ class TodoContext:
22
24
  set_todos: Callable[[list[model.TodoItem]], None]
23
25
 
24
26
 
27
+ @dataclass
28
+ class SessionTodoStore:
29
+ """Adapter exposing session todos through an explicit interface."""
30
+
31
+ session: Session
32
+
33
+ def get(self) -> list[model.TodoItem]:
34
+ return self.session.todos
35
+
36
+ def set(self, todos: list[model.TodoItem]) -> None:
37
+ self.session.todos = todos
38
+
39
+
25
40
  @dataclass
26
41
  class ToolContextToken:
27
42
  """Tokens used to restore tool execution context.
@@ -31,15 +46,13 @@ class ToolContextToken:
31
46
  finishes running.
32
47
  """
33
48
 
34
- file_tracker_token: Token[MutableMapping[str, float] | None] | None
49
+ file_tracker_token: Token[FileTracker | None] | None
35
50
  todo_token: Token[TodoContext | None] | None
36
51
 
37
52
 
38
53
  # Holds the current file tracker mapping for tool execution context.
39
54
  # Set by Agent/Reminder right before invoking a tool.
40
- current_file_tracker_var: ContextVar[MutableMapping[str, float] | None] = ContextVar(
41
- "current_file_tracker", default=None
42
- )
55
+ current_file_tracker_var: ContextVar[FileTracker | None] = ContextVar("current_file_tracker", default=None)
43
56
 
44
57
 
45
58
  # Holds the todo access context for tools.
@@ -55,10 +68,7 @@ def set_tool_context_from_session(session: Session) -> ToolContextToken:
55
68
  """
56
69
 
57
70
  file_tracker_token = current_file_tracker_var.set(session.file_tracker)
58
- todo_ctx = TodoContext(
59
- get_todos=lambda: session.todos,
60
- set_todos=lambda todos: setattr(session, "todos", todos),
61
- )
71
+ todo_ctx = build_todo_context(session)
62
72
  todo_token = current_todo_context_var.set(todo_ctx)
63
73
  return ToolContextToken(file_tracker_token=file_tracker_token, todo_token=todo_token)
64
74
 
@@ -73,9 +83,7 @@ def reset_tool_context(token: ToolContextToken) -> None:
73
83
 
74
84
 
75
85
  @contextmanager
76
- def tool_context(
77
- file_tracker: MutableMapping[str, float], todo_ctx: TodoContext
78
- ) -> Generator[ToolContextToken, None, None]:
86
+ def tool_context(file_tracker: FileTracker, todo_ctx: TodoContext) -> Generator[ToolContextToken]:
79
87
  """Context manager for setting and resetting tool execution context."""
80
88
 
81
89
  file_tracker_token = current_file_tracker_var.set(file_tracker)
@@ -87,7 +95,14 @@ def tool_context(
87
95
  reset_tool_context(token)
88
96
 
89
97
 
90
- def get_current_file_tracker() -> MutableMapping[str, float] | None:
98
+ def build_todo_context(session: Session) -> TodoContext:
99
+ """Create a TodoContext backed by the given session."""
100
+
101
+ store = SessionTodoStore(session)
102
+ return TodoContext(get_todos=store.get, set_todos=store.set)
103
+
104
+
105
+ def get_current_file_tracker() -> FileTracker | None:
91
106
  """Return the current file tracker mapping for this tool context."""
92
107
 
93
108
  return current_file_tracker_var.get()
@@ -1,4 +1,5 @@
1
- from typing import Callable, TypeVar
1
+ from collections.abc import Callable
2
+ from typing import TypeVar
2
3
 
3
4
  from klaude_code.core.tool.sub_agent_tool import SubAgentTool
4
5
  from klaude_code.core.tool.tool_abc import ToolABC
@@ -61,18 +62,17 @@ def load_agent_tools(
61
62
 
62
63
  if sub_agent_type is not None:
63
64
  profile = get_sub_agent_profile(sub_agent_type)
64
- if not profile.enabled_for_model(model_name):
65
- return []
66
65
  return get_tool_schemas(list(profile.tool_set))
67
66
 
68
67
  # Main agent tools
69
68
  if "gpt-5" in model_name:
70
- tool_names = [tools.BASH, tools.READ, tools.APPLY_PATCH, tools.UPDATE_PLAN]
69
+ tool_names = [tools.BASH, tools.READ, tools.APPLY_PATCH, tools.MOVE, tools.UPDATE_PLAN]
71
70
  elif "gemini-3" in model_name:
72
- tool_names = [tools.BASH, tools.READ, tools.EDIT, tools.WRITE]
71
+ tool_names = [tools.BASH, tools.READ, tools.EDIT, tools.WRITE, tools.MOVE]
73
72
  else:
74
- tool_names = [tools.BASH, tools.READ, tools.EDIT, tools.WRITE, tools.TODO_WRITE]
73
+ tool_names = [tools.BASH, tools.READ, tools.EDIT, tools.WRITE, tools.MOVE, tools.TODO_WRITE]
75
74
 
76
75
  tool_names.extend(sub_agent_tool_names(enabled_only=True, model_name=model_name))
77
- tool_names.extend([tools.SKILL, tools.MERMAID, tools.MEMORY])
76
+ tool_names.extend([tools.SKILL, tools.MERMAID])
77
+ # tool_names.extend([tools.MEMORY])
78
78
  return get_tool_schemas(tool_names)
@@ -3,10 +3,10 @@ from collections.abc import AsyncGenerator, Callable, Iterable, Sequence
3
3
  from dataclasses import dataclass
4
4
 
5
5
  from klaude_code import const
6
- from klaude_code.core.tool.tool_abc import ToolABC
6
+ from klaude_code.core.tool.report_back_tool import ReportBackTool
7
+ from klaude_code.core.tool.tool_abc import ToolABC, ToolConcurrencyPolicy
7
8
  from klaude_code.core.tool.truncation import truncate_tool_output
8
- from klaude_code.protocol import model
9
- from klaude_code.protocol.sub_agent import is_sub_agent_tool
9
+ from klaude_code.protocol import model, tools
10
10
 
11
11
 
12
12
  async def run_tool(tool_call: model.ToolCallItem, registry: dict[str, type[ToolABC]]) -> model.ToolResultItem:
@@ -19,6 +19,13 @@ async def run_tool(tool_call: model.ToolCallItem, registry: dict[str, type[ToolA
19
19
  Returns:
20
20
  The result of the tool execution.
21
21
  """
22
+ # Special handling for report_back tool (not registered in global registry)
23
+ if tool_call.name == tools.REPORT_BACK:
24
+ tool_result = await ReportBackTool.call(tool_call.arguments)
25
+ tool_result.call_id = tool_call.call_id
26
+ tool_result.tool_name = tool_call.name
27
+ return tool_result
28
+
22
29
  if tool_call.name not in registry:
23
30
  return model.ToolResultItem(
24
31
  call_id=tool_call.call_id,
@@ -34,13 +41,10 @@ async def run_tool(tool_call: model.ToolCallItem, registry: dict[str, type[ToolA
34
41
  truncation_result = truncate_tool_output(tool_result.output, tool_call)
35
42
  tool_result.output = truncation_result.output
36
43
  if truncation_result.was_truncated and truncation_result.saved_file_path:
37
- tool_result.ui_extra = model.ToolResultUIExtra(
38
- type=model.ToolResultUIExtraType.TRUNCATION,
39
- truncation=model.TruncationUIExtra(
40
- saved_file_path=truncation_result.saved_file_path,
41
- original_length=truncation_result.original_length,
42
- truncated_length=truncation_result.truncated_length,
43
- ),
44
+ tool_result.ui_extra = model.TruncationUIExtra(
45
+ saved_file_path=truncation_result.saved_file_path,
46
+ original_length=truncation_result.original_length,
47
+ truncated_length=truncation_result.truncated_length,
44
48
  )
45
49
  return tool_result
46
50
  except asyncio.CancelledError:
@@ -84,8 +88,8 @@ class ToolExecutor:
84
88
  """Execute and coordinate a batch of tool calls for a single turn.
85
89
 
86
90
  The executor is responsible for:
87
- - Partitioning tool calls into regular tools and sub-agent tools
88
- - Running regular tools sequentially and sub-agent tools concurrently
91
+ - Partitioning tool calls into sequential and concurrent tools
92
+ - Running sequential tools one by one and concurrent tools in parallel
89
93
  - Emitting ToolCall/ToolResult events and tool side-effect events
90
94
  - Tracking unfinished calls so `cancel()` can synthesize cancellation results
91
95
  """
@@ -101,9 +105,9 @@ class ToolExecutor:
101
105
 
102
106
  self._unfinished_calls: dict[str, model.ToolCallItem] = {}
103
107
  self._call_event_emitted: set[str] = set()
104
- self._sub_agent_tasks: set[asyncio.Task[list[ToolExecutorEvent]]] = set()
108
+ self._concurrent_tasks: set[asyncio.Task[list[ToolExecutorEvent]]] = set()
105
109
 
106
- async def run_tools(self, tool_calls: list[model.ToolCallItem]) -> AsyncGenerator[ToolExecutorEvent, None]:
110
+ async def run_tools(self, tool_calls: list[model.ToolCallItem]) -> AsyncGenerator[ToolExecutorEvent]:
107
111
  """Run the given tool calls and yield execution events.
108
112
 
109
113
  Tool calls are partitioned into regular tools and sub-agent tools. Regular tools
@@ -114,10 +118,10 @@ class ToolExecutor:
114
118
  for tool_call in tool_calls:
115
119
  self._unfinished_calls[tool_call.call_id] = tool_call
116
120
 
117
- regular_tool_calls, sub_agent_tool_calls = self._partition_tool_calls(tool_calls)
121
+ sequential_tool_calls, concurrent_tool_calls = self._partition_tool_calls(tool_calls)
118
122
 
119
- # Run regular tools sequentially.
120
- for tool_call in regular_tool_calls:
123
+ # Run sequential tools one by one.
124
+ for tool_call in sequential_tool_calls:
121
125
  tool_call_event = self._build_tool_call_started(tool_call)
122
126
  self._call_event_emitted.add(tool_call.call_id)
123
127
  yield tool_call_event
@@ -131,16 +135,16 @@ class ToolExecutor:
131
135
  for exec_event in result_events:
132
136
  yield exec_event
133
137
 
134
- # Run sub-agent tools concurrently.
135
- if sub_agent_tool_calls:
138
+ # Run concurrent tools (sub-agents, web tools) in parallel.
139
+ if concurrent_tool_calls:
136
140
  execution_tasks: list[asyncio.Task[list[ToolExecutorEvent]]] = []
137
- for tool_call in sub_agent_tool_calls:
141
+ for tool_call in concurrent_tool_calls:
138
142
  tool_call_event = self._build_tool_call_started(tool_call)
139
143
  self._call_event_emitted.add(tool_call.call_id)
140
144
  yield tool_call_event
141
145
 
142
146
  task = asyncio.create_task(self._run_single_tool_call(tool_call))
143
- self._register_sub_agent_task(task)
147
+ self._register_concurrent_task(task)
144
148
  execution_tasks.append(task)
145
149
 
146
150
  for task in asyncio.as_completed(execution_tasks):
@@ -160,7 +164,7 @@ class ToolExecutor:
160
164
  def cancel(self) -> Iterable[ToolExecutorEvent]:
161
165
  """Cancel unfinished tool calls and synthesize error results.
162
166
 
163
- - Cancels any running sub-agent tool tasks so they stop emitting events.
167
+ - Cancels any running concurrent tool tasks so they stop emitting events.
164
168
  - For each unfinished tool call, yields a ToolExecutionCallStarted (if not
165
169
  already emitted for this turn) followed by a ToolExecutionResult with
166
170
  error status and a standard cancellation output. The corresponding
@@ -169,11 +173,11 @@ class ToolExecutor:
169
173
 
170
174
  events_to_yield: list[ToolExecutorEvent] = []
171
175
 
172
- # Cancel running sub-agent tool tasks.
173
- for task in list(self._sub_agent_tasks):
176
+ # Cancel running concurrent tool tasks.
177
+ for task in list(self._concurrent_tasks):
174
178
  if not task.done():
175
179
  task.cancel()
176
- self._sub_agent_tasks.clear()
180
+ self._concurrent_tasks.clear()
177
181
 
178
182
  if not self._unfinished_calls:
179
183
  return events_to_yield
@@ -198,26 +202,30 @@ class ToolExecutor:
198
202
 
199
203
  return events_to_yield
200
204
 
201
- def _register_sub_agent_task(self, task: asyncio.Task[list[ToolExecutorEvent]]) -> None:
202
- self._sub_agent_tasks.add(task)
205
+ def _register_concurrent_task(self, task: asyncio.Task[list[ToolExecutorEvent]]) -> None:
206
+ self._concurrent_tasks.add(task)
203
207
 
204
208
  def _cleanup(completed: asyncio.Task[list[ToolExecutorEvent]]) -> None:
205
- self._sub_agent_tasks.discard(completed)
209
+ self._concurrent_tasks.discard(completed)
206
210
 
207
211
  task.add_done_callback(_cleanup)
208
212
 
209
- @staticmethod
210
213
  def _partition_tool_calls(
214
+ self,
211
215
  tool_calls: list[model.ToolCallItem],
212
216
  ) -> tuple[list[model.ToolCallItem], list[model.ToolCallItem]]:
213
- regular_tool_calls: list[model.ToolCallItem] = []
214
- sub_agent_tool_calls: list[model.ToolCallItem] = []
217
+ sequential_tool_calls: list[model.ToolCallItem] = []
218
+ concurrent_tool_calls: list[model.ToolCallItem] = []
215
219
  for tool_call in tool_calls:
216
- if is_sub_agent_tool(tool_call.name):
217
- sub_agent_tool_calls.append(tool_call)
220
+ tool_cls = self._registry.get(tool_call.name)
221
+ policy = (
222
+ tool_cls.metadata().concurrency_policy if tool_cls is not None else ToolConcurrencyPolicy.SEQUENTIAL
223
+ )
224
+ if policy == ToolConcurrencyPolicy.CONCURRENT:
225
+ concurrent_tool_calls.append(tool_call)
218
226
  else:
219
- regular_tool_calls.append(tool_call)
220
- return regular_tool_calls, sub_agent_tool_calls
227
+ sequential_tool_calls.append(tool_call)
228
+ return sequential_tool_calls, concurrent_tool_calls
221
229
 
222
230
  def _build_tool_call_started(self, tool_call: model.ToolCallItem) -> ToolExecutionCallStarted:
223
231
  return ToolExecutionCallStarted(tool_call=tool_call)
@@ -244,7 +252,7 @@ class ToolExecutor:
244
252
  for side_effect in side_effects:
245
253
  if side_effect == model.ToolSideEffect.TODO_CHANGE:
246
254
  todos: list[model.TodoItem] | None = None
247
- if tool_result.ui_extra is not None and tool_result.ui_extra.todo_list is not None:
255
+ if isinstance(tool_result.ui_extra, model.TodoListUIExtra):
248
256
  todos = tool_result.ui_extra.todo_list.todos
249
257
  if todos is not None:
250
258
  side_effect_events.append(ToolExecutionTodoChange(todos=todos))
@@ -21,16 +21,22 @@ class TruncationResult:
21
21
  truncated_length: int = 0
22
22
 
23
23
 
24
+ FILE_SAVED_PATTERN = re.compile(r"<file_saved>([^<]+)</file_saved>")
25
+
26
+
27
+ def _extract_saved_file_path(output: str) -> str | None:
28
+ """Extract file path from <file_saved> tag if present."""
29
+ match = FILE_SAVED_PATTERN.search(output)
30
+ return match.group(1) if match else None
31
+
32
+
24
33
  def _extract_url_filename(url: str) -> str:
25
34
  """Extract a safe filename from a URL."""
26
35
  parsed = urlparse(url)
27
36
  # Combine host and path for a meaningful filename
28
37
  host = parsed.netloc.replace(".", "_").replace(":", "_")
29
38
  path = parsed.path.strip("/").replace("/", "_")
30
- if path:
31
- name = f"{host}_{path}"
32
- else:
33
- name = host
39
+ name = f"{host}_{path}" if path else host
34
40
  # Sanitize: keep only alphanumeric, underscore, hyphen
35
41
  name = re.sub(r"[^a-zA-Z0-9_\-]", "_", name)
36
42
  # Limit length
@@ -106,33 +112,42 @@ class SmartTruncationStrategy(TruncationStrategy):
106
112
  file_path = self.truncation_dir / filename
107
113
  file_path.write_text(output, encoding="utf-8")
108
114
  return str(file_path)
109
- except (OSError, IOError):
115
+ except OSError:
110
116
  return None
111
117
 
112
118
  def truncate(self, output: str, tool_call: model.ToolCallItem | None = None) -> TruncationResult:
119
+ if tool_call and tool_call.name == tools.READ:
120
+ # Do not truncate Read tool outputs
121
+ return TruncationResult(output=output, was_truncated=False, original_length=len(output))
122
+
113
123
  original_length = len(output)
114
124
 
115
125
  if original_length <= self.max_length:
116
126
  return TruncationResult(output=output, was_truncated=False, original_length=original_length)
117
127
 
118
- # Save full output to file
119
- saved_file_path = self._save_to_file(output, tool_call)
128
+ # Check if file was already saved (e.g., by WebFetch)
129
+ existing_file_path = _extract_saved_file_path(output)
130
+ saved_file_path = existing_file_path or self._save_to_file(output, tool_call)
131
+
132
+ # Strip existing <file_saved> tag to avoid duplication in head/tail
133
+ content_to_truncate = FILE_SAVED_PATTERN.sub("", output).lstrip("\n") if existing_file_path else output
134
+ content_length = len(content_to_truncate)
120
135
 
121
- truncated_length = original_length - self.head_chars - self.tail_chars
122
- head_content = output[: self.head_chars]
123
- tail_content = output[-self.tail_chars :]
136
+ truncated_length = content_length - self.head_chars - self.tail_chars
137
+ head_content = content_to_truncate[: self.head_chars]
138
+ tail_content = content_to_truncate[-self.tail_chars :]
124
139
 
125
140
  # Build truncated output with file info
126
141
  if saved_file_path:
127
142
  header = (
128
- f"<system-reminder>Output truncated: {truncated_length} chars hidden. "
129
- f"Full tool output saved to {saved_file_path}. "
130
- f"Use Read with limit+offset or rg/grep to inspect.\n"
143
+ f"<system-reminder>Output truncated ({truncated_length} chars hidden) to reduce context usage. "
144
+ f"Full content saved to <file_saved>{saved_file_path}</file_saved>. "
145
+ f"Use Read(offset, limit) or rg to inspect if needed. "
131
146
  f"Showing first {self.head_chars} and last {self.tail_chars} chars:</system-reminder>\n\n"
132
147
  )
133
148
  else:
134
149
  header = (
135
- f"<system-reminder>Output truncated: {truncated_length} chars hidden. "
150
+ f"<system-reminder>Output truncated ({truncated_length} chars hidden) to reduce context usage. "
136
151
  f"Showing first {self.head_chars} and last {self.tail_chars} chars:</system-reminder>\n\n"
137
152
  )
138
153
 
@@ -17,5 +17,48 @@ Diagrams are especially valuable for visualizing:
17
17
  - Sequence and timing of operations
18
18
  - Decision trees and conditional logic
19
19
 
20
+ # Syntax
21
+ - ALWAYS wrap node labels in double quotes, especially when they contain spaces, special characters, or non-ASCII text
22
+ - This applies to all node types: regular nodes, subgraph titles, and edge labels
23
+
24
+ Examples:
25
+ ```mermaid
26
+ graph LR
27
+ A["User Input"] --> B["Process Data"]
28
+ B --> C["Output Result"]
29
+ ```
30
+
31
+ ```mermaid
32
+ flowchart TD
33
+ subgraph auth["Authentication Module"]
34
+ login["Login Service"]
35
+ oauth["OAuth Provider"]
36
+ end
37
+ ```
38
+
39
+ ```mermaid
40
+ sequenceDiagram
41
+ participant client as "Web Client"
42
+ participant server as "API Server"
43
+ client ->> server: "Send Request"
44
+ ```
45
+
20
46
  # Styling
21
47
  - When defining custom classDefs, always define fill color, stroke color, and text color ("fill", "stroke", "color") explicitly
48
+ - Use colors to distinguish node types and improve readability
49
+
50
+ ## Color Palette
51
+ - Cyan #e0f0f0 - information, data flow
52
+ - Green #e0f0e0 - success, completion
53
+ - Blue #e0e8f5 - primary actions, main flow
54
+ - Purple #ede0f5 - highlights, special nodes
55
+ - Orange #f5ebe0 - warnings, pending
56
+ - Red #f5e0e0 - errors, critical
57
+ - Grey #e8e8e8 - neutral elements
58
+ - Yellow #f5f5e0 - attention, notes
59
+
60
+ Example:
61
+ ```mermaid
62
+ classDef primary fill:#e0e8f5,stroke:#3078C5,color:#1a1a1a
63
+ classDef success fill:#e0f0e0,stroke:#00875f,color:#1a1a1a
64
+ ```
@@ -31,7 +31,7 @@ class MermaidTool(ToolABC):
31
31
  "type": "object",
32
32
  "properties": {
33
33
  "code": {
34
- "description": "The Mermaid diagram code to render (DO NOT override with custom colors or other styles, DO NOT use HTML tags in node labels)",
34
+ "description": "The Mermaid diagram code to render (DO NOT use HTML tags in node labels)",
35
35
  "type": "string",
36
36
  },
37
37
  },
@@ -49,10 +49,7 @@ class MermaidTool(ToolABC):
49
49
 
50
50
  link = cls._build_link(args.code)
51
51
  line_count = cls._count_lines(args.code)
52
- ui_extra = model.ToolResultUIExtra(
53
- type=model.ToolResultUIExtraType.MERMAID_LINK,
54
- mermaid_link=model.MermaidLinkUIExtra(link=link, line_count=line_count),
55
- )
52
+ ui_extra = model.MermaidLinkUIExtra(code=args.code, link=link, line_count=line_count)
56
53
  output = f"Mermaid diagram rendered successfully ({line_count} lines)."
57
54
  return model.ToolResultItem(status="success", output=output, ui_extra=ui_extra)
58
55