klaude-code 1.2.28__tar.gz → 1.2.30__tar.gz

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 (207) hide show
  1. {klaude_code-1.2.28 → klaude_code-1.2.30}/PKG-INFO +1 -1
  2. {klaude_code-1.2.28 → klaude_code-1.2.30}/pyproject.toml +1 -1
  3. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/cli/config_cmd.py +13 -6
  4. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/cli/list_model.py +1 -1
  5. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/command/__init__.py +3 -0
  6. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/command/clear_cmd.py +4 -1
  7. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/command/command_abc.py +4 -0
  8. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/command/fork_session_cmd.py +2 -2
  9. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/command/model_cmd.py +42 -2
  10. klaude_code-1.2.30/src/klaude_code/command/resume_cmd.py +51 -0
  11. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/command/status_cmd.py +1 -1
  12. klaude_code-1.2.30/src/klaude_code/config/__init__.py +19 -0
  13. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/config/assets/builtin_config.yaml +11 -0
  14. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/config/config.py +21 -17
  15. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/executor.py +42 -8
  16. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/task.py +7 -6
  17. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/shell/bash_tool.py +5 -2
  18. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/web/mermaid_tool.py +2 -2
  19. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/protocol/commands.py +1 -0
  20. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/protocol/model.py +1 -0
  21. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/protocol/op.py +12 -0
  22. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/protocol/op_handler.py +5 -0
  23. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/session/export.py +12 -13
  24. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/session/session.py +2 -2
  25. klaude_code-1.2.30/src/klaude_code/session/templates/mermaid_viewer.html +926 -0
  26. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/event_handler.py +3 -1
  27. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/assistant.py +4 -2
  28. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/common.py +2 -0
  29. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/developer.py +9 -7
  30. klaude_code-1.2.30/src/klaude_code/ui/renderers/mermaid_viewer.py +57 -0
  31. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/metadata.py +1 -0
  32. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/thinking.py +1 -1
  33. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/tools.py +214 -26
  34. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/rich/theme.py +21 -0
  35. klaude_code-1.2.28/src/klaude_code/config/__init__.py +0 -9
  36. {klaude_code-1.2.28 → klaude_code-1.2.30}/README.md +0 -0
  37. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/__init__.py +0 -0
  38. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/auth/__init__.py +0 -0
  39. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/auth/codex/__init__.py +0 -0
  40. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/auth/codex/exceptions.py +0 -0
  41. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/auth/codex/jwt_utils.py +0 -0
  42. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/auth/codex/oauth.py +0 -0
  43. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/auth/codex/token_manager.py +0 -0
  44. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/cli/__init__.py +0 -0
  45. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/cli/auth_cmd.py +0 -0
  46. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/cli/debug.py +0 -0
  47. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/cli/main.py +0 -0
  48. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/cli/runtime.py +0 -0
  49. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/cli/self_update.py +0 -0
  50. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/cli/session_cmd.py +0 -0
  51. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/command/debug_cmd.py +0 -0
  52. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/command/export_cmd.py +0 -0
  53. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/command/export_online_cmd.py +0 -0
  54. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/command/help_cmd.py +0 -0
  55. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/command/prompt-init.md +0 -0
  56. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/command/prompt-jj-describe.md +0 -0
  57. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/command/prompt_command.py +0 -0
  58. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/command/refresh_cmd.py +0 -0
  59. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/command/registry.py +0 -0
  60. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/command/release_notes_cmd.py +0 -0
  61. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/command/terminal_setup_cmd.py +0 -0
  62. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/command/thinking_cmd.py +0 -0
  63. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/config/assets/__init__.py +0 -0
  64. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/config/builtin_config.py +0 -0
  65. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/config/select_model.py +0 -0
  66. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/const.py +0 -0
  67. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/__init__.py +0 -0
  68. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/agent.py +0 -0
  69. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/manager/__init__.py +0 -0
  70. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/manager/llm_clients.py +0 -0
  71. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/manager/llm_clients_builder.py +0 -0
  72. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/manager/sub_agent_manager.py +0 -0
  73. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/prompt.py +0 -0
  74. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-claude-code.md +0 -0
  75. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +0 -0
  76. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +0 -0
  77. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-codex.md +0 -0
  78. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-gemini.md +0 -0
  79. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-minimal.md +0 -0
  80. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-sub-agent-explore.md +0 -0
  81. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-sub-agent-oracle.md +0 -0
  82. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-sub-agent-web.md +0 -0
  83. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/prompts/prompt-sub-agent.md +0 -0
  84. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/reminders.py +0 -0
  85. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/__init__.py +0 -0
  86. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/__init__.py +0 -0
  87. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/_utils.py +0 -0
  88. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/apply_patch.py +0 -0
  89. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/apply_patch_tool.md +0 -0
  90. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/apply_patch_tool.py +0 -0
  91. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/diff_builder.py +0 -0
  92. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/edit_tool.md +0 -0
  93. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/edit_tool.py +0 -0
  94. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/read_tool.md +0 -0
  95. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/read_tool.py +0 -0
  96. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/write_tool.md +0 -0
  97. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/file/write_tool.py +0 -0
  98. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/report_back_tool.py +0 -0
  99. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/shell/__init__.py +0 -0
  100. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/shell/bash_tool.md +0 -0
  101. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/shell/command_safety.py +0 -0
  102. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/skill/__init__.py +0 -0
  103. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/skill/skill_tool.md +0 -0
  104. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/skill/skill_tool.py +0 -0
  105. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/sub_agent_tool.py +0 -0
  106. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/todo/__init__.py +0 -0
  107. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/todo/todo_write_tool.md +0 -0
  108. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/todo/todo_write_tool.py +0 -0
  109. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/todo/todo_write_tool_raw.md +0 -0
  110. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/todo/update_plan_tool.md +0 -0
  111. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/todo/update_plan_tool.py +0 -0
  112. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/tool_abc.py +0 -0
  113. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/tool_context.py +0 -0
  114. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/tool_registry.py +0 -0
  115. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/tool_runner.py +0 -0
  116. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/truncation.py +0 -0
  117. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/web/__init__.py +0 -0
  118. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/web/mermaid_tool.md +0 -0
  119. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/web/web_fetch_tool.md +0 -0
  120. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/web/web_fetch_tool.py +0 -0
  121. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/web/web_search_tool.md +0 -0
  122. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/tool/web/web_search_tool.py +0 -0
  123. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/core/turn.py +0 -0
  124. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/llm/__init__.py +0 -0
  125. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/llm/anthropic/__init__.py +0 -0
  126. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/llm/anthropic/client.py +0 -0
  127. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/llm/anthropic/input.py +0 -0
  128. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/llm/client.py +0 -0
  129. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/llm/codex/__init__.py +0 -0
  130. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/llm/codex/client.py +0 -0
  131. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/llm/input_common.py +0 -0
  132. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
  133. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/llm/openai_compatible/client.py +0 -0
  134. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/llm/openai_compatible/input.py +0 -0
  135. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/llm/openai_compatible/stream.py +0 -0
  136. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/llm/openai_compatible/tool_call_accumulator.py +0 -0
  137. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/llm/openrouter/__init__.py +0 -0
  138. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/llm/openrouter/client.py +0 -0
  139. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/llm/openrouter/input.py +0 -0
  140. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/llm/openrouter/reasoning.py +0 -0
  141. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/llm/registry.py +0 -0
  142. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/llm/responses/__init__.py +0 -0
  143. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/llm/responses/client.py +0 -0
  144. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/llm/responses/input.py +0 -0
  145. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/llm/usage.py +0 -0
  146. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/protocol/__init__.py +0 -0
  147. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/protocol/events.py +0 -0
  148. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/protocol/llm_param.py +0 -0
  149. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/protocol/sub_agent/__init__.py +0 -0
  150. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/protocol/sub_agent/explore.py +0 -0
  151. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/protocol/sub_agent/oracle.py +0 -0
  152. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/protocol/sub_agent/task.py +0 -0
  153. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/protocol/sub_agent/web.py +0 -0
  154. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/protocol/tools.py +0 -0
  155. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/session/__init__.py +0 -0
  156. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/session/codec.py +0 -0
  157. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/session/selector.py +0 -0
  158. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/session/store.py +0 -0
  159. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/session/templates/export_session.html +0 -0
  160. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/skill/__init__.py +0 -0
  161. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/skill/assets/deslop/SKILL.md +0 -0
  162. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/skill/assets/dev-docs/SKILL.md +0 -0
  163. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/skill/assets/handoff/SKILL.md +0 -0
  164. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/skill/assets/jj-workspace/SKILL.md +0 -0
  165. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/skill/assets/skill-creator/SKILL.md +0 -0
  166. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/skill/loader.py +0 -0
  167. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/skill/manager.py +0 -0
  168. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/skill/system_skills.py +0 -0
  169. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/trace/__init__.py +0 -0
  170. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/trace/log.py +0 -0
  171. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/__init__.py +0 -0
  172. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/core/__init__.py +0 -0
  173. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/core/display.py +0 -0
  174. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/core/input.py +0 -0
  175. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/core/stage_manager.py +0 -0
  176. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/modes/__init__.py +0 -0
  177. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/modes/debug/__init__.py +0 -0
  178. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/modes/debug/display.py +0 -0
  179. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/modes/exec/__init__.py +0 -0
  180. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/modes/exec/display.py +0 -0
  181. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/__init__.py +0 -0
  182. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/clipboard.py +0 -0
  183. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/completers.py +0 -0
  184. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/display.py +0 -0
  185. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/input_prompt_toolkit.py +0 -0
  186. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/key_bindings.py +0 -0
  187. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/modes/repl/renderer.py +0 -0
  188. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/__init__.py +0 -0
  189. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/diffs.py +0 -0
  190. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/errors.py +0 -0
  191. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/sub_agent.py +0 -0
  192. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/renderers/user_input.py +0 -0
  193. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/rich/__init__.py +0 -0
  194. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/rich/cjk_wrap.py +0 -0
  195. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/rich/code_panel.py +0 -0
  196. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/rich/live.py +0 -0
  197. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/rich/markdown.py +0 -0
  198. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/rich/quote.py +0 -0
  199. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/rich/searchable_text.py +0 -0
  200. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/rich/status.py +0 -0
  201. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/terminal/__init__.py +0 -0
  202. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/terminal/color.py +0 -0
  203. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/terminal/control.py +0 -0
  204. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/terminal/notifier.py +0 -0
  205. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/terminal/progress_bar.py +0 -0
  206. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/utils/__init__.py +0 -0
  207. {klaude_code-1.2.28 → klaude_code-1.2.30}/src/klaude_code/ui/utils/common.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: klaude-code
3
- Version: 1.2.28
3
+ Version: 1.2.30
4
4
  Summary: Minimal code agent CLI
5
5
  Requires-Dist: anthropic>=0.66.0
6
6
  Requires-Dist: chardet>=5.2.0
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "klaude-code"
7
- version = "1.2.28"
7
+ version = "1.2.30"
8
8
  description = "Minimal code agent CLI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -6,7 +6,7 @@ import sys
6
6
 
7
7
  import typer
8
8
 
9
- from klaude_code.config import config_path, load_config
9
+ from klaude_code.config import config_path, create_example_config, example_config_path, load_config
10
10
  from klaude_code.trace import log
11
11
 
12
12
 
@@ -57,17 +57,24 @@ def edit_config() -> None:
57
57
  else: # Linux and other Unix systems
58
58
  editor = "xdg-open"
59
59
 
60
- # Ensure config file exists
61
- load_config()
60
+ # Ensure config directory exists and create example config if needed
61
+ config_path.parent.mkdir(parents=True, exist_ok=True)
62
+ if create_example_config():
63
+ log(f"Created example config: {example_config_path}", style="dim")
64
+
65
+ # Decide which file to open
66
+ target_path = config_path if config_path.exists() else example_config_path
67
+ if target_path == example_config_path:
68
+ log(f"Opening example config (copy to {config_path.name} to use)", style="yellow")
62
69
 
63
70
  try:
64
71
  if editor == "open -a TextEdit":
65
- subprocess.run(["open", "-a", "TextEdit", str(config_path)], check=True)
72
+ subprocess.run(["open", "-a", "TextEdit", str(target_path)], check=True)
66
73
  elif editor in ["open", "xdg-open"]:
67
74
  # For open/xdg-open, we need to pass the file directly
68
- subprocess.run([editor, str(config_path)], check=True)
75
+ subprocess.run([editor, str(target_path)], check=True)
69
76
  else:
70
- subprocess.run([editor, str(config_path)], check=True)
77
+ subprocess.run([editor, str(target_path)], check=True)
71
78
  except subprocess.CalledProcessError as e:
72
79
  log((f"Error: Failed to open editor: {e}", "red"))
73
80
  raise typer.Exit(1) from None
@@ -218,7 +218,7 @@ def display_models_and_providers(config: Config):
218
218
 
219
219
  panel = Panel(
220
220
  panel_content,
221
- title=Text(f"Provider: {provider.provider_name}", style="white bold"),
221
+ title=Text(f"Provider: {provider.provider_name}", style=ThemeKey.CONFIG_PROVIDER),
222
222
  border_style=ThemeKey.CONFIG_PANEL_BORDER,
223
223
  padding=(0, 1),
224
224
  title_align="left",
@@ -36,6 +36,7 @@ def ensure_commands_loaded() -> None:
36
36
  from .model_cmd import ModelCommand
37
37
  from .refresh_cmd import RefreshTerminalCommand
38
38
  from .release_notes_cmd import ReleaseNotesCommand
39
+ from .resume_cmd import ResumeCommand
39
40
  from .status_cmd import StatusCommand
40
41
  from .terminal_setup_cmd import TerminalSetupCommand
41
42
  from .thinking_cmd import ThinkingCommand
@@ -47,6 +48,7 @@ def ensure_commands_loaded() -> None:
47
48
  register(ThinkingCommand())
48
49
  register(ModelCommand())
49
50
  register(ForkSessionCommand())
51
+ register(ResumeCommand())
50
52
  load_prompt_commands()
51
53
  register(StatusCommand())
52
54
  register(HelpCommand())
@@ -70,6 +72,7 @@ def __getattr__(name: str) -> object:
70
72
  "ModelCommand": "model_cmd",
71
73
  "RefreshTerminalCommand": "refresh_cmd",
72
74
  "ReleaseNotesCommand": "release_notes_cmd",
75
+ "ResumeCommand": "resume_cmd",
73
76
  "StatusCommand": "status_cmd",
74
77
  "TerminalSetupCommand": "terminal_setup_cmd",
75
78
  "ThinkingCommand": "thinking_cmd",
@@ -15,4 +15,7 @@ class ClearCommand(CommandABC):
15
15
 
16
16
  async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
17
17
  del user_input # unused
18
- return CommandResult(operations=[op.ClearSessionOperation(session_id=agent.session.id)])
18
+ return CommandResult(
19
+ operations=[op.ClearSessionOperation(session_id=agent.session.id)],
20
+ persist_user_input=False,
21
+ )
@@ -42,6 +42,10 @@ class CommandResult(BaseModel):
42
42
  ) = None # List of UI events to display immediately
43
43
  operations: list[op.Operation] | None = None
44
44
 
45
+ # Persistence controls: some slash commands are UI/control actions and should not be written to session history.
46
+ persist_user_input: bool = True
47
+ persist_events: bool = True
48
+
45
49
 
46
50
  class CommandABC(ABC):
47
51
  """Abstract base class for slash commands."""
@@ -24,7 +24,7 @@ class ForkSessionCommand(CommandABC):
24
24
  command_output=model.CommandOutput(command_name=self.name),
25
25
  ),
26
26
  )
27
- return CommandResult(events=[event])
27
+ return CommandResult(events=[event], persist_user_input=False, persist_events=False)
28
28
 
29
29
  new_session = agent.session.fork()
30
30
  await new_session.wait_for_flush()
@@ -39,4 +39,4 @@ class ForkSessionCommand(CommandABC):
39
39
  ),
40
40
  ),
41
41
  )
42
- return CommandResult(events=[event])
42
+ return CommandResult(events=[event], persist_user_input=False, persist_events=False)
@@ -1,9 +1,43 @@
1
1
  import asyncio
2
2
 
3
+ import questionary
4
+
3
5
  from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
4
6
  from klaude_code.config.select_model import select_model_from_config
5
7
  from klaude_code.protocol import commands, events, model, op
6
8
 
9
+ SELECT_STYLE = questionary.Style(
10
+ [
11
+ ("instruction", "ansibrightblack"),
12
+ ("pointer", "ansicyan"),
13
+ ("highlighted", "ansicyan"),
14
+ ("text", "ansibrightblack"),
15
+ ]
16
+ )
17
+
18
+
19
+ def _confirm_change_default_model_sync(selected_model: str) -> bool:
20
+ choices: list[questionary.Choice] = [
21
+ questionary.Choice(title="No (session only)", value=False),
22
+ questionary.Choice(title="Yes (save as default)", value=True),
23
+ ]
24
+
25
+ try:
26
+ # Add a blank line between the model selector and this confirmation prompt.
27
+ questionary.print("")
28
+ result = questionary.select(
29
+ message=f"Save '{selected_model}' as default model (main_model)?",
30
+ choices=choices,
31
+ pointer="→",
32
+ instruction="Use arrow keys to move, Enter to select",
33
+ use_jk_keys=False,
34
+ style=SELECT_STYLE,
35
+ ).ask()
36
+ except KeyboardInterrupt:
37
+ return False
38
+
39
+ return bool(result)
40
+
7
41
 
8
42
  class ModelCommand(CommandABC):
9
43
  """Display or change the model configuration."""
@@ -44,7 +78,13 @@ class ModelCommand(CommandABC):
44
78
  )
45
79
  ]
46
80
  )
47
-
81
+ save_as_default = await asyncio.to_thread(_confirm_change_default_model_sync, selected_model)
48
82
  return CommandResult(
49
- operations=[op.ChangeModelOperation(session_id=agent.session.id, model_name=selected_model)]
83
+ operations=[
84
+ op.ChangeModelOperation(
85
+ session_id=agent.session.id,
86
+ model_name=selected_model,
87
+ save_as_default=save_as_default,
88
+ )
89
+ ]
50
90
  )
@@ -0,0 +1,51 @@
1
+ import asyncio
2
+
3
+ from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
4
+ from klaude_code.protocol import commands, events, model, op
5
+ from klaude_code.session.selector import resume_select_session
6
+
7
+
8
+ class ResumeCommand(CommandABC):
9
+ """Resume a previous session."""
10
+
11
+ @property
12
+ def name(self) -> commands.CommandName:
13
+ return commands.CommandName.RESUME
14
+
15
+ @property
16
+ def summary(self) -> str:
17
+ return "Resume a previous session"
18
+
19
+ @property
20
+ def is_interactive(self) -> bool:
21
+ return True
22
+
23
+ async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
24
+ del user_input # unused
25
+
26
+ if agent.session.messages_count > 0:
27
+ event = events.DeveloperMessageEvent(
28
+ session_id=agent.session.id,
29
+ item=model.DeveloperMessageItem(
30
+ content="Cannot resume: current session already has messages. Use `klaude -r` to start a new instance with session selection.",
31
+ command_output=model.CommandOutput(command_name=self.name, is_error=True),
32
+ ),
33
+ )
34
+ return CommandResult(events=[event], persist_user_input=False, persist_events=False)
35
+
36
+ selected_session_id = await asyncio.to_thread(resume_select_session)
37
+ if selected_session_id is None:
38
+ event = events.DeveloperMessageEvent(
39
+ session_id=agent.session.id,
40
+ item=model.DeveloperMessageItem(
41
+ content="(no session selected)",
42
+ command_output=model.CommandOutput(command_name=self.name),
43
+ ),
44
+ )
45
+ return CommandResult(events=[event], persist_user_input=False, persist_events=False)
46
+
47
+ return CommandResult(
48
+ operations=[op.ResumeSessionOperation(target_session_id=selected_session_id)],
49
+ persist_user_input=False,
50
+ persist_events=False,
51
+ )
@@ -152,4 +152,4 @@ class StatusCommand(CommandABC):
152
152
  ),
153
153
  )
154
154
 
155
- return CommandResult(events=[event])
155
+ return CommandResult(events=[event], persist_user_input=False, persist_events=False)
@@ -0,0 +1,19 @@
1
+ from .config import (
2
+ Config,
3
+ UserConfig,
4
+ config_path,
5
+ create_example_config,
6
+ example_config_path,
7
+ load_config,
8
+ print_no_available_models_hint,
9
+ )
10
+
11
+ __all__ = [
12
+ "Config",
13
+ "UserConfig",
14
+ "config_path",
15
+ "create_example_config",
16
+ "example_config_path",
17
+ "load_config",
18
+ "print_no_available_models_hint",
19
+ ]
@@ -53,6 +53,17 @@ provider_list:
53
53
  protocol: openrouter
54
54
  api_key: ${OPENROUTER_API_KEY}
55
55
  model_list:
56
+ - model_name: gpt-5-mini
57
+ model_params:
58
+ model: openai/gpt-5-mini
59
+ max_tokens: 128000
60
+ context_limit: 400000
61
+ thinking:
62
+ reasoning_effort: high
63
+ cost:
64
+ input: 0.25
65
+ output: 2.0
66
+ cache_read: 0.03
56
67
  - model_name: gpt-5.1-codex-max
57
68
  model_params:
58
69
  model: openai/gpt-5.1-codex-max
@@ -51,6 +51,7 @@ def resolve_api_key(value: str | None) -> str | None:
51
51
 
52
52
 
53
53
  config_path = Path.home() / ".klaude" / "klaude-config.yaml"
54
+ example_config_path = Path.home() / ".klaude" / "klaude-config.example.yaml"
54
55
 
55
56
 
56
57
  class ModelConfig(BaseModel):
@@ -227,8 +228,8 @@ class Config(BaseModel):
227
228
  def get_example_config() -> UserConfig:
228
229
  """Generate example config for user reference (will be commented out)."""
