fix: skip sending files not in the workding dir to the lsp

Kujtim Hoxha created

Change summary

internal/agent/coordinator.go       |  2 +-
internal/agent/tools/diagnostics.go | 24 +++++++++++++++++++++---
internal/agent/tools/edit.go        |  2 +-
internal/agent/tools/multiedit.go   |  2 +-
internal/agent/tools/view.go        |  2 +-
internal/agent/tools/write.go       |  2 +-
internal/lsp/client.go              | 24 ++++++++++++++++++++++++
7 files changed, 50 insertions(+), 8 deletions(-)

Detailed changes

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

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)

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("<result>\n%s\n</result>\n", response.Content)
-			text += getDiagnostics(params.FilePath, lspClients)
+			text += getDiagnostics(params.FilePath, lspClients, workingDir)
 			response.Content = text
 			return response, nil
 		})

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("<result>\n%s\n</result>\n", response.Content)
-			text += getDiagnostics(params.FilePath, lspClients)
+			text += getDiagnostics(params.FilePath, lspClients, workingDir)
 			response.Content = text
 			return response, nil
 		})

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</file>\n"
-			output += getDiagnostics(filePath, lspClients)
+			output += getDiagnostics(filePath, lspClients, workingDir)
 			recordFileRead(filePath)
 			return fantasy.WithResponseMetadata(
 				fantasy.NewTextResponse(output),

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("<result>\n%s\n</result>", result)
-			result += getDiagnostics(filePath, lspClients)
+			result += getDiagnostics(filePath, lspClients, workingDir)
 			return fantasy.WithResponseMetadata(fantasy.NewTextResponse(result),
 				WriteResponseMetadata{
 					Diff:      diff,

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)
+}