Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
# beans-uk55
title: '@ mention file/directory attachment in agent chat'
status: completed
type: feature
priority: normal
created_at: 2026-03-21T09:33:57Z
updated_at: 2026-03-21T09:46:22Z
---

Allow users to type @ in the agent composer to autocomplete and attach files/directories from the codebase as context. File contents are injected into the prompt sent to Claude Code.

## Summary of Changes

Implemented @-mention file/directory attachment in the agent chat composer:

### Backend
- Added `listFiles` GraphQL query that uses `git ls-files` to list tracked files, filtered by prefix, with directory deduplication (one level of depth)
- Extended `sendAgentMessage` mutation to accept `attachments: [FileAttachmentInput!]` — attached paths are prepended as context hints in the user message
- Added `FileEntry` type and `FileAttachmentInput` input to the GraphQL schema
- Added unit tests for `ListFiles` resolver

### Frontend
- Added @-detection in the composer textarea — typing `@` (preceded by whitespace or start-of-text) opens an autocomplete dropdown
- Dropdown queries the backend via `ListFiles` with debounced input (100ms)
- Keyboard navigation: ArrowUp/Down to navigate, Enter/Tab to select, Escape to close
- Selecting a file adds it as a pill below the textarea and removes the @query text
- Selecting a directory replaces the query to drill deeper (keeps dropdown open)
- Pending attachments shown as removable pills with file/folder icons
- Attachments are passed through to the GraphQL mutation via the store
9 changes: 7 additions & 2 deletions frontend/src/lib/agentChat.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
type SubagentActivity as GqlSubagentActivity,
type InteractionType,
type ImageInput,
type FileAttachmentInput,
} from './graphql/generated';

export type AgentMessageImage = GqlAgentMessageImage;
Expand All @@ -29,6 +30,7 @@ export type PendingInteraction = GqlPendingInteraction;
export type SubagentActivity = GqlSubagentActivity;
export type AgentSession = AgentSessionFieldsFragment;
export type ImageUploadInput = ImageInput;
export type FileAttachment = FileAttachmentInput;

export class AgentChatStore {
session = $state<AgentSession | null>(null);
Expand Down Expand Up @@ -104,7 +106,8 @@ export class AgentChatStore {
async sendMessage(
beanId: string,
message: string,
images?: ImageUploadInput[]
images?: ImageUploadInput[],
attachments?: FileAttachment[]
): Promise<boolean> {
this.sending = true;
this.error = null;
Expand All @@ -119,6 +122,7 @@ export class AgentChatStore {
role: AgentMessageRole.User,
content: message,
images: [],
attachments: attachments?.map(a => a.path) ?? [],
diff: null
}
]
Expand All @@ -129,7 +133,8 @@ export class AgentChatStore {
.mutation(SendAgentMessageDocument, {
beanId,
message,
images: images ?? null
images: images ?? null,
attachments: attachments?.length ? attachments : null
})
.toPromise();

Expand Down
3 changes: 2 additions & 1 deletion frontend/src/lib/components/AgentChat.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@
{systemStatus}
{subagentActivities}
{quickReplies}
onSend={(text, images) => { internalScrollTrigger++; store.sendMessage(beanId, text, images); }}
workspaceId={beanId}
onSend={(text, images, attachments) => { internalScrollTrigger++; store.sendMessage(beanId, text, images, attachments); }}
onStop={() => store.stop(beanId)}
onSetMode={setAgentMode}
onSetEffort={(effort) => store.setEffort(beanId, effort)}
Expand Down
Loading
Loading