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
    }
  ]
}
相关推荐
一念杂记2 小时前
零基础训练出懂你的AI助手——工作流、Skill、MCP服务
人工智能·ai编程
蔡俊锋2 小时前
AI代理落地指南:从Demo到生产级的实战攻略
人工智能·深度学习·hermes·ai团队知识沉淀
小龙报2 小时前
【数据结构与算法】一文拿捏链式二叉树:遍历 + 统计 + 层序 + 完全树
java·c语言·开发语言·c++·人工智能·语言模型·visual studio
最后一只小白2 小时前
聊天状态以及流畅运行
人工智能·语音识别
mahtengdbb12 小时前
SimAM无参数注意力机制改进YOLOv26神经科学启发的自适应特征增强突破
人工智能·yolo·目标跟踪
JarvanMo2 小时前
八个开源Flutter应用,让你成为更好的开发者
前端
小艳加油2 小时前
AI引领自然科学全流程革新:生物、地球、农业、气象、生态、环境、GIS案例实战+Python/R代码+科研绘图+时空大数据
机器学习·统计分析·自然科学
喵叔哟2 小时前
10.【.NET10 实战--孢子记账--产品智能化】--其余第三方包批量升级
人工智能·.net
做cv的小昊2 小时前
【TJU】研究生应用统计学课程笔记(5)——第二章 参数估计(2.3 C-R不等式)
c语言·笔记·线性代数·机器学习·数学建模·r语言·概率论