add memory_get tool and docker-based make test target

This commit is contained in:
DBT
2026-02-23 09:59:57 +00:00
parent 756bfe2302
commit 0996c51978
3 changed files with 130 additions and 0 deletions

View File

@@ -161,6 +161,12 @@ deps:
run: build
@$(BUILD_DIR)/$(BINARY_NAME) $(ARGS)
## test: Build and compile-check in Docker (Dockerfile.test)
test:
@echo "Running Docker compile test..."
docker build -f Dockerfile.test -t clawgo:test .
@echo "Docker compile test passed"
## help: Show this help message
help:
@echo "clawgo Makefile"

View File

@@ -80,6 +80,7 @@ func NewAgentLoop(cfg *config.Config, msgBus *bus.MessageBus, provider providers
// Register memory tools
memorySearchTool := tools.NewMemorySearchTool(workspace)
toolsRegistry.Register(memorySearchTool)
toolsRegistry.Register(tools.NewMemoryGetTool(workspace))
toolsRegistry.Register(tools.NewMemoryWriteTool(workspace))
// Register parallel execution tool (leveraging Go's concurrency)

123
pkg/tools/memory_get.go Normal file
View File

@@ -0,0 +1,123 @@
package tools
import (
"bufio"
"context"
"fmt"
"os"
"path/filepath"
"strings"
)
type MemoryGetTool struct {
workspace string
}
func NewMemoryGetTool(workspace string) *MemoryGetTool {
return &MemoryGetTool{workspace: workspace}
}
func (t *MemoryGetTool) Name() string {
return "memory_get"
}
func (t *MemoryGetTool) Description() string {
return "Read safe snippets from MEMORY.md or memory/*.md using optional line range."
}
func (t *MemoryGetTool) Parameters() map[string]interface{} {
return map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"path": map[string]interface{}{
"type": "string",
"description": "Relative path to MEMORY.md or memory/*.md",
},
"from": map[string]interface{}{
"type": "integer",
"description": "Start line (1-indexed)",
"default": 1,
},
"lines": map[string]interface{}{
"type": "integer",
"description": "Number of lines to read",
"default": 80,
},
},
"required": []string{"path"},
}
}
func (t *MemoryGetTool) Execute(ctx context.Context, args map[string]interface{}) (string, error) {
rawPath, _ := args["path"].(string)
rawPath = strings.TrimSpace(rawPath)
if rawPath == "" {
return "", fmt.Errorf("path is required")
}
from := 1
if v, ok := args["from"].(float64); ok && int(v) > 0 {
from = int(v)
}
lines := 80
if v, ok := args["lines"].(float64); ok && int(v) > 0 {
lines = int(v)
}
if lines > 500 {
lines = 500
}
fullPath := filepath.Clean(filepath.Join(t.workspace, rawPath))
if !t.isAllowedMemoryPath(fullPath) {
return "", fmt.Errorf("path not allowed: %s", rawPath)
}
f, err := os.Open(fullPath)
if err != nil {
return "", err
}
defer f.Close()
scanner := bufio.NewScanner(f)
lineNo := 0
end := from + lines - 1
var out strings.Builder
for scanner.Scan() {
lineNo++
if lineNo < from {
continue
}
if lineNo > end {
break
}
out.WriteString(fmt.Sprintf("%d: %s\n", lineNo, scanner.Text()))
}
if err := scanner.Err(); err != nil {
return "", err
}
content := strings.TrimSpace(out.String())
if content == "" {
return fmt.Sprintf("No content in range for %s (from=%d, lines=%d)", rawPath, from, lines), nil
}
rel, _ := filepath.Rel(t.workspace, fullPath)
return fmt.Sprintf("Source: %s#L%d-L%d\n%s", rel, from, end, content), nil
}
func (t *MemoryGetTool) isAllowedMemoryPath(fullPath string) bool {
workspaceMemory := filepath.Join(t.workspace, "MEMORY.md")
if fullPath == workspaceMemory {
return true
}
memoryDir := filepath.Join(t.workspace, "memory")
rel, err := filepath.Rel(memoryDir, fullPath)
if err != nil {
return false
}
if strings.HasPrefix(rel, "..") {
return false
}
return strings.HasSuffix(strings.ToLower(fullPath), ".md")
}