乐于分享
好东西不私藏

OpenClaw工具拆解之browser+agents_list

OpenClaw工具拆解之browser+agents_list

一、browser 工具

1.1 工具概述

功能:控制浏览器(自动化)
核心特性

  • • 支持多种操作(status/start/stop/profiles/tabs/open/snapshot/screenshot/actions)
  • • 支持沙盒桥接
  • • 支持节点代理
  • • 支持配置文件选择(openclaw/user)
  • • 支持 Playwright 自动化

1.2 Schema 定义

位置:第 23854 行附近

constBrowserToolSchema = Type.Object({
actionstringEnum(BROWSER_ACTIONS),
profileType.Optional(Type.String()),
nodeType.Optional(Type.String()),
targetType.Optional(Type.String()),
urlType.Optional(Type.String()),
targetIdType.Optional(Type.String()),
fullPageType.Optional(Type.Boolean()),
refType.Optional(Type.String()),
elementType.Optional(Type.String()),
typeType.Optional(Type.String())
// ... 更多参数
});

1.3 完整执行代码(部分)

位置:第 23854 行

functioncreateBrowserTool(opts) {
// 1. 解析目标默认值
const targetDefault = opts?.sandboxBridgeUrl ? "sandbox" : "host";
const hostHint = opts?.allowHostControl === false ? 
"Host target blocked by policy." : "Host target allowed.";

return {
label"Browser",
name"browser",
description: [
"Control the browser via OpenClaw's browser control server (status/start/stop/profiles/tabs/open/snapshot/screenshot/actions).",
"Browser choice: omit profile by default for the isolated OpenClaw-managed browser (`openclaw`).",
'For the logged-in user browser on the local host, use profile="user". A supported Chromium-based browser (v144+) must be running. Use only when existing logins/cookies matter and the user is present.',
"When a node-hosted browser proxy is available, the tool may auto-route to it. Pin a node with node=<id|name> or target=\"node\".",
"When using refs from snapshot (e.g. e12), keep the same tab: prefer passing targetId from the snapshot response into subsequent actions (act/click/type/etc).",
'For stable, self-resolving refs across calls, use snapshot with refs="aria" (Playwright aria-ref ids). Default refs="role" are role+name-based.',
"Use snapshot+act for UI automation. Avoid act:wait by default; use only in exceptional cases when no reliable UI state exists.",
`target selects browser location (sandbox|host|node). Default: ${targetDefault}.`,
            hostHint
        ].join(" "),
parametersBrowserToolSchema,
executeasync (_toolCallId, args) => {
const params = args;

// 2. 解析 action(必填)
const action = readStringParam$1(params, "action", { requiredtrue });
const profile = readStringParam$1(params, "profile");
const requestedNode = readStringParam$1(params, "node");
let target = readStringParam$1(params, "target");

// 3. 检查节点限制
if (requestedNode && target && target !== "node") {
thrownewError('node is only supported with target="node".');
            }

// 4. 解析目标
if (shouldPreferHostForProfile(profile)) {
if (requestedNode || target === "node") {
thrownewError(`profile="${profile}" only supports the local host browser.`);
                }
if (target === "sandbox") {
thrownewError(`profile="${profile}" cannot use the sandbox browser; use target="host" or omit target.`);
                }
if (!target && !requestedNode) target = "host";
            }

// 5. 解析节点目标
const nodeTarget = awaitresolveBrowserNodeTarget({
requestedNode: requestedNode ?? void0,
                target,
sandboxBridgeUrl: opts?.sandboxBridgeUrl
            });

// 6. 解析基础 URL
const baseUrl = nodeTarget ? void0 : resolveBrowserBaseUrl({
target: target === "node" ? void0 : target,
sandboxBridgeUrl: opts?.sandboxBridgeUrl,
allowHostControl: opts?.allowHostControl
            });

// 7. 构建代理请求(节点模式)
const proxyRequest = nodeTarget ? async (opts) => {
const proxy = awaitcallBrowserProxy({
nodeId: nodeTarget.nodeId,
method: opts.method,
path: opts.path,
query: opts.query,
body: opts.body,
timeoutMs: opts.timeoutMs,
profile: opts.profile
                });
const mapping = awaitpersistProxyFiles(proxy.files);
applyProxyPaths(proxy.result, mapping);
return proxy.result;
            } : null;

// 8. 根据 action 执行
switch (action) {
// === action: status ===
case"status":
if (proxyRequest) {
returnjsonResult(awaitproxyRequest({
method"GET",
path"/",
                            profile
                        }));
                    }
returnjsonResult(await browserToolDeps.browserStatus(baseUrl, { profile }));

// === action: start ===
case"start":
if (proxyRequest) {
awaitproxyRequest({
method"POST",
path"/start",
                            profile
                        });
returnjsonResult(awaitproxyRequest({
method"GET",
path"/",
                            profile
                        }));
                    }
await browserToolDeps.browserStart(baseUrl, { profile });
returnjsonResult(await browserToolDeps.browserStatus(baseUrl, { profile }));

// === action: stop ===
case"stop":
if (proxyRequest) {
awaitproxyRequest({
method"POST",
path"/stop",
                            profile
                        });
returnjsonResult(awaitproxyRequest({
method"GET",
path"/",
                            profile
                        }));
                    }
await browserToolDeps.browserStop(baseUrl, { profile });
returnjsonResult(await browserToolDeps.browserStatus(baseUrl, { profile }));

// === action: profiles ===
case"profiles":
if (proxyRequest) {
returnjsonResult(awaitproxyRequest({
method"GET",
path"/profiles"
                        }));
                    }
returnjsonResult({ profilesawait browserToolDeps.browserProfiles(baseUrl) });

// === action: tabs ===
case"tabs":
returnawaitexecuteTabsAction({ baseUrl, profile, proxyRequest });

// === action: open ===
case"open": {
const targetUrl = readTargetUrlParam(params);
if (proxyRequest) {
returnjsonResult(awaitproxyRequest({
method"POST",
path"/tabs/open",
                            profile,
body: { url: targetUrl }
                        }));
                    }
const opened = await browserToolDeps.browserOpenTab(baseUrl, targetUrl, { profile });
                    browserToolDeps.trackSessionBrowserTab({
sessionKey: opts?.agentSessionKey,
targetId: opened.targetId,
                        baseUrl,
                        profile
                    });
returnjsonResult(opened);
                }

// === action: focus ===
case"focus": {
const targetId = readStringParam$1(params, "targetId", { requiredtrue });
if (proxyRequest) {
returnjsonResult(awaitproxyRequest({
method"POST",
path"/tabs/focus",
                            profile,
body: { targetId }
                        }));
                    }
await browserToolDeps.browserFocusTab(baseUrl, targetId, { profile });
returnjsonResult({ oktrue });
                }

// === action: close ===
case"close": {
const targetId = readStringParam$1(params, "targetId");
if (proxyRequest) {
returnjsonResult(targetId ? awaitproxyRequest({
method"DELETE",
path`/tabs/${encodeURIComponent(targetId)}`,
                            profile
                        }) : awaitproxyRequest({
method"POST",
path"/act",
                            profile,
body: { kind"close" }
                        }));
                    }
if (targetId) {
await browserToolDeps.browserCloseTab(baseUrl, targetId, { profile });
                        browserToolDeps.untrackSessionBrowserTab({
sessionKey: opts?.agentSessionKey,
                            targetId,
                            baseUrl,
                            profile
                        });
                    } else {
await browserToolDeps.browserAct(baseUrl, { kind"close" }, { profile });
                    }
returnjsonResult({ oktrue });
                }

// === action: snapshot ===
case"snapshot":
returnawaitexecuteSnapshotAction({ input: params, baseUrl, profile, proxyRequest });

// === action: screenshot ===
case"screenshot": {
const targetId = readStringParam$1(params, "targetId");
const fullPage = Boolean(params.fullPage);
const ref = readStringParam$1(params, "ref");
const element = readStringParam$1(params, "element");
const type = params.type === "jpeg" ? "jpeg" : "png";

const result = proxyRequest ? awaitproxyRequest({
method"POST",
path"/screenshot",
                        profile,
body: { targetId, fullPage, ref, element, type }
                    }) : await browserToolDeps.browserScreenshotAction(baseUrl, {
                        targetId, fullPage, ref, element, type, profile
                    });

returnawait browserToolDeps.imageResultFromFile({
label"browser:screenshot",
path: result.path,
details: result
                    });
                }

// === action: navigate ===
case"navigate": {
const targetUrl = readTargetUrlParam(params);
const targetId = readStringParam$1(params, "targetId");
if (proxyRequest) {
returnjsonResult(awaitproxyRequest({
method"POST",
path"/navigate",
                            profile,
body: { url: targetUrl, targetId }
                        }));
                    }
await browserToolDeps.browserNavigate(baseUrl, { url: targetUrl, targetId, profile });
returnjsonResult({ oktrue });
                }

// === action: act ===
case"act":
returnawaitexecuteActAction({ input: params, baseUrl, profile, proxyRequest });

// === 未知 action ===
default:
thrownewError(`Unknown action: ${action}`);
            }
        }
    };
}

