OpenClaw工具拆解之sandboxed_write+sandboxed_edit

一、sandboxed_write 工具

1.1 工具概述

功能 :沙盒中写入文件
核心特性:

  • 通过 bridge 写入隔离文件系统
  • 自动创建父目录
  • 支持沙盒根目录限制
  • 参数标准化包装

1.2 创建函数

位置:第 115006 行

javascript 复制代码
function createSandboxedWriteTool(params) {
    return wrapToolParamNormalization(createWriteTool(params.root, { 
        operations: createSandboxWriteOperations(params) 
    }), CLAUDE_PARAM_GROUPS.write);
}

1.3 沙盒写入操作

位置:第 115068 行

javascript 复制代码
function createSandboxWriteOperations(params) {
    return {
        // 1. 创建目录
        mkdir: async (dir) => {
            await params.bridge.mkdirp({
                filePath: dir,
                cwd: params.root
            });
        },
        
        // 2. 写入文件
        writeFile: async (absolutePath, content) => {
            await params.bridge.writeFile({
                filePath: absolutePath,
                cwd: params.root,
                data: content
            });
        }
    };
}

1.4 包装链

复制代码
createWriteTool (外部库)
    ↓ (使用沙盒操作)
wrapToolParamNormalization (参数标准化)
    ↓ (CLAUDE_PARAM_GROUPS.write)
最终工具

1.5 参数标准化

javascript 复制代码
const CLAUDE_PARAM_GROUPS = {
    write: [
        {
            keys: ["path", "file_path", "filePath", "file"],
            label: "path alias"
        },
        {
            keys: ["content"],
            label: "content"
        }
    ]
};

const CLAUDE_PARAM_ALIASES = [
    { original: "path", alias: "file_path" },
    { original: "path", alias: "filePath" },
    { original: "path", alias: "file" }
];

1.6 执行流程图

复制代码
sandboxed_write 工具调用
    ↓
1. 参数标准化
   ├─ path/file_path/filePath/file → path
   └─ content → content
    ↓
2. 验证必填参数
    ↓
3. 沙盒桥接写入
    ├─ bridge.mkdirp(创建父目录)
    └─ bridge.writeFile(写入文件)
    ↓
4. 返回结果

1.7 返回结果格式

成功:

json 复制代码
{
  "content": [{
    "type": "text",
    "text": "Successfully wrote 123 bytes to sandbox:src/main.js"
  }],
  "details": {
    "path": "src/main.js",
    "bytesWritten": 123,
    "sandbox": true
  }
}

二、sandboxed_edit 工具

2.1 工具概述

功能 :沙盒中编辑文件
核心特性:

  • 通过 bridge 访问隔离文件系统
  • 支持编辑失败恢复
  • 支持参数别名
  • 精确文本替换

2.2 创建函数

位置:第 115009 行

javascript 复制代码
function createSandboxedEditTool(params) {
    return wrapToolParamNormalization(
        wrapEditToolWithRecovery(createEditTool(params.root, { 
            operations: createSandboxEditOperations(params) 
        }), {
            root: params.root,
            readFile: async (absolutePath) => (await params.bridge.readFile({
                filePath: absolutePath,
                cwd: params.root
            })).toString("utf8")
        }), 
        CLAUDE_PARAM_GROUPS.edit
    );
}

2.3 沙盒编辑操作

位置:第 115077 行

javascript 复制代码
function createSandboxEditOperations(params) {
    return {
        // 1. 读取文件
        readFile: (absolutePath) => params.bridge.readFile({
            filePath: absolutePath,
            cwd: params.root
        }),
        
        // 2. 写入文件
        writeFile: (absolutePath, content) => params.bridge.writeFile({
            filePath: absolutePath,
            cwd: params.root,
            data: content
        }),
        
        // 3. 访问检查
        access: async (absolutePath) => {
            if (!await params.bridge.stat({
                filePath: absolutePath,
                cwd: params.root
            })) {
                throw createFsAccessError("ENOENT", absolutePath);
            }
        }
    };
}

2.4 编辑恢复包装

位置:第 114462 行

javascript 复制代码
function wrapEditToolWithRecovery(base, options) {
    return {
        ...base,
        execute: async (toolCallId, params, signal, onUpdate) => {
            // 1. 读取参数
            const { pathParam, oldText, newText } = readEditToolParams(params);
            
            // 2. 解析绝对路径
            const absolutePath = typeof pathParam === "string" ? 
                resolveEditPath(options.root, pathParam) : void 0;
            
            // 3. 读取原始内容
            let originalContent;
            if (absolutePath && newText !== void 0) {
                try {
                    originalContent = await options.readFile(absolutePath);
                } catch {}
            }
            
            // 4. 执行编辑
            try {
                return await base.execute(toolCallId, params, signal, onUpdate);
            } catch (err) {
                // 5. 编辑失败处理
                if (!absolutePath) throw err;
                
                let currentContent;
                try {
                    currentContent = await options.readFile(absolutePath);
                } catch {}
                
                // 6. 判断编辑是否已应用
                if (typeof currentContent === "string" && newText !== void 0) {
                    if (didEditLikelyApply({
                        originalContent,
                        currentContent,
                        oldText,
                        newText
                    })) {
                        return buildEditSuccessResult(pathParam ?? absolutePath);
                    }
                }
                
                // 7. 添加不匹配提示
                if (typeof currentContent === "string" && 
                    err instanceof Error && 
                    shouldAddMismatchHint(err)) {
                    throw appendMismatchHint(err, currentContent);
                }
                
                throw err;
            }
        }
    };
}

