Skip to content

fix: prevent idle cursor blink from forcing scroll to bottom in Linux terminals#2429

Open
GH-ytym wants to merge 2 commits into
MoonshotAI:mainfrom
GH-ytym:main
Open

fix: prevent idle cursor blink from forcing scroll to bottom in Linux terminals#2429
GH-ytym wants to merge 2 commits into
MoonshotAI:mainfrom
GH-ytym:main

Conversation

@GH-ytym

@GH-ytym GH-ytym commented Jun 4, 2026

Copy link
Copy Markdown

Related Issue:

Resolve #2422

Description:

Problem

When the conversation is complete and the user scrolls up to read history,
the terminal automatically jumps back to the bottom every ~1 second.
This makes it impossible to read long outputs without quitting the shell.

Root Cause

CustomPromptSession.__enter__() starts a background _refresh() task
that calls app.invalidate() on a timer. When idle, this happens every
1 second, causing prompt_toolkit to re-render and reposition the cursor
to the bottom input box. Some Linux terminal emulators (GNOME Terminal,
Konsole, Deepin Terminal, etc.) auto-scroll the viewport to keep the
cursor visible, which is why VS Code's xterm.js-based terminal is not
affected.

Fix

Skip app.invalidate() when the session is truly idle:

  • no agent running
  • no modal (approval/question) active

In that case, only expire stale toast entries (to avoid leaking memory),
and rely on natural refresh triggers:

  • user typing (on_text_changed)
  • agent output updates
  • modal open/close

Verification

Tested locally on Linux with GNOME Terminal. With this patch, scrolling up
to read history no longer snaps back to the bottom.

@devin-ai-integration devin-ai-integration Bot left a comment

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.

Devin Review found 1 potential issue.

View 2 additional findings in Devin Review.

Open in Devin Review

Comment on lines +1862 to +1865
is_idle = (
self._active_prompt_delegate() is None
and self._active_modal_delegate() is None
)

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.

🟡 Idle check doesn't account for _fast_refresh_provider, breaking MCP loading status updates

The new is_idle check only considers _active_prompt_delegate() and _active_modal_delegate(), but ignores _fast_refresh_provider. When MCP servers are loading (_mcp_status_loading() returns True at src/kimi_cli/ui/shell/__init__.py:449-453), there is no active prompt or modal delegate, so is_idle is True and app.invalidate() is never called. However, the sleep interval logic at lines 1883-1891 still correctly selects _RUNNING_REFRESH_INTERVAL when _fast_refresh_provider() is truthy, meaning the loop spins at a fast rate without actually triggering any UI redraws. The MCP loading spinner/status block (rendered via _render_agent_status_render_status_block at line 1828, specifically designed for the no-running-delegate case) will freeze until MCP loading completes or the user interacts with the prompt.

Suggested change
is_idle = (
self._active_prompt_delegate() is None
and self._active_modal_delegate() is None
)
is_idle = (
self._active_prompt_delegate() is None
and self._active_modal_delegate() is None
and not (
self._fast_refresh_provider is not None
and self._fast_refresh_provider()
)
)
Open in Devin Review

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants