feat: add /ego-mgr slash command
新增 /ego-mgr slash command 支持: - - 获取字段值 - - 设置字段值 - - 列出所有字段名 - - 删除字段 - - 添加 Agent Scope 字段 - - 添加 Public Scope 字段 - - 显示所有字段和值 实现细节: - 创建 EgoMgrSlashCommand 类处理所有子命令 - 使用 pcexec 工具调用 ego-mgr 二进制 - 自动注入 AGENT_ID, AGENT_WORKSPACE, AGENT_VERIFY 环境变量 - 支持带空格的字段值 - 友好的错误提示和用法说明
This commit is contained in:
214
plugin/commands/ego-mgr-slash.ts
Normal file
214
plugin/commands/ego-mgr-slash.ts
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
import { pcexec } from '../tools/pcexec';
|
||||||
|
|
||||||
|
export interface EgoMgrSlashCommandOptions {
|
||||||
|
/** OpenClaw base path */
|
||||||
|
openclawPath: string;
|
||||||
|
/** Current agent ID */
|
||||||
|
agentId: string;
|
||||||
|
/** Current workspace directory */
|
||||||
|
workspaceDir: string;
|
||||||
|
/** Callback for replies */
|
||||||
|
onReply: (message: string) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sentinel value injected into every pcexec subprocess */
|
||||||
|
const AGENT_VERIFY = 'IF YOU ARE AN AGENT/MODEL, YOU SHOULD NEVER TOUCH THIS ENV VARIABLE';
|
||||||
|
|
||||||
|
export class EgoMgrSlashCommand {
|
||||||
|
private openclawPath: string;
|
||||||
|
private agentId: string;
|
||||||
|
private workspaceDir: string;
|
||||||
|
private onReply: (message: string) => Promise<void>;
|
||||||
|
private binDir: string;
|
||||||
|
|
||||||
|
constructor(options: EgoMgrSlashCommandOptions) {
|
||||||
|
this.openclawPath = options.openclawPath;
|
||||||
|
this.agentId = options.agentId;
|
||||||
|
this.workspaceDir = options.workspaceDir;
|
||||||
|
this.onReply = options.onReply;
|
||||||
|
this.binDir = require('path').join(this.openclawPath, 'bin');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle /ego-mgr slash command
|
||||||
|
* @param command Full command string (e.g., "/ego-mgr get name")
|
||||||
|
*/
|
||||||
|
async handle(command: string): Promise<void> {
|
||||||
|
const parts = command.trim().split(/\s+/);
|
||||||
|
// Remove the "/ego-mgr" prefix
|
||||||
|
const args = parts.slice(1);
|
||||||
|
const subcommand = args[0];
|
||||||
|
|
||||||
|
if (!subcommand) {
|
||||||
|
await this.showUsage();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch (subcommand) {
|
||||||
|
case 'get':
|
||||||
|
await this.handleGet(args.slice(1));
|
||||||
|
break;
|
||||||
|
case 'set':
|
||||||
|
await this.handleSet(args.slice(1));
|
||||||
|
break;
|
||||||
|
case 'list':
|
||||||
|
await this.handleList(args.slice(1));
|
||||||
|
break;
|
||||||
|
case 'delete':
|
||||||
|
await this.handleDelete(args.slice(1));
|
||||||
|
break;
|
||||||
|
case 'add-column':
|
||||||
|
await this.handleAddColumn(args.slice(1));
|
||||||
|
break;
|
||||||
|
case 'add-public-column':
|
||||||
|
await this.handleAddPublicColumn(args.slice(1));
|
||||||
|
break;
|
||||||
|
case 'show':
|
||||||
|
await this.handleShow();
|
||||||
|
break;
|
||||||
|
case 'help':
|
||||||
|
default:
|
||||||
|
await this.showUsage();
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
await this.onReply(`❌ 错误: ${error.message || error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async showUsage(): Promise<void> {
|
||||||
|
const usage = [
|
||||||
|
'**ego-mgr 命令用法**',
|
||||||
|
'',
|
||||||
|
'`/ego-mgr get <column-name>` - 获取字段值',
|
||||||
|
'`/ego-mgr set <column-name> <value>` - 设置字段值',
|
||||||
|
'`/ego-mgr list` - 列出所有字段名',
|
||||||
|
'`/ego-mgr delete <column-name>` - 删除字段',
|
||||||
|
'`/ego-mgr add-column <column-name>` - 添加 Agent Scope 字段',
|
||||||
|
'`/ego-mgr add-public-column <column-name>` - 添加 Public Scope 字段',
|
||||||
|
'`/ego-mgr show` - 显示所有字段和值',
|
||||||
|
'',
|
||||||
|
'示例:',
|
||||||
|
'`/ego-mgr get name`',
|
||||||
|
'`/ego-mgr set timezone Asia/Shanghai`',
|
||||||
|
].join('\n');
|
||||||
|
await this.onReply(usage);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleGet(args: string[]): Promise<void> {
|
||||||
|
if (args.length < 1) {
|
||||||
|
await this.onReply('❌ 用法: `/ego-mgr get <column-name>`');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const columnName = args[0];
|
||||||
|
const result = await this.execEgoMgr(['get', columnName]);
|
||||||
|
await this.onReply(`**${columnName}**: ${result || '(空)'}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleSet(args: string[]): Promise<void> {
|
||||||
|
if (args.length < 2) {
|
||||||
|
await this.onReply('❌ 用法: `/ego-mgr set <column-name> <value>`');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const columnName = args[0];
|
||||||
|
const value = args.slice(1).join(' '); // Support values with spaces
|
||||||
|
await this.execEgoMgr(['set', columnName, value]);
|
||||||
|
await this.onReply(`✅ 已设置 **${columnName}** = \`${value}\``);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleList(args: string[]): Promise<void> {
|
||||||
|
const result = await this.execEgoMgr(['list', 'columns']);
|
||||||
|
if (!result.trim()) {
|
||||||
|
await this.onReply('📋 暂无字段定义');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const columns = result.split('\n').filter(Boolean);
|
||||||
|
const lines = ['**字段列表**:', ''];
|
||||||
|
for (const col of columns) {
|
||||||
|
lines.push(`• ${col}`);
|
||||||
|
}
|
||||||
|
await this.onReply(lines.join('\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleDelete(args: string[]): Promise<void> {
|
||||||
|
if (args.length < 1) {
|
||||||
|
await this.onReply('❌ 用法: `/ego-mgr delete <column-name>`');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const columnName = args[0];
|
||||||
|
await this.execEgoMgr(['delete', columnName]);
|
||||||
|
await this.onReply(`✅ 已删除字段 **${columnName}**`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleAddColumn(args: string[]): Promise<void> {
|
||||||
|
if (args.length < 1) {
|
||||||
|
await this.onReply('❌ 用法: `/ego-mgr add-column <column-name>`');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const columnName = args[0];
|
||||||
|
await this.execEgoMgr(['add', 'column', columnName]);
|
||||||
|
await this.onReply(`✅ 已添加 Agent Scope 字段 **${columnName}**`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleAddPublicColumn(args: string[]): Promise<void> {
|
||||||
|
if (args.length < 1) {
|
||||||
|
await this.onReply('❌ 用法: `/ego-mgr add-public-column <column-name>`');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const columnName = args[0];
|
||||||
|
await this.execEgoMgr(['add', 'public-column', columnName]);
|
||||||
|
await this.onReply(`✅ 已添加 Public Scope 字段 **${columnName}**`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleShow(): Promise<void> {
|
||||||
|
const result = await this.execEgoMgr(['show']);
|
||||||
|
if (!result.trim()) {
|
||||||
|
await this.onReply('📋 暂无字段数据');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const lines = ['**字段数据**:', ''];
|
||||||
|
lines.push('```');
|
||||||
|
lines.push(result);
|
||||||
|
lines.push('```');
|
||||||
|
await this.onReply(lines.join('\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute ego-mgr binary via pcexec
|
||||||
|
*/
|
||||||
|
private async execEgoMgr(args: string[]): Promise<string> {
|
||||||
|
const currentPath = process.env.PATH || '';
|
||||||
|
const newPath = currentPath.includes(this.binDir)
|
||||||
|
? currentPath
|
||||||
|
: `${currentPath}:${this.binDir}`;
|
||||||
|
|
||||||
|
const command = `ego-mgr ${args.map(a => this.shellEscape(a)).join(' ')}`;
|
||||||
|
|
||||||
|
const result = await pcexec(command, {
|
||||||
|
cwd: this.workspaceDir,
|
||||||
|
env: {
|
||||||
|
AGENT_ID: this.agentId,
|
||||||
|
AGENT_WORKSPACE: this.workspaceDir,
|
||||||
|
AGENT_VERIFY,
|
||||||
|
PATH: newPath,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.exitCode !== 0 && result.stderr) {
|
||||||
|
throw new Error(result.stderr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.stdout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape a string for shell usage
|
||||||
|
*/
|
||||||
|
private shellEscape(str: string): string {
|
||||||
|
// Simple escaping for common cases
|
||||||
|
if (/^[a-zA-Z0-9._-]+$/.test(str)) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
return `'${str.replace(/'/g, "'\"'\"'")}'`;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
startApiServer,
|
startApiServer,
|
||||||
} from './core/index';
|
} from './core/index';
|
||||||
import { SlashCommandHandler } from './commands/slash-commands';
|
import { SlashCommandHandler } from './commands/slash-commands';
|
||||||
|
import { EgoMgrSlashCommand } from './commands/ego-mgr-slash';
|
||||||
|
|
||||||
/** Sentinel value injected into every pcexec subprocess */
|
/** Sentinel value injected into every pcexec subprocess */
|
||||||
const AGENT_VERIFY = 'IF YOU ARE AN AGENT/MODEL, YOU SHOULD NEVER TOUCH THIS ENV VARIABLE';
|
const AGENT_VERIFY = 'IF YOU ARE AN AGENT/MODEL, YOU SHOULD NEVER TOUCH THIS ENV VARIABLE';
|
||||||
@@ -110,6 +111,28 @@ function register(api: any, config?: any) {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Register /ego-mgr slash command
|
||||||
|
if (api.registerSlashCommand) {
|
||||||
|
api.registerSlashCommand({
|
||||||
|
name: 'ego-mgr',
|
||||||
|
description: 'Manage agent identity/profile fields',
|
||||||
|
handler: async (ctx: any, command: string) => {
|
||||||
|
const egoMgrSlash = new EgoMgrSlashCommand({
|
||||||
|
openclawPath,
|
||||||
|
agentId: ctx.agentId || '',
|
||||||
|
workspaceDir: ctx.workspaceDir || '',
|
||||||
|
onReply: async (message: string) => {
|
||||||
|
if (ctx.reply) {
|
||||||
|
await ctx.reply(message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await egoMgrSlash.handle(command);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
logger.info('Registered /ego-mgr slash command');
|
||||||
|
}
|
||||||
|
|
||||||
logger.info('PaddedCell plugin initialized');
|
logger.info('PaddedCell plugin initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,4 +148,5 @@ module.exports.StatusManager = StatusManager;
|
|||||||
module.exports.createApiServer = createApiServer;
|
module.exports.createApiServer = createApiServer;
|
||||||
module.exports.startApiServer = startApiServer;
|
module.exports.startApiServer = startApiServer;
|
||||||
module.exports.SlashCommandHandler = SlashCommandHandler;
|
module.exports.SlashCommandHandler = SlashCommandHandler;
|
||||||
|
module.exports.EgoMgrSlashCommand = EgoMgrSlashCommand;
|
||||||
module.exports.AGENT_VERIFY = AGENT_VERIFY;
|
module.exports.AGENT_VERIFY = AGENT_VERIFY;
|
||||||
|
|||||||
Reference in New Issue
Block a user