2.5 参数标准化

javascript 复制代码
const CLAUDE_PARAM_GROUPS = {
    edit: [
        {
            keys: ["path", "file_path", "filePath", "file"],
            label: "path alias"
        },
        {
            keys: ["oldText", "old_string", "old_text", "oldString"],
            label: "oldText alias"
        },
        {
            keys: ["newText", "new_string", "new_text", "newString"],
            label: "newText alias",
            allowEmpty: true
        }
    ]
};

const CLAUDE_PARAM_ALIASES = [
    // path 别名
    { original: "path", alias: "file_path" },
    { original: "path", alias: "filePath" },
    { original: "path", alias: "file" },
    
    // oldText 别名
    { original: "oldText", alias: "old_string" },
    { original: "oldText", alias: "old_text" },
    { original: "oldText", alias: "oldString" },
    
    // newText 别名
    { original: "newText", alias: "new_string" },
    { original: "newText", alias: "new_text" },
    { original: "newText", alias: "newString" }
];

2.6 执行流程图

复制代码
sandboxed_edit 工具调用
    ↓
1. 参数标准化
   ├─ path 别名处理
   ├─ oldText 别名处理
   └─ newText 别名处理
    ↓
2. 验证必填参数
    ↓
3. 读取原始内容
    ↓
4. 执行编辑(精确替换)
    ↓
5. 编辑失败处理
   ├─ 读取当前内容
   ├─ 判断是否部分成功
   └─ 添加不匹配提示
    ↓
6. 返回结果

三、关键机制对比

3.1 功能定位

特性 sandboxed_write sandboxed_edit
用途 创建/覆盖文件 精确修改文件
写操作 覆盖写入 精确替换
恢复机制 不支持 支持

3.2 参数支持

特性 sandboxed_write sandboxed_edit
参数别名 4 个(path) 9 个(path/oldText/newText)
必填参数 path, content path, oldText
可选参数 无 newText(可为空)

3.3 安全限制

限制类型 sandboxed_write sandboxed_edit
沙盒根目录 root 限制 root 限制
桥接访问 必需 必需
路径边界 bridge 路径 bridge 路径

四、沙盒工具系列总结

4.1 三个沙盒工具

工具 功能 桥接操作
sandboxed_read 读取文件 bridge.readFile, bridge.stat, detectMime
sandboxed_write 写入文件 bridge.mkdirp, bridge.writeFile
sandboxed_edit 编辑文件 bridge.readFile, bridge.writeFile, bridge.stat

4.2 共同特点

  • 通过 bridge 访问隔离文件系统
  • 不能直接访问 host 文件系统
  • 用于容器/Docker/沙盒环境
  • 支持 modelContextWindowTokens 限制
  • 支持图片 sanitization

4.3 使用场景

复制代码
沙盒模式启用时:
├─ 读取文件 → sandboxed_read
├─ 写入文件 → sandboxed_write
└─ 编辑文件 → sandboxed_edit

非沙盒模式时:
├─ 读取文件 → read
├─ 写入文件 → write
└─ 编辑文件 → edit

五、使用示例

5.1 sandboxed_write 工具调用

用户 :在沙盒中创建文件

大模型返回:

json 复制代码
{
  "tool_call": {
    "name": "sandboxed_write",
    "arguments": { 
      "path": "src/main.js",
      "content": "console.log(\"Hello\");"
    }
  }
}

执行结果:

json 复制代码
{
  "content": [{
    "type": "text",
    "text": "Successfully wrote 27 bytes to sandbox:src/main.js"
  }],
  "details": {
    "path": "src/main.js",
    "bytesWritten": 27,
    "sandbox": true
  }
}

5.2 sandboxed_edit 工具调用

用户 :修改沙盒中的文件

大模型返回:

json 复制代码
{
  "tool_call": {
    "name": "sandboxed_edit",
    "arguments": { 
      "path": "src/main.js",
      "oldText": "console.log(\"Hello\");",
      "newText": "console.log(\"Hello, World!\");"
    }
  }
}

执行结果:

json 复制代码
{
  "content": [{
    "type": "text",
    "text": "Successfully edited sandbox:src/main.js"
  }],
  "details": {
    "path": "src/main.js",
    "sandbox": true
  }
}
相关推荐
西索斯coding3 分钟前
Claude Code 工具调用返回空怎么办?claude-opus-5.5 的 input_schema 校验踩坑记录与修复方案
服务器·前端·javascript·ai
疯狂敲代码的老刘4 分钟前
MarvellousSuspender浏览器扩展:1.4K星开源标签页冻结神器,Chrome吃内存终结者的干净复活版
前端·chrome
码上上班5 分钟前
shell 脚本知识点总结
前端·chrome
程序员Sunday7 分钟前
Flex 和 Grid 有什么区别?一维布局、二维布局怎么选?
前端·css·面试
Jlzn88889 分钟前
华南PACK线自动化率怎么统计才靠谱
前端·javascript·自动化
Rain的Java大神之路10 分钟前
Java 工程师做 AI,核心机会在「落地」而非「训练」
java·后端·ai编程
坊钰11 分钟前
【LangGraph框架入门级】4. 支持联网搜索的智能代理系统
python·ai编程·langgraph
Rain的Java大神实战圈18 分钟前
Java 工程师做 AI,核心机会在「落地」而非「训练」
ai编程
拖孩29 分钟前
AI 时代,个人开发者到底能不能靠一个小程序赚到钱
前端·后端·微信小程序
Hilaku30 分钟前
ERP选型时不问MCP,等于默认三年后再付一次集成费!
前端·javascript·程序员