Skip to content

fix(vscode): support filenames with spaces and unicode in @mentions#11977

Merged
marius-kilocode merged 9 commits into
Kilo-Org:mainfrom
sylwester-liljegren:fix/file-mention-spaces-unicode
Jul 15, 2026
Merged

fix(vscode): support filenames with spaces and unicode in @mentions#11977
marius-kilocode merged 9 commits into
Kilo-Org:mainfrom
sylwester-liljegren:fix/file-mention-spaces-unicode

Conversation

@sylwester-liljegren

@sylwester-liljegren sylwester-liljegren commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Issue

Stumbled previously upon some cases while using @mentions, where the handling of whitespaces/special letters weren't properly handled, hence the creation of this PR.

Context

File @mentions in the chat input did not work correctly when filenames contained spaces (e.g. org data.xlsx) or non-ASCII characters (e.g. файл.txt, 文件.txt). Two separate bugs were responsible:

  1. Highlight disappeared in the sent message. The full @mention-test/org data.xlsx appeared highlighted while typing, but after sending only @mention-test/org stayed highlighted and data.xlsx rendered as plain text.

  2. Server returned "File not found" for paths with spaces. When sending a message with a space-containing mention, the extension generated an invalid file:// URL with a literal space instead of %20. Bun's fileURLToPath parsed this malformed URL and truncated the resolved path at the first space (e.g. mention-test\my instead of mention-test\my quarterly report.txt), causing a ReadTool failure.

Review also surfaced a related, pre-existing gap: mentioning the same file twice in one message only kept the first occurrence highlighted. That's fixed here too, alongside two edge cases the fix itself introduced (see below).

Manual testing also turned up a third, related bug: reverting to a message with a space-containing mention and resending it reproduced the same "File not found" truncation, even with bugs 1 and 2 already fixed — through a completely different code path (see Bug 3 below).

Implementation

Bug 1 — rendering truncation for mentions with spaces or non-ASCII characters

buildFileAttachments previously returned attachments without source.text position data. buildHighlightedTextSegments in message-highlight.ts checks for refs with position info; finding none, it fell back to MENTION_RE detection. That regex uses [\w./-] character classes which stop at whitespace, so @org data.xlsx matched nothing and rendered as plain text.

Fix: buildFileAttachments now computes the exact character positions of each mention via text.indexOf(token) and includes them as source.text on the attachment. The server stores this, and the renderer's resolve() path uses text.indexOf(source.value), which handles spaces and all Unicode correctly. MENTION_RE itself is left in its original, conservative form (no space handling) — it's only a fallback for messages sent before this fix that lack source.text, and broadening it to span spaces was tried and reverted during review because it made the fallback swallow ordinary prose following any unrelated @mention (e.g. @agent check the report for v1.2 details would highlight the entire span through v1.2).

Bug 2 — URL encoding

VS Code's webview (Chromium) does not percent-encode spaces when assigning to url.pathname on a file:// URL, despite this being required by the URL spec. The resulting literal space in url.href caused Bun's fileURLToPath on the server side to silently truncate the path at the first space character.

Fix: spaces are explicitly pre-encoded as %20 before assignment to url.pathname. Other non-ASCII characters (Cyrillic, Chinese, etc.) are correctly encoded by the URL class, so only spaces need this explicit treatment.

Repeated-mention highlighting

mentionedPaths is a Set<string>, so buildFileAttachments produces exactly one ref per unique path even when it's mentioned more than once in the same message — previously the second occurrence just fell through to MENTION_RE's regex-based detect(), which found every regex match independently; once source.text made resolve() the active path instead, only the first occurrence located.

Fix: resolve() now also searches the remainder of the text for repeats of each ref's exact mention value. Doing this correctly needs a boundary check, not a plain substring search — a naive one would let a shorter mention match as a literal prefix of a longer, distinct one that starts the same way (@a.ts inside @a.tsx, or @report.csv inside @report.csv.bak), truncating the longer mention's own highlight. The boundary check accepts a repeat only when the adjacent character couldn't extend it into a longer path — ordinary punctuation (comma, sentence-ending period, closing paren) is fine, but a word character, slash, hyphen, or a dot followed by another word character is treated as a continuation and rejected. The character class used is Unicode-aware (\p{L}/\p{N}, not \w, which is ASCII-only in JavaScript regex), so the same collision protection applies to Cyrillic and CJK mentions, not just ASCII ones.

Bug 3 — mention truncation after revert-and-resend

Reverting to a message that had a space-containing @mention, then resending the same text, reproduced the "File not found" truncation error again, through a mechanism unrelated to bugs 1 and 2.

