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
@@ -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
  )
@@ -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
 
@@ -7,7 +7,7 @@ from rich.padding import Padding
7
7
  from rich.text import Text
8
8
 
9
9
  from klaude_code import const
10
- from klaude_code.protocol import events, model
10
+ from klaude_code.protocol import events, model, tools
11
11
  from klaude_code.protocol.sub_agent import is_sub_agent_tool as _is_sub_agent_tool
12
12
  from klaude_code.ui.renderers import diffs as r_diffs
13
13
  from klaude_code.ui.renderers.common import create_grid
@@ -435,35 +435,28 @@ def get_truncation_info(tr: events.ToolResultEvent) -> model.TruncationUIExtra |
435
435
  return _extract_truncation(tr.ui_extra)
436
436
 
437
437
 
438
- # Tool name to mark mapping
439
- _TOOL_MARKS: dict[str, str] = {
440
- "Read": "",
441
- "Edit": "",
442
- "Write": "→",
443
- "MultiEdit": "→",
444
- "Bash": ">",
445
- "apply_patch": "→",
446
- "TodoWrite": "◎",
447
- "update_plan": "◎",
448
- "Mermaid": "⧉",
449
- "Memory": "★",
450
- "Skill": "◈",
451
- }
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
+
452
444
 
453
445
  # Tool name to active form mapping (for spinner status)
454
446
  _TOOL_ACTIVE_FORM: dict[str, str] = {
455
- "Bash": "Bashing",
456
- "apply_patch": "Patching",
457
- "Edit": "Editing",
458
- "MultiEdit": "Editing",
459
- "Read": "Reading",
460
- "Write": "Writing",
461
- "TodoWrite": "Planning",
462
- "update_plan": "Planning",
463
- "Skill": "Skilling",
464
- "Mermaid": "Diagramming",
465
- "Memory": "Memorizing",
466
- "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",
467
460
  }
468
461
 
469
462
 
@@ -490,7 +483,6 @@ def render_tool_call(e: events.ToolCallEvent) -> RenderableType | None:
490
483
 
491
484
  Returns a Rich Renderable or None if the tool call should not be rendered.
492
485
  """
493
- from klaude_code.protocol import tools
494
486
 
495
487
  if is_sub_agent_tool(e.tool_name):
496
488
  return None
@@ -518,6 +510,8 @@ def render_tool_call(e: events.ToolCallEvent) -> RenderableType | None:
518
510
  return render_memory_tool_call(e.arguments)
519
511
  case tools.SKILL:
520
512
  return render_generic_tool_call(e.tool_name, e.arguments, "◈")
513
+ case tools.REPORT_BACK:
514
+ return render_report_back_tool_call()
521
515
  case _:
522
516
  return render_generic_tool_call(e.tool_name, e.arguments)
523
517
 
@@ -533,7 +527,6 @@ def render_tool_result(e: events.ToolResultEvent) -> RenderableType | None:
533
527
 
534
528
  Returns a Rich Renderable or None if the tool result should not be rendered.
535
529
  """
536
- from klaude_code.protocol import tools
537
530
  from klaude_code.ui.renderers import errors as r_errors
538
531
 
539
532
  if is_sub_agent_tool(e.tool_name):
@@ -32,9 +32,9 @@ class NoInsetCodeBlock(CodeBlock):
32
32
  self.lexer_name,
33
33
  theme=self.theme,
34
34
  word_wrap=True,
35
- padding=(0, 1),
35
+ padding=(0, 0),
36
36
  )
37
- yield Panel.fit(syntax, padding=(0, 0), style="markdown.code.block", box=box.SIMPLE)
37
+ yield Panel.fit(syntax, padding=(0, 0), box=box.HORIZONTALS, border_style="markdown.code.border")
38
38
 
39
39
 
40
40
  class ThinkingCodeBlock(CodeBlock):
@@ -42,7 +42,7 @@ class ThinkingCodeBlock(CodeBlock):
42
42
 
43
43
  def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
44
44
  code = str(self.text).rstrip()
45
- text = Text("```\n" + code + "\n```")
45
+ text = Text(code, "markdown.code.block")
46
46
  yield text
47
47
 
48
48
 
@@ -52,7 +52,10 @@ class LeftHeading(Heading):
52
52
  def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
53
53
  text = self.text