229
230
  return UserConfig(
230
- main_model="my-model",
231
- sub_agent_models={"explore": "fast-model", "oracle": "smart-model", "webagent": "fast-model", "task": "opus"},
231
+ main_model="opus",
232
+ sub_agent_models={"explore": "haiku", "oracle": "gpt-5.2", "webagent": "sonnet", "task": "sonnet"},
232
233
  provider_list=[
233
234
  UserProviderConfig(
234
235
  provider_name="my-provider",
@@ -357,30 +358,32 @@ def _load_user_config() -> UserConfig | None:
357
358
  raise ValueError(f"Invalid config file: {config_path}") from e
358
359
 
359
360
 
360
- def _ensure_config_file_exists() -> None:
361
- """Ensure config file exists with commented example."""
362
- if config_path.exists():
363
- return
361
+ def create_example_config() -> bool:
362
+ """Create example config file if it doesn't exist.
363
+
364
+ Returns:
365
+ True if file was created, False if it already exists.
366
+ """
367
+ if example_config_path.exists():
368
+ return False
364
369
 
365
370
  example_config = get_example_config()
366
- config_path.parent.mkdir(parents=True, exist_ok=True)
371
+ example_config_path.parent.mkdir(parents=True, exist_ok=True)
367
372
  config_dict = example_config.model_dump(mode="json", exclude_none=True)
368
373
 
369
- # Comment out all example config lines
370
374
  yaml_str = yaml.dump(config_dict, default_flow_style=False, sort_keys=False) or ""
371
- commented_yaml = "# Custom configuration (optional)\n"
372
- commented_yaml += "# Built-in providers (anthropic, openai, openrouter, deepseek) are available automatically.\n"
373
- commented_yaml += "# Just set the corresponding API key environment variable to use them.\n"
374
- commented_yaml += "#\n"
375
- commented_yaml += "# Example custom provider:\n"
376
- commented_yaml += "\n".join(f"# {line}" if line.strip() else "#" for line in yaml_str.splitlines())
377
- _ = config_path.write_text(commented_yaml)
375
+ header = "# Example configuration for klaude-code\n"
376
+ header += "# Copy this file to klaude-config.yaml and modify as needed.\n"
377
+ header += "# Run `klaude list` to see available models.\n"
378
+ header += "#\n"
379
+ header += "# Built-in providers (anthropic, openai, openrouter, deepseek) are available automatically.\n"
380
+ header += "# Just set the corresponding API key environment variable to use them.\n\n"
381
+ _ = example_config_path.write_text(header + yaml_str)
382
+ return True
378
383
 
379
384
 
380
385
  def _load_config_uncached() -> Config:
381
386
  """Load and merge builtin + user config. Always returns a valid Config."""
382
- _ensure_config_file_exists()
383
-
384
387
  builtin_config = _get_builtin_config()
385
388
  user_config = _load_user_config()
386
389
 
@@ -417,6 +420,7 @@ def print_no_available_models_hint() -> None:
417
420
  log(f" export {env_var}=<your-api-key>", style="dim")
418
421
  log("")
419
422
  log(f"Or add custom providers in: {config_path}", style="dim")
423
+ log(f"See example config: {example_config_path}", style="dim")
420
424
 
421
425
 
422
426
  # Expose cache control for tests and callers that need to invalidate the cache.
@@ -203,13 +203,15 @@ class ExecutorContext:
203
203
  raise ValueError("Multiple RunAgentOperation results are not supported")
204
204
 
205
205
  persisted_user_input = run_ops[0].input if run_ops else user_input
206
- agent.session.append_history(
207
- [model.UserMessageItem(content=persisted_user_input.text, images=persisted_user_input.images)]
208
- )
206
+
207
+ if result.persist_user_input:
208
+ agent.session.append_history(
209
+ [model.UserMessageItem(content=persisted_user_input.text, images=persisted_user_input.images)]
210
+ )
209
211
 
210
212
  if result.events:
211
213
  for evt in result.events:
212
- if isinstance(evt, events.DeveloperMessageEvent):
214
+ if result.persist_events and isinstance(evt, events.DeveloperMessageEvent):
213
215
  agent.session.append_history([evt.item])
214
216
  await self.emit_event(evt)
215
217
 
@@ -237,12 +239,13 @@ class ExecutorContext:
237
239
  agent.session.model_config_name = operation.model_name
238
240
  agent.session.model_thinking = llm_config.thinking
239
241
 
240
- # Save the selection as default main_model
241
- config.main_model = operation.model_name
242
- await config.save()
242
+ if operation.save_as_default:
243
+ config.main_model = operation.model_name
244
+ await config.save()
243
245
 
246
+ default_note = " (saved as default)" if operation.save_as_default else ""
244
247
  developer_item = model.DeveloperMessageItem(
245
- content=f"Switched to: {llm_config.model}",
248
+ content=f"Switched to: {llm_config.model}{default_note}",
246
249
  command_output=model.CommandOutput(command_name=commands.CommandName.MODEL),
247
250
  )
248
251
  agent.session.append_history([developer_item])
@@ -305,6 +308,37 @@ class ExecutorContext:
305
308
  )
306
309
  await self.emit_event(events.DeveloperMessageEvent(session_id=agent.session.id, item=developer_item))
307
310
 
311
+ async def handle_resume_session(self, operation: op.ResumeSessionOperation) -> None:
312
+ target_session = Session.load(operation.target_session_id)
313
+ if (
314
+ target_session.model_thinking is not None
315
+ and target_session.model_name
316
+ and target_session.model_name == self.llm_clients.main.model_name
317
+ ):
318
+ self.llm_clients.main.get_llm_config().thinking = target_session.model_thinking
319
+
320
+ profile = self.model_profile_provider.build_profile(self.llm_clients.main)
321
+ from klaude_code.core.agent import Agent
322
+
323
+ agent = Agent(session=target_session, profile=profile)
324
+
325
+ async for evt in agent.replay_history():
326
+ await self.emit_event(evt)
327
+
328
+ await self.emit_event(
329
+ events.WelcomeEvent(
330
+ work_dir=str(target_session.work_dir),
331
+ llm_config=self.llm_clients.main.get_llm_config(),
332
+ )
333
+ )
334
+
335
+ self._agent = agent
336
+ log_debug(
337
+ f"Resumed session: {target_session.id}",
338
+ style="cyan",
339
+ debug_type=DebugType.EXECUTION,
340
+ )
341
+
308
342
  async def handle_export_session(self, operation: op.ExportSessionOperation) -> None:
309
343
  agent = await self._ensure_agent(operation.session_id)
310
344
  try:
@@ -238,12 +238,13 @@ class TaskExecutor:
238
238
  return
239
239
 
240
240
  if turn is None or turn.task_finished:
241
- # Sub-agent with empty result should retry instead of finishing
242
- if ctx.sub_agent_state is not None and turn is not None and not turn.task_result.strip():
243
- yield events.ErrorEvent(
244
- error_message="Sub-agent returned empty result, retrying...",
245
- can_retry=True,
246
- )
241
+ # Empty result should retry instead of finishing
242
+ if turn is not None and not turn.task_result.strip():
243
+ if ctx.sub_agent_state is not None:
244
+ error_msg = "Sub-agent returned empty result, retrying..."
245
+ else:
246
+ error_msg = "Agent returned empty result, retrying..."
247
+ yield events.ErrorEvent(error_message=error_msg, can_retry=True)
247
248
  continue
248
249
  break
249
250
 
@@ -334,7 +334,9 @@ class BashTool(ToolABC):
334
334
  _best_effort_update_file_tracker(args.command)
335
335
  return model.ToolResultItem(
336
336
  status="success",
337
- output=output.strip(),
337
+ # Preserve leading whitespace for tools like `nl -ba`.
338
+ # Only trim trailing newlines to avoid adding an extra blank line in the UI.
339
+ output=output.rstrip("\n"),
338
340
  )
339
341
  else:
340
342
  combined = ""
@@ -346,7 +348,8 @@ class BashTool(ToolABC):
346
348
  combined = f"Command exited with code {rc}"
347
349
  return model.ToolResultItem(
348
350
  status="error",
349
- output=combined.strip(),
351
+ # Preserve leading whitespace; only trim trailing newlines.
352
+ output=combined.rstrip("\n"),
350
353
  )
351
354
  except FileNotFoundError:
352
355
  return model.ToolResultItem(
@@ -11,7 +11,7 @@ from klaude_code.core.tool.tool_abc import ToolABC, load_desc
11
11
  from klaude_code.core.tool.tool_registry import register
12
12
  from klaude_code.protocol import llm_param, model, tools
13
13
 
14
- _MERMAID_LIVE_PREFIX = "https://mermaid.live/edit#pako:"
14
+ _MERMAID_LIVE_PREFIX = "https://mermaid.live/view#pako:"
15
15
 
16
16
 
17
17
  @register(tools.MERMAID)
@@ -49,7 +49,7 @@ class MermaidTool(ToolABC):
49
49
 
50
50
  link = cls._build_link(args.code)
51
51
  line_count = cls._count_lines(args.code)
52
- ui_extra = model.MermaidLinkUIExtra(link=link, line_count=line_count)
52
+ ui_extra = model.MermaidLinkUIExtra(code=args.code, link=link, line_count=line_count)
53
53
  output = f"Mermaid diagram rendered successfully ({line_count} lines)."
54
54
  return model.ToolResultItem(status="success", output=output, ui_extra=ui_extra)
55
55
 
@@ -16,6 +16,7 @@ class CommandName(str, Enum):
16
16
  RELEASE_NOTES = "release-notes"
17
17
  THINKING = "thinking"
18
18
  FORK_SESSION = "fork-session"
19
+ RESUME = "resume"
19
20
  # PLAN and DOC are dynamically registered now, but kept here if needed for reference
20
21
  # or we can remove them if no code explicitly imports them.
21
22
  # PLAN = "plan"
@@ -127,6 +127,7 @@ class SessionIdUIExtra(BaseModel):
127
127
 
128
128
  class MermaidLinkUIExtra(BaseModel):
129
129
  type: Literal["mermaid_link"] = "mermaid_link"
130
+ code: str = ""
130
131
  link: str
131
132
  line_count: int
132
133
 
@@ -27,6 +27,7 @@ class OperationType(Enum):
27
27
  CHANGE_MODEL = "change_model"
28
28
  CHANGE_THINKING = "change_thinking"
29
29
  CLEAR_SESSION = "clear_session"
30
+ RESUME_SESSION = "resume_session"
30
31
  EXPORT_SESSION = "export_session"
31
32
  INTERRUPT = "interrupt"
32
33
  INIT_AGENT = "init_agent"
@@ -73,6 +74,7 @@ class ChangeModelOperation(Operation):
73
74
  type: OperationType = OperationType.CHANGE_MODEL
74
75
  session_id: str
75
76
  model_name: str
77
+ save_as_default: bool = False
76
78
 
77
79
  async def execute(self, handler: OperationHandler) -> None:
78
80
  await handler.handle_change_model(self)
@@ -98,6 +100,16 @@ class ClearSessionOperation(Operation):
98
100
  await handler.handle_clear_session(self)
99
101
 
100
102
 
103
+ class ResumeSessionOperation(Operation):
104
+ """Operation for resuming a different session."""
105
+
106
+ type: OperationType = OperationType.RESUME_SESSION
107
+ target_session_id: str
108
+
109
+ async def execute(self, handler: OperationHandler) -> None:
110
+ await handler.handle_resume_session(self)
111
+
112
+
101
113
  class ExportSessionOperation(Operation):
102
114
  """Operation for exporting a session transcript to HTML."""
103
115
 
@@ -16,6 +16,7 @@ if TYPE_CHECKING:
16
16
  ExportSessionOperation,
17
17
  InitAgentOperation,
18
18
  InterruptOperation,
19
+ ResumeSessionOperation,
19
20
  RunAgentOperation,
20
21
  UserInputOperation,
21
22
  )
@@ -44,6 +45,10 @@ class OperationHandler(Protocol):
44
45
  """Handle a clear session operation."""
45
46
  ...
46
47
 
48
+ async def handle_resume_session(self, operation: ResumeSessionOperation) -> None:
49
+ """Handle a resume session operation."""
50
+ ...
51
+
47
52
  async def handle_export_session(self, operation: ExportSessionOperation) -> None:
48
53
  """Handle an export session operation."""
49
54
  ...
@@ -421,12 +421,6 @@ 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 _get_diff_ui_extra(ui_extra: model.ToolResultUIExtra | None) -> model.DiffUIExtra | None:
425
- if isinstance(ui_extra, model.DiffUIExtra):
426
- return ui_extra
427
- return None
428
-
429
-
430
424
  def _render_markdown_doc(doc: model.MarkdownDocUIExtra) -> str:
431
425
  encoded = _escape_html(doc.content)
432
426
  file_path = _escape_html(doc.file_path)
@@ -481,21 +475,28 @@ def _build_add_only_diff(text: str, file_path: str) -> model.DiffUIExtra:
481
475
  def _get_mermaid_link_html(
482
476
  ui_extra: model.ToolResultUIExtra | None, tool_call: model.ToolCallItem | None = None
483
477
  ) -> str | None:
484
- if tool_call and tool_call.name == "Mermaid":
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":
485
488
  try:
486
489
  args = json.loads(tool_call.arguments)
487
490
  code = args.get("code", "")
488
491
  except (json.JSONDecodeError, TypeError):
489
492
  code = ""
490
- else:
491
- code = ""
493
+ line_count = code.count("\n") + 1 if code else 0
492
494
 
493
- if not code and not isinstance(ui_extra, model.MermaidLinkUIExtra):
495
+ if not code and not link:
494
496
  return None
495
497
 
496
498
  # Prepare code for rendering and copy
497
499
  escaped_code = _escape_html(code) if code else ""
498
- line_count = code.count("\n") + 1 if code else 0
499
500
 
500
501
  # Build Toolbar
501
502
  toolbar_items: list[str] = []
@@ -512,8 +513,6 @@ def _get_mermaid_link_html(
512
513
  '<button type="button" class="fullscreen-mermaid-btn" title="View Fullscreen">Fullscreen</button>'
513
514
  )
514
515
 
515
- link = ui_extra.link if isinstance(ui_extra, model.MermaidLinkUIExtra) else None
516
-
517
516
  if link:
518
517
  link_url = _escape_html(link)
519
518
  buttons_html.append(
@@ -213,7 +213,7 @@ class Session(BaseModel):
213
213
  forked.file_tracker = {k: v.model_copy(deep=True) for k, v in self.file_tracker.items()}
214
214
  forked.todos = [todo.model_copy(deep=True) for todo in self.todos]
215
215
 
216
- items = [cast(model.ConversationItem, it.model_copy(deep=True)) for it in self.conversation_history]
216
+ items = [it.model_copy(deep=True) for it in self.conversation_history]
217
217
  if items:
218
218
  forked.append_history(items)
219
219
 
@@ -246,7 +246,7 @@ class Session(BaseModel):
246
246
  def need_turn_start(self, prev_item: model.ConversationItem | None, item: model.ConversationItem) -> bool:
247
247
  if not isinstance(
248
248
  item,
249
- model.ReasoningEncryptedItem | model.ReasoningTextItem | model.AssistantMessageItem | model.ToolCallItem,
249
+ model.ReasoningTextItem | model.AssistantMessageItem | model.ToolCallItem,
250
250
  ):
251
251
  return False
252
252
  if prev_item is None: