Skip to content

feat(vscode): add file picker to @ mention dropdown#12028

Open
sylwester-liljegren wants to merge 4 commits into
Kilo-Org:mainfrom
sylwester-liljegren:feat/vscode-file-picker-mention
Open

feat(vscode): add file picker to @ mention dropdown#12028
sylwester-liljegren wants to merge 4 commits into
Kilo-Org:mainfrom
sylwester-liljegren:feat/vscode-file-picker-mention

Conversation

@sylwester-liljegren

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

Copy link
Copy Markdown
Contributor

Issue

Exception: this is a small, self-contained feature request that came directly from manual testing feedback; no tracking issue was filed.

Context

Users frequently need to reference files that live outside the current workspace (e.g. configuration files, shared libraries in a parent directory, or files on a different drive on Windows). The existing @ mention system only lists files within the workspace, making it impossible to attach out-of-workspace files without manually typing the full path.

Implementation

Added a Browse files... option to the @ mention dropdown that opens VS Code's native file picker dialog. When a file is selected, its full absolute path is inserted as a styled @/full/path mention with the same atomic selection/backspace behavior as workspace file mentions.

Key pieces:

  • New webview→extension message requestFilePicker and extension→webview message filePickerResult
  • KiloProvider.ts handles the request via vscode.window.showOpenDialog
  • useFileMention.ts stores the textarea/selection state before opening the dialog and restores it via execCommand after the result arrives
  • buildFileAttachments was updated to handle Windows absolute paths (C:/...) and backslash normalization so out-of-workspace files are correctly resolved to file:// URLs
  • The file picker item sits at the top of the dropdown (below Terminal and Git changes) with a separator before regular file results

Screenshots / Video

file_picker_demo.1.mp4

How to Test

Manual/local verification

  • Ran bun run typecheck, bun run lint, and bun run test:unit from packages/kilo-vscode/ — all pass
  • Launched the extension in dev mode, typed @ in the prompt input, and confirmed Browse files... appears near the top of the dropdown, below Terminal and Git changes, with a divider before workspace file results
  • Selected Browse files..., picked a file outside the workspace, and confirmed the full path is inserted as a styled mention
  • Sent the message and confirmed the out-of-workspace file content was attached correctly
  • Verified on Windows with paths containing backslashes (e.g. C:\Users\...\file.sh) — previously failed with a "file not found" error because the path was resolved relative to the workspace directory; now normalized to forward slashes and resolved as an absolute path

Reviewer test steps

  1. Open a workspace in the Kilo VS Code extension
  2. Focus the prompt input and type @
  3. Select Browse files...
  4. Pick a file outside the current workspace (e.g. from your home directory)
  5. Send the message and confirm the file content is attached correctly

Blocked checks and substitute verification

  • No screenshot/recording tool was available in this session to capture and upload the dropdown UI; substitute verification was the manual dev-mode walkthrough described above, confirmed visually by the human tester.

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.

Sylwester Liljegren added 2 commits July 8, 2026 02:12
…cker-mention

# Conflicts:
#	packages/kilo-vscode/webview-ui/src/hooks/useFileMention.ts
suppress = true
try {
textarea.setSelectionRange(state.atStart, state.atEnd)
document.execCommand("insertText", false, `@${norm}${suffix}`)

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: Missing focus() before execCommand("insertText", ...)

insertFilePickerResult calls textarea.setSelectionRange(...) and then document.execCommand("insertText", ...), but never calls textarea.focus() first. Compare with the synchronous selectMention path (line 188), which calls textarea.focus() before inserting.

This path runs asynchronously, after the native OS file-picker dialog (opened via showOpenDialog in KiloProvider.ts) closes and the filePickerResult message arrives. execCommand("insertText") operates on the currently focused editable element, not merely on whatever element had setSelectionRange called on it. If VS Code doesn't restore focus to this exact <textarea> after the native dialog closes, the insert can silently no-op (or land somewhere else), while knownPaths/mentionedPaths/syncMentionedPaths/state.setText all proceed as if the insertion succeeded, since they read textarea.value after the execCommand call regardless of whether it actually changed anything.

Consider calling textarea.focus() before setSelectionRange/execCommand, similar to the existing selectMention flow.


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

const match = before.match(AT_PATTERN)!
const prefix = /^\s/.test(match[0]) ? 1 : 0
const atPos = match.index! + prefix
pickerState = { textarea, atStart: atPos, atEnd: cursor, setText: _setText, onSelect }

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.

SUGGESTION: No request correlation for the file-picker round trip

pickerState is a single shared closure variable that stores the pending picker request's textarea/selection, with no request id. This is inconsistent with requestFileSearch in this same file, which correlates responses via an incrementing fileSearchCounter embedded in requestId (see line 111, 127-132), and RequestFilePickerMessage/FilePickerResultMessage (in webview-messages.ts/extension-messages.ts) don't carry a requestId either, unlike the sibling RequestFileSearchMessage/FileSearchResultMessage.

Today this is likely benign since showOpenDialog is a modal dialog and Agent Manager only mounts one PromptInput/useFileMention instance at a time, so a second requestFilePicker can't realistically be triggered while one is in flight. But it's a latent footgun if that assumption ever changes (e.g. a future split-view or re-entrant trigger) — a stale/duplicate filePickerResult would silently apply to whatever pickerState happens to be set at the time. Worth considering a requestId for consistency with the established pattern, even if not strictly required today.


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


const seedFromText = (text: string) => {
const re = /@([\w./-]+\.[\w]+|[\w.-]+\/[\w./-]+)/g
const re = /@([\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: Regex change lets URL-like text after @ be treated as a known path

Adding : to both alternatives of this regex (to support Windows drive letters like @C:/Users/file.ts) also makes it match URL-like text such as @https://example.com, which the old regex (/@([\w./-]+\.[\w]+|[\w.-]+\/[\w./-]+)/g, no colon in the character classes) correctly rejected — the colon after https broke the match entirely before.

With the new regex, seedFromText would call knownPaths.add("https://example.com") for such text, causing it to be treated as a "known mentioned path" for highlighting. Downstream, buildFileAttachments would then try to build a file attachment for it: since "https://example.com" isn't detected as absolute by isAbsolutePath, it gets prefixed with the workspace dir (${dir}/https://example.com), producing a nonsensical file path/attachment.

Consider scoping the added : support to only the drive-letter case (e.g. requiring it immediately follow a single letter at the start) rather than allowing it anywhere in the path segment.


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

</>
)}
</div>
<Show when={item.type === "file-picker" && mention.mentionResults().length > 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: Separator can render as a trailing divider with nothing below it

This condition checks the total mentionResults().length, not whether any items actually follow the file-picker row. buildMentionResults always orders results as [...terminal, ...gitChanges, FILE_PICKER_RESULT, ...fileResults]. If terminal and/or git-changes match but there are zero file results for the current query (e.g. typing @t matches "terminal" but no files), mentionResults() is [terminal, FILE_PICKER_RESULT] — length 2, so this renders true, but FILE_PICKER_RESULT is the last item, producing a divider below it with nothing underneath.

Consider checking whether this item is followed by another item instead, e.g. index() < mention.mentionResults().length - 1 combined with the file-picker type check.


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


it("always includes file picker result", () => {
const result = buildMentionResults("", [])
expect(result[result.length - 1]).toEqual(FILE_PICKER_RESULT)

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.

SUGGESTION: Test doesn't actually validate picker placement

This test calls buildMentionResults("", []) (empty items) and asserts result[result.length - 1] equals FILE_PICKER_RESULT. Since items is empty, FILE_PICKER_RESULT is trivially both the first and last element of the result — this would pass identically whether the implementation splices it before or after ...results, so it doesn't verify the "picker appears before regular file results" placement the test name implies. The sibling tests above (e.g. lines 75-83) already correctly verify ordering using non-empty items; this one could be dropped as redundant, or rewritten to assert an index/position using a non-empty items array.


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

@kilo-code-bot

kilo-code-bot Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Resolved since last review
  • insertFilePickerResult now calls textarea.focus() before setSelectionRange/execCommand("insertText", ...) instead of after, so focus is correctly restored to the textarea before the native-dialog-closed insert runs.
Files Reviewed (1 file in incremental diff)
  • packages/kilo-vscode/webview-ui/src/hooks/useFileMention.ts - 0 new issues
Previous Review Summaries (2 snapshots, latest commit 4b7fcee)

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

Previous review (commit 4b7fcee)

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-vscode/webview-ui/src/hooks/useFileMention.ts 411 The new textarea.focus() call is placed after the execCommand("insertText", ...) call instead of before it, so the underlying issue (execCommand may no-op if the textarea isn't focused after the native dialog closes) is not actually fixed by this commit.
Resolved since last review
  • Regex now scopes the drive-letter prefix to a single letter directly after @, so @https://example.com no longer registers as a known path.
  • Dropdown separator now only renders when a file result follows the picker row, fixing the trailing-divider case.
  • File-picker request/response now carries a requestId, closing the correlation gap between the picker dialog and the async result.
  • The buildMentionResults placement test now uses non-empty items and asserts full ordering, no longer vacuous.
Files Reviewed (9 files in incremental diff)
  • packages/kilo-vscode/src/KiloProvider.ts - 0 issues
  • packages/kilo-vscode/src/kilo-provider/file-picker.ts - 0 issues
  • packages/kilo-vscode/tests/unit/file-mention-utils.test.ts - 0 issues
  • packages/kilo-vscode/tests/unit/use-file-mention.test.ts - 0 issues
  • packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx - 0 issues
  • packages/kilo-vscode/webview-ui/src/hooks/file-mention-utils.ts - 0 issues
  • packages/kilo-vscode/webview-ui/src/hooks/useFileMention.ts - 1 issue (carried over, not newly commented — already has an active inline comment on line 411)
  • packages/kilo-vscode/webview-ui/src/types/messages/extension-messages.ts - 0 issues
  • packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts - 0 issues

Fix these issues in Kilo Cloud

Previous review (commit 1dfed48)

Status: 5 Issues Found | Recommendation: Address before merge

Overview

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

WARNING

File Line Issue
packages/kilo-vscode/webview-ui/src/hooks/useFileMention.ts 371 insertFilePickerResult never calls textarea.focus() before execCommand("insertText", ...), unlike the synchronous selectMention path — the async picker-result path may silently no-op if focus isn't restored to the textarea after the native dialog closes
packages/kilo-vscode/webview-ui/src/hooks/useFileMention.ts 347 Adding : to the seedFromText regex to support Windows drive letters also lets it match URL-like text after @ (e.g. @https://example.com), which the old regex correctly rejected — this can register bogus "known paths" and produce nonsensical file attachments
packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx 1106 The dropdown separator condition checks total mentionResults().length rather than whether items follow the file-picker row, so it can render a trailing divider with nothing below it when the picker ends up as the last item

SUGGESTION

File Line Issue
packages/kilo-vscode/webview-ui/src/hooks/useFileMention.ts 162 pickerState/the file-picker message protocol has no request correlation id, unlike the fileSearchCounter/requestId pattern used for file search — low practical risk today (modal dialog, single mounted instance) but a latent inconsistency
packages/kilo-vscode/tests/unit/file-mention-utils.test.ts 87 "always includes file picker result" test uses an empty items array, so it can't actually distinguish correct placement from incorrect placement — vacuous relative to its stated intent
Files Reviewed (10 files)
  • .changeset/vscode-file-picker-mention.md - 0 issues
  • packages/kilo-vscode/src/KiloProvider.ts - 0 issues
  • packages/kilo-vscode/tests/unit/file-mention-utils.test.ts - 1 issue
  • packages/kilo-vscode/tests/unit/use-file-mention.test.ts - 0 issues
  • packages/kilo-vscode/webview-ui/src/components/chat/PromptInput.tsx - 1 issue
  • packages/kilo-vscode/webview-ui/src/hooks/file-mention-utils.ts - 0 issues
  • packages/kilo-vscode/webview-ui/src/hooks/useFileMention.ts - 3 issues
  • packages/kilo-vscode/webview-ui/src/styles/prompt-dropdowns.css - 0 issues
  • packages/kilo-vscode/webview-ui/src/types/messages/extension-messages.ts - 0 issues
  • packages/kilo-vscode/webview-ui/src/types/messages/webview-messages.ts - 0 issues

Fix these issues in Kilo Cloud


Reviewed by claude-sonnet-5 · Input: 28 · Output: 7.2K · Cached: 624.7K

Review guidance: REVIEW.md from base branch main

@marius-kilocode

marius-kilocode commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

@sylwester-liljegren One potential blocker: What if the user declined access to a file. This solution essentially bypasses that restriction. One could argue that's ok, the user selected it. But what if the file would need to be edited? Then subsequent reads would fail.

@sylwester-liljegren

sylwester-liljegren commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

@sylwester-liljegren One potential blocker: What if the user declined access to a file. This solution essentially bypasses that restriction. One could argue that's ok, the user selected it. But what if the file would need to be edited? Then subsequent reads would fail.

@marius-kilocode Good point, I should have thought of it earlier. Just a question before making any changes: If the user mentions a file outside the workspace for the first time and then it references it for additional reads/writes all while the user previously blocked access to either that file or the directory that the file is within, shouldn't AI inform the user about its lack of access to the file for further reference and either a) prompt the user to allow AI to access that specific file at that path or b) just tell user to adjust the deny rules instead.

Or the simpler way is to just insert a styled path into chat textarea and then let AI go through permissions system to check if it access the file or not and depending on how things are set up, then deny if there is an explicit deny rule and ask if no explicit allow rule exists (otherwise, just allow).

What do you think is most sound?

EDIT: I'm leaning toward the second option as it is more cleaner and more architecturally consistent. I'll implement this unless you have any other opinion on this matter.

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