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
    }
  ]
}
相关推荐
Koi慢热6 小时前
DayDayMap学术社区体验:卫星网络测绘专题用下来怎么样
网络·人工智能·windows·web安全·网络安全
净水深流6 小时前
中央厨房冷链技术实践:多温区改造、WMS落地与IoT温控架构
大数据·数据库·人工智能·冷库冷链
万联WANFLOW6 小时前
从“连接网络”到“编排业务”:企业网络架构正在发生什么变化?
网络·人工智能
MatrixOrigin7 小时前
从 App + Database 到 Agent + Context:下一代企业 AI Infra 的架构推演
人工智能·ai-native·矩阵起源·企业ai基础设施·ai state
mCell7 小时前
用 Knip 清理 AI Coding 留下的冗余代码
前端·ai编程·前端工程化
是Yu欸7 小时前
鸿蒙PC移植:2048 从网页小游戏到 AI 桌面应用
大数据·人工智能·算法·数据挖掘·openharmony·codex
熠速7 小时前
HIL测试中的总线与通信协议
人工智能·自动驾驶·仿真测试·硬件在环半实物仿真
马良神笔7 小时前
RRF 融合
人工智能
smartpi_ai7 小时前
离线语音模块误识别问题全面解决方案:命令词设计、防误触与灵敏度调优指南
人工智能·单片机·语音识别
湘美书院--湘美谈教育7 小时前
湘美书院AI时代的禅悟集:人机智能,反照诸我
大数据·人工智能·深度学习·机器学习·生活