klaude-code 1.2.14__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 (54) 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 +6 -2
  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/input_common.py +1 -1
  24. klaude_code/llm/openrouter/input.py +14 -3
  25. klaude_code/llm/responses/input.py +19 -0
  26. klaude_code/protocol/events.py +1 -0
  27. klaude_code/protocol/model.py +24 -14
  28. klaude_code/protocol/sub_agent/__init__.py +117 -0
  29. klaude_code/protocol/sub_agent/explore.py +63 -0
  30. klaude_code/protocol/sub_agent/oracle.py +91 -0
  31. klaude_code/protocol/sub_agent/task.py +61 -0
  32. klaude_code/protocol/sub_agent/web_fetch.py +74 -0
  33. klaude_code/protocol/tools.py +1 -0
  34. klaude_code/session/export.py +12 -6
  35. klaude_code/session/session.py +12 -2
  36. klaude_code/session/templates/export_session.html +20 -24
  37. klaude_code/ui/modes/repl/completers.py +1 -1
  38. klaude_code/ui/modes/repl/event_handler.py +34 -3
  39. klaude_code/ui/modes/repl/renderer.py +9 -9
  40. klaude_code/ui/renderers/developer.py +18 -7
  41. klaude_code/ui/renderers/metadata.py +57 -84
  42. klaude_code/ui/renderers/sub_agent.py +59 -3
  43. klaude_code/ui/renderers/thinking.py +3 -3
  44. klaude_code/ui/renderers/tools.py +67 -30
  45. klaude_code/ui/rich/markdown.py +45 -57
  46. klaude_code/ui/rich/status.py +32 -14
  47. klaude_code/ui/rich/theme.py +18 -17
  48. {klaude_code-1.2.14.dist-info → klaude_code-1.2.16.dist-info}/METADATA +3 -2
  49. {klaude_code-1.2.14.dist-info → klaude_code-1.2.16.dist-info}/RECORD +53 -47
  50. klaude_code/protocol/sub_agent.py +0 -354
  51. /klaude_code/core/prompts/{prompt-subagent-webfetch.md → prompt-sub-agent-webfetch.md} +0 -0
  52. /klaude_code/core/prompts/{prompt-subagent.md → prompt-sub-agent.md} +0 -0
  53. {klaude_code-1.2.14.dist-info → klaude_code-1.2.16.dist-info}/WHEEL +0 -0
  54. {klaude_code-1.2.14.dist-info → klaude_code-1.2.16.dist-info}/entry_points.txt +0 -0
@@ -9,6 +9,7 @@ from rich.text import Text
9
9
 
10
10
  from klaude_code.protocol import events, model
11
11
  from klaude_code.trace import is_debug_enabled
12
+ from klaude_code.ui.renderers.common import create_grid
12
13
  from klaude_code.ui.rich.theme import ThemeKey
13
14
  from klaude_code.ui.utils.common import format_number
14
15
 
@@ -24,161 +25,133 @@ def _get_version() -> str:
24
25
  def _render_task_metadata_block(
25
26
  metadata: model.TaskMetadata,
26
27
  *,
27
- indent: int = 0,
28
+ is_sub_agent: bool = False,
28
29
  show_context_and_time: bool = True,
29
- ) -> list[RenderableType]:
30
+ ) -> RenderableType:
30
31
  """Render a single TaskMetadata block.
31
32
 
32
33
  Args:
33
34
  metadata: The TaskMetadata to render.
34
- indent: Number of spaces to indent (0 for main, 2 for sub-agents).
35
+ is_sub_agent: Whether this is a sub-agent block.
35
36
  show_context_and_time: Whether to show context usage percent and time.
36
37
 
37
38
  Returns:
38
- List of renderables for this metadata block.
39
+ A renderable for this metadata block.
39
40
  """
41
+ grid = create_grid()
42
+
40
43
  # Get currency symbol
41
44
  currency = metadata.usage.currency if metadata.usage else "USD"
42
45
  currency_symbol = "¥" if currency == "CNY" else "$"
43
46
 
