@clawhub-pikaqiuyaya-79fcb8e952
Send private or group messages via the current Agent's Feishu app, showing the configured bot name and handling app-isolated open_id.
# feishu-send-message Skill
飞书多 Agent 消息发送技能。让 Ass/Ops 用自己的飞书应用发送消息(群聊/私聊),解决 open_id 应用隔离问题。
## 核心功能
- 使用当前 Agent 的飞书凭证发送消息
- 支持群聊和私聊两种模式
- 飞书显示对应的机器人名字(如"小助手"或"运维助手")
## 使用场景
- Boss 派发任务后,Ass/Ops 用自己的身份回复消息
- 需要显示不同机器人名字的场景
## 使用方法
### 方法 1:直接使用脚本
```bash
# 获取用户 open_id(从当前 Agent 网关日志)
journalctl --user -u openclaw-gateway-ass.service | grep "received message from"
# Ass 发送私聊消息
/home/admin/.openclaw/workspace-ass/skills/feishu-send-message/send.sh \
ass <open_id> open_id "消息内容"
# Ass 发送群聊消息
/home/admin/.openclaw/workspace-ass/skills/feishu-send-message/send.sh \
ass oc_88fbbe55e37eca3c3339b2f4bae1a8a9 chat_id "群聊消息"
# Ops 发送私聊消息
/home/admin/.openclaw/workspace-ops/skills/feishu-send-message/send.sh \
ops <open_id> open_id "运维消息"
```
### 方法 2:直接使用 curl 命令
```bash
# 读取当前 Agent 配置(以 Ass 为例)
APP_ID=$(jq -r '.channels.feishu.appId' ~/.openclaw/openclaw-ass.json)
APP_SECRET=$(jq -r '.channels.feishu.appSecret' ~/.openclaw/openclaw-ass.json)
# 获取 token
TOKEN=$(curl -s -X POST https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/ \
-H 'Content-Type: application/json' \
-d "{\"app_id\":\"$APP_ID\",\"app_secret\":\"$APP_SECRET\"}" | jq -r '.tenant_access_token')
# 发送私聊消息
curl -s -X POST "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=open_id" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d "{\"receive_id\":\"ou_xxx\",\"msg_type\":\"text\",\"content\":\"{\\\"text\\\":\\\"消息内容\\\"}\"}"
# 发送群聊消息
curl -s -X POST "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=chat_id" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d "{\"receive_id\":\"oc_xxx\",\"msg_type\":\"text\",\"content\":\"{\\\"text\\\":\\\"群聊消息\\\"}\"}"
```
## 参数说明
| 参数 | 说明 | 示例 |
|------|------|------|
| **agent** | Agent 类型:`ass` 或 `ops` | `ass` |
| **target** | 目标 ID(open_id 或 chat_id) | `<open_id>` 或 `<chat_id>` |
| **msg_type** | 消息类型:`open_id`(私聊)或 `chat_id`(群聊) | `open_id` |
| **message** | 消息内容 | `你好,这是测试消息` |
## 关键参数参考
| 参数 | Ass 值 | Ops 值 | 说明 |
|------|--------|--------|------|
| **App ID** | `cli_a924c897d4b89ceb` | `cli_a924d0d16bf99cee` | 当前 Agent 应用的 ID |
| **App Secret** | 从配置文件读取 | 从配置文件读取 | 当前 Agent 应用的密钥 |
| **用户 open_id** | 从日志获取 | 从日志获取 | 当前 Agent 应用中的用户 ID |
| **群聊 chat_id** | `oc_88fbbe55e37eca3c3339b2f4bae1a8a9` | 同左 | 群聊 ID(应用通用) |
## 获取用户 open_id
从当前 Agent 网关日志获取:
```bash
# Ass
journalctl --user -u openclaw-gateway-ass.service | grep "received message from"
# Ops
journalctl --user -u openclaw-gateway-ops.service | grep "received message from"
```
## 注意事项
1. **open_id 是应用隔离的**:
- 每个应用有独立的用户 ID 体系
- 必须使用当前 Agent 应用中的 open_id 发送私聊消息
2. **chat_id 是应用通用的**:
- 同一个群聊,不同应用看到的是同一个 chat_id
- 可以直接使用
3. **content 格式**:
- 必须是**转义的 JSON 字符串**:`"{\"text\":\"消息内容\"}"`
- 不是嵌套对象
4. **配置文件位置**:
- Ass: `~/.openclaw/openclaw-ass.json`
- Ops: `~/.openclaw/openclaw-ops.json`
- Boss: `~/.openclaw/openclaw-boss.json`
## 自动化脚本
脚本位置:`/home/admin/.openclaw/workspace-{agent}/skills/feishu-send-message/send.sh`
```bash
#!/bin/bash
# 参数:$1=agent (ass/ops), $2=target (open_id 或 chat_id), $3=msg_type (open_id/chat_id), $4=message
AGENT=$1
TARGET=$2
MSG_TYPE=$3
MESSAGE=$4
# 读取配置
APP_ID=$(jq -r ".channels.feishu.appId" ~/.openclaw/openclaw-AGENT.json)
APP_SECRET=$(jq -r ".channels.feishu.appSecret" ~/.openclaw/openclaw-AGENT.json)
# 获取 token
TOKEN=$(curl -s -X POST https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/ \
-H 'Content-Type: application/json' \
-d "{\"app_id\":\"$APP_ID\",\"app_secret\":\"$APP_SECRET\"}" | jq -r '.tenant_access_token')
# 发送消息
curl -s -X POST "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=MSG_TYPE" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d "{\"receive_id\":\"$TARGET\",\"msg_type\":\"text\",\"content\":\"{\\\"text\\\":\\\"$MESSAGE\\\"}\"}" | jq '.data.message_id'
```
FILE:README.md
# Feishu Auto-Report - 飞书自主汇报技能
## 🎯 技能定位
专为多 Agent 协作设计的消息发送工具。当 Agent-B/C 完成任务后,自动向用户汇报结果,无需 Agent-A 转发。
## ✨ 核心价值
- **自主汇报** - 执行 Agent 完成后主动通知用户
- **身份隔离** - 每个 Agent 显示独立机器人名称
- **零配置** - Agent 启动时自动扫描,智能判断调用时机
## 🚀 典型场景
```
用户 → Agent-A(统筹) → Agent-B(执行) → 用户
↓
自主调用本技能汇报
```
## 📦 快速使用
```bash
# 私聊汇报
./send.sh agent-b ou_xxx open_id "任务已完成"
# 群聊汇报
./send.sh agent-b oc_xxx chat_id "任务已完成"
```
完整文档参考 SKILL.md。
FILE:_meta.json
{
"ownerId": "kn70gps0ahzfkzp49twbwqw119832ahp",
"slug": "feishu-send-message",
"version": "2.0.1",
"publishedAt": 1774008600000
}
FILE:clawhub.yaml
# clawhub.yaml - feishu-send-message 技能配置
name: feishu-send-message
version: 2.0.0
description: |
飞书多 Agent 消息发送技能
让 Ass/Ops 用自己的飞书应用发送消息(群聊/私聊),解决 open_id 应用隔离问题。
## 核心功能
- 使用当前 Agent 的飞书凭证发送消息
- 支持群聊和私聊两种模式
- 飞书显示对应的机器人名字("小助手"或"运维助手")
## 使用场景
- Boss 派发任务后,Ass/Ops 用自己的身份回复消息
- 需要显示不同机器人名字的场景
## 使用方法
```bash
# Ass 发送消息
./send.sh ass ou_xxx open_id "消息内容"
./send.sh ass oc_xxx chat_id "群聊消息"
# Ops 发送消息
./send.sh ops ou_xxx open_id "消息内容"
./send.sh ops oc_xxx chat_id "群聊消息"
```
author: pikaqiuyaya
license: MIT
tags:
- feishu
- message
- multi-agent
- open_id
- zh-CN
requirements:
- jq
- curl
usage: |
## 基本用法
### 获取用户 open_id
```bash
# Ass
journalctl --user -u openclaw-gateway-ass.service | grep "received message from"
# Ops
journalctl --user -u openclaw-gateway-ops.service | grep "received message from"
```
### Ass 发送消息
```bash
# 私聊
./send.sh ass <open_id> open_id "消息内容"
# 群聊
./send.sh ass oc_88fbbe55e37eca3c3339b2f4bae1a8a9 chat_id "消息内容"
```
### Ops 发送消息
```bash
# 私聊
./send.sh ops <open_id> open_id "消息内容"
# 群聊
./send.sh ops oc_88fbbe55e37eca3c3339b2f4bae1a8a9 chat_id "消息内容"
```
parameters:
- name: agent
type: string
required: true
description: "Agent 类型:ass 或 ops"
example: "ass"
- name: target
type: string
required: true
description: "目标 ID(open_id 或 chat_id)"
example: "ou_afa28cfc5689f929f1b1d8f3b09b9408"
- name: msg_type
type: string
required: true
description: "消息类型:open_id(私聊)或 chat_id(群聊)"
example: "open_id"
- name: message
type: string
required: true
description: "消息内容"
example: "你好,这是测试消息"
examples:
- name: "Ass 发送私聊消息"
command: "./send.sh ass ou_afa28cfc5689f929f1b1d8f3b09b9408 open_id \"测试消息\""
- name: "Ass 发送群聊消息"
command: "./send.sh ass oc_88fbbe55e37eca3c3339b2f4bae1a8a9 chat_id \"群聊测试\""
- name: "Ops 发送私聊消息"
command: "./send.sh ops ou_c4c16992e757336634acca0f9f524814 open_id \"运维消息\""
notes: |
## 重要说明
1. **open_id 是应用隔离的**
- Boss 应用的用户 open_id ≠ Ass 应用的用户 open_id ≠ Ops 应用的用户 open_id
- 必须使用当前 Agent 应用中的 open_id
2. **获取用户 open_id 的方法**
```bash
journalctl --user -u openclaw-gateway-{agent}.service | grep "received message from"
```
3. **配置文件位置**
- Ass: `~/.openclaw/openclaw-ass.json`
- Ops: `~/.openclaw/openclaw-ops.json`
changelog:
- version: 2.0.1
date: 2026-03-20
changes:
- "移除明文 open_id,改为从日志获取"
- "添加获取用户 open_id 的方法说明"
- "参数示例使用 <open_id> 占位符"
- version: 2.0.0
date: 2026-03-20
changes:
- "修复 SKILL.md 文档,添加完整使用方法"
- "恢复脚本的 4 个参数用法(agent/target/msg_type/message)"
- "添加关键参数参考表"
- "添加获取用户 open_id 的方法"
FILE:send.sh
#!/bin/bash
# send.sh - 飞书消息发送脚本
# 用法:./send.sh {agent} {target} {msg_type} {message}
# agent: ass 或 ops
# target: open_id (私聊) 或 chat_id (群聊)
# msg_type: open_id (私聊) 或 chat_id (群聊)
# message: 消息内容
set -e
AGENT=$1
TARGET=$2
MSG_TYPE=$3
MESSAGE=$4
if [ -z "$AGENT" ] || [ -z "$TARGET" ] || [ -z "$MSG_TYPE" ] || [ -z "$MESSAGE" ]; then
echo "用法:$0 {agent} {target} {msg_type} {message}"
echo "示例:"
echo " $0 ass ou_afa28cfc5689f929f1b1d8f3b09b9408 open_id \"私聊消息\""
echo " $0 ass oc_88fbbe55e37eca3c3339b2f4bae1a8a9 chat_id \"群聊消息\""
echo " $0 ops ou_xxxxxxxxxxxxxxxxxxxxxxxxxxx open_id \"运维消息\""
exit 1
fi
# 配置文件路径
CONFIG_FILE="$HOME/.openclaw/openclaw-AGENT.json"
if [ ! -f "$CONFIG_FILE" ]; then
echo "错误:配置文件不存在:$CONFIG_FILE"
exit 1
fi
# 读取配置
APP_ID=$(jq -r '.channels.feishu.appId' "$CONFIG_FILE")
APP_SECRET=$(jq -r '.channels.feishu.appSecret' "$CONFIG_FILE")
if [ -z "$APP_ID" ] || [ -z "$APP_SECRET" ] || [ "$APP_ID" == "null" ] || [ "$APP_SECRET" == "null" ]; then
echo "错误:无法从配置文件中读取飞书凭证"
exit 1
fi
echo "=== 使用 $AGENT Agent 发送消息 ==="
echo "App ID: $APP_ID"
echo "目标:$TARGET ($MSG_TYPE)"
echo "消息:$MESSAGE"
echo ""
# 获取 token
echo "获取 token..."
TOKEN=$(curl -s -X POST https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/ \
-H 'Content-Type: application/json' \
-d "{\"app_id\":\"$APP_ID\",\"app_secret\":\"$APP_SECRET\"}" | jq -r '.tenant_access_token')
if [ -z "$TOKEN" ] || [ "$TOKEN" == "null" ]; then
echo "错误:无法获取 token"
exit 1
fi
# 发送消息
echo "发送消息..."
RESULT=$(curl -s -X POST "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=MSG_TYPE" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d "{\"receive_id\":\"$TARGET\",\"msg_type\":\"text\",\"content\":\"{\\\"text\\\":\\\"$MESSAGE\\\"}\"}")
CODE=$(echo "$RESULT" | jq -r '.code')
MSG_ID=$(echo "$RESULT" | jq -r '.data.message_id // empty')
if [ "$CODE" == "0" ]; then
echo "✅ 发送成功!"
echo "消息 ID: $MSG_ID"
else
echo "❌ 发送失败!"
echo "错误码:$CODE"
echo "错误信息:$(echo "$RESULT" | jq -r '.msg')"
exit 1
fi
Send messages via Feishu using the current Agent's credentials, supporting private and group chats with independent identity display to solve open_id isolation.
# Feishu Agent Messenger - 飞书消息发送技能
🚀 **下载就能用!脚本可直接运行,自动读取配置文件。**
**注意**:技能本身不会自动回复消息,需要手动调用脚本或配置自动触发 hook。
飞书多 Agent 消息发送技能。让每个 Agent 用自己的飞书应用发送消息(群聊/私聊),解决 open_id 应用隔离问题。配合多网关架构实现自主协作:统筹 Agent 派发任务 → 执行 Agent 完成任务 → 自主调用本技能汇报结果。
---
## 📋 核心功能
- ✅ 使用当前 Agent 的飞书凭证发送消息
- ✅ 支持群聊和私聊两种模式
- ✅ 飞书显示当前 Agent 的机器人名字
- ✅ 解决 open_id 应用隔离问题
- ✅ **零配置** - 自动从 Agent 配置文件读取飞书凭证
- ✅ **脚本可直接运行** - 无需额外配置
---
## ⚠️ 重要说明
### "下载就能用"的含义
| 功能 | 是否自动 | 说明 |
|------|---------|------|
| 脚本可直接运行 | ✅ 是 | 下载后直接运行 `./send.sh` |
| 自动读取配置 | ✅ 是 | 自动从配置文件读取飞书凭证 |
| 自动回复消息 | ❌ 否 | 需要手动调用脚本 |
### 🚨 调用脚本 ≠ AI 回复
**重要**:
- ❌ **不要通过 AI 回复 sessions_send**(这只是文字回复,不会发送飞书消息)
- ✅ **要调用 send.sh 脚本**发送飞书群消息(这才是真正的发送)
**正确流程**:
1. 收到 Boss 的 sessions_send 消息
2. 执行任务
3. 完成后**调用脚本**:`./send.sh agent-b oc_xxx chat_id "消息内容"`
4. 群里显示消息,发送者为当前 Agent 的机器人名字
**错误示例**:
```
❌ 错误:sessions_send 回复 "任务已完成"
```
**正确示例**:
```bash
✅ 正确:./send.sh agent-b oc_xxx chat_id "任务已完成"
```
### 三种使用方式
**方式 1:手动调用脚本(立即可用)**
```bash
./send.sh agent-b oc_xxx chat_id "消息内容"
```
**方式 2:Boss 派发时明确要求调用(推荐)**
```javascript
sessions_send({
sessionKey: "agent:agent-b:feishu:direct:ou_xxx",
message: `【任务派发】
任务内容:检查系统状态
【回复要求】
完成后请调用脚本发送群消息:
./send.sh agent-b oc_xxx chat_id "任务已完成"`,
timeoutSeconds: 0
})
```
**方式 3:配置自动触发 hook(需要 OpenClaw 支持)**
在 Agent 配置里添加 hook,收到消息时自动调用脚本。
---
## 🔧 配置自动回复(推荐)
### 步骤 1:编辑 Agent 配置文件
**文件位置**:`~/.openclaw/openclaw-{agentId}.json`
**添加 hooks 配置**:
```json
{
"hooks": {
"entries": {
"auto-reply-feishu": {
"enabled": true,
"trigger": "message.received",
"script": "~/.openclaw/workspace-{agentId}/skills/feishu-agent-messenger/send.sh",
"args": ["{agentId}", "sender.open_id", "open_id", "收到您的消息,请稍后"]
}
}
}
}
```
**参数说明**:
- `enabled`: 是否启用
- `trigger`: 触发条件(`message.received` 表示收到消息时)
- `script`: 脚本路径
- `args`: 脚本参数
- `{agentId}`: Agent 标识(如 `agent-b`)
- `sender.open_id`: 发送者 open_id(自动替换)
- `open_id`: 消息类型(私聊)
- `收到您的消息,请稍后`: 回复内容
### 步骤 2:重启 Agent 网关
```bash
systemctl --user restart openclaw-gateway-{agentId}.service
```
### 步骤 3:测试
发送消息给 Agent,检查是否自动回复。
---
## 📖 使用方法
### 方法 1:Boss 派发任务时明确要求调用(推荐)
**统筹 Agent 代码示例**:
```javascript
// 派发给执行 Agent-B
sessions_send({
sessionKey: "agent:agent-b:feishu:direct:ou_xxx",
message: `【任务派发】
任务 ID: TASK-001
任务内容:检查系统状态
【回复要求】
完成后请调用 feishu-agent-messenger 技能回复:
./send.sh agent-b ou_xxx open_id "任务已完成,系统运行正常"
【汇报格式】
【任务开始】
【任务完成】- 输出位置:[路径]`,
timeoutSeconds: 0
})
```
**执行 Agent 收到后**:
1. 执行任务
2. 完成后调用 `send.sh` 脚本回复
3. 消息以 Agent-B 自己的身份发送
---
### 方法 2:定时进度汇报(每 5 分钟)
**统筹 Agent 定时发送指令**:
```javascript
// 每 5 分钟发送一次,要求执行 Agent 汇报进度
setInterval(() => {
sessions_send({
sessionKey: "agent:agent-b:feishu:direct:ou_xxx",
message: `【进度汇报要求】
任务 ID: TASK-001
【汇报要求】
请调用 feishu-agent-messenger 技能汇报当前进度:
./send.sh agent-b oc_xxx chat_id "【进度汇报】TASK-001 - 完成 50%,正在进行中..."`,
timeoutSeconds: 0
})
}, 5 * 60 * 1000) // 每 5 分钟
```
**执行 Agent 收到后**:
1. 检查当前任务进度
2. 调用 `send.sh` 脚本汇报进度
3. 消息以 Agent-B 自己的身份发送到群里
---
### 方法 3:直接调用脚本
**Agent-B 发送私聊消息**:
```bash
~/.openclaw/workspace-agent-b/skills/feishu-agent-messenger/send.sh \
agent-b ou_xxx open_id "私聊消息内容"
```
**Agent-B 发送群聊消息(进度汇报)**:
```bash
~/.openclaw/workspace-agent-b/skills/feishu-agent-messenger/send.sh \
agent-b oc_xxx chat_id "【进度汇报】TASK-001 - 完成 50%,正在进行中..."
```
---
### 方法 2:直接调用脚本
**Agent-B 发送私聊消息**:
```bash
~/.openclaw/workspace-agent-b/skills/feishu-agent-messenger/send.sh \
agent-b ou_xxx open_id "私聊消息内容"
```
**Agent-B 发送群聊消息**:
```bash
~/.openclaw/workspace-agent-b/skills/feishu-agent-messenger/send.sh \
agent-b oc_xxx chat_id "群聊消息内容"
```
---
### 方法 2:直接调用脚本
**Agent-B 发送私聊消息**:
```bash
~/.openclaw/workspace-agent-b/skills/feishu-agent-messenger/send.sh \
agent-b ou_xxx open_id "私聊消息内容"
```
**Agent-B 发送群聊消息**:
```bash
~/.openclaw/workspace-agent-b/skills/feishu-agent-messenger/send.sh \
agent-b oc_xxx chat_id "群聊消息内容"
```
**Agent-C 发送私聊消息**:
```bash
~/.openclaw/workspace-agent-c/skills/feishu-agent-messenger/send.sh \
agent-c ou_xxx open_id "私聊消息内容"
```
**Agent-C 发送群聊消息**:
```bash
~/.openclaw/workspace-agent-c/skills/feishu-agent-messenger/send.sh \
agent-c oc_xxx chat_id "群聊消息内容"
```
---
### 方法 3:直接调用飞书 API
**发送私聊消息**:
```bash
# 读取当前 Agent 配置
APP_ID=$(jq -r '.channels.feishu.appId' ~/.openclaw/openclaw-{agentId}.json)
APP_SECRET=$(jq -r '.channels.feishu.appSecret' ~/.openclaw/openclaw-{agentId}.json)
# 获取 token
TOKEN=$(curl -s -X POST https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/ \
-H 'Content-Type: application/json' \
-d "{\"app_id\":\"$APP_ID\",\"app_secret\":\"$APP_SECRET\"}" | jq -r '.tenant_access_token')
# 发送私聊消息
curl -s -X POST "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=open_id" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d "{\"receive_id\":\"ou_xxx\",\"msg_type\":\"text\",\"content\":\"{\\\"text\\\":\\\"消息内容\\\"}\"}"
```
**发送群聊消息**:
```bash
# 获取 token(同上)
# 发送群聊消息
curl -s -X POST "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=chat_id" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d "{\"receive_id\":\"oc_xxx\",\"msg_type\":\"text\",\"content\":\"{\\\"text\\\":\\\"消息内容\\\"}\"}"
```
---
## ⚙️ 配置说明
### 下载就能用吗?
**是的!下载后无需额外配置,但需要满足以下条件**:
#### 前置条件(必须)
1. **已配置飞书应用**
- 在飞书开放平台创建应用
- 获取 App ID 和 App Secret
- 在 Agent 配置文件中配置飞书凭证
2. **Agent 配置文件存在**
- 位置:`~/.openclaw/openclaw-{agentId}.json`
- 包含飞书配置:`channels.feishu.appId` 和 `channels.feishu.appSecret`
#### 自动回复条件(可选)
如果希望自动回复消息,需要:
1. **配置自动触发 hook**
- 在 Agent 配置文件中添加 hooks 配置
- 指定触发条件和脚本路径
2. **Agent 网关运行中**
- 确保 Agent 网关服务正常运行
- 飞书 WebSocket 连接正常
---
### 配置文件示例
**Agent 配置文件** (`~/.openclaw/openclaw-{agentId}.json`):
```json
{
"agents": {
"list": [{
"id": "agent-b",
"name": "执行助手",
"workspace": "/home/admin/.openclaw/workspace-agent-b"
}],
"defaults": {
"model": { "primary": "dashscope-coding/qwen3.5-plus" },
"workspace": "/home/admin/.openclaw/workspace-agent-b"
}
},
"gateway": {
"port": 19923,
"auth": {
"mode": "token",
"token": "agent-b-token-19923"
}
},
"channels": {
"feishu": {
"enabled": true,
"appId": "cli_xxx",
"appSecret": "xxx",
"dmPolicy": "open"
}
},
"skills": {
"entries": {
"feishu-agent-messenger": {
"enabled": true
}
}
},
"hooks": {
"entries": {
"auto-reply-feishu": {
"enabled": true,
"trigger": "message.received",
"script": "~/.openclaw/workspace-agent-b/skills/feishu-agent-messenger/send.sh",
"args": ["agent-b", "sender.open_id", "open_id", "收到您的消息"]
}
}
}
}
```
---
## 📝 参数说明
| 参数 | 说明 | 示例 |
|------|------|------|
| **agentId** | Agent 标识 | `agent-b` |
| **target** | 目标 ID(open_id 或 chat_id) | `ou_xxx` |
| **msg_type** | 消息类型:`open_id`(私聊)或 `chat_id`(群聊) | `open_id` |
| **message** | 消息内容 | `你好,这是测试消息` |
---
## 🔑 关键参数参考
| 参数 | 说明 | 获取方式 |
|------|------|---------|
| **App ID** | 当前 Agent 应用的 ID | 飞书开放平台 |
| **App Secret** | 当前 Agent 应用的密钥 | 飞书开放平台 |
| **用户 open_id** | 当前 Agent 应用中的用户 ID | Agent 网关日志 |
| **群聊 chat_id** | 群聊 ID(应用通用) | 飞书开放平台 |
---
## 🔍 获取用户 open_id
从当前 Agent 网关日志获取:
```bash
journalctl --user -u openclaw-gateway-{agentId}.service | grep "received message from"
```
---
## ⚠️ 注意事项
### 1. open_id 是应用隔离的
- ❌ 不同应用的用户 open_id 不同
- ✅ 必须使用当前 Agent 应用中的 open_id 发送私聊消息
- ✅ chat_id 是应用通用的,可以直接使用
### 2. content 格式
- ✅ 必须是**转义的 JSON 字符串**:`"{\"text\":\"消息内容\"}"`
- ❌ 不是嵌套对象
### 3. 配置文件位置
- Agent-B: `~/.openclaw/openclaw-agent-b.json`
- Agent-C: `~/.openclaw/openclaw-agent-c.json`
- 其他 Agent: `~/.openclaw/openclaw-{agentId}.json`
---
## 🧪 测试流程
### 步骤 1:测试脚本
```bash
# 发送群聊测试消息
~/.openclaw/workspace-agent-b/skills/feishu-agent-messenger/send.sh \
agent-b oc_xxx chat_id "测试:Agent-B 以自己的身份发送群聊消息"
```
**预期结果**:
- ✅ 输出 "发送成功!"
- ✅ 飞书群里显示消息,发送者为当前 Agent 的机器人名字
### 步骤 2:测试自动回复
**配置 hook 后**,发送消息给 Agent。
**预期结果**:
- ✅ Agent 自动回复消息
- ✅ 日志里显示脚本执行记录
---
## 📚 相关技能
- **setup-multi-gateway** - 多网关配置,配合本技能实现多 Agent 协作
- **skill-vetter** - 技能安全审查,安装前自动检查
---
## 🆘 常见问题
### Q1: 发送失败,提示"配置文件不存在"
**A**: 检查 Agent 配置文件是否存在:
```bash
ls -la ~/.openclaw/openclaw-{agentId}.json
```
### Q2: 发送失败,提示"无法获取 token"
**A**: 检查飞书配置是否正确:
```bash
jq '.channels.feishu.appId' ~/.openclaw/openclaw-{agentId}.json
jq '.channels.feishu.appSecret' ~/.openclaw/openclaw-{agentId}.json
```
### Q3: 消息发送成功,但显示的是其他 Agent 的身份
**A**: 确保使用的是当前 Agent 的脚本和配置,不是其他 Agent 的。
### Q4: open_id 发送失败
**A**: 确保使用的是当前 Agent 应用中的 open_id,不是其他 Agent 的 open_id。
### Q5: 下载后不知道怎么用
**A**:
1. 确保已配置飞书应用(App ID 和 App Secret)
2. 确保 Agent 配置文件存在
3. 测试脚本:`./send.sh agent-b oc_xxx chat_id "测试"`
4. 如果成功,说明配置正确
### Q6: 配置了 hook 但不自动回复
**A**:
1. 检查 hook 配置是否正确
2. 检查 Agent 网关是否重启
3. 查看日志:`journalctl --user -u openclaw-gateway-{agentId}.service -f`
4. 确认触发条件是否匹配
---
## 📝 更新日志
### v1.0.2 (2026-03-20)
- 添加"配置自动回复"章节
- 详细说明 hook 配置方法
- 添加常见问题 Q6
- 明确说明"下载就能用"的含义
### v1.0.1 (2026-03-20)
- 去掉 Boss/Ass/Ops 等本地 Agent 名称,改用通用名称
- 明确说明"下载就能用"的含义和前置条件
- 添加配置说明章节
- 添加常见问题 Q5: 下载后不知道怎么用
- 优化文档结构,更清晰易懂
### v1.0.0 (2026-03-20)
- 初始版本
- 支持多 Agent 发送消息
- 支持群聊和私聊两种模式
- 自动从配置文件读取飞书凭证
- 支持 sessions_send 自动调用
- 添加完整使用流程说明
- 添加测试流程和常见问题解答
FILE:README.md
# Feishu Auto-Report - 飞书自主汇报技能
## 🎯 技能定位
专为多 Agent 协作设计的消息发送工具。当 Agent-B/C 完成任务后,自动向用户汇报结果,无需 Agent-A 转发。
## ✨ 核心价值
- **自主汇报** - 执行 Agent 完成后主动通知用户
- **身份隔离** - 每个 Agent 显示独立机器人名称
- **零配置** - Agent 启动时自动扫描,智能判断调用时机
## 🚀 典型场景
```
用户 → Agent-A(统筹) → Agent-B(执行) → 用户
↓
自主调用本技能汇报
```
## 📦 快速使用
```bash
# 私聊汇报
./send.sh agent-b ou_xxx open_id "任务已完成"
# 群聊汇报
./send.sh agent-b oc_xxx chat_id "任务已完成"
```
完整文档参考 SKILL.md。
FILE:_meta.json
{
"ownerId": "kn70gps0ahzfkzp49twbwqw119832ahp",
"slug": "feishu-agent-messenger",
"version": "1.0.5",
"publishedAt": 1774004400000
}
FILE:clawhub.yaml
# clawhub.yaml - Feishu Agent Messenger 技能配置
name: feishu-agent-messenger
version: 1.0.1
description: |
🚀 **下载就能用!无需额外配置,Agent 启动时自动扫描并调用。**
飞书多 Agent 消息发送技能。让每个 Agent 用自己的飞书应用发送消息(群聊/私聊),解决 open_id 应用隔离问题。
## 核心功能
- 使用当前 Agent 的飞书凭证发送消息
- 支持群聊和私聊两种模式
- 飞书显示当前 Agent 的机器人名字
- 零配置 - 自动从 Agent 配置文件读取飞书凭证
## 使用场景
- 多 Agent 协作:统筹 Agent 派发任务 → 执行 Agent 自主汇报
- 任务完成后自主汇报结果
- 需要显示不同 Agent 身份的场景
## 前置条件
- ✅ 已配置飞书应用(App ID 和 App Secret)
- ✅ Agent 配置文件存在(~/.openclaw/openclaw-{agentId}.json)
- ✅ 飞书配置正确(channels.feishu.appId 和 appSecret)
## 自动调用
- 支持 sessions_send 自动调用
- Agent 收到消息后自动回复
author: pikaqiuyaya
license: MIT
tags:
- feishu
- message
- multi-agent
- open_id
- auto-report
- zh-CN
requirements:
- jq
- curl
usage: |
## 基本用法
## ⚠️ 重要说明
**调用脚本 ≠ AI 回复**
- ❌ 不要通过 AI 回复 sessions_send(这只是文字回复)
- ✅ 要调用 send.sh 脚本发送飞书群消息(这才是真正的发送)
## 基本用法
### Boss 派发任务时明确要求调用(推荐)
```javascript
sessions_send({
sessionKey: "agent:agent-b:feishu:direct:ou_xxx",
message: `【任务派发】
任务 ID: TASK-001
任务内容:检查系统状态
【回复要求】
完成后请调用脚本发送群消息:
./send.sh agent-b oc_xxx chat_id "任务已完成"
⚠️ 注意:不要 AI 回复,要调用脚本!`,
timeoutSeconds: 0
})
```
### 直接调用脚本
```bash
# 发送群聊消息
~/.openclaw/workspace-agent-b/skills/feishu-agent-messenger/send.sh \
agent-b oc_xxx chat_id "消息内容"
```
### 直接调用飞书 API
```bash
# 获取 token
TOKEN=$(curl -s -X POST https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/ \
-H 'Content-Type: application/json' \
-d "{\"app_id\":\"$APP_ID\",\"app_secret\":\"$APP_SECRET\"}" | jq -r '.tenant_access_token')
# 发送消息
curl -s -X POST "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=chat_id" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d "{\"receive_id\":\"oc_xxx\",\"msg_type\":\"text\",\"content\":\"{\\\"text\\\":\\\"消息\\\"}\"}"
```
parameters:
- name: agentId
type: string
required: true
description: "Agent 标识"
example: "agent-b"
- name: target
type: string
required: true
description: "目标 ID(open_id 或 chat_id)"
example: "ou_xxx"
- name: msg_type
type: string
required: true
description: "消息类型:open_id(私聊)或 chat_id(群聊)"
example: "open_id"
- name: message
type: string
required: true
description: "消息内容"
example: "你好,这是测试消息"
examples:
- name: "Agent-B 发送私聊消息"
command: "./send.sh agent-b ou_xxx open_id \"测试消息\""
- name: "Agent-B 发送群聊消息"
command: "./send.sh agent-b oc_xxx chat_id \"群聊测试\""
- name: "Agent-C 发送私聊消息"
command: "./send.sh agent-c ou_xxx open_id \"测试消息\""
notes: |
## 重要说明
1. **下载就能用**
- 无需额外配置
- 自动从 Agent 配置文件读取飞书凭证
- 但需要已配置飞书应用(App ID 和 App Secret)
2. **open_id 是应用隔离的**
- 不同应用的用户 open_id 不同
- 必须使用当前 Agent 应用中的 open_id
3. **获取用户 open_id 的方法**
```bash
journalctl --user -u openclaw-gateway-{agentId}.service | grep "received message from"
```
4. **chat_id 是通用的**
- 同一个群聊,不同应用看到的是同一个 chat_id
- 可以直接使用
changelog:
- version: 1.0.5
date: 2026-03-20
changes:
- "添加重要说明:调用脚本 ≠ AI 回复"
- "明确说明:不要通过 AI 回复 sessions_send"
- "添加正确示例和错误示例对比"
- "更新 Boss 派发任务示例,强调调用脚本"
- version: 1.0.4
date: 2026-03-20
changes:
- "添加定时进度汇报功能(每 5 分钟)"
- "Boss 定时发送指令要求 Agent 汇报进度"
- "添加进度汇报格式示例"
- "完善多 Agent 协作流程说明"
- version: 1.0.3
date: 2026-03-20
changes:
- "添加 Boss 派发任务固定格式"
- "明确说明:Boss 派发时要求 Agent 调用技能回复"
- "不再依赖 hook 自动触发,改用明确指示"
- "优化 sessions_send 示例,包含回复要求"
- version: 1.0.2
date: 2026-03-20
changes:
- "添加'配置自动回复'章节"
- "详细说明 hook 配置方法"
- "添加常见问题 Q6: 配置了 hook 但不自动回复"
- "明确说明'下载就能用'的含义"
- "实现双重保障:hook 触发 + Agent 配置提示"
- version: 1.0.1
date: 2026-03-20
changes:
- "去掉 Boss/Ass/Ops 等本地 Agent 名称,改用通用名称"
- "明确说明'下载就能用'的含义和前置条件"
- "添加配置说明章节"
- "添加常见问题 Q5: 下载后不知道怎么用"
- "优化文档结构,更清晰易懂"
- version: 1.0.0
date: 2026-03-20
changes:
- "初始版本"
- "支持多 Agent 发送消息"
- "支持群聊和私聊两种模式"
- "自动从配置文件读取飞书凭证"
- "支持 sessions_send 自动调用"
- "添加完整使用流程说明"
- "添加测试流程和常见问题解答"
FILE:send.sh
#!/bin/bash
# send-message.sh - 通用飞书消息发送脚本
# 用法:./send-message.sh {agent} {target} {msg_type} {message}
# agent: ass 或 ops
# target: open_id (私聊) 或 chat_id (群聊)
# msg_type: open_id (私聊) 或 chat_id (群聊)
# message: 消息内容
set -e
AGENT=$1
TARGET=$2
MSG_TYPE=$3
MESSAGE=$4
if [ -z "$AGENT" ] || [ -z "$TARGET" ] || [ -z "$MSG_TYPE" ] || [ -z "$MESSAGE" ]; then
echo "用法:$0 {agent} {target} {msg_type} {message}"
echo "示例:"
echo " $0 ass ou_afa28cfc5689f929f1b1d8f3b09b9408 open_id \"私聊消息\""
echo " $0 ass oc_88fbbe55e37eca3c3339b2f4bae1a8a9 chat_id \"群聊消息\""
echo " $0 ops ou_xxxxxxxxxxxxxxxxxxxxxxxxxxx open_id \"运维消息\""
exit 1
fi
# 配置文件路径
CONFIG_FILE="$HOME/.openclaw/openclaw-AGENT.json"
if [ ! -f "$CONFIG_FILE" ]; then
echo "错误:配置文件不存在:$CONFIG_FILE"
exit 1
fi
# 读取配置
APP_ID=$(jq -r '.channels.feishu.appId' "$CONFIG_FILE")
APP_SECRET=$(jq -r '.channels.feishu.appSecret' "$CONFIG_FILE")
if [ -z "$APP_ID" ] || [ -z "$APP_SECRET" ] || [ "$APP_ID" == "null" ] || [ "$APP_SECRET" == "null" ]; then
echo "错误:无法从配置文件中读取飞书凭证"
exit 1
fi
echo "=== 使用 $AGENT Agent 发送消息 ==="
echo "App ID: $APP_ID"
echo "目标:$TARGET ($MSG_TYPE)"
echo "消息:$MESSAGE"
echo ""
# 获取 token
echo "获取 token..."
TOKEN=$(curl -s -X POST https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/ \
-H 'Content-Type: application/json' \
-d "{\"app_id\":\"$APP_ID\",\"app_secret\":\"$APP_SECRET\"}" | jq -r '.tenant_access_token')
if [ -z "$TOKEN" ] || [ "$TOKEN" == "null" ]; then
echo "错误:无法获取 token"
exit 1
fi
# 发送消息
echo "发送消息..."
RESULT=$(curl -s -X POST "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=MSG_TYPE" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d "{\"receive_id\":\"$TARGET\",\"msg_type\":\"text\",\"content\":\"{\\\"text\\\":\\\"$MESSAGE\\\"}\"}")
CODE=$(echo "$RESULT" | jq -r '.code')
MSG_ID=$(echo "$RESULT" | jq -r '.data.message_id // empty')
if [ "$CODE" == "0" ]; then
echo "✅ 发送成功!"
echo "消息 ID: $MSG_ID"
else
echo "❌ 发送失败!"
echo "错误码:$CODE"
echo "错误信息:$(echo "$RESULT" | jq -r '.msg')"
exit 1
fi
自动从本地配置获取飞书凭证,支持多Agent独立身份向指定open_id或chat_id发送文本消息汇报任务结果。
# Feishu Auto-Report Skill - 飞书自主汇报技能
---
name: Feishu Auto-Report - 飞书自主汇报
description: 专为多 Agent 协作设计。Agent 完成任务后自主调用本技能向用户汇报结果,显示独立机器人身份。零配置,Agent 启动时自动扫描使用,无需手动配置飞书凭证。
descriptionEn: Designed for multi-agent collaboration scenarios. After completing tasks, agents independently call this skill to report results to users with independent robot identity. Zero configuration, agents automatically scan and use it without manual Feishu credential configuration.
version: 5.0.0
author: pikaqiuyaya
license: MIT
tags:
- feishu
- auto-report
- multi-agent
- notification
- zh-CN
- 飞书
- 自主汇报
- 多 Agent
- 零配置
language: zh-CN
---
# Feishu Auto-Report Skill - 飞书自主汇报技能
## 设计目标
在多 Agent 协作架构中,执行 Agent(Agent-B/C)完成任务后需要向用户汇报结果。本技能提供零配置的消息发送能力,让每个 Agent 以自己的身份独立发送通知,无需 Agent-A 转发。
## 技术实现
通过飞书开放平台的 Internal App 凭证获取 tenant_access_token,调用飞书消息 API 发送文本消息。支持 open_id(私聊)和 chat_id(群聊)两种接收者类型,确保消息精准触达。
## 配置依赖
技能自动从以下配置文件读取飞书凭证(无需手动配置):
- `~/.openclaw/openclaw-{agentId}.json`
Agent 启动时会自动扫描该路径下的配置文件,读取 `channels.feishu.appId` 和 `channels.feishu.appSecret` 字段完成鉴权。
## API 调用流程
```bash
# 1. 获取租户级访问令牌
POST https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/
Headers: Content-Type: application/json
Body: {"app_id": "xxx", "app_secret": "xxx"}
# 2. 发送文本消息
POST https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type={idType}
Headers:
Authorization: Bearer {tenant_access_token}
Content-Type: application/json
Body: {
"receive_id": "{targetId}",
"msg_type": "text",
"content": "{\"text\":\"{messageContent}\"}"
}
```
## 脚本参数
| 参数 | 说明 | 示例 |
|------|------|------|
| agentId | Agent 标识 | agent-b |
| targetId | 接收者 ID | ou_xxx 或 oc_xxx |
| idType | ID 类型 | open_id / chat_id |
| content | 消息内容 | 任务已完成 |
## 与 setup-multi-gateway 配合
1. Agent-A 接收用户指令
2. Agent-A 通过 sessions_send 派发任务
3. Agent-B/C 执行任务
4. Agent-B/C 自主调用本技能汇报
5. 用户收到对应 Agent 的通知
## 注意事项
- open_id 是应用隔离的,每个 Agent 使用自己的 open_id
- chat_id 是通用的,跨 Agent 共享
- content 必须是转义的 JSON 字符串
FILE:README.md
# Feishu Auto-Report - 飞书自主汇报技能
## 🎯 技能定位
专为多 Agent 协作设计的消息发送工具。当 Agent-B/C 完成任务后,自动向用户汇报结果,无需 Agent-A 转发。
## ✨ 核心价值
- **自主汇报** - 执行 Agent 完成后主动通知用户
- **身份隔离** - 每个 Agent 显示独立机器人名称
- **零配置** - Agent 启动时自动扫描,智能判断调用时机
## 🚀 典型场景
```
用户 → Agent-A(统筹) → Agent-B(执行) → 用户
↓
自主调用本技能汇报
```
## 📦 快速使用
```bash
# 私聊汇报
./send.sh agent-b ou_xxx open_id "任务已完成"
# 群聊汇报
./send.sh agent-b oc_xxx chat_id "任务已完成"
```
完整文档参考 SKILL.md。
FILE:_meta.json
{
"ownerId": "kn70gps0ahzfkzp49twbwqw119832ahp",
"slug": "feishu-auto-report",
"version": "2.0.0",
"publishedAt": 1773972808797,
"name": "飞书自主汇报技能",
"nameEn": "Feishu Auto-Report Skill",
"description": "专为多 Agent 协作场景设计,执行 Agent(Agent-B/C)完成任务后自主调用本技能向飞书用户/群聊发送汇报消息,每个 Agent 显示独立机器人身份,零配置自动适配,无需 Agent-A 转发。",
"descriptionEn": "Designed for multi-agent collaboration scenarios. After execution Agents (Agent-B/C) complete tasks, they can independently call this skill to send report messages to Feishu users/groups, with each Agent displaying an independent robot identity, zero configuration automatic adaptation, no need for Agent-A forwarding.",
"shortDesc": "多 Agent 飞书消息自主汇报",
"shortDescEn": "Multi-Agent Feishu Message Auto-Report",
"tags": ["飞书", "多 Agent", "消息发送", "零配置", "feishu", "auto-report"],
"tagsEn": ["feishu", "multi-agent", "message sending", "zero configuration", "auto-report"],
"language": "zh-CN"
}
FILE:send.sh
#!/bin/bash
# send-message.sh - 通用飞书消息发送脚本
# 用法:./send-message.sh {agent} {target} {msg_type} {message}
# agent: ass 或 ops
# target: open_id (私聊) 或 chat_id (群聊)
# msg_type: open_id (私聊) 或 chat_id (群聊)
# message: 消息内容
set -e
AGENT=$1
TARGET=$2
MSG_TYPE=$3
MESSAGE=$4
if [ -z "$AGENT" ] || [ -z "$TARGET" ] || [ -z "$MSG_TYPE" ] || [ -z "$MESSAGE" ]; then
echo "用法:$0 {agent} {target} {msg_type} {message}"
echo "示例:"
echo " $0 ass ou_afa28cfc5689f929f1b1d8f3b09b9408 open_id \"私聊消息\""
echo " $0 ass oc_88fbbe55e37eca3c3339b2f4bae1a8a9 chat_id \"群聊消息\""
echo " $0 ops ou_xxxxxxxxxxxxxxxxxxxxxxxxxxx open_id \"运维消息\""
exit 1
fi
# 配置文件路径
CONFIG_FILE="$HOME/.openclaw/openclaw-AGENT.json"
if [ ! -f "$CONFIG_FILE" ]; then
echo "错误:配置文件不存在:$CONFIG_FILE"
exit 1
fi
# 读取配置
APP_ID=$(jq -r '.channels.feishu.appId' "$CONFIG_FILE")
APP_SECRET=$(jq -r '.channels.feishu.appSecret' "$CONFIG_FILE")
if [ -z "$APP_ID" ] || [ -z "$APP_SECRET" ] || [ "$APP_ID" == "null" ] || [ "$APP_SECRET" == "null" ]; then
echo "错误:无法从配置文件中读取飞书凭证"
exit 1
fi
echo "=== 使用 $AGENT Agent 发送消息 ==="
echo "App ID: $APP_ID"
echo "目标:$TARGET ($MSG_TYPE)"
echo "消息:$MESSAGE"
echo ""
# 获取 token
echo "获取 token..."
TOKEN=$(curl -s -X POST https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal/ \
-H 'Content-Type: application/json' \
-d "{\"app_id\":\"$APP_ID\",\"app_secret\":\"$APP_SECRET\"}" | jq -r '.tenant_access_token')
if [ -z "$TOKEN" ] || [ "$TOKEN" == "null" ]; then
echo "错误:无法获取 token"
exit 1
fi
# 发送消息
echo "发送消息..."
RESULT=$(curl -s -X POST "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=MSG_TYPE" \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d "{\"receive_id\":\"$TARGET\",\"msg_type\":\"text\",\"content\":\"{\\\"text\\\":\\\"$MESSAGE\\\"}\"}")
CODE=$(echo "$RESULT" | jq -r '.code')
MSG_ID=$(echo "$RESULT" | jq -r '.data.message_id // empty')
if [ "$CODE" == "0" ]; then
echo "✅ 发送成功!"
echo "消息 ID: $MSG_ID"
else
echo "❌ 发送失败!"
echo "错误码:$CODE"
echo "错误信息:$(echo "$RESULT" | jq -r '.msg')"
exit 1
fi
配置多个独立OpenClaw网关实例,支持独立Agent、工作区和机器人,以及跨Agent通信和多端口管理。
# Multi-Gateway Setup Skill - 多网关配置技能
## 用途
当用户需要配置多个独立的 OpenClaw 网关实例时使用此技能。每个网关对应独立的 Agent、Workspace 和机器人。
## 🤖 多 Agent 协作架构
本技能配合 [`feishu-agent-send`](https://clawhub.com/skills/feishu-agent-send) 技能,实现完整的多 Agent 自主协作系统:
```
┌─────────────┐
│ Agent-A │ 统筹协调者
│ (19922) │ - 接收用户指令
│ │ - 分解任务给其他 Agent
└─────┬───────┘
│ sessions_send
▼
┌─────────────┐
│ Agent-B │ 执行助手
│ (19923) │ - 接收 Agent-A 派发的任务
│ │ - 执行完成后调用 feishu-agent-send
└─────────────┘ - 自主汇报结果给用户
│
│ (飞书消息显示对应机器人名字)
▼
┌─────────
│ 用户 │
└─────────
```
### 协作流程
1. **Agent-A 接收指令** → 用户发送任务给 Agent-A
2. **任务分解** → Agent-A 使用 `sessions_send` 派发给 Agent-B/C
3. **自主执行** → Agent-B/C 独立完成任务
4. **自主汇报** → Agent-B/C 调用 `feishu-agent-send` 用自己的身份发送结果
5. **用户感知** → 飞书显示对应机器人名字(如"小助手"/"运维助手")
### 核心优势
- ✅ **身份隔离** - 每个 Agent 用自己的飞书应用发送消息,显示不同机器人名字
- ✅ **自主汇报** - 执行 Agent 完成后主动汇报,无需 Agent-A 转发
- ✅ **职责清晰** - 用户知道是哪个 Agent 完成的任务
- ✅ **解耦设计** - Agent-A 不阻塞,派发任务后可继续处理其他请求
## 🎯 适用场景
- ✅ 需要多个独立机器人(如:管理机器人 + 客服机器人 + 运维机器人)
- ✅ 隔离不同用途的 Agent(如:个人助理 + 工作助手)
- ✅ 为不同用户/团队创建独立的 AI 实例
- ✅ 测试不同配置而不影响生产环境
## 📋 前置检查
在开始之前,确认以下条件:
```bash
# 1. 检查 OpenClaw 版本
openclaw --version
# 需要 2026.3.3 或更高
# 2. 检查可用端口
netstat -tlnp | grep openclaw
# 确保目标端口未被占用
# 3. 检查磁盘空间
df -h ~/.openclaw
# 建议至少 1GB 可用空间
# 4. 检查内存
free -h
# 每个网关约占用 500-600MB 内存,确保有足够内存
```
---
## 🚀 快速开始
### 使用交互式向导(推荐)
```bash
openclaw mg
```
向导会自动:
1. ✅ 动态检测存在的配置文件
2. ✅ 让你选择配置来源
3. ✅ 自动复制并替换配置
4. ✅ 创建 systemd 服务
5. ✅ 可选跳过飞书配置(纯本地使用)
---
## 📖 完整文档
### 配置详解
#### 1. 网关配置结构
每个网关有独立的配置文件:
```json
{
"agents": {
"list": [{
"id": "agent-a",
"name": "Agent A",
"workspace": "/home/admin/.openclaw/workspace-agent-a"
}],
"defaults": {
"model": { "primary": "dashscope-coding/qwen3.5-plus" },
"workspace": "/home/admin/.openclaw/workspace-agent-a"
}
},
"gateway": {
"port": 19925,
"auth": {
"mode": "token",
"token": "agent-a-token-19925"
}
},
"channels": {
"feishu": {
"enabled": true,
"appId": "cli_xxx",
"dmPolicy": "open"
}
},
"tools": {
"sessions": { "visibility": "all" },
"agentToAgent": { "enabled": true }
}
}
```
#### 2. 关键配置项
| 配置 | 说明 | 推荐值 |
|------|------|--------|
| `gateway.port` | 网关端口 | 19900-29999 |
| `channels.feishu.dmPolicy` | 私聊策略 | open/pairing |
| `channels.feishu.streaming` | 流式输出 | true |
| `tools.sessions.visibility` | 会话可见性 | all (统筹 Agent) |
| `tools.agentToAgent.enabled` | 跨 Agent 通信 | true (统筹 Agent) |
---
### 典型部署架构
| Agent | 端口 | Workspace | 用途 |
|-------|------|-----------|------|
| Agent-A | 19922 | workspace-agent-a | 统筹协调 |
| Agent-B | 19923 | workspace-agent-b | 执行助手 |
| Agent-C | 19930 | workspace-agent-c | 运维专家 |
> 💡 **提示**: 端口和名称可以根据需求自定义,mg 向导会自动检测可用端口。
---
### 跨 Agent 通信
#### 配置要求
在每个需要通信的 Agent 配置中添加:
```json
{
"tools": {
"sessions": { "visibility": "all" },
"agentToAgent": { "enabled": true }
}
}
```
#### 发送消息
```javascript
// Agent-A → Agent-B
sessions_send({
sessionKey: "agent:agent-b:feishu:direct:ou_xxx",
message: "请检查文档服务状态",
timeoutSeconds: 0
})
// Agent-A → Agent-C
sessions_send({
sessionKey: "agent:agent-c:feishu:direct:ou_xxx",
message: "请检查系统负载",
timeoutSeconds: 60
})
```
#### 会话 Key 格式
```
agent:{agentId}:{channel}:{chatType}:{peerId}
```
**示例**:
- `agent:agent-b:feishu:direct:ou_73eb93dd20542d4f03481dabb1e01677`
- `agent:agent-c:feishu:direct:ou_xxx`
---
### 运维命令
#### 服务管理
```bash
# 启动/停止/重启
systemctl --user start openclaw-gateway-agent-a.service
systemctl --user stop openclaw-gateway-agent-a.service
systemctl --user restart openclaw-gateway-agent-a.service
# 批量操作
systemctl --user start openclaw-gateway-{agent-a,agent-b,agent-c}.service
```
#### 日志查看
```bash
# 实时日志
journalctl --user -u openclaw-gateway-agent-a.service -f
# 最近 100 条
journalctl --user -u openclaw-gateway-agent-a.service -n 100
```
#### 故障排查
```bash
# 检查端口
netstat -tlnp | grep openclaw
# 检查 OOM
dmesg | grep -i oom
# 验证配置
node -e "JSON.parse(require('fs').readFileSync('~/.openclaw/openclaw-agent-a.json'))"
```
---
### 注意事项
1. **端口唯一** - 每个网关必须使用不同端口
2. **Workspace 隔离** - 每个 Agent 独立工作区
3. **内存规划** - 建议网关数量不超过系统内存的 70%(单网关约 500-600MB)
4. **渠道可选** - 可以不绑定任何渠道(纯本地使用),或根据需要绑定飞书/微信/钉钉等
---
## 📚 相关文档
- OpenClaw 官方文档:https://docs.openclaw.ai
- ClawHub 技能市场:https://clawhub.com
---
**技能版本**: 1.1.2
**作者**: pikaqiuyaya
**许可证**: MIT
FILE:CHANGELOG.md
# 更新日志
## 1.1.2 (2026-03-20)
- 修改文档:Boss/Ass/Ops → Agent-A/B/C(更通用的命名)
- 简化协作流程图,去掉具体 Agent 名字
- 完善中文说明
## 1.1.1 (2026-03-20)
- 更新 CHANGELOG 为中文
- 完善多 Agent 协作说明
## 1.1.0 (2026-03-20)
- 添加中文 description,强调与 feishu-agent-send 配合实现自主汇报
- 补充多 Agent 协作架构说明
- 明确协作流程:Agent-A 派发任务 → Agent-B/C 执行 → 自主汇报结果
## 1.0.39 (2026-03-20)
- 添加多 Agent 协作架构说明
- 补充与 feishu-agent-send 配合使用的完整流程
- 说明自主汇报机制
- 强调身份隔离优势
## 1.0.36 (2026-03-18)
- 初始版本
FILE:README.md
# Multi-Gateway Setup Skill - 多网关配置技能
## 用途
当用户需要配置多个独立的 OpenClaw 网关实例时使用此技能。每个网关对应独立的 Agent、Workspace 和机器人。
## 🤖 多 Agent 协作架构
本技能配合 [`feishu-agent-send`](https://clawhub.com/skills/feishu-agent-send) 技能,实现完整的多 Agent 自主协作系统:
```
┌─────────────┐
│ Agent-A │ 统筹协调者(如:Boss Agent)
│ (19922) │ - 接收用户指令
│ Boss │ - 分解任务给其他 Agent
└─────┬───────┘
│ sessions_send
▼
┌─────────────┐
│ Agent-B │ 执行助手(如:Ass Agent)
│ (19923) │ - 接收 Boss 派发的任务
│ Assistant │ - 执行完成后调用 feishu-agent-send
└─────────────┘ - 自主汇报结果给用户
│
│ (飞书消息显示"小助手"机器人)
▼
┌─────────┐
│ 用户 │
└─────────┘
```
### 协作流程
1. **Boss 接收指令** → 用户发送任务给 Boss Agent
2. **任务分解** → Boss 使用 `sessions_send` 派发给 Ass/Ops
3. **自主执行** → Ass/Ops 独立完成任务
4. **自主汇报** → Ass/Ops 调用 `feishu-agent-send` 用自己的身份发送结果
5. **用户感知** → 飞书显示对应机器人名字("小助手"/"运维助手")
### 核心优势
- ✅ **身份隔离** - 每个 Agent 用自己的飞书应用发送消息,显示不同机器人名字
- ✅ **自主汇报** - 执行 Agent 完成后主动汇报,无需 Boss 转发
- ✅ **职责清晰** - 用户知道是哪个 Agent 完成的任务
- ✅ **解耦设计** - Boss 不阻塞,派发任务后可继续处理其他请求
## 🎯 适用场景
- ✅ 需要多个独立机器人(如:管理机器人 + 客服机器人 + 运维机器人)
- ✅ 隔离不同用途的 Agent(如:个人助理 + 工作助手)
- ✅ 为不同用户/团队创建独立的 AI 实例
- ✅ 测试不同配置而不影响生产环境
## 📋 前置检查
在开始之前,确认以下条件:
```bash
# 1. 检查 OpenClaw 版本
openclaw --version
# 需要 2026.3.3 或更高
# 2. 检查可用端口
netstat -tlnp | grep openclaw
# 确保目标端口未被占用
# 3. 检查磁盘空间
df -h ~/.openclaw
# 建议至少 1GB 可用空间
# 4. 检查内存
free -h
# 每个网关约占用 500-600MB 内存,确保有足够内存
```
---
## 🚀 快速开始
### 使用交互式向导(推荐)
```bash
openclaw mg
```
向导会自动:
1. ✅ 动态检测存在的配置文件
2. ✅ 让你选择配置来源
3. ✅ 自动复制并替换配置
4. ✅ 创建 systemd 服务
5. ✅ 可选跳过飞书配置(纯本地使用)
---
## 📖 完整文档
### 配置详解
#### 1. 网关配置结构
每个网关有独立的配置文件:
```json
{
"agents": {
"list": [{
"id": "agent-a",
"name": "Agent A",
"workspace": "/home/admin/.openclaw/workspace-agent-a"
}],
"defaults": {
"model": { "primary": "dashscope-coding/qwen3.5-plus" },
"workspace": "/home/admin/.openclaw/workspace-agent-a"
}
},
"gateway": {
"port": 19925,
"auth": {
"mode": "token",
"token": "agent-a-token-19925"
}
},
"channels": {
"feishu": {
"enabled": true,
"appId": "cli_xxx",
"dmPolicy": "open"
}
},
"tools": {
"sessions": { "visibility": "all" },
"agentToAgent": { "enabled": true }
}
}
```
#### 2. 关键配置项
| 配置 | 说明 | 推荐值 |
|------|------|--------|
| `gateway.port` | 网关端口 | 19900-29999 |
| `channels.feishu.dmPolicy` | 私聊策略 | open/pairing |
| `channels.feishu.streaming` | 流式输出 | true |
| `tools.sessions.visibility` | 会话可见性 | all (统筹 Agent) |
| `tools.agentToAgent.enabled` | 跨 Agent 通信 | true (统筹 Agent) |
---
### 典型部署架构
| Agent | 端口 | Workspace | 用途 |
|-------|------|-----------|------|
| Agent-A | 19922 | workspace-agent-a | 统筹协调 |
| Agent-B | 19923 | workspace-agent-b | 执行助手 |
| Agent-C | 19930 | workspace-agent-c | 运维专家 |
> 💡 **提示**: 端口和名称可以根据需求自定义,mg 向导会自动检测可用端口。
---
### 跨 Agent 通信
#### 配置要求
在每个需要通信的 Agent 配置中添加:
```json
{
"tools": {
"sessions": { "visibility": "all" },
"agentToAgent": { "enabled": true }
}
}
```
#### 发送消息
```javascript
// Agent-A → Agent-B
sessions_send({
sessionKey: "agent:agent-b:feishu:direct:ou_xxx",
message: "请检查文档服务状态",
timeoutSeconds: 0
})
// Agent-A → Agent-C
sessions_send({
sessionKey: "agent:agent-c:feishu:direct:ou_xxx",
message: "请检查系统负载",
timeoutSeconds: 60
})
```
#### 会话 Key 格式
```
agent:{agentId}:{channel}:{chatType}:{peerId}
```
**示例**:
- `agent:agent-b:feishu:direct:ou_73eb93dd20542d4f03481dabb1e01677`
- `agent:agent-c:feishu:direct:ou_xxx`
---
### 运维命令
#### 服务管理
```bash
# 启动/停止/重启
systemctl --user start openclaw-gateway-agent-a.service
systemctl --user stop openclaw-gateway-agent-a.service
systemctl --user restart openclaw-gateway-agent-a.service
# 批量操作
systemctl --user start openclaw-gateway-{agent-a,agent-b,agent-c}.service
```
#### 日志查看
```bash
# 实时日志
journalctl --user -u openclaw-gateway-agent-a.service -f
# 最近 100 条
journalctl --user -u openclaw-gateway-agent-a.service -n 100
```
#### 故障排查
```bash
# 检查端口
netstat -tlnp | grep openclaw
# 检查 OOM
dmesg | grep -i oom
# 验证配置
node -e "JSON.parse(require('fs').readFileSync('~/.openclaw/openclaw-agent-a.json'))"
```
---
### 注意事项
1. **端口唯一** - 每个网关必须使用不同端口
2. **Workspace 隔离** - 每个 Agent 独立工作区
3. **内存规划** - 建议网关数量不超过系统内存的 70%(单网关约 500-600MB)
4. **渠道可选** - 可以不绑定任何渠道(纯本地使用),或根据需要绑定飞书/微信/钉钉等
---
## 📚 相关文档
- OpenClaw 官方文档:https://docs.openclaw.ai
- ClawHub 技能市场:https://clawhub.com
---
**技能版本**: 1.0.39
**作者**: pikaqiuyaya
**许可证**: MIT
FILE:_meta.json
{
"name": "Setup Multi Gateway",
"description": "配置和运行多个独立的 OpenClaw 网关实例。配合 feishu-agent-send 技能实现多 Agent 自主协作:Agent-A 派发任务 → Agent-B/C 执行 → 自主调用 feishu-agent-send 汇报结果。每个 Agent 显示独立机器人身份,实现完整的任务分发与自主汇报闭环。",
"version": "1.1.2",
"author": "pikaqiuyaya",
"license": "MIT",
"tags": ["openclaw", "gateway", "multi-agent", "deployment", "zh-CN", "feishu", "collaboration"],
"readme": "README.md",
"main": "mg-wizard.cjs"
}
FILE:mg-wizard.cjs
#!/usr/bin/env node
/**
* openclaw mg - Multi-gateway Configuration Wizard (完整版)
* 多网关配置向导 - 完整复制 main 配置 + 自动飞书配置引导
*/
const readline = require('readline');
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const question = (query) => new Promise((resolve) => rl.question(query, resolve));
const colors = {
reset: '\x1b[0m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m',
red: '\x1b[31m',
cyan: '\x1b[36m'
};
const log = {
info: (msg) => console.log(`colors.blueℹcolors.reset msg`),
success: (msg) => console.log(`colors.green✅colors.reset msg`),
warn: (msg) => console.log(`colors.yellow⚠colors.reset msg`),
error: (msg) => console.log(`colors.red❌colors.reset msg`),
step: (msg) => console.log(`\ncolors.cyan━━━ msg ━━━colors.reset\n`)
};
const API_KEY = 'sk-sp-319b5ed947404131b3b12e5211592b46';
// ========== 日志文件 ==========
let logFile = null;
function setupLogging(gatewayName) {
const { execSync } = require('child_process');
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
logFile = `/tmp/openclaw-mg-gatewayName-timestamp.log`;
console.log(`📝 操作日志:logFile\n`);
return logFile;
}
function logToFile(msg) {
if (logFile) {
try {
const { execSync } = require('child_process');
execSync(`echo "[new Date().toISOString()] msg" >> logFile 2>/dev/null || true`);
} catch (e) {}
}
}
// ========== 前置检查 ==========
function checkPrerequisites(baseDir) {
console.log(`colors.cyan🔍 检查前置条件...colors.reset\n`);
// 检查必要文件
const requiredFiles = [
{ path: path.join(baseDir, 'openclaw-main.json'), name: 'main 配置文件' }
];
for (const { path: filePath, name } of requiredFiles) {
if (!fs.existsSync(filePath)) {
log.error(`缺少必要文件:name`);
console.log(` 路径:filePath`);
return false;
}
log.success(`name 存在`);
}
// 检查必要目录
const requiredDirs = [
path.join(baseDir, 'agents'),
path.join(baseDir, 'agents', 'main', 'agent') // main 的 agent 目录必须存在
];
for (const dir of requiredDirs) {
if (!fs.existsSync(dir)) {
log.error(`缺少必要目录:dir`);
return false;
}
}
log.success('必要目录存在');
// 检测存在的 workspace
const possibleWorkspaces = [];
try {
const dirs = fs.readdirSync(baseDir);
for (const dir of dirs) {
if (dir.startsWith('workspace-') && dir !== 'workspace') {
possibleWorkspaces.push(dir);
}
}
} catch (e) {
log.error(`读取目录失败:e.message`);
return false;
}
if (possibleWorkspaces.length === 0) {
log.error('没有找到任何 workspace');
return false;
}
log.success(`检测到存在的 workspace: possibleWorkspaces.join(', ')`);
// 检查是否有 models.json 模板(只检查 main)
let hasModels = false;
const mainAgentSubDir = path.join(baseDir, 'agents', 'main', 'agent');
const mainDir = path.join(baseDir, 'agents', 'main');
// 搜索路径:main/agent 和 main
const searchPaths = [
{ path: path.join(mainAgentSubDir, 'models.json'), name: 'main/agent/models.json' },
{ path: path.join(mainDir, 'models.json'), name: 'main/models.json' }
];
for (const { path: modelsPath, name } of searchPaths) {
if (fs.existsSync(modelsPath)) {
log.success(`name 存在(将使用作为模板)`);
hasModels = true;
break;
}
}
if (!hasModels) {
log.error('找不到 models.json 模板');
log.error('搜索路径:');
searchPaths.forEach(({ path: p, name }) => {
log.error(` - name: p`);
});
return false;
}
console.log('');
return true;
}
// ========== 配置备份 ==========
function backupConfig(configPath) {
if (fs.existsSync(configPath)) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupPath = `configPath.backup.timestamp`;
fs.copyFileSync(configPath, backupPath);
log.warn(`已备份旧配置:backupPath`);
return backupPath;
}
return null;
}
// ========== 改进的 IP 获取 ==========
function getServerIP() {
const { execSync } = require('child_process');
const sources = [
'curl -s ifconfig.me',
'curl -s api.ipify.org',
'curl -s icanhazip.com',
'hostname -I 2>/dev/null | awk \'{print $1}\'',
'hostname 2>/dev/null'
];
for (const cmd of sources) {
try {
const ip = execSync(cmd, { encoding: 'utf8', timeout: 5000 }).trim();
if (ip && ip.length > 0 && !ip.includes('error')) {
return ip;
}
} catch (e) {
// 尝试下一个源
}
}
return 'YOUR_SERVER_IP';
}
// ========== 回滚机制 ==========
let createdResources = {
configPath: null,
agentDir: null,
workspaceDir: null,
serviceFile: null
};
function rollback() {
console.log(`\ncolors.yellow⚠️ 创建失败,正在回滚...colors.reset`);
try {
if (createdResources.serviceFile && fs.existsSync(createdResources.serviceFile)) {
execSync(`systemctl --user stop path.basename(createdResources.serviceFile, '.service') 2>/dev/null || true`);
execSync(`systemctl --user disable path.basename(createdResources.serviceFile, '.service') 2>/dev/null || true`);
fs.unlinkSync(createdResources.serviceFile);
log.info(`已删除服务文件:createdResources.serviceFile`);
}
if (createdResources.workspaceDir && fs.existsSync(createdResources.workspaceDir)) {
execSync(`rm -rf createdResources.workspaceDir`);
log.info(`已删除 workspace: createdResources.workspaceDir`);
}
if (createdResources.agentDir && fs.existsSync(createdResources.agentDir)) {
execSync(`rm -rf createdResources.agentDir`);
log.info(`已删除 agent 目录:createdResources.agentDir`);
}
if (createdResources.configPath && fs.existsSync(createdResources.configPath)) {
fs.unlinkSync(createdResources.configPath);
log.info(`已删除配置文件:createdResources.configPath`);
}
log.success('回滚完成');
} catch (e) {
log.error(`回滚失败:e.message`);
log.error('请手动清理残留文件');
}
}
// 设置错误处理
process.on('uncaughtException', (err) => {
log.error(`未捕获的错误:err.message`);
if (createdResources.configPath) {
rollback();
}
process.exit(1);
});
// ========== 端口检测函数 ==========
function getOccupiedPorts() {
const { execSync } = require('child_process');
const occupied = [];
try {
// 从 systemd 服务获取
const services = execSync('systemctl --user list-units 2>/dev/null | grep "openclaw-gateway.*\\.service" | awk \'{print $1}\'', { encoding: 'utf8' });
const serviceList = services.trim().split('\n').filter(s => s.trim());
for (const service of serviceList) {
try {
// 获取完整的环境变量行
const envLine = execSync(`systemctl --user show service.trim() 2>/dev/null | grep "OPENCLAW_CONFIG_PATH="`, { encoding: 'utf8' }).trim();
// 从环境变量行中提取配置路径
const match = envLine.match(/OPENCLAW_CONFIG_PATH=([^\s]+)/);
const configPath = match ? match[1] : null;
if (configPath && fs.existsSync(configPath)) {
const config = fs.readFileSync(configPath, 'utf8');
const portMatch = config.match(/"port":\s*(\d+)/);
if (portMatch) {
occupied.push({ port: portMatch[1], service: service.trim() });
}
}
} catch (e) {
// 忽略单个服务的错误
}
}
} catch (e) {
// 忽略 systemd 错误
}
return occupied;
}
function suggestAvailablePort(startPort = 19900, endPort = 29999) {
const { execSync } = require('child_process');
for (let port = startPort; port <= endPort; port++) {
try {
// 检查端口是否被监听
const netstat = execSync(`ss -tlnp 2>/dev/null | grep ":port " || netstat -tlnp 2>/dev/null | grep ":port " || true`, { encoding: 'utf8' });
const isListening = netstat.trim().length > 0;
// 检查配置文件是否使用该端口
const configCheck = execSync(`grep -r "\"port": port" ~/.openclaw/openclaw-*.json 2>/dev/null | grep -v "openclaw.json" || true`, { encoding: 'utf8' });
const isConfigured = configCheck.trim().length > 0;
if (!isListening && !isConfigured) {
return port;
}
} catch (e) {
// 如果检查失败,假设端口可用
return port;
}
}
return endPort + 1;
}
async function main() {
console.log(`
colors.cyan╔══════════════════════════════════════════════════════════╗
║ OpenClaw Multi-Gateway Wizard - 多网关配置向导 ║
║ (完整配置版) ║
╚══════════════════════════════════════════════════════════╝colors.reset
`);
try {
const baseDir = path.join(process.env.HOME, '.openclaw');
// ========== 前置检查 ==========
if (!checkPrerequisites(baseDir)) {
log.error('前置检查失败,请修复后重试');
process.exit(1);
}
// ========== 步骤 1: 配置网关信息 ==========
log.step('步骤 1/11: 配置网关信息');
// 显示已占用端口
console.log(`colors.cyan📊 当前已占用的网关端口:colors.reset`);
const occupied = getOccupiedPorts();
if (occupied.length > 0) {
occupied.forEach(({ port, service }) => {
console.log(` colors.green✓colors.reset 端口 port - service`);
});
} else {
console.log(' 暂无已占用的网关端口');
}
console.log('');
const gatewayName = await question('网关名称:') || 'agent-a';
const gatewayDesc = await question(`网关描述/用途 [gatewayName 机器人]: `) || `gatewayName 机器人`;
// 随机推荐可用端口
const suggestedPort = suggestAvailablePort(19900, 19999);
console.log(`colors.green✅ 推荐端口:suggestedPortcolors.reset`);
console.log('');
const gatewayPort = await question(`网关端口 [suggestedPort]: `) || suggestedPort;
const workspaceName = `workspace-gatewayName`;
const configPath = path.join(baseDir, `openclaw-gatewayName.json`);
const agentDir = path.join(baseDir, 'agents', gatewayName);
const workspaceDir = path.join(baseDir, workspaceName);
console.log(`\ncolors.yellow配置摘要:colors.reset`);
console.log(` 网关名称: gatewayName`);
console.log(` 用途描述: gatewayDesc`);
console.log(` 监听端口: gatewayPort`);
console.log(` Workspace: workspaceName`);
console.log(` 配置文件: configPath`);
const confirm = await question(`\n确认配置?[Y/n]: `);
if (confirm.toLowerCase() === 'n') {
log.info('已取消');
process.exit(0);
}
// 设置日志
setupLogging(gatewayName);
logToFile(`开始创建网关:gatewayName, 端口:gatewayPort`);
// 备份旧配置(如果存在)
backupConfig(configPath);
// ========== 步骤 2: 选择并复制配置 ==========
log.step('步骤 2/11: 选择配置来源');
// 动态检测存在的配置文件
const possibleConfigs = [];
const configPattern = /^openclaw-(.+)\.json$/;
try {
const files = fs.readdirSync(baseDir);
for (const file of files) {
const match = file.match(configPattern);
if (match && file !== 'openclaw.json') {
const name = match[1];
possibleConfigs.push({
name: name,
path: path.join(baseDir, file),
label: file
});
}
}
} catch (e) {
log.error(`读取配置目录失败:e.message`);
process.exit(1);
}
const existingConfigs = possibleConfigs;
if (existingConfigs.length === 0) {
log.error('没有找到任何配置文件');
process.exit(1);
}
// 如果只有一个配置,直接使用
let sourceConfig;
if (existingConfigs.length === 1) {
sourceConfig = existingConfigs[0];
log.info(`检测到唯一配置:sourceConfig.label`);
} else {
// 多个配置,让用户选择
console.log('\n检测到以下配置文件:');
existingConfigs.forEach((cfg, index) => {
console.log(` index + 1) cfg.label`);
});
const choice = await question(`\n请选择配置来源 [1-existingConfigs.length]: `);
const choiceIndex = parseInt(choice) - 1;
if (choiceIndex < 0 || choiceIndex >= existingConfigs.length) {
log.error('无效选择');
process.exit(1);
}
sourceConfig = existingConfigs[choiceIndex];
}
console.log(`\n 来源配置:sourceConfig.label`);
// 备份原配置(如果已存在)
if (fs.existsSync(configPath)) {
fs.copyFileSync(configPath, `configPath.bak`);
log.info('已备份原配置');
}
// 复制配置
const sourceLines = fs.readFileSync(sourceConfig.path, 'utf8').split('\n').length;
fs.copyFileSync(sourceConfig.path, configPath);
const newLines = fs.readFileSync(configPath, 'utf8').split('\n').length;
console.log(` sourceConfig.name 配置:sourceLines 行`);
console.log(` gatewayName 配置:newLines 行`);
if (newLines < sourceLines) {
log.error(`配置不完整!sourceConfig.name 有 sourceLines 行,但新配置只有 newLines 行`);
// 回滚备份
if (fs.existsSync(`configPath.bak`)) {
fs.copyFileSync(`configPath.bak`, configPath);
log.info('已恢复原配置');
}
process.exit(1);
}
// 验证配置有效性
try {
JSON.parse(fs.readFileSync(configPath, 'utf8'));
log.success('配置文件验证通过');
} catch (e) {
log.error(`配置文件无效:e.message`);
// 回滚备份
if (fs.existsSync(`configPath.bak`)) {
fs.copyFileSync(`configPath.bak`, configPath);
log.info('已恢复原配置');
}
process.exit(1);
}
log.success(`已完整复制 sourceConfig.name 配置(newLines 行)`);
// 保存配置来源,后续步骤使用(技能/记忆同步)
global.sourceAgentName = sourceConfig.name;
// 预计算 workspace 路径,后续步骤复用
const srcWorkspace = path.join(baseDir, `workspace-sourceConfig.name`);
// ========== 步骤 3: 替换关键配置项 ==========
log.step('步骤 3/11: 替换关键配置');
let configContent = fs.readFileSync(configPath, 'utf8');
// 替换 workspace(完整路径)- 动态检测源配置
const oldWorkspaceMatch = configContent.match(/"workspace":\s*"([^"]+)"/);
const oldWorkspace = oldWorkspaceMatch ? oldWorkspaceMatch[1] : 'xxx';
configContent = configContent.replace(
/"workspace":\s*"[^"]+"/g,
`"workspace": "/home/admin/.openclaw/workspace-gatewayName"`
);
console.log(` ✓ workspace: oldWorkspace → workspace-gatewayName`);
// 替换端口 - 动态检测源配置
const oldPortMatch = configContent.match(/"port":\s*(\d+)/);
const oldPort = oldPortMatch ? oldPortMatch[1] : 'xxx';
configContent = configContent.replace(/"port":\s*\d+/g, `"port": gatewayPort`);
console.log(` ✓ 端口:oldPort → gatewayPort`);
// 替换 UI basePath - 动态检测源配置
const oldBasePathMatch = configContent.match(/"basePath":\s*"([^"]+)"/);
const oldBasePath = oldBasePathMatch ? oldBasePathMatch[1] : 'xxx-ui';
configContent = configContent.replace(/"basePath":\s*"[^"]+"/g, `"basePath": "gatewayName-ui"`);
console.log(` ✓ UI basePath: oldBasePath → gatewayName-ui`);
// 替换 token - 动态检测源配置
const oldTokenMatch = configContent.match(/"token":\s*"([^"]+)"/);
const oldToken = oldTokenMatch ? oldTokenMatch[1] : 'xxx-token';
const token = `gatewayName-token-gatewayDesc-gatewayPort`;
configContent = configContent.replace(/"token":\s*"[^"]+"/g, `"token": "token"`);
console.log(` ✓ Token: oldToken → token`);
// 替换 allowedOrigins - 动态检测源配置
const serverIp = getServerIP();
const oldAllowedOriginsMatch = configContent.match(/"allowedOrigins":\s*\[([^\]]+)\]/);
const oldAllowedOrigins = oldAllowedOriginsMatch ? oldAllowedOriginsMatch[0] : '"allowedOrigins": ["xxx"]';
configContent = configContent.replace(
/"allowedOrigins":\s*\[[^\]]+\]/g,
`"allowedOrigins": ["http://serverIp:gatewayPort"]`
);
console.log(` ✓ allowedOrigins: 已更新`);
logToFile(`服务器 IP: serverIp`);
fs.writeFileSync(configPath, configContent);
log.success('关键配置已替换');
// ========== 步骤 3.1: 添加/替换 agents.list ==========
log.step('步骤 3.1/11: 配置 agents.list');
configContent = fs.readFileSync(configPath, 'utf8');
// 检查是否已有 agents.list
if (configContent.includes('"list": [')) {
// 已有 list,替换其中的配置
configContent = configContent.replace(
/"id":\s*"[^"]+",/g,
`"id": "gatewayName",`
);
configContent = configContent.replace(
/"name":\s*"[^"]*",\n\s*"agentDir":/g,
`"name": "gatewayDesc",\n "agentDir":`
);
configContent = configContent.replace(
/"agentDir":\s*"[^"]+"/g,
`"agentDir": "/home/admin/.openclaw/agents/gatewayName"`
);
configContent = configContent.replace(
/"workspace":\s*"[^"]+",\n\s*\}/g,
`"workspace": "/home/admin/.openclaw/workspace-gatewayName",\n }`
);
console.log(` ✓ agents.list: 已更新 (gatewayName)`);
} else {
// 没有 list,使用模板添加
const agentsListTemplate = `"agents": {\n "list": [\n {\n "id": "gatewayName",\n "name": "gatewayDesc",\n "agentDir": "/home/admin/.openclaw/agents/gatewayName",\n "workspace": "/home/admin/.openclaw/workspace-gatewayName"\n }\n ],\n "defaults":`;
configContent = configContent.replace(
/"agents":\s*{\s*"defaults":/,
agentsListTemplate
);
console.log(` ✓ agents.list: 已添加 (gatewayName)`);
}
fs.writeFileSync(configPath, configContent);
log.success('agents.list 配置完成');
// ========== 步骤 3.2: 全面检查并替换所有名称 ==========
log.step('步骤 3.2/11: 全面检查并替换名称');
configContent = fs.readFileSync(configPath, 'utf8');
let modified = false;
// 替换所有可能的 agent 名称引用
const replacements = [
// 替换 agent ID
{ pattern: /"id":\s*"[^"]+"/g, replacement: `"id": "gatewayName"`, desc: 'agent ID' },
// 替换 agent 名称
{ pattern: /"name":\s*"[^"]+"/g, replacement: `"name": "gatewayDesc"`, desc: 'agent 名称' },
// 替换 agentDir
{ pattern: /"agentDir":\s*"[^"]+"/g, replacement: `"agentDir": "/home/admin/.openclaw/agents/gatewayName"`, desc: 'agentDir' },
// 替换 workspace
{ pattern: /"workspace":\s*"[^"]+"/g, replacement: `"workspace": "/home/admin/.openclaw/workspace-gatewayName"`, desc: 'workspace' },
// 替换 UI basePath
{ pattern: /"basePath":\s*"[^"]+"/g, replacement: `"basePath": "gatewayName-ui"`, desc: 'UI basePath' },
// 替换 token 中的名称
{ pattern: /"token":\s*"[^"]+"/g, replacement: `"token": "gatewayName-token-gatewayDesc-gatewayPort"`, desc: 'token' },
];
for (const { pattern, replacement, desc } of replacements) {
const matches = configContent.match(pattern);
if (matches && matches.length > 0) {
configContent = configContent.replace(pattern, replacement);
console.log(` ✓ 已替换 desc: matches.length 处`);
modified = true;
}
}
if (!modified) {
console.log(` ✓ 所有名称已正确配置`);
}
fs.writeFileSync(configPath, configContent);
log.success('名称检查完成');
// ========== 步骤 3.5: 飞书配置修改提示 ==========
console.log(`\ncolors.yellow⚠️ 飞书应用配置确认colors.reset`);
console.log(`colors.gray━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━colors.reset`);
console.log('当前配置复制了 main 的飞书应用,需要确认:\n');
console.log(' 1) 使用新的飞书应用(推荐,每个网关独立)');
console.log(' 2) 使用和 main 相同的应用(共用应用)');
console.log(' 3) 跳过飞书配置(不绑定任何渠道,纯本地使用)\n');
const feishuChoice = await question('请选择 [1/2/3]: ');
if (feishuChoice === '1') {
console.log('\n请在飞书开发者后台创建新应用,然后输入配置:');
const newAppId = await question('新 App ID (cli_xxx): ');
const newAppSecret = await question('新 App Secret: ');
if (newAppId && newAppSecret) {
// 重新读取并修改配置
configContent = fs.readFileSync(configPath, 'utf8');
configContent = configContent.replace(/"appId":\s*"[^"]*"/, `"appId": "newAppId"`);
configContent = configContent.replace(/"appSecret":\s*"[^"]*"/, `"appSecret": "newAppSecret"`);
fs.writeFileSync(configPath, configContent);
log.success(`已更新飞书配置:newAppId`);
}
} else if (feishuChoice === '2') {
log.info('使用和 main 相同的飞书应用');
} else if (feishuChoice === '3') {
log.info('跳过飞书配置 - 网关将不绑定任何渠道(纯本地使用)');
log.info('后续如需绑定渠道,可手动修改配置文件或运行 openclaw channels login');
// 标记跳过后续飞书配置步骤
global.skipFeishuConfig = true;
}
// ========== 步骤 4: 复制 agents 配置(包含 API Key) ==========
log.step('步骤 4/11: 复制 agents 配置');
// 动态检测存在的 agent 目录
const possibleAgents = [];
try {
const agentDirs = fs.readdirSync(path.join(baseDir, 'agents'));
for (const dir of agentDirs) {
const agentDir = path.join(baseDir, 'agents', dir);
const agentSubDir = path.join(agentDir, 'agent');
if (fs.existsSync(agentSubDir) || fs.existsSync(agentDir)) {
possibleAgents.push(dir);
}
}
} catch (e) {
log.error(`读取 agent 目录失败:e.message`);
process.exit(1);
}
log.info(`检测到存在的 agent: '无'`);
// 构建搜索路径列表(动态)
const searchDirs = [];
for (const agent of possibleAgents) {
const agentSubDir = path.join(baseDir, 'agents', agent, 'agent');
const agentDir = path.join(baseDir, 'agents', agent);
if (fs.existsSync(agentSubDir)) searchDirs.push({ path: agentSubDir, name: `agent/agent` });
if (fs.existsSync(agentDir)) searchDirs.push({ path: agentDir, name: agent });
}
// 如果没有检测到任何 agent,使用 main 作为后备
if (searchDirs.length === 0) {
const mainAgentSubDir = path.join(baseDir, 'agents', 'main', 'agent');
const mainDir = path.join(baseDir, 'agents', 'main');
if (fs.existsSync(mainAgentSubDir)) searchDirs.push({ path: mainAgentSubDir, name: 'main/agent' });
if (fs.existsSync(mainDir)) searchDirs.push({ path: mainDir, name: 'main' });
}
if (!fs.existsSync(agentDir)) fs.mkdirSync(agentDir, { recursive: true });
if (!fs.existsSync(path.join(agentDir, 'agent'))) fs.mkdirSync(path.join(agentDir, 'agent'), { recursive: true });
// ========== 复制 agent 子目录内容 ==========
// 使用动态检测的目录列表
const dstAgentSubDir = path.join(agentDir, 'agent');
let agentFilesCopied = false;
// 按优先级尝试从检测到的目录复制
for (const { path: srcDir, name } of searchDirs) {
if (agentFilesCopied) break;
try {
const files = fs.readdirSync(srcDir);
if (files.length > 0) {
files.forEach(file => {
const src = path.join(srcDir, file);
const dst = path.join(dstAgentSubDir, file);
if (fs.statSync(src).isFile() && file !== 'models.json') {
fs.copyFileSync(src, dst);
}
});
log.success(`已复制 agent 配置(从 name)`);
agentFilesCopied = true;
}
} catch (e) {
log.warn(`从 name 复制失败:e.message`);
}
}
if (!agentFilesCopied) {
log.error('没有找到可复制的 agent 配置');
process.exit(1);
}
// ========== 复制 models.json(动态搜索) ==========
const dstModels = path.join(agentDir, 'models.json');
let modelsCopied = false;
// 使用动态检测的目录列表搜索 models.json
for (const { path: srcDir, name } of searchDirs) {
const modelsPath = path.join(srcDir, 'models.json');
if (fs.existsSync(modelsPath)) {
let modelsContent = fs.readFileSync(modelsPath, 'utf8');
modelsContent = modelsContent.replace(/\$api-key/g, API_KEY);
fs.writeFileSync(dstModels, modelsContent);
log.success(`已复制 models.json (从 name, API Key 已修复)`);
modelsCopied = true;
break;
}
}
if (!modelsCopied) {
log.error('无法复制 models.json(所有搜索路径都没有)');
log.error('搜索路径:');
searchPaths.forEach(({ path: p, name }) => {
log.error(` - name: p`);
});
process.exit(1);
}
// ========== 步骤 5: 创建目录结构 ==========
log.step('步骤 5/11: 创建目录结构');
const dirsToCreate = [
path.join(workspaceDir, 'memory'),
path.join(workspaceDir, 'skills'),
path.join(agentDir, 'sessions')
];
dirsToCreate.forEach(dir => {
try {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
log.success(`创建目录:dir`);
} else {
log.info(`目录已存在:dir`);
}
} catch (e) {
log.error(`创建目录失败:dir - e.message`);
process.exit(1);
}
});
// 从选择的配置来源复制基础文件
['AGENTS.md', 'SOUL.md', 'TOOLS.md'].forEach(file => {
const src = path.join(srcWorkspace, file);
const dst = path.join(workspaceDir, file);
if (fs.existsSync(src)) {
try {
fs.copyFileSync(src, dst);
log.success(`复制文件:file (从 global.sourceAgentName)`);
} catch (e) {
log.warn(`复制 file 失败:e.message`);
}
}
});
log.success('目录结构已创建');
// ========== 步骤 6: 选择是否复制技能 ==========
log.step('步骤 6/11: 选择技能同步');
const srcSkillsDir = path.join(baseDir, `workspace-global.sourceAgentName || 'main'`, 'skills');
const dstSkills = path.join(workspaceDir, 'skills');
// 列出可用技能
const availableSkills = [];
if (fs.existsSync(srcSkillsDir)) {
fs.readdirSync(srcSkillsDir).forEach(skill => {
const src = path.join(srcSkillsDir, skill);
if (fs.statSync(src).isDirectory()) {
availableSkills.push(skill);
}
});
}
console.log(`发现 availableSkills.length 个可用技能(从 global.sourceAgentName):\n`);
availableSkills.forEach((skill, i) => {
console.log(` [String(i+1).padStart(2)] skill`);
});
console.log(` [ a] 全选`);
console.log(` [ n] 不复制(全新开始)`);
console.log(` [ b] 只复制基础技能 (self-improving-agent, proactive-agent)`);
console.log('\n提示:输入多个数字用逗号分隔,如:1,3,5');
const skillInput = await question('\n请选择要同步的技能:') || 'b';
if (!fs.existsSync(dstSkills)) fs.mkdirSync(dstSkills, { recursive: true });
let skillsToCopy = [];
if (skillInput.toLowerCase() === 'n') {
console.log(' 选择:不复制技能(全新开始)');
log.success('技能目录已创建,可以后续手动添加');
} else if (skillInput.toLowerCase() === 'a') {
console.log(' 选择:完整复制所有技能');
skillsToCopy = availableSkills;
} else if (skillInput.toLowerCase() === 'b') {
console.log(' 选择:只复制基础技能');
skillsToCopy = ['self-improving-agent', 'proactive-agent'].filter(s => availableSkills.includes(s));
} else {
// 解析选择的数字
const indices = skillInput.split(',').map(s => parseInt(s.trim()) - 1).filter(n => !isNaN(n) && n >= 0 && n < availableSkills.length);
skillsToCopy = indices.map(i => availableSkills[i]);
console.log(` 选择:复制 skillsToCopy.length 个技能`);
}
// 执行复制
let copiedCount = 0;
skillsToCopy.forEach(skill => {
const src = path.join(srcSkillsDir, skill);
const dst = path.join(dstSkills, skill);
if (fs.existsSync(src)) {
if (fs.existsSync(dst)) fs.rmSync(dst, { recursive: true, force: true });
fs.cpSync(src, dst, { recursive: true });
log.success(`同步技能:skill`);
copiedCount++;
}
});
if (copiedCount > 0) log.success(`共同步 copiedCount 个技能`);
// ========== 步骤 7: 选择是否复制记忆 ==========
log.step('步骤 7/11: 选择记忆同步');
const srcMemory = path.join(srcWorkspace, 'memory');
const dstMemory = path.join(workspaceDir, 'memory');
// 列出可用记忆文件
const memoryFiles = [];
if (fs.existsSync(srcMemory)) {
fs.readdirSync(srcMemory).forEach(file => {
if (file.endsWith('.md')) {
memoryFiles.push(file);
}
});
}
if (memoryFiles.length > 0) {
console.log(`发现 memoryFiles.length 个记忆文件(从 global.sourceAgentName):\n`);
memoryFiles.forEach((file, i) => {
console.log(` [String(i+1).padStart(2)] file`);
});
console.log(` [ a] 全选`);
console.log(` [ n] 不复制(全新开始)`);
const memoryInput = await question('\n请选择要同步的记忆文件:') || 'n';
if (!fs.existsSync(dstMemory)) fs.mkdirSync(dstMemory, { recursive: true });
let memoriesToCopy = [];
if (memoryInput.toLowerCase() === 'n') {
console.log(' 选择:不复制记忆文件(全新开始)');
log.success('记忆目录已创建,可以后续手动添加');
} else if (memoryInput.toLowerCase() === 'a') {
console.log(' 选择:复制所有记忆文件');
memoriesToCopy = memoryFiles;
} else {
// 解析选择的数字
const indices = memoryInput.split(',').map(s => parseInt(s.trim()) - 1).filter(n => !isNaN(n) && n >= 0 && n < memoryFiles.length);
memoriesToCopy = indices.map(i => memoryFiles[i]);
console.log(` 选择:复制 memoriesToCopy.length 个记忆文件`);
}
// 执行复制
let copiedCount = 0;
memoriesToCopy.forEach(file => {
const src = path.join(srcMemory, file);
const dst = path.join(dstMemory, file);
fs.copyFileSync(src, dst);
log.success(`复制记忆:file`);
copiedCount++;
});
if (copiedCount > 0) log.success(`已复制 copiedCount 个记忆文件`);
} else {
console.log(' 没有找到记忆文件');
if (!fs.existsSync(dstMemory)) fs.mkdirSync(dstMemory, { recursive: true });
log.success('记忆目录已创建');
}
// ========== 步骤 8: 创建 systemd 服务 ==========
log.step('步骤 8/11: 创建 systemd 服务');
const systemdDir = path.join(process.env.HOME, '.config', 'systemd', 'user');
if (!fs.existsSync(systemdDir)) fs.mkdirSync(systemdDir, { recursive: true });
const serviceFile = path.join(systemdDir, `openclaw-gateway-gatewayName.service`);
fs.writeFileSync(serviceFile, `[Unit]
Description=OpenClaw Gateway - gatewayDesc
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/node /opt/openclaw/dist/index.js gateway
Restart=on-failure
RestartSec=10
Environment=HOME=process.env.HOME
Environment=TMPDIR=/tmp/openclaw-gatewayName
Environment=OPENCLAW_SERVICE_MARKER=openclaw-gatewayName
Environment=OPENCLAW_SERVICE_KIND=gateway
Environment=OPENCLAW_CONFIG_PATH=configPath
Environment=OPENCLAW_AGENT_DIR=agentDir/agent
Environment=PI_CODING_AGENT_DIR=agentDir/agent
[Install]
WantedBy=default.target
`);
log.success(`创建服务文件:serviceFile`);
// ========== 配置内存限制 ==========
log.step('配置内存限制');
try {
// 从选择的配置来源读取内存限制(如果存在)
const memoryLimitConf = path.join(srcWorkspace, '.openclaw', 'memory-limit.conf');
let gatewayCount = 1;
let memPerGateway = 600; // 默认 600MB
let totalMemMB = 0; // 初始化
if (fs.existsSync(memoryLimitConf)) {
// 从配置文件读取
const memoryConfig = fs.readFileSync(memoryLimitConf, 'utf8');
const countMatch = memoryConfig.match(/GATEWAY_COUNT=(\d+)/);
const memMatch = memoryConfig.match(/MEMORY_PER_GATEWAY=(\d+)/);
if (countMatch) gatewayCount = parseInt(countMatch[1]);
if (memMatch) memPerGateway = parseInt(memMatch[1]);
log.info(`从配置读取:gatewayCount个网关,memPerGatewayMB/网关`);
} else {
// 自动计算
const memInfo = fs.readFileSync('/proc/meminfo', 'utf8');
const totalMemMatch = memInfo.match(/MemTotal:\s*(\d+)/);
const totalMemKB = totalMemMatch ? parseInt(totalMemMatch[1]) : 4000000;
totalMemMB = Math.floor(totalMemKB / 1024);
// 统计现有网关数量
try {
const services = execSync('ls ~/.config/systemd/user/openclaw-gateway-*.service 2>/dev/null || true', { encoding: 'utf8' });
gatewayCount = services.trim().split('\n').filter(s => s.trim()).length + 1; // +1 表示新网关
} catch (e) {
gatewayCount = 1;
}
// 计算每个网关的内存(系统内存的 70% / 网关数量)
const availableMem = Math.floor(totalMemMB * 70 / 100);
memPerGateway = Math.floor(availableMem / gatewayCount);
if (memPerGateway < 400) memPerGateway = 400; // 最少 400MB
if (memPerGateway > 800) memPerGateway = 800; // 最多 800MB
log.info(`自动计算:系统 totalMemMBMB, gatewayCount个网关,memPerGatewayMB/网关`);
}
if (gatewayCount < 1) gatewayCount = 1;
// 计算内存限制
const memoryHigh = Math.floor(memPerGateway * 80 / 100);
const memorySwap = Math.floor(memPerGateway * 50 / 100);
// 创建内存限制配置
const serviceDropInDir = path.join(systemdDir, `gatewayName.service.d`);
if (!fs.existsSync(serviceDropInDir)) fs.mkdirSync(serviceDropInDir, { recursive: true });
const memoryLimitConfig = `[Service]
MemoryHigh=memoryHighM
MemoryMax=memPerGatewayM
MemorySwapMax=memorySwapM
`;
const memoryLimitFile = path.join(serviceDropInDir, 'memory-limit.conf');
fs.writeFileSync(memoryLimitFile, memoryLimitConfig);
if (totalMemMB > 0) {
log.success(`配置内存限制:MemoryMax=memPerGatewayM (系统 totalMemMBMB, gatewayCount个网关)`);
} else {
log.success(`配置内存限制:MemoryMax=memPerGatewayM`);
}
log.info(`MemoryHigh=memoryHighM (软限制)`);
log.info(`MemorySwapMax=memorySwapM`);
// 重新加载 systemd
execSync('systemctl --user daemon-reload', { stdio: 'pipe' });
log.success('systemd 已重新加载');
} catch (e) {
log.warn(`内存限制配置失败:e.message`);
log.warn('可以稍后手动配置:bash setup-gateway-memory-limits.sh');
// 至少重新加载 systemd
execSync('systemctl --user daemon-reload', { stdio: 'pipe' });
log.success('systemd 已重新加载');
}
// ========== 步骤 9: 启动网关 ==========
log.step('步骤 9/11: 启动网关');
const enableService = await question('是否现在启用并启动服务?[Y/n]: ');
if (enableService.toLowerCase() !== 'n') {
execSync(`systemctl --user enable openclaw-gateway-gatewayName.service`, { stdio: 'pipe' });
log.success('启用服务');
execSync(`systemctl --user start openclaw-gateway-gatewayName.service`, { stdio: 'pipe' });
log.success('启动服务');
// 动态等待网关启动(轮询检查,最多 30 秒)
console.log(' ⏳ 等待网关启动...');
let gatewayStarted = false;
const maxAttempts = 30;
for (let i = 1; i <= maxAttempts; i++) {
try {
const status = execSync(`systemctl --user is-active openclaw-gateway-gatewayName.service`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
if (status === 'active') {
log.success(`网关状态:active (运行中) - 尝试 i/maxAttempts`);
gatewayStarted = true;
break;
}
} catch (e) {
// 继续等待
}
if (i < maxAttempts) {
process.stdout.write(`\r 等待中... (i/maxAttempts)`);
await new Promise(r => setTimeout(r, 1000));
}
}
process.stdout.write('\r \r');
if (!gatewayStarted) {
log.warn('网关未在规定时间内启动');
log.warn(`检查 systemd 日志:journalctl --user -u openclaw-gateway-gatewayName.service -n 30`);
log.warn(`检查应用日志:tail -n 30 /tmp/openclaw/openclaw-gatewayName.log`);
// 显示最近的错误日志
try {
console.log('\n最近日志片段:');
execSync(`tail -n 20 /tmp/openclaw/openclaw-gatewayName.log 2>/dev/null | grep -i error || echo '暂无错误日志'`, { stdio: 'inherit' });
} catch (e) {
// 忽略
}
}
// 检查端口监听(多等 5 秒,给应用初始化时间)
console.log(' ⏳ 等待端口监听...');
await new Promise(r => setTimeout(r, 5000));
let portListening = false;
const maxPortAttempts = 10;
for (let i = 1; i <= maxPortAttempts; i++) {
try {
// 尝试多种检测方法
const ssCheck = execSync(`ss -tlnp 2>/dev/null | grep ":gatewayPort " || true`, { encoding: 'utf8' });
const netstatCheck = execSync(`netstat -tlnp 2>/dev/null | grep ":gatewayPort " || true`, { encoding: 'utf8' });
if (ssCheck.trim() || netstatCheck.trim()) {
log.success(`端口 gatewayPort 正在监听 (尝试 i/maxPortAttempts)`);
portListening = true;
break;
}
} catch (e) {
// 继续尝试
}
if (i < maxPortAttempts) {
process.stdout.write(`\r 等待端口监听... (i/maxPortAttempts)`);
await new Promise(r => setTimeout(r, 1000));
}
}
process.stdout.write('\r \r');
if (!portListening) {
log.warn(`端口 gatewayPort 未监听`);
log.warn('可能原因:网关启动失败或正在初始化');
log.warn(`查看日志:journalctl --user -u openclaw-gateway-gatewayName.service -n 30`);
log.warn(`或查看:tail -f /tmp/openclaw/openclaw-gatewayName.log`);
}
}
// ========== 步骤 9.5: 检查 dmPolicy 配置 ==========
log.step('步骤 9.5/11: 检查配对模式配置');
// 读取配置中的 dmPolicy
let dmPolicy = 'open'; // 默认
try {
const configContent = fs.readFileSync(configPath, 'utf8');
const dmPolicyMatch = configContent.match(/"dmPolicy":\s*"([^"]+)"/);
if (dmPolicyMatch) {
dmPolicy = dmPolicyMatch[1];
}
} catch (e) {
log.warn('无法读取 dmPolicy 配置,默认为 open');
}
console.log(`\ncolors.cyan配对模式检测:colors.reset`);
if (dmPolicy === 'pairing') {
console.log(` colors.yellow⚠colors.reset 当前配置:dmPolicy = "pairing" (需要配对)`);
console.log(` colors.gray说明:用户首次给机器人发消息时,会收到一个 6 位数配对码colors.reset`);
} else {
console.log(` colors.green✅colors.reset 当前配置:dmPolicy = "open" (无需配对)`);
console.log(` colors.gray说明:用户可以直接给机器人发消息,无需配对colors.reset`);
}
console.log('');
// ========== 步骤 9.5: 生成飞书配置清单 ==========
if (!global.skipFeishuConfig) {
log.step('步骤 9.5/11: 生成飞书配置清单');
const checklistPath = path.join(workspaceDir, 'feishu-checklist.md');
const checklistContent = `# 飞书配置检查清单 - gatewayDesc
## 应用信息
- **应用名称**: gatewayDesc
- **App ID**: (待填写)
- **App Secret**: (待填写)
## 1. 权限配置
请在飞书开发者后台「权限管理」页面添加以下权限:
### 必需权限
- [ ] contact:contact.base:readonly - 读取用户基本信息
- [ ] im:message - 发送和接收消息
- [ ] im:message.receive - 接收消息
- [ ] im:message.send - 发送消息
- [ ] im:chat - 访问群聊信息
- [ ] im:chat.p2p - 访问私聊会话
- [ ] cardkit:card:write - 创建卡片消息
- [ ] websocket - 使用长连接
- [ ] event:im.message.receive_v1 - 接收消息事件
- [ ] bot:chat - 机器人访问聊天
### 可选权限(根据需求)
- [ ] aily:file:read - 读取文件
- [ ] aily:file:write - 写入文件
- [ ] contact:user.employee_id:readonly - 读取员工 ID
## 2. 事件订阅
- [ ] 订阅方式:使用长连接接收事件/回调
- [ ] 添加事件:im.message.receive_v1
- [ ] 保存配置
## 3. 验证步骤
- [ ] 在飞书中给机器人发消息
- [ ] 机器人能够正常回复
- [ ] 如果 dmPolicy=pairing,完成配对授权
## 4. 本地配置
- **配置文件**: configPath
- **端口**: gatewayPort
- **服务名称**: openclaw-gateway-gatewayName.service
## 管理命令
\`\`\`bash
# 查看状态
systemctl --user status openclaw-gateway-gatewayName.service
# 查看日志
journalctl --user -u openclaw-gateway-gatewayName.service -f
# 重启网关
systemctl --user restart openclaw-gateway-gatewayName.service
# 查看实时日志
tail -f /tmp/openclaw/openclaw-gatewayName.log
\`\`\`
---
**生成时间**: new Date().toISOString()
`;
fs.writeFileSync(checklistPath, checklistContent);
log.success(`生成飞书配置清单:checklistPath`);
console.log(`\ncolors.cyan提示:colors.reset可以打印此清单,在飞书后台逐项配置\n`);
} else {
log.info('已跳过飞书配置清单生成(用户选择纯本地使用)');
}
// ========== 步骤 10: 飞书配置引导 ==========
if (!global.skipFeishuConfig) {
log.step('步骤 10/11: 飞书配置引导');
console.log(`colors.cyan━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━colors.reset`);
console.log(`colors.yellow📋 现在需要导入飞书应用权限colors.reset`);
console.log(`colors.cyan━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━colors.reset\n`);
console.log('请在飞书开发者后台「权限管理」页面,直接导入以下配置:\n');
console.log(`colors.gray━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━colors.reset`);
console.log(colors.yellow + JSON.stringify({
scopes: {
tenant: [
"aily:file:read",
"aily:file:write",
"application:application.app_message_stats.overview:readonly",
"application:application:self_manage",
"application:bot.menu:write",
"contact:user.employee_id:readonly",
"corehr:file:download",
"event:ip_list",
"im:chat.access_event.bot_p2p_chat:read",
"im:chat.members:bot_access",
"im:message",
"im:message.group_at_msg:readonly",
"im:message.p2p_msg:readonly",
"im:message:readonly",
"im:message:send_as_bot",
"im:resource"
],
user: ["aily:file:read", "aily:file:write", "im:chat.access_event.bot_p2p_chat:read"]
}
}, null, 2) + colors.reset);
console.log(`colors.gray━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━colors.reset\n`);
console.log('操作步骤:');
console.log(' 1. 访问飞书开发者后台');
console.log(' 2. 进入应用 → 权限管理');
console.log(' 3. 点击「批量导入」或手动添加上述权限');
console.log(' 4. 点击「申请权限」');
console.log(' 5. 等待审核通过(通常几分钟)\n');
await question('✅ 权限已导入后,按回车继续...');
log.success('权限已导入');
// 等待长连接 - 智能检测(总共 15 秒)
console.log('\n🔗 正在等待飞书长连接自动建立...\n');
let connected = false;
const checkLogPath = `/tmp/openclaw/openclaw-gatewayName.log`;
const maxAttempts = 15; // 最多 15 次
const intervalMs = 1000; // 每次间隔 1 秒
for (let i = 1; i <= maxAttempts; i++) {
try {
// 检查日志中的连接成功标志
const logContent = execSync(`tail -100 checkLogPath 2>/dev/null || true`, { encoding: 'utf8' });
// 多个连接成功标志(按优先级排序)
const connectedSigns = [
// 最高优先级:能收到消息,说明长连接肯定建立了
'feishu[default]: received message',
'feishu[default]: dispatching to agent',
// 其次:WebSocket 已启动
'WebSocket client started',
'ws client ready',
'event-dispatch is ready',
'[feishu] feishu[default]: WebSocket'
];
// 检查是否有错误
const errorSigns = ['WebSocket closed', 'connection failed', 'auth failed', 'permission denied', 'Access denied'];
const hasError = errorSigns.some(sign => logContent.includes(sign));
// 检查是否连接成功(任意一个标志即可)
const hasConnected = connectedSigns.some(sign => logContent.includes(sign));
if (hasConnected && !hasError) {
log.success('飞书长连接已建立!');
connected = true;
break;
}
if (hasError) {
log.warn('检测到权限错误,请确认权限已开通');
// 继续等待,因为权限可能刚开通还在生效中
}
} catch(e) {
// 日志文件可能还不存在
}
if (i < maxAttempts) {
process.stdout.write(`\r ⏳ 等待中... (i/maxAttempts)`);
await new Promise(r => setTimeout(r, intervalMs));
}
}
process.stdout.write('\r \r');
if (connected) {
log.success('验证通过:飞书长连接正常');
} else {
log.warn('飞书长连接尚未建立');
log.warn('请检查:1) 权限是否已开通 2) App ID/Secret 是否正确');
log.warn(`查看日志:tail -f checkLogPath`);
}
} else {
log.info('已跳过飞书配置引导(用户选择纯本地使用)');
}
// ========== 步骤 11: 添加接收消息事件 ==========
if (!global.skipFeishuConfig) {
log.step('步骤 11/11: 添加接收消息事件');
console.log(`colors.cyan━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━colors.reset`);
console.log(`colors.yellow📋 现在需要添加接收消息事件colors.reset`);
console.log(`colors.cyan━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━colors.reset\n`);
console.log('请在飞书开发者后台「事件订阅」页面:\n');
console.log(' 1. 进入应用 → 事件订阅');
console.log(' 2. 确保订阅方式选择:「使用长连接接收事件/回调」');
console.log(' 3. 点击「添加事件」');
console.log(' 4. 搜索并添加:im.message.receive_v1');
console.log(' 5. 点击「保存」\n');
await question('✅ 事件已添加后,按回车继续...');
log.success('已确认事件订阅配置');
} else {
log.info('已跳过事件订阅配置(用户选择纯本地使用)');
}
// ========== 总结 ==========
console.log(`\ncolors.green━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━colors.reset`);
console.log(`colors.green🎉 恭喜!gatewayDesc 配置完成!colors.reset`);
console.log(`colors.green━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━colors.reset\n`);
console.log('配置摘要:');
console.log(` Agent 名称:gatewayName`);
console.log(` 网关名称:gatewayDesc`);
console.log(` 端口:gatewayPort`);
console.log('');
console.log('✅ 已完成:');
console.log(' ✓ 配置复制(完整复制 main)');
console.log(' ✓ API Key 修复');
console.log(' ✓ systemd 服务创建');
if (!global.skipFeishuConfig) {
console.log(' ✓ 飞书权限导入');
console.log(' ✓ 长连接建立');
console.log(' ✓ 接收消息事件添加');
} else {
console.log(' ○ 飞书配置(已跳过,纯本地使用)');
}
console.log('');
console.log('管理命令:');
console.log(` 查看状态:systemctl --user status openclaw-gateway-gatewayName.service`);
console.log(` 查看日志:journalctl --user -u openclaw-gateway-gatewayName.service -f`);
console.log(` 重启网关:systemctl --user restart openclaw-gateway-gatewayName.service`);
console.log('');
// ========== 步骤 12: 配对码授权(根据 dmPolicy 决定) ==========
// 如果跳过了飞书配置,则跳过配对测试
if (!global.skipFeishuConfig && dmPolicy === 'pairing') {
// 需要配对模式
console.log(`colors.cyan━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━colors.reset`);
console.log(`colors.yellow📬 现在进行配对授权colors.reset`);
console.log(`colors.cyan━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━colors.reset\n`);
console.log(`colors.yellow⚠colors.reset 当前配置为 colors.boldpairingcolors.reset 模式,需要配对才能接收消息。\n`);
console.log('操作步骤:');
console.log(' 1. 在飞书中给机器人发送任意消息');
console.log(' 2. 机器人会回复一个配对码(6 位数字)');
console.log(' 3. 将配对码输入到这里\n');
const needPairing = await question('是否需要现在配对?[Y/n]: ');
if (needPairing.toLowerCase() !== 'n') {
console.log('\n请前往飞书给机器人发消息,然后输入配对码...');
const pairingCode = await question('配对码(6 位数字): ');
if (pairingCode && pairingCode.length >= 4) {
console.log('\n正在提交配对码...');
// 调用 openclaw pairing 命令
try {
const result = execSync(`openclaw pairing pairingCode.trim() --agent gatewayName`, {
encoding: 'utf8',
stdio: 'pipe'
});
log.success('配对成功!');
console.log(result);
} catch (e) {
log.warn('配对命令执行失败,请手动配对');
log.warn(`手动配对:openclaw pairing pairingCode.trim() --agent gatewayName`);
}
} else {
log.info('跳过配对');
}
console.log('\n配对后测试:');
console.log(` 在飞书中给机器人发消息,应该能正常对话了!\n`);
} else {
log.info('跳过配对(稍后需要时可手动执行:openclaw pairing <配对码> --agent ' + gatewayName + ')');
}
} else {
// open 模式,无需配对
console.log(`colors.cyan━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━colors.reset`);
console.log(`colors.green✅ 无需配对colors.reset`);
console.log(`colors.cyan━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━colors.reset\n`);
console.log(`colors.green✓colors.reset 当前配置为 colors.boldopencolors.reset 模式,无需配对即可使用。\n`);
// open 模式下不显示测试提示,直接显示下一步
}
console.log('');
console.log('下一步:');
if (global.skipFeishuConfig) {
console.log(' ℹ️ 飞书配置已跳过(纯本地使用)');
console.log(' 如需绑定渠道,可手动修改配置文件或运行 openclaw channels login');
console.log('');
console.log('管理命令:');
console.log(` 查看状态:systemctl --user status openclaw-gateway-gatewayName.service`);
console.log(` 查看日志:journalctl --user -u openclaw-gateway-gatewayName.service -f`);
} else {
console.log(' 1. 在飞书上给机器人发消息');
console.log(' 2. 开始正常使用!');
}
console.log('');
} catch (error) {
log.error(`配置出错:error.message`);
process.exit(1);
} finally {
rl.close();
}
}
main();
展示完整终端命令及其全部输出,包括正常、错误、长输出、交互和后台运行,格式清晰易读,便于理解和调试。
# Command Output Display - 命令输出展示技能
## 核心原则
**用户应该能像坐在你旁边看你操作终端一样,看到每一个命令和它的完整输出!**
## 📋 展示规范
### 1. 命令执行前
**格式:**
```bash
$ <command>
```
**说明:**
- 使用 `$` 前缀表示要执行的命令
- 如果是多行命令,完整展示
- 如果有注释,用 `#` 标注
**示例:**
```bash
$ systemctl --user status openclaw-gateway-boss.service --no-pager
```
```bash
$ cat > /tmp/test.json << 'EOF'
{
"config": "value"
}
EOF
```
### 2. 命令执行后
**格式:**
```bash
# 输出:
<command output here>
```
**说明:**
- 使用 `# 输出:` 标记输出开始
- 完整展示 stdout 和 stderr
- 保持原始格式(包括颜色代码如果有的话)
- 不要截断重要信息
**示例:**
```bash
# 输出:
● openclaw-gateway-boss.service - OpenClaw Gateway - Boss (爪爪机器人)
Loaded: loaded (/home/admin/.config/systemd/user/openclaw-gateway-boss.service; enabled)
Active: active (running) since Thu 2026-03-12 16:04:48 CST
Main PID: 5820 (MainThread)
```
### 3. 错误输出
**格式:**
```bash
# 输出(错误):
<error output>
# 错误信息:
- 退出码:<exit code>
- 详细说明:<details>
```
**示例:**
```bash
# 输出(错误):
Job for openclaw-gateway-boss.service failed because the control process exited with error code.
# 错误信息:
- 退出码:1
- 服务状态:failed
- 可能原因:配置文件不存在
```
## 🎯 完整流程示例
### 示例 1: 服务管理
```
📝 目标:检查 boss 网关服务状态
执行命令:
```bash
$ systemctl --user status openclaw-gateway-boss.service --no-pager -n 10
```
# 输出:
● openclaw-gateway-boss.service - OpenClaw Gateway - Boss (爪爪机器人)
Loaded: loaded (/home/admin/.config/systemd/user/openclaw-gateway-boss.service; enabled; vendor preset: enabled)
Active: active (running) since Thu 2026-03-12 16:04:48 CST; 6s ago
Main PID: 5820 (MainThread)
CGroup: /user.slice/user-1000.slice/[email protected]/openclaw-gateway-boss.service
└─5820 /usr/bin/node /opt/openclaw/dist/index.js gateway
Mar 12 16:04:48 iZbp14orusdl51yeoyqk9yZ systemd[992]: Started OpenClaw Gateway - Boss (爪爪机器人).
✅ 服务运行正常
```
### 示例 2: 文件操作
```
📝 目标:创建配置文件
执行命令:
```bash
$ cat > /home/admin/.openclaw/openclaw-boss.json << 'EOF'
{
"gateway": {
"port": 19922,
"mode": "local"
}
}
EOF
```
# 输出:
(无输出表示成功)
验证文件:
```bash
$ ls -lh /home/admin/.openclaw/openclaw-boss.json
```
# 输出:
-rw-r--r-- 1 admin admin 6.0K Mar 12 17:50 /home/admin/.openclaw/openclaw-boss.json
✅ 文件创建成功,大小 6KB
```
### 示例 3: 多步骤操作
```
📝 任务:配置新网关
━━━━━━━━━━━━━━━━━━━━━━━━━━━
步骤 1/4: 创建目录
━━━━━━━━━━━━━━━━━━━━━━━━━━━
执行命令:
```bash
$ mkdir -p /home/admin/.openclaw/agents/boss
```
# 输出:
(无输出)
✅ 完成
━━━━━━━━━━━━━━━━━━━━━━━━━━━
步骤 2/4: 复制 agent 配置
━━━━━━━━━━━━━━━━━━━━━━━━━━━
执行命令:
```bash
$ cp -r /home/admin/.openclaw/agents/main/* /home/admin/.openclaw/agents/boss/
```
# 输出:
(无输出)
✅ 完成
━━━━━━━━━━━━━━━━━━━━━━━━━━━
步骤 3/4: 创建配置文件
━━━━━━━━━━━━━━━━━━━━━━━━━━━
[展示文件内容...]
✅ 配置文件已创建
━━━━━━━━━━━━━━━━━━━━━━━━━━━
步骤 4/4: 验证结构
━━━━━━━━━━━━━━━━━━━━━━━━━━━
执行命令:
```bash
$ tree -L 2 /home/admin/.openclaw/agents/boss/
```
# 输出:
/home/admin/.openclaw/agents/boss/
├── agent
│ └── agent.json
└── sessions
└── sessions.json
✅ 目录结构正确
━━━━━━━━━━━━━━━━━━━━━━━━━━━
总结:所有步骤完成!
━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
### 示例 4: 调试问题
```
⚠️ 服务启动失败,正在调试...
📝 步骤 1: 查看服务状态
执行命令:
```bash
$ systemctl --user status openclaw-gateway-boss.service --no-pager
```
# 输出:
● openclaw-gateway-boss.service - OpenClaw Gateway - Boss
Loaded: loaded
Active: failed (Result: exit-code)
Main PID: 1234 (code=exited, status=1/FAILURE)
❌ 服务启动失败
---
📝 步骤 2: 查看详细日志
执行命令:
```bash
$ journalctl --user -u openclaw-gateway-boss.service -n 20 --no-pager
```
# 输出:
Mar 12 16:05:14 node[1234]: error: unknown option '--config'
Mar 12 16:05:14 systemd[992]: openclaw-gateway-boss.service: Failed with result 'exit-code'.
🔍 发现问题:命令行参数 `--config` 不被支持
---
📝 步骤 3: 检查正确的参数
执行命令:
```bash
$ node /opt/openclaw/dist/index.js gateway --help
```
# 输出:
Usage: gateway [options]
Options:
--port <port> Gateway port (default: 10041)
--verbose Enable verbose logging
--help Show help
✅ 应该使用 `--port` 而不是 `--config`
```
## 🛠️ 特殊情况处理
### 1. 长输出
如果输出很长(超过 50 行):
```bash
# 输出(前 50 行):
[显示前 50 行]
... (共 234 行,需要看完整输出吗?)
```
### 2. 二进制输出
如果命令产生二进制输出或乱码:
```bash
# 输出:
[二进制数据,已跳过]
# 文件信息:
-rwxr-xr-x 1 admin admin 1.2M Mar 12 17:50 binary_file
```
### 3. 交互式命令
如果需要用户交互:
```
⚠️ 这个命令需要交互输入:
```bash
$ openclaw channels login
```
输出:
```
? 选择渠道类型:
❯ WhatsApp
Telegram
Discord
```
需要你在终端亲自操作,我没法代劳。要我继续其他步骤吗?
```
### 4. 后台命令
如果命令在后台运行:
```
📝 启动后台服务...
执行命令:
```bash
$ systemctl --user start openclaw-gateway.service &
```
# 输出:
[后台运行中...]
等待 3 秒后检查状态:
```bash
$ systemctl --user is-active openclaw-gateway.service
```
# 输出:
active
✅ 服务已在后台启动
```
## 📊 输出解读
不要只展示输出,还要帮助解读:
```bash
# 输出:
tcp 0 0 0.0.0.0:19922 0.0.0.0:* LISTEN 5820/openclaw-gatew
# 解读:
- 端口 19922 正在监听
- 进程 ID: 5820
- 绑定地址:0.0.0.0 (所有接口)
✅ 网关已成功启动
```
## ✅ 检查清单
执行命令后,确保:
- [ ] 展示了完整的命令
- [ ] 展示了完整的输出
- [ ] 包含了错误信息(如果有)
- [ ] 提供了解读或总结
- [ ] 格式清晰易读
---
**目标:** 让用户感觉就像坐在你旁边,看着你在终端上操作一样!
统筹协调各 Agent 任务,分配和跟踪 Ass Agent 与 Ops Agent 工作,汇总并向用户报告整体进展和状态。
# Boss Agent - 统筹协调 Agent
## 角色定位
你是**Boss Agent**,是系统的**统筹协调者**。
## 核心职责
### 1. 统筹全局
- 接收用户的总体指令
- 分解任务给 Ass Agent 和 Ops Agent
- 跟踪任务进度
- 汇总结果汇报给用户
### 2. 信息收集
- **读取 Ass Agent 的信息** - 了解小助手的工作状态
- **读取 Ops Agent 的信息** - 了解运维机器人的工作状态
- **跨 Agent 查询** - 可以访问其他 Agent 的会话历史、记忆、状态
### 3. 任务分发
- 给 Ass Agent 分配日常任务
- 给 Ops Agent 分配运维任务
- 协调多 Agent 协作
### 4. 汇报总结
- 向用户汇报整体进展
- 汇总各 Agent 的工作结果
- 提供全局视角的分析
## 🔧 技术实现
### 访问其他 Agent 的方式
#### 1. 读取会话历史
```bash
# 查看 Ass Agent 的会话
sessions_list --agent ass
# 查看 Ops Agent 的会话
sessions_list --agent ops
```
#### 2. 发送任务
```bash
# 给 Ass Agent 发送任务
sessions_send --session-key agent:ass:main --message "请处理这个任务:..."
# 给 Ops Agent 发送任务
sessions_send --session-key agent:ops:main --message "请执行运维任务:..."
```
#### 3. 查询状态
```bash
# 检查各 Agent 状态
systemctl --user status openclaw-gateway-ass.service
systemctl --user status openclaw-gateway-ops.service
```
## 📋 工作流程
### 示例:用户说"检查所有服务状态"
```
1. 【接收任务】
用户:"检查所有服务状态"
2. 【分解任务】
Boss 思考:
- Ass Agent 负责:文档服务、用户服务
- Ops Agent 负责:系统服务、网关服务
3. 【分发任务】
→ 发送消息给 Ass Agent:
"请检查文档服务和用户服务的状态"
→ 发送消息给 Ops Agent:
"请检查系统服务和网关服务的状态"
4. 【等待回复】
← 接收 Ass Agent 回复:
"文档服务:正常,用户服务:正常"
← 接收 Ops Agent 回复:
"系统服务:正常,网关服务:重启中"
5. 【汇总汇报】
→ 发送给用户:
"✅ 所有服务检查完成:
Ass Agent 负责:
- 文档服务:✓ 正常
- 用户服务:✓ 正常
Ops Agent 负责:
- 系统服务:✓ 正常
- 网关服务:⚠ 重启中(预计 2 分钟)
整体状态:正常"
```
## 🎯 权限说明
### 可以做的:
- ✅ 读取 Ass Agent 的会话历史
- ✅ 读取 Ops Agent 的会话历史
- ✅ 向 Ass Agent 发送任务
- ✅ 向 Ops Agent 发送任务
- ✅ 查询所有 Agent 的状态
- ✅ 汇总信息并汇报
### 需要用户授权的:
- ⚠️ 修改其他 Agent 的配置
- ⚠️ 重启其他 Agent 的服务
- ⚠️ 删除其他 Agent 的数据
## 📊 与其他 Agent 的关系
| 角色 | 职责 | 汇报对象 |
|------|------|----------|
| **Boss (我)** | 统筹协调 | 用户 |
| **Ass** | 日常助手 | Boss |
| **Ops** | 运维支持 | Boss |
## 💡 工作原则
1. **透明** - 让用户知道你在协调哪些 Agent
2. **高效** - 并行分发任务,不串行等待
3. **准确** - 汇总信息时保持准确,不歪曲
4. **及时** - 任务完成后立刻汇报
## 🚀 启动检查
确认所有 Agent 运行正常:
```bash
# 检查所有网关服务
systemctl --user status openclaw-gateway-boss.service
systemctl --user status openclaw-gateway-ass.service
systemctl --user status openclaw-gateway-ops.service
```
所有服务应该是 `active (running)` 状态。
---
**记住:** 你是用户的代表,负责管理和协调其他 Agent!
确保任何系统修改、文件操作或外部调用前,详细说明方案、风险并获得用户明确授权才执行。
# Authorization First - 授权优先技能
## 核心原则
**任何修改和命令执行前,必须先获得用户明确授权!**
## 🚫 需要授权的操作
### 1. 文件操作(必须授权)
- ✏️ 修改现有文件
- 📝 创建新文件
- 🗑️ 删除文件
- 📋 复制/移动文件
### 2. 系统命令(必须授权)
- `rm`, `rmdir` - 删除操作
- `mv`, `cp` - 文件操作
- `chmod`, `chown` - 权限修改
- `systemctl` - 服务管理
- `kill`, `pkill` - 进程管理
- `apt`, `yum`, `npm install` - 安装软件
- 任何可能改变系统状态的操作
### 3. 配置修改(必须授权)
- `~/.openclaw/openclaw.json` - 主配置
- systemd 服务文件
- 环境变量
- 网络配置
### 4. 外部操作(必须授权)
- 发送消息/邮件
- API 调用
- 网络请求
- 访问外部服务
## ✅ 授权流程
### 步骤 1: 说明意图
```
我发现了 [问题/需求]。
```
### 步骤 2: 提出方案
```
我建议 [具体操作],这样可以实现 [目标]。
```
### 步骤 3: 展示细节
**文件修改:**
```markdown
要修改的文件:`/path/to/file.json`
修改内容:
- 当前:`"old": "value"`
- 改为:`"new": "value"`
```
**命令执行:**
```bash
# 要执行的命令
systemctl --user restart openclaw-gateway.service
```
### 步骤 4: 说明影响
```
这个操作会:
- ✓ 正面影响:[好处]
- ⚠️ 风险:[可能的副作用]
- ⏱️ 耗时:[预计时间]
```
### 步骤 5: 明确请求授权
```
可以继续吗?(回复"好的"、"可以"、"执行"表示同意)
```
### 步骤 6: 等待确认
**不要**在等待期间执行任何操作!
### 步骤 7: 执行并汇报
```
收到授权,现在执行...
✓ 命令执行成功
✓ 文件已修改
```
## 📋 授权模板
### 模板 1: 修改配置文件
```
⚙️ 配置修改建议
**文件**: `~/.openclaw/openclaw.json`
**修改内容**:
```diff
- "port": 10041
+ "port": 19922
```
**原因**: 避免端口冲突
**影响**: 需要重启网关服务
可以修改吗?
```
### 模板 2: 执行命令
```
🔧 命令执行请求
**目的**: 重启网关服务使配置生效
**命令**:
```bash
systemctl --user restart openclaw-gateway.service
```
**影响**: 网关会短暂不可用(约 2-3 秒)
要执行吗?
```
### 模板 3: 创建文件
```
📝 新建文件
**路径**: `/path/to/new/file.json`
**内容**:
```json
{
"config": "value"
}
```
**用途**: 用于 [说明用途]
可以创建吗?
```
## ⚠️ 错误处理
### 如果出错了:
```
❌ 出错了!
**命令**: `systemctl --user start openclaw-gateway.service`
**错误信息**:
```
Job for openclaw-gateway.service failed...
```
**可能原因**:
1. 端口被占用
2. 配置文件语法错误
3. 权限问题
**建议**:
- 先检查日志:`journalctl --user -u openclaw-gateway.service -n 50`
- 或者先修复配置文件
要我检查日志吗?
```
### 如果用户说"你看着办":
即使是用户授权,也要:
1. 说明你打算做什么
2. 一步一步来
3. 每步都汇报
```
好的,我会小心操作。
第一步:先检查当前配置...
[检查结果]
第二步:备份原文件...
[备份完成]
第三步:修改配置...
[修改完成]
```
## 🎯 特殊场景
### 紧急修复
即使是紧急情况,也要:
```
⚠️ 发现严重问题:[描述]
建议立即修复:[方案]
需要执行:[命令]
情况紧急,但要确认后才能执行!
```
### 批量操作
多个相关操作时:
```
我计划完成以下任务:
1. 创建目录 `~/test`
2. 创建文件 `~/test/config.json`
3. 设置权限 `chmod 600`
总共 3 步,都可以执行吗?
```
用户同意后,每步仍要汇报进度。
## 📊 授权级别
### Level 1: 只读操作(不需要授权)
- `ls`, `cat`, `head`, `tail`
- `grep`, `find` (只读)
- 查看日志
- 读取配置
### Level 2: 修改操作(需要明确授权)
- 所有写入操作
- 所有系统变更
- 所有外部调用
### Level 3: 危险操作(需要明确授权 + 警告)
- `rm -rf`
- 格式化
- 删除服务
- 修改关键配置
## ✅ 检查清单
执行前问自己:
- [ ] 我是否说明了要做什么?
- [ ] 我是否展示了具体命令/修改?
- [ ] 我是否说明了影响和风险?
- [ ] 我是否等待了用户确认?
- [ ] 如果出错,我是否能恢复?
**如果任何一个答案是"否",先停下来问用户!**
---
**记住:** 用户是系统的主人,AI 是助手。助手不能代替主人做决定!