klaude-code 1.2.27__py3-none-any.whl → 1.2.29__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- klaude_code/cli/config_cmd.py +13 -6
- klaude_code/cli/debug.py +9 -1
- klaude_code/cli/list_model.py +1 -1
- klaude_code/cli/main.py +39 -14
- klaude_code/cli/runtime.py +11 -5
- klaude_code/command/__init__.py +3 -0
- klaude_code/command/export_online_cmd.py +15 -12
- klaude_code/command/fork_session_cmd.py +42 -0
- klaude_code/config/__init__.py +11 -1
- klaude_code/config/config.py +21 -17
- klaude_code/config/select_model.py +1 -0
- klaude_code/core/executor.py +2 -1
- klaude_code/core/reminders.py +52 -16
- klaude_code/core/tool/web/mermaid_tool.md +17 -0
- klaude_code/core/tool/web/mermaid_tool.py +2 -2
- klaude_code/llm/openai_compatible/tool_call_accumulator.py +17 -1
- klaude_code/protocol/commands.py +1 -0
- klaude_code/protocol/model.py +2 -0
- klaude_code/session/export.py +61 -17
- klaude_code/session/session.py +23 -1
- klaude_code/session/templates/mermaid_viewer.html +926 -0
- klaude_code/trace/log.py +7 -1
- klaude_code/ui/modes/repl/__init__.py +3 -44
- klaude_code/ui/modes/repl/completers.py +35 -3
- klaude_code/ui/modes/repl/event_handler.py +9 -5
- klaude_code/ui/modes/repl/input_prompt_toolkit.py +32 -65
- klaude_code/ui/modes/repl/renderer.py +1 -6
- klaude_code/ui/renderers/assistant.py +4 -2
- klaude_code/ui/renderers/common.py +11 -4
- klaude_code/ui/renderers/developer.py +26 -7
- klaude_code/ui/renderers/errors.py +10 -5
- klaude_code/ui/renderers/mermaid_viewer.py +58 -0
- klaude_code/ui/renderers/tools.py +46 -18
- klaude_code/ui/rich/markdown.py +4 -4
- klaude_code/ui/rich/theme.py +12 -2
- {klaude_code-1.2.27.dist-info → klaude_code-1.2.29.dist-info}/METADATA +1 -1
- {klaude_code-1.2.27.dist-info → klaude_code-1.2.29.dist-info}/RECORD +39 -36
- {klaude_code-1.2.27.dist-info → klaude_code-1.2.29.dist-info}/entry_points.txt +1 -0
- {klaude_code-1.2.27.dist-info → klaude_code-1.2.29.dist-info}/WHEEL +0 -0
klaude_code/session/export.py
CHANGED
|
@@ -421,10 +421,39 @@ def _render_diff_span(span: model.DiffSpan, line_kind: str) -> str:
|
|
|
421
421
|
return f'<span class="diff-span">{text}</span>'
|
|
422
422
|
|
|
423
423
|
|
|
424
|
-
def
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
424
|
+
def _render_markdown_doc(doc: model.MarkdownDocUIExtra) -> str:
|
|
425
|
+
encoded = _escape_html(doc.content)
|
|
426
|
+
file_path = _escape_html(doc.file_path)
|
|
427
|
+
header = f'<div class="diff-file">{file_path} <span style="font-weight: normal; color: var(--text-dim); font-size: 12px; margin-left: 8px;">(markdown content)</span></div>'
|
|
428
|
+
|
|
429
|
+
# Using a container that mimics diff-view but for markdown
|
|
430
|
+
content = (
|
|
431
|
+
f'<div class="markdown-content markdown-body" data-raw="{encoded}" '
|
|
432
|
+
f'style="padding: 12px; border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--bg-body); margin-top: 4px;">'
|
|
433
|
+
f'<noscript><pre style="white-space: pre-wrap;">{encoded}</pre></noscript>'
|
|
434
|
+
f"</div>"
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
line_count = doc.content.count("\n") + 1
|
|
438
|
+
open_attr = " open"
|
|
439
|
+
|
|
440
|
+
return (
|
|
441
|
+
f'<details class="diff-collapsible"{open_attr}>'
|
|
442
|
+
f"<summary>File Content ({line_count} lines)</summary>"
|
|
443
|
+
f'<div style="margin-top: 8px;">'
|
|
444
|
+
f"{header}"
|
|
445
|
+
f"{content}"
|
|
446
|
+
f"</div>"
|
|
447
|
+
f"</details>"
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _collect_ui_extras(ui_extra: model.ToolResultUIExtra | None) -> list[model.ToolResultUIExtra]:
|
|
452
|
+
if ui_extra is None:
|
|
453
|
+
return []
|
|
454
|
+
if isinstance(ui_extra, model.MultiUIExtra):
|
|
455
|
+
return list(ui_extra.items)
|
|
456
|
+
return [ui_extra]
|
|
428
457
|
|
|
429
458
|
|
|
430
459
|
def _build_add_only_diff(text: str, file_path: str) -> model.DiffUIExtra:
|
|
@@ -446,21 +475,28 @@ def _build_add_only_diff(text: str, file_path: str) -> model.DiffUIExtra:
|
|
|
446
475
|
def _get_mermaid_link_html(
|
|
447
476
|
ui_extra: model.ToolResultUIExtra | None, tool_call: model.ToolCallItem | None = None
|
|
448
477
|
) -> str | None:
|
|
449
|
-
|
|
478
|
+
code = ""
|
|
479
|
+
link: str | None = None
|
|
480
|
+
line_count = 0
|
|
481
|
+
|
|
482
|
+
if isinstance(ui_extra, model.MermaidLinkUIExtra):
|
|
483
|
+
code = ui_extra.code
|
|
484
|
+
link = ui_extra.link
|
|
485
|
+
line_count = ui_extra.line_count
|
|
486
|
+
|
|
487
|
+
if not code and tool_call and tool_call.name == "Mermaid":
|
|
450
488
|
try:
|
|
451
489
|
args = json.loads(tool_call.arguments)
|
|
452
490
|
code = args.get("code", "")
|
|
453
491
|
except (json.JSONDecodeError, TypeError):
|
|
454
492
|
code = ""
|
|
455
|
-
|
|
456
|
-
code = ""
|
|
493
|
+
line_count = code.count("\n") + 1 if code else 0
|
|
457
494
|
|
|
458
|
-
if not code and not
|
|
495
|
+
if not code and not link:
|
|
459
496
|
return None
|
|
460
497
|
|
|
461
498
|
# Prepare code for rendering and copy
|
|
462
499
|
escaped_code = _escape_html(code) if code else ""
|
|
463
|
-
line_count = code.count("\n") + 1 if code else 0
|
|
464
500
|
|
|
465
501
|
# Build Toolbar
|
|
466
502
|
toolbar_items: list[str] = []
|
|
@@ -477,8 +513,6 @@ def _get_mermaid_link_html(
|
|
|
477
513
|
'<button type="button" class="fullscreen-mermaid-btn" title="View Fullscreen">Fullscreen</button>'
|
|
478
514
|
)
|
|
479
515
|
|
|
480
|
-
link = ui_extra.link if isinstance(ui_extra, model.MermaidLinkUIExtra) else None
|
|
481
|
-
|
|
482
516
|
if link:
|
|
483
517
|
link_url = _escape_html(link)
|
|
484
518
|
buttons_html.append(
|
|
@@ -567,19 +601,26 @@ def _format_tool_call(tool_call: model.ToolCallItem, result: model.ToolResultIte
|
|
|
567
601
|
]
|
|
568
602
|
|
|
569
603
|
if result:
|
|
570
|
-
|
|
571
|
-
|
|
604
|
+
extras = _collect_ui_extras(result.ui_extra)
|
|
605
|
+
|
|
606
|
+
mermaid_extra = next((x for x in extras if isinstance(x, model.MermaidLinkUIExtra)), None)
|
|
607
|
+
mermaid_source = mermaid_extra if mermaid_extra else result.ui_extra
|
|
608
|
+
mermaid_html = _get_mermaid_link_html(mermaid_source, tool_call)
|
|
572
609
|
|
|
573
610
|
should_hide_text = tool_call.name in ("TodoWrite", "update_plan") and result.status != "error"
|
|
574
611
|
|
|
575
|
-
if
|
|
612
|
+
if (
|
|
613
|
+
tool_call.name == "Edit"
|
|
614
|
+
and not any(isinstance(x, model.DiffUIExtra) for x in extras)
|
|
615
|
+
and result.status != "error"
|
|
616
|
+
):
|
|
576
617
|
try:
|
|
577
618
|
args_data = json.loads(tool_call.arguments)
|
|
578
619
|
file_path = args_data.get("file_path", "Unknown file")
|
|
579
620
|
old_string = args_data.get("old_string", "")
|
|
580
621
|
new_string = args_data.get("new_string", "")
|
|
581
622
|
if old_string == "" and new_string:
|
|
582
|
-
|
|
623
|
+
extras.append(_build_add_only_diff(new_string, file_path))
|
|
583
624
|
except (json.JSONDecodeError, TypeError):
|
|
584
625
|
pass
|
|
585
626
|
|
|
@@ -591,8 +632,11 @@ def _format_tool_call(tool_call: model.ToolCallItem, result: model.ToolResultIte
|
|
|
591
632
|
else:
|
|
592
633
|
items_to_render.append(_render_text_block(result.output))
|
|
593
634
|
|
|
594
|
-
|
|
595
|
-
|
|
635
|
+
for extra in extras:
|
|
636
|
+
if isinstance(extra, model.DiffUIExtra):
|
|
637
|
+
items_to_render.append(_render_diff_block(extra))
|
|
638
|
+
elif isinstance(extra, model.MarkdownDocUIExtra):
|
|
639
|
+
items_to_render.append(_render_markdown_doc(extra))
|
|
596
640
|
|
|
597
641
|
if mermaid_html:
|
|
598
642
|
items_to_render.append(mermaid_html)
|
klaude_code/session/session.py
CHANGED
|
@@ -197,6 +197,28 @@ class Session(BaseModel):
|
|
|
197
197
|
)
|
|
198
198
|
self._store.append_and_flush(session_id=self.id, items=items, meta=meta)
|
|
199
199
|
|
|
200
|
+
def fork(self, *, new_id: str | None = None) -> Session:
|
|
201
|
+
"""Create a new session as a fork of the current session.
|
|
202
|
+
|
|
203
|
+
The forked session copies metadata and conversation history, but does not
|
|
204
|
+
modify the current session.
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
forked = Session.create(id=new_id, work_dir=self.work_dir)
|
|
208
|
+
|
|
209
|
+
forked.sub_agent_state = None
|
|
210
|
+
forked.model_name = self.model_name
|
|
211
|
+
forked.model_config_name = self.model_config_name
|
|
212
|
+
forked.model_thinking = self.model_thinking.model_copy(deep=True) if self.model_thinking is not None else None
|
|
213
|
+
forked.file_tracker = {k: v.model_copy(deep=True) for k, v in self.file_tracker.items()}
|
|
214
|
+
forked.todos = [todo.model_copy(deep=True) for todo in self.todos]
|
|
215
|
+
|
|
216
|
+
items = [it.model_copy(deep=True) for it in self.conversation_history]
|
|
217
|
+
if items:
|
|
218
|
+
forked.append_history(items)
|
|
219
|
+
|
|
220
|
+
return forked
|
|
221
|
+
|
|
200
222
|
async def wait_for_flush(self) -> None:
|
|
201
223
|
await self._store.wait_for_flush(self.id)
|
|
202
224
|
|
|
@@ -224,7 +246,7 @@ class Session(BaseModel):
|
|
|
224
246
|
def need_turn_start(self, prev_item: model.ConversationItem | None, item: model.ConversationItem) -> bool:
|
|
225
247
|
if not isinstance(
|
|
226
248
|
item,
|
|
227
|
-
model.
|
|
249
|
+
model.ReasoningTextItem | model.AssistantMessageItem | model.ToolCallItem,
|
|
228
250
|
):
|
|
229
251
|
return False
|
|
230
252
|
if prev_item is None:
|