核心执行流程概览
大模型返回工具调用 ↓OpenClaw 接收 tool_call ↓查找工具定义(name → execute 函数) ↓执行 execute(toolCallId, params, signal, onUpdate) ↓返回结果(content + details) ↓发送给大模型一、exec 工具执行代码
1.1 工具创建函数
位置:第 16558 行
functioncreateExecTool(defaults) {// 1. 解析默认配置const defaultBackgroundMs = clampWithDefault(defaults?.backgroundMs ?? readEnvInt("PI_BASH_YIELD_MS"), 1e4, 10, 12e4);const allowBackground = defaults?.allowBackground ?? true;const defaultTimeoutSec = typeof defaults?.timeoutSec === "number" && defaults.timeoutSec > 0 ? defaults.timeoutSec : 1800;// 2. 解析安全策略(白名单、黑名单等)const { safeBins, safeBinProfiles, trustedSafeBinDirs } = resolveExecSafeBinRuntimePolicy({...});// 3. 返回工具定义return {name: "exec",label: "exec",description: "Execute shell commands with background continuation...",parameters: execSchema,execute: async (_toolCallId, args, signal, onUpdate) => {// === 执行逻辑开始 ===const params = args;// 1. 参数验证if (!params.command) thrownewError("Provide a command to start.");// 2. 解析后台运行请求const backgroundRequested = params.background === true;const yieldRequested = typeof params.yieldMs === "number";const yieldWindow = allowBackground ? (backgroundRequested ? 0 : clampWithDefault(params.yieldMs ?? defaultBackgroundMs, defaultBackgroundMs, 10, 12e4)) : null;// 3. 解析提权请求const elevatedRequested = elevatedMode !== "off";if (elevatedRequested) {if (!elevatedDefaults?.enabled || !elevatedDefaults.allowed) {thrownewError("elevated is not available right now..."); } }// 4. 确定执行主机(sandbox/gateway/node)let host = requestedHost ?? configuredHost;if (elevatedRequested) host = "gateway";// 5. 确定安全模式let security = minSecurity(configuredSecurity, normalizeExecSecurity$1(params.security) ?? configuredSecurity);if (elevatedRequested && elevatedMode === "full") security = "full";// 6. 确定审批模式let ask = maxAsk(configuredAsk, normalizeExecAsk$1(params.ask) ?? configuredAsk);const bypassApprovals = elevatedRequested && elevatedMode === "full";if (bypassApprovals) ask = "off";// 7. 解析工作目录const rawWorkdir = params.workdir?.trim() || defaults?.cwd || process.cwd();let workdir = rawWorkdir;if (sandbox) {const resolved = awaitresolveSandboxWorkdir({ workdir: rawWorkdir, sandbox, warnings }); workdir = resolved.hostWorkdir; }// 8. 解析环境变量(安全检查)const env = sandbox && host === "sandbox" ? buildSandboxEnv({...}) : hostEnvResult?.env ?? inheritedBaseEnv;// 9. 处理不同主机的执行逻辑if (host === "node") {returnexecuteNodeHostCommand({...}); }if (host === "gateway" && !bypassApprovals) {// 10. Gateway 白名单检查(可能需要用户审批)const gatewayResult = awaitprocessGatewayAllowlist({...});if (gatewayResult.pendingResult) {// 需要审批,返回待处理状态return gatewayResult.pendingResult; } execCommandOverride = gatewayResult.execCommandOverride; }// 11. 执行命令const run = awaitrunExecProcess({command: params.command,execCommand: execCommandOverride, workdir, env, sandbox, containerWorkdir,usePty: params.pty === true && !sandbox, warnings,maxOutput: DEFAULT_MAX_OUTPUT,pendingMaxOutput: DEFAULT_PENDING_MAX_OUTPUT, notifyOnExit, notifyOnExitEmptySuccess,timeoutSec: effectiveTimeout, onUpdate });// 12. 处理后台运行let yielded = false;let yieldTimer = null;constonYieldNow = () => {if (yieldTimer) clearTimeout(yieldTimer);if (yielded) return; yielded = true;markBackgrounded(run.session);resolveRunning(); };if (allowBackground && yieldWindow !== null) {if (yieldWindow === 0) onYieldNow();else yieldTimer = setTimeout(onYieldNow, yieldWindow); }// 13. 等待执行完成returnnewPromise((resolve, reject) => { run.promise.then((outcome) => {if (yieldTimer) clearTimeout(yieldTimer);if (yielded || run.session.backgrounded) return;resolve(buildExecForegroundResult({ outcome, cwd: run.session.cwd, warningText: getWarningText() })); }).catch((err) => {if (yieldTimer) clearTimeout(yieldTimer);if (yielded || run.session.backgrounded) return;reject(err); }); }); } };}1.2 执行流程图
exec 工具调用 ↓1. 参数验证(command 必填) ↓2. 解析后台运行请求(background/yieldMs) ↓3. 解析提权请求(elevated) ↓4. 确定主机(sandbox/gateway/node) ↓5. Gateway 白名单检查 ├─ 需要审批 → 返回 pending 状态,等待用户 /approve └─ 无需审批 → 继续 ↓6. 启动进程(runExecProcess) ├─ 创建子进程 ├─ 监听输出 ├─ 记录日志 └─ 返回 session ↓7. 判断是否后台运行 ├─ 是 → 立即返回"正在运行",用户可用 process 工具管理 └─ 否 → 等待完成,返回完整输出1.3 返回结果格式
前台运行完成:
{"content":[{"type":"text","text":"命令输出内容..."}],"details":{"status":"completed","exitCode":0,"cwd":"/path/to/workdir"}}后台运行中:
{"content":[{"type":"text","text":"Command still running (session abc123, pid 12345). Use process (list/poll/log/write/kill/clear/remove) for follow-up."}],"details":{"status":"running","sessionId":"abc123","pid":12345,"startedAt":1711716000000,"cwd":"/path/to/workdir","tail":"部分输出..."}}需要审批:
{"content":[{"type":"text","text":"Approval required (id abc123).\nHost: gateway\nCWD: /workspace\nCommand:\n```sh\nrm -rf /tmp/test\n```\nReply with: /approve abc123 allow-once|allow-always|deny"}],"details":{"status":"pending_approval","approvalId":"abc123"}}二、process 工具执行代码
2.1 工具创建函数
位置:第 17108 行
functioncreateProcessTool(defaults) {if (defaults?.cleanupMs !== void0) setJobTtlMs(defaults.cleanupMs);const scopeKey = defaults?.scopeKey;const supervisor = getProcessSupervisor();constisInScope = (session) => !scopeKey || session?.scopeKey === scopeKey;return {name: "process",label: "process",description: "Manage running exec sessions: list, poll, log, write, send-keys, submit, paste, kill.",parameters: processSchema,execute: async (_toolCallId, args, _signal, _onUpdate) => {const params = args;// === action: list ===if (params.action === "list") {const running = listRunningSessions() .filter((s) =>isInScope(s)) .map((s) => ({sessionId: s.id,status: "running",pid: s.pid ?? void0,startedAt: s.startedAt,runtimeMs: Date.now() - s.startedAt,cwd: s.cwd,command: s.command,name: deriveSessionName(s.command),tail: s.tail,truncated: s.truncated }));const finished = listFinishedSessions() .filter((s) =>isInScope(s)) .map((s) => ({sessionId: s.id,status: s.status,startedAt: s.startedAt,endedAt: s.endedAt,runtimeMs: s.endedAt - s.startedAt,cwd: s.cwd,command: s.command,name: deriveSessionName(s.command),tail: s.tail,truncated: s.truncated,exitCode: s.exitCode ?? void0,exitSignal: s.exitSignal ?? void0 }));return {content: [{type: "text",text: [...running, ...finished] .toSorted((a, b) => b.startedAt - a.startedAt) .map((s) => {const label = s.name ? truncateMiddle(s.name, 80) : truncateMiddle(s.command, 120);return`${s.sessionId}${pad(s.status, 9)}${formatDurationCompact$1(s.runtimeMs) ?? "n/a"} :: ${label}`; }) .join("\n") || "No running or recent sessions." }],details: {status: "completed",sessions: [...running, ...finished] } }; }// === 其他 action 需要 sessionId ===if (!params.sessionId) return {content: [{ type: "text", text: "sessionId is required for this action." }],details: { status: "failed" } };const session = getSession(params.sessionId);const finished = getFinishedSession(params.sessionId);const scopedSession = isInScope(session) ? session : void0;const scopedFinished = isInScope(finished) ? finished : void0;// === action: poll ===if (params.action === "poll") {if (!scopedSession) {if (scopedFinished) {// 已完成,返回最终结果return {content: [{type: "text",text: (scopedFinished.tail || "(no output recorded)") + `\n\nProcess exited with ${scopedFinished.exitSignal ? `signal ${scopedFinished.exitSignal}` : `code ${scopedFinished.exitCode ?? 0}`}.` }],details: {status: scopedFinished.status === "completed" ? "completed" : "failed",sessionId: params.sessionId,exitCode: scopedFinished.exitCode ?? void0 } }; }return { content: [{ type: "text", text: `No session found for ${params.sessionId}` }], details: { status: "failed" } }; }if (!scopedSession.backgrounded) return { content: [{ type: "text", text: `Session ${params.sessionId} is not backgrounded.` }], details: { status: "failed" } };// 等待指定时间(timeout)const pollWaitMs = resolvePollWaitMs(params.timeout);if (pollWaitMs > 0 && !scopedSession.exited) {const deadline = Date.now() + pollWaitMs;while (!scopedSession.exited && Date.now() < deadline) {awaitnewPromise((resolve) =>setTimeout(resolve, Math.max(0, Math.min(250, deadline - Date.now())))); } }// 获取输出const { stdout, stderr } = drainSession(scopedSession);const exited = scopedSession.exited;if (exited) {markExited(scopedSession, scopedSession.exitCode ?? null, scopedSession.exitSignal ?? null, status); }const output = [stdout.trimEnd(), stderr.trimEnd()].filter(Boolean).join("\n").trim();return {content: [{type: "text",text: (output || "(no new output)") + (exited ? `\n\nProcess exited with code ${scopedSession.exitCode}.` : "\n\nProcess still running.") }],details: {status: exited ? "completed" : "running",sessionId: params.sessionId,exitCode: exited ? scopedSession.exitCode : void0 } }; }// === action: log ===if (params.action === "log") {if (!scopedSession || !scopedSession.backgrounded) {return { content: [{ type: "text", text: `Session ${params.sessionId} is not backgrounded.` }], details: { status: "failed" } }; }constwindow = resolveLogSliceWindow(params.offset, params.limit);const { slice, totalLines, totalChars } = sliceLogLines(scopedSession.aggregated, window.effectiveOffset, window.effectiveLimit);return {content: [{ type: "text", text: (slice || "(no output yet)") + defaultTailNote(totalLines, window.usingDefaultTail) }],details: {status: scopedSession.exited ? "completed" : "running",sessionId: params.sessionId,total: totalLines, totalLines, totalChars } }; }// === action: write ===if (params.action === "write") {const { ok, stdin, session } = resolveBackgroundedWritableStdin();if (!ok) return stdin.result; // 错误结果awaitwriteToStdin(stdin, params.data);return {content: [{ type: "text", text: `Wrote ${params.data.length} bytes to stdin.` }],details: {status: session.exited ? "completed" : "running",sessionId: params.sessionId } }; }// === action: send-keys ===if (params.action === "send-keys") {const { ok, stdin, session } = resolveBackgroundedWritableStdin();if (!ok) return stdin.result;// 解析按键const keys = params.keys || [];const hex = params.hex || [];const literal = params.literal;if (literal) {awaitwriteToStdin(stdin, literal); } elseif (hex.length > 0) {const buffer = Buffer.from(hex.join(""), "hex");awaitnewPromise((resolve, reject) => { stdin.write(buffer, (err) => err ? reject(err) : resolve()); }); } else {// 发送命名按键(如 "enter", "up", "down" 等)for (const key of keys) {const keyData = namedKeyMap.get(key.toLowerCase()) || key;awaitwriteToStdin(stdin, keyData); } }return {content: [{ type: "text", text: `Sent keys to session.` }],details: { status: "running", sessionId: params.sessionId } }; }// === action: paste ===if (params.action === "paste") {const { ok, stdin, session } = resolveBackgroundedWritableStdin();if (!ok) return stdin.result;// 发送括号粘贴模式(避免输入被解释为命令)if (params.bracketed) {awaitwriteToStdin(stdin, BRACKETED_PASTE_START); }awaitwriteToStdin(stdin, params.text);if (params.bracketed) {awaitwriteToStdin(stdin, BRACKETED_PASTE_END); }return {content: [{ type: "text", text: `Pasted ${params.text.length} characters.` }],details: { status: "running", sessionId: params.sessionId } }; }// === action: submit ===if (params.action === "submit") {const { ok, stdin, session } = resolveBackgroundedWritableStdin();if (!ok) return stdin.result;// 发送 EOF(关闭 stdin) stdin.end();return {content: [{ type: "text", text: "Submitted EOF to stdin." }],details: { status: "running", sessionId: params.sessionId } }; }// === action: kill ===if (params.action === "kill") {if (!scopedSession) {return { content: [{ type: "text", text: `No active session found for ${params.sessionId}` }], details: { status: "failed" } }; }// 尝试通过 supervisor 取消const cancelled = cancelManagedSession(params.sessionId);if (!cancelled) {// 回退到直接 kill 进程树terminateSessionFallback(scopedSession); }return {content: [{ type: "text", text: `Killed session ${params.sessionId}.` }],details: { status: "killed", sessionId: params.sessionId } }; }return { content: [{ type: "text", text: `Unknown action: ${params.action}` }], details: { status: "failed" } }; } };}2.2 支持的 Actions
list | ||
poll | ||
log | ||
write | ||
send-keys | ||
paste | ||
submit | ||
kill |
三、read 工具执行代码
3.1 工具包装链
createReadTool (外部库) ↓createOpenClawReadTool (OpenClaw 包装) ↓wrapToolWorkspaceRootGuard (工作目录限制) ↓最终工具3.2 OpenClaw 包装代码
位置:第 115027 行
functioncreateOpenClawReadTool(base, options) {return { ...patchToolSchemaForClaudeCompatibility(base),execute: async (toolCallId, params, signal) => {// 1. 参数标准化(支持 path/file_path/filePath/file 别名)const normalized = normalizeToolParams(params);const record = normalized ?? (params && typeof params === "object" ? params : void0);// 2. 验证必填参数assertRequiredParams(record, CLAUDE_PARAM_GROUPS.read, base.name);// 3. 执行读取(支持自适应分页)const result = awaitexecuteReadWithAdaptivePaging({ base, toolCallId,args: normalized ?? params ?? {}, signal,maxBytes: resolveAdaptiveReadMaxBytes(options) });// 4. 处理文件路径const filePath = typeof record?.path === "string" ? String(record.path) : "<unknown>";// 5. 处理图片结果(如果是图片文件)returnsanitizeToolResultImages(awaitnormalizeReadImageResult(stripReadTruncationContentDetails(result), filePath ), `read:${filePath}`, options?.imageSanitization ); } };}3.3 自适应分页读取
位置:第 114782 行
asyncfunctionexecuteReadWithAdaptivePaging(params) {const userLimit = params.args.limit;// 1. 用户指定了 limit,直接返回if (typeof userLimit === "number" && Number.isFinite(userLimit) && userLimit > 0) {returnawait params.base.execute(params.toolCallId, params.args, params.signal); }// 2. 自适应分页读取const offsetRaw = params.args.offset;let nextOffset = typeof offsetRaw === "number" && Number.isFinite(offsetRaw) && offsetRaw > 0 ? Math.floor(offsetRaw) : 1;let firstResult = null;let aggregatedText = "";let aggregatedBytes = 0;let capped = false;let continuationOffset;for (let page = 0; page < MAX_ADAPTIVE_READ_PAGES; page += 1) {// 读取下一页const pageArgs = { ...params.args, offset: nextOffset };const pageResult = await params.base.execute(params.toolCallId, pageArgs, params.signal); firstResult ??= pageResult;const rawText = getToolResultText$1(pageResult);if (typeof rawText !== "string") return pageResult;// 检查是否被截断const truncation = extractReadTruncationDetails(pageResult);const canContinue = Boolean(truncation?.truncated) && !truncation?.firstLineExceedsLimit && (truncation?.outputLines ?? 0) > 0 && page < MAX_ADAPTIVE_READ_PAGES - 1;const pageText = canContinue ? stripReadContinuationNotice(rawText) : rawText;// 聚合文本const delimiter = aggregatedText ? "\n\n" : "";const nextBytes = Buffer.byteLength(`${delimiter}${pageText}`, "utf-8");// 检查是否超过最大字节数if (aggregatedText && aggregatedBytes + nextBytes > params.maxBytes) { capped = true; continuationOffset = nextOffset;break; } aggregatedText += `${delimiter}${pageText}`; aggregatedBytes += nextBytes;if (!canContinue || !truncation) returnwithToolResultText(pageResult, aggregatedText); nextOffset += truncation.outputLines; continuationOffset = nextOffset;if (aggregatedBytes >= params.maxBytes) { capped = true;break; } }// 3. 返回聚合结果if (!firstResult) returnawait params.base.execute(params.toolCallId, params.args, params.signal);let finalText = aggregatedText;if (capped && continuationOffset) { finalText += `\n\n[Read output capped at ${formatBytes(params.maxBytes)} for this call. Use offset=${continuationOffset} to continue.]`; }returnwithToolResultText(firstResult, finalText);}3.4 执行流程
read 工具调用 ↓1. 参数标准化(path/file_path/filePath/file → path) ↓2. 验证必填参数(path) ↓3. 判断是否指定 limit ├─ 是 → 直接读取指定行数 └─ 否 → 自适应分页读取 ↓4. 自适应分页 ├─ 读取第 1 页(默认 2000 行或 50KB) ├─ 检查是否截断 ├─ 是 → 继续读第 2 页 ├─ 聚合文本 ├─ 重复直到不截断或达到最大页数 └─ 返回聚合结果 ↓5. 处理图片文件(如果是图片) ├─ 检测 MIME 类型 ├─ 转换为 Base64 └─ 作为附件发送 ↓6. 返回结果四、write 工具执行代码
4.1 工具包装链
createWriteTool (外部库) ↓wrapToolParamNormalization (参数标准化) ↓wrapToolWorkspaceRootGuard (工作目录限制) ↓最终工具4.2 Host 写入操作
位置:第 115057 行
asyncfunctionwriteHostFile(absolutePath, content) {const resolved = path.resolve(absolutePath);await fs$1.mkdir(path.dirname(resolved), { recursive: true });await fs$1.writeFile(resolved, content, "utf-8");}functioncreateHostWriteOperations(root, options) {if (!(options?.workspaceOnly ?? false)) {// 无限制模式return {mkdir: async (dir) => {const resolved = path.resolve(dir);await fs$1.mkdir(resolved, { recursive: true }); },writeFile: writeHostFile }; }// workspaceOnly 模式(限制只能访问工作区)return {mkdir: async (dir) => {const relative = toRelativeWorkspacePath(root, dir, { allowRoot: true });const resolved = relative ? path.resolve(root, relative) : path.resolve(root);awaitassertSandboxPath({ filePath: resolved, cwd: root, root });await fs$1.mkdir(resolved, { recursive: true }); },writeFile: async (absolutePath, content) => {awaitwriteFileWithinRoot({rootDir: root,relativePath: toRelativeWorkspacePath(root, absolutePath),data: content,mkdir: true }); } };}4.3 执行流程
write 工具调用 ↓1. 参数标准化(path/file_path/filePath/file → path) ↓2. 验证必填参数(path, content) ↓3. 解析路径(绝对/相对) ↓4. 检查 workspaceOnly 限制 ├─ 是 → 验证路径在工作区内 └─ 否 → 允许任意路径 ↓5. 创建父目录(mkdir -p) ↓6. 写入文件(覆盖模式) ↓7. 返回成功结果五、edit 工具执行代码
5.1 工具包装链
createEditTool (外部库) ↓wrapEditToolWithRecovery (编辑恢复机制) ↓wrapToolParamNormalization (参数标准化) ↓wrapToolWorkspaceRootGuard (工作目录限制) ↓最终工具5.2 编辑恢复机制
位置:第 114462 行
functionwrapEditToolWithRecovery(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) : void0;// 3. 读取原始内容(用于恢复判断)let originalContent;if (absolutePath && newText !== void0) {try { originalContent = await options.readFile(absolutePath); } catch {} }// 4. 执行编辑try {returnawait 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 !== void0) {if (didEditLikelyApply({ originalContent, currentContent, oldText, newText })) {// 编辑已应用,返回成功returnbuildEditSuccessResult(pathParam ?? absolutePath); } }// 7. 添加不匹配提示if (typeof currentContent === "string" && err instanceofError && shouldAddMismatchHint(err)) {throwappendMismatchHint(err, currentContent); }throw err; } } };}5.3 参数标准化
位置:第 114541 行
constCLAUDE_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 } ]};constCLAUDE_PARAM_ALIASES = [ { original: "path", alias: "file_path" }, { original: "path", alias: "filePath" }, { original: "path", alias: "file" }, { original: "oldText", alias: "old_string" }, { original: "oldText", alias: "old_text" }, { original: "oldText", alias: "oldString" }, { original: "newText", alias: "new_string" }, { original: "newText", alias: "new_text" }, { original: "newText", alias: "newString" }];5.4 执行流程
edit 工具调用 ↓1. 参数标准化(9 个别名 → 3 个标准参数) ├─ path/file_path/filePath/file → path ├─ oldText/old_string/old_text/oldString → oldText └─ newText/new_string/new_text/newString → newText ↓2. 验证必填参数(path, oldText) ↓3. 读取原始内容(用于恢复判断) ↓4. 执行编辑(精确替换) ├─ 查找 oldText(必须完全匹配) ├─ 替换为 newText └─ 保存文件 ↓5. 编辑失败处理 ├─ 读取当前内容 ├─ 判断编辑是否已部分应用 ├─ 是 → 返回成功 └─ 否 → 添加不匹配提示(显示当前内容) ↓6. 返回结果六、工具调用完整流程
6.1 从大模型到执行
1. 大模型返回 tool_call { "id": "call_abc123", "type": "function", "function": { "name": "exec", "arguments": "{\"command\": \"ls -la\"}" } } ↓2. OpenClaw 解析 tool_call - 查找工具定义(tools.find(t => t.name === "exec")) - 解析参数(JSON.parse) ↓3. 执行工具 tool.execute(toolCallId, params, signal, onUpdate) ↓4. 捕获结果 - content: [{ type: "text", text: "..." }] - details: { status: "completed", ... } ↓5. 发送给大模型 { "role": "tool", "tool_call_id": "call_abc123", "content": "..." } ↓6. 大模型生成回复 ↓7. 发送给用户6.2 工具调用示例
用户:列出当前目录的文件
大模型思考:需要执行 ls -la 命令
大模型返回:
{"role":"assistant","content":null,"tool_calls":[{"id":"call_abc123","type":"function","function":{"name":"exec","arguments":"{\"command\": \"ls -la\"}"}}]}OpenClaw 执行:
const tool = tools.find(t => t.name === "exec");const params = JSON.parse("{\"command\": \"ls -la\"}");const result = await tool.execute("call_abc123", params, signal, onUpdate);返回结果:
{"role":"tool","tool_call_id":"call_abc123","content":"total 48\ndrwxr-xr-x 5 user user 4096 Mar 29 21:00 .\ndrwxr-xr-x 10 user user 4096 Mar 29 20:00 ..\n-rw-r--r-- 1 user user 234 Mar 29 20:00 README.md\n..."}大模型生成回复:
当前目录有以下文件:- README.md (234 字节)- src/ (目录)- docs/ (目录)...七、关键机制总结
7.1 参数标准化
所有工具都支持参数别名,让大模型更灵活:
// read 工具path = params.path ?? params.file_path ?? params.filePath ?? params.file// edit 工具oldText = params.oldText ?? params.old_string ?? params.old_text ?? params.oldStringnewText = params.newText ?? params.new_string ?? params.new_text ?? params.newString7.2 安全限制
• 工作目录限制: workspaceOnly=true时只能访问工作区• 沙盒隔离:沙盒模式通过 bridge访问文件系统• 命令白名单:exec 命令需要匹配白名单或通过审批
7.3 错误恢复
• edit 工具:编辑失败时判断是否部分成功 • read 工具:自适应分页,避免一次性读取大文件 • exec 工具:后台运行 + process 工具管理
7.4 结果格式
所有工具返回统一格式:
{content: [{ type: "text", text: "..." }], // 文本内容details: { status: "completed/running/failed", ... } // 元数据}
夜雨聆风