Skip to content

feat(console): #file mentions attach working-dir files to chat messages - #399

Merged
ytallo merged 1 commit into
mainfrom
feat/console-file-mentions
Jul 3, 2026
Merged

feat(console): #file mentions attach working-dir files to chat messages#399
ytallo merged 1 commit into
mainfrom
feat/console-file-mentions

Conversation

@ytallo

@ytallo ytallo commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Closes MOT-3860

Summary

Adds file mentions to the console chat composer: type #, fuzzy-pick a file from the session working directory, and its content is attached to the message the model receives.

  • # opens a files-only typeahead (bounded coder::tree snapshot, fuzzy-filtered, 30s TTL cache) and inserts a #file(<path>) pill; @ function mentions are unchanged. minLength: 1 on the trigger keeps markdown headings (# foo) from opening the menu.
  • On send, mentioned files are read in one coder::read-file batch scoped by fs_scope: { root: workingDir } and attached as <attached-file …> text blocks on a structured harness::send user message. Plain sends keep the string-sugar wire form.
  • Failures never block the send: a missing/binary/over-budget file becomes an error placeholder block plus a warning notice. Caps: 20 unique mentions per send; the server byte budget flags truncated reads on the block.
  • Transcript rendering collapses attachment blocks into size-labeled chips and renders mention tokens as pills, both live and on reload from the server transcript.
  • Extracts the shared FlipMenu dropdown from the mentions/slash typeahead menus instead of copying it a third time.

Zero harness/shell changes — client-side expansion over existing jail-validated functions; the transcript stores the content snapshot, so replays are deterministic and compaction-safe.

Test plan

  • Unit: 866/866 web tests pass (23 new — index flattening/filtering/TTL, expansion formatting and failure paths, header round-trip, entry-mapper chip collapse)
  • tsc -b, vite build, and biome clean on all touched files
  • E2E on a live stack: #READM lists repo files (default excludes honored) → pill inserts → send fires coder::read-file then structured harness::send (verified in traces) → model reply summarizes the attached README
  • Reload: reopened conversation renders text + pill + README.md 11kb chip purely from the server transcript, no raw <attached-file leakage
  • Regressions: @fn menu, /compact interception, and plain string-sugar sends unchanged; # heading opens no menu
  • Follow-ups (out of scope): directory mentions, image attachments as image blocks, wiring the upload button to real sends

Summary by CodeRabbit

  • New Features

    • You can now type #file(...) mentions in chat to attach files from the current working directory.
    • File mentions appear as inline pills in the editor and in rendered messages.
    • Long menus now flip upward automatically to stay visible.
  • Bug Fixes

    • File mention expansion now continues even if some files fail to load, while showing warnings.
    • Attached file content is now handled correctly in message history and optimistic sends.
    • Mention menus and file lookups now respect the active working directory.

@vercel

vercel Bot commented Jul 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
workers Ready Ready Preview, Comment Jul 3, 2026 7:43pm
workers-tech-spec Ready Ready Preview, Comment Jul 3, 2026 7:43pm

Request Review

@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

skill-check — worker

0 verified, 32 skipped (no docs/).

Layer Result
structure
vale
ai
render

Four for four. Nicely done.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ytallo, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 47 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 10583cf4-ddf0-468c-85f8-07c4cbbe15e8

📥 Commits

Reviewing files that changed from the base of the PR and between 3711d2a and 752f2f1.

📒 Files selected for processing (19)
  • console/web/src/components/chat/ChatView.tsx
  • console/web/src/components/chat/Composer.tsx
  • console/web/src/components/chat/LexicalShell.tsx
  • console/web/src/components/chat/lexical/FileMentionNode.tsx
  • console/web/src/components/chat/lexical/FileMentionTransformPlugin.tsx
  • console/web/src/components/chat/lexical/FileMentionsPlugin.tsx
  • console/web/src/components/chat/lexical/FlipMenu.tsx
  • console/web/src/components/chat/lexical/MentionsPlugin.tsx
  • console/web/src/components/chat/lexical/SlashCommandsPlugin.tsx
  • console/web/src/lib/backend/harness-send.ts
  • console/web/src/lib/backend/real.ts
  • console/web/src/lib/backend/types.ts
  • console/web/src/lib/file-index.test.ts
  • console/web/src/lib/file-index.ts
  • console/web/src/lib/file-mentions.test.ts
  • console/web/src/lib/file-mentions.ts
  • console/web/src/lib/markdown.tsx
  • console/web/src/lib/sessions/entry-mapper.test.ts
  • console/web/src/lib/sessions/entry-mapper.ts
📝 Walkthrough

Walkthrough

This PR adds a #file(<path>) mention feature to the chat composer: a cached working-directory file index with fuzzy filtering, Lexical editor nodes/plugins for typeahead and inline pills, expansion of mentions into attachment blocks sent through a new structured backend message shape, and rendering/parsing of attachment chips in chat history.

Changes

File Mention Feature

Layer / File(s) Summary
File index fetching and fuzzy filtering
console/web/src/lib/file-index.ts, console/web/src/lib/file-index.test.ts
Fetches/caches a coder::tree snapshot scoped to a working directory, flattens it into relative paths, and fuzzy-filters/ranks matches; tested for caching, TTL refetch, and fallback behavior.
Mention parsing and content expansion
console/web/src/lib/file-mentions.ts, console/web/src/lib/file-mentions.test.ts
Parses #file(...) mentions, batch-reads file contents via coder::read-file, and serializes results into <attached-file> blocks, attachment metadata, and failures, with header escape/parse helpers.
Lexical file-mention node and transform
.../lexical/FileMentionNode.tsx, .../lexical/FileMentionTransformPlugin.tsx
Adds FileMentionNode decorator with pill UI, selection, and deletion, plus a text-node transform converting #file(<path>) into the node.
Typeahead menu and shared FlipMenu
.../lexical/FileMentionsPlugin.tsx, .../lexical/FlipMenu.tsx, .../lexical/MentionsPlugin.tsx, .../lexical/SlashCommandsPlugin.tsx, LexicalShell.tsx, Composer.tsx
Adds #-triggered file typeahead backed by the file index, extracts a reusable FlipMenu dropdown used by function-mention and slash-command menus, and wires workingDir through ComposerLexicalShell.
Sending expanded attachments to backend
ChatView.tsx, backend/types.ts, backend/harness-send.ts, backend/real.ts
ChatView expands mentions before streaming, patches optimistic message attachments, and appends failure notices; backend types/request shapes gain attachedBlocks/structured HarnessUserMessage support.
Rendering and parsing history attachments
lib/markdown.tsx, lib/sessions/entry-mapper.ts, related tests
Markdown renders #file(...) pills; entry-mapper splits <attached-file> blocks out of user content into attachment chips and preserves snapshot-derived attachments during reconciliation.

Estimated code review effort: 4 (Complex) | ~75 minutes

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ChatView
    participant FileMentions as file-mentions.ts
    participant Backend as backend.stream/real.ts
    participant FileIndex as file-index.ts

    User->>ChatView: types "`#file`(path)" and submits
    ChatView->>FileMentions: parseFileMentions(text)
    FileMentions-->>ChatView: mention paths
    ChatView->>FileMentions: expandFileMentions(workingDir, paths)
    FileMentions->>FileIndex: coder::read-file trigger
    FileIndex-->>FileMentions: file contents / errors
    FileMentions-->>ChatView: blocks, attachments, failures
    ChatView->>ChatView: patch optimistic message attachments
    ChatView->>Backend: stream(prompt, {attachedBlocks})
    Backend->>Backend: build structured HarnessUserMessage
    Backend-->>ChatView: stream events
Loading
sequenceDiagram
    participant User
    participant Editor as LexicalShell/Composer
    participant Plugin as FileMentionsPlugin
    participant FileIndex as file-index.ts

    User->>Editor: types "#"
    Editor->>Plugin: trigger typeahead
    Plugin->>FileIndex: fetchFileIndex(workingDir)
    FileIndex-->>Plugin: cached or fresh index
    Plugin->>Plugin: fuzzyFilterFiles(index, query)
    Plugin-->>Editor: render FlipMenu options
    User->>Editor: selects option
    Editor->>Editor: insert FileMentionNode pill
Loading

Possibly related PRs

  • iii-hq/workers#141: Both PRs modify ChatView.tsx's submit/stream orchestration involving backend.stream options.
  • iii-hq/workers#163: Both PRs extend the same handleSubmit flow in ChatView.tsx, with this PR adding mention parsing after the /compact branch added there.

