klaude-code 2.0.0__py3-none-any.whl → 2.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- klaude_code/cli/cost_cmd.py +1 -1
- klaude_code/cli/runtime.py +1 -8
- klaude_code/command/debug_cmd.py +1 -1
- klaude_code/command/export_online_cmd.py +4 -4
- klaude_code/command/fork_session_cmd.py +6 -6
- klaude_code/command/help_cmd.py +1 -1
- klaude_code/command/model_cmd.py +1 -1
- klaude_code/command/registry.py +10 -1
- klaude_code/command/release_notes_cmd.py +1 -1
- klaude_code/command/resume_cmd.py +2 -2
- klaude_code/command/status_cmd.py +2 -2
- klaude_code/command/terminal_setup_cmd.py +2 -2
- klaude_code/command/thinking_cmd.py +1 -1
- klaude_code/config/assets/builtin_config.yaml +4 -0
- klaude_code/const.py +5 -3
- klaude_code/core/executor.py +15 -36
- klaude_code/core/reminders.py +55 -68
- klaude_code/core/tool/__init__.py +0 -2
- klaude_code/core/tool/file/edit_tool.py +3 -2
- klaude_code/core/tool/todo/todo_write_tool.py +1 -2
- klaude_code/core/tool/tool_registry.py +3 -3
- klaude_code/protocol/events.py +1 -0
- klaude_code/protocol/message.py +3 -11
- klaude_code/protocol/model.py +79 -13
- klaude_code/protocol/op.py +0 -13
- klaude_code/protocol/op_handler.py +0 -5
- klaude_code/protocol/sub_agent/explore.py +0 -15
- klaude_code/protocol/sub_agent/task.py +1 -1
- klaude_code/protocol/sub_agent/web.py +1 -17
- klaude_code/protocol/tools.py +0 -1
- klaude_code/ui/modes/exec/display.py +2 -3
- klaude_code/ui/modes/repl/display.py +1 -1
- klaude_code/ui/modes/repl/event_handler.py +2 -10
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +5 -1
- klaude_code/ui/modes/repl/key_bindings.py +135 -1
- klaude_code/ui/modes/repl/renderer.py +2 -1
- klaude_code/ui/renderers/bash_syntax.py +36 -4
- klaude_code/ui/renderers/common.py +8 -6
- klaude_code/ui/renderers/developer.py +113 -97
- klaude_code/ui/renderers/metadata.py +28 -15
- klaude_code/ui/renderers/tools.py +17 -59
- klaude_code/ui/rich/markdown.py +69 -11
- klaude_code/ui/rich/theme.py +22 -17
- klaude_code/ui/terminal/selector.py +36 -17
- {klaude_code-2.0.0.dist-info → klaude_code-2.0.2.dist-info}/METADATA +1 -1
- {klaude_code-2.0.0.dist-info → klaude_code-2.0.2.dist-info}/RECORD +48 -50
- klaude_code/core/tool/file/move_tool.md +0 -41
- klaude_code/core/tool/file/move_tool.py +0 -435
- {klaude_code-2.0.0.dist-info → klaude_code-2.0.2.dist-info}/WHEEL +0 -0
- {klaude_code-2.0.0.dist-info → klaude_code-2.0.2.dist-info}/entry_points.txt +0 -0
klaude_code/cli/cost_cmd.py
CHANGED
klaude_code/cli/runtime.py
CHANGED
|
@@ -25,7 +25,6 @@ from klaude_code.ui.modes.repl import build_repl_status_snapshot
|
|
|
25
25
|
from klaude_code.ui.modes.repl.input_prompt_toolkit import REPLStatusSnapshot
|
|
26
26
|
from klaude_code.ui.terminal.color import is_light_terminal_background
|
|
27
27
|
from klaude_code.ui.terminal.control import install_sigint_double_press_exit, start_esc_interrupt_monitor
|
|
28
|
-
from klaude_code.ui.terminal.progress_bar import OSC94States, emit_osc94
|
|
29
28
|
|
|
30
29
|
|
|
31
30
|
class PrintCapable(Protocol):
|
|
@@ -269,11 +268,6 @@ async def cleanup_app_components(components: AppComponents) -> None:
|
|
|
269
268
|
await components.event_queue.put(events.EndEvent())
|
|
270
269
|
await components.display_task
|
|
271
270
|
finally:
|
|
272
|
-
# Always attempt to clear Ghostty progress bar and restore cursor visibility
|
|
273
|
-
# Best-effort only; never fail cleanup due to OSC errors
|
|
274
|
-
with contextlib.suppress(Exception):
|
|
275
|
-
emit_osc94(OSC94States.HIDDEN)
|
|
276
|
-
|
|
277
271
|
# Ensure the terminal cursor is visible even if Rich's Status spinner
|
|
278
272
|
# did not get a chance to stop cleanly (e.g. on KeyboardInterrupt).
|
|
279
273
|
# If this fails the shell can still recover via `reset`/`stty sane`.
|
|
@@ -452,8 +446,7 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
|
|
|
452
446
|
print(MSG, file=sys.stderr)
|
|
453
447
|
|
|
454
448
|
def _hide_progress() -> None:
|
|
455
|
-
|
|
456
|
-
emit_osc94(OSC94States.HIDDEN)
|
|
449
|
+
return
|
|
457
450
|
|
|
458
451
|
restore_sigint = install_sigint_double_press_exit(_show_toast_once, _hide_progress)
|
|
459
452
|
|
klaude_code/command/debug_cmd.py
CHANGED
|
@@ -72,7 +72,7 @@ class DebugCommand(CommandABC):
|
|
|
72
72
|
session_id=agent.session.id,
|
|
73
73
|
item=message.DeveloperMessage(
|
|
74
74
|
parts=message.text_parts_from_str(content),
|
|
75
|
-
|
|
75
|
+
ui_extra=model.build_command_output_extra(self.name),
|
|
76
76
|
),
|
|
77
77
|
)
|
|
78
78
|
]
|
|
@@ -42,7 +42,7 @@ class ExportOnlineCommand(CommandABC):
|
|
|
42
42
|
session_id=agent.session.id,
|
|
43
43
|
item=message.DeveloperMessage(
|
|
44
44
|
parts=message.text_parts_from_str("surge.sh CLI not found. Install with: npm install -g surge"),
|
|
45
|
-
|
|
45
|
+
ui_extra=model.build_command_output_extra(self.name, is_error=True),
|
|
46
46
|
),
|
|
47
47
|
)
|
|
48
48
|
return CommandResult(events=[event])
|
|
@@ -59,7 +59,7 @@ class ExportOnlineCommand(CommandABC):
|
|
|
59
59
|
session_id=agent.session.id,
|
|
60
60
|
item=message.DeveloperMessage(
|
|
61
61
|
parts=message.text_parts_from_str(f"Not logged in to surge.sh. Please run: {login_cmd}"),
|
|
62
|
-
|
|
62
|
+
ui_extra=model.build_command_output_extra(self.name, is_error=True),
|
|
63
63
|
),
|
|
64
64
|
)
|
|
65
65
|
return CommandResult(events=[event])
|
|
@@ -73,7 +73,7 @@ class ExportOnlineCommand(CommandABC):
|
|
|
73
73
|
session_id=agent.session.id,
|
|
74
74
|
item=message.DeveloperMessage(
|
|
75
75
|
parts=message.text_parts_from_str(f"Session deployed to: {url}"),
|
|
76
|
-
|
|
76
|
+
ui_extra=model.build_command_output_extra(self.name),
|
|
77
77
|
),
|
|
78
78
|
)
|
|
79
79
|
return CommandResult(events=[event])
|
|
@@ -84,7 +84,7 @@ class ExportOnlineCommand(CommandABC):
|
|
|
84
84
|
session_id=agent.session.id,
|
|
85
85
|
item=message.DeveloperMessage(
|
|
86
86
|
parts=message.text_parts_from_str(f"Failed to deploy session: {exc}\n{traceback.format_exc()}"),
|
|
87
|
-
|
|
87
|
+
ui_extra=model.build_command_output_extra(self.name, is_error=True),
|
|
88
88
|
),
|
|
89
89
|
)
|
|
90
90
|
return CommandResult(events=[event])
|
|
@@ -207,7 +207,7 @@ class ForkSessionCommand(CommandABC):
|
|
|
207
207
|
session_id=agent.session.id,
|
|
208
208
|
item=message.DeveloperMessage(
|
|
209
209
|
parts=message.text_parts_from_str("(no messages to fork)"),
|
|
210
|
-
|
|
210
|
+
ui_extra=model.build_command_output_extra(self.name),
|
|
211
211
|
),
|
|
212
212
|
)
|
|
213
213
|
return CommandResult(events=[event], persist_user_input=False, persist_events=False)
|
|
@@ -227,8 +227,8 @@ class ForkSessionCommand(CommandABC):
|
|
|
227
227
|
session_id=agent.session.id,
|
|
228
228
|
item=message.DeveloperMessage(
|
|
229
229
|
parts=message.text_parts_from_str(f"Session forked successfully. New session id: {new_session.id}"),
|
|
230
|
-
|
|
231
|
-
|
|
230
|
+
ui_extra=model.build_command_output_extra(
|
|
231
|
+
self.name,
|
|
232
232
|
ui_extra=model.SessionIdUIExtra(session_id=new_session.id),
|
|
233
233
|
),
|
|
234
234
|
),
|
|
@@ -243,7 +243,7 @@ class ForkSessionCommand(CommandABC):
|
|
|
243
243
|
session_id=agent.session.id,
|
|
244
244
|
item=message.DeveloperMessage(
|
|
245
245
|
parts=message.text_parts_from_str("(fork cancelled)"),
|
|
246
|
-
|
|
246
|
+
ui_extra=model.build_command_output_extra(self.name),
|
|
247
247
|
),
|
|
248
248
|
)
|
|
249
249
|
return CommandResult(events=[event], persist_user_input=False, persist_events=False)
|
|
@@ -264,8 +264,8 @@ class ForkSessionCommand(CommandABC):
|
|
|
264
264
|
parts=message.text_parts_from_str(
|
|
265
265
|
f"Session forked ({fork_description}). New session id: {new_session.id}"
|
|
266
266
|
),
|
|
267
|
-
|
|
268
|
-
|
|
267
|
+
ui_extra=model.build_command_output_extra(
|
|
268
|
+
self.name,
|
|
269
269
|
ui_extra=model.SessionIdUIExtra(session_id=new_session.id),
|
|
270
270
|
),
|
|
271
271
|
),
|
klaude_code/command/help_cmd.py
CHANGED
|
@@ -43,7 +43,7 @@ Available slash commands:"""
|
|
|
43
43
|
session_id=agent.session.id,
|
|
44
44
|
item=message.DeveloperMessage(
|
|
45
45
|
parts=message.text_parts_from_str("\n".join(lines)),
|
|
46
|
-
|
|
46
|
+
ui_extra=model.build_command_output_extra(self.name),
|
|
47
47
|
),
|
|
48
48
|
)
|
|
49
49
|
|
klaude_code/command/model_cmd.py
CHANGED
|
@@ -76,7 +76,7 @@ class ModelCommand(CommandABC):
|
|
|
76
76
|
session_id=agent.session.id,
|
|
77
77
|
item=message.DeveloperMessage(
|
|
78
78
|
parts=message.text_parts_from_str("(no change)"),
|
|
79
|
-
|
|
79
|
+
ui_extra=model.build_command_output_extra(self.name),
|
|
80
80
|
),
|
|
81
81
|
)
|
|
82
82
|
]
|
klaude_code/command/registry.py
CHANGED
|
@@ -183,6 +183,15 @@ async def dispatch_command(user_input: message.UserInputPayload, agent: Agent, *
|
|
|
183
183
|
if isinstance(command_identifier, commands.CommandName)
|
|
184
184
|
else None
|
|
185
185
|
)
|
|
186
|
+
ui_extra = (
|
|
187
|
+
model.build_command_output_extra(
|
|
188
|
+
command_output.command_name,
|
|
189
|
+
ui_extra=command_output.ui_extra,
|
|
190
|
+
is_error=command_output.is_error,
|
|
191
|
+
)
|
|
192
|
+
if command_output is not None
|
|
193
|
+
else None
|
|
194
|
+
)
|
|
186
195
|
return CommandResult(
|
|
187
196
|
events=[
|
|
188
197
|
events.DeveloperMessageEvent(
|
|
@@ -191,7 +200,7 @@ async def dispatch_command(user_input: message.UserInputPayload, agent: Agent, *
|
|
|
191
200
|
parts=message.text_parts_from_str(
|
|
192
201
|
f"Command {command_identifier} error: [{e.__class__.__name__}] {e!s}"
|
|
193
202
|
),
|
|
194
|
-
|
|
203
|
+
ui_extra=ui_extra,
|
|
195
204
|
),
|
|
196
205
|
)
|
|
197
206
|
]
|
|
@@ -77,7 +77,7 @@ class ReleaseNotesCommand(CommandABC):
|
|
|
77
77
|
session_id=agent.session.id,
|
|
78
78
|
item=message.DeveloperMessage(
|
|
79
79
|
parts=message.text_parts_from_str(content),
|
|
80
|
-
|
|
80
|
+
ui_extra=model.build_command_output_extra(self.name),
|
|
81
81
|
),
|
|
82
82
|
)
|
|
83
83
|
|
|
@@ -92,7 +92,7 @@ class ResumeCommand(CommandABC):
|
|
|
92
92
|
parts=message.text_parts_from_str(
|
|
93
93
|
"Cannot resume: current session already has messages. Use `klaude -r` to start a new instance with session selection."
|
|
94
94
|
),
|
|
95
|
-
|
|
95
|
+
ui_extra=model.build_command_output_extra(self.name, is_error=True),
|
|
96
96
|
),
|
|
97
97
|
)
|
|
98
98
|
return CommandResult(events=[event], persist_user_input=False, persist_events=False)
|
|
@@ -103,7 +103,7 @@ class ResumeCommand(CommandABC):
|
|
|
103
103
|
session_id=agent.session.id,
|
|
104
104
|
item=message.DeveloperMessage(
|
|
105
105
|
parts=message.text_parts_from_str("(no session selected)"),
|
|
106
|
-
|
|
106
|
+
ui_extra=model.build_command_output_extra(self.name),
|
|
107
107
|
),
|
|
108
108
|
)
|
|
109
109
|
return CommandResult(events=[event], persist_user_input=False, persist_events=False)
|
|
@@ -141,8 +141,8 @@ class StatusCommand(CommandABC):
|
|
|
141
141
|
session_id=session.id,
|
|
142
142
|
item=message.DeveloperMessage(
|
|
143
143
|
parts=message.text_parts_from_str(format_status_content(aggregated)),
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
ui_extra=model.build_command_output_extra(
|
|
145
|
+
self.name,
|
|
146
146
|
ui_extra=model.SessionStatusUIExtra(
|
|
147
147
|
usage=aggregated.total,
|
|
148
148
|
task_count=aggregated.task_count,
|
|
@@ -229,7 +229,7 @@ class TerminalSetupCommand(CommandABC):
|
|
|
229
229
|
session_id=agent.session.id,
|
|
230
230
|
item=message.DeveloperMessage(
|
|
231
231
|
parts=message.text_parts_from_str(msg),
|
|
232
|
-
|
|
232
|
+
ui_extra=model.build_command_output_extra(self.name),
|
|
233
233
|
),
|
|
234
234
|
)
|
|
235
235
|
]
|
|
@@ -243,7 +243,7 @@ class TerminalSetupCommand(CommandABC):
|
|
|
243
243
|
session_id=agent.session.id,
|
|
244
244
|
item=message.DeveloperMessage(
|
|
245
245
|
parts=message.text_parts_from_str(msg),
|
|
246
|
-
|
|
246
|
+
ui_extra=model.build_command_output_extra(self.name, is_error=True),
|
|
247
247
|
),
|
|
248
248
|
)
|
|
249
249
|
]
|
|
@@ -82,7 +82,7 @@ class ThinkingCommand(CommandABC):
|
|
|
82
82
|
session_id=agent.session.id,
|
|
83
83
|
item=message.DeveloperMessage(
|
|
84
84
|
parts=message.text_parts_from_str("(no change)"),
|
|
85
|
-
|
|
85
|
+
ui_extra=model.build_command_output_extra(self.name),
|
|
86
86
|
),
|
|
87
87
|
)
|
|
88
88
|
]
|
klaude_code/const.py
CHANGED
|
@@ -122,13 +122,14 @@ TOOL_OUTPUT_TRUNCATION_DIR = "/tmp/klaude" # Directory for saving full truncate
|
|
|
122
122
|
TAB_EXPAND_WIDTH = 8 # Tab expansion width for text rendering
|
|
123
123
|
DIFF_PREFIX_WIDTH = 4 # Width of line number prefix in diff display
|
|
124
124
|
MAX_DIFF_LINES = 500 # Maximum lines to show in diff output
|
|
125
|
-
INVALID_TOOL_CALL_MAX_LENGTH =
|
|
126
|
-
TRUNCATE_DISPLAY_MAX_LINE_LENGTH =
|
|
127
|
-
TRUNCATE_DISPLAY_MAX_LINES =
|
|
125
|
+
INVALID_TOOL_CALL_MAX_LENGTH = 200 # Maximum length for invalid tool call display
|
|
126
|
+
TRUNCATE_DISPLAY_MAX_LINE_LENGTH = 200 # Maximum line length for truncated display
|
|
127
|
+
TRUNCATE_DISPLAY_MAX_LINES = 4 # Maximum lines for truncated display
|
|
128
128
|
MIN_HIDDEN_LINES_FOR_INDICATOR = 5 # Minimum hidden lines before showing truncation indicator
|
|
129
129
|
SUB_AGENT_RESULT_MAX_LINES = 10 # Maximum lines for sub-agent result display
|
|
130
130
|
TRUNCATE_HEAD_MAX_LINES = 2 # Maximum lines for sub-agent error display
|
|
131
131
|
BASH_OUTPUT_PANEL_THRESHOLD = 10 # Bash output line threshold for CodePanel display
|
|
132
|
+
BASH_MULTILINE_STRING_TRUNCATE_MAX_LINES = 2 # Max lines shown for heredoc / multiline string tokens in bash tool calls
|
|
132
133
|
URL_TRUNCATE_MAX_LENGTH = 400 # Maximum length for URL truncation in display
|
|
133
134
|
QUERY_DISPLAY_TRUNCATE_LENGTH = 80 # Maximum length for search query display
|
|
134
135
|
NOTIFY_COMPACT_LIMIT = 160 # Maximum length for notification body text
|
|
@@ -141,6 +142,7 @@ NOTIFY_COMPACT_LIMIT = 160 # Maximum length for notification body text
|
|
|
141
142
|
UI_REFRESH_RATE_FPS = 10 # UI refresh rate (frames per second)
|
|
142
143
|
CROP_ABOVE_LIVE_REFRESH_PER_SECOND = 4.0 # CropAboveLive default refresh rate
|
|
143
144
|
MARKDOWN_STREAM_LIVE_REPAINT_ENABLED = True # Enable live area for streaming markdown
|
|
145
|
+
MARKDOWN_STREAM_SYNCHRONIZED_OUTPUT_ENABLED = True # Use terminal "Synchronized Output" to reduce flicker
|
|
144
146
|
STREAM_MAX_HEIGHT_SHRINK_RESET_LINES = 20 # Reset stream height ceiling after this shrinkage
|
|
145
147
|
MARKDOWN_LEFT_MARGIN = 2 # Left margin (columns) for markdown rendering
|
|
146
148
|
MARKDOWN_RIGHT_MARGIN = 2 # Right margin (columns) for markdown rendering
|
klaude_code/core/executor.py
CHANGED
|
@@ -176,35 +176,6 @@ class ExecutorContext:
|
|
|
176
176
|
"""Initialize an agent for a session and replay history to UI."""
|
|
177
177
|
await self._ensure_agent(operation.session_id)
|
|
178
178
|
|
|
179
|
-
async def handle_user_input(self, operation: op.UserInputOperation) -> None:
|
|
180
|
-
"""Handle a user input operation.
|
|
181
|
-
|
|
182
|
-
Core should not parse slash commands. The UI/CLI layer is responsible for
|
|
183
|
-
turning raw user input into one or more operations.
|
|
184
|
-
"""
|
|
185
|
-
|
|
186
|
-
if operation.session_id is None:
|
|
187
|
-
raise ValueError("session_id cannot be None")
|
|
188
|
-
|
|
189
|
-
session_id = operation.session_id
|
|
190
|
-
agent = await self._ensure_agent(session_id)
|
|
191
|
-
user_input = operation.input
|
|
192
|
-
|
|
193
|
-
await self.emit_event(
|
|
194
|
-
events.UserMessageEvent(content=user_input.text, session_id=session_id, images=user_input.images)
|
|
195
|
-
)
|
|
196
|
-
agent.session.append_history(
|
|
197
|
-
[message.UserMessage(parts=message.parts_from_text_and_images(user_input.text, user_input.images))]
|
|
198
|
-
)
|
|
199
|
-
|
|
200
|
-
await self.handle_run_agent(
|
|
201
|
-
op.RunAgentOperation(
|
|
202
|
-
id=operation.id,
|
|
203
|
-
session_id=session_id,
|
|
204
|
-
input=user_input,
|
|
205
|
-
)
|
|
206
|
-
)
|
|
207
|
-
|
|
208
179
|
async def handle_run_agent(self, operation: op.RunAgentOperation) -> None:
|
|
209
180
|
agent = await self._ensure_agent(operation.session_id)
|
|
210
181
|
existing_active = self.task_manager.get(operation.id)
|
|
@@ -234,7 +205,7 @@ class ExecutorContext:
|
|
|
234
205
|
default_note = " (saved as default)" if operation.save_as_default else ""
|
|
235
206
|
developer_item = message.DeveloperMessage(
|
|
236
207
|
parts=message.text_parts_from_str(f"Switched to: {llm_config.model}{default_note}"),
|
|
237
|
-
|
|
208
|
+
ui_extra=model.build_command_output_extra(commands.CommandName.MODEL),
|
|
238
209
|
)
|
|
239
210
|
agent.session.append_history([developer_item])
|
|
240
211
|
await self.emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
|
|
@@ -243,7 +214,11 @@ class ExecutorContext:
|
|
|
243
214
|
self._on_model_change(llm_client.model_name)
|
|
244
215
|
|
|
245
216
|
if operation.emit_welcome_event:
|
|
246
|
-
await self.emit_event(
|
|
217
|
+
await self.emit_event(
|
|
218
|
+
events.WelcomeEvent(
|
|
219
|
+
llm_config=llm_config, work_dir=str(agent.session.work_dir), show_klaude_code_info=False
|
|
220
|
+
)
|
|
221
|
+
)
|
|
247
222
|
|
|
248
223
|
async def handle_change_thinking(self, operation: op.ChangeThinkingOperation) -> None:
|
|
249
224
|
"""Handle a change thinking operation.
|
|
@@ -279,13 +254,17 @@ class ExecutorContext:
|
|
|
279
254
|
if operation.emit_switch_message:
|
|
280
255
|
developer_item = message.DeveloperMessage(
|
|
281
256
|
parts=message.text_parts_from_str(f"Thinking changed: {current} -> {new_status}"),
|
|
282
|
-
|
|
257
|
+
ui_extra=model.build_command_output_extra(commands.CommandName.THINKING),
|
|
283
258
|
)
|
|
284
259
|
agent.session.append_history([developer_item])
|
|
285
260
|
await self.emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
|
|
286
261
|
|
|
287
262
|
if operation.emit_welcome_event:
|
|
288
|
-
await self.emit_event(
|
|
263
|
+
await self.emit_event(
|
|
264
|
+
events.WelcomeEvent(
|
|
265
|
+
work_dir=str(agent.session.work_dir), llm_config=config, show_klaude_code_info=False
|
|
266
|
+
)
|
|
267
|
+
)
|
|
289
268
|
|
|
290
269
|
async def handle_clear_session(self, operation: op.ClearSessionOperation) -> None:
|
|
291
270
|
agent = await self._ensure_agent(operation.session_id)
|
|
@@ -297,7 +276,7 @@ class ExecutorContext:
|
|
|
297
276
|
|
|
298
277
|
developer_item = message.DeveloperMessage(
|
|
299
278
|
parts=message.text_parts_from_str("started new conversation"),
|
|
300
|
-
|
|
279
|
+
ui_extra=model.build_command_output_extra(commands.CommandName.CLEAR),
|
|
301
280
|
)
|
|
302
281
|
await self.emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
|
|
303
282
|
await self.emit_event(
|
|
@@ -348,7 +327,7 @@ class ExecutorContext:
|
|
|
348
327
|
await asyncio.to_thread(self._open_file, output_path)
|
|
349
328
|
developer_item = message.DeveloperMessage(
|
|
350
329
|
parts=message.text_parts_from_str(f"Session exported and opened: {output_path}"),
|
|
351
|
-
|
|
330
|
+
ui_extra=model.build_command_output_extra(commands.CommandName.EXPORT),
|
|
352
331
|
)
|
|
353
332
|
agent.session.append_history([developer_item])
|
|
354
333
|
await self.emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
|
|
@@ -357,7 +336,7 @@ class ExecutorContext:
|
|
|
357
336
|
|
|
358
337
|
developer_item = message.DeveloperMessage(
|
|
359
338
|
parts=message.text_parts_from_str(f"Failed to export session: {exc}\n{traceback.format_exc()}"),
|
|
360
|
-
|
|
339
|
+
ui_extra=model.build_command_output_extra(commands.CommandName.EXPORT, is_error=True),
|
|
361
340
|
)
|
|
362
341
|
agent.session.append_history([developer_item])
|
|
363
342
|
await self.emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
|
klaude_code/core/reminders.py
CHANGED
|
@@ -71,10 +71,13 @@ def get_at_patterns_with_source(session: Session) -> list[AtPatternSource]:
|
|
|
71
71
|
patterns.append(AtPatternSource(pattern=path_str, mentioned_in=None))
|
|
72
72
|
break
|
|
73
73
|
|
|
74
|
-
if isinstance(item, message.DeveloperMessage) and item.
|
|
75
|
-
for
|
|
76
|
-
|
|
77
|
-
|
|
74
|
+
if isinstance(item, message.DeveloperMessage) and item.ui_extra:
|
|
75
|
+
for ui_item in item.ui_extra.items:
|
|
76
|
+
if not isinstance(ui_item, model.MemoryLoadedUIItem):
|
|
77
|
+
continue
|
|
78
|
+
for mem in ui_item.files:
|
|
79
|
+
for pattern in mem.mentioned_patterns:
|
|
80
|
+
patterns.append(AtPatternSource(pattern=pattern, mentioned_in=mem.path))
|
|
78
81
|
return patterns
|
|
79
82
|
|
|
80
83
|
|
|
@@ -113,7 +116,8 @@ def _is_tracked_file_unchanged(session: Session, path: str) -> bool:
|
|
|
113
116
|
async def _load_at_file_recursive(
|
|
114
117
|
session: Session,
|
|
115
118
|
pattern: str,
|
|
116
|
-
|
|
119
|
+
at_ops: list[model.AtFileOp],
|
|
120
|
+
formatted_blocks: list[str],
|
|
117
121
|
collected_images: list[message.ImageURLPart],
|
|
118
122
|
visited: set[str],
|
|
119
123
|
base_dir: Path | None = None,
|
|
@@ -135,14 +139,15 @@ async def _load_at_file_recursive(
|
|
|
135
139
|
args = ReadTool.ReadArguments(file_path=path_str)
|
|
136
140
|
tool_result = await ReadTool.call_with_args(args)
|
|
137
141
|
images = [part for part in tool_result.parts if isinstance(part, message.ImageURLPart)]
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
|
|
143
|
+
tool_args = args.model_dump_json(exclude_none=True)
|
|
144
|
+
formatted_blocks.append(
|
|
145
|
+
f"""Called the {tools.READ} tool with the following input: {tool_args}
|
|
146
|
+
Result of calling the {tools.READ} tool:
|
|
147
|
+
{tool_result.output_text}
|
|
148
|
+
"""
|
|
145
149
|
)
|
|
150
|
+
at_ops.append(model.AtFileOp(operation="Read", path=path_str, mentioned_in=mentioned_in))
|
|
146
151
|
if images:
|
|
147
152
|
collected_images.extend(images)
|
|
148
153
|
|
|
@@ -155,7 +160,8 @@ async def _load_at_file_recursive(
|
|
|
155
160
|
await _load_at_file_recursive(
|
|
156
161
|
session,
|
|
157
162
|
nested,
|
|
158
|
-
|
|
163
|
+
at_ops,
|
|
164
|
+
formatted_blocks,
|
|
159
165
|
collected_images,
|
|
160
166
|
visited,
|
|
161
167
|
base_dir=path.parent,
|
|
@@ -165,13 +171,15 @@ async def _load_at_file_recursive(
|
|
|
165
171
|
quoted_path = shlex.quote(path_str)
|
|
166
172
|
args = BashTool.BashArguments(command=f"ls {quoted_path}")
|
|
167
173
|
tool_result = await BashTool.call_with_args(args)
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
+
|
|
175
|
+
tool_args = args.model_dump_json(exclude_none=True)
|
|
176
|
+
formatted_blocks.append(
|
|
177
|
+
f"""Called the {tools.BASH} tool with the following input: {tool_args}
|
|
178
|
+
Result of calling the {tools.BASH} tool:
|
|
179
|
+
{tool_result.output_text}
|
|
180
|
+
"""
|
|
174
181
|
)
|
|
182
|
+
at_ops.append(model.AtFileOp(operation="List", path=path_str + "/", mentioned_in=mentioned_in))
|
|
175
183
|
finally:
|
|
176
184
|
reset_tool_context(context_token)
|
|
177
185
|
|
|
@@ -184,7 +192,8 @@ async def at_file_reader_reminder(
|
|
|
184
192
|
if not at_pattern_sources:
|
|
185
193
|
return None
|
|
186
194
|
|
|
187
|
-
|
|
195
|
+
at_ops: list[model.AtFileOp] = []
|
|
196
|
+
formatted_blocks: list[str] = []
|
|
188
197
|
collected_images: list[message.ImageURLPart] = []
|
|
189
198
|
visited: set[str] = set()
|
|
190
199
|
|
|
@@ -192,30 +201,23 @@ async def at_file_reader_reminder(
|
|
|
192
201
|
await _load_at_file_recursive(
|
|
193
202
|
session,
|
|
194
203
|
source.pattern,
|
|
195
|
-
|
|
204
|
+
at_ops,
|
|
205
|
+
formatted_blocks,
|
|
196
206
|
collected_images,
|
|
197
207
|
visited,
|
|
198
208
|
mentioned_in=source.mentioned_in,
|
|
199
209
|
)
|
|
200
210
|
|
|
201
|
-
if len(
|
|
211
|
+
if len(formatted_blocks) == 0:
|
|
202
212
|
return None
|
|
203
213
|
|
|
204
|
-
at_files_str = "\n\n".join(
|
|
205
|
-
[
|
|
206
|
-
f"""Called the {result.tool_name} tool with the following input: {result.tool_args}
|
|
207
|
-
Result of calling the {result.tool_name} tool:
|
|
208
|
-
{result.result}
|
|
209
|
-
"""
|
|
210
|
-
for result in at_files.values()
|
|
211
|
-
]
|
|
212
|
-
)
|
|
214
|
+
at_files_str = "\n\n".join(formatted_blocks)
|
|
213
215
|
return message.DeveloperMessage(
|
|
214
216
|
parts=message.parts_from_text_and_images(
|
|
215
217
|
f"""<system-reminder>{at_files_str}\n</system-reminder>""",
|
|
216
218
|
collected_images or None,
|
|
217
219
|
),
|
|
218
|
-
|
|
220
|
+
ui_extra=model.DeveloperUIExtra(items=[model.AtFileOpsUIItem(ops=at_ops)]),
|
|
219
221
|
)
|
|
220
222
|
|
|
221
223
|
|
|
@@ -299,7 +301,7 @@ Here are the existing contents of your todo list:
|
|
|
299
301
|
|
|
300
302
|
{model.todo_list_str(session.todos)}</system-reminder>"""
|
|
301
303
|
),
|
|
302
|
-
|
|
304
|
+
ui_extra=model.DeveloperUIExtra(items=[model.TodoReminderUIItem(reason="not_used_recently")]),
|
|
303
305
|
)
|
|
304
306
|
|
|
305
307
|
if session.need_todo_not_used_cooldown_counter > 0:
|
|
@@ -357,10 +359,12 @@ async def file_changed_externally_reminder(
|
|
|
357
359
|
)
|
|
358
360
|
return message.DeveloperMessage(
|
|
359
361
|
parts=message.parts_from_text_and_images(
|
|
360
|
-
f"""<system-reminder>{changed_files_str}""",
|
|
362
|
+
f"""<system-reminder>{changed_files_str}</system-reminder>""",
|
|
361
363
|
collected_images or None,
|
|
362
364
|
),
|
|
363
|
-
|
|
365
|
+
ui_extra=model.DeveloperUIExtra(
|
|
366
|
+
items=[model.ExternalFileChangesUIItem(paths=[file_path for file_path, _, _ in changed_files])]
|
|
367
|
+
),
|
|
364
368
|
)
|
|
365
369
|
|
|
366
370
|
return None
|
|
@@ -426,7 +430,7 @@ async def image_reminder(session: Session) -> message.DeveloperMessage | None:
|
|
|
426
430
|
parts=message.text_parts_from_str(
|
|
427
431
|
f"<system-reminder>User attached {image_count} image{'s' if image_count > 1 else ''} in their message. Make sure to analyze and reference these images as needed.</system-reminder>"
|
|
428
432
|
),
|
|
429
|
-
|
|
433
|
+
ui_extra=model.DeveloperUIExtra(items=[model.UserImagesUIItem(count=image_count)]),
|
|
430
434
|
)
|
|
431
435
|
|
|
432
436
|
|
|
@@ -456,7 +460,7 @@ async def skill_reminder(session: Session) -> message.DeveloperMessage | None:
|
|
|
456
460
|
|
|
457
461
|
return message.DeveloperMessage(
|
|
458
462
|
parts=message.text_parts_from_str(content),
|
|
459
|
-
|
|
463
|
+
ui_extra=model.DeveloperUIExtra(items=[model.SkillActivatedUIItem(name=skill.name)]),
|
|
460
464
|
)
|
|
461
465
|
|
|
462
466
|
|
|
@@ -487,7 +491,7 @@ async def memory_reminder(session: Session) -> message.DeveloperMessage | None:
|
|
|
487
491
|
path_str = str(memory_path)
|
|
488
492
|
if memory_path.exists() and memory_path.is_file() and not _is_memory_loaded(session, path_str):
|
|
489
493
|
try:
|
|
490
|
-
text = memory_path.read_text()
|
|
494
|
+
text = memory_path.read_text(encoding="utf-8", errors="replace")
|
|
491
495
|
_mark_memory_loaded(session, path_str)
|
|
492
496
|
memories.append(Memory(path=path_str, instruction=instruction, content=text))
|
|
493
497
|
except (PermissionError, UnicodeDecodeError, OSError):
|
|
@@ -496,32 +500,19 @@ async def memory_reminder(session: Session) -> message.DeveloperMessage | None:
|
|
|
496
500
|
memories_str = "\n\n".join(
|
|
497
501
|
[f"Contents of {memory.path} ({memory.instruction}):\n\n{memory.content}" for memory in memories]
|
|
498
502
|
)
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
if patterns:
|
|
504
|
-
memory_mentioned[memory.path] = patterns
|
|
505
|
-
|
|
503
|
+
loaded_files = [
|
|
504
|
+
model.MemoryFileLoaded(path=memory.path, mentioned_patterns=_extract_at_patterns(memory.content))
|
|
505
|
+
for memory in memories
|
|
506
|
+
]
|
|
506
507
|
return message.DeveloperMessage(
|
|
507
508
|
parts=message.text_parts_from_str(
|
|
508
|
-
f"""<system-reminder>
|
|
509
|
+
f"""<system-reminder>
|
|
510
|
+
Loaded memory files. Follow these instructions. Do not mention them to the user unless explicitly asked.
|
|
509
511
|
|
|
510
|
-
# claudeMd
|
|
511
|
-
Codebase and user instructions are shown below. Be sure to adhere to these instructions. IMPORTANT: These instructions OVERRIDE any default behavior and you MUST follow them exactly as written.
|
|
512
512
|
{memories_str}
|
|
513
|
-
|
|
514
|
-
#important-instruction-reminders
|
|
515
|
-
Do what has been asked; nothing more, nothing less.
|
|
516
|
-
NEVER create files unless they're absolutely necessary for achieving your goal.
|
|
517
|
-
ALWAYS prefer editing an existing file to creating a new one.
|
|
518
|
-
NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.
|
|
519
|
-
|
|
520
|
-
IMPORTANT: this context may or may not be relevant to your tasks. You should not respond to this context unless it is highly relevant to your task.
|
|
521
513
|
</system-reminder>"""
|
|
522
514
|
),
|
|
523
|
-
|
|
524
|
-
memory_mentioned=memory_mentioned or None,
|
|
515
|
+
ui_extra=model.DeveloperUIExtra(items=[model.MemoryLoadedUIItem(files=loaded_files)]),
|
|
525
516
|
)
|
|
526
517
|
return None
|
|
527
518
|
|
|
@@ -572,7 +563,7 @@ async def last_path_memory_reminder(
|
|
|
572
563
|
continue
|
|
573
564
|
if mem_path.exists() and mem_path.is_file():
|
|
574
565
|
try:
|
|
575
|
-
text = mem_path.read_text()
|
|
566
|
+
text = mem_path.read_text(encoding="utf-8", errors="replace")
|
|
576
567
|
except (PermissionError, UnicodeDecodeError, OSError):
|
|
577
568
|
continue
|
|
578
569
|
_mark_memory_loaded(session, mem_path_str)
|
|
@@ -589,20 +580,16 @@ async def last_path_memory_reminder(
|
|
|
589
580
|
memories_str = "\n\n".join(
|
|
590
581
|
[f"Contents of {memory.path} ({memory.instruction}):\n\n{memory.content}" for memory in memories]
|
|
591
582
|
)
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
if patterns:
|
|
597
|
-
memory_mentioned[memory.path] = patterns
|
|
598
|
-
|
|
583
|
+
loaded_files = [
|
|
584
|
+
model.MemoryFileLoaded(path=memory.path, mentioned_patterns=_extract_at_patterns(memory.content))
|
|
585
|
+
for memory in memories
|
|
586
|
+
]
|
|
599
587
|
return message.DeveloperMessage(
|
|
600
588
|
parts=message.text_parts_from_str(
|
|
601
589
|
f"""<system-reminder>{memories_str}
|
|
602
590
|
</system-reminder>"""
|
|
603
591
|
),
|
|
604
|
-
|
|
605
|
-
memory_mentioned=memory_mentioned or None,
|
|
592
|
+
ui_extra=model.DeveloperUIExtra(items=[model.MemoryLoadedUIItem(files=loaded_files)]),
|
|
606
593
|
)
|
|
607
594
|
|
|
608
595
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from .file.apply_patch import DiffError, process_patch
|
|
2
2
|
from .file.apply_patch_tool import ApplyPatchTool
|
|
3
3
|
from .file.edit_tool import EditTool
|
|
4
|
-
from .file.move_tool import MoveTool
|
|
5
4
|
from .file.read_tool import ReadTool
|
|
6
5
|
from .file.write_tool import WriteTool
|
|
7
6
|
from .report_back_tool import ReportBackTool
|
|
@@ -36,7 +35,6 @@ __all__ = [
|
|
|
36
35
|
"EditTool",
|
|
37
36
|
"FileTracker",
|
|
38
37
|
"MermaidTool",
|
|
39
|
-
"MoveTool",
|
|
40
38
|
"ReadTool",
|
|
41
39
|
"ReportBackTool",
|
|
42
40
|
"SafetyCheckResult",
|