klaude-code 1.2.18__py3-none-any.whl → 1.2.20__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 (74) hide show
  1. klaude_code/cli/main.py +42 -22
  2. klaude_code/cli/runtime.py +46 -2
  3. klaude_code/{version.py → cli/self_update.py} +110 -2
  4. klaude_code/command/__init__.py +1 -3
  5. klaude_code/command/clear_cmd.py +5 -4
  6. klaude_code/command/command_abc.py +5 -40
  7. klaude_code/command/debug_cmd.py +2 -2
  8. klaude_code/command/diff_cmd.py +2 -1
  9. klaude_code/command/export_cmd.py +14 -49
  10. klaude_code/command/export_online_cmd.py +10 -4
  11. klaude_code/command/help_cmd.py +2 -1
  12. klaude_code/command/model_cmd.py +7 -5
  13. klaude_code/command/prompt-jj-workspace.md +18 -0
  14. klaude_code/command/prompt_command.py +16 -9
  15. klaude_code/command/refresh_cmd.py +3 -2
  16. klaude_code/command/registry.py +98 -28
  17. klaude_code/command/release_notes_cmd.py +2 -1
  18. klaude_code/command/status_cmd.py +2 -1
  19. klaude_code/command/terminal_setup_cmd.py +2 -1
  20. klaude_code/command/thinking_cmd.py +6 -4
  21. klaude_code/core/executor.py +187 -180
  22. klaude_code/core/manager/sub_agent_manager.py +3 -0
  23. klaude_code/core/prompt.py +4 -1
  24. klaude_code/core/prompts/prompt-sub-agent-explore.md +14 -2
  25. klaude_code/core/prompts/prompt-sub-agent-web.md +3 -3
  26. klaude_code/core/reminders.py +70 -26
  27. klaude_code/core/task.py +13 -12
  28. klaude_code/core/tool/__init__.py +2 -0
  29. klaude_code/core/tool/file/apply_patch_tool.py +3 -1
  30. klaude_code/core/tool/file/edit_tool.py +7 -5
  31. klaude_code/core/tool/file/multi_edit_tool.py +7 -5
  32. klaude_code/core/tool/file/read_tool.md +1 -1
  33. klaude_code/core/tool/file/read_tool.py +8 -4
  34. klaude_code/core/tool/file/write_tool.py +8 -6
  35. klaude_code/core/tool/memory/skill_loader.py +12 -10
  36. klaude_code/core/tool/shell/bash_tool.py +89 -17
  37. klaude_code/core/tool/sub_agent_tool.py +5 -1
  38. klaude_code/core/tool/tool_abc.py +18 -0
  39. klaude_code/core/tool/tool_context.py +6 -6
  40. klaude_code/core/tool/tool_registry.py +1 -1
  41. klaude_code/core/tool/tool_runner.py +7 -7
  42. klaude_code/core/tool/web/web_fetch_tool.py +77 -22
  43. klaude_code/core/tool/web/web_search_tool.py +5 -1
  44. klaude_code/llm/anthropic/client.py +25 -9
  45. klaude_code/llm/openai_compatible/client.py +5 -2
  46. klaude_code/llm/openrouter/client.py +7 -3
  47. klaude_code/llm/responses/client.py +6 -1
  48. klaude_code/protocol/model.py +8 -1
  49. klaude_code/protocol/op.py +47 -0
  50. klaude_code/protocol/op_handler.py +25 -1
  51. klaude_code/protocol/sub_agent/web.py +1 -1
  52. klaude_code/session/codec.py +71 -0
  53. klaude_code/session/export.py +21 -11
  54. klaude_code/session/session.py +186 -322
  55. klaude_code/session/store.py +215 -0
  56. klaude_code/session/templates/export_session.html +48 -47
  57. klaude_code/ui/modes/repl/completers.py +211 -71
  58. klaude_code/ui/modes/repl/event_handler.py +7 -23
  59. klaude_code/ui/modes/repl/input_prompt_toolkit.py +5 -7
  60. klaude_code/ui/modes/repl/renderer.py +2 -2
  61. klaude_code/ui/renderers/common.py +54 -0
  62. klaude_code/ui/renderers/developer.py +2 -3
  63. klaude_code/ui/renderers/errors.py +1 -1
  64. klaude_code/ui/renderers/metadata.py +10 -1
  65. klaude_code/ui/renderers/tools.py +3 -4
  66. klaude_code/ui/rich/__init__.py +10 -1
  67. klaude_code/ui/rich/cjk_wrap.py +228 -0
  68. klaude_code/ui/rich/status.py +0 -1
  69. klaude_code/ui/utils/common.py +0 -18
  70. {klaude_code-1.2.18.dist-info → klaude_code-1.2.20.dist-info}/METADATA +18 -2
  71. {klaude_code-1.2.18.dist-info → klaude_code-1.2.20.dist-info}/RECORD +73 -70
  72. klaude_code/ui/utils/debouncer.py +0 -42
  73. {klaude_code-1.2.18.dist-info → klaude_code-1.2.20.dist-info}/WHEEL +0 -0
  74. {klaude_code-1.2.18.dist-info → klaude_code-1.2.20.dist-info}/entry_points.txt +0 -0