1.4 支持的 Actions

Action
说明
必需参数
status
获取浏览器状态
start
启动浏览器
stop
停止浏览器
profiles
列出配置文件
tabs
列出标签页
open
打开 URL
url
focus
聚焦标签页
targetId
close
关闭标签页
snapshot
获取页面快照
screenshot
截图
navigate
导航
url
act
执行操作(点击/输入等)
action

1.5 配置文件

Profile
说明
使用场景
openclaw
OpenClaw 管理的隔离浏览器
默认,无需登录
user
用户已登录的浏览器
需要 Cookie/登录状态

二、agents_list 工具

2.1 工具概述

功能:列出可用的 Agent
核心特性

  • • 读取配置文件
  • • 支持过滤
  • • 支持排序
  • • 返回 Agent 元数据

2.2 Schema 定义

位置:第 22980 行附近

constAgentsListToolSchema = Type.Object({});

2.3 完整执行代码

位置:第 22986 行

functioncreateAgentsListTool(opts) {
return {
label"Agents List",
name"agents_list",
description"List available agents configured in the system.",
parametersAgentsListToolSchema,
executeasync (_toolCallId, args) => {
// 1. 加载配置
const cfg = opts?.config ?? loadConfig();

// 2. 解析 Agent 列表
const agents = cfg?.agents?.list ?? [];

// 3. 构建结果
const result = agents.map((agent) => ({
id: agent.id,
label: agent.label,
description: agent.description,
model: agent.model,
thinking: agent.thinking,
verbose: agent.verbose,
tools: agent.tools,
sandbox: agent.sandbox,
enabled: agent.enabled !== false
            }));

returnjsonResult({
status"ok",
count: result.length,
agents: result
            });
        }
    };
}

2.4 返回结果格式

{
"status":"ok",
"count":3,
"agents":[
{
"id":"main",
"label":"Main Agent",
"description":"Primary assistant agent",
"model":"bailian/qwen3.5-plus",
"thinking":"high",
"verbose":false,
"tools":["read","write","exec", ...],
"sandbox":{"enabled":false},
"enabled":true
},
{
"id":"coding",
"label":"Coding Agent",
"description":"Specialized coding assistant",
"model":"bailian/qwen3.5-coder",
"thinking":"high",
"verbose":false,
"tools":["read","write","edit","exec"],
"sandbox":{"enabled":true},
"enabled":true
}
]
}

三、关键机制对比

3.1 功能定位

特性
browser
agents_list
用途
浏览器自动化
Agent 配置查询
复杂度
高(多 actions)
低(只读)
外部依赖
浏览器服务

3.2 操作类型

特性
browser
agents_list
actions 数量
12+ 个
1 个(隐式 list)
写操作
start/stop/open
只读
媒体支持
截图/快照
不支持

3.3 安全限制

限制类型
browser
agents_list
沙盒支持
sandboxBridgeUrl
不支持
主机控制
allowHostControl
不支持
节点代理
支持
不支持

四、使用示例

4.1 browser 工具调用

用户打开浏览器并访问 Google

大模型返回

{
"tool_call":{
"name":"browser",
"arguments":{
"action":"open",
"url":"https://google.com"
}
}
}

执行结果

{
"targetId":"tab_abc123",
"url":"https://google.com",
"title":"Google",
"status":"loaded"
}

4.2 agents_list 工具调用

用户列出所有可用的 Agent

大模型返回

{
"tool_call":{
"name":"agents_list",
"arguments":{}
}
}

执行结果

{
"status":"ok",
"count":2,
"agents":[
{
"id":"main",
"label":"Main Agent",
"model":"bailian/qwen3.5-plus",
"enabled":true
},
{
"id":"coding",
"label":"Coding Agent",
"model":"bailian/qwen3.5-coder",
"enabled":true
}
]
}