44
- # Line 1: Model and Provider
45
- prefix = (
46
- Text(" " * indent + "• ", style=ThemeKey.METADATA_BOLD)
47
- if indent == 0
48
- else Text(" " * indent + "└ ", style=ThemeKey.METADATA_DIM)
49
- )
50
- model_text = Text()
51
- model_text.append_text(prefix).append_text(Text(metadata.model_name, style=ThemeKey.METADATA_BOLD))
47
+ # First column: mark only
48
+ mark = Text("└", style=ThemeKey.METADATA_DIM) if is_sub_agent else Text("•", style=ThemeKey.METADATA_BOLD)
49
+
50
+ # Second column: model@provider / tokens / cost / ...
51
+ content = Text()
52
+ content.append_text(Text(metadata.model_name, style=ThemeKey.METADATA_BOLD))
52
53
  if metadata.provider is not None:
53
- model_text.append_text(Text("@", style=ThemeKey.METADATA)).append_text(
54
+ content.append_text(Text("@", style=ThemeKey.METADATA)).append_text(
54
55
  Text(metadata.provider.lower().replace(" ", "-"), style=ThemeKey.METADATA)
55
56
  )
56
57
 
57
- renderables: list[RenderableType] = [model_text]
58
-
59
- # Line 2: Token consumption, Context, TPS, Cost
60
- parts2: list[Text] = []
58
+ # All info parts (tokens, cost, context, etc.)
59
+ parts: list[Text] = []
61
60
 
62
61
  if metadata.usage is not None:
63
- # Input
64
- input_parts: list[tuple[str, str]] = [
65
- ("input:", ThemeKey.METADATA_DIM),
66
- (format_number(metadata.usage.input_tokens), ThemeKey.METADATA_DIM),
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
+ )
67
67
  ]
68
- if metadata.usage.input_cost is not None:
69
- input_parts.append((f"({currency_symbol}{metadata.usage.input_cost:.4f})", ThemeKey.METADATA_DIM))
70
- parts2.append(Text.assemble(*input_parts))
71
-
72
- # Cached
73
68
  if metadata.usage.cached_tokens > 0:
74
- cached_parts: list[tuple[str, str]] = [
75
- ("cached:", ThemeKey.METADATA_DIM),
76
- (format_number(metadata.usage.cached_tokens), ThemeKey.METADATA_DIM),
77
- ]
78
- if metadata.usage.cache_read_cost is not None:
79
- cached_parts.append((f"({currency_symbol}{metadata.usage.cache_read_cost:.4f})", ThemeKey.METADATA_DIM))
80
- parts2.append(Text.assemble(*cached_parts))
81
-
82
- # Output
83
- output_parts: list[tuple[str, str]] = [
84
- ("output:", ThemeKey.METADATA_DIM),
85
- (format_number(metadata.usage.output_tokens), ThemeKey.METADATA_DIM),
86
- ]
87
- if metadata.usage.output_cost is not None:
88
- output_parts.append((f"({currency_symbol}{metadata.usage.output_cost:.4f})", ThemeKey.METADATA_DIM))
89
- parts2.append(Text.assemble(*output_parts))
90
-
91
- # Reasoning
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
+ )
92
80
  if metadata.usage.reasoning_tokens > 0:
93
- parts2.append(
81
+ token_parts.append(
94
82
  Text.assemble(
95
- ("thinking", ThemeKey.METADATA_DIM),
96
- (":", ThemeKey.METADATA_DIM),
97
- (
98
- format_number(metadata.usage.reasoning_tokens),
99
- ThemeKey.METADATA_DIM,
100
- ),
83
+ ("think ", ThemeKey.METADATA_DIM),
84
+ (format_number(metadata.usage.reasoning_tokens), ThemeKey.METADATA),
101
85
  )
102
86
  )
87
+ parts.append(Text(" · ").join(token_parts))
103
88
 
104
89
  # Cost
105
90
  if metadata.usage is not None and metadata.usage.total_cost is not None:
106
- parts2.append(
91
+ parts.append(
107
92
  Text.assemble(
108
- ("cost", ThemeKey.METADATA_DIM),
109
- (":", ThemeKey.METADATA_DIM),
110
- (f"{currency_symbol}{metadata.usage.total_cost:.4f}", ThemeKey.METADATA_DIM),
93
+ (currency_symbol, ThemeKey.METADATA_DIM),
94
+ (f"{metadata.usage.total_cost:.4f}", ThemeKey.METADATA),
111
95
  )
112
96
  )
113
- if parts2:
114
- line2 = Text(" / ", style=ThemeKey.METADATA_DIM).join(parts2)
115
- renderables.append(Padding(line2, (0, 0, 0, indent + 2)))
116
-
117
- parts3: list[Text] = []
118
97
  if metadata.usage is not None:
119
98
  # Context (only for main agent)
120
99
  if show_context_and_time and metadata.usage.context_usage_percent is not None:
121
100
  context_size = format_number(metadata.usage.context_size or 0)
122
- parts3.append(
101
+ parts.append(
123
102
  Text.assemble(
124
- ("context", ThemeKey.METADATA_DIM),
125
- (":", ThemeKey.METADATA_DIM),
126
- (
127
- f"{context_size}({metadata.usage.context_usage_percent:.1f}%)",
128
- ThemeKey.METADATA_DIM,
129
- ),
103
+ (context_size, ThemeKey.METADATA),
104
+ (f" ({metadata.usage.context_usage_percent:.1f}%)", ThemeKey.METADATA_DIM),
130
105
  )
131
106
  )
132
107
 
133
108
  # TPS
134
109
  if metadata.usage.throughput_tps is not None:
135
- parts3.append(
110
+ parts.append(
136
111
  Text.assemble(
112
+ (f"{metadata.usage.throughput_tps:.1f} ", ThemeKey.METADATA),
137
113
  ("tps", ThemeKey.METADATA_DIM),
138
- (":", ThemeKey.METADATA_DIM),
139
- (f"{metadata.usage.throughput_tps:.1f}", ThemeKey.METADATA_DIM),
140
114
  )
141
115
  )
142
116
 
143
117
  # Duration
144
118
  if show_context_and_time and metadata.task_duration_s is not None:
145
- parts3.append(
119
+ parts.append(
146
120
  Text.assemble(
147
- ("time", ThemeKey.METADATA_DIM),
148
- (":", ThemeKey.METADATA_DIM),
149
- (f"{metadata.task_duration_s:.1f}s", ThemeKey.METADATA_DIM),
121
+ (f"{metadata.task_duration_s:.1f}", ThemeKey.METADATA),
122
+ ("s", ThemeKey.METADATA_DIM),
150
123
  )
151
124
  )
152
125
 
153
126
  # Turn count
154
127
  if show_context_and_time and metadata.turn_count > 0:
155
- parts3.append(
128
+ parts.append(
156
129
  Text.assemble(
157
- ("turns", ThemeKey.METADATA_DIM),
158
- (":", ThemeKey.METADATA_DIM),
159
- (str(metadata.turn_count), ThemeKey.METADATA_DIM),
130
+ (str(metadata.turn_count), ThemeKey.METADATA),
131
+ (" turns", ThemeKey.METADATA_DIM),
160
132
  )
161
133
  )
162
134
 
163
- if parts3:
164
- line2 = Text(" / ", style=ThemeKey.METADATA_DIM).join(parts3)
165
- renderables.append(Padding(line2, (0, 0, 0, indent + 2)))
135
+ if parts:
136
+ content.append_text(Text(" · ", style=ThemeKey.METADATA_DIM))
137
+ content.append_text(Text(" · ", style=ThemeKey.METADATA_DIM).join(parts))
166
138
 
167
- return renderables
139
+ grid.add_row(mark, content)
140
+ return grid if not is_sub_agent else Padding(grid, (0, 0, 0, 2))
168
141
 
169
142
 
170
143
  def render_task_metadata(e: events.TaskMetadataEvent) -> RenderableType:
171
144
  """Render task metadata including main agent and sub-agents, aggregated by model+provider."""
172
145
  renderables: list[RenderableType] = []
173
146
 
174
- renderables.extend(_render_task_metadata_block(e.metadata.main, indent=0, show_context_and_time=True))
147
+ renderables.append(_render_task_metadata_block(e.metadata.main, is_sub_agent=False, show_context_and_time=True))
175
148
 
176
149
  # Aggregate by (model_name, provider), sorted by total_cost descending
177
150
  sorted_items = model.TaskMetadata.aggregate_by_model(e.metadata.sub_agent_task_metadata)
178
151
 
179
152
  # Render each aggregated model block
180
153
  for meta in sorted_items:
181
- renderables.extend(_render_task_metadata_block(meta, indent=2, show_context_and_time=False))
154
+ renderables.append(_render_task_metadata_block(meta, is_sub_agent=True, show_context_and_time=False))
182
155
 
183
156
  return Group(*renderables)
184
157
 
@@ -1,6 +1,8 @@
1
1
  import json
2
+ from typing import Any, cast
2
3
 
3
4
  from rich.console import Group, RenderableType
5
+ from rich.json import JSON
4
6
  from rich.panel import Panel
5
7
  from rich.style import Style
6
8
  from rich.text import Text
@@ -12,20 +14,67 @@ from klaude_code.ui.rich.markdown import NoInsetMarkdown
12
14
  from klaude_code.ui.rich.theme import ThemeKey
13
15
 
14
16
 
17
+ def _compact_schema_value(value: dict[str, Any]) -> str | list[Any] | dict[str, Any]:
18
+ """Convert a JSON Schema value to compact representation."""
19
+ value_type = value.get("type", "any")
20
+ desc = value.get("description", "")
21
+
22
+ if value_type == "object":
23
+ props = value.get("properties", {})
24
+ return {k: _compact_schema_value(v) for k, v in props.items()}
25
+ elif value_type == "array":
26
+ items = value.get("items", {})
27
+ # If items have no description, use the array's description
28
+ if desc and not items.get("description"):
29
+ items = {**items, "description": desc}
30
+ return [_compact_schema_value(items)]
31
+ else:
32
+ if desc:
33
+ return f"{value_type} // {desc}"
34
+ return value_type
35
+
36
+
37
+ def _compact_schema(schema: dict[str, Any]) -> dict[str, Any] | list[Any] | str:
38
+ """Convert JSON Schema to compact representation for display."""
39
+ return _compact_schema_value(schema)
40
+
41
+
15
42
  def render_sub_agent_call(e: model.SubAgentState, style: Style | None = None) -> RenderableType:
16
43
  """Render sub-agent tool call header and prompt body."""
17
44
  desc = Text(
18
45
  f" {e.sub_agent_desc} ",
19
46
  style=Style(color=style.color if style else None, bold=True, reverse=True),
20
47
  )
21
- return Group(
48
+ elements: list[RenderableType] = [
22
49
  Text.assemble((e.sub_agent_type, ThemeKey.TOOL_NAME), " ", desc),
23
50
  Text(e.sub_agent_prompt, style=style or ""),
24
- )
51
+ ]
52
+ if e.output_schema:
53
+ elements.append(Text("\nExpected Output Format JSON:", style=style or ""))
54
+ compact = _compact_schema(e.output_schema)
55
+ schema_text = json.dumps(compact, ensure_ascii=False, indent=2)
56
+ elements.append(JSON(schema_text))
57
+ return Group(*elements)
25
58
 
26
59
 
27
- def render_sub_agent_result(result: str, *, code_theme: str, style: Style | None = None) -> RenderableType:
60
+ def render_sub_agent_result(
61
+ result: str, *, code_theme: str, style: Style | None = None, has_structured_output: bool = False
62
+ ) -> RenderableType:
28
63
  stripped_result = result.strip()
64
+
65
+ # Use rich JSON for structured output
66
+ if has_structured_output:
67
+ return Panel.fit(
68
+ Group(
69
+ Text(
70
+ "use /export to view full output",
71
+ style=ThemeKey.TOOL_RESULT,
72
+ ),
73
+ JSON(stripped_result),
74
+ ),
75
+ border_style=ThemeKey.LINES,
76
+ )
77
+
29
78
  lines = stripped_result.splitlines()
30
79
  if len(lines) > const.SUB_AGENT_RESULT_MAX_LINES:
31
80
  hidden_count = len(lines) - const.SUB_AGENT_RESULT_MAX_LINES
@@ -53,6 +102,7 @@ def build_sub_agent_state_from_tool_call(e: events.ToolCallEvent) -> model.SubAg
53
102
  return None
54
103
  description = profile.name
55
104
  prompt = ""
105
+ output_schema: dict[str, Any] | None = None
56
106
  if e.arguments:
57
107
  try:
58
108
  payload: dict[str, object] = json.loads(e.arguments)
@@ -64,8 +114,14 @@ def build_sub_agent_state_from_tool_call(e: events.ToolCallEvent) -> model.SubAg
64
114
  prompt_value = payload.get("prompt") or payload.get("task")
65
115
  if isinstance(prompt_value, str):
66
116
  prompt = prompt_value.strip()
117
+ # Extract output_schema if profile supports it
118
+ if profile.output_schema_arg:
119
+ schema_value = payload.get(profile.output_schema_arg)
120
+ if isinstance(schema_value, dict):
121
+ output_schema = cast(dict[str, Any], schema_value)
67
122
  return model.SubAgentState(
68
123
  sub_agent_type=profile.name,
69
124
  sub_agent_desc=description,
70
125
  sub_agent_prompt=prompt,
126
+ output_schema=output_schema,
71
127
  )
@@ -2,7 +2,7 @@ from rich.console import RenderableType
2
2
  from rich.padding import Padding
3
3
  from rich.text import Text
4
4
 
5
- from klaude_code.ui.rich.markdown import NoInsetMarkdown
5
+ from klaude_code.ui.rich.markdown import ThinkingMarkdown
6
6
  from klaude_code.ui.rich.theme import ThemeKey
7
7
 
8
8
 
@@ -16,7 +16,7 @@ def _normalize_thinking_content(content: str) -> str:
16
16
  content.rstrip()
17
17
  .replace("**\n\n", "** \n")
18
18
  .replace("\\n\\n\n\n", "") # Weird case of Gemini 3
19
- .replace("****", "**\n\n**") # remove extra newlines after bold titles
19
+ .replace("****", "**\n\n**") # Remove extra newlines after bold titles
20
20
  )
21
21
 
22
22
 
@@ -30,7 +30,7 @@ def render_thinking(content: str, *, code_theme: str, style: str) -> RenderableT
30
30
  return None
31
31
 
32
32
  return Padding.indent(
33
- NoInsetMarkdown(
33
+ ThinkingMarkdown(
34
34
  _normalize_thinking_content(content),
35
35
  code_theme=code_theme,
36
36
  style=style,
@@ -1,12 +1,13 @@
1
1
  import json
2
2
  from pathlib import Path
3
+ from typing import Any, cast
3
4
 
4
5
  from rich.console import RenderableType
5
6
  from rich.padding import Padding
6
7
  from rich.text import Text
7
8
 
8
9
  from klaude_code import const
9
- from klaude_code.protocol import events, model
10
+ from klaude_code.protocol import events, model, tools
10
11
  from klaude_code.protocol.sub_agent import is_sub_agent_tool as _is_sub_agent_tool
11
12
  from klaude_code.ui.renderers import diffs as r_diffs
12
13
  from klaude_code.ui.renderers.common import create_grid
@@ -58,6 +59,49 @@ def render_generic_tool_call(tool_name: str, arguments: str, markup: str = "•"
58
59
  return grid
59
60
 
60
61
 
62
+ def render_bash_tool_call(arguments: str) -> RenderableType:
63
+ grid = create_grid()
64
+ tool_name_column = Text.assemble((">", ThemeKey.TOOL_MARK), " ", ("Bash", ThemeKey.TOOL_NAME))
65
+
66
+ try:
67
+ payload_raw: Any = json.loads(arguments) if arguments else {}
68
+ except json.JSONDecodeError:
69
+ summary = Text(
70
+ arguments.strip()[: const.INVALID_TOOL_CALL_MAX_LENGTH],
71
+ style=ThemeKey.INVALID_TOOL_CALL_ARGS,
72
+ )
73
+ grid.add_row(tool_name_column, summary)
74
+ return grid
75
+
76
+ if not isinstance(payload_raw, dict):
77
+ summary = Text(
78
+ str(payload_raw)[: const.INVALID_TOOL_CALL_MAX_LENGTH],
79
+ style=ThemeKey.INVALID_TOOL_CALL_ARGS,
80
+ )
81
+ grid.add_row(tool_name_column, summary)
82
+ return grid
83
+
84
+ payload: dict[str, object] = cast(dict[str, object], payload_raw)
85
+
86
+ summary = Text("", ThemeKey.TOOL_PARAM)
87
+ command = payload.get("command")
88
+ timeout_ms = payload.get("timeout_ms")
89
+
90
+ if isinstance(command, str) and command.strip():
91
+ summary.append(command.strip(), style=ThemeKey.TOOL_PARAM)
92
+
93
+ if isinstance(timeout_ms, int):
94
+ if summary:
95
+ summary.append(" ")
96
+ if timeout_ms >= 1000 and timeout_ms % 1000 == 0:
97
+ summary.append(f"{timeout_ms // 1000}s", style=ThemeKey.TOOL_TIMEOUT)
98
+ else:
99
+ summary.append(f"{timeout_ms}ms", style=ThemeKey.TOOL_TIMEOUT)
100
+
101
+ grid.add_row(tool_name_column, summary)
102
+ return grid
103
+
104
+
61
105
  def render_update_plan_tool_call(arguments: str) -> RenderableType:
62
106
  grid = create_grid()
63
107
  tool_name_column = Text.assemble(("◎", ThemeKey.TOOL_MARK), " ", ("Update Plan", ThemeKey.TOOL_NAME))
@@ -391,35 +435,28 @@ def get_truncation_info(tr: events.ToolResultEvent) -> model.TruncationUIExtra |
391
435
  return _extract_truncation(tr.ui_extra)
392
436
 
393
437
 
394
- # Tool name to mark mapping
395
- _TOOL_MARKS: dict[str, str] = {
396
- "Read": "",
397
- "Edit": "",
398
- "Write": "→",
399
- "MultiEdit": "→",
400
- "Bash": ">",
401
- "apply_patch": "→",
402
- "TodoWrite": "◎",
403
- "update_plan": "◎",
404
- "Mermaid": "⧉",
405
- "Memory": "★",
406
- "Skill": "◈",
407
- }
438
+ def render_report_back_tool_call() -> RenderableType:
439
+ grid = create_grid()
440
+ tool_name_column = Text.assemble(("", ThemeKey.TOOL_MARK), " ", ("Report Back", ThemeKey.TOOL_NAME))
441
+ grid.add_row(tool_name_column, "")
442
+ return grid
443
+
408
444
 
409
445
  # Tool name to active form mapping (for spinner status)
410
446
  _TOOL_ACTIVE_FORM: dict[str, str] = {
411
- "Bash": "Bashing",
412
- "apply_patch": "Patching",
413
- "Edit": "Editing",
414
- "MultiEdit": "Editing",
415
- "Read": "Reading",
416
- "Write": "Writing",
417
- "TodoWrite": "Planning",
418
- "update_plan": "Planning",
419
- "Skill": "Skilling",
420
- "Mermaid": "Diagramming",
421
- "Memory": "Memorizing",
422
- "WebFetch": "Fetching",
447
+ tools.BASH: "Bashing",
448
+ tools.APPLY_PATCH: "Patching",
449
+ tools.EDIT: "Editing",
450
+ tools.MULTI_EDIT: "Editing",
451
+ tools.READ: "Reading",
452
+ tools.WRITE: "Writing",
453
+ tools.TODO_WRITE: "Planning",
454
+ tools.UPDATE_PLAN: "Planning",
455
+ tools.SKILL: "Skilling",
456
+ tools.MERMAID: "Diagramming",
457
+ tools.MEMORY: "Memorizing",
458
+ tools.WEB_FETCH: "Fetching",
459
+ tools.REPORT_BACK: "Submitting Report",
423
460
  }
424
461
 
425
462
 
@@ -446,7 +483,6 @@ def render_tool_call(e: events.ToolCallEvent) -> RenderableType | None:
446
483
 
447
484
  Returns a Rich Renderable or None if the tool call should not be rendered.
448
485
  """
449
- from klaude_code.protocol import tools
450
486
 
451
487
  if is_sub_agent_tool(e.tool_name):
452
488
  return None
@@ -461,7 +497,7 @@ def render_tool_call(e: events.ToolCallEvent) -> RenderableType | None:
461
497
  case tools.MULTI_EDIT:
462
498
  return render_multi_edit_tool_call(e.arguments)
463
499
  case tools.BASH:
464
- return render_generic_tool_call(e.tool_name, e.arguments, ">")
500
+ return render_bash_tool_call(e.arguments)
465
501
  case tools.APPLY_PATCH:
466
502
  return render_apply_patch_tool_call(e.arguments)
467
503
  case tools.TODO_WRITE:
@@ -474,6 +510,8 @@ def render_tool_call(e: events.ToolCallEvent) -> RenderableType | None:
474
510
  return render_memory_tool_call(e.arguments)
475
511
  case tools.SKILL:
476
512
  return render_generic_tool_call(e.tool_name, e.arguments, "◈")
513
+ case tools.REPORT_BACK:
514
+ return render_report_back_tool_call()
477
515
  case _:
478
516
  return render_generic_tool_call(e.tool_name, e.arguments)
479
517
 
@@ -489,7 +527,6 @@ def render_tool_result(e: events.ToolResultEvent) -> RenderableType | None:
489
527
 
490
528
  Returns a Rich Renderable or None if the tool result should not be rendered.
491
529
  """
492
- from klaude_code.protocol import tools
493
530
  from klaude_code.ui.renderers import errors as r_errors
494
531
 
495
532
  if is_sub_agent_tool(e.tool_name):