Revert restores the original message's text into the input via a setChatBoxMessage window message. PromptInput handled this by calling seedFromText(text), which re-derives candidate mention paths from the raw text using a regex — the same conservative, space-free pattern as MENTION_RE. For @mention-test/my quarterly report.txt, its slash-based alternative matches only mention-test/my, stopping at the first space (no extension is required for that alternative, so it doesn't even need a dot to match). That truncated candidate is then added to knownPaths and, critically, passes syncMentionedPaths' own boundary check too — a real space genuinely follows my in the full filename, so the truncated candidate looks like a complete, validly-bounded mention from the regex's perspective. It ends up in mentionedPaths alongside the real, full path, and buildFileAttachments builds a second, broken attachment for it — producing the same truncated File not found: ...\mention-test\my error alongside a correct read of the real file.

Fix: revertSession() now also extracts the reverted message's file parts' exact source.path values (recorded by the Bug 1 fix) and sends them alongside the restored text. PromptInput's setChatBoxMessage handler seeds these exact paths directly via a new seedFromParts() function instead of falling back to seedFromText's regex re-derivation, when they're available. seedFromParts skips the flawed candidate-discovery step entirely and just prunes the known-good paths against the current text, so it can't reintroduce the truncation. seedFromText itself is left unchanged and still used as a fallback for callers without exact path data (native undo restoring a draft, or messages sent before source.path existed).

Screenshots / Video

before after
image image
before (following a revert) after (following a revert)
image image

The repeated-mention, boundary-check, and revert-resend fixes are covered by unit tests rather than a distinct visual state beyond what's shown above — they correct edge cases (duplicates, prefix collisions, punctuation, Unicode, revert/resend) in the same highlighting and file-resolution behavior.

How to Test

Manual/local verification

Create test files and mention them via the autocomplete dropdown in the chat:

New-Item -ItemType Directory -Force -Path 'mention-test', 'mention-test\my data'
'secret: SPACE_FILE_OK'   | Set-Content 'mention-test\org data.xlsx'
'secret: MULTI_SPACE_OK'  | Set-Content 'mention-test\my quarterly report.txt'
'secret: DIR_SPACE_OK'    | Set-Content 'mention-test\my data\report.txt'
'secret: CYRILLIC_OK'     | Set-Content 'mention-test\файл.txt'
'secret: CHINESE_OK'      | Set-Content 'mention-test\文件.txt'

For each file, type @ in the chat input, select the file from the dropdown, and send:

What is the secret value in @mention-test/org data.xlsx ?

Expected: the full path is highlighted as a single mention chip in the sent message, the agent reply references the correct secret value, and no "File not found" error appears.

Reviewer test steps

  1. Check out the branch and rebuild the extension (bun run extension).
  2. Open a workspace that contains files with spaces in their names.
  3. Type @ in the chat input and select a file with spaces in its name from the autocomplete dropdown.
  4. Send the message and confirm:
    • The full @path/with spaces.ext is highlighted as one chip in the sent message bubble.
    • No File not found error box appears.
    • The agent can reference the file contents correctly.
  5. Repeat with a Cyrillic or CJK filename to confirm non-ASCII paths are unaffected.
  6. Mention the same file twice in one message (e.g. "compare @file.ts with the older @file.ts, please") and confirm both occurrences stay highlighted, including one directly followed by punctuation.
  7. Send a message with a space-containing mention, revert the conversation back to before that message, then resend the same text. Confirm no File not found error appears and the highlight/read behavior matches the original send.

Blocked checks and substitute verification

  • The bun turbo typecheck pre-push hook exits with code 66 on @opencode-ai/ui in the local turbo execution context, but bun run typecheck in that package passes when run directly. This is a pre-existing local environment issue unrelated to these changes.
  • Targeted checks run and passing:
    • bun run typecheck from packages/kilo-vscode/
    • bun run lint from packages/kilo-vscode/
    • bun test tests/unit/file-mention-utils.test.ts tests/unit/use-file-mention.test.ts from packages/kilo-vscode/ — 78 tests pass
    • bun test src/components/message-highlight.test.ts from packages/kilo-ui/ — 20 tests pass

Checklist

  • Issue linked above, or exception explained
  • Tests/verification described
  • Screenshots/video included for visual changes, or marked N/A
  • Changeset considered for user-facing changes
  • I personally reviewed the diff and can explain the changes, including any AI-assisted work.

* `my folder/report q1.xlsx`. The second alternative handles slash-based paths.
* This regex is the fallback used only when no source position data is available.
*/
const MENTION_RE = /@((?:[\w./-]+ )*[\w./-]+\.[\w]+|[\w.-]+\/[\w./-]+)/g

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.

WARNING: Broadened fallback regex can over-match ordinary prose, not just file mentions

The new first alternative (?:[\w./-]+ )*[\w./-]+\.[\w]+ is used by detect() whenever a message has zero refs with position data — i.e. for every plain-text message without a file/agent attachment, not just legacy mentions. Because the repeated group has no upper bound on how many space-separated words it can swallow before the final name.ext token, any @ followed eventually by a decimal number or dotted token later in the same sentence gets pulled into the match.

Example: "@support team for v2.0 details" matches the whole span @support team for v2.0 as a highlighted "file" mention, even though only v2.0 looks vaguely file-like and nothing here is an actual attachment. This will render as an incorrect highlighted/clickable chip around unrelated prose in normal chat messages.

Consider bounding the number of space-separated segments (e.g. {0,2} instead of *), or only applying the space-tolerant alternative when there is some other signal (e.g. an actual space-containing path in mentionedPaths) rather than as a blanket regex over all plain text.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

for (const path of mentionedPaths) {
if (text.includes(`@${path}`)) {
const token = `@${path}`
const idx = text.indexOf(token)

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.

WARNING: Repeated mentions of the same path in one message only highlight the first occurrence

mentionedPaths is a Set<string>, so each distinct path produces at most one FileAttachment, and idx = text.indexOf(token) always resolves to the first occurrence of @path in the text. If the same file is mentioned twice in one message (e.g. "see @foo.ts and again @foo.ts"), only the first @foo.ts gets a source.text position; the renderer's resolve() in message-highlight.ts has no second ref to relocate for the duplicate, so the second occurrence stays as plain text instead of being highlighted.

Before this change, highlighting for messages without space-containing mentions relied entirely on detect()'s regex fallback, which highlights every occurrence via matchAll. So this is a real (if narrow) behavior regression for the duplicate-mention case, traded for fixing the primary space/Unicode bug. Worth a look if duplicate mentions in a single message are a supported/expected scenario.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Incremental review of the latest commit (6bf4ce0be3), which fixes mention truncation on revert-and-resend. revertSession() now extracts the reverted message's exact source.path values and sends them alongside the restored text via setChatBoxMessage; PromptInput seeds them directly through a new seedFromParts() in useFileMention.ts, bypassing seedFromText()'s regex-based re-derivation that truncates paths at the first space. The change correctly falls back to seedFromText when no exact paths are available (e.g. messages sent before source.path existed), and syncMentionedPaths performs an exact bounded-string match against the full path so space-containing mentions resolve correctly. New unit tests exercise the real useFileMention hook (no mocks of logic under test) and cover the seeding, pruning, and multi-path cases.

Files Reviewed (5 files)
  • packages/kilo-vscode/webview-ui/src/context/session.tsx
  • packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx
  • packages/kilo-vscode/webview-ui/src/hooks/useFileMention.ts
  • packages/kilo-vscode/webview-ui/src/types/messages/extension-messages.ts
  • packages/kilo-vscode/tests/unit/use-file-mention.test.ts
Previous Review Summaries (6 snapshots, latest commit b793d57)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit b793d57)

Status: No Issues Found | Recommendation: Merge

The previously flagged ASCII-only \w boundary check is fixed in b793d571b8: PATH_CONTINUATION and continuesPath in message-highlight.ts now use Unicode property escapes (\p{L}, \p{N}) with the u flag instead of \w, so Cyrillic and CJK mentions are correctly recognized as path-continuation characters, preventing the prefix-collision regression for non-ASCII filenames. Regression tests for Cyrillic (файл/файлы) and CJK (文件/文件夹) collisions were added and confirmed against the prior implementation.

Files Reviewed (2 files)
  • packages/kilo-ui/src/components/message-highlight.ts
  • packages/kilo-ui/src/components/message-highlight.test.ts

Previous review (commit 28175b2)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/kilo-ui/src/components/message-highlight.ts 65 PATH_CONTINUATION/continuesPath use ASCII-only \w, so a Unicode filename (e.g. файл.txt) can falsely match as a prefix of a longer, distinct Unicode filename (e.g. файлы.txt) — the same @a.ts/@a.tsx collision this code was written to prevent, reintroduced for the non-ASCII paths this PR targets
Files Reviewed (2 files)
  • packages/kilo-ui/src/components/message-highlight.ts - 1 issue
  • packages/kilo-ui/src/components/message-highlight.test.ts - 0 issues

The previously reported whitespace-only boundary check issue (repeats adjacent to punctuation losing their highlight) is resolved in 28175b2e4b by replacing the whitespace check with the new PATH_CONTINUATION/continuesPath logic. That fix works correctly for ASCII paths but relies on \w, which is ASCII-only in JavaScript regex, so it does not extend the same protection to the Unicode filenames (Cyrillic, CJK) this PR is meant to support.

Fix these issues in Kilo Cloud

Previous review (commit 55c5879)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/kilo-ui/src/components/message-highlight.ts 78 New repeats() boundary check requires literal whitespace before/after a match, so a repeat mention adjacent to punctuation (sentence-ending period, comma, closing paren) is silently dropped instead of highlighted
Files Reviewed (2 files)
  • packages/kilo-ui/src/components/message-highlight.ts - 1 issue
  • packages/kilo-ui/src/components/message-highlight.test.ts - 0 issues

The previously reported prefix-collision issue (@a.ts matching inside @a.tsx) is resolved in 55c58791f0 via the new boundary-checked repeats() helper. That fix is stricter than needed, though: it requires whitespace on both sides of a repeat match rather than "not a path-continuation character" (the boundary MENTION_RE itself uses), so repeats next to punctuation lose their highlight.

Fix these issues in Kilo Cloud

Previous review (commit 44bea91)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/kilo-ui/src/components/message-highlight.ts 78 New repeat-mention search does a plain substring indexOf; when one mention's text is a literal prefix of a later, distinct mention's text (e.g. @a.ts vs @a.tsx), it can consume part of the later mention and drop its highlight
Files Reviewed (2 files)
  • packages/kilo-ui/src/components/message-highlight.ts - 1 issue
  • packages/kilo-ui/src/components/message-highlight.test.ts - 0 issues

Both previously reported issues (over-matching fallback regex; duplicate mentions losing highlighting) are resolved in 44bea9196e: the fallback regex was reverted to its conservative, space-free form, and resolve() now re-scans for repeated occurrences of each ref's exact mention text.

Fix these issues in Kilo Cloud

Previous review (commit 72c4d10)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/kilo-ui/src/components/message-highlight.ts 31 Broadened fallback regex ((?:[\w./-]+ )*[\w./-]+\.[\w]+) can over-match ordinary prose in any attachment-free message, not just legacy space-containing mentions
packages/kilo-vscode/webview-ui/src/hooks/file-mention-utils.ts 184 Repeated mentions of the same path in one message only get a source.text position for the first occurrence, so duplicate mentions lose highlighting that the old regex-only fallback provided
Files Reviewed (5 files)
  • .changeset/file-mention-spaces-unicode.md - 0 issues
  • packages/kilo-ui/src/components/message-highlight.ts - 1 issue
  • packages/kilo-ui/src/components/message-highlight.test.ts - 0 issues
  • packages/kilo-vscode/webview-ui/src/hooks/file-mention-utils.ts - 1 issue
  • packages/kilo-vscode/tests/unit/file-mention-utils.test.ts - 0 issues

Fix these issues in Kilo Cloud

Previous review (commit 5d8c84b)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 2
SUGGESTION 0
Issue Details (click to expand)

WARNING

File Line Issue
packages/kilo-ui/src/components/message-highlight.ts 31 Broadened fallback regex ((?:[\w./-]+ )*[\w./-]+\.[\w]+) can over-match ordinary prose in any attachment-free message, not just legacy space-containing mentions
packages/kilo-vscode/webview-ui/src/hooks/file-mention-utils.ts 184 Repeated mentions of the same path in one message only get a source.text position for the first occurrence, so duplicate mentions lose highlighting that the old regex-only fallback provided
Files Reviewed (4 files)
  • packages/kilo-ui/src/components/message-highlight.ts - 1 issue
  • packages/kilo-ui/src/components/message-highlight.test.ts - 0 issues
  • packages/kilo-vscode/webview-ui/src/hooks/file-mention-utils.ts - 1 issue
  • packages/kilo-vscode/tests/unit/file-mention-utils.test.ts - 0 issues

Fix these issues in Kilo Cloud


Reviewed by claude-sonnet-5-20260630 · Input: 48 · Output: 11.4K · Cached: 1.5M

Review guidance: REVIEW.md from base branch main

@sylwester-liljegren

Copy link
Copy Markdown
Contributor Author

Addressed both review issues in 44bea91:

1. Over-matching regex — Reverted MENTION_RE in message-highlight.ts back to its original conservative form (no space handling). The primary fix for space/unicode filenames doesn't depend on it: buildFileAttachments computes exact source.text positions, and the renderer's resolve() path locates the exact known mention text via indexOf rather than pattern-matching. The regex is now only a fallback for messages that pre-date this fix and lack source.text, and it no longer risks swallowing ordinary prose following an unrelated @mention.

2. Duplicate mentions losing highlighting — Since mentionedPaths is a Set<string>, buildFileAttachments will always produce one ref per unique path regardless of how many times it's mentioned in a message. Fixed this at the renderer level instead: resolve() now also searches the remaining text for exact repeats of each ref's resolved value, so every occurrence of a path stays highlighted (not just the first), whether or not it contains spaces or non-ASCII characters.

Added tests for both: over-matching prevention (v1.2-in-prose case) and repeated mentions (plain and space-containing).

// text for exact repeats of this ref's mention text so every occurrence
// stays highlighted, not just the first.
let repeat = text.indexOf(next.source.value, index)
while (repeat !== -1) {

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.

WARNING: Repeat-search can bleed into a different, later mention when one mention's text is a prefix of another's

text.indexOf(next.source.value, index) does a plain substring search with no boundary check. If a later, distinct mention's text has next.source.value as a literal prefix (e.g. @a.ts mentioned once, and @a.tsx mentioned later in the same message — a common collision in TS/TSX codebases), this loop matches @a.ts inside @a.tsx, pushing a bogus {start, end} range that only covers @a.ts and advances index past part of the real @a.tsx occurrence.

When @a.tsx appears before any duplicate of @a.ts, the subsequent locate() call for the a.tsx ref then fails to find its own text after that corrupted index, so the real @a.tsx mention is dropped entirely and rendered as a wrong @a.ts chip followed by a stray plain x.

Consider requiring a non-word boundary after the match (e.g. check text[end] is not \w before treating it as a repeat), or track already-claimed ranges instead of relying purely on index position.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@sylwester-liljegren

Copy link
Copy Markdown
Contributor Author

Addressed in 55c5879.

Prefix-collision bug in repeat-mention search — Confirmed the issue: the plain indexOf search let a shorter mention's value (@a.ts) match as a literal prefix inside a longer, distinct mention starting the same way (@a.tsx), truncating the longer mention's highlight and dropping its own ref from being located entirely.

Fixed by requiring each repeat candidate to be flanked by whitespace or a string edge on both sides before accepting it — the same token-boundary convention already used in syncMentionedPaths's (?:^|\s)@path(?:\s|$) pattern. Added a regression test for the exact @a.ts / @a.tsx collision case, plus re-verified by hand that the existing stale-position-relocation and repeated-mention tests still resolve to the same expected output with the new boundary check in place.

if (found === -1) break

const end = found + value.length
const before = found === 0 || /\s/.test(text[found - 1] ?? "")

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.

WARNING: Boundary check requires literal whitespace, so repeats followed/preceded by punctuation lose their highlight

before/after only accept whitespace or a string edge as the token boundary. This fixes the prefix-collision case, but it's stricter than the token definition detect()/MENTION_RE uses elsewhere (word chars, ., /, -), so it silently drops legitimate repeats that are adjacent to punctuation rather than whitespace — e.g. a second mention at the end of a sentence ("... again @file.ts.") or inside parentheses ("(@file.ts)" following an earlier mention) won't satisfy the whitespace check and will be rendered as a plain, unhighlighted repeat even though it's the exact same mention text.

Consider testing for "not a continuation character" (e.g. !/[\w./-]/.test(...)) instead of "is whitespace", matching the boundary semantics MENTION_RE already uses — that still blocks @a.ts from matching inside @a.tsx (x is a continuation char) while allowing repeats followed by ., ,, ), etc.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@sylwester-liljegren

Copy link
Copy Markdown
Contributor Author

Addressed in 28175b2.

Over-strict punctuation rejection — Confirmed: the whitespace-only boundary check from 55c58791f0 correctly prevented the @a.ts/@a.tsx collision, but also rejected legitimate repeats followed directly by ordinary punctuation (comma, sentence-ending period, closing paren).

Replaced the boundary condition with a path-continuation check matching MENTION_RE's own character class (word chars, dot, slash, hyphen) instead of requiring literal whitespace. A repeat is now rejected only when the adjacent character could actually extend it into a longer, different path. The dot needed one character of lookahead to disambiguate: it only counts as a continuation when followed by another word character (e.g. @report.csv + .bak), so a lone trailing sentence period no longer blocks highlighting, while a genuine compound-extension collision (@report.csv inside @report.csv.bak) still correctly gets rejected.

Added tests for: a repeat followed by a comma, a sentence-ending period, a closing paren, and the compound-extension collision case (to confirm the original fix's intent is preserved, not just relaxed away).

// Word characters, slashes, and hyphens unambiguously continue a path token.
// A dot is handled separately (see continuesPath) since it is both a common
// sentence-ending character and a path/extension separator.
const PATH_CONTINUATION = /[\w/-]/

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.

WARNING: ASCII-only \w boundary check reintroduces the prefix-collision bug for non-ASCII filenames

PATH_CONTINUATION (and continuesPath, which reuses it) is /[\w/-]/ without the u flag, so \w only matches [A-Za-z0-9_]. Any Unicode letter — Cyrillic, CJK, etc. — is not recognized as a continuation character, even though such characters are exactly what this PR adds support for.

Concretely: if a message mentions two distinct files файл.txt and файлы.txt (each gets its own source.text ref via buildFileAttachments), repeats() for the shorter @файл.txt ref will happily match the literal prefix @файл.txt inside the later, unrelated @файлы.txt occurrence — text[end] is ы, which PATH_CONTINUATION.test("ы") reports as false, so after is true and the bogus repeat is accepted. This is the same @a.ts/@a.tsx collision this file's repeats() was specifically written to prevent (see the existing regression test), just reintroduced for non-ASCII paths — the shorter mention's highlight bleeds into the longer, distinct mention, and resolve()'s index then advances past part of the real файлы.txt occurrence, corrupting or dropping its own highlight.

Consider adding the u flag and switching to a Unicode-aware character class (e.g. /[\w/-]/u still won't fix it by itself since \w is defined as ASCII-only even under u; something like /[\p{L}\p{N}_/-]/u is needed) so continuation detection works consistently for the Unicode filenames this PR targets.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@sylwester-liljegren

Copy link
Copy Markdown
Contributor Author

Addressed in b793d57.

ASCII-only \w in the boundary check — Confirmed: \w in JavaScript regex matches ASCII letters/digits only. Verified directly: /\w/.test("ы") is false, while /\p{L}/u.test("ы") is true. This meant a Cyrillic mention like @файл wasn't recognized as continuing into the distinct, longer @файлы, silently reintroducing the exact @a.ts/@a.tsx collision this check exists to prevent — specifically for the non-ASCII paths this PR targets.

Replaced \w with Unicode property escapes (\p{L}, \p{N} with the u flag) in both PATH_CONTINUATION and the dot-lookahead in continuesPath, so any Unicode letter or digit is recognized as a continuation character, not just ASCII ones.

Added regression tests for a Cyrillic collision (@файл inside @файлы) and a CJK collision (@文件 inside @文件夹), both of which I confirmed would fail against the prior ASCII-only implementation before applying the fix.

@marius-kilocode

Copy link
Copy Markdown
Collaborator

Hi @sylwester-liljegren. Thanks for the contribution, can you establish if those are worth fixing? I would also love to keep the change minimal since this is just a small improvement, can you confirm that this is the minimal path to achieve our follow up?

Blocking: rebase required before merge. The PR is DIRTY against main, with conflicts in packages/kilo-vscode/webview-ui/src/hooks/file-mention-utils.ts and packages/kilo-vscode/webview-ui/src/hooks/useFileMention.ts. Current main added file-picker support, bidi caret handling, and, critically, workspace-containment checks for attachments. Resolving buildFileAttachments by taking the PR version around file-mention-utils.ts:175 would remove the containment guard and risk restoring the external-file permission-bypass path. The rebase must retain normalization and isInsideWorkspace() checks, then add the new source metadata and URL handling on top.

Medium: repeated mentions hide a different mention between them. packages/kilo-ui/src/components/message-highlight.ts:129-132 finds all later repetitions of the first ref and advances the shared cursor to the last repetition before resolving subsequent refs. For @a.ts @b.ts @a.ts, the second @a.ts advances index past @b.ts, so @b.ts is rendered as plain text. I reproduced this against the PR head. Add a regression test with interleaved duplicate and distinct file/agent refs.

Medium: prefix protection fails for valid space-containing filenames. packages/kilo-ui/src/components/message-highlight.ts:105-107 treats whitespace as a valid token boundary. With both a.txt and a.txt backup.txt attached, @a.txt @a.txt backup.txt renders the second mention as a chip for @a.txt followed by unhighlighted backup.txt. The new feature explicitly supports spaces within paths, so a space cannot always indicate the end of a distinct mention. I reproduced this against the PR head.

Medium: literal percent-encoded-looking filenames resolve to the wrong file. packages/kilo-vscode/webview-ui/src/hooks/file-mention-utils.ts:193 only replaces spaces before assigning pathname. For a real filename such as 100%20real.txt, the produced URL includes %20, and Bun.fileURLToPath() decodes it to 100 real.txt. URL construction must escape literal percent signs as well as spaces, ideally by encoding path segments rather than selectively replacing spaces. Add a test that round-trips a filename containing literal %20.

@sylwester-liljegren

Copy link
Copy Markdown
Contributor Author

Hi @marius-kilocode , I think it is worth it. I'll look into this and I'll let you know when this PR is ready for you to review.

Sylwester Liljegren added 8 commits July 14, 2026 13:13
Two bugs prevented @mentions from working correctly when filenames
contained spaces or non-ASCII characters:

1. Sent-message highlighting broke for paths with spaces.
   buildFileAttachments did not set source.text position data on
   file attachments, so the renderer fell back to MENTION_RE which
   uses [\w./-] and stops at whitespace. Now source.text is computed
   via text.indexOf and included in the attachment. The renderer's
   resolve() path uses source.value with indexOf, which handles
   spaces and any Unicode correctly.

2. Server read failed with 'File not found' for paths with spaces.
   VS Code's webview (Chromium) does not percent-encode spaces when
   setting url.pathname on a file:// URL. The resulting literal space
   in url.href caused Bun's fileURLToPath to truncate the resolved
   path at the first space. Spaces are now pre-encoded as %20 before
   assignment.

Additionally, MENTION_RE in message-highlight.ts is updated to
match filenames that include space-separated segments (e.g.
'org data.xlsx') as a fallback for messages that pre-date this fix
and therefore lack source.text data.
Two issues flagged by review:

1. The broadened MENTION_RE fallback regex over-matched ordinary prose.
   A pattern permissive enough to span space-separated path segments
   also swallows text following any unrelated @mention up to the next
   dotted/versioned token (e.g. '@agent check report for v1.2 details'
   highlighted the entire span through 'v1.2'). Reverted MENTION_RE to
   its original conservative form; the primary fix for space/unicode
   filenames does not depend on it; it now relies on source.text data
   computed in buildFileAttachments and resolved via exact text search.

2. Repeated mentions of the same path in one message only highlighted
   the first occurrence. mentionedPaths is a Set, so buildFileAttachments
   produces exactly one ref per unique path regardless of how many times
   it appears in the text. Once that ref carries source.text, resolve()
   was used instead of the old regex-based detect() fallback, which
   used to independently highlight every occurrence via a global regex
   scan. resolve() now also searches the remaining text for exact repeats
   of each ref's resolved mention value, so every occurrence of a path
   stays highlighted, not just the first.

Added tests covering: over-matching prevention, repeated plain mentions,
and repeated mentions containing a space.
The repeat-mention search added in 44bea91 used a plain substring
indexOf, which let a shorter mention's value match as a literal prefix
of a longer, distinct mention that starts the same way (e.g. '@a.ts'
matches inside '@a.tsx'). This truncated the longer mention's highlight
to just its prefix and dropped its own ref from being located.

Repeats are now only accepted when flanked by whitespace or a string
edge on both sides, matching the same token-boundary convention already
used elsewhere (syncMentionedPaths' (?:^|\\s)@path(?:\\s|$) pattern).

Added a regression test for the exact a.ts/a.tsx collision case.
The whitespace-only boundary check added in 55c5879 to prevent
'@a.ts' from falsely matching inside '@a.tsx' was stricter than
necessary: it required literal whitespace on both sides, so a repeated
mention directly followed by ordinary punctuation (a trailing comma,
sentence-ending period, or closing paren) silently lost its highlight.

Replaced the whitespace check with a path-continuation check matching
MENTION_RE's own character class (word chars, dot, slash, hyphen).
A repeat is now rejected only when the adjacent character could extend
it into a longer, different path. The dot is handled with one
character of lookahead: it counts as a continuation only when another
word character follows (e.g. '@report.csv' + '.bak'), so a lone
trailing sentence period does not block highlighting, while a genuine
compound-extension collision still correctly does.

Added tests for: repeat followed by a comma, a sentence-ending period,
a closing paren, and a compound-extension collision that must still
be rejected (@report.csv inside @report.csv.bak).
PATH_CONTINUATION and continuesPath used \w, which in JavaScript regex
matches ASCII letters/digits only. A Cyrillic or CJK mention (e.g.
'@файл') was therefore not recognized as continuing into a longer,
distinct mention that starts the same way (e.g. '@файлы'), silently
reintroducing the same collision the check exists to prevent (the
@a.ts/@a.tsx case) specifically for the non-ASCII paths this PR is
meant to support.

Replaced \w with Unicode property escapes (\p{L}, \p{N} with the u
flag), which correctly recognize any Unicode letter or digit as a
continuation character, not just ASCII ones.

Added regression tests for a Cyrillic prefix collision (@файл inside
@файлы) and a CJK one (@文件 inside @文件夹), both verified to fail
against the prior ASCII-only implementation.
Reverting to a message with a space-containing @mention, then resending
the same text, reproduced the 'File not found' truncation error even
after the buildFileAttachments/URL-encoding fix, because the revert path
uses a different, unrelated mechanism to reconstruct known mention paths.

Revert restores the original message's text into the input via a
setChatBoxMessage window message, which PromptInput handles by calling
seedFromText(text). seedFromText re-derives candidate mention paths from
raw text with a regex (the same conservative, space-free pattern as
MENTION_RE) — for '@mention-test/my quarterly report.txt' its slash
alternative matches only 'mention-test/my', stopping at the first space.
That truncated candidate is then added to knownPaths and, critically,
passes syncMentionedPaths' own boundary check too: a real space genuinely
follows 'my' in the full filename, so the truncated candidate looks like
a complete, validly-bounded mention from the regex's perspective. The
truncated path then ends up in mentionedPaths alongside the real one, and
buildFileAttachments builds a second, broken attachment for it, producing
the same truncated 'File not found: ...\mention-test\my' error alongside
a correct read of the real file.

Fix: revertSession() now also extracts the reverted message's file parts'
exact source.path values (recorded by the earlier buildFileAttachments
fix) and sends them alongside the restored text. PromptInput's
setChatBoxMessage handler seeds these exact paths directly via a new
seedFromParts() function instead of falling back to seedFromText's regex
re-derivation, when they're available. seedFromParts skips the flawed
candidate-discovery step entirely and just prunes the known-good paths
against the current text, so it can't reintroduce the truncation.

seedFromText itself is left unchanged and still used as a fallback for
callers without exact path data (native undo restoring a draft, or
messages sent before source.path existed) — broadening its regex to
handle spaces was already tried and reverted elsewhere in this PR because
it causes ordinary prose to be over-matched.

Added tests: a regression test locking in seedFromText's known limitation
(so future refactors don't silently 'fix' it in a way that goes
unnoticed), and coverage for seedFromParts handling a space-containing
path correctly, pruning stale paths, and seeding multiple paths.
…ncoding

- Rework message-highlight.ts's resolve()/repeats() to locate each ref
  independently and track claimed ranges, instead of threading a single
  moving cursor through every ref. Fixes an interleaved-mentions bug where
  locating a later repeat of one mention (e.g. the second `@a.ts` in
  `@a.ts @b.ts @a.ts`) could advance past and hide a distinct mention
  (`@b.ts`) that sits between the repeats.
- Reject a repeat match when it is a literal prefix of another known ref's
  exact mention text, not just when the following character looks like a
  generic path-continuation character. A trailing space can no longer be
  assumed to end a mention now that paths may contain spaces, so
  `@a.txt @a.txt backup.txt` no longer truncates the second, longer mention.
- Escape literal percent characters (in addition to spaces) before assigning
  to the file:// URL's pathname in buildFileAttachments, so a real filename
  like "100%20real.txt" round-trips correctly instead of being decoded as
  "100 real.txt" server-side.
@sylwester-liljegren sylwester-liljegren force-pushed the fix/file-mention-spaces-unicode branch from 6bf4ce0 to cbdb2b1 Compare July 14, 2026 11:49
…-containing paths

syncMentionedPaths tested each known path independently with a boundary
regex that treated any whitespace after "@path" as proof the mention ends
there. That assumption broke once paths can contain spaces: a stale,
unrelated "a.txt" from an earlier mention would incorrectly survive
pruning whenever the current text also mentions the longer, distinct
"a.txt backup.txt", since a real space genuinely follows "a.txt" as part
of that longer path. The stale path would then get re-attached via
buildFileAttachments, silently reading the wrong file's contents.

Track already-accepted (longest-first) matches and reject a candidate
occurrence when it's a literal prefix of one of them at the same text
position, mirroring the same fix already applied to message-highlight.ts's
repeat-mention detection.
@sylwester-liljegren

Copy link
Copy Markdown
Contributor Author

@marius-kilocode , I have updated the PR now. You may look into this PR now if you would like to.

@marius-kilocode marius-kilocode merged commit a76dc77 into Kilo-Org:main Jul 15, 2026
28 checks passed
@marius-kilocode

Copy link
Copy Markdown
Collaborator

Thanks @sylwester-liljegren. It looked good, I merged it!

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.

2 participants