OpenClaw工具拆解之subagents+gateway

一、subagents 工具

1.1 工具概述

功能 :管理已生成的子 agent
核心特性

  • 3 个 actions(list/kill/steer)
  • 支持按标签/序号/会话键定位
  • 支持批量终止(target=all)
  • 消息长度限制(4000 字符)
  • 最近活跃度过滤(recentMinutes)

1.2 Schema 定义

位置:第 112950 行

javascript 复制代码
const SubagentsToolSchema = Type.Object({
    action: optionalStringEnum$3(["list", "kill", "steer"]),
    target: Type.Optional(Type.String()),
    message: Type.Optional(Type.String()),
    recentMinutes: Type.Optional(Type.Number({ minimum: 1 }))
});

1.3 完整执行代码

位置:第 112961 行

javascript 复制代码
function createSubagentsTool(opts) {
    return {
        label: "Subagents",
        name: "subagents",
        description: "List, kill, or steer spawned sub-agents for this requester session. Use this for sub-agent orchestration.",
        parameters: SubagentsToolSchema,
        execute: async (_toolCallId, args) => {
            const params = args;
            
            // 1. 解析 action(默认 list)
            const action = readStringParam$1(params, "action") ?? "list";
            const cfg = loadConfig();
            
            // 2. 解析子 agent 控制器
            const controller = resolveSubagentController({
                cfg,
                agentSessionKey: opts?.agentSessionKey
            });
            
            // 3. 获取子 agent 运行列表
            const runs = listControlledSubagentRuns(controller.controllerSessionKey);
            const recentMinutesRaw = readNumberParam(params, "recentMinutes");
            const recentMinutes = recentMinutesRaw ? 
                Math.max(1, Math.min(MAX_RECENT_MINUTES, Math.floor(recentMinutesRaw))) : 30;
            
            const pendingDescendantCount = createPendingDescendantCounter();
            const isActive = (entry) => isActiveSubagentRun(entry, pendingDescendantCount);
            
            // === action: list ===
            if (action === "list") {
                const list = buildSubagentList({
                    cfg,
                    runs,
                    recentMinutes
                });
                
                return jsonResult({
                    status: "ok",
                    action: "list",
                    requesterSessionKey: controller.controllerSessionKey,
                    callerSessionKey: controller.callerSessionKey,
                    callerIsSubagent: controller.callerIsSubagent,
                    total: list.total,
                    active: list.active.map(({ line: _line, ...view }) => view),
                    recent: list.recent.map(({ line: _line, ...view }) => view),
                    text: list.text
                });
            }
            
            // === action: kill ===
            if (action === "kill") {
                const target = readStringParam$1(params, "target", { required: true });
                
                // 批量终止
                if (target === "all" || target === "*") {
                    const result = await killAllControlledSubagentRuns({
                        cfg,
                        controller,
                        runs
                    });
                    
                    if (result.status === "forbidden") {
                        return jsonResult({
                            status: "forbidden",
                            action: "kill",
                            target: "all",
                            error: result.error
                        });
                    }
                    
                    return jsonResult({
                        status: "ok",
                        action: "kill",
                        target: "all",
                        killed: result.killed,
                        labels: result.labels,
                        text: result.killed > 0 ? 
                            `killed ${result.killed} subagent${result.killed === 1 ? "" : "s"}.` : 
                            "no running subagents to kill."
                    });
                }
                
                // 单个终止
                const resolved = resolveControlledSubagentTarget(runs, target, {
                    recentMinutes,
                    isActive
                });
                
                if (!resolved.entry) {
                    return jsonResult({
                        status: "error",
                        action: "kill",
                        target,
                        error: resolved.error ?? "Unknown subagent target."
                    });
                }
                
                const result = await killControlledSubagentRun({
                    cfg,
                    controller,
                    entry: resolved.entry
                });
                
                return jsonResult({
                    status: result.status,
                    action: "kill",
                    target,
                    runId: result.runId,
                    sessionKey: result.sessionKey,
                    label: result.label,
                    cascadeKilled: "cascadeKilled" in result ? result.cascadeKilled : void 0,
                    cascadeLabels: "cascadeLabels" in result ? result.cascadeLabels : void 0,
                    error: "error" in result ? result.error : void 0,
                    text: result.text
                });
            }
            
            // === action: steer ===
            if (action === "steer") {
                const target = readStringParam$1(params, "target", { required: true });
                const message = readStringParam$1(params, "message", { required: true });
                
                // 检查消息长度
                if (message.length > 4000) {
                    return jsonResult({
                        status: "error",
                        action: "steer",
                        target,
                        error: `Message too long (${message.length} chars, max ${MAX_STEER_MESSAGE_CHARS}).`
                    });
                }
                
                const resolved = resolveControlledSubagentTarget(runs, target, {
                    recentMinutes,
                    isActive
                });
                
                if (!resolved.entry) {
                    return jsonResult({
                        status: "error",
                        action: "steer",
                        target,
                        error: resolved.error ?? "Unknown subagent target."
                    });
                }
                
                const result = await steerControlledSubagentRun({
                    cfg,
                    controller,
                    entry: resolved.entry,
                    message
                });
                
                return jsonResult({
                    status: result.status,
                    action: "steer",
                    target,
                    runId: result.runId,
                    sessionKey: result.sessionKey,
                    sessionId: result.sessionId,
                    mode: "mode" in result ? result.mode : void 0,
                    label: "label" in result ? result.label : void 0,
                    error: "error" in result ? result.error : void 0,
                    text: result.text
                });
            }
            
            return jsonResult({
                status: "error",
                error: "Unsupported action."
            });
        }
    };
}

