Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Only write entries that are worth mentioning to users.
- Shell: Exclude empty current session from `/sessions` picker — completely empty sessions (no conversation history and no custom title) are no longer shown in the session list; sessions with a custom title are still displayed
- Shell: Fix slash command completion Enter key behavior — accepting a completion now submits in a single Enter press; auto-submit is limited to slash command completions only; file mention completions (`@`) accept without submitting so the user can continue editing; re-completion after accepting is suppressed to prevent stale completion state
- Shell: Add directory scope toggle to `/sessions` picker — press `Ctrl+A` to switch between showing sessions for the current working directory only or across all known directories; uses a new full-screen session picker UI with header scope indicator and footer hint bar
- Tool: On Windows, the Shell tool now prefers PowerShell 7+ (`pwsh`) when available, and falls back to inbox Windows PowerShell (`powershell.exe`)
- Shell: Add `/btw` side question command — ask a quick question during streaming without interrupting the main conversation; uses the same system prompt and tool definitions for prompt cache alignment; responses display in a scrollable modal panel with streaming support
- Shell: Redesign bottom dynamic area — split the monolithic `visualize.py` (1865 lines) into a modular package (`visualize/`) with dedicated modules for input routing, interactive prompts, approval/question panels, and btw modal; unify input semantics with `classify_input()` for consistent command routing
- Shell: Add queue and steer dual-channel input during streaming — Enter queues messages for delivery after the current turn; Ctrl+S injects messages immediately into the running turn's context; queued messages display in the prompt area with count indicator and can be recalled with ↑
Expand Down
1 change: 1 addition & 0 deletions docs/en/release-notes/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This page documents the changes in each Kimi Code CLI release.
- Shell: Exclude empty current session from `/sessions` picker — completely empty sessions (no conversation history and no custom title) are no longer shown in the session list; sessions with a custom title are still displayed
- Shell: Fix slash command completion Enter key behavior — accepting a completion now submits in a single Enter press; auto-submit is limited to slash command completions only; file mention completions (`@`) accept without submitting so the user can continue editing; re-completion after accepting is suppressed to prevent stale completion state
- Shell: Add directory scope toggle to `/sessions` picker — press `Ctrl+A` to switch between showing sessions for the current working directory only or across all known directories; uses a new full-screen session picker UI with header scope indicator and footer hint bar
- Tool: On Windows, the Shell tool now prefers PowerShell 7+ (`pwsh`) when available, and falls back to inbox Windows PowerShell (`powershell.exe`)
- Shell: Add `/btw` side question command — ask a quick question during streaming without interrupting the main conversation; uses the same system prompt and tool definitions for prompt cache alignment; responses display in a scrollable modal panel with streaming support
- Shell: Redesign bottom dynamic area — split the monolithic `visualize.py` (1865 lines) into a modular package (`visualize/`) with dedicated modules for input routing, interactive prompts, approval/question panels, and btw modal; unify input semantics with `classify_input()` for consistent command routing
- Shell: Add queue and steer dual-channel input during streaming — Enter queues messages for delivery after the current turn; Ctrl+S injects messages immediately into the running turn's context; queued messages display in the prompt area with count indicator and can be recalled with ↑
Expand Down
1 change: 1 addition & 0 deletions docs/zh/release-notes/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- Shell:从 `/sessions` 选择器中排除空的当前会话——完全为空的会话(既无对话记录也无自定义标题)不再显示在会话列表中;有自定义标题的会话仍然正常显示
- Shell:修复斜杠命令补全 Enter 键行为——接受补全后现在通过一次 Enter 即可提交命令;自动提交仅限于斜杠命令补全,文件引用(`@`)补全接受后不提交以便继续编辑;接受补全时抑制重新补全,防止过时的补全状态
- Shell:为 `/sessions` 会话选择器新增目录范围切换功能——按 `Ctrl+A` 可在"仅当前工作目录"和"所有已知目录"之间切换会话列表;采用全屏会话选择器 UI,顶部显示当前范围,底部显示快捷键提示
- Tool:Windows 上 Shell 工具在可用时优先使用 PowerShell 7+(`pwsh`),否则回退至系统自带的 Windows PowerShell(`powershell.exe`)
- Shell:新增 `/btw` 侧问命令——在 streaming 期间提出快速问题,不打断主对话;使用相同的系统提示词和工具定义以对齐 Prompt 缓存;响应在可滚动的模态面板中显示,支持流式输出
- Shell:重新设计底部动态区——将单体 `visualize.py`(1865 行)拆分为模块化包(`visualize/`),包含输入路由、交互式提示、审批/提问面板和 btw 模态面板等独立模块;通过 `classify_input()` 统一输入语义,实现一致的命令路由
- Shell:新增 streaming 期间的排队和 steer 双通道输入——Enter 将消息排队,在当前轮次结束后发送;Ctrl+S 将消息立即注入到正在运行的轮次上下文中;排队消息在提示区域显示计数指示器,可通过 ↑ 键召回编辑
Expand Down
51 changes: 42 additions & 9 deletions src/kimi_cli/utils/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,53 @@

import os
import platform
import shutil
from dataclasses import dataclass
from typing import Literal

from kaos.path import KaosPath


def _windows_shell_candidates() -> list[KaosPath]:
"""PowerShell executables to probe, in order.

Prefer PowerShell 7+ (`pwsh`) when present, then fall back to Windows PowerShell 5.1
(`powershell.exe`), matching common developer installs while remaining usable on systems
that only ship the inbox shell.
"""
candidates: list[KaosPath] = []
seen: set[str] = set()

def add(path: str) -> None:
normalized = os.path.normcase(os.path.normpath(path))
if normalized not in seen:
seen.add(normalized)
candidates.append(KaosPath(path))

pwsh = shutil.which("pwsh")
if pwsh:
add(pwsh)

program_files = os.environ.get("ProgramW6432") or os.environ.get(
"ProgramFiles", r"C:\Program Files"
)
add(os.path.join(program_files, "PowerShell", "7", "pwsh.exe"))

system_root = os.environ.get("SYSTEMROOT", r"C:\Windows")
add(
os.path.join(
system_root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe"
)
)

powershell = shutil.which("powershell")
if powershell:
add(powershell)

add("powershell.exe")
return candidates


@dataclass(slots=True, frozen=True, kw_only=True)
class Environment:
os_kind: Literal["Windows", "Linux", "macOS"] | str
Expand All @@ -33,16 +74,8 @@ async def detect() -> Environment:

if os_kind == "Windows":
shell_name = "Windows PowerShell"
system_root = os.environ.get("SYSTEMROOT", r"C:\Windows")
possible_paths = [
KaosPath(
os.path.join(
system_root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe"
)
),
]
fallback_path = KaosPath("powershell.exe")
for path in possible_paths:
for path in _windows_shell_candidates():
if await path.is_file():
shell_path = path
break
Expand Down
58 changes: 56 additions & 2 deletions tests/utils/test_utils_environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,15 @@ async def test_environment_detection_windows(monkeypatch):
monkeypatch.setattr(platform, "machine", lambda: "AMD64")
monkeypatch.setattr(platform, "version", lambda: "10.0.19044")
monkeypatch.setenv("SYSTEMROOT", r"C:\Windows")
monkeypatch.delenv("ProgramW6432", raising=False)
monkeypatch.setattr("kimi_cli.utils.environment.shutil.which", lambda *_a, **_k: None)

expected = os.path.join(
r"C:\Windows", "System32", "WindowsPowerShell", "v1.0", "powershell.exe"
)

async def _mock_is_file(self: KaosPath) -> bool:
return str(self) == expected
return os.path.normcase(str(self)) == os.path.normcase(expected)

monkeypatch.setattr(KaosPath, "is_file", _mock_is_file)

Expand All @@ -49,7 +51,58 @@ async def _mock_is_file(self: KaosPath) -> bool:
assert env.os_arch == "AMD64"
assert env.os_version == "10.0.19044"
assert env.shell_name == "Windows PowerShell"
assert str(env.shell_path) == expected
assert os.path.normcase(str(env.shell_path)) == os.path.normcase(expected)


@pytest.mark.skipif(platform.system() == "Windows", reason="Skipping test on Windows")
async def test_environment_detection_windows_prefers_pwsh_from_path(monkeypatch):
monkeypatch.setattr(platform, "system", lambda: "Windows")
monkeypatch.setattr(platform, "machine", lambda: "AMD64")
monkeypatch.setattr(platform, "version", lambda: "10.0.19044")
monkeypatch.setenv("SYSTEMROOT", r"C:\Windows")
expected = os.path.join(r"C:\Program Files", "PowerShell", "7", "pwsh.exe")

def _which(cmd: str, path: str | None = None) -> str | None:
if cmd == "pwsh":
return expected
return None

monkeypatch.setattr("kimi_cli.utils.environment.shutil.which", _which)

async def _mock_is_file(self: KaosPath) -> bool:
return os.path.normcase(str(self)) == os.path.normcase(expected)

monkeypatch.setattr(KaosPath, "is_file", _mock_is_file)

from kimi_cli.utils.environment import Environment

env = await Environment.detect()
assert env.shell_name == "Windows PowerShell"
assert os.path.normcase(str(env.shell_path)) == os.path.normcase(expected)


@pytest.mark.skipif(platform.system() == "Windows", reason="Skipping test on Windows")
async def test_environment_detection_windows_prefers_pwsh_from_program_files(monkeypatch):
monkeypatch.setattr(platform, "system", lambda: "Windows")
monkeypatch.setattr(platform, "machine", lambda: "AMD64")
monkeypatch.setattr(platform, "version", lambda: "10.0.19044")
monkeypatch.setenv("SYSTEMROOT", r"C:\Windows")
monkeypatch.delenv("ProgramW6432", raising=False)
monkeypatch.setenv("ProgramFiles", r"C:\Program Files")
monkeypatch.setattr("kimi_cli.utils.environment.shutil.which", lambda *_a, **_k: None)

expected = os.path.join(r"C:\Program Files", "PowerShell", "7", "pwsh.exe")

async def _mock_is_file(self: KaosPath) -> bool:
return os.path.normcase(str(self)) == os.path.normcase(expected)

monkeypatch.setattr(KaosPath, "is_file", _mock_is_file)

from kimi_cli.utils.environment import Environment

env = await Environment.detect()
assert env.shell_name == "Windows PowerShell"
assert os.path.normcase(str(env.shell_path)) == os.path.normcase(expected)


@pytest.mark.skipif(platform.system() == "Windows", reason="Skipping test on Windows")
Expand All @@ -58,6 +111,7 @@ async def test_environment_detection_windows_fallback(monkeypatch):
monkeypatch.setattr(platform, "machine", lambda: "AMD64")
monkeypatch.setattr(platform, "version", lambda: "10.0.19044")
monkeypatch.setenv("SYSTEMROOT", r"C:\Windows")
monkeypatch.setattr("kimi_cli.utils.environment.shutil.which", lambda *_a, **_k: None)

async def _mock_is_file(self: KaosPath) -> bool:
return False
Expand Down