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.
- {klaude_code-2.9.1 → klaude_code-2.10.0}/PKG-INFO +1 -1
- {klaude_code-2.9.1 → klaude_code-2.10.0}/pyproject.toml +1 -1
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/app/runtime.py +1 -1
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/cli/cost_cmd.py +4 -4
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/cli/list_model.py +1 -2
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/const.py +4 -3
- klaude_code-2.10.0/src/klaude_code/core/bash_mode.py +276 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/executor.py +40 -7
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/manager/llm_clients.py +1 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/manager/llm_clients_builder.py +2 -2
- klaude_code-2.10.0/src/klaude_code/core/memory.py +140 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/reminders.py +17 -89
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/turn.py +10 -4
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/events.py +17 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/op.py +12 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/op_handler.py +5 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/resume_cmd.py +1 -1
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/commands.py +15 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/command_output.py +4 -5
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/developer.py +1 -3
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/metadata.py +23 -23
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/rich/code_panel.py +31 -16
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/rich/markdown.py +53 -124
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/rich/theme.py +19 -10
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/tools.py +1 -0
- klaude_code-2.10.0/src/klaude_code/tui/components/user_input.py +98 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/welcome.py +47 -2
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/display.py +15 -7
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/input/completers.py +8 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/input/key_bindings.py +37 -1
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/input/prompt_toolkit.py +58 -31
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/machine.py +63 -3
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/renderer.py +113 -19
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/runner.py +22 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/terminal/notifier.py +11 -12
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/terminal/selector.py +1 -1
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/ui/terminal/title.py +4 -2
- klaude_code-2.9.1/src/klaude_code/tui/components/assistant.py +0 -2
- klaude_code-2.9.1/src/klaude_code/tui/components/user_input.py +0 -109
- {klaude_code-2.9.1 → klaude_code-2.10.0}/README.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/.DS_Store +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/app/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/AGENTS.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/antigravity/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/antigravity/exceptions.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/antigravity/oauth.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/antigravity/pkce.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/antigravity/token_manager.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/base.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/claude/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/claude/exceptions.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/claude/oauth.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/claude/token_manager.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/codex/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/codex/exceptions.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/codex/jwt_utils.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/codex/oauth.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/codex/token_manager.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/auth/env.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/cli/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/cli/auth_cmd.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/cli/config_cmd.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/cli/debug.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/cli/main.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/cli/self_update.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/config/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/config/assets/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/config/assets/builtin_config.yaml +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/config/builtin_config.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/config/config.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/config/model_matcher.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/config/sub_agent_model_helper.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/config/thinking.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/agent.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/agent_profile.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/compaction/AGENTS.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/compaction/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/compaction/compaction.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/compaction/overflow.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/compaction/prompts.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/loaded_skills.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/manager/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/manager/sub_agent_manager.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/prompts/prompt-antigravity.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/prompts/prompt-claude-code.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/prompts/prompt-codex.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/prompts/prompt-gemini.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/prompts/prompt-minimal.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/prompts/prompt-sub-agent-explore.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/prompts/prompt-sub-agent-image-gen.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/prompts/prompt-sub-agent-web.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/prompts/prompt-sub-agent.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/task.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/context.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/_utils.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/apply_patch.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/apply_patch_tool.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/apply_patch_tool.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/diff_builder.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/edit_tool.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/edit_tool.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/read_tool.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/read_tool.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/write_tool.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/file/write_tool.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/offload.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/report_back_tool.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/shell/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/shell/bash_tool.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/shell/bash_tool.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/shell/command_safety.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/sub_agent/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/sub_agent/image_gen.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/sub_agent/image_gen.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/sub_agent/task.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/sub_agent/task.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/todo/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/todo/todo_write_tool.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/todo/todo_write_tool.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/todo/todo_write_tool_raw.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/todo/update_plan_tool.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/todo/update_plan_tool.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/tool_abc.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/tool_registry.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/tool_runner.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/web/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/web/mermaid_tool.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/web/mermaid_tool.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/web/web_fetch_tool.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/web/web_fetch_tool.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/web/web_search_tool.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/tool/web/web_search_tool.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/anthropic/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/anthropic/client.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/anthropic/input.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/antigravity/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/antigravity/client.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/antigravity/input.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/bedrock_anthropic/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/bedrock_anthropic/client.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/claude/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/claude/client.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/client.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/google/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/google/client.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/google/input.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/image.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/input_common.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/json_stable.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openai_codex/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openai_codex/client.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openai_codex/prompt_sync.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openai_compatible/client.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openai_compatible/input.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openai_compatible/stream.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openai_responses/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openai_responses/client.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openai_responses/input.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openrouter/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openrouter/client.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openrouter/input.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/openrouter/reasoning.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/partial_message.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/registry.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/stream_parts.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/llm/usage.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/log.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/commands.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/llm_param.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/message.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/model.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/sub_agent/AGENTS.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/sub_agent/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/sub_agent/explore.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/sub_agent/image_gen.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/sub_agent/task.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/sub_agent/web.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/protocol/tools.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/session/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/session/codec.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/session/export.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/session/selector.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/session/session.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/session/store.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/session/templates/export_session.html +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/session/templates/mermaid_viewer.html +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/skill/.DS_Store +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/skill/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/skill/assets/.DS_Store +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/skill/assets/create-plan/SKILL.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/skill/assets/deslop/SKILL.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/skill/assets/handoff/SKILL.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/skill/assets/skill-creator/SKILL.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/skill/loader.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/skill/manager.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/skill/system_skills.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/clear_cmd.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/command_abc.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/compact_cmd.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/continue_cmd.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/copy_cmd.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/debug_cmd.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/export_cmd.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/export_online_cmd.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/fork_session_cmd.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/model_cmd.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/model_picker.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/prompt-init.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/prompt_command.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/refresh_cmd.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/registry.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/status_cmd.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/sub_agent_model_cmd.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/command/thinking_cmd.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/bash_syntax.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/common.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/diffs.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/errors.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/mermaid_viewer.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/rich/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/rich/cjk_wrap.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/rich/live.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/rich/quote.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/rich/status.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/sub_agent.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/components/thinking.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/input/AGENTS.md +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/input/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/input/drag_drop.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/input/images.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/input/paste.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/terminal/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/terminal/color.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/terminal/control.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/terminal/image.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/tui/terminal/progress_bar.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/ui/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/ui/common.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/ui/core/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/ui/core/display.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/ui/core/input.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/ui/debug_mode.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/ui/terminal/__init__.py +0 -0
- {klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/update.py +0 -0
|
@@ -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 = "
|
|
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 = "
|
|
356
|
+
model_prefix = " ╰─ " if is_last_model else " ├─ "
|
|
357
357
|
else:
|
|
358
|
-
model_prefix = " │
|
|
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 = "
|
|
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 = "
|
|
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 =
|
|
160
|
-
MARKDOWN_RIGHT_MARGIN =
|
|
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,
|
|
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
|
|
{klaude_code-2.9.1 → klaude_code-2.10.0}/src/klaude_code/core/manager/llm_clients_builder.py
RENAMED
|
@@ -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
|