mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 22:17:36 +00:00
fix: route list_files("memory") to DB in managed mode
The memory interceptor handled read_file/write_file for memory paths but list_files hit the filesystem, returning "Directory does not exist". Now list_files queries memory_documents from PostgreSQL via the existing ListDocuments store method. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
a9ce64981d
commit
6d9374a1a5
3 files changed
+78
-4
No files matched your search
@@ -157,6 +157,13 @@ func wireManagedExtras(
|
||||
}
|
||||
}
|
||||
}
|
||||
if listTool, ok := toolsReg.Get("list_files"); ok {
|
||||
if ia, ok := listTool.(tools.InterceptorAware); ok {
|
||||
if stores.Memory != nil {
|
||||
ia.SetMemoryInterceptor(tools.NewMemoryInterceptor(stores.Memory, workspace))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wire group writer cache for permission checks (managed mode only)
|
||||
if groupWriterCache != nil {
|
||||
|
||||
@@ -12,10 +12,20 @@ import (
|
||||
|
||||
// ListFilesTool lists files in a directory, optionally through a sandbox container.
|
||||
type ListFilesTool struct {
|
||||
workspace string
|
||||
restrict bool
|
||||
deniedPrefixes []string // path prefixes to deny access to (e.g. .goclaw)
|
||||
sandboxMgr sandbox.Manager
|
||||
workspace string
|
||||
restrict bool
|
||||
deniedPrefixes []string // path prefixes to deny access to (e.g. .goclaw)
|
||||
sandboxMgr sandbox.Manager
|
||||
contextFileIntc *ContextFileInterceptor // unused, satisfies InterceptorAware
|
||||
memIntc *MemoryInterceptor // nil = no memory routing (standalone mode)
|
||||
}
|
||||
|
||||
func (t *ListFilesTool) SetContextFileInterceptor(intc *ContextFileInterceptor) {
|
||||
t.contextFileIntc = intc
|
||||
}
|
||||
|
||||
func (t *ListFilesTool) SetMemoryInterceptor(intc *MemoryInterceptor) {
|
||||
t.memIntc = intc
|
||||
}
|
||||
|
||||
// DenyPaths adds path prefixes that list_files must reject/filter.
|
||||
@@ -54,6 +64,19 @@ func (t *ListFilesTool) Execute(ctx context.Context, args map[string]interface{}
|
||||
path = "."
|
||||
}
|
||||
|
||||
// Virtual FS: route memory directory listing to DB (managed mode)
|
||||
if t.memIntc != nil {
|
||||
if listing, handled, err := t.memIntc.ListFiles(ctx, path); handled {
|
||||
if err != nil {
|
||||
return ErrorResult(fmt.Sprintf("failed to list memory files: %v", err))
|
||||
}
|
||||
if listing == "" {
|
||||
return SilentResult("No memory files stored yet")
|
||||
}
|
||||
return SilentResult(listing)
|
||||
}
|
||||
}
|
||||
|
||||
// Sandbox routing (sandboxKey from ctx — thread-safe)
|
||||
sandboxKey := ToolSandboxKeyFromCtx(ctx)
|
||||
if t.sandboxMgr != nil && sandboxKey != "" {
|
||||
|
||||
@@ -2,6 +2,7 @@ package tools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -12,6 +13,20 @@ import (
|
||||
"github.com/nextlevelbuilder/goclaw/internal/store"
|
||||
)
|
||||
|
||||
// isMemoryDir checks if a path refers to the memory directory itself.
|
||||
// Handles "memory", "./memory", "/workspace/memory" etc.
|
||||
func isMemoryDir(path, workspace string) bool {
|
||||
clean := filepath.Clean(path)
|
||||
if clean == "memory" {
|
||||
return true
|
||||
}
|
||||
if workspace != "" && filepath.IsAbs(clean) {
|
||||
expected := filepath.Join(filepath.Clean(workspace), "memory")
|
||||
return clean == expected
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isMemoryPath checks if a path refers to a memory file (MEMORY.md, memory.md, memory/*).
|
||||
// Handles both relative and absolute paths (when workspace is provided).
|
||||
func isMemoryPath(path, workspace string) bool {
|
||||
@@ -124,3 +139,32 @@ func (m *MemoryInterceptor) WriteFile(ctx context.Context, path, content string)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// ListFiles lists memory documents from the DB when path is the memory directory.
|
||||
// Returns (listing, true, nil) if handled, or ("", false, nil) if not a memory path.
|
||||
func (m *MemoryInterceptor) ListFiles(ctx context.Context, path string) (string, bool, error) {
|
||||
if !isMemoryDir(path, m.workspace) {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
agentID := store.AgentIDFromContext(ctx)
|
||||
if agentID == uuid.Nil {
|
||||
return "", false, nil
|
||||
}
|
||||
|
||||
userID := store.UserIDFromContext(ctx)
|
||||
docs, err := m.memStore.ListDocuments(ctx, agentID.String(), userID)
|
||||
if err != nil {
|
||||
return "", true, err
|
||||
}
|
||||
|
||||
if len(docs) == 0 {
|
||||
return "", true, nil
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
for _, doc := range docs {
|
||||
fmt.Fprintf(&sb, "[FILE] %s\n", doc.Path)
|
||||
}
|
||||
return sb.String(), true, nil
|
||||
}
|
||||
Reference in new issue
Block a user