klaude-code 1.7.0__tar.gz → 1.7.1__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 (217) hide show
  1. {klaude_code-1.7.0 → klaude_code-1.7.1}/PKG-INFO +50 -6
  2. {klaude_code-1.7.0 → klaude_code-1.7.1}/README.md +49 -5
  3. {klaude_code-1.7.0 → klaude_code-1.7.1}/pyproject.toml +1 -1
  4. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/cli/main.py +10 -0
  5. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/cli/runtime.py +2 -2
  6. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/command/fork_session_cmd.py +7 -0
  7. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/config/assets/builtin_config.yaml +24 -0
  8. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/config/config.py +5 -0
  9. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/const.py +17 -2
  10. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/executor.py +16 -3
  11. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/task.py +5 -3
  12. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/shell/command_safety.py +3 -5
  13. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/protocol/events.py +1 -0
  14. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/session/export.py +14 -2
  15. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/session/session.py +49 -2
  16. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/session/store.py +3 -0
  17. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/session/templates/export_session.html +210 -18
  18. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/modes/repl/input_prompt_toolkit.py +6 -46
  19. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/modes/repl/renderer.py +5 -1
  20. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/renderers/developer.py +1 -1
  21. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/renderers/sub_agent.py +1 -1
  22. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/__init__.py +0 -0
  23. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/auth/__init__.py +0 -0
  24. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/auth/codex/__init__.py +0 -0
  25. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/auth/codex/exceptions.py +0 -0
  26. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/auth/codex/jwt_utils.py +0 -0
  27. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/auth/codex/oauth.py +0 -0
  28. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/auth/codex/token_manager.py +0 -0
  29. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/cli/__init__.py +0 -0
  30. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/cli/auth_cmd.py +0 -0
  31. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/cli/config_cmd.py +0 -0
  32. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/cli/debug.py +0 -0
  33. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/cli/list_model.py +0 -0
  34. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/cli/self_update.py +0 -0
  35. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/cli/session_cmd.py +0 -0
  36. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/command/__init__.py +0 -0
  37. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/command/clear_cmd.py +0 -0
  38. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/command/command_abc.py +0 -0
  39. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/command/debug_cmd.py +0 -0
  40. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/command/export_cmd.py +0 -0
  41. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/command/export_online_cmd.py +0 -0
  42. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/command/help_cmd.py +0 -0
  43. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/command/model_cmd.py +0 -0
  44. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/command/model_select.py +0 -0
  45. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/command/prompt-init.md +0 -0
  46. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/command/prompt-jj-describe.md +0 -0
  47. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/command/prompt_command.py +0 -0
  48. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/command/refresh_cmd.py +0 -0
  49. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/command/registry.py +0 -0
  50. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/command/release_notes_cmd.py +0 -0
  51. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/command/resume_cmd.py +0 -0
  52. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/command/status_cmd.py +0 -0
  53. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/command/terminal_setup_cmd.py +0 -0
  54. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/command/thinking_cmd.py +0 -0
  55. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/config/__init__.py +0 -0
  56. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/config/assets/__init__.py +0 -0
  57. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/config/builtin_config.py +0 -0
  58. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/config/select_model.py +0 -0
  59. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/config/thinking.py +0 -0
  60. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/__init__.py +0 -0
  61. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/agent.py +0 -0
  62. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/manager/__init__.py +0 -0
  63. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/manager/llm_clients.py +0 -0
  64. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/manager/llm_clients_builder.py +0 -0
  65. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/manager/sub_agent_manager.py +0 -0
  66. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/prompt.py +0 -0
  67. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/prompts/prompt-claude-code.md +0 -0
  68. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +0 -0
  69. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +0 -0
  70. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/prompts/prompt-codex.md +0 -0
  71. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/prompts/prompt-gemini.md +0 -0
  72. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/prompts/prompt-minimal.md +0 -0
  73. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/prompts/prompt-sub-agent-explore.md +0 -0
  74. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/prompts/prompt-sub-agent-oracle.md +0 -0
  75. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/prompts/prompt-sub-agent-web.md +0 -0
  76. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/prompts/prompt-sub-agent.md +0 -0
  77. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/reminders.py +0 -0
  78. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/__init__.py +0 -0
  79. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/file/__init__.py +0 -0
  80. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/file/_utils.py +0 -0
  81. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/file/apply_patch.py +0 -0
  82. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/file/apply_patch_tool.md +0 -0
  83. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/file/apply_patch_tool.py +0 -0
  84. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/file/diff_builder.py +0 -0
  85. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/file/edit_tool.md +0 -0
  86. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/file/edit_tool.py +0 -0
  87. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/file/move_tool.md +0 -0
  88. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/file/move_tool.py +0 -0
  89. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/file/read_tool.md +0 -0
  90. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/file/read_tool.py +0 -0
  91. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/file/write_tool.md +0 -0
  92. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/file/write_tool.py +0 -0
  93. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/report_back_tool.py +0 -0
  94. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/shell/__init__.py +0 -0
  95. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/shell/bash_tool.md +0 -0
  96. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/shell/bash_tool.py +0 -0
  97. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/skill/__init__.py +0 -0
  98. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/skill/skill_tool.md +0 -0
  99. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/skill/skill_tool.py +0 -0
  100. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/sub_agent_tool.py +0 -0
  101. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/todo/__init__.py +0 -0
  102. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/todo/todo_write_tool.md +0 -0
  103. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/todo/todo_write_tool.py +0 -0
  104. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/todo/todo_write_tool_raw.md +0 -0
  105. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/todo/update_plan_tool.md +0 -0
  106. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/todo/update_plan_tool.py +0 -0
  107. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/tool_abc.py +0 -0
  108. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/tool_context.py +0 -0
  109. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/tool_registry.py +0 -0
  110. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/tool_runner.py +0 -0
  111. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/truncation.py +0 -0
  112. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/web/__init__.py +0 -0
  113. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/web/mermaid_tool.md +0 -0
  114. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/web/mermaid_tool.py +0 -0
  115. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/web/web_fetch_tool.md +0 -0
  116. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/web/web_fetch_tool.py +0 -0
  117. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/web/web_search_tool.md +0 -0
  118. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/tool/web/web_search_tool.py +0 -0
  119. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/core/turn.py +0 -0
  120. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/__init__.py +0 -0
  121. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/anthropic/__init__.py +0 -0
  122. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/anthropic/client.py +0 -0
  123. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/anthropic/input.py +0 -0
  124. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/bedrock/__init__.py +0 -0
  125. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/bedrock/client.py +0 -0
  126. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/client.py +0 -0
  127. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/codex/__init__.py +0 -0
  128. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/codex/client.py +0 -0
  129. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/google/__init__.py +0 -0
  130. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/google/client.py +0 -0
  131. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/google/input.py +0 -0
  132. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/input_common.py +0 -0
  133. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
  134. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/openai_compatible/client.py +0 -0
  135. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/openai_compatible/input.py +0 -0
  136. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/openai_compatible/stream.py +0 -0
  137. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/openai_compatible/tool_call_accumulator.py +0 -0
  138. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/openrouter/__init__.py +0 -0
  139. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/openrouter/client.py +0 -0
  140. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/openrouter/input.py +0 -0
  141. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/openrouter/reasoning.py +0 -0
  142. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/registry.py +0 -0
  143. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/responses/__init__.py +0 -0
  144. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/responses/client.py +0 -0
  145. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/responses/input.py +0 -0
  146. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/llm/usage.py +0 -0
  147. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/protocol/__init__.py +0 -0
  148. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/protocol/commands.py +0 -0
  149. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/protocol/llm_param.py +0 -0
  150. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/protocol/model.py +0 -0
  151. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/protocol/op.py +0 -0
  152. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/protocol/op_handler.py +0 -0
  153. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/protocol/sub_agent/__init__.py +0 -0
  154. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/protocol/sub_agent/explore.py +0 -0
  155. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/protocol/sub_agent/oracle.py +0 -0
  156. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/protocol/sub_agent/task.py +0 -0
  157. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/protocol/sub_agent/web.py +0 -0
  158. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/protocol/tools.py +0 -0
  159. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/session/__init__.py +0 -0
  160. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/session/codec.py +0 -0
  161. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/session/selector.py +0 -0
  162. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/session/templates/mermaid_viewer.html +0 -0
  163. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/skill/__init__.py +0 -0
  164. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/skill/assets/deslop/SKILL.md +0 -0
  165. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/skill/assets/dev-docs/SKILL.md +0 -0
  166. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/skill/assets/handoff/SKILL.md +0 -0
  167. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/skill/assets/jj-workspace/SKILL.md +0 -0
  168. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/skill/assets/skill-creator/SKILL.md +0 -0
  169. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/skill/loader.py +0 -0
  170. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/skill/manager.py +0 -0
  171. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/skill/system_skills.py +0 -0
  172. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/trace/__init__.py +0 -0
  173. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/trace/log.py +0 -0
  174. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/__init__.py +0 -0
  175. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/core/__init__.py +0 -0
  176. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/core/display.py +0 -0
  177. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/core/input.py +0 -0
  178. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/core/stage_manager.py +0 -0
  179. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/modes/__init__.py +0 -0
  180. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/modes/debug/__init__.py +0 -0
  181. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/modes/debug/display.py +0 -0
  182. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/modes/exec/__init__.py +0 -0
  183. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/modes/exec/display.py +0 -0
  184. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/modes/repl/__init__.py +0 -0
  185. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/modes/repl/clipboard.py +0 -0
  186. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/modes/repl/completers.py +0 -0
  187. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/modes/repl/display.py +0 -0
  188. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/modes/repl/event_handler.py +0 -0
  189. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/modes/repl/key_bindings.py +0 -0
  190. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/renderers/__init__.py +0 -0
  191. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/renderers/assistant.py +0 -0
  192. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/renderers/bash_syntax.py +0 -0
  193. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/renderers/common.py +0 -0
  194. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/renderers/diffs.py +0 -0
  195. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/renderers/errors.py +0 -0
  196. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/renderers/mermaid_viewer.py +0 -0
  197. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/renderers/metadata.py +0 -0
  198. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/renderers/thinking.py +0 -0
  199. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/renderers/tools.py +0 -0
  200. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/renderers/user_input.py +0 -0
  201. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/rich/__init__.py +0 -0
  202. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/rich/cjk_wrap.py +0 -0
  203. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/rich/code_panel.py +0 -0
  204. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/rich/live.py +0 -0
  205. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/rich/markdown.py +0 -0
  206. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/rich/quote.py +0 -0
  207. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/rich/searchable_text.py +0 -0
  208. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/rich/status.py +0 -0
  209. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/rich/theme.py +0 -0
  210. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/terminal/__init__.py +0 -0
  211. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/terminal/color.py +0 -0
  212. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/terminal/control.py +0 -0
  213. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/terminal/notifier.py +0 -0
  214. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/terminal/progress_bar.py +0 -0
  215. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/terminal/selector.py +0 -0
  216. {klaude_code-1.7.0 → klaude_code-1.7.1}/src/klaude_code/ui/utils/__init__.py +0 -0
  217. {klaude_code-1.7.0 → klaude_code-1.7.1}/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.7.0
