feat: 创建 agent-creator-with-binding skill
整合 feishu-agent-binding 功能,一站式创建 Agent 并配置飞书绑定 主要功能: - 创建 Agent 工作空间 - 配置飞书机器人账户 - 配置路由绑定(账户级/群聊级) - 配置会话隔离 - 启用 Agent 间调用 - 自动备份和回退机制 支持模式: - 账户级绑定(新机器人/现有机器人) - 群聊级绑定 - 不绑定(后台 Agent) 安全特性: - 修改前自动备份 openclaw.json - 任何失败自动回退 - 详细的错误提示和恢复指南
This commit is contained in:
242
README.md
Normal file
242
README.md
Normal file
@@ -0,0 +1,242 @@
|
||||
# agent-creator-with-binding
|
||||
|
||||
一站式创建 OpenClaw Agent 并配置飞书机器人绑定。
|
||||
|
||||
**整合了 feishu-agent-binding 功能,无需额外 skill!**
|
||||
|
||||
## 功能特性
|
||||
|
||||
- ✅ 交互式创建 Agent
|
||||
- ✅ 支持三种绑定模式(账户级/群聊级/不绑定)
|
||||
- ✅ 飞书机器人配置(整合 feishu-agent-binding)
|
||||
- ✅ 自动配置路由绑定
|
||||
- ✅ 自动更新 openclaw.json
|
||||
- ✅ 自动备份配置
|
||||
- ✅ 自动重启 Gateway
|
||||
- ✅ 会话隔离配置
|
||||
- ✅ Agent 间调用配置
|
||||
|
||||
## 安装
|
||||
|
||||
Skill 已位于:
|
||||
```
|
||||
/home/admin/.openclaw/workspace/skills/agent-creator-with-binding/
|
||||
```
|
||||
|
||||
**无需安装 feishu-agent-binding!**
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 交互模式
|
||||
|
||||
```bash
|
||||
openclaw skills run agent-creator-with-binding
|
||||
```
|
||||
|
||||
按提示输入:
|
||||
1. Agent ID 和名称
|
||||
2. 绑定模式(A/B/C)
|
||||
3. 飞书配置(如需要)
|
||||
4. 确认执行
|
||||
|
||||
### 命令行模式
|
||||
|
||||
#### 账户级绑定(新机器人)
|
||||
|
||||
```bash
|
||||
openclaw skills run agent-creator-with-binding -- \
|
||||
--agent-id email-assistant \
|
||||
--agent-name "邮件助手" \
|
||||
--binding-mode account \
|
||||
--new-bot \
|
||||
--app-id cli_xxx \
|
||||
--app-secret yyy
|
||||
```
|
||||
|
||||
#### 账户级绑定(现有机器人)
|
||||
|
||||
```bash
|
||||
openclaw skills run agent-creator-with-binding -- \
|
||||
--agent-id email-assistant \
|
||||
--binding-mode account \
|
||||
--account-id bot-existing
|
||||
```
|
||||
|
||||
#### 群聊级绑定
|
||||
|
||||
```bash
|
||||
openclaw skills run agent-creator-with-binding -- \
|
||||
--agent-id project-assistant \
|
||||
--binding-mode group \
|
||||
--account-id bot-main \
|
||||
--chat-id oc_xxx
|
||||
```
|
||||
|
||||
#### 不绑定(后台 Agent)
|
||||
|
||||
```bash
|
||||
openclaw skills run agent-creator-with-binding -- \
|
||||
--agent-id data-processor \
|
||||
--binding-mode none
|
||||
```
|
||||
|
||||
## 参数说明
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| --agent-id | ✅ | Agent 唯一标识 |
|
||||
| --agent-name | ❌ | 显示名称 |
|
||||
| --binding-mode | ❌ | account/group/none |
|
||||
| --new-bot | ❌ | 是否新机器人 |
|
||||
| --app-id | ❌ | 飞书 App ID |
|
||||
| --app-secret | ❌ | 飞书 App Secret |
|
||||
| --account-id | ❌ | 飞书账户 ID |
|
||||
| --chat-id | ❌ | 群聊 ID |
|
||||
| --bot-name | ❌ | 机器人名称 |
|
||||
| --dm-policy | ❌ | DM 策略:open/pairing/allowlist |
|
||||
| --skip-confirm | ❌ | 跳过确认 |
|
||||
| --skip-restart | ❌ | 跳过重启 |
|
||||
|
||||
## 配置变更
|
||||
|
||||
执行后会修改:
|
||||
- `~/.openclaw/openclaw.json`
|
||||
- `~/.openclaw/workspace-<agent-id>/`
|
||||
- `~/.openclaw/agents/<agent-id>/`
|
||||
|
||||
## 恢复方法
|
||||
|
||||
```bash
|
||||
# 找到最近的备份
|
||||
ls -lt ~/.openclaw/backups/openclaw.json.* | head -1
|
||||
|
||||
# 恢复
|
||||
cp ~/.openclaw/backups/openclaw.json.<timestamp> ~/.openclaw/openclaw.json
|
||||
openclaw gateway restart
|
||||
```
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
# 查看 agent 列表
|
||||
openclaw agents list --bindings
|
||||
|
||||
# 查看 Gateway 状态
|
||||
openclaw gateway status
|
||||
|
||||
# 查看飞书账户
|
||||
openclaw channels status --probe
|
||||
```
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
agent-creator-with-binding/
|
||||
├── SKILL.md
|
||||
├── package.json
|
||||
├── index.js # 主入口(整合 feishu-agent-binding 功能)
|
||||
├── README.md
|
||||
├── lib/
|
||||
│ ├── config-manager.js # 配置管理
|
||||
│ └── validator.js # 参数验证
|
||||
├── templates/
|
||||
│ ├── soul.md.template
|
||||
│ └── agents.md.template
|
||||
└── scripts/
|
||||
```
|
||||
|
||||
## 绑定模式说明
|
||||
|
||||
### 账户级绑定
|
||||
|
||||
该飞书账户的所有消息 → 指定 Agent
|
||||
|
||||
适用:一个机器人专门服务一个 Agent
|
||||
|
||||
生成的绑定:
|
||||
```json
|
||||
{
|
||||
"agentId": "email-assistant",
|
||||
"match": {
|
||||
"channel": "feishu",
|
||||
"accountId": "bot-email"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 群聊级绑定
|
||||
|
||||
特定群聊的消息 → 指定 Agent
|
||||
|
||||
适用:把 Agent 绑定到特定群聊
|
||||
|
||||
**注意:** 群聊级绑定优先级更高,会覆盖账户级绑定!
|
||||
|
||||
生成的绑定:
|
||||
```json
|
||||
{
|
||||
"agentId": "project-assistant",
|
||||
"match": {
|
||||
"channel": "feishu",
|
||||
"peer": {
|
||||
"kind": "group",
|
||||
"id": "oc_xxx"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 配置结构示例
|
||||
|
||||
添加新机器人后,配置会变成这样(保留现有配置):
|
||||
|
||||
```json
|
||||
{
|
||||
"agents": {
|
||||
"list": [
|
||||
{ "id": "main" },
|
||||
{
|
||||
"id": "email-assistant",
|
||||
"name": "邮件助手",
|
||||
"workspace": "/home/admin/.openclaw/workspace-email-assistant"
|
||||
}
|
||||
]
|
||||
},
|
||||
"bindings": [
|
||||
{
|
||||
"agentId": "email-assistant",
|
||||
"match": {
|
||||
"channel": "feishu",
|
||||
"accountId": "bot-email"
|
||||
}
|
||||
}
|
||||
],
|
||||
"channels": {
|
||||
"feishu": {
|
||||
"accounts": {
|
||||
"bot-email": {
|
||||
"appId": "cli_xxx",
|
||||
"appSecret": "yyy",
|
||||
"botName": "邮件助手",
|
||||
"dmPolicy": "open",
|
||||
"allowFrom": ["*"],
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"session": {
|
||||
"dmScope": "per-account-channel-peer"
|
||||
},
|
||||
"tools": {
|
||||
"agentToAgent": {
|
||||
"enabled": true,
|
||||
"allow": ["email-assistant"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT
|
||||
215
SKILL.md
Normal file
215
SKILL.md
Normal file
@@ -0,0 +1,215 @@
|
||||
---
|
||||
name: agent-creator-with-binding
|
||||
description: 创建新 Agent 并配置飞书机器人绑定(整合版,无需额外 skill)
|
||||
invocations:
|
||||
- words:
|
||||
- 创建 agent
|
||||
- 创建子 agent
|
||||
- 新 agent 绑定
|
||||
- agent 绑定飞书
|
||||
- 添加飞书机器人
|
||||
- 配置飞书机器人
|
||||
description: 创建新 Agent 并配置飞书机器人绑定(整合 feishu-agent-binding 功能)
|
||||
---
|
||||
|
||||
# agent-creator-with-binding
|
||||
|
||||
一站式创建 OpenClaw Agent 并配置飞书机器人绑定。
|
||||
|
||||
**整合功能:**
|
||||
- ✅ Agent 创建
|
||||
- ✅ 飞书机器人配置(原 feishu-agent-binding 功能)
|
||||
- ✅ 路由绑定配置
|
||||
- ✅ 会话隔离配置
|
||||
- ✅ Agent 间调用配置
|
||||
|
||||
**无需额外 skill!**
|
||||
|
||||
## 完整工作流程
|
||||
|
||||
### 模式 1:创建新 Agent + 账户级绑定(新机器人)
|
||||
|
||||
```
|
||||
1. 输入 Agent 基础信息(ID、名称、描述)
|
||||
↓
|
||||
2. 选择绑定模式:account
|
||||
↓
|
||||
3. 选择:新飞书机器人
|
||||
↓
|
||||
4. 输入飞书凭证(App ID、App Secret)
|
||||
↓
|
||||
5. 预览配置
|
||||
↓
|
||||
6. 确认执行
|
||||
↓
|
||||
7. 创建 Agent + 配置飞书账户 + 更新 bindings + 重启 Gateway
|
||||
```
|
||||
|
||||
### 模式 2:创建新 Agent + 账户级绑定(现有机器人)
|
||||
|
||||
```
|
||||
1. 输入 Agent 基础信息
|
||||
↓
|
||||
2. 选择绑定模式:account
|
||||
↓
|
||||
3. 选择:现有飞书机器人
|
||||
↓
|
||||
4. 从列表选择账户
|
||||
↓
|
||||
5. 预览配置
|
||||
↓
|
||||
6. 确认执行
|
||||
↓
|
||||
7. 创建 Agent + 更新 bindings + 重启 Gateway
|
||||
```
|
||||
|
||||
### 模式 3:创建新 Agent + 群聊级绑定
|
||||
|
||||
```
|
||||
1. 输入 Agent 基础信息
|
||||
↓
|
||||
2. 选择绑定模式:group
|
||||
↓
|
||||
3. 选择飞书账户
|
||||
↓
|
||||
4. 输入群聊 ID
|
||||
↓
|
||||
5. 预览配置
|
||||
↓
|
||||
6. 确认执行
|
||||
↓
|
||||
7. 创建 Agent + 更新 bindings + 重启 Gateway
|
||||
```
|
||||
|
||||
### 模式 4:创建新 Agent(不绑定,后台 Agent)
|
||||
|
||||
```
|
||||
1. 输入 Agent 基础信息
|
||||
↓
|
||||
2. 选择绑定模式:none
|
||||
↓
|
||||
3. 预览配置
|
||||
↓
|
||||
4. 确认执行
|
||||
↓
|
||||
5. 创建 Agent + 重启 Gateway
|
||||
```
|
||||
|
||||
## 使用方式
|
||||
|
||||
### 交互模式
|
||||
|
||||
```bash
|
||||
openclaw skills run agent-creator-with-binding
|
||||
```
|
||||
|
||||
### 命令行模式
|
||||
|
||||
```bash
|
||||
# 模式 1:账户级绑定(新机器人)
|
||||
openclaw skills run agent-creator-with-binding -- \
|
||||
--agent-id email-assistant \
|
||||
--agent-name "邮件助手" \
|
||||
--binding-mode account \
|
||||
--new-bot \
|
||||
--app-id cli_xxx \
|
||||
--app-secret yyy
|
||||
|
||||
# 模式 2:账户级绑定(现有机器人)
|
||||
openclaw skills run agent-creator-with-binding -- \
|
||||
--agent-id email-assistant \
|
||||
--binding-mode account \
|
||||
--account-id bot-existing
|
||||
|
||||
# 模式 3:群聊级绑定
|
||||
openclaw skills run agent-creator-with-binding -- \
|
||||
--agent-id project-assistant \
|
||||
--agent-name "项目助手" \
|
||||
--binding-mode group \
|
||||
--account-id bot-main \
|
||||
--chat-id oc_xxx
|
||||
|
||||
# 模式 4:不绑定(后台 Agent)
|
||||
openclaw skills run agent-creator-with-binding -- \
|
||||
--agent-id data-processor \
|
||||
--agent-name "数据处理器" \
|
||||
--binding-mode none
|
||||
```
|
||||
|
||||
## 参数说明
|
||||
|
||||
| 参数 | 必填 | 说明 |
|
||||
|------|------|------|
|
||||
| --agent-id | ✅ | Agent 唯一标识 |
|
||||
| --agent-name | ❌ | 显示名称(默认同 agent-id) |
|
||||
| --binding-mode | ❌ | 绑定模式:account/group/none |
|
||||
| --new-bot | ❌ | 是否创建新飞书机器人 |
|
||||
| --app-id | ❌ | 飞书 App ID(新机器人时需要) |
|
||||
| --app-secret | ❌ | 飞书 App Secret(新机器人时需要) |
|
||||
| --account-id | ❌ | 飞书账户 ID(现有机器人时需要) |
|
||||
| --chat-id | ❌ | 群聊 ID(群聊绑定时需要) |
|
||||
| --bot-name | ❌ | 机器人名称 |
|
||||
| --dm-policy | ❌ | DM 策略:open/pairing/allowlist |
|
||||
| --skip-confirm | ❌ | 跳过确认 |
|
||||
| --skip-restart | ❌ | 跳过重启 |
|
||||
|
||||
## 配置变更
|
||||
|
||||
执行后会修改:
|
||||
- `~/.openclaw/openclaw.json`
|
||||
- 添加 `agents.list` 条目
|
||||
- 添加 `bindings` 规则
|
||||
- 添加/更新 `channels.feishu.accounts`
|
||||
- 配置 `session.dmScope`
|
||||
- 启用 `tools.agentToAgent`
|
||||
- `~/.openclaw/workspace-<agent-id>/` - 创建 agent 工作空间
|
||||
- `~/.openclaw/agents/<agent-id>/` - 创建 agent 状态目录
|
||||
|
||||
## 备份和回退
|
||||
|
||||
### 自动备份
|
||||
|
||||
**执行任何修改前自动备份!**
|
||||
|
||||
- 备份位置:`~/.openclaw/backups/openclaw.json.<timestamp>`
|
||||
- 备份时机:在创建 Agent 之前
|
||||
- 备份内容:完整的 openclaw.json
|
||||
|
||||
### 自动回退
|
||||
|
||||
**任何步骤失败自动回退!**
|
||||
|
||||
| 失败场景 | 回退操作 |
|
||||
|---------|---------|
|
||||
| 创建 Agent 失败 | 恢复 openclaw.json |
|
||||
| 更新配置失败 | 删除 Agent 目录 + 恢复 openclaw.json |
|
||||
| 配置飞书账户失败 | 删除 Agent 目录 + 恢复 openclaw.json |
|
||||
| Gateway 重启失败 | 不回退(配置正确,需手动重启) |
|
||||
|
||||
### 手动回退
|
||||
|
||||
如配置有误,可从备份恢复:
|
||||
|
||||
```bash
|
||||
# 找到最近的备份
|
||||
ls -lt ~/.openclaw/backups/openclaw.json.* | head -1
|
||||
|
||||
# 恢复配置
|
||||
cp ~/.openclaw/backups/openclaw.json.<timestamp> ~/.openclaw/openclaw.json
|
||||
|
||||
# 重启 Gateway
|
||||
openclaw gateway restart
|
||||
```
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
# 查看 agent 列表和绑定
|
||||
openclaw agents list --bindings
|
||||
|
||||
# 查看 Gateway 状态
|
||||
openclaw gateway status
|
||||
|
||||
# 查看飞书账户状态
|
||||
openclaw channels status --probe
|
||||
```
|
||||
685
index.js
Normal file
685
index.js
Normal file
@@ -0,0 +1,685 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* agent-creator-with-binding
|
||||
* 创建新 Agent 并配置飞书机器人绑定(整合版)
|
||||
*
|
||||
* 支持的路由绑定方案:
|
||||
* 1. 账户级绑定 - 该飞书账户的所有消息路由到指定 Agent
|
||||
* 2. 群聊级绑定 - 特定群聊的消息路由到指定 Agent
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const readline = require('readline');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
// 配置路径
|
||||
const CONFIG_PATH = path.join(process.env.HOME, '.openclaw', 'openclaw.json');
|
||||
const BACKUP_DIR = path.join(process.env.HOME, '.openclaw', 'backups');
|
||||
|
||||
// 颜色输出
|
||||
const colors = {
|
||||
reset: '\x1b[0m',
|
||||
green: '\x1b[32m',
|
||||
yellow: '\x1b[33m',
|
||||
red: '\x1b[31m',
|
||||
cyan: '\x1b[36m',
|
||||
gray: '\x1b[90m',
|
||||
bold: '\x1b[1m'
|
||||
};
|
||||
|
||||
const log = {
|
||||
info: (msg) => console.log(`${colors.cyan}ℹ${colors.reset} ${msg}`),
|
||||
success: (msg) => console.log(`${colors.green}✓${colors.reset} ${msg}`),
|
||||
warning: (msg) => console.log(`${colors.yellow}⚠${colors.reset} ${msg}`),
|
||||
error: (msg) => console.log(`${colors.red}✗${colors.reset} ${msg}`),
|
||||
step: (num, total, msg) => console.log(`\n${colors.cyan}[${num}/${total}]${colors.reset} ${msg}`),
|
||||
preview: (msg) => console.log(`${colors.gray}${msg}${colors.reset}`),
|
||||
bold: (msg) => console.log(`${colors.bold}${msg}${colors.reset}`)
|
||||
};
|
||||
|
||||
// 创建 readline 接口
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
});
|
||||
|
||||
// promisified question
|
||||
function question(query) {
|
||||
return new Promise(resolve => rl.question(query, resolve));
|
||||
}
|
||||
|
||||
// 读取配置
|
||||
function loadConfig() {
|
||||
try {
|
||||
const content = fs.readFileSync(CONFIG_PATH, 'utf8');
|
||||
return JSON.parse(content);
|
||||
} catch (err) {
|
||||
log.error(`读取配置失败:${err.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
function saveConfig(config) {
|
||||
try {
|
||||
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf8');
|
||||
return true;
|
||||
} catch (err) {
|
||||
log.error(`保存配置失败:${err.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建备份
|
||||
function createBackup() {
|
||||
try {
|
||||
if (!fs.existsSync(BACKUP_DIR)) {
|
||||
fs.mkdirSync(BACKUP_DIR, { recursive: true });
|
||||
}
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const backupPath = path.join(BACKUP_DIR, `openclaw.json.${timestamp}`);
|
||||
fs.copyFileSync(CONFIG_PATH, backupPath);
|
||||
log.success(`配置已备份:${path.basename(backupPath)}`);
|
||||
return backupPath;
|
||||
} catch (err) {
|
||||
log.error(`创建备份失败:${err.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 回退配置
|
||||
function rollback(backupPath) {
|
||||
if (!backupPath || !fs.existsSync(backupPath)) {
|
||||
log.error('备份文件不存在,无法回退');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
log.warning('正在回退配置...');
|
||||
fs.copyFileSync(backupPath, CONFIG_PATH);
|
||||
log.success('配置已回退');
|
||||
return true;
|
||||
} catch (err) {
|
||||
log.error(`回退失败:${err.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 清理已创建的 Agent(回退用)
|
||||
function cleanupAgent(agentId) {
|
||||
try {
|
||||
const workspacePath = `/home/admin/.openclaw/workspace-${agentId}`;
|
||||
const agentPath = `/home/admin/.openclaw/agents/${agentId}`;
|
||||
|
||||
if (fs.existsSync(workspacePath)) {
|
||||
fs.rmSync(workspacePath, { recursive: true, force: true });
|
||||
log.warning(`已删除工作空间:${workspacePath}`);
|
||||
}
|
||||
if (fs.existsSync(agentPath)) {
|
||||
fs.rmSync(agentPath, { recursive: true, force: true });
|
||||
log.warning(`已删除 Agent 目录:${agentPath}`);
|
||||
}
|
||||
return true;
|
||||
} catch (err) {
|
||||
log.error(`清理 Agent 失败:${err.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 验证函数
|
||||
function validateAgentId(id) {
|
||||
return /^[a-z0-9-]+$/.test(id);
|
||||
}
|
||||
|
||||
function validateAppId(appId) {
|
||||
return /^cli_[a-zA-Z0-9]+$/.test(appId);
|
||||
}
|
||||
|
||||
function validateAccountId(id) {
|
||||
return /^[a-z0-9-]+$/.test(id);
|
||||
}
|
||||
|
||||
function validateChatId(id) {
|
||||
return /^oc_[a-zA-Z0-9]+$/.test(id);
|
||||
}
|
||||
|
||||
// 创建 Agent
|
||||
function createAgent(agentId) {
|
||||
log.step(1, 6, `创建 Agent: ${agentId}`);
|
||||
try {
|
||||
execSync(`openclaw agents add ${agentId}`, { stdio: 'inherit' });
|
||||
log.success(`Agent ${agentId} 创建成功`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
log.error(`创建 Agent 失败:${err.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 配置飞书账户(整合 feishu-agent-binding 功能)
|
||||
function configureFeishuAccount(options) {
|
||||
if (!options.newBot) return true;
|
||||
|
||||
log.step(4, 6, '配置飞书机器人账户');
|
||||
|
||||
const config = loadConfig();
|
||||
|
||||
// 确保 channels.feishu.accounts 存在
|
||||
if (!config.channels) config.channels = {};
|
||||
if (!config.channels.feishu) config.channels.feishu = { enabled: true };
|
||||
if (!config.channels.feishu.accounts) config.channels.feishu.accounts = {};
|
||||
|
||||
// 添加账户
|
||||
config.channels.feishu.accounts[options.accountId] = {
|
||||
appId: options.appId,
|
||||
appSecret: options.appSecret,
|
||||
botName: options.botName || `${options.agentName}机器人`,
|
||||
dmPolicy: options.dmPolicy || 'open',
|
||||
allowFrom: ['*'],
|
||||
enabled: true
|
||||
};
|
||||
|
||||
if (saveConfig(config)) {
|
||||
log.success(`已添加飞书账户:${options.accountId}`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 更新配置(添加 agent + bindings)
|
||||
function updateConfig(options) {
|
||||
log.step(3, 6, '更新配置');
|
||||
|
||||
const config = loadConfig();
|
||||
|
||||
// 1. 添加 agent 到 agents.list
|
||||
if (!config.agents) config.agents = {};
|
||||
if (!config.agents.list) config.agents.list = [];
|
||||
|
||||
const agentExists = config.agents.list.find(a => a.id === options.agentId);
|
||||
if (!agentExists) {
|
||||
config.agents.list.push({
|
||||
id: options.agentId,
|
||||
name: options.agentName || options.agentId,
|
||||
workspace: `/home/admin/.openclaw/workspace-${options.agentId}`
|
||||
});
|
||||
log.success(`已添加 agent 到 agents.list`);
|
||||
} else {
|
||||
log.warning(`Agent ${options.agentId} 已存在`);
|
||||
}
|
||||
|
||||
// 2. 添加 bindings 规则
|
||||
if (!config.bindings) config.bindings = [];
|
||||
if (options.bindingMode !== 'none') {
|
||||
const binding = {
|
||||
agentId: options.agentId,
|
||||
match: {
|
||||
channel: 'feishu'
|
||||
}
|
||||
};
|
||||
|
||||
if (options.bindingMode === 'account') {
|
||||
binding.match.accountId = options.accountId;
|
||||
log.success(`已添加账户级绑定:${options.agentId} ← ${options.accountId}`);
|
||||
} else if (options.bindingMode === 'group') {
|
||||
binding.match.peer = {
|
||||
kind: 'group',
|
||||
id: options.chatId
|
||||
};
|
||||
log.success(`已添加群聊级绑定:${options.agentId} ← ${options.chatId}`);
|
||||
}
|
||||
|
||||
config.bindings.push(binding);
|
||||
}
|
||||
|
||||
// 3. 配置会话隔离
|
||||
if (!config.session) config.session = {};
|
||||
config.session.dmScope = 'per-account-channel-peer';
|
||||
log.success(`已配置会话隔离:per-account-channel-peer`);
|
||||
|
||||
// 4. 启用 agent 间调用
|
||||
if (!config.tools) config.tools = {};
|
||||
if (!config.tools.agentToAgent) config.tools.agentToAgent = {};
|
||||
config.tools.agentToAgent.enabled = true;
|
||||
if (!config.tools.agentToAgent.allow) {
|
||||
config.tools.agentToAgent.allow = [];
|
||||
}
|
||||
if (!config.tools.agentToAgent.allow.includes(options.agentId)) {
|
||||
config.tools.agentToAgent.allow.push(options.agentId);
|
||||
}
|
||||
log.success(`已启用 agent 间调用`);
|
||||
|
||||
if (saveConfig(config)) {
|
||||
log.success('配置已更新');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 显示预览
|
||||
function showPreview(options) {
|
||||
log.step(5, 6, '预览配置');
|
||||
|
||||
console.log(`
|
||||
${colors.bold}即将执行以下配置变更:${colors.reset}
|
||||
|
||||
${colors.bold}=== Agent 配置 ===${colors.reset}
|
||||
新增 agent:
|
||||
id: ${options.agentId}
|
||||
name: ${options.agentName || options.agentId}
|
||||
workspace: /home/admin/.openclaw/workspace-${options.agentId}
|
||||
|
||||
${colors.bold}=== 绑定配置 ===${colors.reset}
|
||||
绑定模式:${options.bindingMode}
|
||||
${options.bindingMode === 'account' ? `飞书账户:${options.accountId}` : ''}
|
||||
${options.bindingMode === 'group' ? `群聊 ID: ${options.chatId}` : ''}
|
||||
${options.newBot ? `新机器人:${options.botName || options.agentName}机器人` : ''}
|
||||
${options.newBot ? `App ID: ${options.appId}` : ''}
|
||||
|
||||
${colors.bold}=== 系统配置 ===${colors.reset}
|
||||
- 会话隔离:per-account-channel-peer
|
||||
- agent 间调用:已启用
|
||||
- 工作空间目录:将自动创建
|
||||
|
||||
${colors.yellow}即将执行的操作:${colors.reset}
|
||||
1. 创建 Agent 工作空间
|
||||
2. 备份当前配置
|
||||
3. 更新 openclaw.json(添加 agent + bindings)
|
||||
${options.newBot ? '4. 配置飞书机器人账户(channels.feishu.accounts)' : ''}
|
||||
${options.newBot ? '5. ' : '4. '}重启 Gateway
|
||||
`);
|
||||
}
|
||||
|
||||
// 重启 Gateway
|
||||
function restartGateway() {
|
||||
log.step(6, 6, '重启 Gateway');
|
||||
try {
|
||||
execSync('openclaw gateway restart', { stdio: 'inherit' });
|
||||
log.success('Gateway 重启完成');
|
||||
return true;
|
||||
} catch (err) {
|
||||
log.error(`重启失败:${err.message}`);
|
||||
log.info('请手动执行:openclaw gateway restart');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示路由方案说明
|
||||
function showRoutingOptions() {
|
||||
console.log(`
|
||||
${colors.bold}📋 路由绑定方案${colors.reset}
|
||||
|
||||
${colors.bold}方案 1:账户级绑定${colors.reset}
|
||||
该飞书账户的所有消息 → 指定 Agent
|
||||
适用:一个机器人专门服务一个 Agent
|
||||
|
||||
${colors.bold}方案 2:群聊级绑定${colors.reset}
|
||||
特定群聊的消息 → 指定 Agent
|
||||
适用:把 Agent 绑定到特定群聊
|
||||
|
||||
${colors.yellow}提示:群聊级绑定优先级更高,会覆盖账户级绑定!${colors.reset}
|
||||
`);
|
||||
}
|
||||
|
||||
// 交互式模式
|
||||
async function interactiveMode() {
|
||||
console.log(`\n${colors.cyan}🤖 Agent 创建 + 飞书绑定助手${colors.reset}\n`);
|
||||
|
||||
// 步骤 1:Agent 基础信息
|
||||
log.step(1, 6, 'Agent 基础信息');
|
||||
const agentId = await question('Agent ID (如 email-assistant): ');
|
||||
|
||||
if (!validateAgentId(agentId)) {
|
||||
log.error('Agent ID 格式无效,只能使用小写字母、数字和连字符');
|
||||
rl.close();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const agentName = await question(`Agent 名称(默认:${agentId}): `) || agentId;
|
||||
const description = await question('Agent 描述(可选): ');
|
||||
|
||||
// 步骤 2:选择绑定模式
|
||||
log.step(2, 6, '选择绑定模式');
|
||||
showRoutingOptions();
|
||||
const modeChoice = await question('选择(A/B/C): ');
|
||||
const bindingMode = modeChoice.toLowerCase() === 'a' ? 'account'
|
||||
: modeChoice.toLowerCase() === 'b' ? 'group' : 'none';
|
||||
|
||||
// 步骤 3:根据绑定模式收集信息
|
||||
let options = { agentId, agentName, description, bindingMode };
|
||||
|
||||
if (bindingMode === 'account') {
|
||||
log.step(3, 6, '配置飞书机器人');
|
||||
const newBotChoice = await question('是否需要新飞书机器人?(y/n): ');
|
||||
options.newBot = newBotChoice.toLowerCase() === 'y';
|
||||
|
||||
if (options.newBot) {
|
||||
const appId = await question('App ID (cli_xxx): ');
|
||||
if (!validateAppId(appId)) {
|
||||
log.error('App ID 格式无效');
|
||||
rl.close();
|
||||
process.exit(1);
|
||||
}
|
||||
options.appId = appId;
|
||||
|
||||
const appSecret = await question('App Secret: ');
|
||||
options.appSecret = appSecret;
|
||||
|
||||
options.accountId = await question(`账户 ID(默认:bot-${agentId}): `) || `bot-${agentId}`;
|
||||
options.botName = await question(`机器人名称(默认:${agentName}机器人): `) || `${agentName}机器人`;
|
||||
options.dmPolicy = await question(`DM 策略(open/pairing/allowlist,默认 open): `) || 'open';
|
||||
} else {
|
||||
// 列出飞书账户供选择
|
||||
log.info('可用飞书账户:');
|
||||
try {
|
||||
const config = loadConfig();
|
||||
const accounts = config.channels?.feishu?.accounts || {};
|
||||
const accountIds = Object.keys(accounts);
|
||||
|
||||
if (accountIds.length === 0) {
|
||||
log.warning('没有找到现有飞书账户,将创建新账户');
|
||||
options.newBot = true;
|
||||
const appId = await question('App ID (cli_xxx): ');
|
||||
options.appId = appId;
|
||||
const appSecret = await question('App Secret: ');
|
||||
options.appSecret = appSecret;
|
||||
options.accountId = await question(`账户 ID(默认:bot-${agentId}): `) || `bot-${agentId}`;
|
||||
options.botName = await question(`机器人名称(默认:${agentName}机器人): `) || `${agentName}机器人`;
|
||||
options.dmPolicy = 'open';
|
||||
} else {
|
||||
accountIds.forEach((id, index) => {
|
||||
console.log(` ${index + 1}. ${id} (${accounts[id].botName || '未命名'})`);
|
||||
});
|
||||
const choice = await question(`选择账户(1-${accountIds.length} 或输入账户 ID): `);
|
||||
const choiceNum = parseInt(choice);
|
||||
if (!isNaN(choiceNum) && choiceNum >= 1 && choiceNum <= accountIds.length) {
|
||||
options.accountId = accountIds[choiceNum - 1];
|
||||
} else {
|
||||
options.accountId = choice;
|
||||
}
|
||||
options.newBot = false;
|
||||
}
|
||||
} catch (err) {
|
||||
log.error(`读取账户列表失败:${err.message}`);
|
||||
options.accountId = await question('现有账户 ID: ');
|
||||
options.newBot = false;
|
||||
}
|
||||
}
|
||||
} else if (bindingMode === 'group') {
|
||||
log.step(3, 6, '配置群聊绑定');
|
||||
options.accountId = await question('使用哪个飞书账户?: ');
|
||||
const chatId = await question('群聊 ID (oc_xxx): ');
|
||||
if (!validateChatId(chatId)) {
|
||||
log.error('群聊 ID 格式无效');
|
||||
rl.close();
|
||||
process.exit(1);
|
||||
}
|
||||
options.chatId = chatId;
|
||||
options.newBot = false;
|
||||
} else {
|
||||
options.newBot = false;
|
||||
}
|
||||
|
||||
// 步骤 4:会话隔离配置
|
||||
log.step(4, 6, '会话隔离配置');
|
||||
console.log('推荐配置:per-account-channel-peer(每个用户/群聊在每个机器人有独立会话)');
|
||||
await question('按 Enter 继续: ');
|
||||
|
||||
// 步骤 5:预览配置
|
||||
showPreview(options);
|
||||
|
||||
// 步骤 6:用户确认
|
||||
const confirm = await question('确认执行?(y/n): ');
|
||||
if (confirm.toLowerCase() !== 'y') {
|
||||
log.info('已取消');
|
||||
rl.close();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
rl.close();
|
||||
|
||||
// 执行配置
|
||||
console.log('');
|
||||
|
||||
// 步骤 1:先备份(在任何修改之前)
|
||||
const backupPath = createBackup();
|
||||
if (!backupPath) {
|
||||
log.error('备份失败,无法继续');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 步骤 2:创建 Agent
|
||||
const agentCreated = createAgent(agentId);
|
||||
if (!agentCreated) {
|
||||
log.error('创建 Agent 失败,正在回退...');
|
||||
rollback(backupPath);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 步骤 3:更新配置
|
||||
const configUpdated = updateConfig(options);
|
||||
if (!configUpdated) {
|
||||
log.error('更新配置失败,正在回退...');
|
||||
cleanupAgent(agentId);
|
||||
rollback(backupPath);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 步骤 4:配置飞书账户(如需要)
|
||||
if (options.newBot) {
|
||||
const feishuConfigured = configureFeishuAccount(options);
|
||||
if (!feishuConfigured) {
|
||||
log.error('配置飞书账户失败,正在回退...');
|
||||
cleanupAgent(agentId);
|
||||
rollback(backupPath);
|
||||
log.warning('openclaw.json 已回退,但飞书侧配置可能需要手动清理');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 步骤 5:重启 Gateway
|
||||
const gatewayRestarted = restartGateway();
|
||||
if (!gatewayRestarted) {
|
||||
log.error('Gateway 重启失败');
|
||||
log.warning('配置已保存,但 Gateway 未重启');
|
||||
log.info('请手动执行:openclaw gateway restart');
|
||||
// 不回退,因为配置是正确的
|
||||
}
|
||||
|
||||
// 完成提示
|
||||
console.log(`\n${'─'.repeat(60)}`);
|
||||
log.success('配置完成!');
|
||||
console.log(`\n验证命令:openclaw agents list --bindings`);
|
||||
console.log(`\n如配置有误,可从备份恢复:`);
|
||||
console.log(` cp ${backupPath} ${CONFIG_PATH}`);
|
||||
console.log(` openclaw gateway restart`);
|
||||
console.log(`${'─'.repeat(60)}\n`);
|
||||
}
|
||||
|
||||
// 快速模式(命令行)
|
||||
function quickMode(options) {
|
||||
if (!options.agentid) {
|
||||
log.error('需要提供 --agent-id');
|
||||
showRoutingOptions();
|
||||
console.log(`
|
||||
${colors.bold}用法:${colors.reset}
|
||||
openclaw skills run agent-creator-with-binding [选项]
|
||||
|
||||
${colors.bold}选项:${colors.reset}
|
||||
--agent-id <id> Agent 唯一标识(必填)
|
||||
--agent-name <name> Agent 显示名称
|
||||
--binding-mode <mode> 绑定模式:account/group/none
|
||||
--new-bot 是否创建新飞书机器人
|
||||
--app-id <id> 飞书 App ID(新机器人时需要)
|
||||
--app-secret <secret> 飞书 App Secret(新机器人时需要)
|
||||
--account-id <id> 飞书账户 ID
|
||||
--chat-id <id> 群聊 ID(群聊绑定时需要)
|
||||
--bot-name <name> 机器人名称
|
||||
--dm-policy <policy> DM 策略:open/pairing/allowlist
|
||||
--skip-confirm 跳过确认
|
||||
--skip-restart 跳过重启
|
||||
|
||||
${colors.bold}示例:${colors.reset}
|
||||
# 账户级绑定(新机器人)
|
||||
openclaw skills run agent-creator-with-binding -- \\
|
||||
--agent-id email-assistant \\
|
||||
--agent-name "邮件助手" \\
|
||||
--binding-mode account \\
|
||||
--new-bot \\
|
||||
--app-id cli_xxx \\
|
||||
--app-secret yyy
|
||||
|
||||
# 账户级绑定(现有机器人)
|
||||
openclaw skills run agent-creator-with-binding -- \\
|
||||
--agent-id email-assistant \\
|
||||
--binding-mode account \\
|
||||
--account-id bot-existing
|
||||
|
||||
# 群聊级绑定
|
||||
openclaw skills run agent-creator-with-binding -- \\
|
||||
--agent-id project-assistant \\
|
||||
--binding-mode group \\
|
||||
--account-id bot-main \\
|
||||
--chat-id oc_xxx
|
||||
|
||||
# 不绑定(后台 Agent)
|
||||
openclaw skills run agent-creator-with-binding -- \\
|
||||
--agent-id data-processor \\
|
||||
--binding-mode none
|
||||
`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`\n${colors.cyan}🤖 Agent 创建 + 飞书绑定助手${colors.reset}\n`);
|
||||
|
||||
const agentOptions = {
|
||||
agentId: options.agentid,
|
||||
agentName: options.agentname || options.agentid,
|
||||
description: options.description,
|
||||
bindingMode: options.bindingmode || 'account',
|
||||
newBot: options.newbot === 'true',
|
||||
appId: options.appid,
|
||||
appSecret: options.appsecret,
|
||||
accountId: options.accountid || `bot-${options.agentid}`,
|
||||
botName: options.botname,
|
||||
dmPolicy: options.dmpolicy || 'open',
|
||||
chatId: options.chatid
|
||||
};
|
||||
|
||||
// 验证
|
||||
if (!validateAgentId(agentOptions.agentId)) {
|
||||
log.error('Agent ID 格式无效');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (agentOptions.bindingMode === 'account' && agentOptions.newBot) {
|
||||
if (!agentOptions.appId || !agentOptions.appSecret) {
|
||||
log.error('新机器人需要提供 --app-id 和 --app-secret');
|
||||
process.exit(1);
|
||||
}
|
||||
if (!validateAppId(agentOptions.appId)) {
|
||||
log.error('App ID 格式无效');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (agentOptions.bindingMode === 'group' && !agentOptions.chatId) {
|
||||
log.error('群聊绑定需要提供 --chat-id');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 预览
|
||||
if (!options.skipconfirm) {
|
||||
showPreview(agentOptions);
|
||||
console.log('\n');
|
||||
}
|
||||
|
||||
// 执行
|
||||
console.log('');
|
||||
|
||||
// 步骤 1:先备份(在任何修改之前)
|
||||
const backupPath = createBackup();
|
||||
if (!backupPath) {
|
||||
log.error('备份失败,无法继续');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 步骤 2:创建 Agent
|
||||
const agentCreated = createAgent(agentOptions.agentId);
|
||||
if (!agentCreated) {
|
||||
log.error('创建 Agent 失败,正在回退...');
|
||||
rollback(backupPath);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 步骤 3:更新配置
|
||||
const configUpdated = updateConfig(agentOptions);
|
||||
if (!configUpdated) {
|
||||
log.error('更新配置失败,正在回退...');
|
||||
cleanupAgent(agentOptions.agentId);
|
||||
rollback(backupPath);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 步骤 4:配置飞书账户(如需要)
|
||||
if (agentOptions.newBot) {
|
||||
const feishuConfigured = configureFeishuAccount(agentOptions);
|
||||
if (!feishuConfigured) {
|
||||
log.error('配置飞书账户失败,正在回退...');
|
||||
cleanupAgent(agentOptions.agentId);
|
||||
rollback(backupPath);
|
||||
log.warning('openclaw.json 已回退,但飞书侧配置可能需要手动清理');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// 步骤 5:重启 Gateway
|
||||
if (!options.skiprestart) {
|
||||
const gatewayRestarted = restartGateway();
|
||||
if (!gatewayRestarted) {
|
||||
log.error('Gateway 重启失败');
|
||||
log.warning('配置已保存,但 Gateway 未重启');
|
||||
log.info('请手动执行:openclaw gateway restart');
|
||||
}
|
||||
}
|
||||
|
||||
// 完成提示
|
||||
console.log(`\n${'─'.repeat(60)}`);
|
||||
log.success('配置完成!');
|
||||
console.log(`\n验证命令:openclaw agents list --bindings`);
|
||||
if (backupPath) {
|
||||
console.log(`\n如配置有误,可从备份恢复:`);
|
||||
console.log(` cp ${backupPath} ${CONFIG_PATH}`);
|
||||
}
|
||||
console.log(`${'─'.repeat(60)}\n`);
|
||||
}
|
||||
|
||||
// 解析参数
|
||||
function parseArgs() {
|
||||
const args = process.argv.slice(2);
|
||||
const options = {};
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
if (arg.startsWith('--')) {
|
||||
const key = arg.slice(2).replace(/-/g, '');
|
||||
const value = args[i + 1] && !args[i + 1].startsWith('--') ? args[i + 1] : 'true';
|
||||
options[key] = value;
|
||||
if (value !== 'true') i++;
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
// 入口
|
||||
const options = parseArgs();
|
||||
|
||||
if (Object.keys(options).length > 0) {
|
||||
quickMode(options);
|
||||
} else {
|
||||
interactiveMode().then(() => {
|
||||
// Done
|
||||
});
|
||||
}
|
||||
151
lib/config-manager.js
Normal file
151
lib/config-manager.js
Normal file
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* 配置管理模块
|
||||
* 负责读写 openclaw.json 配置
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const CONFIG_PATH = path.join(process.env.HOME, '.openclaw', 'openclaw.json');
|
||||
const BACKUP_DIR = path.join(process.env.HOME, '.openclaw', 'backups');
|
||||
|
||||
/**
|
||||
* 读取配置
|
||||
* @returns {Object} 配置对象
|
||||
*/
|
||||
function loadConfig() {
|
||||
const content = fs.readFileSync(CONFIG_PATH, 'utf8');
|
||||
return JSON.parse(content);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存配置
|
||||
* @param {Object} config - 配置对象
|
||||
* @returns {boolean} 是否成功
|
||||
*/
|
||||
function saveConfig(config) {
|
||||
try {
|
||||
fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf8');
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.error(`保存配置失败:${err.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建配置备份
|
||||
* @returns {string|null} 备份文件路径
|
||||
*/
|
||||
function createBackup() {
|
||||
try {
|
||||
if (!fs.existsSync(BACKUP_DIR)) {
|
||||
fs.mkdirSync(BACKUP_DIR, { recursive: true });
|
||||
}
|
||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
||||
const backupPath = path.join(BACKUP_DIR, `openclaw.json.${timestamp}`);
|
||||
fs.copyFileSync(CONFIG_PATH, backupPath);
|
||||
return backupPath;
|
||||
} catch (err) {
|
||||
console.error(`创建备份失败:${err.message}`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加 Agent 到配置
|
||||
* @param {Object} config - 配置对象
|
||||
* @param {string} agentId - Agent ID
|
||||
* @param {string} agentName - Agent 名称
|
||||
* @param {string} workspace - 工作空间路径
|
||||
*/
|
||||
function addAgent(config, agentId, agentName, workspace) {
|
||||
if (!config.agents) config.agents = {};
|
||||
if (!config.agents.list) config.agents.list = [];
|
||||
|
||||
const exists = config.agents.list.find(a => a.id === agentId);
|
||||
if (!exists) {
|
||||
config.agents.list.push({
|
||||
id: agentId,
|
||||
name: agentName,
|
||||
workspace: workspace
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加绑定规则
|
||||
* @param {Object} config - 配置对象
|
||||
* @param {string} agentId - Agent ID
|
||||
* @param {string} mode - 绑定模式 (account/group)
|
||||
* @param {Object} match - 匹配规则
|
||||
*/
|
||||
function addBinding(config, agentId, mode, match) {
|
||||
if (!config.bindings) config.bindings = [];
|
||||
|
||||
config.bindings.push({
|
||||
agentId: agentId,
|
||||
match: {
|
||||
channel: 'feishu',
|
||||
...match
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加飞书账户
|
||||
* @param {Object} config - 配置对象
|
||||
* @param {string} accountId - 账户 ID
|
||||
* @param {Object} accountConfig - 账户配置
|
||||
*/
|
||||
function addFeishuAccount(config, accountId, accountConfig) {
|
||||
if (!config.channels) config.channels = {};
|
||||
if (!config.channels.feishu) config.channels.feishu = { enabled: true };
|
||||
if (!config.channels.feishu.accounts) config.channels.feishu.accounts = {};
|
||||
|
||||
config.channels.feishu.accounts[accountId] = accountConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置会话隔离
|
||||
* @param {Object} config - 配置对象
|
||||
* @param {string} dmScope - 会话隔离级别
|
||||
*/
|
||||
function configureSession(config, dmScope = 'per-account-channel-peer') {
|
||||
if (!config.session) config.session = {};
|
||||
config.session.dmScope = dmScope;
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用 Agent 间调用
|
||||
* @param {Object} config - 配置对象
|
||||
* @param {string[]} allowAgents - 允许被调用的 Agent 列表
|
||||
*/
|
||||
function enableAgentToAgent(config, allowAgents = []) {
|
||||
if (!config.tools) config.tools = {};
|
||||
if (!config.tools.agentToAgent) config.tools.agentToAgent = {};
|
||||
|
||||
config.tools.agentToAgent.enabled = true;
|
||||
if (!config.tools.agentToAgent.allow) {
|
||||
config.tools.agentToAgent.allow = [];
|
||||
}
|
||||
|
||||
allowAgents.forEach(agentId => {
|
||||
if (!config.tools.agentToAgent.allow.includes(agentId)) {
|
||||
config.tools.agentToAgent.allow.push(agentId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
loadConfig,
|
||||
saveConfig,
|
||||
createBackup,
|
||||
addAgent,
|
||||
addBinding,
|
||||
addFeishuAccount,
|
||||
configureSession,
|
||||
enableAgentToAgent,
|
||||
CONFIG_PATH,
|
||||
BACKUP_DIR
|
||||
};
|
||||
121
lib/validator.js
Normal file
121
lib/validator.js
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* 验证工具模块
|
||||
* 负责验证各种 ID 和参数格式
|
||||
*/
|
||||
|
||||
/**
|
||||
* 验证 Agent ID 格式
|
||||
* @param {string} id - Agent ID
|
||||
* @returns {boolean} 是否有效
|
||||
*/
|
||||
function validateAgentId(id) {
|
||||
return /^[a-z0-9-]+$/.test(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证飞书 App ID 格式
|
||||
* @param {string} appId - App ID
|
||||
* @returns {boolean} 是否有效
|
||||
*/
|
||||
function validateAppId(appId) {
|
||||
return /^cli_[a-zA-Z0-9]+$/.test(appId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证飞书账户 ID 格式
|
||||
* @param {string} id - 账户 ID
|
||||
* @returns {boolean} 是否有效
|
||||
*/
|
||||
function validateAccountId(id) {
|
||||
return /^[a-z0-9-]+$/.test(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证群聊 ID 格式
|
||||
* @param {string} id - 群聊 ID
|
||||
* @returns {boolean} 是否有效
|
||||
*/
|
||||
function validateChatId(id) {
|
||||
return /^oc_[a-zA-Z0-9]+$/.test(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证用户 ID 格式
|
||||
* @param {string} id - 用户 ID
|
||||
* @returns {boolean} 是否有效
|
||||
*/
|
||||
function validateUserId(id) {
|
||||
return /^ou_[a-zA-Z0-9]+$/.test(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证完整配置
|
||||
* @param {Object} config - 配置对象
|
||||
* @returns {string[]} 错误列表
|
||||
*/
|
||||
function validateConfig(config) {
|
||||
const errors = [];
|
||||
|
||||
if (!config.agents?.list) {
|
||||
errors.push('缺少 agents.list 配置');
|
||||
return errors;
|
||||
}
|
||||
|
||||
// 验证 agents
|
||||
const agentIds = new Set();
|
||||
for (const agent of config.agents.list) {
|
||||
if (!agent.id) {
|
||||
errors.push('存在缺少 id 的 agent');
|
||||
}
|
||||
if (agentIds.has(agent.id)) {
|
||||
errors.push(`Agent ID "${agent.id}" 重复`);
|
||||
}
|
||||
agentIds.add(agent.id);
|
||||
}
|
||||
|
||||
// 验证飞书账户
|
||||
const accounts = config.channels?.feishu?.accounts || {};
|
||||
for (const [key, acc] of Object.entries(accounts)) {
|
||||
if (!validateAccountId(key)) {
|
||||
errors.push(`账户 ID "${key}" 格式无效`);
|
||||
}
|
||||
if (!validateAppId(acc.appId)) {
|
||||
errors.push(`[${key}] App ID 格式无效:${acc.appId}`);
|
||||
}
|
||||
if (!acc.appSecret) {
|
||||
errors.push(`[${key}] App Secret 不能为空`);
|
||||
}
|
||||
}
|
||||
|
||||
// 验证 bindings
|
||||
const bindings = config.bindings || [];
|
||||
for (const binding of bindings) {
|
||||
if (!binding.agentId) {
|
||||
errors.push('存在缺少 agentId 的 binding');
|
||||
}
|
||||
if (!binding.match) {
|
||||
errors.push('存在缺少 match 的 binding');
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证绑定模式
|
||||
* @param {string} mode - 绑定模式
|
||||
* @returns {boolean} 是否有效
|
||||
*/
|
||||
function validateBindingMode(mode) {
|
||||
return ['account', 'group', 'none'].includes(mode);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
validateAgentId,
|
||||
validateAppId,
|
||||
validateAccountId,
|
||||
validateChatId,
|
||||
validateUserId,
|
||||
validateConfig,
|
||||
validateBindingMode
|
||||
};
|
||||
17
package.json
Normal file
17
package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "agent-creator-with-binding",
|
||||
"version": "1.0.0",
|
||||
"description": "创建新 Agent 并配置飞书机器人绑定",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"start": "node index.js"
|
||||
},
|
||||
"keywords": [
|
||||
"openclaw",
|
||||
"agent",
|
||||
"feishu",
|
||||
"binding"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT"
|
||||
}
|
||||
30
templates/agents.md.template
Normal file
30
templates/agents.md.template
Normal file
@@ -0,0 +1,30 @@
|
||||
# AGENTS.md - {{agentName}} 工作指令
|
||||
|
||||
## 职责范围
|
||||
{{#each responsibilities}}
|
||||
{{this}}
|
||||
{{/each}}
|
||||
|
||||
## 工作流程
|
||||
1. 接收用户请求
|
||||
2. 分析请求类型
|
||||
3. 调用相应工具或技能
|
||||
4. 返回结果
|
||||
|
||||
## 工具使用
|
||||
- 使用飞书工具时,遵循 feishu-* 技能规范
|
||||
- 需要外部 API 时,先确认用户授权
|
||||
- 访问项目目录时使用绝对路径
|
||||
|
||||
## 依赖读取
|
||||
- 项目上下文:`/home/admin/.openclaw/projects/<项目名>/context.md`
|
||||
- 任务追踪:`/home/admin/.openclaw/projects/<项目名>/tasks.json`
|
||||
|
||||
## 输出文件
|
||||
- 输出到:`/home/admin/.openclaw/projects/<项目名>/<职责目录>/`
|
||||
- 格式:Markdown 或用户指定格式
|
||||
|
||||
## 会话管理
|
||||
- 每个用户/群聊有独立会话
|
||||
- 不主动提及与其他用户的对话
|
||||
- 会话上下文限制在当前用户/群聊
|
||||
25
templates/soul.md.template
Normal file
25
templates/soul.md.template
Normal file
@@ -0,0 +1,25 @@
|
||||
# SOUL.md - {{agentName}}
|
||||
|
||||
## 身份
|
||||
- **名称:** {{agentName}}
|
||||
- **职责:** {{description}}
|
||||
- **类型:** {{agentType}}
|
||||
|
||||
## 行为准则
|
||||
1. 遵循 OpenClaw 规范
|
||||
2. 保护用户隐私
|
||||
3. 及时响应用户需求
|
||||
4. 在职责范围内提供专业帮助
|
||||
|
||||
## 可用 Skills
|
||||
本 agent 使用以下 skills(从共享目录加载):
|
||||
- feishu-agent-binding: 飞书机器人配置管理
|
||||
- searxng: 隐私保护的搜索引擎
|
||||
|
||||
## 项目目录
|
||||
读取和写入:`/home/admin/.openclaw/projects/<项目名>/`
|
||||
|
||||
## 会话隔离
|
||||
- 每个用户私聊有独立会话
|
||||
- 每个群聊有独立会话
|
||||
- 不混淆不同用户/群聊的上下文
|
||||
Reference in New Issue
Block a user