klaude-code 2.9.1__tar.gz → 2.10.0__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 (257) hide show
  1. {klaude_code-2.9.1 → klaude_code-2.10.0}/PKG-INFO +1 -1
  2. {klaude_code-2.9.1 → klaude_code-2.10.0}/pyproject.toml +1 -1
  3. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/app/runtime.py +1 -1
  4. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/cli/cost_cmd.py +4 -4
  5. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/cli/list_model.py +1 -2
  6. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/const.py +4 -3
  7. klaude_code-2.10.0/src/klaude_code/core/bash_mode.py +276 -0
  8. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/executor.py +40 -7
  9. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/manager/llm_clients.py +1 -0
  10. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/manager/llm_clients_builder.py +2 -2
  11. klaude_code-2.10.0/src/klaude_code/core/memory.py +140 -0
  12. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/reminders.py +17 -89
  13. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/turn.py +10 -4
  14. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/events.py +17 -0
  15. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/op.py +12 -0
  16. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/op_handler.py +5 -0
  17. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/resume_cmd.py +1 -1
  18. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/commands.py +15 -0
  19. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/command_output.py +4 -5
  20. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/developer.py +1 -3
  21. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/metadata.py +23 -23
  22. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/rich/code_panel.py +31 -16
  23. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/rich/markdown.py +53 -124
  24. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/rich/theme.py +19 -10
  25. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/tools.py +1 -0
  26. klaude_code-2.10.0/src/klaude_code/tui/components/user_input.py +98 -0
  27. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/welcome.py +47 -2
  28. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/display.py +15 -7
  29. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/input/completers.py +8 -0
  30. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/input/key_bindings.py +37 -1
  31. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/input/prompt_toolkit.py +58 -31
  32. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/machine.py +63 -3
  33. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/renderer.py +113 -19
  34. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/runner.py +22 -0
  35. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/terminal/notifier.py +11 -12
  36. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/terminal/selector.py +1 -1
  37. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/ui/terminal/title.py +4 -2
  38. klaude_code-2.9.1/src/klaude_code/tui/components/assistant.py +0 -2
  39. klaude_code-2.9.1/src/klaude_code/tui/components/user_input.py +0 -109
  40. {klaude_code-2.9.1 → klaude_code-2.10.0}/README.md +0 -0
  41. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/.DS_Store +0 -0
  42. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/__init__.py +0 -0
  43. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/app/__init__.py +0 -0
  44. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/AGENTS.md +0 -0
  45. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/__init__.py +0 -0
  46. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/antigravity/__init__.py +0 -0
  47. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/antigravity/exceptions.py +0 -0
  48. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/antigravity/oauth.py +0 -0
  49. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/antigravity/pkce.py +0 -0
  50. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/antigravity/token_manager.py +0 -0
  51. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/base.py +0 -0
  52. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/claude/__init__.py +0 -0
  53. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/claude/exceptions.py +0 -0
  54. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/claude/oauth.py +0 -0
  55. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/claude/token_manager.py +0 -0
  56. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/codex/__init__.py +0 -0
  57. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/codex/exceptions.py +0 -0
  58. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/codex/jwt_utils.py +0 -0
  59. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/codex/oauth.py +0 -0
  60. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/codex/token_manager.py +0 -0
  61. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/env.py +0 -0
  62. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/cli/__init__.py +0 -0
  63. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/cli/auth_cmd.py +0 -0
  64. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/cli/config_cmd.py +0 -0
  65. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/cli/debug.py +0 -0
  66. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/cli/main.py +0 -0
  67. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/cli/self_update.py +0 -0
  68. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/config/__init__.py +0 -0
  69. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/config/assets/__init__.py +0 -0
  70. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/config/assets/builtin_config.yaml +0 -0
  71. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/config/builtin_config.py +0 -0
  72. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/config/config.py +0 -0
  73. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/config/model_matcher.py +0 -0
  74. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/config/sub_agent_model_helper.py +0 -0
  75. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/config/thinking.py +0 -0
  76. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/__init__.py +0 -0
  77. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/agent.py +0 -0
  78. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/agent_profile.py +0 -0
  79. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/compaction/AGENTS.md +0 -0
  80. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/compaction/__init__.py +0 -0
  81. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/compaction/compaction.py +0 -0
  82. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/compaction/overflow.py +0 -0
  83. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/compaction/prompts.py +0 -0
  84. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/loaded_skills.py +0 -0
  85. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/manager/__init__.py +0 -0
  86. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/manager/sub_agent_manager.py +0 -0
  87. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/prompts/prompt-antigravity.md +0 -0
  88. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/prompts/prompt-claude-code.md +0 -0
  89. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +0 -0
  90. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2.md +0 -0
  91. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/prompts/prompt-codex.md +0 -0
  92. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/prompts/prompt-gemini.md +0 -0
  93. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/prompts/prompt-minimal.md +0 -0
  94. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/prompts/prompt-sub-agent-explore.md +0 -0
  95. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/prompts/prompt-sub-agent-image-gen.md +0 -0
  96. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/prompts/prompt-sub-agent-web.md +0 -0
  97. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/prompts/prompt-sub-agent.md +0 -0
  98. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/task.py +0 -0
  99. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/__init__.py +0 -0
  100. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/context.py +0 -0
  101. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/__init__.py +0 -0
  102. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/_utils.py +0 -0
  103. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/apply_patch.py +0 -0
  104. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/apply_patch_tool.md +0 -0
  105. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/apply_patch_tool.py +0 -0
  106. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/diff_builder.py +0 -0
  107. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/edit_tool.md +0 -0
  108. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/edit_tool.py +0 -0
  109. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/read_tool.md +0 -0
  110. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/read_tool.py +0 -0
  111. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/write_tool.md +0 -0
  112. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/write_tool.py +0 -0
  113. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/offload.py +0 -0
  114. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/report_back_tool.py +0 -0
  115. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/shell/__init__.py +0 -0
  116. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/shell/bash_tool.md +0 -0
  117. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/shell/bash_tool.py +0 -0
  118. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/shell/command_safety.py +0 -0
  119. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/sub_agent/__init__.py +0 -0
  120. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/sub_agent/image_gen.md +0 -0
  121. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/sub_agent/image_gen.py +0 -0
  122. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/sub_agent/task.md +0 -0
  123. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/sub_agent/task.py +0 -0
  124. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/todo/__init__.py +0 -0
  125. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/todo/todo_write_tool.md +0 -0
  126. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/todo/todo_write_tool.py +0 -0
  127. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/todo/todo_write_tool_raw.md +0 -0
  128. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/todo/update_plan_tool.md +0 -0
  129. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/todo/update_plan_tool.py +0 -0
  130. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/tool_abc.py +0 -0
  131. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/tool_registry.py +0 -0
  132. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/tool_runner.py +0 -0
  133. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/web/__init__.py +0 -0
  134. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/web/mermaid_tool.md +0 -0
  135. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/web/mermaid_tool.py +0 -0
  136. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/web/web_fetch_tool.md +0 -0
  137. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/web/web_fetch_tool.py +0 -0
  138. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/web/web_search_tool.md +0 -0
  139. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/web/web_search_tool.py +0 -0
  140. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/__init__.py +0 -0
  141. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/anthropic/__init__.py +0 -0
  142. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/anthropic/client.py +0 -0
  143. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/anthropic/input.py +0 -0
  144. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/antigravity/__init__.py +0 -0
  145. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/antigravity/client.py +0 -0
  146. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/antigravity/input.py +0 -0
  147. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/bedrock_anthropic/__init__.py +0 -0
  148. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/bedrock_anthropic/client.py +0 -0
  149. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/claude/__init__.py +0 -0
  150. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/claude/client.py +0 -0
  151. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/client.py +0 -0
  152. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/google/__init__.py +0 -0
  153. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/google/client.py +0 -0
  154. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/google/input.py +0 -0
  155. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/image.py +0 -0
  156. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/input_common.py +0 -0
  157. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/json_stable.py +0 -0
  158. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openai_codex/__init__.py +0 -0
  159. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openai_codex/client.py +0 -0
  160. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openai_codex/prompt_sync.py +0 -0
  161. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
  162. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openai_compatible/client.py +0 -0
  163. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openai_compatible/input.py +0 -0
  164. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openai_compatible/stream.py +0 -0
  165. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openai_responses/__init__.py +0 -0
  166. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openai_responses/client.py +0 -0
  167. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openai_responses/input.py +0 -0
  168. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openrouter/__init__.py +0 -0
  169. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openrouter/client.py +0 -0
  170. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openrouter/input.py +0 -0
  171. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openrouter/reasoning.py +0 -0
  172. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/partial_message.py +0 -0
  173. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/registry.py +0 -0
  174. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/stream_parts.py +0 -0
  175. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/usage.py +0 -0
  176. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/log.py +0 -0
  177. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/__init__.py +0 -0
  178. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/commands.py +0 -0
  179. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/llm_param.py +0 -0
  180. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/message.py +0 -0
  181. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/model.py +0 -0
  182. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/sub_agent/AGENTS.md +0 -0
  183. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/sub_agent/__init__.py +0 -0
  184. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/sub_agent/explore.py +0 -0
  185. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/sub_agent/image_gen.py +0 -0
  186. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/sub_agent/task.py +0 -0
  187. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/sub_agent/web.py +0 -0
  188. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/tools.py +0 -0
  189. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/session/__init__.py +0 -0
  190. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/session/codec.py +0 -0
  191. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/session/export.py +0 -0
  192. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/session/selector.py +0 -0
  193. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/session/session.py +0 -0
  194. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/session/store.py +0 -0
  195. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/session/templates/export_session.html +0 -0
  196. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/session/templates/mermaid_viewer.html +0 -0
  197. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/skill/.DS_Store +0 -0
  198. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/skill/__init__.py +0 -0
  199. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/skill/assets/.DS_Store +0 -0
  200. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/skill/assets/create-plan/SKILL.md +0 -0
  201. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/skill/assets/deslop/SKILL.md +0 -0
  202. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/skill/assets/handoff/SKILL.md +0 -0
  203. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/skill/assets/skill-creator/SKILL.md +0 -0
  204. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/skill/loader.py +0 -0
  205. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/skill/manager.py +0 -0
  206. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/skill/system_skills.py +0 -0
  207. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/__init__.py +0 -0
  208. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/__init__.py +0 -0
  209. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/clear_cmd.py +0 -0
  210. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/command_abc.py +0 -0
  211. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/compact_cmd.py +0 -0
  212. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/continue_cmd.py +0 -0
  213. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/copy_cmd.py +0 -0
  214. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/debug_cmd.py +0 -0
  215. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/export_cmd.py +0 -0
  216. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/export_online_cmd.py +0 -0
  217. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/fork_session_cmd.py +0 -0
  218. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/model_cmd.py +0 -0
  219. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/model_picker.py +0 -0
  220. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/prompt-init.md +0 -0
  221. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/prompt_command.py +0 -0
  222. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/refresh_cmd.py +0 -0
  223. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/registry.py +0 -0
  224. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/status_cmd.py +0 -0
  225. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/sub_agent_model_cmd.py +0 -0
  226. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/thinking_cmd.py +0 -0
  227. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/__init__.py +0 -0
  228. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/bash_syntax.py +0 -0
  229. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/common.py +0 -0
  230. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/diffs.py +0 -0
  231. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/errors.py +0 -0
  232. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/mermaid_viewer.py +0 -0
  233. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/rich/__init__.py +0 -0
  234. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/rich/cjk_wrap.py +0 -0
  235. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/rich/live.py +0 -0
  236. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/rich/quote.py +0 -0
  237. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/rich/status.py +0 -0
  238. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/sub_agent.py +0 -0
  239. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/thinking.py +0 -0
  240. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/input/AGENTS.md +0 -0
  241. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/input/__init__.py +0 -0
  242. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/input/drag_drop.py +0 -0
  243. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/input/images.py +0 -0
  244. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/input/paste.py +0 -0
  245. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/terminal/__init__.py +0 -0
  246. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/terminal/color.py +0 -0
  247. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/terminal/control.py +0 -0
  248. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/terminal/image.py +0 -0
  249. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/terminal/progress_bar.py +0 -0
  250. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/ui/__init__.py +0 -0
  251. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/ui/common.py +0 -0
  252. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/ui/core/__init__.py +0 -0
  253. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/ui/core/display.py +0 -0
  254. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/ui/core/input.py +0 -0
  255. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/ui/debug_mode.py +0 -0
  256. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/ui/terminal/__init__.py +0 -0
  257. {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/update.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: klaude-code
3
- Version: 2.9.1
3
+ Version: 2.10.0
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 = "2.9.1"
7
+ version = "2.10.0"
8
8
  description = "Minimal code agent CLI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.13"
@@ -87,7 +87,7 @@ async def initialize_app_components(
87
87
  )
88
88
 
89
89
  if on_model_change is not None:
90
- on_model_change(llm_clients.main.model_name)
90
+ on_model_change(llm_clients.main_model_alias)
91
91
 
92
92
  executor_task = asyncio.create_task(executor.start())
93
93
 
@@ -343,7 +343,7 @@ def render_cost_table(daily_stats: dict[str, DailyStats]) -> Table:
343
343
  sub_list = list(group.sub_providers.values())
344
344
  for sub_idx, sub_group in enumerate(sub_list):
345
345
  is_last_sub = sub_idx == len(sub_list) - 1
346
- sub_prefix = " └─ " if is_last_sub else " ├─ "
346
+ sub_prefix = " ╰─ " if is_last_sub else " ├─ "
347
347
 
348
348
  # Sub-provider row
349
349
  add_stats_row(sub_group.total, prefix=sub_prefix, bold=True)
@@ -353,15 +353,15 @@ def render_cost_table(daily_stats: dict[str, DailyStats]) -> Table:
353
353
  is_last_model = model_idx == len(sub_group.models) - 1
354
354
  # Indent based on whether sub-provider is last
355
355
  if is_last_sub:
356
- model_prefix = " └─ " if is_last_model else " ├─ "
356
+ model_prefix = " ╰─ " if is_last_model else " ├─ "
357
357
  else:
358
- model_prefix = " │ └─ " if is_last_model else " │ ├─ "
358
+ model_prefix = " │ ╰─ " if is_last_model else " │ ├─ "
359
359
  add_stats_row(stats, prefix=model_prefix)
360
360
  else:
361
361
  # No sub-providers: render two-level tree (direct models)
362
362
  for model_idx, stats in enumerate(group.models):
363
363
  is_last_model = model_idx == len(group.models) - 1
364
- model_prefix = " └─ " if is_last_model else " ├─ "
364
+ model_prefix = " ╰─ " if is_last_model else " ├─ "
365
365
  add_stats_row(stats, prefix=model_prefix)
366
366
 
367
367
  if show_subtotal:
@@ -338,7 +338,7 @@ def _build_models_table(
338
338
  model_count = len(provider.model_list)
339
339
  for i, model in enumerate(provider.model_list):
340
340
  is_last = i == model_count - 1
341
- prefix = " └─ " if is_last else " ├─ "
341
+ prefix = " ╰─ " if is_last else " ├─ "
342
342
 
343
343
  if provider_disabled:
344
344
  name = Text.assemble(
@@ -439,7 +439,6 @@ def display_models_and_providers(config: Config, *, show_all: bool = False):
439
439
  # Provider info panel
440
440
  provider_panel = _build_provider_info_panel(provider, provider_available, disabled=provider.disabled)
441
441
  console.print(provider_panel)
442
- console.print()
443
442
 
444
443
  # Models table for this provider
445
444
  models_table = _build_models_table(provider, config)
@@ -71,7 +71,6 @@ DEFAULT_ANTHROPIC_THINKING_BUDGET_TOKENS = 2048 # Default thinking budget token
71
71
 
72
72
  TODO_REMINDER_TOOL_CALL_THRESHOLD = 10 # Tool call count threshold for todo reminder
73
73
  REMINDER_COOLDOWN_TURNS = 3 # Cooldown turns between reminder triggers
74
- MEMORY_FILE_NAMES = ["CLAUDE.md", "AGENTS.md", "AGENT.md"] # Memory file names to search for
75
74
 
76
75
 
77
76
  # =============================================================================
@@ -92,6 +91,7 @@ BINARY_CHECK_SIZE = 8192 # Bytes to check for binary file detection
92
91
 
93
92
  BASH_DEFAULT_TIMEOUT_MS = 120000 # Default timeout for bash commands (milliseconds)
94
93
  BASH_TERMINATE_TIMEOUT_SEC = 1.0 # Timeout before escalating to SIGKILL (seconds)
94
+ BASH_MODE_SESSION_OUTPUT_MAX_BYTES = 200 * 1024 * 1024 # Max command output captured for session history
95
95
 
96
96
 
97
97
  # =============================================================================
@@ -156,8 +156,8 @@ CROP_ABOVE_LIVE_REFRESH_PER_SECOND = 4.0 # CropAboveLive default refresh rate
156
156
  MARKDOWN_STREAM_LIVE_REPAINT_ENABLED = True # Enable live area for streaming markdown
157
157
  MARKDOWN_STREAM_SYNCHRONIZED_OUTPUT_ENABLED = True # Use terminal "Synchronized Output" to reduce flicker
158
158
  STREAM_MAX_HEIGHT_SHRINK_RESET_LINES = 20 # Reset stream height ceiling after this shrinkage
159
- MARKDOWN_LEFT_MARGIN = 2 # Left margin (columns) for markdown rendering
160
- MARKDOWN_RIGHT_MARGIN = 2 # Right margin (columns) for markdown rendering
159
+ MARKDOWN_LEFT_MARGIN = 0 # Left margin (columns) for markdown rendering
160
+ MARKDOWN_RIGHT_MARGIN = 0 # Right margin (columns) for markdown rendering
161
161
 
162
162
 
163
163
  # =============================================================================
@@ -171,6 +171,7 @@ STATUS_WAITING_TEXT = "Loading …"
171
171
  STATUS_THINKING_TEXT = "Thinking …"
172
172
  STATUS_COMPOSING_TEXT = "Composing"
173
173
  STATUS_COMPACTING_TEXT = "Compacting"
174
+ STATUS_RUNNING_TEXT = "Running …"
174
175
 
175
176
  # Backwards-compatible alias for the default spinner status text.
176
177
  STATUS_DEFAULT_TEXT = STATUS_WAITING_TEXT
@@ -0,0 +1,276 @@
1
+ """Bash-mode execution helpers.
2
+
3
+ This module provides the implementation for running non-interactive shell commands
4
+ with streaming output to the UI, plus session history recording.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import asyncio
10
+ import contextlib
11
+ import os
12
+ import re
13
+ import secrets
14
+ import shutil
15
+ import signal
16
+ import subprocess
17
+ import sys
18
+ from collections.abc import Awaitable, Callable
19
+ from dataclasses import dataclass
20
+ from pathlib import Path
21
+ from typing import TextIO
22
+
23
+ from klaude_code.const import BASH_MODE_SESSION_OUTPUT_MAX_BYTES, BASH_TERMINATE_TIMEOUT_SEC, TOOL_OUTPUT_TRUNCATION_DIR
24
+ from klaude_code.core.tool.offload import offload_tool_output
25
+ from klaude_code.protocol import events, message
26
+ from klaude_code.session.session import Session
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class _BashModeToolCall:
31
+ tool_name: str = "Bash"
32
+
33
+
34
+ _ANSI_ESCAPE_RE = re.compile(
35
+ r"""
36
+ \x1B
37
+ (?:
38
+ \[[0-?]*[ -/]*[@-~] | # CSI sequences
39
+ \][0-?]*.*?(?:\x07|\x1B\\) | # OSC sequences
40
+ P.*?(?:\x07|\x1B\\) | # DCS sequences
41
+ _.*?(?:\x07|\x1B\\) | # APC sequences
42
+ \^.*?(?:\x07|\x1B\\) | # PM sequences
43
+ [@-Z\\-_] # 2-char sequences
44
+ )
45
+ """,
46
+ re.VERBOSE | re.DOTALL,
47
+ )
48
+
49
+
50
+ def _format_inline_code(text: str) -> str:
51
+ if not text:
52
+ return "``"
53
+ max_run = 0
54
+ run = 0
55
+ for ch in text:
56
+ if ch == "`":
57
+ run += 1
58
+ max_run = max(max_run, run)
59
+ else:
60
+ run = 0
61
+ fence = "`" * (max_run + 1)
62
+ return f"{fence}{text}{fence}"
63
+
64
+
65
+ def _resolve_shell_command(command_text: str) -> list[str]:
66
+ # Use the user's default shell when possible.
67
+ # - macOS/Linux: $SHELL (supports bash/zsh/fish)
68
+ # - Windows: prefer pwsh/powershell
69
+ if sys.platform == "win32": # pragma: no cover
70
+ exe = "pwsh" if shutil.which("pwsh") else "powershell"
71
+ return [exe, "-NoProfile", "-Command", command_text]
72
+
73
+ shell_path = os.environ.get("SHELL")
74
+ shell_name = Path(shell_path).name.lower() if shell_path else ""
75
+ if shell_path and shell_name in {"bash", "zsh", "fish"}:
76
+ # Use -lic to load both login profile and interactive config (e.g. aliases from .zshrc)
77
+ return [shell_path, "-lic", command_text]
78
+ return ["bash", "-lic", command_text]
79
+
80
+
81
+ async def _terminate_process(proc: asyncio.subprocess.Process) -> None:
82
+ if proc.returncode is not None:
83
+ return
84
+
85
+ try:
86
+ if os.name == "posix":
87
+ os.killpg(proc.pid, signal.SIGTERM)
88
+ else: # pragma: no cover
89
+ proc.terminate()
90
+ except ProcessLookupError:
91
+ return
92
+ except OSError:
93
+ pass
94
+
95
+ with contextlib.suppress(Exception):
96
+ await asyncio.wait_for(proc.wait(), timeout=BASH_TERMINATE_TIMEOUT_SEC)
97
+ return
98
+
99
+ with contextlib.suppress(Exception):
100
+ if os.name == "posix":
101
+ os.killpg(proc.pid, signal.SIGKILL)
102
+ else: # pragma: no cover
103
+ proc.kill()
104
+ with contextlib.suppress(Exception):
105
+ await asyncio.wait_for(proc.wait(), timeout=BASH_TERMINATE_TIMEOUT_SEC)
106
+
107
+
108
+ async def _emit_clean_chunk(
109
+ *,
110
+ emit_event: Callable[[events.Event], Awaitable[None]],
111
+ session_id: str,
112
+ chunk: str,
113
+ out_file: TextIO,
114
+ ) -> None:
115
+ if not chunk:
116
+ return
117
+
118
+ cleaned = _ANSI_ESCAPE_RE.sub("", chunk)
119
+ if cleaned:
120
+ await emit_event(events.BashCommandOutputDeltaEvent(session_id=session_id, content=cleaned))
121
+ with contextlib.suppress(Exception):
122
+ out_file.write(cleaned)
123
+
124
+
125
+ async def run_bash_command(
126
+ *,
127
+ emit_event: Callable[[events.Event], Awaitable[None]],
128
+ session: Session,
129
+ session_id: str,
130
+ command: str,
131
+ ) -> None:
132
+ """Run a non-interactive bash command with streaming output to the UI.
133
+
134
+ The full (cleaned) output is appended to session history in a single UserMessage
135
+ as: `Ran <command>` plus truncated output via offload strategy.
136
+ """
137
+
138
+ await emit_event(events.BashCommandStartEvent(session_id=session_id, command=command))
139
+
140
+ # Create a log file to support large outputs without holding everything in memory.
141
+ # Use TOOL_OUTPUT_TRUNCATION_DIR (system temp) for consistency with offload.
142
+ tmp_root = Path(TOOL_OUTPUT_TRUNCATION_DIR)
143
+ tmp_root.mkdir(parents=True, exist_ok=True)
144
+ log_path = tmp_root / f"klaude-bash-mode-{secrets.token_hex(8)}.log"
145
+
146
+ env = os.environ.copy()
147
+ env.update(
148
+ {
149
+ "GIT_TERMINAL_PROMPT": "0",
150
+ "PAGER": "cat",
151
+ "GIT_PAGER": "cat",
152
+ "EDITOR": "true",
153
+ "VISUAL": "true",
154
+ "GIT_EDITOR": "true",
155
+ "JJ_EDITOR": "true",
156
+ "TERM": "dumb",
157
+ }
158
+ )
159
+
160
+ proc: asyncio.subprocess.Process | None = None
161
+ cancelled = False
162
+ exit_code: int | None = None
163
+
164
+ # Hold back any trailing ESC-started sequence to avoid leaking control codes
165
+ # when the subprocess output is chunked.
166
+ pending = ""
167
+
168
+ try:
169
+ kwargs: dict[str, object] = {
170
+ "stdin": asyncio.subprocess.DEVNULL,
171
+ "stdout": asyncio.subprocess.PIPE,
172
+ "stderr": asyncio.subprocess.STDOUT,
173
+ "env": env,
174
+ }
175
+ if os.name == "posix":
176
+ kwargs["start_new_session"] = True
177
+ elif os.name == "nt": # pragma: no cover
178
+ kwargs["creationflags"] = getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0)
179
+
180
+ shell_argv = _resolve_shell_command(command)
181
+ proc = await asyncio.create_subprocess_exec(*shell_argv, **kwargs) # type: ignore[arg-type]
182
+ assert proc.stdout is not None
183
+
184
+ with log_path.open("w", encoding="utf-8", errors="replace") as out_file:
185
+ while True:
186
+ data = await proc.stdout.read(4096)
187
+ if not data:
188
+ break
189
+ piece = data.decode(errors="replace")
190
+ pending += piece
191
+
192
+ # Keep from the last ESC onwards to avoid emitting incomplete sequences.
193
+ last_esc = pending.rfind("\x1b")
194
+ if last_esc == -1:
195
+ to_emit, pending = pending, ""
196
+ elif last_esc < len(pending) - 128:
197
+ to_emit, pending = pending[:last_esc], pending[last_esc:]
198
+ else:
199
+ # Wait for more bytes to complete the sequence.
200
+ continue
201
+
202
+ await _emit_clean_chunk(
203
+ emit_event=emit_event,
204
+ session_id=session_id,
205
+ chunk=to_emit,
206
+ out_file=out_file,
207
+ )
208
+
209
+ if pending:
210
+ await _emit_clean_chunk(
211
+ emit_event=emit_event,
212
+ session_id=session_id,
213
+ chunk=pending,
214
+ out_file=out_file,
215
+ )
216
+ pending = ""
217
+
218
+ exit_code = await proc.wait()
219
+
220
+ except asyncio.CancelledError:
221
+ cancelled = True
222
+ if proc is not None:
223
+ with contextlib.suppress(Exception):
224
+ await asyncio.shield(_terminate_process(proc))
225
+ except Exception as exc:
226
+ # Surface errors to the UI as a final line.
227
+ msg = f"Execution error: {exc.__class__.__name__} {exc}"
228
+ await emit_event(events.BashCommandOutputDeltaEvent(session_id=session_id, content=msg))
229
+ finally:
230
+ header = f"Ran {_format_inline_code(command)}"
231
+
232
+ record_lines: list[str] = [header]
233
+ if cancelled:
234
+ record_lines.append("\n(command cancelled)")
235
+ elif isinstance(exit_code, int) and exit_code != 0:
236
+ record_lines.append(f"\nCommand exited with code {exit_code}")
237
+
238
+ output_text = ""
239
+ output_note_added = False
240
+ try:
241
+ if log_path.exists() and log_path.stat().st_size > BASH_MODE_SESSION_OUTPUT_MAX_BYTES:
242
+ record_lines.append(
243
+ f"\n\n<system-reminder>Output truncated due to length. Full output saved to: {log_path} </system-reminder>"
244
+ )
245
+ output_note_added = True
246
+ else:
247
+ output_text = log_path.read_text("utf-8", errors="replace") if log_path.exists() else ""
248
+ except OSError:
249
+ output_text = ""
250
+
251
+ if output_text.strip() == "":
252
+ if not cancelled and not output_note_added:
253
+ record_lines.append("\n(no output)")
254
+ await emit_event(events.BashCommandOutputDeltaEvent(session_id=session_id, content="(no output)\n"))
255
+ else:
256
+ offloaded = offload_tool_output(output_text, _BashModeToolCall())
257
+ record_lines.append("\n\n" + offloaded.output)
258
+
259
+ # Always emit an end event so the renderer can finalize formatting.
260
+ await emit_event(
261
+ events.BashCommandEndEvent(
262
+ session_id=session_id,
263
+ exit_code=exit_code,
264
+ cancelled=cancelled,
265
+ )
266
+ )
267
+ session.append_history(
268
+ [
269
+ message.UserMessage(
270
+ parts=message.parts_from_text_and_images(
271
+ "".join(record_lines).rstrip(),
272
+ None,
273
+ )
274
+ )
275
+ ]
276
+ )
@@ -18,9 +18,11 @@ from klaude_code.config import load_config
18
18
  from klaude_code.config.sub_agent_model_helper import SubAgentModelHelper
19
19
  from klaude_code.core.agent import Agent
20
20
  from klaude_code.core.agent_profile import DefaultModelProfileProvider, ModelProfileProvider
21
+ from klaude_code.core.bash_mode import run_bash_command
21
22
  from klaude_code.core.compaction import CompactionReason, run_compaction
22
23
  from klaude_code.core.loaded_skills import get_loaded_skill_names_by_location
23
24
  from klaude_code.core.manager import LLMClients, SubAgentManager
25
+ from klaude_code.core.memory import get_existing_memory_paths_by_location
24
26
  from klaude_code.llm.registry import create_llm_client
25
27
  from klaude_code.log import DebugType, log_debug
26
28
  from klaude_code.protocol import commands, events, message, model, op
@@ -134,18 +136,19 @@ class AgentRuntime:
134
136
  compact_llm_client=self._llm_clients.compact,
135
137
  )
136
138
 
137
- async for evt in agent.replay_history():
138
- await self._emit_event(evt)
139
-
140
139
  await self._emit_event(
141
140
  events.WelcomeEvent(
142
141
  session_id=session.id,
143
142
  work_dir=str(session.work_dir),
144
143
  llm_config=self._llm_clients.main.get_llm_config(),
145
144
  loaded_skills=get_loaded_skill_names_by_location(),
145
+ loaded_memories=get_existing_memory_paths_by_location(work_dir=session.work_dir),
146
146
  )
147
147
  )
148
148
 
149
+ async for evt in agent.replay_history():
150
+ await self._emit_event(evt)
151
+
149
152
  self._agent = agent
150
153
  log_debug(
151
154
  f"Initialized agent for session: {session.id}",
@@ -179,6 +182,23 @@ class AgentRuntime:
179
182
  )
180
183
  self._task_manager.register(operation.id, task, operation.session_id)
181
184
 
185
+ async def run_bash(self, operation: op.RunBashOperation) -> None:
186
+ agent = await self.ensure_agent(operation.session_id)
187
+
188
+ existing_active = self._task_manager.get(operation.id)
189
+ if existing_active is not None and not existing_active.task.done():
190
+ raise RuntimeError(f"Active task already registered for operation {operation.id}")
191
+
192
+ task: asyncio.Task[None] = asyncio.create_task(
193
+ self._run_bash_task(
194
+ session=agent.session,
195
+ command=operation.command,
196
+ task_id=operation.id,
197
+ session_id=operation.session_id,
198
+ )
199
+ )
200
+ self._task_manager.register(operation.id, task, operation.session_id)
201
+
182
202
  async def continue_agent(self, operation: op.ContinueAgentOperation) -> None:
183
203
  """Continue agent execution without adding a new user message."""
184
204
  agent = await self.ensure_agent(operation.session_id)
@@ -230,6 +250,7 @@ class AgentRuntime:
230
250
  work_dir=str(agent.session.work_dir),
231
251
  llm_config=self._llm_clients.main.get_llm_config(),
232
252
  loaded_skills=get_loaded_skill_names_by_location(),
253
+ loaded_memories=get_existing_memory_paths_by_location(work_dir=agent.session.work_dir),
233
254
  )
234
255
  )
235
256
 
@@ -249,18 +270,19 @@ class AgentRuntime:
249
270
  compact_llm_client=self._llm_clients.compact,
250
271
  )
251
272
 
252
- async for evt in agent.replay_history():
253
- await self._emit_event(evt)
254
-
255
273
  await self._emit_event(
256
274
  events.WelcomeEvent(
257
275
  session_id=target_session.id,
258
276
  work_dir=str(target_session.work_dir),
259
277
  llm_config=self._llm_clients.main.get_llm_config(),
260
278
  loaded_skills=get_loaded_skill_names_by_location(),
279
+ loaded_memories=get_existing_memory_paths_by_location(work_dir=target_session.work_dir),
261
280
  )
262
281
  )
263
282
 
283
+ async for evt in agent.replay_history():
284
+ await self._emit_event(evt)
285
+
264
286
  self._agent = agent
265
287
  log_debug(
266
288
  f"Resumed session: {target_session.id}",
@@ -359,6 +381,14 @@ class AgentRuntime:
359
381
  debug_type=DebugType.EXECUTION,
360
382
  )
361
383
 
384
+ async def _run_bash_task(self, *, session: Session, command: str, task_id: str, session_id: str) -> None:
385
+ await run_bash_command(
386
+ emit_event=self._emit_event,
387
+ session=session,
388
+ session_id=session_id,
389
+ command=command,
390
+ )
391
+
362
392
  async def _run_compaction_task(
363
393
  self,
364
394
  agent: Agent,
@@ -467,7 +497,7 @@ class ModelSwitcher:
467
497
  config.main_model = model_name
468
498
  await config.save()
469
499
 
470
- return llm_config, llm_client.model_name
500
+ return llm_config, model_name
471
501
 
472
502
  def change_thinking(self, agent: Agent, *, thinking: Thinking) -> Thinking | None:
473
503
  """Apply thinking configuration to the agent's active LLM config and persisted session."""
@@ -540,6 +570,9 @@ class ExecutorContext:
540
570
  async def handle_run_agent(self, operation: op.RunAgentOperation) -> None:
541
571
  await self._agent_runtime.run_agent(operation)
542
572
 
573
+ async def handle_run_bash(self, operation: op.RunBashOperation) -> None:
574
+ await self._agent_runtime.run_bash(operation)
575
+
543
576
  async def handle_continue_agent(self, operation: op.ContinueAgentOperation) -> None:
544
577
  await self._agent_runtime.continue_agent(operation)
545
578
 
@@ -19,6 +19,7 @@ class LLMClients:
19
19
  """Container for LLM clients used by main agent and sub-agents."""
20
20
 
21
21
  main: LLMClientABC
22
+ main_model_alias: str = ""
22
23
  sub_clients: dict[SubAgentType, LLMClientABC] = dataclass_field(default_factory=_default_sub_clients)
23
24
  compact: LLMClientABC | None = None
24
25
 
@@ -53,7 +53,7 @@ def build_llm_clients(
53
53
  compact_client = create_llm_client(compact_llm_config)
54
54
 
55
55
  if skip_sub_agents:
56
- return LLMClients(main=main_client, compact=compact_client)
56
+ return LLMClients(main=main_client, main_model_alias=model_name, compact=compact_client)
57
57
 
58
58
  helper = SubAgentModelHelper(config)
59
59
  sub_agent_configs = helper.build_sub_agent_client_configs()
@@ -63,4 +63,4 @@ def build_llm_clients(
63
63
  sub_llm_config = config.get_model_config(sub_model_name)
64
64
  sub_clients[sub_agent_type] = create_llm_client(sub_llm_config)
65
65
 
66
- return LLMClients(main=main_client, sub_clients=sub_clients, compact=compact_client)
66
+ return LLMClients(main=main_client, main_model_alias=model_name, sub_clients=sub_clients, compact=compact_client)
@@ -0,0 +1,140 @@
1
+ """Memory file loading and management.
2
+
3
+ This module handles CLAUDE.md and AGENTS.md memory files - discovery, loading,
4
+ and providing summaries for UI display.
5
+ """
6
+
7
+ from collections.abc import Callable
8
+ from pathlib import Path
9
+
10
+ from pydantic import BaseModel
11
+
12
+ MEMORY_FILE_NAMES = ["CLAUDE.md", "AGENTS.md", "AGENT.md"]
13
+
14
+
15
+ class Memory(BaseModel):
16
+ """Represents a loaded memory file."""
17
+
18
+ path: str
19
+ instruction: str
20
+ content: str
21
+
22
+
23
+ def get_memory_paths(*, work_dir: Path) -> list[tuple[Path, str]]:
24
+ """Return all possible memory file paths with their descriptions."""
25
+ user_dirs = [Path.home() / ".claude", Path.home() / ".codex"]
26
+ project_dirs = [work_dir, work_dir / ".claude"]
27
+
28
+ paths: list[tuple[Path, str]] = []
29
+ for d in user_dirs:
30
+ for fname in MEMORY_FILE_NAMES:
31
+ paths.append((d / fname, "user's private global instructions for all projects"))
32
+ for d in project_dirs:
33
+ for fname in MEMORY_FILE_NAMES:
34
+ paths.append((d / fname, "project instructions, checked into the codebase"))
35
+ return paths
36
+
37
+
38
+ def get_existing_memory_files(*, work_dir: Path) -> dict[str, list[str]]:
39
+ """Return existing memory file paths grouped by location (user/project)."""
40
+ result: dict[str, list[str]] = {"user": [], "project": []}
41
+ work_dir = work_dir.resolve()
42
+
43
+ for memory_path, _instruction in get_memory_paths(work_dir=work_dir):
44
+ if memory_path.exists() and memory_path.is_file():
45
+ path_str = str(memory_path)
46
+ resolved = memory_path.resolve()
47
+ try:
48
+ resolved.relative_to(work_dir)
49
+ result["project"].append(path_str)
50
+ except ValueError:
51
+ result["user"].append(path_str)
52
+
53
+ return result
54
+
55
+
56
+ def get_existing_memory_paths_by_location(*, work_dir: Path) -> dict[str, list[str]]:
57
+ """Return existing memory file paths grouped by location for WelcomeEvent."""
58
+ result = get_existing_memory_files(work_dir=work_dir)
59
+ if not result.get("user") and not result.get("project"):
60
+ return {}
61
+ return result
62
+
63
+
64
+ def format_memory_content(memory: Memory) -> str:
65
+ """Format a single memory file content for display."""
66
+ return f"Contents of {memory.path} ({memory.instruction}):\n\n{memory.content}"
67
+
68
+
69
+ def format_memories_reminder(memories: list[Memory], include_header: bool = True) -> str:
70
+ """Format memory files into a system reminder string."""
71
+ memories_str = "\n\n".join(format_memory_content(m) for m in memories)
72
+ if include_header:
73
+ return f"""<system-reminder>
74
+ Loaded memory files. Follow these instructions. Do not mention them to the user unless explicitly asked.
75
+
76
+ {memories_str}
77
+ </system-reminder>"""
78
+ return f"<system-reminder>{memories_str}\n</system-reminder>"
79
+
80
+
81
+ def discover_memory_files_near_paths(
82
+ paths: list[str],
83
+ *,
84
+ work_dir: Path,
85
+ is_memory_loaded: Callable[[str], bool],
86
+ mark_memory_loaded: Callable[[str], None],
87
+ ) -> list[Memory]:
88
+ """Discover and load CLAUDE.md/AGENTS.md from directories containing accessed files.
89
+
90
+ Args:
91
+ paths: List of file paths that have been accessed.
92
+ is_memory_loaded: Callback to check if a memory file is already loaded.
93
+ mark_memory_loaded: Callback to mark a memory file as loaded.
94
+
95
+ Returns:
96
+ List of newly discovered Memory objects.
97
+ """
98
+ memories: list[Memory] = []
99
+ work_dir = work_dir.resolve()
100
+ seen_memory_files: set[str] = set()
101
+
102
+ for p_str in paths:
103
+ p = Path(p_str)
104
+ full = (work_dir / p).resolve() if not p.is_absolute() else p.resolve()
105
+ try:
106
+ _ = full.relative_to(work_dir)
107
+ except ValueError:
108
+ continue
109
+
110
+ deepest_dir = full if full.is_dir() else full.parent
111
+
112
+ try:
113
+ rel_parts = deepest_dir.relative_to(work_dir).parts
114
+ except ValueError:
115
+ continue
116
+
117
+ current_dir = work_dir
118
+ for part in rel_parts:
119
+ current_dir = current_dir / part
120
+ for fname in MEMORY_FILE_NAMES:
121
+ mem_path = current_dir / fname
122
+ mem_path_str = str(mem_path)
123
+ if mem_path_str in seen_memory_files or is_memory_loaded(mem_path_str):
124
+ continue
125
+ if mem_path.exists() and mem_path.is_file():
126
+ try:
127
+ text = mem_path.read_text(encoding="utf-8", errors="replace")
128
+ except (PermissionError, UnicodeDecodeError, OSError):
129
+ continue
130
+ mark_memory_loaded(mem_path_str)
131
+ seen_memory_files.add(mem_path_str)
132
+ memories.append(
133
+ Memory(
134
+ path=mem_path_str,
135
+ instruction="project instructions, discovered near last accessed path",
136
+ content=text,
137
+ )
138
+ )
139
+
140
+ return memories