1.4 子 agent 定位逻辑

javascript 复制代码
function resolveControlledSubagentTarget(runs, target, options) {
    const { recentMinutes, isActive } = options;
    
    // 1. 排序运行列表(按开始时间倒序)
    const sorted = sortSubagentRuns(runs);
    
    // 2. 去重(按 childSessionKey)
    const deduped = [];
    const seenChildSessionKeys = new Set();
    for (const entry of sorted) {
        if (seenChildSessionKeys.has(entry.childSessionKey)) continue;
        seenChildSessionKeys.add(entry.childSessionKey);
        deduped.push(entry);
    }
    
    // 3. 特殊值:last
    if (target === "last") {
        return { entry: deduped[0] };
    }
    
    // 4. 序号定位(1-based)
    if (/^\d+$/.test(target)) {
        const idx = Number.parseInt(target, 10);
        const numericOrder = [
            ...deduped.filter((entry) => isActive(entry)),
            ...deduped.filter((entry) => !isActive(entry) && !!entry.endedAt && (entry.endedAt ?? 0) >= recentCutoff)
        ];
        if (!Number.isFinite(idx) || idx <= 0 || idx > numericOrder.length) {
            return { error: `Invalid index: ${target}` };
        }
        return { entry: numericOrder[idx - 1] };
    }
    
    // 5. 会话键定位
    if (target.includes(":")) {
        const bySessionKey = deduped.find((entry) => entry.childSessionKey === target);
        return bySessionKey ? { entry: bySessionKey } : { error: `Unknown session: ${target}` };
    }
    
    // 6. 精确标签匹配
    const lowered = target.toLowerCase();
    const byExactLabel = deduped.filter((entry) => entry.label.toLowerCase() === lowered);
    if (byExactLabel.length === 1) {
        return { entry: byExactLabel[0] };
    }
    if (byExactLabel.length > 1) {
        return { error: `Ambiguous label: ${target}` };
    }
    
    // 7. 标签前缀匹配
    const byLabelPrefix = deduped.filter((entry) => entry.label.toLowerCase().startsWith(lowered));
    if (byLabelPrefix.length === 1) {
        return { entry: byLabelPrefix[0] };
    }
    if (byLabelPrefix.length > 1) {
        return { error: `Ambiguous label prefix: ${target}` };
    }
    
    // 8. runId 前缀匹配
    const byRunIdPrefix = deduped.filter((entry) => entry.runId.startsWith(target));
    if (byRunIdPrefix.length === 1) {
        return { entry: byRunIdPrefix[0] };
    }
    if (byRunIdPrefix.length > 1) {
        return { error: `Ambiguous runId prefix: ${target}` };
    }
    
    return { error: `Unknown target: ${target}` };
}

