feat: 创建 agent-creator-with-binding skill

整合 feishu-agent-binding 功能,一站式创建 Agent 并配置飞书绑定

主要功能:
- 创建 Agent 工作空间
- 配置飞书机器人账户
- 配置路由绑定(账户级/群聊级)
- 配置会话隔离
- 启用 Agent 间调用
- 自动备份和回退机制

支持模式:
- 账户级绑定(新机器人/现有机器人)
- 群聊级绑定
- 不绑定(后台 Agent)

安全特性:
- 修改前自动备份 openclaw.json
- 任何失败自动回退
- 详细的错误提示和恢复指南
This commit is contained in:
openclaw
2026-03-17 15:37:17 +08:00
commit ea6dd18fe4
8 changed files with 1486 additions and 0 deletions

242
README.md Normal file
View 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
View 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
View 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`);
// 步骤 1Agent 基础信息
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
View 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
View 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
View 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"
}

View 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 或用户指定格式
## 会话管理
- 每个用户/群聊有独立会话
- 不主动提及与其他用户的对话
- 会话上下文限制在当前用户/群聊

View 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/<项目名>/`
## 会话隔离
- 每个用户私聊有独立会话
- 每个群聊有独立会话
- 不混淆不同用户/群聊的上下文