Skip to content

fix(shell): persist pasted text placeholders#2388

Open
Pluviobyte wants to merge 1 commit into
MoonshotAI:mainfrom
Pluviobyte:fix/pasted-text-placeholder-persistence
Open

fix(shell): persist pasted text placeholders#2388
Pluviobyte wants to merge 1 commit into
MoonshotAI:mainfrom
Pluviobyte:fix/pasted-text-placeholder-persistence

Conversation

@Pluviobyte
Copy link
Copy Markdown

@Pluviobyte Pluviobyte commented May 28, 2026

Related Issue

Resolve #1946

Description

Long pasted text is folded into a placeholder such as [Pasted text #1] before it appears in the prompt. That works only while the original PastedTextPlaceholderHandler still has the in-memory entry. After prompt/session history recall, a new PromptPlaceholderManager cannot resolve the old entry, so resolve_command() falls back to sending the literal placeholder text to the model. That matches #1946: the model receives [Pasted text ...] instead of the pasted content.

Fix

  • Persist new pasted-text placeholders into AttachmentCache, similar to image placeholders.
  • New tokens use a stable cache-backed format: [Pasted text:<id>.txt +N lines].
  • A new PromptPlaceholderManager using the same cache root can resolve the token back to the original pasted text.
  • Existing legacy tokens ([Pasted text #n]) still work within the same manager.
  • Missing legacy or persisted pasted-text tokens no longer get sent literally; they resolve to an explicit missing-content message instead.

Notes on PR size

This is a little over 100 changed lines because it keeps legacy placeholder compatibility while adding cache-backed pasted text. The related bug issue already exists and this stays scoped to ui/shell/placeholders.py plus focused prompt tests.

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked the related issue.
  • I have added tests that prove my fix is effective.
  • I have updated CHANGELOG.md (manual Unreleased entry, following the existing one-line-per-bullet style; make gen-changelog not run because this is a focused bug fix).
  • make gen-docs not run — N/A: no user-facing CLI/config documentation change.

Test plan

  • UV_PYTHON=3.12 uv run ruff check src/kimi_cli/ui/shell/placeholders.py tests/ui_and_conv/test_prompt_placeholders.py tests/ui_and_conv/test_prompt_clipboard.py tests/ui_and_conv/test_prompt_history.py → clean
  • UV_PYTHON=3.12 uv run ruff format --check src/kimi_cli/ui/shell/placeholders.py tests/ui_and_conv/test_prompt_placeholders.py tests/ui_and_conv/test_prompt_clipboard.py tests/ui_and_conv/test_prompt_history.py → clean
  • UV_PYTHON=3.12 uv run pytest tests/ui_and_conv/test_prompt_placeholders.py tests/ui_and_conv/test_prompt_clipboard.py tests/ui_and_conv/test_prompt_history.py tests/ui_and_conv/test_prompt_external_editor.py -q43 passed

Proof of fix

  • test_placeholder_manager_resolves_pasted_text_with_new_manager creates a long paste token with one manager, then resolves it with a fresh manager and the same cache root; the original text is sent, not the placeholder.
  • test_placeholder_manager_uses_error_text_for_missing_persisted_text ensures a missing cache entry does not leak the literal placeholder to the model.
  • test_placeholder_manager_uses_error_text_for_unknown_legacy_text_token covers old [Pasted text #n] tokens when their in-memory entry is unavailable.

Made with Cursor


Open in Devin Review

Store long pasted text through the prompt attachment cache so placeholder tokens can be resolved after history recall creates a fresh PromptPlaceholderManager.

Keep legacy [Pasted text #n] tokens readable while preventing unresolved pasted text placeholders from being sent to the model verbatim; missing payloads now resolve to an explicit error message.

Fixes MoonshotAI#1946

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: aa47175f46

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +352 to +353
if match.match.re is _PERSISTED_PASTED_TEXT_PLACEHOLDER_RE:
return match.raw
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 Expand persisted paste tokens outside history writes

When a long paste is entered into a running approval/question modal, those delegates pass serialize_for_history as their text_expander (src/kimi_cli/ui/shell/visualize/_approval_panel.py and _question_panel.py). With this branch returning the raw persisted token here, reject feedback or “Other” answers containing pasted text now submit [Pasted text:…] instead of the original content, whereas legacy in-memory tokens were expanded. Keep history preservation separate from the expansion path used by these modal callers.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

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

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 4 additional findings in Devin Review.

Open in Devin Review

Comment on lines +352 to +353
if match.match.re is _PERSISTED_PASTED_TEXT_PLACEHOLDER_RE:
return match.raw
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.

🔴 serialize_for_history used as text_expander no longer expands persisted placeholders, sending raw tokens to the model

The PR changed serialize_for_history (line 352-353) to return match.raw for persisted placeholders, which is correct for the history serialization use case. However, serialize_for_history is also used as the text_expander callback in the approval panel (src/kimi_cli/ui/shell/__init__.py:1399) and question panel (src/kimi_cli/ui/shell/visualize/_interactive.py:514). These callers expect placeholders to be expanded to their full text content before sending to the model. Since persisted tokens are now the default (happy path), when a user pastes long text in a question or approval feedback field, the model receives the literal placeholder string [Pasted text:abc123.txt +15 lines] instead of the actual pasted content. The correct method to use as text_expander would be expand_for_editor, which always expands both legacy and persisted text placeholders via expand_text.

Prompt for agents
The issue is that serialize_for_history now preserves persisted placeholder tokens (returning match.raw), but it is also used as a text_expander callback in two places that need full expansion:

1. src/kimi_cli/ui/shell/__init__.py:1399 - ApprovalPromptDelegate text_expander
2. src/kimi_cli/ui/shell/visualize/_interactive.py:514 - QuestionPromptDelegate text_expander

Both of these call sites should use expand_for_editor instead of serialize_for_history as the text_expander, since expand_for_editor always expands text placeholders (both legacy and persisted) to their full content while keeping image tokens as-is. Change both sites from:
  text_expander=self._prompt_session._get_placeholder_manager().serialize_for_history
to:
  text_expander=self._prompt_session._get_placeholder_manager().expand_for_editor
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

Development

Successfully merging this pull request may close these issues.

[Bug]: [Pasted X chars] is sent as-is instead of actual text content

1 participant