Skip to content

Feat/vscode context mentions - #842

Open
addyCooks wants to merge 7 commits into
Nano-Collective:mainfrom
addyCooks:feat/vscode-context-mentions
Open

Feat/vscode context mentions#842
addyCooks wants to merge 7 commits into
Nano-Collective:mainfrom
addyCooks:feat/vscode-context-mentions

Conversation

@addyCooks

@addyCooks addyCooks commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Closes #747.

Summary

Implements @ mention autocomplete in the VS Code extension's chat composer, allowing users to explicitly attach workspace files, folders, and open editors as context. Typing @ displays a dropdown with fuzzy-matched suggestions; selecting an item attaches it as a chip that's expanded into the AI's context on submit.

Files Changed

New Modules

  • mention-search.ts
  • mention-search.spec.ts
  • mention-utils.js
  • mention-utils.spec.ts
  • context-attachment.ts
  • context-attachment.spec.ts

Modified Files

  • webview-protocol.ts
  • chat-webview-provider.ts
  • chat-panel.js
  • chat-panel.html
  • .changeset/vscode-context-mentions.md

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation update

Changeset

  • Added a changeset (pnpm changeset) describing this change for the changelog

Testing

Automated Tests

  • New features include passing tests in .spec.ts/tsx files
  • All existing tests pass (pnpm test:all completes successfully)
  • Tests cover both success and error scenarios

Manual Testing

  • Tested with Ollama
  • Tested with OpenRouter
  • Tested with OpenAI-compatible API
  • Tested MCP integration (if applicable)

###Checklist

  • Code follows project style guidelines
  • Self-review completed
  • Documentation updated (if needed)
  • No breaking changes (or clearly documented)
  • Appropriate logging added using structured logging

Typing `@` in the chat input opens a floating dropdown of workspace files,
folders, and open editors. Selecting one attaches it as a context chip.

Reuses the existing attachment pipeline rather than adding a parallel one:
completions push into the same `attachedPaths` state the attach button
already feeds, so chip rendering, `@[file]` serialization and host-side
expansion are untouched.

- Search runs on the extension host via workspace.findFiles, which honours
  files.exclude/search.exclude; open editor tabs resolve with no disk I/O
- Requests carry an id so out-of-order postMessage responses are dropped
- `@` triggers only at line start or after whitespace, so emails and
  decorators do not open the dropdown
- Cap attached file reads at 100 KB and skip binaries, since `@` makes
  attaching cheap enough to exhaust the context window by accident
CI runs ava with FORCE_COLOR=1, so Ink emits the themed indicator wrapped
in escape sequences and the frame line starts with \x1b[34m rather than
`>`. The anchored regexes could therefore never match, while locally the
colours are stripped and the tests passed.

Strip the frame with strip-ansi before splitting, matching the existing
convention in app-container.spec.tsx. Verified under both
CI=true FORCE_COLOR=1 and a plain local run.
The "reports and restores submitted drafts with attachments" test flaked in
CI. Instrumenting it showed the frame rendered the typed text but onSubmit
was called zero times, so the poll for the submitted value ran out its
budget with nothing pending - the Enter had been swallowed, not delayed.

Ink's useInput unsubscribes from stdin and resubscribes across a re-render,
so an Enter written while React is still flushing the typed characters is
lost. Raising the deadline cannot help when no submit is in flight.

Route every Enter through a helper that first waits for the frame to stop
changing, which keeps the keystroke clear of that window. Resending Enter
was rejected as an alternative: handleSubmit has no empty-input guard, so a
duplicate would submit '' and clobber the captured value.

Verified with the full file under CI=true FORCE_COLOR=1: six consecutive
clean runs, against a one-in-three failure rate beforehand.
The first pass spread this feature across four new modules plus three spec
files, and picked up changes unrelated to Nano-Collective#747. Reviewing it meant reading
1.4k added lines for what is a dropdown over a workspace search.

Everything now lives in the files the feature already had to touch:

- mention-search.ts collapses into a single _searchMentions method on
  ChatWebviewProvider. Ranking and matching are unchanged; the injectable
  MentionSearchDeps seam and the hand-rolled posix path helpers are gone,
  since path.relative/basename/dirname already handle both separators.
- mention-utils.js collapses into chat-panel.js. findMentionQuery moves in
  as-is; removeMentionToken was three lines at its only call site, so it is
  inlined there. That drops the extra <script> tag, its CSP-nonce wiring and
  the globalThis handoff.
- MentionItem moves to webview-protocol.ts next to the two messages that
  carry it, and its source enum narrows to an isEditor flag.

Reverted as out of scope for this issue:

- context-attachment.ts and the 100 KB cap / binary sniff it added to
  _expandContextAttachments. Worth doing, but it is not @ autocomplete and
  the attach button has always had the same exposure - separate PR.
- The user-input and styled-select-input spec fixes, which are already
  their own branches.
- chat-panel.css, a Tailwind rebuild whose only delta was an unrelated
  .container rule; every class the dropdown uses was already compiled.

The three spec files go with their modules. AVA only globs source/**, so
plugins/vscode specs never ran in CI and none of them were executing.

No behaviour change: same trigger rules, same ranking, same keyboard
handling, same postMessage protocol.
@addyCooks
addyCooks force-pushed the feat/vscode-context-mentions branch from 9230511 to 7dc000d Compare August 10, 2026 20:31
@will-lamerton

Copy link
Copy Markdown
Member

Hey @addyCooks - thanks for this, the design is solid - host-side search, the request-id staleness guard, mousedown over click, and folding the keys into the existing keydown listener are all the right calls.

A few things before merge:

The description doesn't match the diff. The body describes mention-search.ts, mention-utils.js, context-attachment.ts and 56 tests, but the final refactor: fold @ mention support into the existing files commit collapsed all of that back inline and took the specs with it. Six files changed, no specs. That leaves ~480 lines of ranking/glob/caret logic uncovered, and the "New features include passing tests" boxes ticked for tests that aren't there. The split you originally described was the better structure - could you restore it?

The 100 KB read cap isn't in the diff either. _expandContextAttachments is still a bare readFileSync with no size check. Your reasoning for adding it was right and matters more now that @ makes hitting a lockfile one keystroke.

Escape while streaming kills the agent turn. The mention Escape branch calls preventDefault() but not stopPropagation(), so it bubbles to the document handler that cancels on Escape when isProcessing. Drafting a message mid-stream and dismissing the dropdown cancels the run.

files.exclude isn't actually honoured. Passing an explicit exclude glob to findFiles replaces the default excludes rather than adding to them (and search.exclude never applies to findFiles). So .env and friends show up in the dropdown, and the changeset currently claims the opposite in the public changelog.

Stale mentionToken. It's only assigned inside requestMentions, after the mentionLastQuery === token.query early return. With @foo @foo, moving the caret from the second mention to the first short-circuits the dedupe and leaves start pointing at the second one, so accepting adds the chip but leaves the literal @foo in the message. Assigning it in syncMentionState fixes it.

Smaller stuff: accepting mid-token leaves the suffix behind (@src/foo with the caret after src leaves /foo); the absPath.toLowerCase() dedupe key collides case-sensitive siblings; @** degrades to a full workspace scan that then filters to nothing; and the listbox has role="option" but no aria-activedescendant linking it to the textarea. The styled-select-input.spec.tsx change is a no-op on main (I ran it) and unrelated to this feature, so probably worth splitting out.

Reverses the structural half of "fold @ mention support into the existing
files" and addresses the defects found in review of Nano-Collective#842.

The fold's argument for dropping the specs was that AVA only globs
source/**, so none of them executed. That is fixed rather than worked
around: ava.files now includes plugins/**, so the three spec files run in
`pnpm test:ava` and in CI. acp-client and acp-process-manager are excluded
explicitly — they transitively require('vscode'), which does not resolve
outside the extension host. They stay in the state they are in today.

Restored:

- mention-search.ts, carrying the fold's improvements forward: MentionItem
  comes from webview-protocol.ts and the source enum stays collapsed to an
  isEditor flag. The injectable MentionSearchDeps seam is back, since
  testability without an extension host is the whole point.
- mention-utils.js and context-attachment.ts, with their specs.

Fixed:

- files.exclude was not honoured. An explicit exclude *replaces* VS Code's
  defaults rather than merging, so .env and anything else hidden by the
  user showed up in the dropdown. _mentionExcludeGlob now folds
  files.exclude and search.exclude into the always-on list, scoped to the
  workspace folder being searched, skipping `when`-clause entries.
- mentionToken was assigned after the dedupe guard in requestMentions.
  With `@foo @foo`, moving the caret between the two short-circuited the
  guard and left start pointing at the wrong mention, so accepting added
  the chip but stripped the other one. Assigned in syncMentionState now.
- Accepting mid-token sliced only up to the caret, stranding the tail:
  `@src/foo` accepted from after `src` left `/foo` behind.
  removeMentionToken takes the whole token.
- The dedupe key folded case unconditionally, so on a case-sensitive
  filesystem `Foo.ts` and `foo.ts` collapsed and one silently vanished.
  Folding is now conditional on the filesystem, injectable for tests.
- `@**` widened to a match-everything glob, scanned the workspace, then
  filtered every result away against the raw `**` needle. Queries with no
  literal characters no longer touch disk, and result filtering uses the
  metacharacter-stripped query so `*app*` still finds app.ts.
- The listbox had role=option rows but nothing tied them to the textarea.
  Added aria-activedescendant, plus role=combobox / aria-expanded /
  aria-controls and stable row ids.
- The mention Escape branch called preventDefault without
  stopPropagation. No handler above it cancels on Escape today, so this is
  not reachable, but the branch should not depend on that.
- The fold had swapped the hand-rolled posix path helpers for node's
  `path`. `path` binds to the host separator, so the Windows-path tests
  passed locally and meant nothing on Linux. Helpers are hand-rolled
  against the posix form again.

Also restores the 100 KB read cap and binary sniff on attached files,
scoped out of the fold as a separate concern — @ makes attaching a
lockfile one keystroke, so it belongs with this change.

The styled-select-input spec fix is dropped from this branch; it is
byte-identical to fix/styled-select-input-ansi-assertions.
Per review feedback, these ANSI stripping changes are unrelated to the @ mention feature and should be in a separate PR.
@addyCooks
addyCooks force-pushed the feat/vscode-context-mentions branch from 7dc000d to ac5abf5 Compare August 13, 2026 21:36
@addyCooks

Copy link
Copy Markdown
Contributor Author

Hey @will-lamerton,
Thank you for the detailed review again : )
I've addressed all 10 issues

The PR description has been updated to accurately reflect the current implementation.
All tests pass, and the feature is ready for re-review.

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.

[Feature] Context-Aware Mentions (@ referencing) in VS Code Extension

2 participants