54
54
  text.justify = "left" # Override justification
55
- if self.tag == "h2":
55
+ if self.tag == "h1":
56
+ h1_text = text.assemble((" ", "markdown.h1"), text, (" ", "markdown.h1"))
57
+ yield h1_text
58
+ elif self.tag == "h2":
56
59
  text.stylize(Style(bold=True, underline=False))
57
60
  yield Rule(title=text, characters="-", style="markdown.h2.border", align="left")
58
61
  else:
@@ -123,10 +126,6 @@ class MarkdownStream:
123
126
  self.when: float = 0.0 # Timestamp of last update
124
127
  self.min_delay: float = 1.0 / 20 # Minimum time between updates (20fps)
125
128
  self.live_window: int = const.MARKDOWN_STREAM_LIVE_WINDOW
126
- # Track the maximum height the live window has ever reached
127
- # so we only pad when it shrinks from a previous height,
128
- # instead of always padding to live_window from the start.
129
- self._live_window_seen_height: int = 0
130
129
 
131
130
  self.theme = theme
132
131
  self.console = console
@@ -221,18 +220,16 @@ class MarkdownStream:
221
220
  Markdown going to the console works better in terminal scrollback buffers.
222
221
  The live window doesn't play nice with terminal scrollback.
223
222
  """
224
- # On the first call, start the Live renderer
225
223
  if not self._live_started:
226
224
  initial_content = self._live_renderable(Text(""), final=False)
225
+ # transient=False keeps final frame on screen after stop()
227
226
  self.live = Live(
228
227
  initial_content,
229
228
  refresh_per_second=1.0 / self.min_delay,
230
229
  console=self.console,
231
230
  )
232
231
  self.live.start()
233
- # Note: self._live_started is now a property derived from self.live
234
232
 
235
- # If live rendering isn't available (e.g., after a final update), stop.
236
233
  if self.live is None:
237
234
  return
238
235
 
@@ -252,61 +249,36 @@ class MarkdownStream:
252
249
 
253
250
  num_lines = len(lines)
254
251
 
255
- # How many lines have "left" the live window and are now considered stable?
256
- # Or if final, consider all lines to be stable.
257
- if not final:
258
- num_lines = max(num_lines - self.live_window, 0)
252
+ # Reserve last live_window lines for Live area to keep height stable
253
+ num_lines = max(num_lines - self.live_window, 0)
259
254
 
260
- # If there is new stable content, append only the new part
261
- # Update Live window to prevent visual duplication
262
- if final or num_lines > 0:
263
- # Lines to append to stable area
255
+ # Print new stable lines above Live window
256
+ if num_lines > 0:
264
257
  num_printed = len(self.printed)
265
258
  to_append_count = num_lines - num_printed
266
259
 
267
260
  if to_append_count > 0:
268
- # Print new stable lines above Live window
269
261
  append_chunk = lines[num_printed:num_lines]
270
262
  append_chunk_text = Text.from_ansi("".join(append_chunk))
271
263
  live = self.live
272
264
  assert live is not None
273
- live.console.print(append_chunk_text) # Print above Live area
274
-
275
- # Track printed stable lines
265
+ live.console.print(append_chunk_text)
276
266
  self.printed = lines[:num_lines]
277
267
 
278
- # Handle final update cleanup
268
+ rest_lines = lines[num_lines:]
269
+
270
+ # Final: render remaining lines without spinner, then stop Live
279
271
  if final:
280
272
  live = self.live
281
273
  assert live is not None
282
- live.update(Text(""))
274
+ rest = "".join(rest_lines)
275
+ rest_text = Text.from_ansi(rest)
276
+ final_renderable = self._live_renderable(rest_text, final=True)
277
+ live.update(final_renderable)
283
278
  live.stop()
284
279
  self.live = None
285
280
  return
286
281
 
287
- # Update Live window to prevent timing issues
288
- # with console.print above. We pad the live region
289
- # so that its height stays stable when it shrinks
290
- # from a previously reached height, avoiding spinner jitter.
291
- rest_lines = lines[num_lines:]
292
-
293
- if not final:
294
- current_height = len(rest_lines)
295
-
296
- # Update the maximum height we've seen so far for this live window.
297
- if current_height > self._live_window_seen_height:
298
- # Never exceed configured live_window, even if logic changes later.
299
- self._live_window_seen_height = min(current_height, self.live_window)
300
-
301
- target_height = min(self._live_window_seen_height, self.live_window)
302
- if target_height > 0 and current_height < target_height:
303
- # Pad only up to the maximum height we've seen so far.
304
- # This keeps the Live region height stable without overshooting,
305
- # which can cause the spinner to jump by a line.
306
- pad_count = target_height - current_height
307
- # Pad after the existing lines so spinner visually stays at the bottom.
308
- rest_lines = rest_lines + ["\n"] * pad_count
309
-
310
282
  rest = "".join(rest_lines)
311
283
  rest = Text.from_ansi(rest)
312
284
  live = self.live
@@ -2,10 +2,10 @@ from __future__ import annotations
2
2
 
3
3
  import contextlib
4
4
  import math
5
+ import random
5
6
  import time
6
7
 
7
8
  import rich.status as rich_status
8
- from rich._spinners import SPINNERS
9
9
  from rich.color import Color
10
10
  from rich.console import Console, ConsoleOptions, RenderableType, RenderResult
11
11
  from rich.spinner import Spinner as RichSpinner
@@ -17,18 +17,25 @@ from klaude_code import const
17
17
  from klaude_code.ui.rich.theme import ThemeKey
18
18
  from klaude_code.ui.terminal.color import get_last_terminal_background_rgb
19
19
 
20
- BREATHING_SPINNER_NAME = "dot"
20
+ # Use an existing Rich spinner name; BreathingSpinner overrides its rendering
21
+ BREATHING_SPINNER_NAME = "dots"
22
+
23
+ # Alternating glyphs for the breathing spinner - switches at each "transparent" point
24
+ # All glyphs are center-symmetric (point-symmetric)
25
+ _BREATHING_SPINNER_GLYPHS_BASE = [
26
+ # Stars
27
+ "✦",
28
+ "✶",
29
+ "✲",
30
+ "⏺",
31
+ "◆",
32
+ "❖",
33
+ ]
34
+
35
+ # Shuffle glyphs on module load for variety across sessions
36
+ BREATHING_SPINNER_GLYPHS = _BREATHING_SPINNER_GLYPHS_BASE.copy()
37
+ random.shuffle(BREATHING_SPINNER_GLYPHS)
21
38
 
22
- SPINNERS.update(
23
- {
24
- BREATHING_SPINNER_NAME: {
25
- "interval": 100,
26
- # Frames content is ignored by the custom breathing spinner implementation,
27
- # but we keep a single-frame list for correct width measurement.
28
- "frames": ["⏺"],
29
- }
30
- }
31
- )
32
39
 
33
40
  _process_start: float | None = None
34
41
 
@@ -126,6 +133,17 @@ def _breathing_intensity() -> float:
126
133
  return 0.5 * (1.0 - math.cos(2.0 * math.pi * phase))
127
134
 
128
135
 
136
+ def _breathing_glyph() -> str:
137
+ """Get the current glyph for the breathing spinner.
138
+
139
+ Alternates between glyphs at each breath cycle (when intensity reaches 0).
140
+ """
141
+ period = max(const.SPINNER_BREATH_PERIOD_SECONDS, 0.1)
142
+ elapsed = _elapsed_since_start()
143
+ cycle = int(elapsed / period)
144
+ return BREATHING_SPINNER_GLYPHS[cycle % len(BREATHING_SPINNER_GLYPHS)]
145
+
146
+
129
147
  def _breathing_style(console: Console, base_style: Style, intensity: float) -> Style:
130
148
  """Blend a base style's foreground color toward terminal background.
131
149
 
@@ -159,7 +177,7 @@ class ShimmerStatusText:
159
177
  def __init__(self, main_text: str | Text, main_style: ThemeKey) -> None:
160
178
  self._main_text = main_text if isinstance(main_text, Text) else Text(main_text)
161
179
  self._main_style = main_style
162
- self._hint_text = Text(" (esc to interrupt)")
180
+ self._hint_text = Text(const.STATUS_HINT_TEXT)
163
181
  self._hint_style = ThemeKey.STATUS_HINT
164
182
 
165
183
  def __rich_console__(self, console: Console, options: ConsoleOptions) -> RenderResult:
@@ -219,7 +237,7 @@ class BreathingSpinner(RichSpinner):
219
237
  intensity = _breathing_intensity()
220
238
  style = _breathing_style(console, base_style, intensity)
221
239
 
222
- glyph = self.frames[0] if self.frames else "⏺"
240
+ glyph = _breathing_glyph()
223
241
  frame = Text(glyph, style=style)
224
242
 
225
243
  if not self.text:
@@ -29,7 +29,7 @@ class Palette:
29
29
  LIGHT_PALETTE = Palette(
30
30
  red="red",
31
31
  yellow="yellow",
32
- green="spring_green4",
32
+ green="#00875f",
33
33
  cyan="cyan",
34
34
  blue="#3078C5",
35
35
  orange="#d77757",
@@ -38,8 +38,8 @@ LIGHT_PALETTE = Palette(
38
38
  grey2="#93a4b1",
39
39
  grey3="#c4ced4",
40
40
  grey_green="#96a096",
41
- purple="slate_blue3",
42
- lavender="steel_blue",
41
+ purple="#5f5fd7",
42
+ lavender="#5f87af",
43
43
  diff_add="#2e5a32 on #e8f5e9",
44
44
  diff_remove="#5a2e32 on #ffebee",
45
45
  code_theme="ansi_light",
@@ -47,11 +47,11 @@ LIGHT_PALETTE = Palette(
47
47
  )
48
48
 
49
49
  DARK_PALETTE = Palette(
50
- red="indian_red",
50
+ red="#d75f5f",
51
51
  yellow="yellow",
52
- green="sea_green3",
52
+ green="#5fd787",
53
53
  cyan="cyan",
54
- blue="deep_sky_blue1",
54
+ blue="#00afff",
55
55
  orange="#e6704e",
56
56
  magenta="magenta",
57
57
  grey1="#99aabb",
@@ -234,7 +234,7 @@ def get_theme(theme: str | None = None) -> Themes:
234
234
  markdown_theme=Theme(
235
235
  styles={
236
236
  "markdown.code": palette.purple,
237
- "markdown.code.block": "on " + palette.text_background,
237
+ "markdown.code.border": palette.grey3,
238
238
  "markdown.h1": "bold reverse",
239
239
  "markdown.h1.border": palette.grey3,
240
240
  "markdown.h2.border": palette.grey3,
@@ -256,6 +256,7 @@ def get_theme(theme: str | None = None) -> Themes:
256
256
  "markdown.hr": palette.grey3,
257
257
  "markdown.item.bullet": palette.grey2,
258
258
  "markdown.item.number": palette.grey2,
259
+ "markdown.code.block": palette.grey1,
259
260
  "markdown.strong": "bold italic " + palette.grey1,
260
261
  }
261
262
  ),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: klaude-code
3
- Version: 1.2.15
3
+ Version: 1.2.16
4
4
  Summary: Add your description here
5
5
  Requires-Dist: anthropic>=0.66.0
6
6
  Requires-Dist: openai>=1.102.0
@@ -22,6 +22,7 @@ An minimal and opinionated code agent with multi-model support.
22
22
  ## Key Features
23
23
  - **Adaptive Tooling**: Model-aware toolsets (Claude Code tools for Sonnet, Codex `apply_patch` for GPT-5.1/Codex).
24
24
  - **Multi-Provider Support**: Compatible with `anthropic-messages-api`,`openai-responses-api`, and `openai-compatible-api`(`openrouter-api`), featuring interleaved thinking, OpenRouter's provider sorting etc.
25
+ - **Structured Sub-Agent Output**: Main agent defines output JSON schema for sub-agents; sub-agents use `report_back` tool with constrained decoding to return schema-compliant structured data.
25
26
  - **Skill System**: Extensible support for loading Claude Skills.
26
27
  - **Session Management**: Robust context preservation with resumable sessions (`--continue`).
27
28
  - **Simple TUI**: Clean interface offering full visibility into model responses, reasoning and actions.
@@ -107,7 +108,7 @@ model_list:
107
108
  provider_routing:
108
109
  sort: throughput
109
110
  main_model: gpt-5.1-codex
110
- subagent_models:
111
+ sub_agent_models:
111
112
  oracle: gpt-5.1-high
112
113
  explore: haiku
113
114
  task: sonnet