乐于分享
好东西不私藏

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({
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

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.",
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 功能定位

特性
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
}
]
}