mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-09-10 06:20:43 +00:00
fix(sandbox): support write_file append in FsBridge (#650)
- Add appendMode bool param to FsBridge.WriteFile (>> vs >) - Pass appendMode through sandbox path in WriteFileTool - EditTool always uses overwrite mode (false) Closes #650
This commit is contained in:
@@ -50,8 +50,9 @@ func (b *FsBridge) ReadFile(ctx context.Context, path string) (string, error) {
|
||||
}
|
||||
|
||||
// WriteFile writes content to a file inside the container, creating directories as needed.
|
||||
// When append is true, content is appended (shell >>); otherwise the file is overwritten (shell >).
|
||||
// Matching TS FsBridge.writeFile().
|
||||
func (b *FsBridge) WriteFile(ctx context.Context, path, content string) error {
|
||||
func (b *FsBridge) WriteFile(ctx context.Context, path, content string, appendMode bool) error {
|
||||
resolved := b.resolvePath(path)
|
||||
|
||||
// Create parent directory
|
||||
@@ -60,8 +61,12 @@ func (b *FsBridge) WriteFile(ctx context.Context, path, content string) error {
|
||||
_, _, _, _ = b.dockerExec(ctx, nil, "mkdir", "-p", dir)
|
||||
}
|
||||
|
||||
redir := ">"
|
||||
if appendMode {
|
||||
redir = ">>"
|
||||
}
|
||||
// Write content via stdin pipe
|
||||
_, stderr, exitCode, err := b.dockerExec(ctx, []byte(content), "sh", "-c", fmt.Sprintf("cat > %q", resolved))
|
||||
_, stderr, exitCode, err := b.dockerExec(ctx, []byte(content), "sh", "-c", fmt.Sprintf("cat %s %q", redir, resolved))
|
||||
if err != nil {
|
||||
return fmt.Errorf("fsbridge write: %w", err)
|
||||
}
|
||||
|
||||
@@ -14,13 +14,13 @@ import (
|
||||
// EditTool performs search-and-replace edits on files.
|
||||
// Supports context file interceptor and sandbox routing.
|
||||
type EditTool struct {
|
||||
workspace string
|
||||
restrict bool
|
||||
deniedPrefixes []string // path prefixes to deny access to (e.g. .goclaw)
|
||||
sandboxMgr sandbox.Manager
|
||||
contextFileIntc *ContextFileInterceptor
|
||||
memIntc *MemoryInterceptor
|
||||
permStore store.ConfigPermissionStore // nil = no group write restriction
|
||||
workspace string
|
||||
restrict bool
|
||||
deniedPrefixes []string // path prefixes to deny access to (e.g. .goclaw)
|
||||
sandboxMgr sandbox.Manager
|
||||
contextFileIntc *ContextFileInterceptor
|
||||
memIntc *MemoryInterceptor
|
||||
permStore store.ConfigPermissionStore // nil = no group write restriction
|
||||
}
|
||||
|
||||
// DenyPaths adds path prefixes that edit must reject.
|
||||
@@ -215,7 +215,7 @@ func (t *EditTool) executeInSandbox(ctx context.Context, path, oldStr, newStr st
|
||||
return result
|
||||
}
|
||||
|
||||
if err := bridge.WriteFile(ctx, containerPath, newContent); err != nil {
|
||||
if err := bridge.WriteFile(ctx, containerPath, newContent, false); err != nil {
|
||||
return ErrorResult(fmt.Sprintf("failed to write file: %v", err) + MaybeFsBridgeHint(err))
|
||||
}
|
||||
|
||||
|
||||
@@ -13,14 +13,14 @@ import (
|
||||
|
||||
// WriteFileTool writes content to a file, optionally through a sandbox container.
|
||||
type WriteFileTool struct {
|
||||
workspace string
|
||||
restrict bool
|
||||
deniedPrefixes []string // path prefixes to deny access to (e.g. .goclaw)
|
||||
sandboxMgr sandbox.Manager
|
||||
contextFileIntc *ContextFileInterceptor // nil = no virtual FS routing
|
||||
memIntc *MemoryInterceptor // nil = no memory routing
|
||||
permStore store.ConfigPermissionStore // nil = no group write restriction
|
||||
workspaceIntc *WorkspaceInterceptor // nil = no team workspace validation
|
||||
workspace string
|
||||
restrict bool
|
||||
deniedPrefixes []string // path prefixes to deny access to (e.g. .goclaw)
|
||||
sandboxMgr sandbox.Manager
|
||||
contextFileIntc *ContextFileInterceptor // nil = no virtual FS routing
|
||||
memIntc *MemoryInterceptor // nil = no memory routing
|
||||
permStore store.ConfigPermissionStore // nil = no group write restriction
|
||||
workspaceIntc *WorkspaceInterceptor // nil = no team workspace validation
|
||||
}
|
||||
|
||||
// DenyPaths adds path prefixes that write_file must reject.
|
||||
@@ -148,7 +148,7 @@ func (t *WriteFileTool) Execute(ctx context.Context, args map[string]any) *Resul
|
||||
// Sandbox routing (sandboxKey from ctx — thread-safe)
|
||||
sandboxKey := ToolSandboxKeyFromCtx(ctx)
|
||||
if t.sandboxMgr != nil && sandboxKey != "" {
|
||||
return t.executeInSandbox(ctx, path, content, sandboxKey, deliver)
|
||||
return t.executeInSandbox(ctx, path, content, sandboxKey, deliver, appendMode)
|
||||
}
|
||||
|
||||
// Host execution — use per-user workspace from context if available
|
||||
@@ -222,7 +222,7 @@ func (t *WriteFileTool) Execute(ctx context.Context, args map[string]any) *Resul
|
||||
return result
|
||||
}
|
||||
|
||||
func (t *WriteFileTool) executeInSandbox(ctx context.Context, path, content, sandboxKey string, deliver bool) *Result {
|
||||
func (t *WriteFileTool) executeInSandbox(ctx context.Context, path, content, sandboxKey string, deliver, appendMode bool) *Result {
|
||||
bridge, err := t.getFsBridge(ctx, sandboxKey)
|
||||
if err != nil {
|
||||
return ErrorResult(fmt.Sprintf("sandbox error: %v", err))
|
||||
@@ -234,11 +234,19 @@ func (t *WriteFileTool) executeInSandbox(ctx context.Context, path, content, san
|
||||
}
|
||||
containerPath := ResolveSandboxPath(path, containerCwd)
|
||||
|
||||
if err := bridge.WriteFile(ctx, containerPath, content); err != nil {
|
||||
return ErrorResult(fmt.Sprintf("failed to write file: %v", err) + MaybeFsBridgeHint(err))
|
||||
if err := bridge.WriteFile(ctx, containerPath, content, appendMode); err != nil {
|
||||
verb := "write"
|
||||
if appendMode {
|
||||
verb = "append to"
|
||||
}
|
||||
return ErrorResult(fmt.Sprintf("failed to %s file: %v", verb, err) + MaybeFsBridgeHint(err))
|
||||
}
|
||||
|
||||
msg := fmt.Sprintf("File written: %s (%d bytes)", path, len(content))
|
||||
verb := "written"
|
||||
if appendMode {
|
||||
verb = "appended"
|
||||
}
|
||||
msg := fmt.Sprintf("File %s: %s (%d bytes)", verb, path, len(content))
|
||||
if deliver {
|
||||
msg += ". File will be automatically delivered to the user — do NOT send it again via message tool."
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user