klaude-code 1.5.0__tar.gz → 1.6.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-1.5.0 → klaude_code-1.6.0}/PKG-INFO +1 -1
- {klaude_code-1.5.0 → klaude_code-1.6.0}/pyproject.toml +2 -1
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/cli/main.py +3 -56
- klaude_code-1.6.0/src/klaude_code/command/fork_session_cmd.py +260 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/command/refresh_cmd.py +4 -4
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/command/resume_cmd.py +21 -11
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/llm/usage.py +1 -1
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/session/__init__.py +2 -2
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/session/selector.py +32 -4
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/session/session.py +18 -12
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/modes/repl/event_handler.py +22 -32
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/modes/repl/renderer.py +1 -1
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/renderers/developer.py +2 -2
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/renderers/metadata.py +8 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/rich/markdown.py +41 -9
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/rich/status.py +83 -22
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/terminal/selector.py +72 -3
- klaude_code-1.5.0/src/klaude_code/command/fork_session_cmd.py +0 -42
- {klaude_code-1.5.0 → klaude_code-1.6.0}/README.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/auth/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/auth/codex/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/auth/codex/exceptions.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/auth/codex/jwt_utils.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/auth/codex/oauth.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/auth/codex/token_manager.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/cli/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/cli/auth_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/cli/config_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/cli/debug.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/cli/list_model.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/cli/runtime.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/cli/self_update.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/cli/session_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/command/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/command/clear_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/command/command_abc.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/command/debug_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/command/export_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/command/export_online_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/command/help_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/command/model_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/command/model_select.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/command/prompt-init.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/command/prompt-jj-describe.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/command/prompt_command.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/command/registry.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/command/release_notes_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/command/status_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/command/terminal_setup_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/command/thinking_cmd.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/config/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/config/assets/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/config/assets/builtin_config.yaml +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/config/builtin_config.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/config/config.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/config/select_model.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/config/thinking.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/const.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/agent.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/executor.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/manager/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/manager/llm_clients.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/manager/llm_clients_builder.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/manager/sub_agent_manager.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/prompt.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/prompts/prompt-claude-code.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/prompts/prompt-codex-gpt-5-1-codex-max.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/prompts/prompt-codex-gpt-5-2-codex.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/prompts/prompt-codex.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/prompts/prompt-gemini.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/prompts/prompt-minimal.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/prompts/prompt-sub-agent-explore.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/prompts/prompt-sub-agent-oracle.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/prompts/prompt-sub-agent-web.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/prompts/prompt-sub-agent.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/reminders.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/task.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/file/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/file/_utils.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/file/apply_patch.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/file/apply_patch_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/file/apply_patch_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/file/diff_builder.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/file/edit_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/file/edit_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/file/move_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/file/move_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/file/read_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/file/read_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/file/write_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/file/write_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/report_back_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/shell/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/shell/bash_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/shell/bash_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/shell/command_safety.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/skill/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/skill/skill_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/skill/skill_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/sub_agent_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/todo/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/todo/todo_write_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/todo/todo_write_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/todo/todo_write_tool_raw.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/todo/update_plan_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/todo/update_plan_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/tool_abc.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/tool_context.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/tool_registry.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/tool_runner.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/truncation.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/web/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/web/mermaid_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/web/mermaid_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/web/web_fetch_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/web/web_fetch_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/web/web_search_tool.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/tool/web/web_search_tool.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/core/turn.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/llm/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/llm/anthropic/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/llm/anthropic/client.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/llm/anthropic/input.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/llm/client.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/llm/codex/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/llm/codex/client.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/llm/input_common.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/llm/openai_compatible/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/llm/openai_compatible/client.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/llm/openai_compatible/input.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/llm/openai_compatible/stream.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/llm/openai_compatible/tool_call_accumulator.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/llm/openrouter/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/llm/openrouter/client.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/llm/openrouter/input.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/llm/openrouter/reasoning.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/llm/registry.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/llm/responses/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/llm/responses/client.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/llm/responses/input.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/protocol/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/protocol/commands.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/protocol/events.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/protocol/llm_param.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/protocol/model.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/protocol/op.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/protocol/op_handler.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/protocol/sub_agent/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/protocol/sub_agent/explore.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/protocol/sub_agent/oracle.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/protocol/sub_agent/task.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/protocol/sub_agent/web.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/protocol/tools.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/session/codec.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/session/export.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/session/store.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/session/templates/export_session.html +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/session/templates/mermaid_viewer.html +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/skill/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/skill/assets/deslop/SKILL.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/skill/assets/dev-docs/SKILL.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/skill/assets/handoff/SKILL.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/skill/assets/jj-workspace/SKILL.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/skill/assets/skill-creator/SKILL.md +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/skill/loader.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/skill/manager.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/skill/system_skills.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/trace/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/trace/log.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/core/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/core/display.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/core/input.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/core/stage_manager.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/modes/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/modes/debug/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/modes/debug/display.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/modes/exec/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/modes/exec/display.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/modes/repl/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/modes/repl/clipboard.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/modes/repl/completers.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/modes/repl/display.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/modes/repl/input_prompt_toolkit.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/modes/repl/key_bindings.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/renderers/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/renderers/assistant.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/renderers/bash_syntax.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/renderers/common.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/renderers/diffs.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/renderers/errors.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/renderers/mermaid_viewer.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/renderers/sub_agent.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/renderers/thinking.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/renderers/tools.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/renderers/user_input.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/rich/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/rich/cjk_wrap.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/rich/code_panel.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/rich/live.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/rich/quote.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/rich/searchable_text.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/rich/theme.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/terminal/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/terminal/color.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/terminal/control.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/terminal/notifier.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/terminal/progress_bar.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/src/klaude_code/ui/utils/__init__.py +0 -0
- {klaude_code-1.5.0 → klaude_code-1.6.0}/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.
|
|
7
|
+
version = "1.6.0"
|
|
8
8
|
description = "Minimal code agent CLI"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
@@ -33,6 +33,7 @@ module-name = "klaude_code"
|
|
|
33
33
|
|
|
34
34
|
[dependency-groups]
|
|
35
35
|
dev = [
|
|
36
|
+
"hypothesis>=6.148.8",
|
|
36
37
|
"import-linter>=2.6",
|
|
37
38
|
"pyright>=1.1.407",
|
|
38
39
|
"pytest>=8.4.1",
|
|
@@ -11,64 +11,11 @@ from klaude_code.cli.config_cmd import register_config_commands
|
|
|
11
11
|
from klaude_code.cli.debug import DEBUG_FILTER_HELP, open_log_file_in_editor, resolve_debug_settings
|
|
12
12
|
from klaude_code.cli.self_update import register_self_update_commands, version_option_callback
|
|
13
13
|
from klaude_code.cli.session_cmd import register_session_commands
|
|
14
|
-
from klaude_code.
|
|
14
|
+
from klaude_code.command.resume_cmd import select_session_sync
|
|
15
|
+
from klaude_code.session import Session
|
|
15
16
|
from klaude_code.trace import DebugType, prepare_debug_log_file
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
def select_session_interactive() -> str | None:
|
|
19
|
-
"""Interactive session selection for CLI.
|
|
20
|
-
|
|
21
|
-
Returns:
|
|
22
|
-
Selected session ID, or None if no session selected or no sessions exist.
|
|
23
|
-
"""
|
|
24
|
-
from klaude_code.trace import log
|
|
25
|
-
|
|
26
|
-
options = build_session_select_options()
|
|
27
|
-
if not options:
|
|
28
|
-
log("No sessions found for this project.")
|
|
29
|
-
return None
|
|
30
|
-
|
|
31
|
-
from prompt_toolkit.styles import Style
|
|
32
|
-
|
|
33
|
-
from klaude_code.ui.terminal.selector import SelectItem, select_one
|
|
34
|
-
|
|
35
|
-
items: list[SelectItem[str]] = []
|
|
36
|
-
for opt in options:
|
|
37
|
-
title = [
|
|
38
|
-
("class:msg", f"{opt.first_user_message}\n"),
|
|
39
|
-
("class:meta", f" {opt.messages_count} · {opt.relative_time} · {opt.model_name} · {opt.session_id}\n\n"),
|
|
40
|
-
]
|
|
41
|
-
items.append(
|
|
42
|
-
SelectItem(
|
|
43
|
-
title=title,
|
|
44
|
-
value=opt.session_id,
|
|
45
|
-
search_text=f"{opt.first_user_message} {opt.model_name} {opt.session_id}",
|
|
46
|
-
)
|
|
47
|
-
)
|
|
48
|
-
|
|
49
|
-
try:
|
|
50
|
-
return select_one(
|
|
51
|
-
message="Select a session to resume:",
|
|
52
|
-
items=items,
|
|
53
|
-
pointer="→",
|
|
54
|
-
style=Style(
|
|
55
|
-
[
|
|
56
|
-
("msg", ""),
|
|
57
|
-
("meta", "fg:ansibrightblack"),
|
|
58
|
-
("pointer", "bold fg:ansigreen"),
|
|
59
|
-
("highlighted", "fg:ansigreen"),
|
|
60
|
-
("search_prefix", "fg:ansibrightblack"),
|
|
61
|
-
("search_success", "noinherit fg:ansigreen"),
|
|
62
|
-
("search_none", "noinherit fg:ansired"),
|
|
63
|
-
("question", "bold"),
|
|
64
|
-
("text", ""),
|
|
65
|
-
]
|
|
66
|
-
),
|
|
67
|
-
)
|
|
68
|
-
except KeyboardInterrupt:
|
|
69
|
-
return None
|
|
70
|
-
|
|
71
|
-
|
|
72
19
|
def set_terminal_title(title: str) -> None:
|
|
73
20
|
"""Set terminal window title using ANSI escape sequence."""
|
|
74
21
|
# Never write terminal control sequences when stdout is not a TTY (pipes/CI/redirects).
|
|
@@ -361,7 +308,7 @@ def main_callback(
|
|
|
361
308
|
session_id: str | None = None
|
|
362
309
|
|
|
363
310
|
if resume:
|
|
364
|
-
session_id =
|
|
311
|
+
session_id = select_session_sync()
|
|
365
312
|
if session_id is None:
|
|
366
313
|
return
|
|
367
314
|
# If user didn't pick, allow fallback to --continue
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import sys
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Literal
|
|
5
|
+
|
|
6
|
+
from prompt_toolkit.styles import Style
|
|
7
|
+
|
|
8
|
+
from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
|
|
9
|
+
from klaude_code.protocol import commands, events, model
|
|
10
|
+
from klaude_code.ui.terminal.selector import SelectItem, select_one
|
|
11
|
+
|
|
12
|
+
FORK_SELECT_STYLE = Style(
|
|
13
|
+
[
|
|
14
|
+
("msg", ""),
|
|
15
|
+
("meta", "fg:ansibrightblack"),
|
|
16
|
+
("separator", "fg:ansibrightblack"),
|
|
17
|
+
("assistant", "fg:ansiblue"),
|
|
18
|
+
("pointer", "bold fg:ansigreen"),
|
|
19
|
+
("search_prefix", "fg:ansibrightblack"),
|
|
20
|
+
("search_success", "noinherit fg:ansigreen"),
|
|
21
|
+
("search_none", "noinherit fg:ansired"),
|
|
22
|
+
("question", "bold"),
|
|
23
|
+
("text", ""),
|
|
24
|
+
]
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class ForkPoint:
|
|
30
|
+
"""A fork point in conversation history."""
|
|
31
|
+
|
|
32
|
+
history_index: int | None # None means fork entire conversation
|
|
33
|
+
user_message: str
|
|
34
|
+
tool_call_stats: dict[str, int] # tool_name -> count
|
|
35
|
+
last_assistant_summary: str
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _truncate(text: str, max_len: int = 60) -> str:
|
|
39
|
+
"""Truncate text to max_len, adding ellipsis if needed."""
|
|
40
|
+
text = text.replace("\n", " ").strip()
|
|
41
|
+
if len(text) <= max_len:
|
|
42
|
+
return text
|
|
43
|
+
return text[: max_len - 3] + "..."
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _build_fork_points(conversation_history: list[model.ConversationItem]) -> list[ForkPoint]:
|
|
47
|
+
"""Build list of fork points from conversation history.
|
|
48
|
+
|
|
49
|
+
Fork points are:
|
|
50
|
+
- Each UserMessageItem position (for UI display, including first which would be empty session)
|
|
51
|
+
- The end of the conversation (fork entire conversation)
|
|
52
|
+
"""
|
|
53
|
+
fork_points: list[ForkPoint] = []
|
|
54
|
+
user_indices: list[int] = []
|
|
55
|
+
|
|
56
|
+
for i, item in enumerate(conversation_history):
|
|
57
|
+
if isinstance(item, model.UserMessageItem):
|
|
58
|
+
user_indices.append(i)
|
|
59
|
+
|
|
60
|
+
# For each UserMessageItem, create a fork point at that position
|
|
61
|
+
for i, user_idx in enumerate(user_indices):
|
|
62
|
+
user_item = conversation_history[user_idx]
|
|
63
|
+
assert isinstance(user_item, model.UserMessageItem)
|
|
64
|
+
|
|
65
|
+
# Find the end of this "task" (next UserMessageItem or end of history)
|
|
66
|
+
next_user_idx = user_indices[i + 1] if i + 1 < len(user_indices) else len(conversation_history)
|
|
67
|
+
|
|
68
|
+
# Count tool calls by name and find last assistant message in this segment
|
|
69
|
+
tool_stats: dict[str, int] = {}
|
|
70
|
+
last_assistant_content = ""
|
|
71
|
+
for j in range(user_idx, next_user_idx):
|
|
72
|
+
item = conversation_history[j]
|
|
73
|
+
if isinstance(item, model.ToolCallItem):
|
|
74
|
+
tool_stats[item.name] = tool_stats.get(item.name, 0) + 1
|
|
75
|
+
elif isinstance(item, model.AssistantMessageItem) and item.content:
|
|
76
|
+
last_assistant_content = item.content
|
|
77
|
+
|
|
78
|
+
fork_points.append(
|
|
79
|
+
ForkPoint(
|
|
80
|
+
history_index=user_idx,
|
|
81
|
+
user_message=user_item.content or "(empty)",
|
|
82
|
+
tool_call_stats=tool_stats,
|
|
83
|
+
last_assistant_summary=_truncate(last_assistant_content) if last_assistant_content else "",
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Add the "fork entire conversation" option at the end
|
|
88
|
+
if user_indices:
|
|
89
|
+
fork_points.append(
|
|
90
|
+
ForkPoint(
|
|
91
|
+
history_index=None, # None means fork entire conversation
|
|
92
|
+
user_message="", # No specific message, this represents the end
|
|
93
|
+
tool_call_stats={},
|
|
94
|
+
last_assistant_summary="",
|
|
95
|
+
)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return fork_points
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _build_select_items(fork_points: list[ForkPoint]) -> list[SelectItem[int | None]]:
|
|
102
|
+
"""Build SelectItem list from fork points."""
|
|
103
|
+
items: list[SelectItem[int | None]] = []
|
|
104
|
+
|
|
105
|
+
for i, fp in enumerate(fork_points):
|
|
106
|
+
is_first = i == 0
|
|
107
|
+
is_last = i == len(fork_points) - 1
|
|
108
|
+
|
|
109
|
+
# Build the title
|
|
110
|
+
title_parts: list[tuple[str, str]] = []
|
|
111
|
+
|
|
112
|
+
# First line: separator (with special markers for first/last fork points)
|
|
113
|
+
if is_first and not is_last:
|
|
114
|
+
title_parts.append(("class:separator", "----- fork from here (empty session) -----\n\n"))
|
|
115
|
+
elif is_last:
|
|
116
|
+
title_parts.append(("class:separator", "----- fork from here (entire session) -----\n\n"))
|
|
117
|
+
else:
|
|
118
|
+
title_parts.append(("class:separator", "----- fork from here -----\n\n"))
|
|
119
|
+
|
|
120
|
+
if not is_last:
|
|
121
|
+
# Second line: user message
|
|
122
|
+
title_parts.append(("class:msg", f"user: {_truncate(fp.user_message, 70)}\n"))
|
|
123
|
+
|
|
124
|
+
# Third line: tool call stats (if any)
|
|
125
|
+
if fp.tool_call_stats:
|
|
126
|
+
tool_parts = [f"{name} × {count}" for name, count in fp.tool_call_stats.items()]
|
|
127
|
+
title_parts.append(("class:meta", f"tools: {', '.join(tool_parts)}\n"))
|
|
128
|
+
|
|
129
|
+
# Fourth line: last assistant message summary (if any)
|
|
130
|
+
if fp.last_assistant_summary:
|
|
131
|
+
title_parts.append(("class:assistant", f"ai: {fp.last_assistant_summary}\n"))
|
|
132
|
+
|
|
133
|
+
# Empty line at the end
|
|
134
|
+
title_parts.append(("class:text", "\n"))
|
|
135
|
+
|
|
136
|
+
items.append(
|
|
137
|
+
SelectItem(
|
|
138
|
+
title=title_parts,
|
|
139
|
+
value=fp.history_index,
|
|
140
|
+
search_text=fp.user_message if not is_last else "fork entire conversation",
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
return items
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _select_fork_point_sync(fork_points: list[ForkPoint]) -> int | None | Literal["cancelled"]:
|
|
148
|
+
"""Interactive fork point selection (sync version for asyncio.to_thread).
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
- int: history index to fork at (exclusive)
|
|
152
|
+
- None: fork entire conversation
|
|
153
|
+
- "cancelled": user cancelled selection
|
|
154
|
+
"""
|
|
155
|
+
items = _build_select_items(fork_points)
|
|
156
|
+
if not items:
|
|
157
|
+
return None
|
|
158
|
+
|
|
159
|
+
# Default to the last option (fork entire conversation)
|
|
160
|
+
last_value = items[-1].value
|
|
161
|
+
|
|
162
|
+
# Non-interactive environments default to forking entire conversation
|
|
163
|
+
if not sys.stdin.isatty() or not sys.stdout.isatty():
|
|
164
|
+
return last_value
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
result = select_one(
|
|
168
|
+
message="Select fork point (messages before this point will be included):",
|
|
169
|
+
items=items,
|
|
170
|
+
pointer="→",
|
|
171
|
+
style=FORK_SELECT_STYLE,
|
|
172
|
+
initial_value=last_value,
|
|
173
|
+
highlight_pointed_item=False,
|
|
174
|
+
)
|
|
175
|
+
if result is None:
|
|
176
|
+
return "cancelled"
|
|
177
|
+
return result
|
|
178
|
+
except KeyboardInterrupt:
|
|
179
|
+
return "cancelled"
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
class ForkSessionCommand(CommandABC):
|
|
183
|
+
"""Fork current session to a new session id and show a resume command."""
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def name(self) -> commands.CommandName:
|
|
187
|
+
return commands.CommandName.FORK_SESSION
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def summary(self) -> str:
|
|
191
|
+
return "Fork the current session and show a resume-by-id command"
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def is_interactive(self) -> bool:
|
|
195
|
+
return True
|
|
196
|
+
|
|
197
|
+
async def run(self, agent: Agent, user_input: model.UserInputPayload) -> CommandResult:
|
|
198
|
+
del user_input # unused
|
|
199
|
+
|
|
200
|
+
if agent.session.messages_count == 0:
|
|
201
|
+
event = events.DeveloperMessageEvent(
|
|
202
|
+
session_id=agent.session.id,
|
|
203
|
+
item=model.DeveloperMessageItem(
|
|
204
|
+
content="(no messages to fork)",
|
|
205
|
+
command_output=model.CommandOutput(command_name=self.name),
|
|
206
|
+
),
|
|
207
|
+
)
|
|
208
|
+
return CommandResult(events=[event], persist_user_input=False, persist_events=False)
|
|
209
|
+
|
|
210
|
+
# Build fork points from conversation history
|
|
211
|
+
fork_points = _build_fork_points(agent.session.conversation_history)
|
|
212
|
+
|
|
213
|
+
if not fork_points:
|
|
214
|
+
# Only one user message, just fork entirely
|
|
215
|
+
new_session = agent.session.fork()
|
|
216
|
+
await new_session.wait_for_flush()
|
|
217
|
+
|
|
218
|
+
event = events.DeveloperMessageEvent(
|
|
219
|
+
session_id=agent.session.id,
|
|
220
|
+
item=model.DeveloperMessageItem(
|
|
221
|
+
content=f"Session forked successfully. New session id: {new_session.id}",
|
|
222
|
+
command_output=model.CommandOutput(
|
|
223
|
+
command_name=self.name,
|
|
224
|
+
ui_extra=model.SessionIdUIExtra(session_id=new_session.id),
|
|
225
|
+
),
|
|
226
|
+
),
|
|
227
|
+
)
|
|
228
|
+
return CommandResult(events=[event], persist_user_input=False, persist_events=False)
|
|
229
|
+
|
|
230
|
+
# Interactive selection
|
|
231
|
+
selected = await asyncio.to_thread(_select_fork_point_sync, fork_points)
|
|
232
|
+
|
|
233
|
+
if selected == "cancelled":
|
|
234
|
+
event = events.DeveloperMessageEvent(
|
|
235
|
+
session_id=agent.session.id,
|
|
236
|
+
item=model.DeveloperMessageItem(
|
|
237
|
+
content="(fork cancelled)",
|
|
238
|
+
command_output=model.CommandOutput(command_name=self.name),
|
|
239
|
+
),
|
|
240
|
+
)
|
|
241
|
+
return CommandResult(events=[event], persist_user_input=False, persist_events=False)
|
|
242
|
+
|
|
243
|
+
# Perform the fork
|
|
244
|
+
new_session = agent.session.fork(until_index=selected)
|
|
245
|
+
await new_session.wait_for_flush()
|
|
246
|
+
|
|
247
|
+
# Build result message
|
|
248
|
+
fork_description = "entire conversation" if selected is None else f"up to message index {selected}"
|
|
249
|
+
|
|
250
|
+
event = events.DeveloperMessageEvent(
|
|
251
|
+
session_id=agent.session.id,
|
|
252
|
+
item=model.DeveloperMessageItem(
|
|
253
|
+
content=f"Session forked ({fork_description}). New session id: {new_session.id}",
|
|
254
|
+
command_output=model.CommandOutput(
|
|
255
|
+
command_name=self.name,
|
|
256
|
+
ui_extra=model.SessionIdUIExtra(session_id=new_session.id),
|
|
257
|
+
),
|
|
258
|
+
),
|
|
259
|
+
)
|
|
260
|
+
return CommandResult(events=[event], persist_user_input=False, persist_events=False)
|
|
@@ -23,7 +23,7 @@ class RefreshTerminalCommand(CommandABC):
|
|
|
23
23
|
|
|
24
24
|
os.system("cls" if os.name == "nt" else "clear")
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
return CommandResult(
|
|
27
27
|
events=[
|
|
28
28
|
events.WelcomeEvent(
|
|
29
29
|
work_dir=str(agent.session.work_dir),
|
|
@@ -35,7 +35,7 @@ class RefreshTerminalCommand(CommandABC):
|
|
|
35
35
|
updated_at=agent.session.updated_at,
|
|
36
36
|
is_load=False,
|
|
37
37
|
),
|
|
38
|
-
]
|
|
38
|
+
],
|
|
39
|
+
persist_user_input=False,
|
|
40
|
+
persist_events=False,
|
|
39
41
|
)
|
|
40
|
-
|
|
41
|
-
return result
|
|
@@ -4,14 +4,14 @@ from prompt_toolkit.styles import Style
|
|
|
4
4
|
|
|
5
5
|
from klaude_code.command.command_abc import Agent, CommandABC, CommandResult
|
|
6
6
|
from klaude_code.protocol import commands, events, model, op
|
|
7
|
-
from klaude_code.session.selector import build_session_select_options
|
|
7
|
+
from klaude_code.session.selector import build_session_select_options, format_user_messages_display
|
|
8
8
|
from klaude_code.trace import log
|
|
9
9
|
from klaude_code.ui.terminal.selector import SelectItem, select_one
|
|
10
10
|
|
|
11
11
|
SESSION_SELECT_STYLE = Style(
|
|
12
12
|
[
|
|
13
|
-
("msg", ""),
|
|
14
|
-
("meta", "
|
|
13
|
+
("msg", "fg:ansibrightblack"),
|
|
14
|
+
("meta", ""),
|
|
15
15
|
("pointer", "bold fg:ansigreen"),
|
|
16
16
|
("highlighted", "fg:ansigreen"),
|
|
17
17
|
("search_prefix", "fg:ansibrightblack"),
|
|
@@ -23,7 +23,7 @@ SESSION_SELECT_STYLE = Style(
|
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
|
|
26
|
-
def
|
|
26
|
+
def select_session_sync() -> str | None:
|
|
27
27
|
"""Interactive session selection (sync version for asyncio.to_thread)."""
|
|
28
28
|
options = build_session_select_options()
|
|
29
29
|
if not options:
|
|
@@ -31,16 +31,26 @@ def _select_session_sync() -> str | None:
|
|
|
31
31
|
return None
|
|
32
32
|
|
|
33
33
|
items: list[SelectItem[str]] = []
|
|
34
|
-
for opt in options:
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
for idx, opt in enumerate(options, 1):
|
|
35
|
+
display_msgs = format_user_messages_display(opt.user_messages)
|
|
36
|
+
title: list[tuple[str, str]] = []
|
|
37
|
+
title.append(("fg:ansibrightblack", f"{idx:2}. "))
|
|
38
|
+
title.append(
|
|
39
|
+
("class:meta", f"{opt.relative_time} · {opt.messages_count} · {opt.model_name} · {opt.session_id}\n")
|
|
40
|
+
)
|
|
41
|
+
for msg in display_msgs:
|
|
42
|
+
if msg == "⋮":
|
|
43
|
+
title.append(("class:msg", f" {msg}\n"))
|
|
44
|
+
else:
|
|
45
|
+
title.append(("class:msg", f" > {msg}\n"))
|
|
46
|
+
title.append(("", "\n"))
|
|
47
|
+
|
|
48
|
+
search_text = " ".join(opt.user_messages) + f" {opt.model_name} {opt.session_id}"
|
|
39
49
|
items.append(
|
|
40
50
|
SelectItem(
|
|
41
51
|
title=title,
|
|
42
52
|
value=opt.session_id,
|
|
43
|
-
search_text=
|
|
53
|
+
search_text=search_text,
|
|
44
54
|
)
|
|
45
55
|
)
|
|
46
56
|
|
|
@@ -83,7 +93,7 @@ class ResumeCommand(CommandABC):
|
|
|
83
93
|
)
|
|
84
94
|
return CommandResult(events=[event], persist_user_input=False, persist_events=False)
|
|
85
95
|
|
|
86
|
-
selected_session_id = await asyncio.to_thread(
|
|
96
|
+
selected_session_id = await asyncio.to_thread(select_session_sync)
|
|
87
97
|
if selected_session_id is None:
|
|
88
98
|
event = events.DeveloperMessageEvent(
|
|
89
99
|
session_id=agent.session.id,
|
|
@@ -81,7 +81,7 @@ class MetadataTracker:
|
|
|
81
81
|
) * 1000
|
|
82
82
|
|
|
83
83
|
if self._last_token_time is not None and self._metadata_item.usage.output_tokens > 0:
|
|
84
|
-
time_duration = self._last_token_time - self.
|
|
84
|
+
time_duration = self._last_token_time - self._request_start_time
|
|
85
85
|
if time_duration >= 0.15:
|
|
86
86
|
self._metadata_item.usage.throughput_tps = self._metadata_item.usage.output_tokens / time_duration
|
|
87
87
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from .selector import SessionSelectOption, build_session_select_options
|
|
1
|
+
from .selector import SessionSelectOption, build_session_select_options, format_user_messages_display
|
|
2
2
|
from .session import Session
|
|
3
3
|
|
|
4
|
-
__all__ = ["Session", "SessionSelectOption", "build_session_select_options"]
|
|
4
|
+
__all__ = ["Session", "SessionSelectOption", "build_session_select_options", "format_user_messages_display"]
|
|
@@ -33,12 +33,39 @@ class SessionSelectOption:
|
|
|
33
33
|
"""Option data for session selection UI."""
|
|
34
34
|
|
|
35
35
|
session_id: str
|
|
36
|
-
|
|
36
|
+
user_messages: list[str]
|
|
37
37
|
messages_count: str
|
|
38
38
|
relative_time: str
|
|
39
39
|
model_name: str
|
|
40
40
|
|
|
41
41
|
|
|
42
|
+
def _format_message(msg: str) -> str:
|
|
43
|
+
"""Format a user message for display (strip and collapse newlines)."""
|
|
44
|
+
return msg.strip().replace("\n", " ")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def format_user_messages_display(messages: list[str]) -> list[str]:
|
|
48
|
+
"""Format user messages for display in session selection.
|
|
49
|
+
|
|
50
|
+
Shows up to 6 messages. If more than 6, shows first 3 and last 3 with ellipsis.
|
|
51
|
+
Each message is on its own line.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
messages: List of user messages.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
List of formatted message lines for display.
|
|
58
|
+
"""
|
|
59
|
+
if len(messages) <= 6:
|
|
60
|
+
return messages
|
|
61
|
+
|
|
62
|
+
# More than 6: show first 3, ellipsis, last 3
|
|
63
|
+
result = messages[:3]
|
|
64
|
+
result.append("⋮")
|
|
65
|
+
result.extend(messages[-3:])
|
|
66
|
+
return result
|
|
67
|
+
|
|
68
|
+
|
|
42
69
|
def build_session_select_options() -> list[SessionSelectOption]:
|
|
43
70
|
"""Build session selection options data.
|
|
44
71
|
|
|
@@ -51,8 +78,9 @@ def build_session_select_options() -> list[SessionSelectOption]:
|
|
|
51
78
|
|
|
52
79
|
options: list[SessionSelectOption] = []
|
|
53
80
|
for s in sessions:
|
|
54
|
-
|
|
55
|
-
|
|
81
|
+
user_messages = [_format_message(m) for m in s.user_messages if m.strip()]
|
|
82
|
+
if not user_messages:
|
|
83
|
+
user_messages = ["N/A"]
|
|
56
84
|
|
|
57
85
|
msg_count = "N/A" if s.messages_count == -1 else f"{s.messages_count} messages"
|
|
58
86
|
model = s.model_name or "N/A"
|
|
@@ -60,7 +88,7 @@ def build_session_select_options() -> list[SessionSelectOption]:
|
|
|
60
88
|
options.append(
|
|
61
89
|
SessionSelectOption(
|
|
62
90
|
session_id=str(s.id),
|
|
63
|
-
|
|
91
|
+
user_messages=user_messages,
|
|
64
92
|
messages_count=msg_count,
|
|
65
93
|
relative_time=_relative_time(s.updated_at),
|
|
66
94
|
model_name=model,
|
|
@@ -197,11 +197,16 @@ 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:
|
|
200
|
+
def fork(self, *, new_id: str | None = None, until_index: int | None = None) -> Session:
|
|
201
201
|
"""Create a new session as a fork of the current session.
|
|
202
202
|
|
|
203
203
|
The forked session copies metadata and conversation history, but does not
|
|
204
204
|
modify the current session.
|
|
205
|
+
|
|
206
|
+
Args:
|
|
207
|
+
new_id: Optional ID for the forked session.
|
|
208
|
+
until_index: If provided, only copy conversation history up to (but not including) this index.
|
|
209
|
+
If None, copy all history.
|
|
205
210
|
"""
|
|
206
211
|
|
|
207
212
|
forked = Session.create(id=new_id, work_dir=self.work_dir)
|
|
@@ -213,7 +218,8 @@ class Session(BaseModel):
|
|
|
213
218
|
forked.file_tracker = {k: v.model_copy(deep=True) for k, v in self.file_tracker.items()}
|
|
214
219
|
forked.todos = [todo.model_copy(deep=True) for todo in self.todos]
|
|
215
220
|
|
|
216
|
-
|
|
221
|
+
history_to_copy = self.conversation_history[:until_index] if until_index is not None else self.conversation_history
|
|
222
|
+
items = [it.model_copy(deep=True) for it in history_to_copy]
|
|
217
223
|
if items:
|
|
218
224
|
forked.append_history(items)
|
|
219
225
|
|
|
@@ -338,7 +344,7 @@ class Session(BaseModel):
|
|
|
338
344
|
updated_at: float
|
|
339
345
|
work_dir: str
|
|
340
346
|
path: str
|
|
341
|
-
|
|
347
|
+
user_messages: list[str] = []
|
|
342
348
|
messages_count: int = -1
|
|
343
349
|
model_name: str | None = None
|
|
344
350
|
|
|
@@ -346,10 +352,11 @@ class Session(BaseModel):
|
|
|
346
352
|
def list_sessions(cls) -> list[SessionMetaBrief]:
|
|
347
353
|
store = get_default_store()
|
|
348
354
|
|
|
349
|
-
def
|
|
355
|
+
def _get_user_messages(session_id: str) -> list[str]:
|
|
350
356
|
events_path = store.paths.events_file(session_id)
|
|
351
357
|
if not events_path.exists():
|
|
352
|
-
return
|
|
358
|
+
return []
|
|
359
|
+
messages: list[str] = []
|
|
353
360
|
try:
|
|
354
361
|
for line in events_path.read_text(encoding="utf-8").splitlines():
|
|
355
362
|
obj_raw = json.loads(line)
|
|
@@ -360,15 +367,14 @@ class Session(BaseModel):
|
|
|
360
367
|
continue
|
|
361
368
|
data_raw = obj.get("data")
|
|
362
369
|
if not isinstance(data_raw, dict):
|
|
363
|
-
|
|
370
|
+
continue
|
|
364
371
|
data = cast(dict[str, Any], data_raw)
|
|
365
372
|
content = data.get("content")
|
|
366
373
|
if isinstance(content, str):
|
|
367
|
-
|
|
368
|
-
return None
|
|
374
|
+
messages.append(content)
|
|
369
375
|
except (OSError, json.JSONDecodeError):
|
|
370
|
-
|
|
371
|
-
return
|
|
376
|
+
pass
|
|
377
|
+
return messages
|
|
372
378
|
|
|
373
379
|
items: list[Session.SessionMetaBrief] = []
|
|
374
380
|
for meta_path in store.iter_meta_files():
|
|
@@ -382,7 +388,7 @@ class Session(BaseModel):
|
|
|
382
388
|
created = float(data.get("created_at", meta_path.stat().st_mtime))
|
|
383
389
|
updated = float(data.get("updated_at", meta_path.stat().st_mtime))
|
|
384
390
|
work_dir = str(data.get("work_dir", ""))
|
|
385
|
-
|
|
391
|
+
user_messages = _get_user_messages(sid)
|
|
386
392
|
messages_count = int(data.get("messages_count", -1))
|
|
387
393
|
model_name = data.get("model_name") if isinstance(data.get("model_name"), str) else None
|
|
388
394
|
|
|
@@ -393,7 +399,7 @@ class Session(BaseModel):
|
|
|
393
399
|
updated_at=updated,
|
|
394
400
|
work_dir=work_dir,
|
|
395
401
|
path=str(meta_path),
|
|
396
|
-
|
|
402
|
+
user_messages=user_messages,
|
|
397
403
|
messages_count=messages_count,
|
|
398
404
|
model_name=model_name,
|
|
399
405
|
)
|