乐于分享
好东西不私藏

OpenClaw工具拆解之exec

OpenClaw工具拆解之exec

如果文章对你有帮助,请点个“关注”

一、工具概述

功能:执行 shell 命令(最核心的基础工具)
核心特性

  • • 支持后台运行(yieldMs/background)
  • • 支持超时控制(timeout)
  • • 支持 PTY 模式(pty=true 用于 TTY 命令)
  • • 支持多主机(sandbox/gateway/node)
  • • 支持提权执行(elevated)
  • • 支持安全模式(deny/allowlist/full)
  • • 支持审批模式(off/on-miss/always)
  • • 支持环境变量(env)
  • • 支持工作目录(cwd/workdir)

二、Schema 定义

位置:第 14782 行

const execSchema = Type.Object({
commandType.String({ description"Shell command to execute" }),
workdirType.Optional(Type.String({ description"Working directory (defaults to cwd)" })),
envType.Optional(Type.Record(Type.String(), Type.String())),
yieldMsType.Optional(Type.Number({ description"Milliseconds to wait before backgrounding (default 10000)" })),
backgroundType.Optional(Type.Boolean({ description"Run in background immediately" })),
timeoutType.Optional(Type.Number({ description"Timeout in seconds (optional, kills process on expiry)" })),
ptyType.Optional(Type.Boolean({ description"Run in a pseudo-terminal (PTY) when available (TTY-required CLIs, coding agents)" })),
elevatedType.Optional(Type.Boolean({ description"Run on the host with elevated permissions (if allowed)" })),
hostType.Optional(Type.String({ description"Exec host (sandbox|gateway|node)." })),
securityType.Optional(Type.String({ description"Exec security mode (deny|allowlist|full)." })),
askType.Optional(Type.String({ description"Exec ask mode (off|on-miss|always)." })),
nodeType.Optional(Type.String({ description"Node id/name for host=node." }))
});

三、完整执行代码

位置:第 16558 行

functioncreateExecTool(defaults) {
// 1. 解析默认配置
const defaultBackgroundMs = clampWithDefault(defaults?.backgroundMs ?? readEnvInt("PI_BASH_YIELD_MS"), 1e41012e4);
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. Use yieldMs/background to continue later via process tool. Use pty=true for TTY-required commands (terminal UIs, coding agents).",
parameters: execSchema,
executeasync (_toolCallId, args, signal, onUpdate) => {
// === 执行逻辑开始 ===
const params = args;

// 1. 参数验证
if (!params.commandthrownewError("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, 1012e4)) : 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,
maxOutputDEFAULT_MAX_OUTPUT,
pendingMaxOutputDEFAULT_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 === 0onYieldNow();
else yieldTimer = setTimeout(onYieldNow, yieldWindow);
            }

// 13. 等待执行完成
returnnewPromise((resolve, reject) => {
                run.promise.then((outcome) => {
if (yieldTimer) clearTimeout(yieldTimer);
if (yielded || run.session.backgroundedreturn;
resolve(buildExecForegroundResult({ outcome, cwd: run.session.cwdwarningTextgetWarningText() }));
                }).catch((err) => {
if (yieldTimer) clearTimeout(yieldTimer);
if (yielded || run.session.backgroundedreturn;
reject(err);
                });
            });
        }
    };
}

四、执行流程图

exec 工具调用
    ↓
1. 参数验证(command 必填)
    ↓
2. 解析后台运行请求(background/yieldMs)
    ↓
3. 解析提权请求(elevated)
    ↓
4. 确定主机(sandbox/gateway/node)
    ↓
5. Gateway 白名单检查
    ├─ 需要审批 → 返回 pending 状态,等待用户 /approve
    └─ 无需审批 → 继续
    ↓
6. 启动进程(runExecProcess)
    ├─ 创建子进程
    ├─ 监听输出
    ├─ 记录日志
    └─ 返回 session
    ↓
7. 判断是否后台运行
    ├─ 是 → 立即返回"正在运行",用户可用 process 工具管理
    └─ 否 → 等待完成,返回完整输出

五、返回结果格式

前台运行完成

{
"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"
}
}

六、参数详解

6.1 必填参数

参数
类型
说明
command
string
要执行的 shell 命令

6.2 可选参数

参数
类型
默认值
说明
workdir
string
cwd
工作目录
env
object
-
环境变量(键值对)
yieldMs
number
10000
后台运行等待时间(毫秒)
background
boolean
false
立即后台运行
timeout
number
1800
超时时间(秒)
pty
boolean
false
使用 PTY(TTY 命令需要)
elevated
boolean
false
提权执行
host
string
sandbox
执行主机(sandbox/gateway/node)
security
string
allowlist
安全模式(deny/allowlist/full)
ask
string
on-miss
审批模式(off/on-miss/always)
node
string
-
节点 ID(host=node 时使用)

七、使用示例

7.1 简单命令

{
"name""exec",
"arguments": {
"command""ls -la"
  }
}

7.2 后台运行

{
"name""exec",
"arguments": {
"command""python long_running_script.py",
"yieldMs"5000
  }
}

7.3 超时控制

{
"name""exec",
"arguments": {
"command""ping -n 100 www.example.com",
"timeout"30
  }
}

7.4 PTY 模式

{
"name""exec",
"arguments": {
"command""htop",
"pty"true
  }
}

7.5 环境变量

{
"name""exec",
"arguments": {
"command""echo $MY_VAR",
"env": {
"MY_VAR""Hello World"
    }
  }
}

八、安全机制

8.1 安全模式

模式
说明
deny
禁止所有命令
allowlist
只允许白名单中的命令
full
允许所有命令(需要提权)

8.2 审批模式

模式
说明
off
无需审批
on-miss
白名单未命中时审批
always
总是审批

8.3 提权执行

{
"name""exec",
"arguments": {
"command""apt update",
"elevated"true,
"host""gateway"
  }
}

前提条件

  • • elevatedDefaults.enabled = true
  • • elevatedDefaults.allowed = true

九、配套工具

process 工具

exec 的配套工具,用于管理后台进程:

Action
说明
list
列出所有进程
poll
轮询进程状态
log
读取进程日志
write
写入 stdin
send-keys
发送按键
paste
粘贴文本
submit
发送 EOF
kill
终止进程

如果文章对你有帮助,请点个“关注”

相关学习资料