OpenClaw工具拆解之browser+agents_list

一、browser 工具

1.1 工具概述

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

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

1.2 Schema 定义

位置:第 23854 行附近

javascript 复制代码
const BrowserToolSchema = 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 行

javascript 复制代码
function createBrowserTool(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") {
                throw new Error('node is only supported with target="node".');
            }
            
            // 4. 解析目标
            if (shouldPreferHostForProfile(profile)) {
                if (requestedNode || target === "node") {
                    throw new Error(`profile="${profile}" only supports the local host browser.`);
                }
                if (target === "sandbox") {
                    throw new Error(`profile="${profile}" cannot use the sandbox browser; use target="host" or omit target.`);
                }
                if (!target && !requestedNode) target = "host";
            }
            
            // 5. 解析节点目标
            const nodeTarget = await resolveBrowserNodeTarget({
                requestedNode: requestedNode ?? void 0,
                target,
                sandboxBridgeUrl: opts?.sandboxBridgeUrl
            });
            
            // 6. 解析基础 URL
            const baseUrl = nodeTarget ? void 0 : resolveBrowserBaseUrl({
                target: target === "node" ? void 0 : target,
                sandboxBridgeUrl: opts?.sandboxBridgeUrl,
                allowHostControl: opts?.allowHostControl
            });
            
            // 7. 构建代理请求(节点模式)
            const proxyRequest = nodeTarget ? async (opts) => {
                const proxy = await callBrowserProxy({
                    nodeId: nodeTarget.nodeId,
                    method: opts.method,
                    path: opts.path,
                    query: opts.query,
                    body: opts.body,
                    timeoutMs: opts.timeoutMs,
                    profile: opts.profile
                });
                const mapping = await persistProxyFiles(proxy.files);
                applyProxyPaths(proxy.result, mapping);
                return proxy.result;
            } : null;
            
            // 8. 根据 action 执行
            switch (action) {
                // === action: status ===
                case "status":
                    if (proxyRequest) {
                        return jsonResult(await proxyRequest({
                            method: "GET",
                            path: "/",
                            profile
                        }));
                    }
                    return jsonResult(await browserToolDeps.browserStatus(baseUrl, { profile }));
                
                // === action: start ===
                case "start":
                    if (proxyRequest) {
                        await proxyRequest({
                            method: "POST",
                            path: "/start",
                            profile
                        });
                        return jsonResult(await proxyRequest({
                            method: "GET",
                            path: "/",
                            profile
                        }));
                    }
                    await browserToolDeps.browserStart(baseUrl, { profile });
                    return jsonResult(await browserToolDeps.browserStatus(baseUrl, { profile }));
                
                // === action: stop ===
                case "stop":
                    if (proxyRequest) {
                        await proxyRequest({
                            method: "POST",
                            path: "/stop",
                            profile
                        });
                        return jsonResult(await proxyRequest({
                            method: "GET",
                            path: "/",
                            profile
                        }));
                    }
                    await browserToolDeps.browserStop(baseUrl, { profile });
                    return jsonResult(await browserToolDeps.browserStatus(baseUrl, { profile }));
                
                // === action: profiles ===
                case "profiles":
                    if (proxyRequest) {
                        return jsonResult(await proxyRequest({
                            method: "GET",
                            path: "/profiles"
                        }));
                    }
                    return jsonResult({ profiles: await browserToolDeps.browserProfiles(baseUrl) });
                
                // === action: tabs ===
                case "tabs":
                    return await executeTabsAction({ baseUrl, profile, proxyRequest });
                
                // === action: open ===
                case "open": {
                    const targetUrl = readTargetUrlParam(params);
                    if (proxyRequest) {
                        return jsonResult(await proxyRequest({
                            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
                    });
                    return jsonResult(opened);
                }
                
                // === action: focus ===
                case "focus": {
                    const targetId = readStringParam$1(params, "targetId", { required: true });
                    if (proxyRequest) {
                        return jsonResult(await proxyRequest({
                            method: "POST",
                            path: "/tabs/focus",
                            profile,
                            body: { targetId }
                        }));
                    }
                    await browserToolDeps.browserFocusTab(baseUrl, targetId, { profile });
                    return jsonResult({ ok: true });
                }
                
                // === action: close ===
                case "close": {
                    const targetId = readStringParam$1(params, "targetId");
                    if (proxyRequest) {
                        return jsonResult(targetId ? await proxyRequest({
                            method: "DELETE",
                            path: `/tabs/${encodeURIComponent(targetId)}`,
                            profile
                        }) : await proxyRequest({
                            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 });
                    }
                    return jsonResult({ ok: true });
                }
                
                // === action: snapshot ===
                case "snapshot":
                    return await executeSnapshotAction({ 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 ? await proxyRequest({
                        method: "POST",
                        path: "/screenshot",
                        profile,
                        body: { targetId, fullPage, ref, element, type }
                    }) : await browserToolDeps.browserScreenshotAction(baseUrl, {
                        targetId, fullPage, ref, element, type, profile
                    });
                    
                    return await 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) {
                        return jsonResult(await proxyRequest({
                            method: "POST",
                            path: "/navigate",
                            profile,
                            body: { url: targetUrl, targetId }
                        }));
                    }
                    await browserToolDeps.browserNavigate(baseUrl, { url: targetUrl, targetId, profile });
                    return jsonResult({ ok: true });
                }
                
                // === action: act ===
                case "act":
                    return await executeActAction({ input: params, baseUrl, profile, proxyRequest });
                
                // === 未知 action ===
                default:
                    throw new Error(`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 行附近

javascript 复制代码
const AgentsListToolSchema = Type.Object({});

2.3 完整执行代码

位置:第 22986 行

javascript 复制代码
function createAgentsListTool(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
            }));
            
            return jsonResult({
                status: "ok",
                count: result.length,
                agents: result
            });
        }
    };
}

2.4 返回结果格式

json 复制代码
{
  "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

大模型返回:

json 复制代码
{
  "tool_call": {
    "name": "browser",
    "arguments": { 
      "action": "open",
      "url": "https://google.com"
    }
  }
}

执行结果:

json 复制代码
{
  "targetId": "tab_abc123",
  "url": "https://google.com",
  "title": "Google",
  "status": "loaded"
}

4.2 agents_list 工具调用

用户 :列出所有可用的 Agent

大模型返回:

json 复制代码
{
  "tool_call": {
    "name": "agents_list",
    "arguments": {}
  }
}

执行结果:

json 复制代码
{
  "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
    }
  ]
}
相关推荐
小和尚同志8 小时前
1.8k star 的开源 token 使用量监控神器— TokenTracker
人工智能·ai编程
极客 - L U9 小时前
神经网络 - 激活函数、损失函数、优化器
人工智能·深度学习·神经网络
数字融合9 小时前
透明化视频三维矿山井下照明重建技术
人工智能·python·数码相机
yi0119 小时前
LeetCode 219:存在重复元素 II——哈希表记录“最近一次出现的位置”
数据结构·人工智能·笔记·python·算法·leetcode·哈希表
xiangzhihong89 小时前
创之星花店多端业务闭环拆解
人工智能
奈落2410 小时前
AI 编程从助手到 Agent:基于两份资料看哪些环节可以交出去,哪些必须自己攥住
大数据·人工智能
Joker可视化开发平台10 小时前
AI短剧接棒真人剧:开机量跌七成,普通人进场窗口在收窄
大数据·人工智能
澳鹏Appen10 小时前
澳鹏电子书 | 强化学习环境:为AI智能体打造高保真训练场
人工智能
吴佳浩10 小时前
单卡5090跑125B 大模型:Strata 把服务器级 MoE 拉进普通 PC
人工智能
Setsuna_F_Seiei10 小时前
前端转型 Agent 开发 06 之 Agent Memory 记忆系统(让 Agent 更智能,更懂你)
前端·agent·ai编程