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