1.5 执行流程图

复制代码
subagents 工具调用
    ↓
1. 解析 action(list/kill/steer)
    ↓
2. 解析子 agent 控制器
    ↓
3. 获取子 agent 运行列表
    ↓
4. 根据 action 执行
    ├─ list → 构建列表(活跃 + 最近)
    ├─ kill → 终止子 agent
    │  ├─ target=all → 批量终止
    │  └─ target=xxx → 定位后终止
    └─ steer → 发送消息给子 agent
       ├─ 检查消息长度(≤4000)
       ├─ 定位子 agent
       └─ 发送消息
    ↓
5. 返回结果

1.6 返回结果格式

list 成功

json 复制代码
{
  "status": "ok",
  "action": "list",
  "total": 5,
  "active": [
    {
      "runId": "abc123",
      "label": "文件分析",
      "status": "running",
      "startedAt": 1711716000000,
      "runtimeMs": 60000
    }
  ],
  "recent": [...],
  "text": "Active:\n[1] 文件分析 (running, 1m)\n..."
}

kill 成功

json 复制代码
{
  "status": "ok",
  "action": "kill",
  "target": "1",
  "killed": 1,
  "labels": ["文件分析"],
  "text": "killed 1 subagent."
}

steer 成功

json 复制代码
{
  "status": "ok",
  "action": "steer",
  "target": "文件分析",
  "runId": "abc123",
  "sessionKey": "subagent:abc123",
  "text": "Message sent to subagent."
}

二、gateway 工具

2.1 工具概述

功能 :管理 Gateway 服务
核心特性

  • 仅所有者可用(ownerOnly=true)
  • 6 个 actions(restart/config.get/config.schema.lookup/config.apply/config.patch/update.run)
  • 配置写入需要 baseHash(乐观锁)
  • 重启后通知用户(note 参数)
  • SIGUSR1 信号重启

2.2 Schema 定义

位置:第 25780 行

javascript 复制代码
const GatewayToolSchema = Type.Object({
    action: stringEnum([
        "restart",
        "config.get",
        "config.schema.lookup",
        "config.apply",
        "config.patch",
        "update.run"
    ]),
    delayMs: Type.Optional(Type.Number()),
    reason: Type.Optional(Type.String()),
    gatewayUrl: Type.Optional(Type.String()),
    gatewayToken: Type.Optional(Type.String()),
    timeoutMs: Type.Optional(Type.Number()),
    path: Type.Optional(Type.String()),
    raw: Type.Optional(Type.String()),
    baseHash: Type.Optional(Type.String()),
    sessionKey: Type.Optional(Type.String()),
    note: Type.Optional(Type.String()),
    restartDelayMs: Type.Optional(Type.Number())
});

2.3 完整执行代码

位置:第 25801 行

