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
    }
  ]
}
相关推荐
AAwangzong3 小时前
AI时代RPA技术解析:如何构建兼顾流程执行与智能决策的自动化平台?
人工智能·经验分享·rpa·智能体
达达表哥3 小时前
AI综合站商业分发系统v3.1纯净版发布 附完整安装教程(排版修正版)
人工智能
众壹新能源科技3 小时前
逆变器直流开关柜温度监测与预防:红外巡检发现3个120度热点
运维·人工智能·自动化
NeoGressAI外贸数字化3 小时前
外贸独立站robots.txt怎么写:6个AI爬虫放行规则
人工智能·爬虫
米软科技4 小时前
数字化转型:多元化集团如何构建高效的AI数字化中台?
大数据·人工智能
幼儿园蛋小黄4 小时前
从 STM32N647 的 AI 部署入门嵌入式:一条可落地的学习路线
人工智能·stm32·学习
Leo.yuan4 小时前
Dora企业级Data Agent应用指南:存量BI复用、私有化部署与权限管理
大数据·人工智能
火山引擎开发者社区4 小时前
从代码执行到任务持续运行:企业级 Agent Sandbox 的云原生实践
人工智能
Nablai4 小时前
LX系列多国语言能力技术解析:ESP32双栈架构下的语种策略设计
人工智能·架构
熊猫钓鱼>_>4 小时前
四种书签,一条时间线:一个读书 App 的记忆颗粒与它的边界案例【鸿蒙心迹】
开发语言·华为·ai编程·harmonyos·鸿蒙·openharmony·鸿蒙pc