mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-27 07:16:19 +09:00
fix: ensure unifying on hardcoded /data mount path
This commit is contained in:
@@ -334,10 +334,7 @@ func (m *Manager) dataRoot() string {
|
||||
}
|
||||
|
||||
func (m *Manager) dataMount() string {
|
||||
if m.cfg.DataMount == "" {
|
||||
return config.DefaultDataMount
|
||||
}
|
||||
return m.cfg.DataMount
|
||||
return config.DefaultDataMount
|
||||
}
|
||||
|
||||
func (m *Manager) imageRef() string {
|
||||
|
||||
@@ -2,6 +2,7 @@ package container
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strings"
|
||||
|
||||
@@ -54,6 +55,10 @@ func NewExecutor(log *slog.Logger, execRunner ExecRunner, execWorkDir string) *E
|
||||
|
||||
// ListTools returns read, write, list, edit, and exec tool descriptors.
|
||||
func (p *Executor) ListTools(ctx context.Context, session mcpgw.ToolSessionContext) ([]mcpgw.ToolDescriptor, error) {
|
||||
wd := p.execWorkDir
|
||||
if wd == "" {
|
||||
wd = defaultExecWorkDir
|
||||
}
|
||||
return []mcpgw.ToolDescriptor{
|
||||
{
|
||||
Name: toolRead,
|
||||
@@ -61,7 +66,7 @@ func (p *Executor) ListTools(ctx context.Context, session mcpgw.ToolSessionConte
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"path": map[string]any{"type": "string", "description": "file path (relative to /data or absolute inside container)"},
|
||||
"path": map[string]any{"type": "string", "description": fmt.Sprintf("file path (relative to %s or absolute inside container)", wd)},
|
||||
},
|
||||
"required": []string{"path"},
|
||||
},
|
||||
@@ -72,7 +77,7 @@ func (p *Executor) ListTools(ctx context.Context, session mcpgw.ToolSessionConte
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"path": map[string]any{"type": "string", "description": "file path (relative to /data or absolute inside container)"},
|
||||
"path": map[string]any{"type": "string", "description": fmt.Sprintf("file path (relative to %s or absolute inside container)", wd)},
|
||||
"content": map[string]any{"type": "string", "description": "file content"},
|
||||
},
|
||||
"required": []string{"path", "content"},
|
||||
@@ -84,7 +89,7 @@ func (p *Executor) ListTools(ctx context.Context, session mcpgw.ToolSessionConte
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"path": map[string]any{"type": "string", "description": "directory path (relative to /data or absolute inside container)"},
|
||||
"path": map[string]any{"type": "string", "description": fmt.Sprintf("directory path (relative to %s or absolute inside container)", wd)},
|
||||
"recursive": map[string]any{"type": "boolean", "description": "list recursively"},
|
||||
},
|
||||
"required": []string{"path"},
|
||||
@@ -96,7 +101,7 @@ func (p *Executor) ListTools(ctx context.Context, session mcpgw.ToolSessionConte
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
"path": map[string]any{"type": "string", "description": "file path (relative to /data or absolute inside container)"},
|
||||
"path": map[string]any{"type": "string", "description": fmt.Sprintf("file path (relative to %s or absolute inside container)", wd)},
|
||||
"old_text": map[string]any{"type": "string", "description": "exact text to find"},
|
||||
"new_text": map[string]any{"type": "string", "description": "replacement text"},
|
||||
},
|
||||
@@ -105,7 +110,7 @@ func (p *Executor) ListTools(ctx context.Context, session mcpgw.ToolSessionConte
|
||||
},
|
||||
{
|
||||
Name: toolExec,
|
||||
Description: "Execute a command in the bot container. Runs in the bot's data directory (/data) by default.",
|
||||
Description: fmt.Sprintf("Execute a command in the bot container. Runs in the bot's data directory (%s) by default.", wd),
|
||||
InputSchema: map[string]any{
|
||||
"type": "object",
|
||||
"properties": map[string]any{
|
||||
@@ -115,7 +120,7 @@ func (p *Executor) ListTools(ctx context.Context, session mcpgw.ToolSessionConte
|
||||
},
|
||||
"work_dir": map[string]any{
|
||||
"type": "string",
|
||||
"description": "Working directory inside the container (default: /data)",
|
||||
"description": fmt.Sprintf("Working directory inside the container (default: %s)", wd),
|
||||
},
|
||||
},
|
||||
"required": []string{"command"},
|
||||
@@ -126,12 +131,15 @@ func (p *Executor) ListTools(ctx context.Context, session mcpgw.ToolSessionConte
|
||||
|
||||
// normalizePath converts paths that the LLM may send as /data/... into relative
|
||||
// paths under the working directory. e.g. /data/test.txt -> test.txt, /data -> .
|
||||
func normalizePath(path string) string {
|
||||
func (p *Executor) normalizePath(path string) string {
|
||||
path = strings.TrimSpace(path)
|
||||
if path == "" {
|
||||
return path
|
||||
}
|
||||
const prefix = "/data"
|
||||
prefix := p.execWorkDir
|
||||
if prefix == "" {
|
||||
prefix = defaultExecWorkDir
|
||||
}
|
||||
if path == prefix {
|
||||
return "."
|
||||
}
|
||||
@@ -150,7 +158,7 @@ func (p *Executor) CallTool(ctx context.Context, session mcpgw.ToolSessionContex
|
||||
|
||||
switch toolName {
|
||||
case toolRead:
|
||||
filePath := normalizePath(mcpgw.StringArg(arguments, "path"))
|
||||
filePath := p.normalizePath(mcpgw.StringArg(arguments, "path"))
|
||||
if filePath == "" {
|
||||
return mcpgw.BuildToolErrorResult("path is required"), nil
|
||||
}
|
||||
@@ -163,7 +171,7 @@ func (p *Executor) CallTool(ctx context.Context, session mcpgw.ToolSessionContex
|
||||
}), nil
|
||||
|
||||
case toolWrite:
|
||||
filePath := normalizePath(mcpgw.StringArg(arguments, "path"))
|
||||
filePath := p.normalizePath(mcpgw.StringArg(arguments, "path"))
|
||||
content := mcpgw.StringArg(arguments, "content")
|
||||
if filePath == "" {
|
||||
return mcpgw.BuildToolErrorResult("path is required"), nil
|
||||
@@ -174,7 +182,7 @@ func (p *Executor) CallTool(ctx context.Context, session mcpgw.ToolSessionContex
|
||||
return mcpgw.BuildToolSuccessResult(map[string]any{"ok": true}), nil
|
||||
|
||||
case toolList:
|
||||
dirPath := normalizePath(mcpgw.StringArg(arguments, "path"))
|
||||
dirPath := p.normalizePath(mcpgw.StringArg(arguments, "path"))
|
||||
if dirPath == "" {
|
||||
dirPath = "."
|
||||
}
|
||||
@@ -196,7 +204,7 @@ func (p *Executor) CallTool(ctx context.Context, session mcpgw.ToolSessionContex
|
||||
return mcpgw.BuildToolSuccessResult(map[string]any{"path": dirPath, "entries": entriesMaps}), nil
|
||||
|
||||
case toolEdit:
|
||||
filePath := normalizePath(mcpgw.StringArg(arguments, "path"))
|
||||
filePath := p.normalizePath(mcpgw.StringArg(arguments, "path"))
|
||||
oldText := mcpgw.StringArg(arguments, "old_text")
|
||||
newText := mcpgw.StringArg(arguments, "new_text")
|
||||
if filePath == "" || oldText == "" {
|
||||
|
||||
@@ -227,8 +227,9 @@ func TestNormalizePath(t *testing.T) {
|
||||
{"", ""},
|
||||
{".", "."},
|
||||
}
|
||||
exec := &Executor{execWorkDir: "/data"}
|
||||
for _, tt := range tests {
|
||||
got := normalizePath(tt.in)
|
||||
got := exec.normalizePath(tt.in)
|
||||
if got != tt.want {
|
||||
t.Errorf("normalizePath(%q) = %q, want %q", tt.in, got, tt.want)
|
||||
}
|
||||
|
||||
@@ -392,7 +392,10 @@ func (p *Executor) resolveAttachmentRef(ctx context.Context, botID, ref, attType
|
||||
}
|
||||
|
||||
// Container media path — resolve via asset storage.
|
||||
const mediaMarker = "/data/media/"
|
||||
mediaMarker := filepath.Join("/data", "media")
|
||||
if !strings.HasSuffix(mediaMarker, "/") {
|
||||
mediaMarker += "/"
|
||||
}
|
||||
if idx := strings.Index(ref, mediaMarker); idx >= 0 && p.assetResolver != nil {
|
||||
storageKey := ref[idx+len(mediaMarker):]
|
||||
asset, err := p.assetResolver.GetByStorageKey(ctx, botID, storageKey)
|
||||
@@ -404,8 +407,11 @@ func (p *Executor) resolveAttachmentRef(ctx context.Context, botID, ref, attType
|
||||
}
|
||||
}
|
||||
|
||||
// Other container /data/ path — ingest into media store first.
|
||||
const dataPrefix = "/data/"
|
||||
// Other container data mount path — ingest into media store first.
|
||||
dataPrefix := "/data"
|
||||
if !strings.HasSuffix(dataPrefix, "/") {
|
||||
dataPrefix += "/"
|
||||
}
|
||||
if strings.HasPrefix(ref, dataPrefix) && p.assetResolver != nil {
|
||||
asset, err := p.assetResolver.IngestContainerFile(ctx, botID, ref)
|
||||
if err == nil {
|
||||
|
||||
@@ -85,7 +85,7 @@ func TestSourceListToolsIncludesSSETools(t *testing.T) {
|
||||
if len(tools) != 1 {
|
||||
t.Fatalf("expected 1 tool, got %d", len(tools))
|
||||
}
|
||||
if tools[0].Name != "remote_sse.search" {
|
||||
if tools[0].Name != "remote_sse_search" {
|
||||
t.Fatalf("unexpected tool alias: %s", tools[0].Name)
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,7 @@ func TestSourceCallToolRoutesToSSEConnection(t *testing.T) {
|
||||
}
|
||||
source := NewSource(slog.Default(), gateway, lister)
|
||||
|
||||
result, err := source.CallTool(context.Background(), mcpgw.ToolSessionContext{BotID: "bot-1"}, "remote_sse.search", map[string]any{"query": "hello"})
|
||||
result, err := source.CallTool(context.Background(), mcpgw.ToolSessionContext{BotID: "bot-1"}, "remote_sse_search", map[string]any{"query": "hello"})
|
||||
if err != nil {
|
||||
t.Fatalf("call tool failed: %v", err)
|
||||
}
|
||||
|
||||
@@ -294,10 +294,7 @@ func (m *Manager) buildVersionSpec(botID string) (ctr.ContainerSpec, error) {
|
||||
if err != nil {
|
||||
return ctr.ContainerSpec{}, err
|
||||
}
|
||||
dataMount := m.cfg.DataMount
|
||||
if dataMount == "" {
|
||||
dataMount = config.DefaultDataMount
|
||||
}
|
||||
dataMount := config.DefaultDataMount
|
||||
resolvPath, err := ctr.ResolveConfSource(dataDir)
|
||||
if err != nil {
|
||||
return ctr.ContainerSpec{}, err
|
||||
@@ -351,11 +348,7 @@ func (m *Manager) ensureDBRecords(ctx context.Context, botID, containerID, runti
|
||||
return pgtype.UUID{}, err
|
||||
}
|
||||
|
||||
containerPath := m.cfg.DataMount
|
||||
if containerPath == "" {
|
||||
containerPath = config.DefaultDataMount
|
||||
}
|
||||
|
||||
containerPath := config.DefaultDataMount
|
||||
if err := m.queries.UpsertContainer(ctx, dbsqlc.UpsertContainerParams{
|
||||
BotID: botUUID,
|
||||
ContainerID: containerID,
|
||||
|
||||
Reference in New Issue
Block a user