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
34 changes: 2 additions & 32 deletions src/kimi_cli/ui/shell/__init__.py
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 NameError: _mcp_status_block, _mcp_status_loading, and _bg_task_count are used but no longer defined

The PR deleted the local function/class definitions for _mcp_status_block, _mcp_status_loading, _BgCountCache, and _bg_task_count (previously at old lines 200-230) but left the references to them at lines 204-206 when constructing the CustomPromptSession. This means entering the interactive shell (Shell.run() without a command) will immediately crash with a NameError. This breaks the primary interactive mode of the CLI.

(Refers to lines 204-206)

Prompt for agents
Restore the deleted local function/class definitions that were previously between lines 200-230 (old) of src/kimi_cli/ui/shell/__init__.py. Specifically, you need to re-add these definitions inside the run() method, before the `with CustomPromptSession(...)` block at line 202:

1. `_mcp_status_block(columns: int)` - closure that reads `self.soul.status.mcp_status` and calls `render_mcp_prompt`
2. `_mcp_status_loading() -> bool` - closure that checks if MCP is loading
3. `_BgCountCache` dataclass with `time` and `count` fields
4. `_bg_cache` instance of `_BgCountCache`
5. `_bg_task_count() -> int` - closure that returns background task count with caching

These were defined between the `_plan_mode_toggle` definition and the `CustomPromptSession` context manager, and should be placed back there (after the new `maybe_disable_kitty_keyboard_protocol()` call at line 200 and before the `with CustomPromptSession(...)` at line 202).
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from kimi_cli.utils.signals import install_sigint_handler
from kimi_cli.utils.slashcmd import SlashCommand, SlashCommandCall, parse_slash_command_call
from kimi_cli.utils.subprocess_env import get_clean_env
from kimi_cli.utils.term import ensure_new_line, ensure_tty_sane
from kimi_cli.utils.term import ensure_new_line, ensure_tty_sane, maybe_disable_kitty_keyboard_protocol
from kimi_cli.wire.types import ContentPart, StatusUpdate


Expand Down Expand Up @@ -197,37 +197,7 @@ async def _plan_mode_toggle() -> bool:
return await self.soul.toggle_plan_mode_from_manual()
return False

def _mcp_status_block(columns: int):
if not isinstance(self.soul, KimiSoul):
return None
snapshot = self.soul.status.mcp_status
if snapshot is None:
return None
return render_mcp_prompt(snapshot)

def _mcp_status_loading() -> bool:
if not isinstance(self.soul, KimiSoul):
return False
snapshot = self.soul.status.mcp_status
return bool(snapshot and snapshot.loading)

@dataclass
class _BgCountCache:
time: float = 0.0
count: int = 0

_bg_cache = _BgCountCache()

def _bg_task_count() -> int:
if not isinstance(self.soul, KimiSoul):
return 0
now = time.monotonic()
if now - _bg_cache.time < 1.0:
return _bg_cache.count
views = list_task_views(self.soul.runtime.background_tasks, active_only=True)
_bg_cache.count = sum(1 for v in views if v.spec.kind == "bash")
_bg_cache.time = now
return _bg_cache.count
maybe_disable_kitty_keyboard_protocol()

with CustomPromptSession(
status_provider=lambda: self.soul.status,
Expand Down
34 changes: 34 additions & 0 deletions src/kimi_cli/utils/term.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import sys
import time

from kimi_cli.utils.envvar import get_env_bool


def ensure_new_line() -> None:
"""Ensure the next prompt starts at column 0 regardless of prior command output."""
Expand Down Expand Up @@ -50,6 +52,33 @@ def ensure_tty_sane() -> None:
termios.tcsetattr(fd, termios.TCSADRAIN, attrs)


def maybe_disable_kitty_keyboard_protocol() -> None:
"""Disable kitty keyboard protocol in terminals that send CSI-u sequences.

This is primarily a workaround for VS Code's integrated terminal, which can
emit CSI-u key sequences that prompt_toolkit doesn't parse.
"""
if sys.platform == "win32":
return
if not sys.stdout.isatty() or not sys.stdin.isatty():
return
if not _should_disable_kitty_keyboard_protocol():
return

_write_escape("\x1b[<u")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid popping the terminal's kitty key mode on startup

CSI < u is the kitty keyboard protocol pop/restore sequence, so sending it unconditionally here changes terminal state without a matching push from this process. In terminals where the parent shell/editor already enabled kitty key handling—or whenever users set KIMI_CLI_DISABLE_KITTY_KEYS=1 outside VS Code—starting kimi will consume the parent's stack entry and leave the outer program in the wrong keyboard mode after exit until it reconfigures the terminal.

Useful? React with 👍 / 👎.



def _should_disable_kitty_keyboard_protocol() -> bool:
env_value = os.getenv("KIMI_CLI_DISABLE_KITTY_KEYS")
if env_value is not None:
return get_env_bool("KIMI_CLI_DISABLE_KITTY_KEYS", default=False)
return _is_vscode_terminal()


def _is_vscode_terminal() -> bool:
return os.getenv("TERM_PROGRAM") == "vscode" or "VSCODE_IPC_HOOK_CLI" in os.environ


def _cursor_position_unix() -> tuple[int, int] | None:
"""Get cursor position (row, column) on Unix. Both are 1-indexed."""
assert sys.platform != "win32"
Expand Down Expand Up @@ -149,6 +178,11 @@ def _write_newline() -> None:
sys.stdout.flush()


def _write_escape(value: str) -> None:
sys.stdout.write(value)
sys.stdout.flush()


def get_cursor_row() -> int | None:
"""Get the current cursor row (1-indexed)."""
if not sys.stdout.isatty() or not sys.stdin.isatty():
Expand Down