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({
action: stringEnum(BROWSER_ACTIONS),
profile: Type.Optional(Type.String()),
node: Type.Optional(Type.String()),
target: Type.Optional(Type.String()),
url: Type.Optional(Type.String()),
targetId: Type.Optional(Type.String()),
fullPage: Type.Optional(Type.Boolean()),
ref: Type.Optional(Type.String()),
element: Type.Optional(Type.String()),
type: Type.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(" "),
parameters: BrowserToolSchema,
execute: async (_toolCallId, args) => {
const params = args;
// 2. 解析 action(必填)
const action = readStringParam$1(params, "action", { required: true });
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({ profiles: await 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", { required: true });
if (proxyRequest) {
returnjsonResult(awaitproxyRequest({
method: "POST",
path: "/tabs/focus",
profile,
body: { targetId }
}));
}
await browserToolDeps.browserFocusTab(baseUrl, targetId, { profile });
returnjsonResult({ ok: true });
}
// === 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({ ok: true });
}
// === 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({ ok: true });
}
// === action: act ===
case"act":
returnawaitexecuteActAction({ input: params, baseUrl, profile, proxyRequest });
// === 未知 action ===
default:
thrownewError(`Unknown action: ${action}`);
}
}
};
}
1.4 支持的 Actions
|
|
|
|
status |
|
|
start |
|
|
stop |
|
|
profiles |
|
|
tabs |
|
|
open |
|
|
focus |
|
|
close |
|
|
snapshot |
|
|
screenshot |
|
|
navigate |
|
|
act |
|
|
1.5 配置文件
|
|
|
|
| openclaw |
|
|
| user |
|
|
二、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.",
parameters: AgentsListToolSchema,
execute: async (_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 功能定位
|
|
|
|
| 用途 |
|
|
| 复杂度 |
|
|
| 外部依赖 |
|
|
3.2 操作类型
|
|
|
|
| actions 数量 |
|
|
| 写操作 |
|
|
| 媒体支持 |
|
|
3.3 安全限制
|
|
|
|
| 沙盒支持 |
|
|
| 主机控制 |
|
|
| 节点代理 |
|
|
四、使用示例
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
}
]
}
夜雨聆风