@@ -8,10 +8,12 @@ handling operations submitted from the CLI and coordinating with agents.
8
8
  from __future__ import annotations
9
9
 
10
10
  import asyncio
11
- from collections.abc import Awaitable, Callable
11
+ import subprocess
12
+ from collections.abc import Callable
12
13
  from dataclasses import dataclass
14
+ from pathlib import Path
13
15
 
14
- from klaude_code.command import InputAction, InputActionType, dispatch_command
16
+ from klaude_code.command import dispatch_command
15
17
  from klaude_code.config import load_config
16
18
  from klaude_code.core.agent import Agent, DefaultModelProfileProvider, ModelProfileProvider
17
19
  from klaude_code.core.manager import LLMClients, SubAgentManager
@@ -20,6 +22,7 @@ from klaude_code.llm.registry import create_llm_client
20
22
  from klaude_code.protocol import commands, events, model, op
21
23
  from klaude_code.protocol.op_handler import OperationHandler
22
24
  from klaude_code.protocol.sub_agent import SubAgentResult
25
+ from klaude_code.session.export import build_export_html, get_default_export_path
23
26
  from klaude_code.session.session import Session
24
27
  from klaude_code.trace import DebugType, log_debug
25
28
 
@@ -76,164 +79,6 @@ class TaskManager:
76
79
  self._tasks.clear()
77
80
 
78
81
 