3
+ Version: 1.7.1
4
4
  Summary: Minimal code agent CLI
5
5
  Requires-Dist: anthropic>=0.66.0
6
6
  Requires-Dist: chardet>=5.2.0
@@ -146,20 +146,62 @@ klaude config
146
146
 
147
147
  ##### Adding Models to Built-in Providers
148
148
 
149
- You can add custom models to existing providers without redefining the entire provider:
149
+ You can add custom models to existing built-in providers without redefining the entire provider. Just reference the `provider_name` and add your `model_list`:
150
150
 
151
151
  ```yaml
152
- # Just specify provider_name and your new models - no need for protocol/api_key
152
+ # ~/.klaude/klaude-config.yaml
153
153
  provider_list:
154
+ - provider_name: openrouter # Reference existing built-in provider
155
+ model_list:
156
+ - model_name: seed
157
+ model_params:
158
+ model: bytedance-seed/seed-1.6 # Model ID from OpenRouter
159
+ context_limit: 262000
160
+ cost:
161
+ input: 0.25
162
+ output: 2
163
+ ```
164
+
165
+ **How merging works:**
166
+ - Your models are merged with the built-in models for that provider
167
+ - You only need `provider_name` and `model_list` - protocol, api_key, etc. are inherited from the built-in config
168
+ - To override a built-in model, use the same `model_name` (e.g., `sonnet` to customize the built-in sonnet)
169
+
170
+ **More examples:**
171
+
172
+ ```yaml
173
+ provider_list:
174
+ # Add multiple models to OpenRouter
154
175
  - provider_name: openrouter
155
176
  model_list:
156
- - model_name: my-custom-model
177
+ - model_name: qwen-coder
178
+ model_params:
179
+ model: qwen/qwen-2.5-coder-32b-instruct
180
+ context_limit: 131072
181
+ cost:
182
+ input: 0.3
183
+ output: 0.9
184
+ - model_name: llama-405b
185
+ model_params:
186
+ model: meta-llama/llama-3.1-405b-instruct
187
+ context_limit: 131072
188
+ cost:
189
+ input: 0.8
190
+ output: 0.8
191
+
192
+ # Add models to Anthropic provider
193
+ - provider_name: anthropic
194
+ model_list:
195
+ - model_name: haiku@ant
157
196
  model_params:
158
- model: some-provider/some-model-id
197
+ model: claude-3-5-haiku-20241022
159
198
  context_limit: 200000
199
+ cost:
200
+ input: 1.0
201
+ output: 5.0
160
202
  ```
