klaude-code 1.2.27__tar.gz → 1.2.28__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-1.2.27 → klaude_code-1.2.28}/PKG-INFO +1 -1
- {klaude_code-1.2.27 → klaude_code-1.2.28}/pyproject.toml +2 -1
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/cli/debug.py +9 -1
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/cli/main.py +39 -14
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/cli/runtime.py +11 -5
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/command/__init__.py +3 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/command/export_online_cmd.py +15 -12
- klaude_code-1.2.28/src/klaude_code/command/fork_session_cmd.py +42 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/config/select_model.py +1 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/executor.py +2 -1
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/reminders.py +52 -16
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/web/mermaid_tool.md +17 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/web/mermaid_tool.py +2 -2
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/llm/openai_compatible/tool_call_accumulator.py +17 -1
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/protocol/commands.py +1 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/protocol/model.py +1 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/session/export.py +51 -6
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/session/session.py +22 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/trace/log.py +7 -1
- klaude_code-1.2.28/src/klaude_code/ui/modes/repl/__init__.py +6 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/modes/repl/completers.py +35 -3
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/modes/repl/event_handler.py +7 -5
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/modes/repl/input_prompt_toolkit.py +32 -65
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/modes/repl/renderer.py +1 -6
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/renderers/common.py +11 -4
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/renderers/developer.py +17 -0
- klaude_code-1.2.28/src/klaude_code/ui/renderers/errors.py +21 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/renderers/tools.py +7 -3
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/rich/markdown.py +4 -4
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/rich/theme.py +6 -2
- klaude_code-1.2.27/src/klaude_code/ui/modes/repl/__init__.py +0 -47
- klaude_code-1.2.27/src/klaude_code/ui/renderers/errors.py +0 -16
- {klaude_code-1.2.27 → klaude_code-1.2.28}/README.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/auth/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/auth/codex/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/auth/codex/exceptions.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/auth/codex/jwt_utils.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/auth/codex/oauth.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/auth/codex/token_manager.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/cli/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/cli/auth_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/cli/config_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/cli/list_model.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/cli/self_update.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/cli/session_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/command/clear_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/command/command_abc.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/command/debug_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/command/export_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/command/help_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/command/model_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/command/prompt-init.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/command/prompt-jj-describe.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/command/prompt_command.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/command/refresh_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/command/registry.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/command/release_notes_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/command/status_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/command/terminal_setup_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/command/thinking_cmd.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/config/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/config/assets/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/config/assets/builtin_config.yaml +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/config/builtin_config.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/config/config.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/const.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/agent.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/manager/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/manager/llm_clients.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/manager/llm_clients_builder.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/manager/sub_agent_manager.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/prompt.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/prompts/prompt-claude-code.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/prompts/prompt-codex.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/prompts/prompt-gemini.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/prompts/prompt-minimal.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/prompts/prompt-sub-agent-explore.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/prompts/prompt-sub-agent-oracle.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/prompts/prompt-sub-agent-web.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/prompts/prompt-sub-agent.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/task.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/file/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/file/_utils.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/file/apply_patch.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/file/apply_patch_tool.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/file/apply_patch_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/file/diff_builder.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/file/edit_tool.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/file/edit_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/file/read_tool.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/file/read_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/file/write_tool.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/file/write_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/report_back_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/shell/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/shell/bash_tool.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/shell/bash_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/shell/command_safety.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/skill/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/skill/skill_tool.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/skill/skill_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/sub_agent_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/todo/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/todo/todo_write_tool.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/todo/todo_write_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/todo/todo_write_tool_raw.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/todo/update_plan_tool.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/todo/update_plan_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/tool_abc.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/tool_context.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/tool_registry.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/tool_runner.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/truncation.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/web/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/web/web_fetch_tool.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/web/web_fetch_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/web/web_search_tool.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/tool/web/web_search_tool.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/core/turn.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/llm/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/llm/anthropic/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/llm/anthropic/client.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/llm/anthropic/input.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/llm/client.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/llm/codex/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/llm/codex/client.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/llm/input_common.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/llm/openai_compatible/client.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/llm/openai_compatible/input.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/llm/openai_compatible/stream.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/llm/openrouter/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/llm/openrouter/client.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/llm/openrouter/input.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/llm/openrouter/reasoning.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/llm/registry.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/llm/responses/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/llm/responses/client.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/llm/responses/input.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/llm/usage.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/protocol/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/protocol/events.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/protocol/llm_param.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/protocol/op.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/protocol/op_handler.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/protocol/sub_agent/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/protocol/sub_agent/explore.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/protocol/sub_agent/oracle.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/protocol/sub_agent/task.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/protocol/sub_agent/web.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/protocol/tools.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/session/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/session/codec.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/session/selector.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/session/store.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/session/templates/export_session.html +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/skill/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/skill/assets/deslop/SKILL.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/skill/assets/dev-docs/SKILL.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/skill/assets/handoff/SKILL.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/skill/assets/jj-workspace/SKILL.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/skill/assets/skill-creator/SKILL.md +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/skill/loader.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/skill/manager.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/skill/system_skills.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/trace/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/core/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/core/display.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/core/input.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/core/stage_manager.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/modes/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/modes/debug/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/modes/debug/display.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/modes/exec/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/modes/exec/display.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/modes/repl/clipboard.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/modes/repl/display.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/modes/repl/key_bindings.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/renderers/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/renderers/assistant.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/renderers/diffs.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/renderers/metadata.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/renderers/sub_agent.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/renderers/thinking.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/renderers/user_input.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/rich/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/rich/cjk_wrap.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/rich/code_panel.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/rich/live.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/rich/quote.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/rich/searchable_text.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/rich/status.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/terminal/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/terminal/color.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/terminal/control.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/terminal/notifier.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/terminal/progress_bar.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/utils/__init__.py +0 -0
- {klaude_code-1.2.27 → klaude_code-1.2.28}/src/klaude_code/ui/utils/common.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "uv_build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "klaude-code"
|
|
7
|
-
version = "1.2.
|
|
7
|
+
version = "1.2.28"
|
|
8
8
|
description = "Minimal code agent CLI"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
@@ -27,6 +27,7 @@ dependencies = [
|
|
|
27
27
|
|
|
28
28
|
[project.scripts]
|
|
29
29
|
klaude = "klaude_code.cli.main:app"
|
|
30
|
+
klaude-code = "klaude_code.cli.main:app"
|
|
30
31
|
|
|
31
32
|
[tool.uv.build-backend]
|
|
32
33
|
module-name = "klaude_code"
|
|
@@ -63,7 +63,15 @@ def open_log_file_in_editor(path: Path) -> None:
|
|
|
63
63
|
editor = "xdg-open"
|
|
64
64
|
|
|
65
65
|
try:
|
|
66
|
-
|
|
66
|
+
# Detach stdin to prevent the editor from interfering with terminal input state.
|
|
67
|
+
# Without this, the spawned process inherits the parent's TTY and can disrupt
|
|
68
|
+
# prompt_toolkit's keyboard handling (e.g., history navigation with up/down keys).
|
|
69
|
+
subprocess.Popen(
|
|
70
|
+
[editor, str(path)],
|
|
71
|
+
stdin=subprocess.DEVNULL,
|
|
72
|
+
stdout=subprocess.DEVNULL,
|
|
73
|
+
stderr=subprocess.DEVNULL,
|
|
74
|
+
)
|
|
67
75
|
except FileNotFoundError:
|
|
68
76
|
log((f"Error: Editor '{editor}' not found", "red"))
|
|
69
77
|
except Exception as exc: # pragma: no cover - best effort
|
|
@@ -16,6 +16,10 @@ from klaude_code.trace import DebugType, prepare_debug_log_file
|
|
|
16
16
|
|
|
17
17
|
def set_terminal_title(title: str) -> None:
|
|
18
18
|
"""Set terminal window title using ANSI escape sequence."""
|
|
19
|
+
# Never write terminal control sequences when stdout is not a TTY (pipes/CI/redirects).
|
|
20
|
+
# This avoids corrupting machine-readable output (e.g., JSON streaming) and log captures.
|
|
21
|
+
if not sys.stdout.isatty():
|
|
22
|
+
return
|
|
19
23
|
sys.stdout.write(f"\033]0;{title}\007")
|
|
20
24
|
sys.stdout.flush()
|
|
21
25
|
|
|
@@ -242,9 +246,43 @@ def main_callback(
|
|
|
242
246
|
) -> None:
|
|
243
247
|
# Only run interactive mode when no subcommand is invoked
|
|
244
248
|
if ctx.invoked_subcommand is None:
|
|
249
|
+
from klaude_code.trace import log
|
|
250
|
+
|
|
251
|
+
resume_by_id_value = resume_by_id.strip() if resume_by_id is not None else None
|
|
252
|
+
if resume_by_id_value == "":
|
|
253
|
+
log(("Error: --resume-by-id cannot be empty", "red"))
|
|
254
|
+
raise typer.Exit(2)
|
|
255
|
+
|
|
256
|
+
if resume_by_id_value is not None and (resume or continue_):
|
|
257
|
+
log(("Error: --resume-by-id cannot be combined with --resume/--continue", "red"))
|
|
258
|
+
raise typer.Exit(2)
|
|
259
|
+
|
|
260
|
+
if resume_by_id_value is not None and not Session.exists(resume_by_id_value):
|
|
261
|
+
log((f"Error: session id '{resume_by_id_value}' not found for this project", "red"))
|
|
262
|
+
log(("Hint: run `klaude --resume` to select an existing session", "yellow"))
|
|
263
|
+
raise typer.Exit(2)
|
|
264
|
+
|
|
265
|
+
# In non-interactive environments, default to exec-mode behavior.
|
|
266
|
+
# This allows: echo "..." | klaude
|
|
267
|
+
if not sys.stdin.isatty() or not sys.stdout.isatty():
|
|
268
|
+
if continue_ or resume or resume_by_id is not None:
|
|
269
|
+
log(("Error: --continue/--resume options require a TTY", "red"))
|
|
270
|
+
log(("Hint: use `klaude exec` for non-interactive usage", "yellow"))
|
|
271
|
+
raise typer.Exit(2)
|
|
272
|
+
|
|
273
|
+
exec_command(
|
|
274
|
+
input_content="",
|
|
275
|
+
model=model,
|
|
276
|
+
select_model=select_model,
|
|
277
|
+
debug=debug,
|
|
278
|
+
debug_filter=debug_filter,
|
|
279
|
+
vanilla=vanilla,
|
|
280
|
+
stream_json=False,
|
|
281
|
+
)
|
|
282
|
+
return
|
|
283
|
+
|
|
245
284
|
from klaude_code.cli.runtime import AppInitConfig, run_interactive
|
|
246
285
|
from klaude_code.config.select_model import select_model_from_config
|
|
247
|
-
from klaude_code.trace import log
|
|
248
286
|
|
|
249
287
|
update_terminal_title()
|
|
250
288
|
|
|
@@ -258,15 +296,6 @@ def main_callback(
|
|
|
258
296
|
# session_id=None means create a new session
|
|
259
297
|
session_id: str | None = None
|
|
260
298
|
|
|
261
|
-
resume_by_id_value = resume_by_id.strip() if resume_by_id is not None else None
|
|
262
|
-
if resume_by_id_value == "":
|
|
263
|
-
log(("Error: --resume-by-id cannot be empty", "red"))
|
|
264
|
-
raise typer.Exit(2)
|
|
265
|
-
|
|
266
|
-
if resume_by_id_value is not None and (resume or continue_):
|
|
267
|
-
log(("Error: --resume-by-id cannot be combined with --resume/--continue", "red"))
|
|
268
|
-
raise typer.Exit(2)
|
|
269
|
-
|
|
270
299
|
if resume:
|
|
271
300
|
session_id = resume_select_session()
|
|
272
301
|
if session_id is None:
|
|
@@ -276,10 +305,6 @@ def main_callback(
|
|
|
276
305
|
session_id = Session.most_recent_session_id()
|
|
277
306
|
|
|
278
307
|
if resume_by_id_value is not None:
|
|
279
|
-
if not Session.exists(resume_by_id_value):
|
|
280
|
-
log((f"Error: session id '{resume_by_id_value}' not found for this project", "red"))
|
|
281
|
-
log(("Hint: run `klaude --resume` to select an existing session", "yellow"))
|
|
282
|
-
raise typer.Exit(2)
|
|
283
308
|
session_id = resume_by_id_value
|
|
284
309
|
# If still no session_id, leave as None to create a new session
|
|
285
310
|
|
|
@@ -255,12 +255,8 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
|
|
|
255
255
|
|
|
256
256
|
# Create status provider for bottom toolbar
|
|
257
257
|
def _status_provider() -> REPLStatusSnapshot:
|
|
258
|
-
# Check for updates (returns None if uv not available)
|
|
259
258
|
update_message = get_update_message()
|
|
260
|
-
|
|
261
|
-
return build_repl_status_snapshot(
|
|
262
|
-
agent=components.executor.context.current_agent, update_message=update_message
|
|
263
|
-
)
|
|
259
|
+
return build_repl_status_snapshot(update_message)
|
|
264
260
|
|
|
265
261
|
# Set up input provider for interactive mode
|
|
266
262
|
def _stop_rich_bottom_ui() -> None:
|
|
@@ -276,9 +272,19 @@ async def run_interactive(init_config: AppInitConfig, session_id: str | None = N
|
|
|
276
272
|
display.wrapped_display.renderer.spinner_stop()
|
|
277
273
|
display.wrapped_display.renderer.stop_bottom_live()
|
|
278
274
|
|
|
275
|
+
# Pass the pre-detected theme to avoid redundant TTY queries.
|
|
276
|
+
# Querying the terminal background again after questionary's interactive selection
|
|
277
|
+
# can interfere with prompt_toolkit's terminal state and break history navigation.
|
|
278
|
+
is_light_background: bool | None = None
|
|
279
|
+
if components.theme == "light":
|
|
280
|
+
is_light_background = True
|
|
281
|
+
elif components.theme == "dark":
|
|
282
|
+
is_light_background = False
|
|
283
|
+
|
|
279
284
|
input_provider: ui.InputProviderABC = ui.PromptToolkitInput(
|
|
280
285
|
status_provider=_status_provider,
|
|
281
286
|
pre_prompt=_stop_rich_bottom_ui,
|
|
287
|
+
is_light_background=is_light_background,
|
|
282
288
|
)
|
|
283
289
|
|
|
284
290
|
# --- Custom Ctrl+C handler: double-press within 2s to exit, single press shows toast ---
|
|
@@ -31,6 +31,7 @@ def ensure_commands_loaded() -> None:
|
|
|
31
31
|
from .debug_cmd import DebugCommand
|
|
32
32
|
from .export_cmd import ExportCommand
|
|
33
33
|
from .export_online_cmd import ExportOnlineCommand
|
|
34
|
+
from .fork_session_cmd import ForkSessionCommand
|
|
34
35
|
from .help_cmd import HelpCommand
|
|
35
36
|
from .model_cmd import ModelCommand
|
|
36
37
|
from .refresh_cmd import RefreshTerminalCommand
|
|
@@ -45,6 +46,7 @@ def ensure_commands_loaded() -> None:
|
|
|
45
46
|
register(RefreshTerminalCommand())
|
|
46
47
|
register(ThinkingCommand())
|
|
47
48
|
register(ModelCommand())
|
|
49
|
+
register(ForkSessionCommand())
|
|
48
50
|
load_prompt_commands()
|
|
49
51
|
register(StatusCommand())
|
|
50
52
|
register(HelpCommand())
|
|
@@ -63,6 +65,7 @@ def __getattr__(name: str) -> object:
|
|
|
63
65
|
"DebugCommand": "debug_cmd",
|
|
64
66
|
"ExportCommand": "export_cmd",
|
|
65
67
|
"ExportOnlineCommand": "export_online_cmd",
|
|
68
|
+
"ForkSessionCommand": "fork_session_cmd",
|
|
66
69
|
"HelpCommand": "help_cmd",
|
|
67
70
|
"ModelCommand": "model_cmd",
|
|
68
71
|
"RefreshTerminalCommand": "refresh_cmd",
|
|
@@ -47,20 +47,23 @@ class ExportOnlineCommand(CommandABC):
|
|
|
47
47
|
)
|
|
48
48
|
return CommandResult(events=[event])
|
|
49
49
|
|
|
50
|
-
# Check if user is logged in to surge
|
|
51
|
-
if not self._is_surge_logged_in(surge_cmd):
|
|
52
|
-
login_cmd = " ".join([*surge_cmd, "login"])
|
|
53
|
-
event = events.DeveloperMessageEvent(
|
|
54
|
-
session_id=agent.session.id,
|
|
55
|
-
item=model.DeveloperMessageItem(
|
|
56
|
-
content=f"Not logged in to surge.sh. Please run: {login_cmd}",
|
|
57
|
-
command_output=model.CommandOutput(command_name=self.name, is_error=True),
|
|
58
|
-
),
|
|
59
|
-
)
|
|
60
|
-
return CommandResult(events=[event])
|
|
61
|
-
|
|
62
50
|
try:
|
|
63
51
|
console = Console()
|
|
52
|
+
# Check login status inside status context since npx surge whoami can be slow
|
|
53
|
+
with console.status(Text("Checking surge.sh login status...", style="dim"), spinner_style="dim"):
|
|
54
|
+
logged_in = self._is_surge_logged_in(surge_cmd)
|
|
55
|
+
|
|
56
|
+
if not logged_in:
|
|
57
|
+
login_cmd = " ".join([*surge_cmd, "login"])
|
|
58
|
+
event = events.DeveloperMessageEvent(
|
|
59
|
+
session_id=agent.session.id,
|
|
60
|
+
item=model.DeveloperMessageItem(
|
|
61
|
+
content=f"Not logged in to surge.sh. Please run: {login_cmd}",
|
|
62
|
+
command_output=model.CommandOutput(command_name=self.name, is_error=True),
|
|
63
|
+
),
|
|
64
|
+
)
|
|
65
|
+
return CommandResult(events=[event])
|
|
66
|
+
|
|
64
67
|
with console.status(Text("Deploying to surge.sh...", style="dim"), spinner_style="dim"):
|
|
65
68
|
html_doc = self._build_html(agent)
|
|
66
69
|
domain = self._generate_domain()
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
|
|
2
|
+
from klaude_code.protocol import commands, events, model
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ForkSessionCommand(CommandABC):
|
|
6
|
+
"""Fork current session to a new session id and show a resume command."""
|
|
7
|
+
|
|
8
|
+
@property
|
|
9
|
+
def name(self) -> commands.CommandName:
|
|
10
|
+
return commands.CommandName.FORK_SESSION
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def summary(self) -> str:
|
|
14
|
+
return "Fork the current session and show a resume-by-id command"
|
|
15
|
+
|
|
16
|
+
async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
|
|
17
|
+
del user_input # unused
|
|
18
|
+
|
|
19
|
+
if agent.session.messages_count == 0:
|
|
20
|
+
event = events.DeveloperMessageEvent(
|
|
21
|
+
session_id=agent.session.id,
|
|
22
|
+
item=model.DeveloperMessageItem(
|
|
23
|
+
content="(no messages to fork)",
|
|
24
|
+
command_output=model.CommandOutput(command_name=self.name),
|
|
25
|
+
),
|
|
26
|
+
)
|
|
27
|
+
return CommandResult(events=[event])
|
|
28
|
+
|
|
29
|
+
new_session = agent.session.fork()
|
|
30
|
+
await new_session.wait_for_flush()
|
|
31
|
+
|
|
32
|
+
event = events.DeveloperMessageEvent(
|
|
33
|
+
session_id=agent.session.id,
|
|
34
|
+
item=model.DeveloperMessageItem(
|
|
35
|
+
content=f"Session forked successfully. New session id: {new_session.id}",
|
|
36
|
+
command_output=model.CommandOutput(
|
|
37
|
+
command_name=self.name,
|
|
38
|
+
ui_extra=model.SessionIdUIExtra(session_id=new_session.id),
|
|
39
|
+
),
|
|
40
|
+
),
|
|
41
|
+
)
|
|
42
|
+
return CommandResult(events=[event])
|
|
@@ -96,6 +96,7 @@ def select_model_from_config(preferred: str | None = None) -> str | None:
|
|
|
96
96
|
else:
|
|
97
97
|
# No matches: show all models without filter hint
|
|
98
98
|
preferred = None
|
|
99
|
+
log(("No matching models found. Showing all models.", "yellow"))
|
|
99
100
|
|
|
100
101
|
# Non-interactive environments (CI/pipes) should never enter an interactive prompt.
|
|
101
102
|
# If we couldn't resolve to a single model deterministically above, fail with a clear hint.
|
|
@@ -404,7 +404,8 @@ class ExecutorContext:
|
|
|
404
404
|
|
|
405
405
|
def _open_file(self, path: Path) -> None:
|
|
406
406
|
try:
|
|
407
|
-
|
|
407
|
+
# Detach stdin to prevent interference with prompt_toolkit's terminal state
|
|
408
|
+
subprocess.run(["open", str(path)], stdin=subprocess.DEVNULL, check=True)
|
|
408
409
|
except FileNotFoundError as exc: # pragma: no cover
|
|
409
410
|
msg = "`open` command not found; please open the HTML manually."
|
|
410
411
|
raise RuntimeError(msg) from exc
|
|
@@ -46,6 +46,17 @@ class AtPatternSource:
|
|
|
46
46
|
mentioned_in: str | None = None
|
|
47
47
|
|
|
48
48
|
|
|
49
|
+
def _extract_at_patterns(content: str) -> list[str]:
|
|
50
|
+
"""Extract @ patterns from content."""
|
|
51
|
+
patterns: list[str] = []
|
|
52
|
+
if "@" in content:
|
|
53
|
+
for match in AT_FILE_PATTERN.finditer(content):
|
|
54
|
+
path_str = match.group("quoted") or match.group("plain")
|
|
55
|
+
if path_str:
|
|
56
|
+
patterns.append(path_str)
|
|
57
|
+
return patterns
|
|
58
|
+
|
|
59
|
+
|
|
49
60
|
def get_at_patterns_with_source(session: Session) -> list[AtPatternSource]:
|
|
50
61
|
"""Get @ patterns from last user input and developer messages, preserving source info."""
|
|
51
62
|
patterns: list[AtPatternSource] = []
|
|
@@ -56,24 +67,14 @@ def get_at_patterns_with_source(session: Session) -> list[AtPatternSource]:
|
|
|
56
67
|
|
|
57
68
|
if isinstance(item, model.UserMessageItem):
|
|
58
69
|
content = item.content or ""
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
path_str = match.group("quoted") or match.group("plain")
|
|
62
|
-
if path_str:
|
|
63
|
-
patterns.append(AtPatternSource(pattern=path_str, mentioned_in=None))
|
|
70
|
+
for path_str in _extract_at_patterns(content):
|
|
71
|
+
patterns.append(AtPatternSource(pattern=path_str, mentioned_in=None))
|
|
64
72
|
break
|
|
65
73
|
|
|
66
|
-
if isinstance(item, model.DeveloperMessageItem):
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
# Use first memory_path as the source if available
|
|
71
|
-
source = item.memory_paths[0] if item.memory_paths else None
|
|
72
|
-
for match in AT_FILE_PATTERN.finditer(content):
|
|
73
|
-
path_str = match.group("quoted") or match.group("plain")
|
|
74
|
-
if path_str:
|
|
75
|
-
patterns.append(AtPatternSource(pattern=path_str, mentioned_in=source))
|
|
76
|
-
|
|
74
|
+
if isinstance(item, model.DeveloperMessageItem) and item.memory_mentioned:
|
|
75
|
+
for memory_path, mentioned_patterns in item.memory_mentioned.items():
|
|
76
|
+
for pattern in mentioned_patterns:
|
|
77
|
+
patterns.append(AtPatternSource(pattern=pattern, mentioned_in=memory_path))
|
|
77
78
|
return patterns
|
|
78
79
|
|
|
79
80
|
|
|
@@ -92,6 +93,23 @@ def get_skill_from_user_input(session: Session) -> str | None:
|
|
|
92
93
|
return None
|
|
93
94
|
|
|
94
95
|
|
|
96
|
+
def _is_tracked_file_unchanged(session: Session, path: str) -> bool:
|
|
97
|
+
status = session.file_tracker.get(path)
|
|
98
|
+
if status is None or status.content_sha256 is None:
|
|
99
|
+
return False
|
|
100
|
+
|
|
101
|
+
try:
|
|
102
|
+
current_mtime = Path(path).stat().st_mtime
|
|
103
|
+
except (OSError, FileNotFoundError):
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
if current_mtime == status.mtime:
|
|
107
|
+
return True
|
|
108
|
+
|
|
109
|
+
current_sha256 = _compute_file_content_sha256(path)
|
|
110
|
+
return current_sha256 is not None and current_sha256 == status.content_sha256
|
|
111
|
+
|
|
112
|
+
|
|
95
113
|
async def _load_at_file_recursive(
|
|
96
114
|
session: Session,
|
|
97
115
|
pattern: str,
|
|
@@ -112,6 +130,8 @@ async def _load_at_file_recursive(
|
|
|
112
130
|
context_token = set_tool_context_from_session(session)
|
|
113
131
|
try:
|
|
114
132
|
if path.exists() and path.is_file():
|
|
133
|
+
if _is_tracked_file_unchanged(session, path_str):
|
|
134
|
+
return
|
|
115
135
|
args = ReadTool.ReadArguments(file_path=path_str)
|
|
116
136
|
tool_result = await ReadTool.call_with_args(args)
|
|
117
137
|
at_files[path_str] = model.AtPatternParseResult(
|
|
@@ -458,6 +478,13 @@ async def memory_reminder(session: Session) -> model.DeveloperMessageItem | None
|
|
|
458
478
|
memories_str = "\n\n".join(
|
|
459
479
|
[f"Contents of {memory.path} ({memory.instruction}):\n\n{memory.content}" for memory in memories]
|
|
460
480
|
)
|
|
481
|
+
# Build memory_mentioned: extract @ patterns from each memory's content
|
|
482
|
+
memory_mentioned: dict[str, list[str]] = {}
|
|
483
|
+
for memory in memories:
|
|
484
|
+
patterns = _extract_at_patterns(memory.content)
|
|
485
|
+
if patterns:
|
|
486
|
+
memory_mentioned[memory.path] = patterns
|
|
487
|
+
|
|
461
488
|
return model.DeveloperMessageItem(
|
|
462
489
|
content=f"""<system-reminder>As you answer the user's questions, you can use the following context:
|
|
463
490
|
|
|
@@ -474,6 +501,7 @@ NEVER proactively create documentation files (*.md) or README files. Only create
|
|
|
474
501
|
IMPORTANT: this context may or may not be relevant to your tasks. You should not respond to this context unless it is highly relevant to your task.
|
|
475
502
|
</system-reminder>""",
|
|
476
503
|
memory_paths=[memory.path for memory in memories],
|
|
504
|
+
memory_mentioned=memory_mentioned or None,
|
|
477
505
|
)
|
|
478
506
|
return None
|
|
479
507
|
|
|
@@ -544,10 +572,18 @@ async def last_path_memory_reminder(
|
|
|
544
572
|
memories_str = "\n\n".join(
|
|
545
573
|
[f"Contents of {memory.path} ({memory.instruction}):\n\n{memory.content}" for memory in memories]
|
|
546
574
|
)
|
|
575
|
+
# Build memory_mentioned: extract @ patterns from each memory's content
|
|
576
|
+
memory_mentioned: dict[str, list[str]] = {}
|
|
577
|
+
for memory in memories:
|
|
578
|
+
patterns = _extract_at_patterns(memory.content)
|
|
579
|
+
if patterns:
|
|
580
|
+
memory_mentioned[memory.path] = patterns
|
|
581
|
+
|
|
547
582
|
return model.DeveloperMessageItem(
|
|
548
583
|
content=f"""<system-reminder>{memories_str}
|
|
549
584
|
</system-reminder>""",
|
|
550
585
|
memory_paths=[memory.path for memory in memories],
|
|
586
|
+
memory_mentioned=memory_mentioned or None,
|
|
551
587
|
)
|
|
552
588
|
|
|
553
589
|
|
|
@@ -45,3 +45,20 @@ sequenceDiagram
|
|
|
45
45
|
|
|
46
46
|
# Styling
|
|
47
47
|
- When defining custom classDefs, always define fill color, stroke color, and text color ("fill", "stroke", "color") explicitly
|
|
48
|
+
- Use colors to distinguish node types and improve readability
|
|
49
|
+
|
|
50
|
+
## Color Palette
|
|
51
|
+
- Cyan #e0f0f0 - information, data flow
|
|
52
|
+
- Green #e0f0e0 - success, completion
|
|
53
|
+
- Blue #e0e8f5 - primary actions, main flow
|
|
54
|
+
- Purple #ede0f5 - highlights, special nodes
|
|
55
|
+
- Orange #f5ebe0 - warnings, pending
|
|
56
|
+
- Red #f5e0e0 - errors, critical
|
|
57
|
+
- Grey #e8e8e8 - neutral elements
|
|
58
|
+
- Yellow #f5f5e0 - attention, notes
|
|
59
|
+
|
|
60
|
+
Example:
|
|
61
|
+
```mermaid
|
|
62
|
+
classDef primary fill:#e0e8f5,stroke:#3078C5,color:#1a1a1a
|
|
63
|
+
classDef success fill:#e0f0e0,stroke:#00875f,color:#1a1a1a
|
|
64
|
+
```
|
|
@@ -11,7 +11,7 @@ from klaude_code.core.tool.tool_abc import ToolABC, load_desc
|
|
|
11
11
|
from klaude_code.core.tool.tool_registry import register
|
|
12
12
|
from klaude_code.protocol import llm_param, model, tools
|
|
13
13
|
|
|
14
|
-
_MERMAID_LIVE_PREFIX = "https://mermaid.live/
|
|
14
|
+
_MERMAID_LIVE_PREFIX = "https://mermaid.live/edit#pako:"
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
@register(tools.MERMAID)
|
|
@@ -31,7 +31,7 @@ class MermaidTool(ToolABC):
|
|
|
31
31
|
"type": "object",
|
|
32
32
|
"properties": {
|
|
33
33
|
"code": {
|
|
34
|
-
"description": "The Mermaid diagram code to render (DO NOT
|
|
34
|
+
"description": "The Mermaid diagram code to render (DO NOT use HTML tags in node labels)",
|
|
35
35
|
"type": "string",
|
|
36
36
|
},
|
|
37
37
|
},
|
|
@@ -1,9 +1,25 @@
|
|
|
1
|
+
import re
|
|
1
2
|
from abc import ABC, abstractmethod
|
|
2
3
|
|
|
3
4
|
from openai.types.chat.chat_completion_chunk import ChoiceDeltaToolCall
|
|
4
5
|
from pydantic import BaseModel, Field
|
|
5
6
|
|
|
6
7
|
from klaude_code.protocol import model
|
|
8
|
+
from klaude_code.trace.log import log_debug
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def normalize_tool_name(name: str) -> str:
|
|
12
|
+
"""Normalize tool name from Gemini-3 format.
|
|
13
|
+
|
|
14
|
+
Gemini-3 sometimes returns tool names in format like 'tool_Edit_mUoY2p3W3r3z8uO5P2nZ'.
|
|
15
|
+
This function extracts the actual tool name (e.g., 'Edit').
|
|
16
|
+
"""
|
|
17
|
+
match = re.match(r"^tool_([A-Za-z]+)_[A-Za-z0-9]+$", name)
|
|
18
|
+
if match:
|
|
19
|
+
normalized = match.group(1)
|
|
20
|
+
log_debug(f"Gemini-3 tool name normalized: {name} -> {normalized}", style="yellow")
|
|
21
|
+
return normalized
|
|
22
|
+
return name
|
|
7
23
|
|
|
8
24
|
|
|
9
25
|
class ToolCallAccumulatorABC(ABC):
|
|
@@ -74,7 +90,7 @@ class BasicToolCallAccumulator(ToolCallAccumulatorABC, BaseModel):
|
|
|
74
90
|
if first_chunk.function is None:
|
|
75
91
|
continue
|
|
76
92
|
if first_chunk.function.name:
|
|
77
|
-
result[-1].name = first_chunk.function.name
|
|
93
|
+
result[-1].name = normalize_tool_name(first_chunk.function.name)
|
|
78
94
|
if first_chunk.function.arguments:
|
|
79
95
|
result[-1].arguments += first_chunk.function.arguments
|
|
80
96
|
return result
|
|
@@ -15,6 +15,7 @@ class CommandName(str, Enum):
|
|
|
15
15
|
STATUS = "status"
|
|
16
16
|
RELEASE_NOTES = "release-notes"
|
|
17
17
|
THINKING = "thinking"
|
|
18
|
+
FORK_SESSION = "fork-session"
|
|
18
19
|
# PLAN and DOC are dynamically registered now, but kept here if needed for reference
|
|
19
20
|
# or we can remove them if no code explicitly imports them.
|
|
20
21
|
# PLAN = "plan"
|
|
@@ -260,6 +260,7 @@ class DeveloperMessageItem(BaseModel):
|
|
|
260
260
|
|
|
261
261
|
# Special fields for reminders UI
|
|
262
262
|
memory_paths: list[str] | None = None
|
|
263
|
+
memory_mentioned: dict[str, list[str]] | None = None # memory_path -> list of @ patterns mentioned in it
|
|
263
264
|
external_file_changes: list[str] | None = None
|
|
264
265
|
todo_use: bool | None = None
|
|
265
266
|
at_files: list[AtPatternParseResult] | None = None
|
|
@@ -427,6 +427,41 @@ def _get_diff_ui_extra(ui_extra: model.ToolResultUIExtra | None) -> model.DiffUI
|
|
|
427
427
|
return None
|
|
428
428
|
|
|
429
429
|
|
|
430
|
+
def _render_markdown_doc(doc: model.MarkdownDocUIExtra) -> str:
|
|
431
|
+
encoded = _escape_html(doc.content)
|
|
432
|
+
file_path = _escape_html(doc.file_path)
|
|
433
|
+
header = f'<div class="diff-file">{file_path} <span style="font-weight: normal; color: var(--text-dim); font-size: 12px; margin-left: 8px;">(markdown content)</span></div>'
|
|
434
|
+
|
|
435
|
+
# Using a container that mimics diff-view but for markdown
|
|
436
|
+
content = (
|
|
437
|
+
f'<div class="markdown-content markdown-body" data-raw="{encoded}" '
|
|
438
|
+
f'style="padding: 12px; border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--bg-body); margin-top: 4px;">'
|
|
439
|
+
f'<noscript><pre style="white-space: pre-wrap;">{encoded}</pre></noscript>'
|
|
440
|
+
f"</div>"
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
line_count = doc.content.count("\n") + 1
|
|
444
|
+
open_attr = " open"
|
|
445
|
+
|
|
446
|
+
return (
|
|
447
|
+
f'<details class="diff-collapsible"{open_attr}>'
|
|
448
|
+
f"<summary>File Content ({line_count} lines)</summary>"
|
|
449
|
+
f'<div style="margin-top: 8px;">'
|
|
450
|
+
f"{header}"
|
|
451
|
+
f"{content}"
|
|
452
|
+
f"</div>"
|
|
453
|
+
f"</details>"
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def _collect_ui_extras(ui_extra: model.ToolResultUIExtra | None) -> list[model.ToolResultUIExtra]:
|
|
458
|
+
if ui_extra is None:
|
|
459
|
+
return []
|
|
460
|
+
if isinstance(ui_extra, model.MultiUIExtra):
|
|
461
|
+
return list(ui_extra.items)
|
|
462
|
+
return [ui_extra]
|
|
463
|
+
|
|
464
|
+
|
|
430
465
|
def _build_add_only_diff(text: str, file_path: str) -> model.DiffUIExtra:
|
|
431
466
|
lines: list[model.DiffLine] = []
|
|
432
467
|
new_line_no = 1
|
|
@@ -567,19 +602,26 @@ def _format_tool_call(tool_call: model.ToolCallItem, result: model.ToolResultIte
|
|
|
567
602
|
]
|
|
568
603
|
|
|
569
604
|
if result:
|
|
570
|
-
|
|
571
|
-
|
|
605
|
+
extras = _collect_ui_extras(result.ui_extra)
|
|
606
|
+
|
|
607
|
+
mermaid_extra = next((x for x in extras if isinstance(x, model.MermaidLinkUIExtra)), None)
|
|
608
|
+
mermaid_source = mermaid_extra if mermaid_extra else result.ui_extra
|
|
609
|
+
mermaid_html = _get_mermaid_link_html(mermaid_source, tool_call)
|
|
572
610
|
|
|
573
611
|
should_hide_text = tool_call.name in ("TodoWrite", "update_plan") and result.status != "error"
|
|
574
612
|
|
|
575
|
-
if
|
|
613
|
+
if (
|
|
614
|
+
tool_call.name == "Edit"
|
|
615
|
+
and not any(isinstance(x, model.DiffUIExtra) for x in extras)
|
|
616
|
+
and result.status != "error"
|
|
617
|
+
):
|
|
576
618
|
try:
|
|
577
619
|
args_data = json.loads(tool_call.arguments)
|
|
578
620
|
file_path = args_data.get("file_path", "Unknown file")
|
|
579
621
|
old_string = args_data.get("old_string", "")
|
|
580
622
|
new_string = args_data.get("new_string", "")
|
|
581
623
|
if old_string == "" and new_string:
|
|
582
|
-
|
|
624
|
+
extras.append(_build_add_only_diff(new_string, file_path))
|
|
583
625
|
except (json.JSONDecodeError, TypeError):
|
|
584
626
|
pass
|
|
585
627
|
|
|
@@ -591,8 +633,11 @@ def _format_tool_call(tool_call: model.ToolCallItem, result: model.ToolResultIte
|
|
|
591
633
|
else:
|
|
592
634
|
items_to_render.append(_render_text_block(result.output))
|
|
593
635
|
|
|
594
|
-
|
|
595
|
-
|
|
636
|
+
for extra in extras:
|
|
637
|
+
if isinstance(extra, model.DiffUIExtra):
|
|
638
|
+
items_to_render.append(_render_diff_block(extra))
|
|
639
|
+
elif isinstance(extra, model.MarkdownDocUIExtra):
|
|
640
|
+
items_to_render.append(_render_markdown_doc(extra))
|
|
596
641
|
|
|
597
642
|
if mermaid_html:
|
|
598
643
|
items_to_render.append(mermaid_html)
|
|
@@ -197,6 +197,28 @@ class Session(BaseModel):
|
|
|
197
197
|
)
|
|
198
198
|
self._store.append_and_flush(session_id=self.id, items=items, meta=meta)
|
|
199
199
|
|
|
200
|
+
def fork(self, *, new_id: str | None = None) -> Session:
|
|
201
|
+
"""Create a new session as a fork of the current session.
|
|
202
|
+
|
|
203
|
+
The forked session copies metadata and conversation history, but does not
|
|
204
|
+
modify the current session.
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
forked = Session.create(id=new_id, work_dir=self.work_dir)
|
|
208
|
+
|
|
209
|
+
forked.sub_agent_state = None
|
|
210
|
+
forked.model_name = self.model_name
|
|
211
|
+
forked.model_config_name = self.model_config_name
|
|
212
|
+
forked.model_thinking = self.model_thinking.model_copy(deep=True) if self.model_thinking is not None else None
|
|
213
|
+
forked.file_tracker = {k: v.model_copy(deep=True) for k, v in self.file_tracker.items()}
|
|
214
|
+
forked.todos = [todo.model_copy(deep=True) for todo in self.todos]
|
|
215
|
+
|
|
216
|
+
items = [cast(model.ConversationItem, it.model_copy(deep=True)) for it in self.conversation_history]
|
|
217
|
+
if items:
|
|
218
|
+
forked.append_history(items)
|
|
219
|
+
|
|
220
|
+
return forked
|
|
221
|
+
|
|
200
222
|
async def wait_for_flush(self) -> None:
|
|
201
223
|
await self._store.wait_for_flush(self.id)
|
|
202
224
|
|
|
@@ -302,6 +302,12 @@ def _trash_path(path: Path) -> None:
|
|
|
302
302
|
"""Send a path to trash, falling back to unlink if trash is unavailable."""
|
|
303
303
|
|
|
304
304
|
try:
|
|
305
|
-
subprocess.run(
|
|
305
|
+
subprocess.run(
|
|
306
|
+
["trash", str(path)],
|
|
307
|
+
stdin=subprocess.DEVNULL,
|
|
308
|
+
stdout=subprocess.DEVNULL,
|
|
309
|
+
stderr=subprocess.DEVNULL,
|
|
310
|
+
check=False,
|
|
311
|
+
)
|
|
306
312
|
except FileNotFoundError:
|
|
307
313
|
path.unlink(missing_ok=True)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
from klaude_code.ui.modes.repl.input_prompt_toolkit import REPLStatusSnapshot
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def build_repl_status_snapshot(update_message: str | None) -> REPLStatusSnapshot:
|
|
5
|
+
"""Build a status snapshot for the REPL bottom toolbar."""
|
|
6
|
+
return REPLStatusSnapshot(update_message=update_message)
|