79
- class InputActionExecutor:
80
- """Execute input actions returned by the command dispatcher.
81
-
82
- This helper encapsulates the logic for running the main agent task,
83
- applying model changes, and clearing conversations so that
84
- :class:`ExecutorContext` stays focused on operation dispatch.
85
- """
86
-
87
- def __init__(
88
- self,
89
- task_manager: TaskManager,
90
- sub_agent_manager: SubAgentManager,
91
- model_profile_provider: ModelProfileProvider,
92
- emit_event: Callable[[events.Event], Awaitable[None]],
93
- ) -> None:
94
- self._task_manager = task_manager
95
- self._sub_agent_manager = sub_agent_manager
96
- self._model_profile_provider = model_profile_provider
97
- self._emit_event = emit_event
98
-
99
- async def run(self, action: InputAction, operation: op.UserInputOperation, agent: Agent) -> None:
100
- """Dispatch and execute a single input action."""
101
-
102
- if operation.session_id is None:
103
- raise ValueError("session_id cannot be None for input actions")
104
-
105
- session_id = operation.session_id
106
-
107
- if action.type == InputActionType.RUN_AGENT:
108
- await self._run_agent_action(action, operation, agent, session_id)
109
- return
110
-
111
- if action.type == InputActionType.CHANGE_MODEL:
112
- if not action.model_name:
113
- raise ValueError("ChangeModel action requires model_name")
114
-
115
- await self._apply_model_change(agent, action.model_name)
116
- return
117
-
118
- if action.type == InputActionType.CLEAR:
119
- await self._apply_clear(agent)
120
- return
121
-
122
- raise ValueError(f"Unsupported input action type: {action.type}")
123
-
124
- async def _run_agent_action(
125
- self,
126
- action: InputAction,
127
- operation: op.UserInputOperation,
128
- agent: Agent,
129
- session_id: str,
130
- ) -> None:
131
- task_input = model.UserInputPayload(text=action.text, images=operation.input.images)
132
-
133
- existing_active = self._task_manager.get(operation.id)
134
- if existing_active is not None and not existing_active.task.done():
135
- raise RuntimeError(f"Active task already registered for operation {operation.id}")
136
-
137
- task: asyncio.Task[None] = asyncio.create_task(
138
- self._run_agent_task(agent, task_input, operation.id, session_id)
139
- )
140
- self._task_manager.register(operation.id, task, session_id)
141
-
142
- async def _run_agent_task(
143
- self,
144
- agent: Agent,
145
- user_input: model.UserInputPayload,
146
- task_id: str,
147
- session_id: str,
148
- ) -> None:
149
- """Run the main agent task and forward events to the UI."""
150
-
151
- try:
152
- log_debug(
153
- f"Starting agent task {task_id} for session {session_id}",
154
- style="green",
155
- debug_type=DebugType.EXECUTION,
156
- )
157
-
158
- async def _runner(state: model.SubAgentState) -> SubAgentResult:
159
- return await self._sub_agent_manager.run_sub_agent(agent, state)
160
-
161
- token = current_run_subtask_callback.set(_runner)
162
- try:
163
- async for event in agent.run_task(user_input):
164
- await self._emit_event(event)
165
- finally:
166
- current_run_subtask_callback.reset(token)
167
-
168
- except asyncio.CancelledError:
169
- log_debug(
170
- f"Agent task {task_id} was cancelled",
171
- style="yellow",
172
- debug_type=DebugType.EXECUTION,
173
- )
174
- await self._emit_event(events.TaskFinishEvent(session_id=session_id, task_result="task cancelled"))
175
-
176
- except Exception as e:
177
- import traceback
178
-
179
- log_debug(
180
- f"Agent task {task_id} failed: {e!s}",
181
- style="red",
182
- debug_type=DebugType.EXECUTION,
183
- )
184
- log_debug(traceback.format_exc(), style="red", debug_type=DebugType.EXECUTION)
185
- await self._emit_event(
186
- events.ErrorEvent(
187
- error_message=f"Agent task failed: [{e.__class__.__name__}] {e!s}",
188
- can_retry=False,
189
- )
190
- )
191
-
192
- finally:
193
- self._task_manager.remove(task_id)
194
- log_debug(
195
- f"Cleaned up agent task {task_id}",
196
- style="cyan",
197
- debug_type=DebugType.EXECUTION,
198
- )
199
-
200
- async def _apply_model_change(self, agent: Agent, model_name: str) -> None:
201
- """Change the model used by the active agent and notify the UI."""
202
-
203
- config = load_config()
204
- if config is None:
205
- raise ValueError("Configuration must be initialized before changing model")
206
-
207
- llm_config = config.get_model_config(model_name)
208
- llm_client = create_llm_client(llm_config)
209
- agent.set_model_profile(self._model_profile_provider.build_profile(llm_client))
210
-
211
- developer_item = model.DeveloperMessageItem(
212
- content=f"switched to model: {model_name}",
213
- command_output=model.CommandOutput(command_name=commands.CommandName.MODEL),
214
- )
215
- agent.session.append_history([developer_item])
216
-
217
- await self._emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
218
- await self._emit_event(events.WelcomeEvent(llm_config=llm_config, work_dir=str(agent.session.work_dir)))
219
-
220
- async def _apply_clear(self, agent: Agent) -> None:
221
- """Start a new conversation for the agent and notify the UI."""
222
-
223
- new_session = Session(work_dir=agent.session.work_dir)
224
- new_session.model_name = agent.session.model_name
225
-
226
- agent.session = new_session
227
- agent.session.save()
228
-
229
- developer_item = model.DeveloperMessageItem(
230
- content="started new conversation",
231
- command_output=model.CommandOutput(command_name=commands.CommandName.CLEAR),
232
- )
233
-
234
- await self._emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
235
-
236
-
237
82
  class ExecutorContext:
238
83
  """
239
84
  Context object providing shared state and operations for the executor.
@@ -249,6 +94,7 @@ class ExecutorContext:
249
94
  event_queue: asyncio.Queue[events.Event],
250
95
  llm_clients: LLMClients,
251
96
  model_profile_provider: ModelProfileProvider | None = None,
97
+ on_model_change: Callable[[str], None] | None = None,
252
98
  ):
253
99
  self.event_queue: asyncio.Queue[events.Event] = event_queue
254
100
  self.llm_clients: LLMClients = llm_clients
@@ -258,12 +104,7 @@ class ExecutorContext:
258
104
 
259
105
  self.task_manager = TaskManager()
260
106
  self.sub_agent_manager = SubAgentManager(event_queue, llm_clients, resolved_profile_provider)
261
- self._action_executor = InputActionExecutor(
262
- task_manager=self.task_manager,
263
- sub_agent_manager=self.sub_agent_manager,
264
- model_profile_provider=resolved_profile_provider,
265
- emit_event=self.emit_event,
266
- )
107
+ self._on_model_change = on_model_change
267
108
  self._agent: Agent | None = None
268
109
 
269
110
  async def emit_event(self, event: events.Event) -> None:
@@ -302,6 +143,13 @@ class ExecutorContext:
302
143
 
303
144
  session = Session.create() if session_id is None else Session.load(session_id)
304
145
 
146
+ if (
147
+ session.model_thinking is not None
148
+ and session.model_name
149
+ and session.model_name == self.llm_clients.main.model_name
150
+ ):
151
+ self.llm_clients.main.get_llm_config().thinking = session.model_thinking
152
+
305
153
  profile = self.model_profile_provider.build_profile(self.llm_clients.main)
306
154
  agent = Agent(session=session, profile=profile)
307
155
 
@@ -328,7 +176,7 @@ class ExecutorContext:
328
176
  await self._ensure_agent(operation.session_id)
329
177
 
330
178
  async def handle_user_input(self, operation: op.UserInputOperation) -> None:
331
- """Handle a user input operation by running it through an agent."""
179
+ """Handle a user input operation by dispatching it into operations."""
332
180
 
333
181
  if operation.session_id is None:
334
182
  raise ValueError("session_id cannot be None")
@@ -337,29 +185,187 @@ class ExecutorContext:
337
185
  agent = await self._ensure_agent(session_id)
338
186
  user_input = operation.input
339
187
 
340
- # emit user input event
188
+ # Emit the original user input to UI (even if the persisted text differs).
341
189
  await self.emit_event(
342
190
  events.UserMessageEvent(content=user_input.text, session_id=session_id, images=user_input.images)
343
191
  )
344
192
 
345
- result = await dispatch_command(user_input.text, agent)
193
+ result = await dispatch_command(user_input, agent, submission_id=operation.id)
194
+ ops: list[op.Operation] = list(result.operations or [])
346
195
 
347
- actions: list[InputAction] = list(result.actions or [])
196
+ run_ops = [candidate for candidate in ops if isinstance(candidate, op.RunAgentOperation)]
197
+ if len(run_ops) > 1:
198
+ raise ValueError("Multiple RunAgentOperation results are not supported")
348
199
 
349
- has_run_agent_action = any(action.type is InputActionType.RUN_AGENT for action in actions)
350
- if not has_run_agent_action:
351
- # No async agent task will run, append user message directly
352
- agent.session.append_history([model.UserMessageItem(content=user_input.text, images=user_input.images)])
200
+ persisted_user_input = run_ops[0].input if run_ops else user_input
201
+ agent.session.append_history(
202
+ [model.UserMessageItem(content=persisted_user_input.text, images=persisted_user_input.images)]
203
+ )
353
204
 
354
205
  if result.events:
355
- agent.session.append_history(
356
- [evt.item for evt in result.events if isinstance(evt, events.DeveloperMessageEvent)]
357
- )
358
206
  for evt in result.events:
207
+ if isinstance(evt, events.DeveloperMessageEvent):
208
+ agent.session.append_history([evt.item])
359
209
  await self.emit_event(evt)
360
210
 
361
- for action in actions:
362
- await self._action_executor.run(action, operation, agent)
211
+ for operation_item in ops:
212
+ await operation_item.execute(handler=self)
213
+
214
+ async def handle_run_agent(self, operation: op.RunAgentOperation) -> None:
215
+ agent = await self._ensure_agent(operation.session_id)
216
+ existing_active = self.task_manager.get(operation.id)
217
+ if existing_active is not None and not existing_active.task.done():
218
+ raise RuntimeError(f"Active task already registered for operation {operation.id}")
219
+ task: asyncio.Task[None] = asyncio.create_task(
220
+ self._run_agent_task(agent, operation.input, operation.id, operation.session_id)
221
+ )
222
+ self.task_manager.register(operation.id, task, operation.session_id)
223
+
224
+ async def handle_change_model(self, operation: op.ChangeModelOperation) -> None:
225
+ agent = await self._ensure_agent(operation.session_id)
226
+ config = load_config()
227
+ if config is None:
228
+ raise ValueError("Configuration must be initialized before changing model")
229
+
230
+ llm_config = config.get_model_config(operation.model_name)
231
+ llm_client = create_llm_client(llm_config)
232
+ agent.set_model_profile(self.model_profile_provider.build_profile(llm_client))
233
+
234
+ agent.session.model_config_name = operation.model_name
235
+ agent.session.model_thinking = llm_config.thinking
236
+
237
+ developer_item = model.DeveloperMessageItem(
238
+ content=f"switched to model: {operation.model_name}",
239
+ command_output=model.CommandOutput(command_name=commands.CommandName.MODEL),
240
+ )
241
+ agent.session.append_history([developer_item])
242
+
243
+ await self.emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
244
+ await self.emit_event(events.WelcomeEvent(llm_config=llm_config, work_dir=str(agent.session.work_dir)))
245
+
246
+ if self._on_model_change is not None:
247
+ self._on_model_change(llm_client.model_name)
248
+
249
+ async def handle_clear_session(self, operation: op.ClearSessionOperation) -> None:
250
+ agent = await self._ensure_agent(operation.session_id)
251
+ new_session = Session.create(work_dir=agent.session.work_dir)
252
+ new_session.model_name = agent.session.model_name
253
+ new_session.model_config_name = agent.session.model_config_name
254
+ new_session.model_thinking = agent.session.model_thinking
255
+ agent.session = new_session
256
+
257
+ developer_item = model.DeveloperMessageItem(
258
+ content="started new conversation",
259
+ command_output=model.CommandOutput(command_name=commands.CommandName.CLEAR),
260
+ )
261
+ await self.emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
262
+
263
+ async def handle_export_session(self, operation: op.ExportSessionOperation) -> None:
264
+ agent = await self._ensure_agent(operation.session_id)
265
+ try:
266
+ output_path = self._resolve_export_output_path(operation.output_path, agent.session)
267
+ html_doc = self._build_export_html(agent)
268
+ await asyncio.to_thread(output_path.parent.mkdir, parents=True, exist_ok=True)
269
+ await asyncio.to_thread(output_path.write_text, html_doc, "utf-8")
270
+ await asyncio.to_thread(self._open_file, output_path)
271
+ developer_item = model.DeveloperMessageItem(
272
+ content=f"Session exported and opened: {output_path}",
273
+ command_output=model.CommandOutput(command_name=commands.CommandName.EXPORT),
274
+ )
275
+ agent.session.append_history([developer_item])
276
+ await self.emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
277
+ except Exception as exc: # pragma: no cover
278
+ import traceback
279
+
280
+ developer_item = model.DeveloperMessageItem(
281
+ content=f"Failed to export session: {exc}\n{traceback.format_exc()}",
282
+ command_output=model.CommandOutput(command_name=commands.CommandName.EXPORT, is_error=True),
283
+ )
284
+ agent.session.append_history([developer_item])
285
+ await self.emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
286
+
287
+ async def _run_agent_task(
288
+ self,
289
+ agent: Agent,
290
+ user_input: model.UserInputPayload,
291
+ task_id: str,
292
+ session_id: str,
293
+ ) -> None:
294
+ try:
295
+ log_debug(
296
+ f"Starting agent task {task_id} for session {session_id}",
297
+ style="green",
298
+ debug_type=DebugType.EXECUTION,
299
+ )
300
+
301
+ async def _runner(state: model.SubAgentState) -> SubAgentResult:
302
+ return await self.sub_agent_manager.run_sub_agent(agent, state)
303
+
304
+ token = current_run_subtask_callback.set(_runner)
305
+ try:
306
+ async for event in agent.run_task(user_input):
307
+ await self.emit_event(event)
308
+ finally:
309
+ current_run_subtask_callback.reset(token)
310
+
311
+ except asyncio.CancelledError:
312
+ log_debug(
313
+ f"Agent task {task_id} was cancelled",
314
+ style="yellow",
315
+ debug_type=DebugType.EXECUTION,
316
+ )
317
+ await self.emit_event(events.TaskFinishEvent(session_id=session_id, task_result="task cancelled"))
318
+
319
+ except Exception as e:
320
+ import traceback
321
+
322
+ log_debug(
323
+ f"Agent task {task_id} failed: {e!s}",
324
+ style="red",
325
+ debug_type=DebugType.EXECUTION,
326
+ )
327
+ log_debug(traceback.format_exc(), style="red", debug_type=DebugType.EXECUTION)
328
+ await self.emit_event(
329
+ events.ErrorEvent(
330
+ error_message=f"Agent task failed: [{e.__class__.__name__}] {e!s}",
331
+ can_retry=False,
332
+ )
333
+ )
334
+ finally:
335
+ self.task_manager.remove(task_id)
336
+ log_debug(
337
+ f"Cleaned up agent task {task_id}",
338
+ style="cyan",
339
+ debug_type=DebugType.EXECUTION,
340
+ )
341
+
342
+ def _resolve_export_output_path(self, raw: str | None, session: Session) -> Path:
343
+ trimmed = (raw or "").strip()
344
+ if trimmed:
345
+ candidate = Path(trimmed).expanduser()
346
+ if not candidate.is_absolute():
347
+ candidate = Path(session.work_dir) / candidate
348
+ if candidate.suffix.lower() != ".html":
349
+ candidate = candidate.with_suffix(".html")
350
+ return candidate
351
+ return get_default_export_path(session)
352
+
353
+ def _build_export_html(self, agent: Agent) -> str:
354
+ profile = agent.profile
355
+ system_prompt = (profile.system_prompt if profile else "") or ""
356
+ tool_schemas = profile.tools if profile else []
357
+ model_name = profile.llm_client.model_name if profile else "unknown"
358
+ return build_export_html(agent.session, system_prompt, tool_schemas, model_name)
359
+
360
+ def _open_file(self, path: Path) -> None:
361
+ try:
362
+ subprocess.run(["open", str(path)], check=True)
363
+ except FileNotFoundError as exc: # pragma: no cover
364
+ msg = "`open` command not found; please open the HTML manually."
365
+ raise RuntimeError(msg) from exc
366
+ except subprocess.CalledProcessError as exc: # pragma: no cover
367
+ msg = f"Failed to open HTML with `open`: {exc}"
368
+ raise RuntimeError(msg) from exc
363
369
 
364
370
  async def handle_interrupt(self, operation: op.InterruptOperation) -> None:
365
371
  """Handle an interrupt by invoking agent.cancel() and cancelling tasks."""
@@ -439,8 +445,9 @@ class Executor:
439
445
  event_queue: asyncio.Queue[events.Event],
440
446
  llm_clients: LLMClients,
441
447
  model_profile_provider: ModelProfileProvider | None = None,
448
+ on_model_change: Callable[[str], None] | None = None,
442
449
  ):
443
- self.context = ExecutorContext(event_queue, llm_clients, model_profile_provider)
450
+ self.context = ExecutorContext(event_queue, llm_clients, model_profile_provider, on_model_change)
444
451
  self.submission_queue: asyncio.Queue[op.Submission] = asyncio.Queue()
445
452
  # Track completion events for all submissions (not just those with ActiveTask)
446
453
  self._completion_events: dict[str, asyncio.Event] = {}
@@ -80,6 +80,9 @@ Only the content passed to `report_back` will be returned to user.\
80
80
  result: str = ""
81
81
  task_metadata: model.TaskMetadata | None = None
82
82
  sub_agent_input = model.UserInputPayload(text=state.sub_agent_prompt, images=None)
83
+ child_session.append_history(
84
+ [model.UserMessageItem(content=sub_agent_input.text, images=sub_agent_input.images)]
85
+ )
83
86
  async for event in child_agent.run_task(sub_agent_input):
84
87
  # Capture TaskFinishEvent content for return
85
88
  if isinstance(event, events.TaskFinishEvent):
@@ -12,6 +12,7 @@ COMMAND_DESCRIPTIONS: dict[str, str] = {
12
12
  "fd": "simple and fast alternative to find",
13
13
  "tree": "directory listing as a tree",
14
14
  "sg": "ast-grep - AST-aware code search",
15
+ "jj": "jujutsu - Git-compatible version control system",
15
16
  }
16
17
 
17
18
  # Mapping from logical prompt keys to resource file paths under the core/prompt directory.
@@ -57,18 +58,20 @@ def _build_env_info(model_name: str) -> str:
57
58
  cwd = Path.cwd()
58
59
  today = datetime.datetime.now().strftime("%Y-%m-%d")
59
60
  is_git_repo = (cwd / ".git").exists()
61
+ is_empty_dir = not any(cwd.iterdir())
60
62
 
61
63
  available_tools: list[str] = []
62
64
  for command, desc in COMMAND_DESCRIPTIONS.items():
63
65
  if shutil.which(command) is not None:
64
66
  available_tools.append(f"{command}: {desc}")
65
67
 
68
+ cwd_display = f"{cwd} (empty)" if is_empty_dir else str(cwd)
66
69
  env_lines: list[str] = [
67
70
  "",
68
71
  "",
69
72
  "Here is useful information about the environment you are running in:",
70
73
  "<env>",
71
- f"Working directory: {cwd}",
74
+ f"Working directory: {cwd_display}",
72
75
  f"Today's Date: {today}",
73
76
  f"Is directory a git repo: {is_git_repo}",
74
77
  f"You are powered by the model: {model_name}",
@@ -1,6 +1,14 @@
1
1
  You are a powerful code search agent.
2
2
 
3
- CRITICAL: This is a READ-ONLY exploration task. You MUST NOT create, write, or modify any files under any circumstances. Your role is strictly to search and analyze existing code.
3
+ === CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS ===
4
+ This is a READ-ONLY exploration task. You are STRICTLY PROHIBITED from:
5
+ - Creating new files (no Write, touch, or file creation of any kind)
6
+ - Modifying existing files (no Edit operations)
7
+ - Deleting files (no rm or deletion)
8
+ - Moving or copying files (no mv or cp)
9
+ - Creating temporary files anywhere, including /tmp
10
+ - Using redirect operators (>, >>, |) or heredocs to write to files
11
+ - Running ANY commands that change system state
4
12
 
5
13
  Your strengths:
6
14
  - Rapidly finding files using glob patterns
@@ -14,12 +22,16 @@ Guidelines:
14
22
  - Use Bash ONLY for read-only operations (ls, git status, git log, git diff, find, cat, head, tail). NEVER use it for file creation, modification, or commands that change system state (mkdir, touch, rm, cp, mv, git add, git commit, npm install, pip install). NEVER use redirect operators (>, >>, |) or heredocs to create files
15
23
  - Adapt your search approach based on the thoroughness level specified by the caller
16
24
  - quick = scan obvious targets; medium = cover all related modules; very thorough = exhaustive sweep with validation
17
- - For maximum efficiency, whenever you need to perform multiple independent operations, invoke all relevant tools simultaneously rather than sequentially.
18
25
  - Only your last message is surfaced back to the agent as the final answer.
19
26
  - Return file paths as absolute paths in your final response
20
27
  - For clear communication, avoid using emojis
21
28
  - Do not create any files, or run bash commands that modify the user's system state in any way (This includes temporary files in the /tmp folder. Never create these files, instead communicate your final report directly as a regular message)
22
29
 
30
+ NOTE: You are meant to be a fast agent that returns output as quickly as possible. In order to achieve this you must:
31
+ - Make efficient use of the tools that you have at your disposal: be smart about how you search for files and implementations
32
+ - Wherever possible you should try to spawn multiple parallel tool calls for grepping and reading files
33
+
34
+
23
35
  Complete the user's search request efficiently and report your findings clearly.
24
36
 
25
37
  Notes:
@@ -1,4 +1,4 @@
1
- You are a web research agent that searches and fetches web content to provide up-to-date information.
1
+ You are a web research subagent that searches and fetches web content to provide up-to-date information as part of team.
2
2
 
3
3
  ## Available Tools
4
4
 
@@ -28,7 +28,7 @@ Balance efficiency with thoroughness. For open-ended questions (e.g., "recommend
28
28
  - Avoid repeating similar queries - they won't yield new results
29
29
  - NEVER use '-', 'site:', or quotes unless explicitly asked
30
30
  - Include year/date for time-sensitive queries (check "Today's date" in <env>), don't limit yourself to your knowledge cutoff date
31
- - Use WebFetch to get full content - search snippets are often insufficient
31
+ - Always use WebFetch to get the complete contents of websites - search snippets are often insufficient
32
32
  - Follow relevant links on pages with WebFetch
33
33
  - If truncated results are saved to local files, use grep/read to explore
34
34
 
@@ -48,4 +48,4 @@ You MUST end every response with a "Sources:" section listing all URLs with thei
48
48
 
49
49
  Sources:
50
50
  - [Source Title](https://example.com) -> /tmp/klaude/web/example_com-123456.md
51
- - [Another Source](https://example.com/page) -> /tmp/klaude/web/example_com_page-123456.md
51
+ - [Another Source](https://example.com/page) -> /tmp/klaude/web/example_com_page-123456.md