161
203
 
162
- Your models are merged with built-in models. To override a built-in model, use the same `model_name`.
204
+ After adding models, run `klaude list` to verify they appear in the model list.
163
205
 
164
206
  ##### Overriding Provider Settings
165
207
 
@@ -230,6 +272,8 @@ provider_list:
230
272
  - `openai` - OpenAI-compatible API
231
273
  - `responses` - OpenAI Responses API (for o-series, GPT-5, Codex)
232
274
  - `openrouter` - OpenRouter API
275
+ - `google` - Google Gemini API
276
+ - `bedrock` - AWS Bedrock (uses AWS credentials instead of api_key)
233
277
  - `codex` - OpenAI Codex CLI (OAuth-based)
234
278
 
235
279
  List configured providers and models:
@@ -125,20 +125,62 @@ klaude config
125
125
 
126
126
  ##### Adding Models to Built-in Providers
127
127
 
128
- You can add custom models to existing providers without redefining the entire provider:
128
+ You can add custom models to existing built-in providers without redefining the entire provider. Just reference the `provider_name` and add your `model_list`:
129
129
 
130
130
  ```yaml
131
- # Just specify provider_name and your new models - no need for protocol/api_key
131
+ # ~/.klaude/klaude-config.yaml
132
132
  provider_list:
133
+ - provider_name: openrouter # Reference existing built-in provider
134
+ model_list:
135
+ - model_name: seed
136
+ model_params:
137
+ model: bytedance-seed/seed-1.6 # Model ID from OpenRouter
138
+ context_limit: 262000
139
+ cost:
140
+ input: 0.25
141
+ output: 2
142
+ ```
143
+
144
+ **How merging works:**
145
+ - Your models are merged with the built-in models for that provider
146
+ - You only need `provider_name` and `model_list` - protocol, api_key, etc. are inherited from the built-in config
147
+ - To override a built-in model, use the same `model_name` (e.g., `sonnet` to customize the built-in sonnet)
148
+
149
+ **More examples:**
150
+
151
+ ```yaml
152
+ provider_list:
153
+ # Add multiple models to OpenRouter
133
154
  - provider_name: openrouter
134
155
  model_list:
135
- - model_name: my-custom-model
156
+ - model_name: qwen-coder
157
+ model_params:
158
+ model: qwen/qwen-2.5-coder-32b-instruct
159
+ context_limit: 131072
160
+ cost:
161
+ input: 0.3
162
+ output: 0.9
163
+ - model_name: llama-405b
164
+ model_params:
165
+ model: meta-llama/llama-3.1-405b-instruct
166
+ context_limit: 131072
167
+ cost:
168
+ input: 0.8
169
+ output: 0.8
170
+
171
+ # Add models to Anthropic provider
172
+ - provider_name: anthropic
173
+ model_list:
174
+ - model_name: haiku@ant
136
175
  model_params:
137
- model: some-provider/some-model-id
176
+ model: claude-3-5-haiku-20241022
138
177
  context_limit: 200000
178
+ cost:
179
+ input: 1.0
180
+ output: 5.0
139
181
  ```
140
182
 
141
- Your models are merged with built-in models. To override a built-in model, use the same `model_name`.
183
+ After adding models, run `klaude list` to verify they appear in the model list.
142
184
 
143
185
  ##### Overriding Provider Settings
144
186
 
@@ -209,6 +251,8 @@ provider_list:
209
251
  - `openai` - OpenAI-compatible API
210
252
  - `responses` - OpenAI Responses API (for o-series, GPT-5, Codex)
211
253
  - `openrouter` - OpenRouter API
254
+ - `google` - Google Gemini API
255
+ - `bedrock` - AWS Bedrock (uses AWS credentials instead of api_key)
212
256
  - `codex` - OpenAI Codex CLI (OAuth-based)
213
257
 
214
258
  List configured providers and models:
@@ -4,7 +4,7 @@ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "klaude-code"
7
- version = "1.7.0"
7
+ version = "1.7.1"
8
8
  description = "Minimal code agent CLI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -95,10 +95,20 @@ def read_input_content(cli_argument: str) -> str | None:
95
95
  return content
96
96
 
97
97
 
98
+ ENV_HELP = """\
99
+ Environment Variables:
100
+
101
+ KLAUDE_READ_GLOBAL_LINE_CAP Max lines to read (default: 2000)
102
+
103
+ KLAUDE_READ_MAX_CHARS Max total chars to read (default: 50000)
104
+ """
105
+
98
106
  app = typer.Typer(
99
107
  add_completion=False,
100
108
  pretty_exceptions_enable=False,
101
109
  no_args_is_help=False,
110
+ rich_markup_mode="rich",
111
+ epilog=ENV_HELP,
102
112
  )
103
113
 
104
114
  # Register subcommands from modules
@@ -379,7 +379,7 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
379
379
  model_name=model_name,
380
380
  save_as_default=False,
381
381
  defer_thinking_selection=True,
382
- emit_welcome_event=False,
382
+ emit_welcome_event=True,
383
383
  emit_switch_message=False,
384
384
  )
385
385
  )
@@ -398,7 +398,7 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
398
398
  op.ChangeThinkingOperation(
399
399
  session_id=sid,
400
400
  thinking=thinking,
401
- emit_welcome_event=False,
401
+ emit_welcome_event=True,
402
402
  emit_switch_message=False,
403
403
  )
404
404
  )
@@ -7,6 +7,7 @@ from prompt_toolkit.styles import Style
7
7
 
8
8
  from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
9
9
  from klaude_code.protocol import commands, events, model
10
+ from klaude_code.ui.modes.repl.clipboard import copy_to_clipboard
10
11
  from klaude_code.ui.terminal.selector import SelectItem, select_one
11
12
 
12
13
  FORK_SELECT_STYLE = Style(
@@ -215,6 +216,9 @@ class ForkSessionCommand(CommandABC):
215
216
  new_session = agent.session.fork()
216
217
  await new_session.wait_for_flush()
217
218
 
219
+ resume_cmd = f"klaude --resume-by-id {new_session.id}"
220
+ copy_to_clipboard(resume_cmd)
221
+
218
222
  event = events.DeveloperMessageEvent(
219
223
  session_id=agent.session.id,
220
224
  item=model.DeveloperMessageItem(
@@ -247,6 +251,9 @@ class ForkSessionCommand(CommandABC):
247
251
  # Build result message
248
252
  fork_description = "entire conversation" if selected is None else f"up to message index {selected}"
249
253
 
254
+ resume_cmd = f"klaude --resume-by-id {new_session.id}"
255
+ copy_to_clipboard(resume_cmd)
256
+
250
257
  event = events.DeveloperMessageEvent(
251
258
  session_id=agent.session.id,
252
259
  item=model.DeveloperMessageItem(
@@ -87,6 +87,30 @@ provider_list:
87
87
  input: 1.75
88
88
  output: 14.0
89
89
  cache_read: 0.17
90
+ - model_name: gpt-5.2-medium
91
+ model_params:
92
+ model: openai/gpt-5.2
93
+ max_tokens: 128000
94
+ context_limit: 400000
95
+ verbosity: high
96
+ thinking:
97
+ reasoning_effort: medium
98
+ cost:
99
+ input: 1.75
100
+ output: 14.0
101
+ cache_read: 0.17
102
+ - model_name: gpt-5.2-low
103
+ model_params:
104
+ model: openai/gpt-5.2
105
+ max_tokens: 128000
106
+ context_limit: 400000
107
+ verbosity: low
108
+ thinking:
109
+ reasoning_effort: low
110
+ cost:
111
+ input: 1.75
112
+ output: 14.0
113
+ cache_read: 0.17
90
114
  - model_name: gpt-5.2-fast
91
115
  model_params:
92
116
  model: openai/gpt-5.2
@@ -257,6 +257,11 @@ def get_example_config() -> UserConfig:
257
257
  model="model-id-from-provider",
258
258
  max_tokens=16000,
259
259
  context_limit=200000,
260
+ cost=llm_param.Cost(
261
+ input=1,
262
+ output=10,
263
+ cache_read=0.1,
264
+ ),
260
265
  ),
261
266
  ),
262
267
  ],
@@ -4,8 +4,21 @@ This module consolidates all magic numbers and configuration values
4
4
  that were previously scattered across the codebase.
5
5
  """
6
6
 
7
+ import os
7
8
  from pathlib import Path
8
9
 
10
+
11
+ def _get_int_env(name: str, default: int) -> int:
12
+ """Get an integer value from environment variable, or return default."""
13
+ val = os.environ.get(name)
14
+ if val is None:
15
+ return default
16
+ try:
17
+ return int(val)
18
+ except ValueError:
19
+ return default
20
+
21
+
9
22
  # =============================================================================
10
23
  # Agent Configuration
11
24
  # =============================================================================
@@ -47,10 +60,12 @@ TODO_REMINDER_TOOL_CALL_THRESHOLD = 10
47
60
  READ_CHAR_LIMIT_PER_LINE = 2000
48
61
 
49
62
  # Maximum number of lines to read from a file
50
- READ_GLOBAL_LINE_CAP = 2000
63
+ # Can be overridden via KLAUDE_READ_GLOBAL_LINE_CAP environment variable
64
+ READ_GLOBAL_LINE_CAP = _get_int_env("KLAUDE_READ_GLOBAL_LINE_CAP", 2000)
51
65
 
52
66
  # Maximum total characters to read (truncates beyond this limit)
53
- READ_MAX_CHARS = 50000
67
+ # Can be overridden via KLAUDE_READ_MAX_CHARS environment variable
68
+ READ_MAX_CHARS = _get_int_env("KLAUDE_READ_MAX_CHARS", 50000)
54
69
 
55
70
  # Maximum image file size in bytes (4MB)
56
71
  READ_MAX_IMAGE_BYTES = 4 * 1024 * 1024
@@ -9,6 +9,7 @@ from __future__ import annotations
9
9
 
10
10
  import asyncio
11
11
  import subprocess
12
+ import sys
12
13
  from collections.abc import Callable
13
14
  from dataclasses import dataclass
14
15
  from pathlib import Path
@@ -427,14 +428,26 @@ class ExecutorContext:
427
428
  return build_export_html(agent.session, system_prompt, tool_schemas, model_name)
428
429
 
429
430
  def _open_file(self, path: Path) -> None:
431
+ # Select platform-appropriate command
432
+ if sys.platform == "darwin":
433
+ cmd = "open"
434
+ elif sys.platform == "win32":
435
+ cmd = "start"
436
+ else:
437
+ cmd = "xdg-open"
438
+
430
439
  try:
431
440
  # Detach stdin to prevent interference with prompt_toolkit's terminal state
432
- subprocess.run(["open", str(path)], stdin=subprocess.DEVNULL, check=True)
441
+ if sys.platform == "win32":
442
+ # Windows 'start' requires shell=True
443
+ subprocess.run(f'start "" "{path}"', shell=True, stdin=subprocess.DEVNULL, check=True)
444
+ else:
445
+ subprocess.run([cmd, str(path)], stdin=subprocess.DEVNULL, check=True)
433
446
  except FileNotFoundError as exc: # pragma: no cover
434
- msg = "`open` command not found; please open the HTML manually."
447
+ msg = f"`{cmd}` command not found; please open the HTML manually."
435
448
  raise RuntimeError(msg) from exc
436
449
  except subprocess.CalledProcessError as exc: # pragma: no cover
437
- msg = f"Failed to open HTML with `open`: {exc}"
450
+ msg = f"Failed to open HTML with `{cmd}`: {exc}"
438
451
  raise RuntimeError(msg) from exc
439
452
 
440
453
  async def handle_interrupt(self, operation: op.InterruptOperation) -> None:
@@ -220,7 +220,9 @@ class TaskExecutor:
220
220
  error_msg = f"Retrying {attempt + 1}/{const.MAX_FAILED_TURN_RETRIES} in {delay:.1f}s"
221
221
  if last_error_message:
222
222
  error_msg = f"{error_msg} - {last_error_message}"
223
- yield events.ErrorEvent(error_message=error_msg, can_retry=True)
223
+ yield events.ErrorEvent(
224
+ error_message=error_msg, can_retry=True, session_id=session_ctx.session_id
225
+ )
224
226
  await asyncio.sleep(delay)
225
227
  finally:
226
228
  self._current_turn = None
@@ -234,7 +236,7 @@ class TaskExecutor:
234
236
  final_error = f"Turn failed after {const.MAX_FAILED_TURN_RETRIES} retries."
235
237
  if last_error_message:
236
238
  final_error = f"{last_error_message}\n{final_error}"
237
- yield events.ErrorEvent(error_message=final_error, can_retry=False)
239
+ yield events.ErrorEvent(error_message=final_error, can_retry=False, session_id=session_ctx.session_id)
238
240
  return
239
241
 
240
242
  if turn is None or turn.task_finished:
@@ -244,7 +246,7 @@ class TaskExecutor:
244
246
  error_msg = "Sub-agent returned empty result, retrying..."
245
247
  else:
246
248
  error_msg = "Agent returned empty result, retrying..."
247
- yield events.ErrorEvent(error_message=error_msg, can_retry=True)
249
+ yield events.ErrorEvent(error_message=error_msg, can_retry=True, session_id=session_ctx.session_id)
248
250
  continue
249
251
  break
250
252
 
@@ -275,12 +275,10 @@ def _is_safe_argv(argv: list[str]) -> SafetyCheckResult:
275
275
  "tag",
276
276
  "clone",
277
277
  "worktree",
278
+ "push",
279
+ "pull",
280
+ "remote",
278
281
  }
279
- # Block remote operations
280
- blocked_git_cmds = {"push", "pull", "remote"}
281
-
282
- if sub in blocked_git_cmds:
283
- return SafetyCheckResult(False, f"git: Remote operation '{sub}' not allowed")
284
282
  if sub not in allowed_git_cmds:
285
283
  return SafetyCheckResult(False, f"git: Subcommand '{sub}' not in allow list")
286
284
  return SafetyCheckResult(True)
@@ -16,6 +16,7 @@ class EndEvent(BaseModel):
16
16
  class ErrorEvent(BaseModel):
17
17
  error_message: str
18
18
  can_retry: bool = False
19
+ session_id: str | None = None
19
20
 
20
21
 
21
22
  class TaskStartEvent(BaseModel):
@@ -308,13 +308,17 @@ def _try_render_todo_args(arguments: str, tool_name: str) -> str | None:
308
308
  return None
309
309
 
310
310
 
311
- def _render_sub_agent_result(content: str) -> str:
311
+ def _render_sub_agent_result(content: str, description: str | None = None) -> str:
312
312
  # Try to format as JSON for better readability
313
313
  try:
314
314
  parsed = json.loads(content)
315
315
  formatted = "```json\n" + json.dumps(parsed, ensure_ascii=False, indent=2) + "\n```"
316
316
  except (json.JSONDecodeError, TypeError):
317
317
  formatted = content
318
+
319
+ if description:
320
+ formatted = f"# {description}\n\n{formatted}"
321
+
318
322
  encoded = _escape_html(formatted)
319
323
  return (
320
324
  f'<div class="sub-agent-result-container">'
@@ -628,7 +632,15 @@ def _format_tool_call(tool_call: model.ToolCallItem, result: model.ToolResultIte
628
632
 
629
633
  if result.output and not should_hide_text:
630
634
  if is_sub_agent_tool(tool_call.name):
631
- items_to_render.append(_render_sub_agent_result(result.output))
635
+ description = None
636
+ try:
637
+ args = json.loads(tool_call.arguments)
638
+ if isinstance(args, dict):
639
+ typed_args = cast(dict[str, Any], args)
640
+ description = cast(str | None, typed_args.get("description"))
641
+ except (json.JSONDecodeError, TypeError):
642
+ pass
643
+ items_to_render.append(_render_sub_agent_result(result.output, description))
632
644
  else:
633
645
  items_to_render.append(_render_text_block(result.output))
634
646
 
@@ -62,6 +62,7 @@ class Session(BaseModel):
62
62
  need_todo_not_used_cooldown_counter: int = Field(exclude=True, default=0)
63
63
 
64
64
  _messages_count_cache: int | None = PrivateAttr(default=None)
65
+ _user_messages_cache: list[str] | None = PrivateAttr(default=None)
65
66
  _store: JsonlSessionStore = PrivateAttr(default_factory=get_default_store)
66
67
 
67
68
  @property
@@ -78,6 +79,20 @@ class Session(BaseModel):
78
79
  def _invalidate_messages_count_cache(self) -> None:
79
80
  self._messages_count_cache = None
80
81
 
82
+ @property
83
+ def user_messages(self) -> list[str]:
84
+ """All user message contents in this session.
85
+
86
+ This is used for session selection UI and search, and is also persisted
87
+ in meta.json to avoid scanning events.jsonl for every session.
88
+ """
89
+
90
+ if self._user_messages_cache is None:
91
+ self._user_messages_cache = [
92
+ it.content for it in self.conversation_history if isinstance(it, model.UserMessageItem) and it.content
93
+ ]
94
+ return self._user_messages_cache
95
+
81
96
  @staticmethod
82
97
  def _project_key() -> str:
83
98
  return _project_key_from_cwd()
@@ -178,6 +193,18 @@ class Session(BaseModel):
178
193
  self.conversation_history.extend(items)
179
194
  self._invalidate_messages_count_cache()
180
195
 
196
+ new_user_messages = [
197
+ it.content for it in items if isinstance(it, model.UserMessageItem) and it.content
198
+ ]
199
+ if new_user_messages:
200
+ if self._user_messages_cache is None:
201
+ # Build from full history once to ensure correctness when resuming older sessions.
202
+ self._user_messages_cache = [
203
+ it.content for it in self.conversation_history if isinstance(it, model.UserMessageItem) and it.content
204
+ ]
205
+ else:
206
+ self._user_messages_cache.extend(new_user_messages)
207
+
181
208
  if self.created_at <= 0:
182
209
  self.created_at = time.time()
183
210
  self.updated_at = time.time()
@@ -188,6 +215,7 @@ class Session(BaseModel):
188
215
  sub_agent_state=self.sub_agent_state,
189
216
  file_tracker=self.file_tracker,
190
217
  todos=list(self.todos),
218
+ user_messages=self.user_messages,
191
219
  created_at=self.created_at,
192
220
  updated_at=self.updated_at,
193
221
  messages_count=self.messages_count,
@@ -311,7 +339,7 @@ class Session(BaseModel):
311
339
  case model.DeveloperMessageItem() as dm:
312
340
  yield events.DeveloperMessageEvent(session_id=self.id, item=dm)
313
341
  case model.StreamErrorItem() as se:
314
- yield events.ErrorEvent(error_message=se.error, can_retry=False)
342
+ yield events.ErrorEvent(error_message=se.error, can_retry=False, session_id=self.id)
315
343
  case _:
316
344
  continue
317
345
  prev_item = it
@@ -378,6 +406,17 @@ class Session(BaseModel):
378
406
  pass
379
407
  return messages
380
408
 
409
+ def _maybe_backfill_user_messages(*, meta_path: Path, meta: dict[str, Any], user_messages: list[str]) -> None:
410
+ if isinstance(meta.get("user_messages"), list):
411
+ return
412
+ meta["user_messages"] = user_messages
413
+ try:
414
+ tmp_path = meta_path.with_suffix(".json.tmp")
415
+ tmp_path.write_text(json.dumps(meta, ensure_ascii=False, indent=2), encoding="utf-8")
416
+ tmp_path.replace(meta_path)
417
+ except OSError:
418
+ return
419
+
381
420
  items: list[Session.SessionMetaBrief] = []
382
421
  for meta_path in store.iter_meta_files():
383
422
  data = _read_json_dict(meta_path)
@@ -390,7 +429,15 @@ class Session(BaseModel):
390
429
  created = float(data.get("created_at", meta_path.stat().st_mtime))
391
430
  updated = float(data.get("updated_at", meta_path.stat().st_mtime))
392
431
  work_dir = str(data.get("work_dir", ""))
393
- user_messages = _get_user_messages(sid)
432
+
433
+ user_messages_raw = data.get("user_messages")
434
+ if isinstance(user_messages_raw, list) and all(
435
+ isinstance(m, str) for m in cast(list[object], user_messages_raw)
436
+ ):
437
+ user_messages = cast(list[str], user_messages_raw)
438
+ else:
439
+ user_messages = _get_user_messages(sid)
440
+ _maybe_backfill_user_messages(meta_path=meta_path, meta=data, user_messages=user_messages)
394
441
  messages_count = int(data.get("messages_count", -1))
395
442
  model_name = data.get("model_name") if isinstance(data.get("model_name"), str) else None
396
443
 
@@ -193,6 +193,7 @@ def build_meta_snapshot(
193
193
  sub_agent_state: model.SubAgentState | None,
194
194
  file_tracker: dict[str, model.FileStatus],
195
195
  todos: list[model.TodoItem],
196
+ user_messages: list[str],
196
197
  created_at: float,
197
198
  updated_at: float,
198
199
  messages_count: int,
@@ -206,6 +207,8 @@ def build_meta_snapshot(
206
207
  "sub_agent_state": sub_agent_state.model_dump(mode="json") if sub_agent_state else None,
207
208
  "file_tracker": {path: status.model_dump(mode="json") for path, status in file_tracker.items()},
208
209
  "todos": [todo.model_dump(mode="json", exclude_defaults=True) for todo in todos],
210
+ # Cache user messages to avoid scanning events.jsonl during session listing.
211
+ "user_messages": list(user_messages),
209
212
  "created_at": created_at,
210
213
  "updated_at": updated_at,
211
214
  "messages_count": messages_count,