klaude-code 1.2.15__py3-none-any.whl → 1.2.16__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 (53) hide show
  1. klaude_code/cli/main.py +66 -42
  2. klaude_code/cli/runtime.py +24 -13
  3. klaude_code/command/export_cmd.py +2 -2
  4. klaude_code/command/prompt-handoff.md +33 -0
  5. klaude_code/command/thinking_cmd.py +5 -1
  6. klaude_code/config/config.py +5 -5
  7. klaude_code/config/list_model.py +1 -1
  8. klaude_code/const/__init__.py +3 -0
  9. klaude_code/core/executor.py +2 -2
  10. klaude_code/core/manager/llm_clients_builder.py +1 -1
  11. klaude_code/core/manager/sub_agent_manager.py +30 -6
  12. klaude_code/core/prompt.py +15 -13
  13. klaude_code/core/prompts/{prompt-subagent-explore.md → prompt-sub-agent-explore.md} +0 -1
  14. klaude_code/core/prompts/{prompt-subagent-oracle.md → prompt-sub-agent-oracle.md} +1 -1
  15. klaude_code/core/reminders.py +75 -32
  16. klaude_code/core/task.py +10 -22
  17. klaude_code/core/tool/__init__.py +2 -0
  18. klaude_code/core/tool/report_back_tool.py +58 -0
  19. klaude_code/core/tool/sub_agent_tool.py +6 -0
  20. klaude_code/core/tool/tool_runner.py +9 -1
  21. klaude_code/core/turn.py +45 -4
  22. klaude_code/llm/anthropic/input.py +14 -5
  23. klaude_code/llm/openrouter/input.py +14 -3
  24. klaude_code/llm/responses/input.py +19 -0
  25. klaude_code/protocol/events.py +1 -0
  26. klaude_code/protocol/model.py +24 -14
  27. klaude_code/protocol/sub_agent/__init__.py +117 -0
  28. klaude_code/protocol/sub_agent/explore.py +63 -0
  29. klaude_code/protocol/sub_agent/oracle.py +91 -0
  30. klaude_code/protocol/sub_agent/task.py +61 -0
  31. klaude_code/protocol/sub_agent/web_fetch.py +74 -0
  32. klaude_code/protocol/tools.py +1 -0
  33. klaude_code/session/export.py +12 -6
  34. klaude_code/session/session.py +12 -2
  35. klaude_code/session/templates/export_session.html +12 -12
  36. klaude_code/ui/modes/repl/completers.py +1 -1
  37. klaude_code/ui/modes/repl/event_handler.py +32 -2
  38. klaude_code/ui/modes/repl/renderer.py +8 -6
  39. klaude_code/ui/renderers/developer.py +18 -7
  40. klaude_code/ui/renderers/metadata.py +24 -12
  41. klaude_code/ui/renderers/sub_agent.py +59 -3
  42. klaude_code/ui/renderers/thinking.py +1 -1
  43. klaude_code/ui/renderers/tools.py +22 -29
  44. klaude_code/ui/rich/markdown.py +20 -48
  45. klaude_code/ui/rich/status.py +32 -14
  46. klaude_code/ui/rich/theme.py +8 -7
  47. {klaude_code-1.2.15.dist-info → klaude_code-1.2.16.dist-info}/METADATA +3 -2
  48. {klaude_code-1.2.15.dist-info → klaude_code-1.2.16.dist-info}/RECORD +52 -46
  49. klaude_code/protocol/sub_agent.py +0 -354
  50. /klaude_code/core/prompts/{prompt-subagent-webfetch.md → prompt-sub-agent-webfetch.md} +0 -0
  51. /klaude_code/core/prompts/{prompt-subagent.md → prompt-sub-agent.md} +0 -0
  52. {klaude_code-1.2.15.dist-info → klaude_code-1.2.16.dist-info}/WHEEL +0 -0
  53. {klaude_code-1.2.15.dist-info → klaude_code-1.2.16.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,91 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from klaude_code.protocol import tools
6
+ from klaude_code.protocol.sub_agent import SubAgentProfile, register_sub_agent
7
+
8
+ ORACLE_DESCRIPTION = """\
9
+ Consult the Oracle - an AI advisor powered by OpenAI's premium reasoning model that can plan, review, and provide expert guidance.
10
+
11
+ The Oracle has access to the following tools: Read, Bash.
12
+
13
+ The Oracle acts as your senior engineering advisor and can help with:
14
+
15
+ WHEN TO USE THE ORACLE:
16
+ - Code reviews and architecture feedback
17
+ - Finding a bug in multiple files
18
+ - Planning complex implementations or refactoring
19
+ - Analyzing code quality and suggesting improvements
20
+ - Answering complex technical questions that require deep reasoning
21
+
22
+ WHEN NOT TO USE THE ORACLE:
23
+ - Simple file reading or searching tasks (use Read or Grep directly)
24
+ - Codebase searches (use Task)
25
+ - Basic code modifications and when you need to execute code changes (do it yourself or use Task)
26
+
27
+ USAGE GUIDELINES:
28
+ 1. Be specific about what you want the Oracle to review, plan, or debug
29
+ 2. Provide relevant context about what you're trying to achieve. If you know that any files are involved, list them and they will be attached.
30
+
31
+
32
+ EXAMPLES:
33
+ - "Review the authentication system architecture and suggest improvements"
34
+ - "Plan the implementation of real-time collaboration features"
35
+ - "Analyze the performance bottlenecks in the data processing pipeline"
36
+ - "Review this API design and suggest better patterns"\
37
+ """
38
+
39
+ ORACLE_PARAMETERS = {
40
+ "properties": {
41
+ "context": {
42
+ "description": "Optional context about the current situation, what you've tried, or background information that would help the Oracle provide better guidance.",
43
+ "type": "string",
44
+ },
45
+ "files": {
46
+ "description": "Optional list of specific file paths (text files, images) that the Oracle should examine as part of its analysis. These files will be attached to the Oracle input.",
47
+ "items": {"type": "string"},
48
+ "type": "array",
49
+ },
50
+ "task": {
51
+ "description": "The task or question you want the Oracle to help with. Be specific about what kind of guidance, review, or planning you need.",
52
+ "type": "string",
53
+ },
54
+ "description": {
55
+ "description": "A short (3-5 word) description of the task",
56
+ "type": "string",
57
+ },
58
+ },
59
+ "required": ["task", "description"],
60
+ "type": "object",
61
+ }
62
+
63
+
64
+ def _oracle_prompt_builder(args: dict[str, Any]) -> str:
65
+ """Build the Oracle prompt from tool arguments."""
66
+ context = args.get("context", "")
67
+ task = args.get("task", "")
68
+ files = args.get("files", [])
69
+
70
+ prompt = f"""\
71
+ Context: {context}
72
+ Task: {task}\
73
+ """
74
+ if files:
75
+ files_str = "\n".join(f"@{file}" for file in files)
76
+ prompt += f"\nRelated files to review:\n{files_str}"
77
+ return prompt
78
+
79
+
80
+ register_sub_agent(
81
+ SubAgentProfile(
82
+ name="Oracle",
83
+ description=ORACLE_DESCRIPTION,
84
+ parameters=ORACLE_PARAMETERS,
85
+ prompt_file="prompts/prompt-sub-agent-oracle.md",
86
+ tool_set=(tools.READ, tools.BASH),
87
+ prompt_builder=_oracle_prompt_builder,
88
+ active_form="Consulting Oracle",
89
+ target_model_filter=lambda model: ("gpt-5" not in model) and ("gemini-3" not in model),
90
+ )
91
+ )
@@ -0,0 +1,61 @@
1
+ from __future__ import annotations
2
+
3
+ from klaude_code.protocol import tools
4
+ from klaude_code.protocol.sub_agent import SubAgentProfile, register_sub_agent
5
+
6
+ TASK_DESCRIPTION = """\
7
+ Launch a new agent to handle complex, multi-step tasks autonomously. \
8
+
9
+ When NOT to use the Task tool:
10
+ - If you want to read a specific file path, use the Read or Bash tool for `rg` instead of the Task tool, to find the match more quickly
11
+ - If you are searching for a specific class definition like "class Foo", use the Bash tool for `rg` instead, to find the match more quickly
12
+ - If you are searching for code within a specific file or set of 2-3 files, use the Read tool instead of the Task tool, to find the match more quickly
13
+ - Other tasks that are not related to the agent descriptions above
14
+
15
+ Usage notes:
16
+ - Launch multiple agents concurrently whenever possible, to maximize performance; to do that, use a single message with multiple tool uses
17
+ - When the agent is done, it will return a single message back to you. The result returned by the agent is not visible to the user. To show the user the result, you should send a text message back to the user with a concise summary of the result.
18
+ - Each agent invocation is stateless. You will not be able to send additional messages to the agent, nor will the agent be able to communicate with you outside of its final report. Therefore, your prompt should contain a highly detailed task description for the agent to perform autonomously and you should specify exactly what information the agent should return back to you in its final and only message to you.
19
+ - The agent's outputs should generally be trusted
20
+ - Clearly tell the agent whether you expect it to write code or just to do research (search, file reads, etc.), since it is not aware of the user's intent
21
+ - If the agent description mentions that it should be used proactively, then you should try your best to use it without the user having to ask for it first. Use your judgement.
22
+ - If the user specifies that they want you to run agents "in parallel", you MUST send a single message with multiple Task tool use content blocks. For example, if you need to launch both a code-reviewer agent and a test-runner agent in parallel, send a single message with both tool calls.
23
+
24
+ Structured output:
25
+ - Provide an `output_format` (JSON Schema) parameter for structured data back from the agent
26
+ - Example: `output_format={"type": "object", "properties": {"files": {"type": "array", "items": {"type": "string"}, "description": "List of file paths that match the search criteria, e.g. ['src/main.py', 'src/utils/helper.py']"}}, "required": ["files"]}`\
27
+ """
28
+
29
+ TASK_PARAMETERS = {
30
+ "type": "object",
31
+ "properties": {
32
+ "description": {
33
+ "type": "string",
34
+ "description": "A short (3-5 word) description of the task",
35
+ },
36
+ "prompt": {
37
+ "type": "string",
38
+ "description": "The task for the agent to perform",
39
+ },
40
+ "output_format": {
41
+ "type": "object",
42
+ "description": (
43
+ "Optional JSON Schema for structured output, better with examples in argument descriptions."
44
+ ),
45
+ },
46
+ },
47
+ "required": ["description", "prompt"],
48
+ "additionalProperties": False,
49
+ }
50
+
51
+ register_sub_agent(
52
+ SubAgentProfile(
53
+ name="Task",
54
+ description=TASK_DESCRIPTION,
55
+ parameters=TASK_PARAMETERS,
56
+ prompt_file="prompts/prompt-sub-agent.md",
57
+ tool_set=(tools.BASH, tools.READ, tools.EDIT, tools.WRITE),
58
+ active_form="Tasking",
59
+ output_schema_arg="output_format",
60
+ )
61
+ )
@@ -0,0 +1,74 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from klaude_code.protocol import tools
6
+ from klaude_code.protocol.sub_agent import SubAgentProfile, register_sub_agent
7
+
8
+ WEB_FETCH_AGENT_DESCRIPTION = """\
9
+ Launch a sub-agent to fetch and analyze web content. Use this when you need to:
10
+ - Retrieve and extract information from a webpage
11
+ - Analyze web page content based on specific instructions
12
+ - Get structured data from URLs
13
+
14
+ This is an autonomous agent with its own reasoning capabilities. It can:
15
+ - Follow links and navigate across multiple pages to gather comprehensive information
16
+ - Decide which related pages to visit based on the initial content
17
+ - Aggregate information from multiple sources into a coherent response
18
+
19
+ The agent will fetch the URL content, handle HTML-to-Markdown conversion automatically, \
20
+ and can use tools like rg to search through large responses that were truncated and saved to files.
21
+
22
+ Usage notes:
23
+ - Provide a clear prompt describing what information to extract or analyze
24
+ - Provide an `output_format` (JSON Schema) parameter for structured data back from the sub-agent
25
+ - Example: `output_format={"type": "object", "properties": {"main_content": {"type": "string", "description": "The main content extracted from the page, e.g. 'This article discusses...'"}, "key_insights": {"type": "array", "items": {"type": "string"}, "description": "Key takeaways from the content, e.g. ['Insight 1', 'Insight 2']"}}, "required": ["main_content", "key_insights"]}`
26
+ - The agent will return a summary of the findings
27
+ - For large web pages, the content may be truncated and saved to a file; the agent can search through it
28
+ - The agent can autonomously follow links to related pages if needed to complete the task\
29
+ """
30
+
31
+ WEB_FETCH_AGENT_PARAMETERS = {
32
+ "type": "object",
33
+ "properties": {
34
+ "description": {
35
+ "type": "string",
36
+ "description": "A short (3-5 word) description of the task",
37
+ },
38
+ "url": {
39
+ "type": "string",
40
+ "description": "The URL to fetch and analyze",
41
+ },
42
+ "prompt": {
43
+ "type": "string",
44
+ "description": "Instructions for analyzing or extracting content from the web page",
45
+ },
46
+ "output_format": {
47
+ "type": "object",
48
+ "description": "Optional JSON Schema for sub-agent structured output",
49
+ },
50
+ },
51
+ "required": ["description", "url", "prompt", "output_format"],
52
+ "additionalProperties": False,
53
+ }
54
+
55
+
56
+ def _web_fetch_prompt_builder(args: dict[str, Any]) -> str:
57
+ """Build the WebFetchAgent prompt from tool arguments."""
58
+ url = args.get("url", "")
59
+ prompt = args.get("prompt", "")
60
+ return f"URL to fetch: {url}\nTask: {prompt}"
61
+
62
+
63
+ register_sub_agent(
64
+ SubAgentProfile(
65
+ name="WebFetchAgent",
66
+ description=WEB_FETCH_AGENT_DESCRIPTION,
67
+ parameters=WEB_FETCH_AGENT_PARAMETERS,
68
+ prompt_file="prompts/prompt-sub-agent-webfetch.md",
69
+ tool_set=(tools.BASH, tools.READ, tools.WEB_FETCH),
70
+ prompt_builder=_web_fetch_prompt_builder,
71
+ active_form="Fetching Web",
72
+ output_schema_arg="output_format",
73
+ )
74
+ )
@@ -10,6 +10,7 @@ SKILL = "Skill"
10
10
  MERMAID = "Mermaid"
11
11
  MEMORY = "Memory"
12
12
  WEB_FETCH = "WebFetch"
13
+ REPORT_BACK = "report_back"
13
14
 
14
15
  # SubAgentType is just a string alias now; agent types are defined via SubAgentProfile
15
16
  SubAgentType = str
@@ -299,18 +299,24 @@ def _try_render_todo_args(arguments: str) -> str | None:
299
299
 
300
300
 
301
301
  def _render_sub_agent_result(content: str) -> str:
302
- encoded = _escape_html(content)
302
+ # Try to format as JSON for better readability
303
+ try:
304
+ parsed = json.loads(content)
305
+ formatted = "```json\n" + json.dumps(parsed, ensure_ascii=False, indent=2) + "\n```"
306
+ except (json.JSONDecodeError, TypeError):
307
+ formatted = content
308
+ encoded = _escape_html(formatted)
303
309
  return (
304
- f'<div class="subagent-result-container">'
305
- f'<div class="subagent-toolbar">'
310
+ f'<div class="sub-agent-result-container">'
311
+ f'<div class="sub-agent-toolbar">'
306
312
  f'<button type="button" class="raw-toggle" aria-pressed="false" title="Toggle raw text view">Raw</button>'
307
313
  f'<button type="button" class="copy-raw-btn" title="Copy raw content">Copy</button>'
308
314
  f"</div>"
309
- f'<div class="subagent-content">'
310
- f'<div class="subagent-rendered markdown-content markdown-body" data-raw="{encoded}">'
315
+ f'<div class="sub-agent-content">'
316
+ f'<div class="sub-agent-rendered markdown-content markdown-body" data-raw="{encoded}">'
311
317
  f'<noscript><pre style="white-space: pre-wrap;">{encoded}</pre></noscript>'
312
318
  f"</div>"
313
- f'<pre class="subagent-raw">{encoded}</pre>'
319
+ f'<pre class="sub-agent-raw">{encoded}</pre>'
314
320
  f"</div>"
315
321
  f"</div>"
316
322
  )
@@ -7,7 +7,7 @@ from typing import ClassVar
7
7
 
8
8
  from pydantic import BaseModel, Field, PrivateAttr
9
9
 
10
- from klaude_code.protocol import events, model
10
+ from klaude_code.protocol import events, model, tools
11
11
 
12
12
 
13
13
  class Session(BaseModel):
@@ -270,6 +270,7 @@ class Session(BaseModel):
270
270
  seen_sub_agent_sessions: set[str] = set()
271
271
  prev_item: model.ConversationItem | None = None
272
272
  last_assistant_content: str = ""
273
+ report_back_result: str | None = None
273
274
  yield events.TaskStartEvent(session_id=self.id, sub_agent_state=self.sub_agent_state)
274
275
  for it in self.conversation_history:
275
276
  if self.need_turn_start(prev_item, it):
@@ -286,6 +287,8 @@ class Session(BaseModel):
286
287
  session_id=self.id,
287
288
  )
288
289
  case model.ToolCallItem() as tc:
290
+ if tc.name == tools.REPORT_BACK:
291
+ report_back_result = tc.arguments
289
292
  yield events.ToolCallEvent(
290
293
  tool_call_id=tc.call_id,
291
294
  tool_name=tc.name,
@@ -336,7 +339,14 @@ class Session(BaseModel):
336
339
  case _:
337
340
  continue
338
341
  prev_item = it
339
- yield events.TaskFinishEvent(session_id=self.id, task_result=last_assistant_content)
342
+
343
+ has_structured_output = report_back_result is not None
344
+ task_result = report_back_result if has_structured_output else last_assistant_content
345
+ yield events.TaskFinishEvent(
346
+ session_id=self.id,
347
+ task_result=task_result,
348
+ has_structured_output=has_structured_output,
349
+ )
340
350
 
341
351
  def _iter_sub_agent_history(
342
352
  self, tool_result: model.ToolResultItem, seen_sub_agent_sessions: set[str]
@@ -632,26 +632,26 @@
632
632
  color: var(--text-dim);
633
633
  }
634
634
 
635
- /* Sub Agent Result */
636
- .subagent-result-container {
635
+ /* Sub-Agent Result */
636
+ .sub-agent-result-container {
637
637
  display: flex;
638
638
  flex-direction: column;
639
639
  gap: 8px;
640
640
  margin-top: 8px;
641
641
  }
642
642
 
643
- .subagent-toolbar {
643
+ .sub-agent-toolbar {
644
644
  display: flex;
645
645
  justify-content: flex-end;
646
646
  align-items: center;
647
647
  margin-bottom: 4px;
648
648
  }
649
649
 
650
- .subagent-content {
650
+ .sub-agent-content {
651
651
  width: 100%;
652
652
  }
653
653
 
654
- .subagent-raw {
654
+ .sub-agent-raw {
655
655
  display: none;
656
656
  font-family: var(--font-mono);
657
657
  font-size: var(--font-size-base);
@@ -662,10 +662,10 @@
662
662
  padding: 20px;
663
663
  }
664
664
 
665
- .subagent-content.show-raw .subagent-rendered {
665
+ .sub-agent-content.show-raw .sub-agent-rendered {
666
666
  display: none;
667
667
  }
668
- .subagent-content.show-raw .subagent-raw {
668
+ .sub-agent-content.show-raw .sub-agent-raw {
669
669
  display: block;
670
670
  }
671
671
 
@@ -1297,17 +1297,17 @@
1297
1297
  });
1298
1298
  });
1299
1299
 
1300
- // Subagent raw toggle
1300
+ // Sub-agent raw toggle
1301
1301
  document
1302
- .querySelectorAll(".subagent-result-container")
1302
+ .querySelectorAll(".sub-agent-result-container")
1303
1303
  .forEach((group) => {
1304
1304
  const toggle = group.querySelector(".raw-toggle");
1305
1305
  const copyBtn = group.querySelector(".copy-raw-btn");
1306
- const block = group.querySelector(".subagent-content");
1306
+ const block = group.querySelector(".sub-agent-content");
1307
1307
  const rendered = block
1308
- ? block.querySelector(".subagent-rendered")
1308
+ ? block.querySelector(".sub-agent-rendered")
1309
1309
  : null;
1310
- const raw = block ? block.querySelector(".subagent-raw") : null;
1310
+ const raw = block ? block.querySelector(".sub-agent-raw") : null;
1311
1311
 
1312
1312
  // Copy button logic
1313
1313
  if (copyBtn && rendered) {
@@ -85,7 +85,7 @@ class _SlashCommandCompleter(Completer):
85
85
  matched: list[tuple[str, object, str]] = []
86
86
  for cmd_name, cmd_obj in commands.items():
87
87
  if cmd_name.startswith(frag):
88
- hint = " [args]" if cmd_obj.support_addition_params else ""
88
+ hint = " [instructions]" if cmd_obj.support_addition_params else ""
89
89
  matched.append((cmd_name, cmd_obj, hint))
90
90
 
91
91
  if not matched:
@@ -121,7 +121,7 @@ class ActivityState:
121
121
  for name, count in self._tool_calls.items():
122
122
  if not first:
123
123
  activity_text.append(", ")
124
- activity_text.append(name, style="bold")
124
+ activity_text.append(name)
125
125
  if count > 1:
126
126
  activity_text.append(f" x {count}")
127
127
  first = False
@@ -175,6 +175,10 @@ class SpinnerStatusState:
175
175
  """Clear activity state for a new turn."""
176
176
  self._activity.reset()
177
177
 
178
+ def get_activity_text(self) -> Text | None:
179
+ """Get current activity text. Returns None if idle."""
180
+ return self._activity.get_activity_text()
181
+
178
182
  def get_status(self) -> Text:
179
183
  """Get current spinner status as rich Text."""
180
184
  activity_text = self._activity.get_activity_text()
@@ -377,6 +381,7 @@ class DisplayEventHandler:
377
381
  self.spinner_status.set_composing(False)
378
382
  self._update_spinner()
379
383
  await self.stage_manager.transition_to(Stage.WAITING)
384
+ self.renderer.print()
380
385
  self.renderer.spinner_start()
381
386
 
382
387
  def _on_tool_call_start(self, event: events.TurnToolCallStartEvent) -> None:
@@ -476,6 +481,7 @@ class DisplayEventHandler:
476
481
  mdstream.update(self.thinking_stream.buffer, final=True)
477
482
  self.thinking_stream.finish()
478
483
  self.renderer.console.pop_theme()
484
+ self.renderer.print()
479
485
  self.renderer.spinner_start()
480
486
 
481
487
  def _maybe_notify_task_finish(self, event: events.TaskFinishEvent) -> None:
@@ -512,7 +518,31 @@ class DisplayEventHandler:
512
518
  if len(todo.content) > 0:
513
519
  status_text = todo.content
514
520
  status_text = status_text.replace("\n", "")
515
- return self._truncate_status_text(status_text, max_length=50)
521
+ max_length = self._calculate_base_status_max_length()
522
+ return self._truncate_status_text(status_text, max_length=max_length)
523
+
524
+ def _calculate_base_status_max_length(self) -> int:
525
+ """Calculate max length for base_status based on terminal width.
526
+
527
+ Reserve space for:
528
+ - Spinner glyph + space: 2 chars
529
+ - " | " separator: 3 chars (only if activity text present)
530
+ - Activity text: actual length (only if present)
531
+ - Status hint text (esc to interrupt)
532
+ """
533
+ terminal_width = self.renderer.console.size.width
534
+
535
+ # Base reserved space: spinner + status hint
536
+ reserved_space = 2 + len(const.STATUS_HINT_TEXT)
537
+
538
+ # Add space for activity text if present
539
+ activity_text = self.spinner_status.get_activity_text()
540
+ if activity_text:
541
+ # " | " separator + actual activity text length
542
+ reserved_space += 3 + len(activity_text.plain)
543
+
544
+ max_length = max(10, terminal_width - reserved_space)
545
+ return max_length
516
546
 
517
547
  def _truncate_status_text(self, text: str, max_length: int) -> str:
518
548
  if len(text) <= max_length:
@@ -50,7 +50,7 @@ class REPLRenderer:
50
50
 
51
51
  self.session_map: dict[str, SessionStatus] = {}
52
52
  self.current_sub_agent_color: Style | None = None
53
- self.subagent_color_index = 0
53
+ self.sub_agent_color_index = 0
54
54
 
55
55
  def register_session(self, session_id: str, sub_agent_state: model.SubAgentState | None = None) -> None:
56
56
  session_status = SessionStatus(
@@ -66,16 +66,16 @@ class REPLRenderer:
66
66
  def _advance_sub_agent_color_index(self) -> None:
67
67
  palette_size = len(self.themes.sub_agent_colors)
68
68
  if palette_size == 0:
69
- self.subagent_color_index = 0
69
+ self.sub_agent_color_index = 0
70
70
  return
71
- self.subagent_color_index = (self.subagent_color_index + 1) % palette_size
71
+ self.sub_agent_color_index = (self.sub_agent_color_index + 1) % palette_size
72
72
 
73
73
  def pick_sub_agent_color(self) -> Style:
74
74
  self._advance_sub_agent_color_index()
75
75
  palette = self.themes.sub_agent_colors
76
76
  if not palette:
77
77
  return Style()
78
- return palette[self.subagent_color_index]
78
+ return palette[self.sub_agent_color_index]
79
79
 
80
80
  def get_session_sub_agent_color(self, session_id: str) -> Style:
81
81
  status = self.session_map.get(session_id)
@@ -100,9 +100,9 @@ class REPLRenderer:
100
100
  if self.current_sub_agent_color:
101
101
  if objects:
102
102
  content = objects[0] if len(objects) == 1 else objects
103
- self.console.print(Quote(content, style=self.current_sub_agent_color))
103
+ self.console.print(Quote(content, style=self.current_sub_agent_color), overflow="ellipsis")
104
104
  return
105
- self.console.print(*objects, style=style, end=end)
105
+ self.console.print(*objects, style=style, end=end, overflow="ellipsis")
106
106
 
107
107
  def display_tool_call(self, e: events.ToolCallEvent) -> None:
108
108
  if r_tools.is_sub_agent_tool(e.tool_name):
@@ -152,6 +152,7 @@ class REPLRenderer:
152
152
  case events.ThinkingEvent() as e:
153
153
  if is_sub_agent:
154
154
  continue
155
+ self.display_thinking_prefix()
155
156
  self.display_thinking(e.content)
156
157
  case events.DeveloperMessageEvent() as e:
157
158
  self.display_developer_message(e)
@@ -233,6 +234,7 @@ class REPLRenderer:
233
234
  r_sub_agent.render_sub_agent_result(
234
235
  event.task_result,
235
236
  code_theme=self.themes.code_theme,
237
+ has_structured_output=event.has_structured_output,
236
238
  )
237
239
  )
238
240
 
@@ -67,13 +67,24 @@ def render_developer_message(e: events.DeveloperMessageEvent) -> RenderableType:
67
67
  if e.item.at_files:
68
68
  grid = create_grid()
69
69
  for at_file in e.item.at_files:
70
- grid.add_row(
71
- Text(" +", style=ThemeKey.REMINDER),
72
- Text.assemble(
73
- (f"{at_file.operation} ", ThemeKey.REMINDER),
74
- render_path(at_file.path, ThemeKey.REMINDER_BOLD),
75
- ),
76
- )
70
+ if at_file.mentioned_in:
71
+ grid.add_row(
72
+ Text(" +", style=ThemeKey.REMINDER),
73
+ Text.assemble(
74
+ (f"{at_file.operation} ", ThemeKey.REMINDER),
75
+ render_path(at_file.path, ThemeKey.REMINDER_BOLD),
76
+ (" mentioned in ", ThemeKey.REMINDER),
77
+ render_path(at_file.mentioned_in, ThemeKey.REMINDER_BOLD),
78
+ ),
79
+ )
80
+ else:
81
+ grid.add_row(
82
+ Text(" +", style=ThemeKey.REMINDER),
83
+ Text.assemble(
84
+ (f"{at_file.operation} ", ThemeKey.REMINDER),
85
+ render_path(at_file.path, ThemeKey.REMINDER_BOLD),
86
+ ),
87
+ )
77
88
  parts.append(grid)
78
89
 
79
90
  if uic := e.item.user_image_count:
@@ -59,20 +59,32 @@ def _render_task_metadata_block(
59
59
  parts: list[Text] = []
60
60
 
61
61
  if metadata.usage is not None:
62
- # Tokens: ↑37k c5k ↓907 r45k
63
- token_parts: list[tuple[str, str]] = [
64
- ("↑", ThemeKey.METADATA_DIM),
65
- (format_number(metadata.usage.input_tokens), ThemeKey.METADATA),
62
+ # Tokens: ↑ 37k cache 5k 907 think 45k
63
+ token_parts: list[Text] = [
64
+ Text.assemble(
65
+ ("↑ ", ThemeKey.METADATA_DIM), (format_number(metadata.usage.input_tokens), ThemeKey.METADATA)
66
+ )
66
67
  ]
67
68
  if metadata.usage.cached_tokens > 0:
68
- token_parts.append((" c", ThemeKey.METADATA_DIM))
69
- token_parts.append((format_number(metadata.usage.cached_tokens), ThemeKey.METADATA))
70
- token_parts.append((" ", ThemeKey.METADATA_DIM))
71
- token_parts.append((format_number(metadata.usage.output_tokens), ThemeKey.METADATA))
69
+ token_parts.append(
70
+ Text.assemble(
71
+ Text("cache ", style=ThemeKey.METADATA_DIM),
72
+ Text(format_number(metadata.usage.cached_tokens), style=ThemeKey.METADATA),
73
+ )
74
+ )
75
+ token_parts.append(
76
+ Text.assemble(
77
+ ("↓ ", ThemeKey.METADATA_DIM), (format_number(metadata.usage.output_tokens), ThemeKey.METADATA)
78
+ )
79
+ )
72
80
  if metadata.usage.reasoning_tokens > 0:
73
- token_parts.append((" r", ThemeKey.METADATA_DIM))
74
- token_parts.append((format_number(metadata.usage.reasoning_tokens), ThemeKey.METADATA))
75
- parts.append(Text.assemble(*token_parts))
81
+ token_parts.append(
82
+ Text.assemble(
83
+ ("think ", ThemeKey.METADATA_DIM),
84
+ (format_number(metadata.usage.reasoning_tokens), ThemeKey.METADATA),
85
+ )
86
+ )
87
+ parts.append(Text(" · ").join(token_parts))
76
88
 
77
89
  # Cost
78
90
  if metadata.usage is not None and metadata.usage.total_cost is not None:
@@ -141,7 +153,7 @@ def render_task_metadata(e: events.TaskMetadataEvent) -> RenderableType:
141
153
  for meta in sorted_items:
142
154
  renderables.append(_render_task_metadata_block(meta, is_sub_agent=True, show_context_and_time=False))
143
155
 
144
- return Padding(Group(*renderables), (0, 0, 0, 1))
156
+ return Group(*renderables)
145
157
 
146
158
 
147
159
  def render_welcome(e: events.WelcomeEvent, *, box_style: Box | None = None) -> RenderableType: