[feat] Mention drive files in the composer with @ - #6521
Conversation
…te plugin The `@` file menu needs the same Lexical mechanics the `/` menu already has. A second plugin would claim Enter at CRITICAL alongside the first and race by mount order even while closed, keep a divergent dismissal latch, and clobber the same aria-activedescendant on the one contenteditable root. So the plugin now takes N palette specs and owns the keyboard once. The `/` palette's behaviour is unchanged: slashCommands.ts keeps every export and its unit test passes unedited. Also moves @floating-ui/react to dependencies, since mobile is about to take a runtime path through it.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
📝 SummarySummary by CodeRabbit
WalkthroughThe composer now supports ChangesFile Mention Palette
Estimated code review effort: 4 (Complex) | ~45 minutes Merge Risk: 🟡 Moderate · up to Folder navigation in the new @ file palette is currently broken because drilling into a folder closes the palette and returns it to root. This behavior and the outstanding formatting failure should be fixed before merge. Sequence Diagram(s)sequenceDiagram
actor User
participant ChatComposer
participant useFilePalette
participant DriveSessionProvider
participant CommandPalettePlugin
participant PalettePanel
User->>ChatComposer: type @
ChatComposer->>useFilePalette: activate file palette
useFilePalette->>DriveSessionProvider: load directory entries
DriveSessionProvider-->>useFilePalette: return drive listing
useFilePalette->>CommandPalettePlugin: provide file sections
CommandPalettePlugin->>PalettePanel: render filtered rows
User->>PalettePanel: select file or folder
PalettePanel->>CommandPalettePlugin: return selection
CommandPalettePlugin->>ChatComposer: insert file reference
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Full details: Linked Issues checkExplanation The PR satisfies the main
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (2)
web/packages/agenta-ui/src/RichChatInput/plugins/PalettePanel.tsx (1)
131-131: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUse Tailwind widths, not
style; as per coding guidelines: “Avoid … inlinestyle={{...}}.”Source: Coding guidelines
web/packages/agenta-chat/src/assets/filePaletteRows.ts (1)
1-6: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winShorten new code comments.
web/packages/agenta-chat/src/assets/filePaletteRows.ts#L1-L6: Reduce this block to one short line.web/packages/agenta-chat/src/assets/filePaletteRows.ts#L43-L46: Reduce this block to one short line.web/packages/agenta-chat/src/assets/filePaletteRows.ts#L73-L76: Reduce this block to one short line.web/storybook/stories/domain/RichChatInput.stories.tsx#L401-L401: Reduce this comment to one short line.web/storybook/stories/domain/RichChatInput.stories.tsx#L477-L477: Reduce this comment to one short line.
As per coding guidelines: “Hard rule. At most ONE short line per comment.”Source: Coding guidelines
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Team
Run ID: 71825f08-d469-460e-b09a-8e97fdb11660
⛔ Files ignored due to path filters (1)
web/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (22)
web/mobile/src/features/chat/Composer.tsxweb/oss/src/components/AgentChatSlice/components/AgentComposerDock.tsxweb/packages/agenta-chat/src/assets/filePaletteRows.tsweb/packages/agenta-chat/src/assets/index.tsweb/packages/agenta-chat/src/components/ChatComposer.tsxweb/packages/agenta-chat/src/hooks/index.tsweb/packages/agenta-chat/src/hooks/useFilePalette.tsxweb/packages/agenta-chat/tests/unit/filePaletteRows.test.tsweb/packages/agenta-entity-ui/src/drive/OriginTag.tsxweb/packages/agenta-shared/src/utils/shortcuts.tsweb/packages/agenta-ui/package.jsonweb/packages/agenta-ui/src/RichChatInput/RichChatInput.tsxweb/packages/agenta-ui/src/RichChatInput/assets/palette.tsweb/packages/agenta-ui/src/RichChatInput/assets/slashCommands.tsweb/packages/agenta-ui/src/RichChatInput/assets/slashPalette.tsxweb/packages/agenta-ui/src/RichChatInput/index.tsweb/packages/agenta-ui/src/RichChatInput/plugins/CommandPalettePlugin.tsxweb/packages/agenta-ui/src/RichChatInput/plugins/PalettePanel.tsxweb/packages/agenta-ui/src/RichChatInput/plugins/SlashCommandPlugin.tsxweb/packages/agenta-ui/tests/unit/filePaletteInsert.render.test.tsxweb/packages/agenta-ui/tests/unit/paletteRun.test.tsweb/storybook/stories/domain/RichChatInput.stories.tsx
💤 Files with no reviewable changes (1)
- web/packages/agenta-ui/src/RichChatInput/plugins/SlashCommandPlugin.tsx
Included review availability: Your plan provides up to 8 included reviews per hour; 3 remain after this review.
Railway Preview Environment
|
3fa5d9d to
f416f84
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Team
Run ID: 9aee6bf2-0de4-4b8e-a1d9-79fb8ba9f497
📒 Files selected for processing (1)
web/packages/agenta-chat/tests/unit/filePaletteRows.test.ts
Included review availability: Your plan provides up to 8 included reviews per hour; 2 remain after this review.
Typing `@` opens the same palette `/` opens, listing the session drive: recents and the root to start, type to search the whole drive, Tab into a folder, Enter to reference a file or a folder. The reference goes in as an inline-code drive path, which is what the sent message already renders as a file chip. Inline code rather than typed backticks because $convertToMarkdownString escapes a backtick in unformatted text — a plain-text path would ship escaped and never resolve. The palette is built in @agenta/chat over the drive's existing lazy listing and search, so both the desktop dock and the mobile composer get it from the one ChatComposer prop. The surfaces that run before a session exists (onboarding, the home task composer) stay off: they have no drive to reference.
The insert has to write an inline-code node: $convertToMarkdownString escapes a backtick typed as ordinary text, so a path written as plain characters would leave the composer escaped and never resolve to a file chip. The render test asserts the serialized message, and that the caret lands outside the span. The story covers the states a reviewer cannot reach by clicking a mock drive — search with the match highlighted, a folder still listing, and no matches.
…column Four things the palette got wrong against the real drive: - Every row carried a Session or Agent pill, so the scope was repeated on each line. Two group headings say it once, and a level that is all one scope needs neither. - The Agent tag was tinted, which read as a status beside the neutral Session one when the two are just halves of the same distinction. Both are quiet now, everywhere the tag appears. - The breadcrumb header drew a house icon of its own on top of the one DriveBreadcrumb already opens with. - A folder row's meta sat 6px left of a file row's: the drill-in button kept its UA padding. The footer hints wrapped to a second line on a deep path too.
- The palette was on by default. `fileMentions` is optional, so a composer that omitted it reached the hook as undefined and picked up its `enabled = true` default, opening the menu on surfaces that have no drive to reference. - `recentRows(recents, 0)` returned every row: the cap was checked after the push, and `=== limit` never tripped from above. - An insert put a space after itself even when the run already had one, so a mention placed mid-sentence left two. - A right-click selected the row under the cursor. - The folder's drill-in target only answered `mousedown`, so keyboard activation did nothing. The action moves to `click`, which both raise. - The shimmer row's width came from an inline style rather than a utility class.
316448c to
e6b4c2d
Compare
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
web/packages/agenta-ui/src/RichChatInput/plugins/CommandPalettePlugin.tsx (1)
259-259: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winKeep the drill-in run active.
replaceRun(active.trigger, "text")inserts@, which closes the run and resetscwd; allow this rewrite to skip the separator.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Team
Run ID: 00178951-9fe2-452e-8af9-bf2c8c28da86
📒 Files selected for processing (5)
web/packages/agenta-chat/src/assets/filePaletteRows.tsweb/packages/agenta-chat/src/hooks/useFilePalette.tsxweb/packages/agenta-chat/tests/unit/filePaletteRows.test.tsweb/packages/agenta-ui/src/RichChatInput/plugins/CommandPalettePlugin.tsxweb/packages/agenta-ui/src/RichChatInput/plugins/PalettePanel.tsx
Included review availability: Your plan provides up to 8 included reviews per hour; 1 remains after this review.
Closes #6139
Context
When an agent writes a file, there is no quick way to point at it in the next message. You either describe the path in prose and hope the agent resolves the right file, or re-upload a copy through the paperclip, even though the session drive already holds the artifact.
The
/palette already solves the same problem for commands. This gives@the equivalent for files.What this adds
Typing
@at a word boundary opens the same palette/opens, listing the session drive:SESSIONandAGENTwhen both scopes are present.Tab(or the row's chevron, for touch) enters a folder and clears the query so the new level lists rather than filters.Escsteps back one level per press, then closes.Enterreferences the highlighted file, or the whole folder.hey@agenta.aimid-sentence never opens it, and with no matchesEnterstill sends the message as written.The mention goes in as an inline-code drive path, which is exactly what a sent message already renders as a file chip through
chatFileRefs:That has to be an inline-code node rather than three typed backtick characters.
$convertToMarkdownStringescapes a backtick in unformatted text (MarkdownExport.exportTextFormat), so a path written as plain characters would ship as\`a/b.md\`and never resolve.Changes
One plugin, N palettes.
SlashCommandPluginbecomesCommandPalettePluginplus a presentationalPalettePanel, driven by a list ofPaletteSpecs. A second independent plugin was not an option: both would claim Enter atCOMMAND_PRIORITY_CRITICALand race by mount order even while closed, keep divergent Escape latches, and clobber each other'saria-activedescendanton the one contenteditable root./behaviour is unchanged.slashCommands.tskeeps every export as an alias layer over the new genericpalette.ts, and its unit test passes unedited. That test is the regression contract.The data lives in
@agenta/chat.useFilePalettesits there because it needs@agenta/entitiesand@agenta/entity-ui, which@agenta/uimay not import. Every visual in the palette contract is aReactNode, so the renderer stays drive-free while the hook supplies icons and the breadcrumb. Rows come from a purefilePaletteRows.tsoveruseLazyDriveTree, which already folds the agent mount underagent-files/and already swaps to the whole-tree fetch while searching.No backend change. Search reuses the client-side path the Files drawer already pays for, held back 180ms and capped at 30 rows, with
useDeferredValuekeeping typing off the scan.Both surfaces, one prop.
ChatComposergainsfileMentions, and the desktop dock andweb/mobile's composer each pass it. It is off by default so the surfaces that run before a session exists (onboarding, the home task composer) are untouched. They have no drive and would open an always-empty menu.@floating-ui/reactmoves fromdevDependenciestodependenciesin@agenta/ui. It resolved by accident before, because mobile never mounted the plugin.Tests
filePaletteInsert.render.test.tsxdrives the palette and asserts the serialized message is`a/b.md`, unescaped, with the caret left outside the code span so the next word stays plain text. This pins the escaping behaviour above.paletteRun.test.tscovers the@pattern: a path stays one run (@docs/gui), an email mid-sentence does not open it, and the dismissal latch discriminates by palette.filePaletteRows.test.tscovers implied folders, theagent-files/fold, runner-plumbing exclusions, ordering, the row cap, and subtree-scoped search.slashCommands.test.tspasses with no edits.@opens, search highlights,Tabdrills two levels into the cloned repo,Escwalks back out, and the sent message renders the file chip.RichChatInputgains aFileMentionsstory with a live palette plus the states you cannot click to (search, folder loading, empty).Known gap: a folder reference does not become a chip in the sent message.
OnDemandFileRefresolves by reading the path, which 404s on a directory, so it stays plain inline code. Correct and readable, but making it chip-able is a separatechatFileRefschange.What to QA
@in the composer. The palette lists recents and the root, grouped intoSESSIONandAGENTif both exist.Tabon a folder row. It enters the folder, the breadcrumb updates, and the query clears.Escsteps back one level, then closes.Enteron a file and send. The message renders the filename as a file chip you can click.hey@agenta.aimid-sentence. The palette must not open.@zzzzwith no matches and pressEnter. The message sends as written./m, where the folder chevron is the tap target instead ofTab./still opens the command palette, filters, and drills into/modeland/permissionsexactly as before.