javascript 复制代码
function createGatewayTool(opts) {
    return {
        label: "Gateway",
        name: "gateway",
        ownerOnly: true,
        description: "Restart, inspect a specific config schema path, apply config, or update the gateway in-place (SIGUSR1). Use config.schema.lookup with a targeted dot path before config edits. Use config.patch for safe partial config updates (merges with existing). Use config.apply only when replacing entire config. Both trigger restart after writing. Always pass a human-readable completion message via the `note` parameter so the system can deliver it to the user after restart.",
        parameters: GatewayToolSchema,
        execute: async (_toolCallId, args) => {
            const params = args;
            
            // 1. 解析 action(必填)
            const action = readStringParam$1(params, "action", { required: true });
            
            // === action: restart ===
            if (action === "restart") {
                // 检查重启是否启用
                if (!isRestartEnabled(opts?.config)) {
                    throw new Error("Gateway restart is disabled (commands.restart=false).");
                }
                
                const sessionKey = typeof params.sessionKey === "string" && params.sessionKey.trim() ? 
                    params.sessionKey.trim() : opts?.agentSessionKey?.trim() || void 0;
                const delayMs = typeof params.delayMs === "number" && Number.isFinite(params.delayMs) ? 
                    Math.floor(params.delayMs) : void 0;
                const reason = typeof params.reason === "string" && params.reason.trim() ? 
                    params.reason.trim().slice(0, 200) : void 0;
                const note = typeof params.note === "string" && params.note.trim() ? 
                    params.note.trim() : void 0;
                
                const { deliveryContext, threadId } = extractDeliveryInfo(sessionKey);
                
                // 写入重启 sentinel
                const payload = {
                    kind: "restart",
                    status: "ok",
                    ts: Date.now(),
                    sessionKey,
                    deliveryContext,
                    threadId,
                    message: note ?? reason ?? null,
                    doctorHint: formatDoctorNonInteractiveHint(),
                    stats: {
                        mode: "gateway.restart",
                        reason
                    }
                };
                
                try {
                    await writeRestartSentinel(payload);
                } catch {}
                
                log$28.info(`gateway tool: restart requested (delayMs=${delayMs ?? "default"}, reason=${reason ?? "none"})`);
                
                return jsonResult(scheduleGatewaySigusr1Restart({
                    delayMs,
                    reason
                }));
            }
            
            // 读取 Gateway 调用选项
            const gatewayOpts = readGatewayCallOptions(params);
            
            // 解析 Gateway 写入元数据
            const resolveGatewayWriteMeta = () => {
                return {
                    sessionKey: typeof params.sessionKey === "string" && params.sessionKey.trim() ? 
                        params.sessionKey.trim() : opts?.agentSessionKey?.trim() || void 0,
                    note: typeof params.note === "string" && params.note.trim() ? 
                        params.note.trim() : void 0,
                    restartDelayMs: typeof params.restartDelayMs === "number" && Number.isFinite(params.restartDelayMs) ? 
                        Math.floor(params.restartDelayMs) : void 0
                };
            };
            
            // 解析配置写入参数
            const resolveConfigWriteParams = async () => {
                const raw = readStringParam$1(params, "raw", { required: true });
                let baseHash = readStringParam$1(params, "baseHash");
                
                if (!baseHash) {
                    baseHash = resolveBaseHashFromSnapshot(
                        await callGatewayTool("config.get", gatewayOpts, {})
                    );
                }
                
                if (!baseHash) {
                    throw new Error("Missing baseHash from config snapshot.");
                }
                
                return {
                    raw,
                    baseHash,
                    ...resolveGatewayWriteMeta()
                };
            };
            
            // === action: config.get ===
            if (action === "config.get") {
                return jsonResult({
                    ok: true,
                    result: await callGatewayTool("config.get", gatewayOpts, {})
                });
            }
            
            // === action: config.schema.lookup ===
            if (action === "config.schema.lookup") {
                return jsonResult({
                    ok: true,
                    result: await callGatewayTool("config.schema.lookup", gatewayOpts, {
                        path: readStringParam$1(params, "path", {
                            required: true,
                            label: "path"
                        })
                    })
                });
            }
            
            // === action: config.apply ===
            if (action === "config.apply") {
                const { raw, baseHash, sessionKey, note, restartDelayMs } = await resolveConfigWriteParams();
                
                return jsonResult({
                    ok: true,
                    result: await callGatewayTool("config.apply", gatewayOpts, {
                        raw,
                        baseHash,
                        sessionKey,
                        note,
                        restartDelayMs
                    })
                });
            }
            
            // === action: config.patch ===
            if (action === "config.patch") {
                const { raw, baseHash, sessionKey, note, restartDelayMs } = await resolveConfigWriteParams();
                
                return jsonResult({
                    ok: true,
                    result: await callGatewayTool("config.patch", gatewayOpts, {
                        raw,
                        baseHash,
                        sessionKey,
                        note,
                        restartDelayMs
                    })
                });
            }
            
            // === action: update.run ===
            if (action === "update.run") {
                const { sessionKey, note, restartDelayMs } = resolveGatewayWriteMeta();
                const updateTimeoutMs = gatewayOpts.timeoutMs ?? DEFAULT_UPDATE_TIMEOUT_MS;
                
                return jsonResult({
                    ok: true,
                    result: await callGatewayTool("update.run", {
                        ...gatewayOpts,
                        timeoutMs: updateTimeoutMs
                    }, {
                        sessionKey,
                        note,
                        restartDelayMs,
                        timeoutMs: updateTimeoutMs
                    })
                });
            }
            
            throw new Error(`Unknown action: ${action}`);
        }
    };
}

2.5 配置写入流程

javascript 复制代码
// 1. 获取当前配置快照
const config = await callGatewayTool("config.get", gatewayOpts, {});

// 2. 提取 baseHash(乐观锁)
const baseHash = resolveBaseHashFromSnapshot(config);

// 3. 准备新配置
const raw = JSON.stringify({ ...config, newSetting: "value" });

// 4. 写入配置(config.apply 或 config.patch)
await callGatewayTool("config.apply", gatewayOpts, {
    raw,
    baseHash,
    sessionKey,
    note,
    restartDelayMs
});

// 5. Gateway 自动重启(SIGUSR1)

2.6 执行流程图

复制代码
gateway 工具调用
    ↓
1. 解析 action(必填)
    ↓
2. 根据 action 执行
    ├─ restart → 写入 sentinel + SIGUSR1
    ├─ config.get → 获取配置
    ├─ config.schema.lookup → 查询 schema
    ├─ config.apply → 应用完整配置
    ├─ config.patch → 应用部分配置
    └─ update.run → 执行更新
    ↓
3. 返回结果

2.7 返回结果格式

restart 成功

json 复制代码
{
  "ok": true,
  "result": {
    "status": "scheduled",
    "delayMs": 1000,
    "reason": "config update"
  }
}

config.get 成功

json 复制代码
{
  "ok": true,
  "result": {
    "hash": "abc123",
    "config": { ... }
  }
}

config.apply 失败(baseHash 不匹配)

json 复制代码
{
  "ok": false,
  "error": "Config hash mismatch. Please refresh and retry."
}

三、关键机制对比

3.1 权限控制

特性 subagents gateway
所有者限制 ownerOnly=true
A2A 策略 不需要 不需要
可见性检查 不需要 不需要

3.2 操作类型

特性 subagents gateway
actions list/kill/steer restart/config.* /update.run
定位目标 target 参数 不需要
批量操作 target=all 不支持

3.3 安全限制

限制类型 subagents gateway
消息长度 4000 字符 不支持
乐观锁 不需要 baseHash 必需
重启限制 不需要 commands.restart

四、使用示例

4.1 subagents 工具调用

用户列出所有子 agent

大模型返回

json 复制代码
{
  "tool_call": {
    "name": "subagents",
    "arguments": { "action": "list" }
  }
}

执行结果

json 复制代码
{
  "status": "ok",
  "action": "list",
  "total": 3,
  "active": [...],
  "recent": [...],
  "text": "Active:\n[1] 文件分析 (running, 1m)\n[2] 数据处理 (running, 5m)\n..."
}

4.2 gateway 工具调用

用户重启 Gateway

大模型返回

json 复制代码
{
  "tool_call": {
    "name": "gateway",
    "arguments": { 
      "action": "restart",
      "note": "配置更新完成"
    }
  }
}

执行结果

json 复制代码
{
  "ok": true,
  "result": {
    "status": "scheduled",
    "delayMs": 1000,
    "reason": null
  }
}
相关推荐
a9511416422 小时前
如何编写带默认值的SQL存储过程_简化前端调用接口设计
jvm·数据库·python
耿雨飞2 小时前
Python 后端开发技术博客专栏 | 第 05 篇 Python 数据模型与标准库精选 -- 写出 Pythonic 的代码
开发语言·python
weixin_408717772 小时前
如何用 CSS 动画与 animationend 事件实现循环渐进式圆点动画
jvm·数据库·python
2301_773553622 小时前
如何自定义修改 Traccar Web 界面模板
jvm·数据库·python
m0_716430072 小时前
CSS如何让响应式图片在容器内居中_利用background-position
jvm·数据库·python
七夜zippoe2 小时前
OpenClaw 定时任务与自动化:Cron 详解
运维·人工智能·自动化·cron·openclaw
djjdjdjdjjdj2 小时前
如何利用 watchEffect 实现在线人数实时统计?Socket 与响应式结合
jvm·数据库·python
啦啦啦_99992 小时前
0. 工具使用
python
执笔画流年呀2 小时前
计算机是如何⼯作的
linux·开发语言·python