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:
Huy Doan authored and GitHub committed 2026-04-03 16:03:57 +07:00
1 parent 80c2c0e0ee
commit 72025698bb
3 files changed
+36 -23

No files matched your search

+21 -13
View File
@@ -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."
}