feat: implement task plans feature (#131)

This commit is contained in:
João Salavisa
2026-01-31 00:51:17 +00:00
committed by GitHub
parent ecf4ffedf8
commit 6eb24006f9
64 changed files with 3447 additions and 736 deletions
@@ -1,152 +0,0 @@
# File Reference Feature Implementation Plan
## Overview
Implement file referencing in chat input with `@filename` autocomplete. When a user types `@`, they should see autocomplete suggestions for files in the workspace. Selected files are passed as **path references** in the prompt - the agent can then read them from its workspace using its own tools.
**Key insight**: The agent runs in the workspace where files exist. We don't need to embed file contents - just tell the agent which files the user is referring to. The agent can read them itself.
## Requirements Analysis
1. **Frontend: `@filename` Autocomplete**
- Detect `@` prefix followed by path characters in chat input
- Debounce search requests (only search when user stops typing ~300ms)
- Call backend to search/filter files in the workspace
- Display matching files as autocomplete options
- Insert selected file reference into the chat input (keep as `@path/to/file.ts`)
2. **Backend: File Search Endpoint**
- New WebSocket action `workspace.files.search` for searching files
- Accept session_id (to route to correct agentctl) and query string
- Return matching file paths (fuzzy match)
3. **Prompt Format**
- Keep `@filepath` references in the message as-is
- Agent sees: "Look at @src/main.ts and fix the bug"
- Agent uses its tools to read the file when needed
## Architecture Flow
```
User types @main.ts → Frontend debounces 300ms → WS request workspace.files.search
↓
Backend routes to agentctl
↓
agentctl searches worktree (git ls-files)
↓
Returns matching files
↓
Frontend shows autocomplete
↓
User selects file → Insert @src/main.ts in input
↓
User submits → message.add with content "Look at @src/main.ts"
↓
Agent receives prompt, reads file using its tools
```
## Files to Modify/Create
### Backend (Go)
1. **`apps/backend/pkg/websocket/actions.go`**
- Add `ActionWorkspaceFilesSearch = "workspace.files.search"`
2. **`apps/backend/internal/agentctl/server/process/workspace_tracker.go`**
- Add `SearchFiles(query string, limit int) ([]string, error)` method
- Use fuzzy/substring matching on cached file list from `git ls-files`
3. **`apps/backend/internal/agentctl/server/api/workspace.go`**
- Add `handleFileSearch` HTTP endpoint: `GET /api/v1/workspace/search?q=&limit=`
4. **`apps/backend/internal/agentctl/client/client.go`**
- Add `SearchFiles(ctx, query, limit) ([]string, error)` method
5. **`apps/backend/internal/agent/handlers/workspace_file_handlers.go`**
- Add `wsSearchFiles` handler for `workspace.files.search`
6. **`apps/backend/internal/agentctl/types/streams/file.go`**
- Add `FileSearchRequest` and `FileSearchResponse` types
### Frontend (TypeScript)
1. **`apps/web/hooks/use-chat-completions.ts`**
- Add file path completion alongside prompt completions
- Accept sessionId prop for file search context
- Use async completion source with debouncing
2. **`apps/web/lib/ws/workspace-files.ts`**
- Add `searchWorkspaceFiles(client, sessionId, query, limit)` function
3. **`apps/web/lib/types/backend.ts`**
- Add `FileSearchResponse` type
4. **`apps/web/components/task/task-chat-input.tsx`**
- Pass sessionId to useChatCompletions hook
5. **`apps/web/components/task/task-chat-panel.tsx`**
- Pass sessionId down to TaskChatInput
## Implementation Approach
### Phase 1: Backend File Search
1. Add `FileSearchResponse` type in `types/streams/file.go`
2. Add `SearchFiles` method in `workspace_tracker.go` (fuzzy match on git ls-files)
3. Add `handleFileSearch` HTTP endpoint in `workspace.go`
4. Add `SearchFiles` client method in `client.go`
5. Add WS action constant in `actions.go`
6. Add `wsSearchFiles` handler in `workspace_file_handlers.go`
### Phase 2: Frontend Autocomplete
1. Add `searchWorkspaceFiles` function in `workspace-files.ts`
2. Add `FileSearchResponse` type in `backend.ts`
3. Extend `useChatCompletions` to accept sessionId and add file completions
4. Update `TaskChatInput` to receive and pass sessionId
5. Update `TaskChatPanel` to pass sessionId to input
## Fuzzy Search Algorithm
Simple substring matching with scoring:
- Exact filename match: highest score
- Filename contains query: high score
- Path contains query: medium score
- Sort by score, return top N results
```go
func (wt *WorkspaceTracker) SearchFiles(query string, limit int) []string {
query = strings.ToLower(query)
var matches []scoredMatch
for _, file := range wt.currentFiles.Files {
path := strings.ToLower(file.Path)
name := filepath.Base(path)
score := 0
if name == query { score = 100 }
else if strings.Contains(name, query) { score = 50 }
else if strings.Contains(path, query) { score = 25 }
if score > 0 {
matches = append(matches, scoredMatch{path: file.Path, score: score})
}
}
sort.Slice(matches, func(i, j int) bool { return matches[i].score > matches[j].score })
// Return top `limit` results
}
```
## Risks & Considerations
1. **Performance**: Large repos could have many files. Solutions:
- Limit search results (default 20)
- Cache file list on workspace tracker (already done via git ls-files)
- Client-side debouncing (300ms)
2. **Session Requirement**: File search requires an active session. Solutions:
- Only show file completions when sessionId is available
- Gracefully return empty results if no session
3. **Concurrent Searches**: User typing fast could queue many searches. Solutions:
- Debouncing handles this naturally
- Each new search supersedes previous results