Suggested reviewers: andersonleal, sergiofilhowz

Poem

A hash, a path, a little pill appears,
Hopping through the index, calming fears.
Files attached with care and grace,
FlipMenu flips to find its place. 🐇
Send the turn, the blocks in tow—
This rabbit's code review is go!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding #file mentions that attach working-directory files to chat messages.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/console-file-mentions

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (4)
console/web/src/lib/file-mentions.ts (1)

92-97: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated TriggerFn type + default-trigger fallback across files.

The TriggerFn type and the "use provided trigger, else lazily fetch getIiiClient() and call .trigger(...)" fallback pattern here (lines 92-97) is duplicated verbatim in file-index.ts (lines 33-36, 148-153). Since both live in the same feature layer, consider extracting a shared helper/module to avoid drift between the two copies.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@console/web/src/lib/file-mentions.ts` around lines 92 - 97, The default
trigger fallback and TriggerFn typing are duplicated in file-mentions and
file-index, so extract that shared logic into a common helper/module and reuse
it from both locations. Move the “use provided trigger, otherwise lazily call
getIiiClient() and client.trigger(...)” behavior into a single reusable
function, then update fileMentions and fileIndex to reference that helper so the
two copies cannot drift.
console/web/src/lib/file-index.ts (1)

17-19: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

No cap on total flattened file count.

max_depth and per_folder_limit bound depth and per-directory fan-out, but there's no ceiling on the total number of files across the whole tree. A wide, shallow repo (many folders each near the 500-item limit) can still produce a very large flattened index that gets fuzzy-filtered on every keystroke in the typeahead. Consider adding a total-node cap (e.g., truncate flattenTree's output or stop the walk once a global budget is hit) as a defense-in-depth measure alongside the existing depth/per-folder bounds.

Also applies to: 144-165

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@console/web/src/lib/file-index.ts` around lines 17 - 19, The file indexing
flow in file-index.ts only limits depth and per-folder fan-out, but flattenTree
can still accumulate an unbounded total number of entries for wide repos. Add a
global maximum node/file budget in the flattening/walk logic so traversal stops
or truncates once the cap is reached, and make sure the same limit is enforced
wherever the flattened list is produced and cached. Use the existing
TREE_MAX_DEPTH, TREE_PER_FOLDER_LIMIT, and flattenTree symbols to locate the
traversal and apply the total-count guard alongside those bounds.
console/web/src/components/chat/lexical/FileMentionNode.tsx (1)

195-251: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the shared selection/delete logic.

EditableFileMentionPill's selection tracking and CLICK_COMMAND/KEY_BACKSPACE_COMMAND/KEY_DELETE_COMMAND wiring duplicates EditableFunctionMentionPill in FunctionMentionNode.tsx almost verbatim. A shared hook (e.g. useMentionPillSelection(nodeKey)) would let future accessibility/selection fixes land once instead of twice.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@console/web/src/components/chat/lexical/FileMentionNode.tsx` around lines 195
- 251, `EditableFileMentionPill` duplicates the same selection and delete
command wiring already used by `EditableFunctionMentionPill`, so extract the
shared logic into a reusable hook such as `useMentionPillSelection(nodeKey)`.
Move the `useLexicalNodeSelection`, `CLICK_COMMAND`, `KEY_BACKSPACE_COMMAND`,
and `KEY_DELETE_COMMAND` handling into that shared hook, then have both mention
pill components consume it while keeping their rendering code separate.
console/web/src/components/chat/lexical/FileMentionTransformPlugin.tsx (1)

26-54: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the shared match→splitText→replace logic.

The start/end branching and splitText handling here is nearly identical to FunctionMentionTransformPlugin's transform. A small shared helper like replaceMatchedTextWithNode(node, match, createNode) would remove this duplication across both transform plugins with minimal risk.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@console/web/src/components/chat/lexical/FileMentionTransformPlugin.tsx`
around lines 26 - 54, The match→splitText→replace flow in
FileMentionTransformPlugin is duplicated and should be shared with
FunctionMentionTransformPlugin. Extract the common logic into a helper such as
replaceMatchedTextWithNode that takes the TextNode, the regex match, and a node
factory, then use it from the transform in FileMentionTransformPlugin to
preserve the current behavior while removing the repeated start/end branching.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@console/web/src/components/chat/lexical/FileMentionNode.tsx`:
- Around line 195-251: `EditableFileMentionPill` duplicates the same selection
and delete command wiring already used by `EditableFunctionMentionPill`, so
extract the shared logic into a reusable hook such as
`useMentionPillSelection(nodeKey)`. Move the `useLexicalNodeSelection`,
`CLICK_COMMAND`, `KEY_BACKSPACE_COMMAND`, and `KEY_DELETE_COMMAND` handling into
that shared hook, then have both mention pill components consume it while
keeping their rendering code separate.

In `@console/web/src/components/chat/lexical/FileMentionTransformPlugin.tsx`:
- Around line 26-54: The match→splitText→replace flow in
FileMentionTransformPlugin is duplicated and should be shared with
FunctionMentionTransformPlugin. Extract the common logic into a helper such as
replaceMatchedTextWithNode that takes the TextNode, the regex match, and a node
factory, then use it from the transform in FileMentionTransformPlugin to
preserve the current behavior while removing the repeated start/end branching.

In `@console/web/src/lib/file-index.ts`:
- Around line 17-19: The file indexing flow in file-index.ts only limits depth
and per-folder fan-out, but flattenTree can still accumulate an unbounded total
number of entries for wide repos. Add a global maximum node/file budget in the
flattening/walk logic so traversal stops or truncates once the cap is reached,
and make sure the same limit is enforced wherever the flattened list is produced
and cached. Use the existing TREE_MAX_DEPTH, TREE_PER_FOLDER_LIMIT, and
flattenTree symbols to locate the traversal and apply the total-count guard
alongside those bounds.

In `@console/web/src/lib/file-mentions.ts`:
- Around line 92-97: The default trigger fallback and TriggerFn typing are
duplicated in file-mentions and file-index, so extract that shared logic into a
common helper/module and reuse it from both locations. Move the “use provided
trigger, otherwise lazily call getIiiClient() and client.trigger(...)” behavior
into a single reusable function, then update fileMentions and fileIndex to
reference that helper so the two copies cannot drift.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: fb1700d6-e1eb-41ab-aefb-ecd3ff18581f

📥 Commits

Reviewing files that changed from the base of the PR and between ec368c4 and 3711d2a.

📒 Files selected for processing (19)
  • console/web/src/components/chat/ChatView.tsx
  • console/web/src/components/chat/Composer.tsx
  • console/web/src/components/chat/LexicalShell.tsx
  • console/web/src/components/chat/lexical/FileMentionNode.tsx
  • console/web/src/components/chat/lexical/FileMentionTransformPlugin.tsx
  • console/web/src/components/chat/lexical/FileMentionsPlugin.tsx
  • console/web/src/components/chat/lexical/FlipMenu.tsx
  • console/web/src/components/chat/lexical/MentionsPlugin.tsx
  • console/web/src/components/chat/lexical/SlashCommandsPlugin.tsx
  • console/web/src/lib/backend/harness-send.ts
  • console/web/src/lib/backend/real.ts
  • console/web/src/lib/backend/types.ts
  • console/web/src/lib/file-index.test.ts
  • console/web/src/lib/file-index.ts
  • console/web/src/lib/file-mentions.test.ts
  • console/web/src/lib/file-mentions.ts
  • console/web/src/lib/markdown.tsx
  • console/web/src/lib/sessions/entry-mapper.test.ts
  • console/web/src/lib/sessions/entry-mapper.ts

- '#' typeahead in the composer lists files from the session working
  directory (coder::tree snapshot, fuzzy-filtered, TTL-cached) and
  inserts #file(<path>) pills; '@' function mentions are unchanged
- on send, mentioned files are read in one coder::read-file batch
  scoped by fs_scope and attached as <attached-file> text blocks on a
  structured harness::send user message; plain sends keep string sugar
- unreadable files (missing, binary, over budget) never block the
  send: they become error placeholder blocks plus a warning notice
- transcript rendering collapses attachment blocks into size-labeled
  chips and renders mention tokens as pills, both live and on reload
- extract the shared FlipMenu dropdown from the mentions and slash
  typeahead menus instead of copying it a third time
@ytallo
ytallo force-pushed the feat/console-file-mentions branch from 3711d2a to 752f2f1 Compare July 3, 2026 19:42
@ytallo
ytallo merged commit 5bfc9c7 into main Jul 3, 2026
14 checks passed
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