diff --git a/internal/agent/coordinator.go b/internal/agent/coordinator.go index 7ce37758897da42cd8515c94c26b9e24add6ea1e..07f698e3633c87a6d2316fab35665c55fa55b97f 100644 --- a/internal/agent/coordinator.go +++ b/internal/agent/coordinator.go @@ -388,7 +388,7 @@ func (c *coordinator) buildTools(ctx context.Context, agent config.Agent) ([]fan ) if len(c.cfg.LSP) > 0 { - allTools = append(allTools, tools.NewDiagnosticsTool(c.lspClients), tools.NewReferencesTool(c.lspClients)) + allTools = append(allTools, tools.NewDiagnosticsTool(c.lspClients, c.cfg.WorkingDir()), tools.NewReferencesTool(c.lspClients)) } var filteredTools []fantasy.AgentTool diff --git a/internal/agent/tools/diagnostics.go b/internal/agent/tools/diagnostics.go index de1fc9a13c95296bb8e637f56b4d0abc4f25c34b..a4d3bc5a00d25b7cd73464cd92f845d4a3b45936 100644 --- a/internal/agent/tools/diagnostics.go +++ b/internal/agent/tools/diagnostics.go @@ -5,6 +5,7 @@ import ( _ "embed" "fmt" "log/slog" + "path/filepath" "sort" "strings" "time" @@ -24,7 +25,7 @@ const DiagnosticsToolName = "lsp_diagnostics" //go:embed diagnostics.md var diagnosticsDescription []byte -func NewDiagnosticsTool(lspClients *csync.Map[string, *lsp.Client]) fantasy.AgentTool { +func NewDiagnosticsTool(lspClients *csync.Map[string, *lsp.Client], workingDir string) fantasy.AgentTool { return fantasy.NewAgentTool( DiagnosticsToolName, string(diagnosticsDescription), @@ -33,7 +34,7 @@ func NewDiagnosticsTool(lspClients *csync.Map[string, *lsp.Client]) fantasy.Agen return fantasy.NewTextErrorResponse("no LSP clients available"), nil } notifyLSPs(ctx, lspClients, params.FilePath) - output := getDiagnostics(params.FilePath, lspClients) + output := getDiagnostics(params.FilePath, lspClients, workingDir) return fantasy.NewTextResponse(output), nil }) } @@ -52,10 +53,16 @@ func notifyLSPs(ctx context.Context, lsps *csync.Map[string, *lsp.Client], filep } } -func getDiagnostics(filePath string, lsps *csync.Map[string, *lsp.Client]) string { +func getDiagnostics(filePath string, lsps *csync.Map[string, *lsp.Client], workingDir string) string { fileDiagnostics := []string{} projectDiagnostics := []string{} + absWd, err := filepath.Abs(workingDir) + if err != nil { + slog.Error("Failed to resolve working directory", "error", err) + return "Error: Failed to resolve working directory" + } + for lspName, client := range lsps.Seq2() { for location, diags := range client.GetDiagnostics() { path, err := location.Path() @@ -63,6 +70,17 @@ func getDiagnostics(filePath string, lsps *csync.Map[string, *lsp.Client]) strin slog.Error("Failed to convert diagnostic location URI to path", "uri", location, "error", err) continue } + + // Skip diagnostics for files outside the working directory. + absPath, err := filepath.Abs(path) + if err != nil { + slog.Debug("Failed to resolve diagnostic path", "path", path, "error", err) + continue + } + if !strings.HasPrefix(absPath, absWd) { + continue + } + isCurrentFile := path == filePath for _, diag := range diags { formattedDiag := formatDiagnostic(path, diag, lspName) diff --git a/internal/agent/tools/edit.go b/internal/agent/tools/edit.go index ccc115be2aa20113d8e3cbf91f1e644e90ce1b98..31dc77aa25360e82dd1010407b6719114ea16413 100644 --- a/internal/agent/tools/edit.go +++ b/internal/agent/tools/edit.go @@ -89,7 +89,7 @@ func NewEditTool(lspClients *csync.Map[string, *lsp.Client], permissions permiss notifyLSPs(ctx, lspClients, params.FilePath) text := fmt.Sprintf("\n%s\n\n", response.Content) - text += getDiagnostics(params.FilePath, lspClients) + text += getDiagnostics(params.FilePath, lspClients, workingDir) response.Content = text return response, nil }) diff --git a/internal/agent/tools/multiedit.go b/internal/agent/tools/multiedit.go index c4a3aa200c8325db87a6bb8d860cade1a8e7025d..39253d3fbd4045192e414ebf8be5b3b533cfcea4 100644 --- a/internal/agent/tools/multiedit.go +++ b/internal/agent/tools/multiedit.go @@ -101,7 +101,7 @@ func NewMultiEditTool(lspClients *csync.Map[string, *lsp.Client], permissions pe // Wait for LSP diagnostics and add them to the response text := fmt.Sprintf("\n%s\n\n", response.Content) - text += getDiagnostics(params.FilePath, lspClients) + text += getDiagnostics(params.FilePath, lspClients, workingDir) response.Content = text return response, nil }) diff --git a/internal/agent/tools/view.go b/internal/agent/tools/view.go index aacd4cab23231c1b27f3d2589578e81e29cf6ed3..5f1cb287cbbab81af539a02136513740f992d739 100644 --- a/internal/agent/tools/view.go +++ b/internal/agent/tools/view.go @@ -185,7 +185,7 @@ func NewViewTool(lspClients *csync.Map[string, *lsp.Client], permissions permiss params.Offset+len(strings.Split(content, "\n"))) } output += "\n\n" - output += getDiagnostics(filePath, lspClients) + output += getDiagnostics(filePath, lspClients, workingDir) recordFileRead(filePath) return fantasy.WithResponseMetadata( fantasy.NewTextResponse(output), diff --git a/internal/agent/tools/write.go b/internal/agent/tools/write.go index 82684001372ee45b1d71fa34384e6e6c7a92db25..7bc67f0f637f2c1274844206443e9eaf2444d426 100644 --- a/internal/agent/tools/write.go +++ b/internal/agent/tools/write.go @@ -163,7 +163,7 @@ func NewWriteTool(lspClients *csync.Map[string, *lsp.Client], permissions permis result := fmt.Sprintf("File successfully written: %s", filePath) result = fmt.Sprintf("\n%s\n", result) - result += getDiagnostics(filePath, lspClients) + result += getDiagnostics(filePath, lspClients, workingDir) return fantasy.WithResponseMetadata(fantasy.NewTextResponse(result), WriteResponseMetadata{ Diff: diff, diff --git a/internal/lsp/client.go b/internal/lsp/client.go index 7d914d9a52ce75f621715273e8f6b9588aa912b7..4556293e0e47334b798039d16726ac67abc13f4e 100644 --- a/internal/lsp/client.go +++ b/internal/lsp/client.go @@ -42,6 +42,9 @@ type Client struct { // Server state serverState atomic.Value + + // Working directory + workingDir string } // New creates a new LSP client using the powernap implementation. @@ -92,6 +95,7 @@ func New(ctx context.Context, name string, config config.LSPConfig, resolver con diagnostics: csync.NewVersionedMap[protocol.DocumentURI, []protocol.Diagnostic](), openFiles: csync.NewMap[string, *OpenFileInfo](), config: config, + workingDir: workDir, } // Initialize server state @@ -265,6 +269,11 @@ func (c *Client) OpenFile(ctx context.Context, filepath string) error { return nil } + // Skip files outside the current working directory. + if !c.isFileInWorkingDir(filepath) { + return nil + } + uri := string(protocol.URIFromPath(filepath)) if _, exists := c.openFiles.Get(uri); exists { @@ -470,3 +479,18 @@ func HasRootMarkers(dir string, rootMarkers []string) bool { } return false } + +// isFileInWorkingDir checks if the given file path is within the client's working directory. +func (c *Client) isFileInWorkingDir(filePath string) bool { + absFilepath, err := filepath.Abs(filePath) + if err != nil { + return false + } + + absWd, err := filepath.Abs(c.workingDir) + if err != nil { + return false + } + + return strings.HasPrefix(absFilepath, absWd) +}