From 045811f5beb5419644a9c1c73b5ccca41171e83c Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Mon, 27 Jul 2026 22:55:18 -0500 Subject: [PATCH] fix(context): keep @ref tokens inline instead of stripping them from the prompt Expanding an @file:/@folder: reference deleted the token from the message it was typed in, leaving a hole in the sentence and no anchor for clients to chip. The attached context block still names each ref, so nothing is lost by leaving the token where the user put it. --- agent/context_references.py | 21 ++++++--------------- tests/agent/test_context_references.py | 5 +++-- 2 files changed, 9 insertions(+), 17 deletions(-) diff --git a/agent/context_references.py b/agent/context_references.py index 8981aa472f50..ab370a5a5924 100644 --- a/agent/context_references.py +++ b/agent/context_references.py @@ -213,8 +213,12 @@ async def preprocess_context_references_async( f"@ context injection warning: {injected_tokens} tokens exceeds the 25% soft limit ({soft_limit})." ) - stripped = _remove_reference_tokens(message, refs) - final = stripped + # Leave the `@file:`/`@folder:` tokens where the user typed them. The token + # IS the reference, not scaffolding around it: clients render each one as an + # inline chip, so stripping them left a sentence with a hole in it ("review + # and ship") and made the desktop re-derive the refs from the attached block + # to show them as a detached list above the prose. + final = message if warnings: final = f"{final}\n\n--- Context Warnings ---\n" + "\n".join(f"- {warning}" for warning in warnings) if blocks: @@ -473,19 +477,6 @@ def _parse_file_reference_value(value: str) -> tuple[str, int | None, int | None return _strip_reference_wrappers(value), None, None -def _remove_reference_tokens(message: str, refs: list[ContextReference]) -> str: - pieces: list[str] = [] - cursor = 0 - for ref in refs: - pieces.append(message[cursor:ref.start]) - cursor = ref.end - pieces.append(message[cursor:]) - text = "".join(pieces) - text = re.sub(r"\s{2,}", " ", text) - text = re.sub(r"\s+([,.;:!?])", r"\1", text) - return text.strip() - - def _is_binary_file(path: Path) -> bool: mime, _ = mimetypes.guess_type(path.name) if mime and not mime.startswith("text/") and not any( diff --git a/tests/agent/test_context_references.py b/tests/agent/test_context_references.py index 51f66fb937af..43242706a1b9 100644 --- a/tests/agent/test_context_references.py +++ b/tests/agent/test_context_references.py @@ -112,8 +112,9 @@ def test_expand_file_range_and_folder_listing(sample_repo: Path): ) assert result.expanded - assert "Review and" in result.message - assert "Review @file:src/main.py:1-2" not in result.message + # The typed `@` tokens stay in the prose — clients render each one as an + # inline chip where the user put it, rather than a detached list. + assert result.message.startswith("Review @file:src/main.py:1-2 and @folder:src/") assert "--- Attached Context ---" in result.message assert "def alpha():" in result.message assert "return 'changed'" in result.message