diff --git a/plugin/commands/ego-mgr-slash.ts b/plugin/commands/ego-mgr-slash.ts new file mode 100644 index 0000000..d153134 --- /dev/null +++ b/plugin/commands/ego-mgr-slash.ts @@ -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; +} + +/** 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; + 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 { + 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 { + const usage = [ + '**ego-mgr 命令用法**', + '', + '`/ego-mgr get ` - 获取字段值', + '`/ego-mgr set ` - 设置字段值', + '`/ego-mgr list` - 列出所有字段名', + '`/ego-mgr delete ` - 删除字段', + '`/ego-mgr add-column ` - 添加 Agent Scope 字段', + '`/ego-mgr add-public-column ` - 添加 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 { + if (args.length < 1) { + await this.onReply('❌ 用法: `/ego-mgr get `'); + return; + } + const columnName = args[0]; + const result = await this.execEgoMgr(['get', columnName]); + await this.onReply(`**${columnName}**: ${result || '(空)'}`); + } + + private async handleSet(args: string[]): Promise { + if (args.length < 2) { + await this.onReply('❌ 用法: `/ego-mgr set `'); + 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 { + 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 { + if (args.length < 1) { + await this.onReply('❌ 用法: `/ego-mgr delete `'); + return; + } + const columnName = args[0]; + await this.execEgoMgr(['delete', columnName]); + await this.onReply(`✅ 已删除字段 **${columnName}**`); + } + + private async handleAddColumn(args: string[]): Promise { + if (args.length < 1) { + await this.onReply('❌ 用法: `/ego-mgr add-column `'); + return; + } + const columnName = args[0]; + await this.execEgoMgr(['add', 'column', columnName]); + await this.onReply(`✅ 已添加 Agent Scope 字段 **${columnName}**`); + } + + private async handleAddPublicColumn(args: string[]): Promise { + if (args.length < 1) { + await this.onReply('❌ 用法: `/ego-mgr add-public-column `'); + return; + } + const columnName = args[0]; + await this.execEgoMgr(['add', 'public-column', columnName]); + await this.onReply(`✅ 已添加 Public Scope 字段 **${columnName}**`); + } + + private async handleShow(): Promise { + 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 { + 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, "'\"'\"'")}'`; + } +} diff --git a/plugin/index.ts b/plugin/index.ts index 410e7c1..9c84e5b 100644 --- a/plugin/index.ts +++ b/plugin/index.ts @@ -10,6 +10,7 @@ import { startApiServer, } from './core/index'; import { SlashCommandHandler } from './commands/slash-commands'; +import { EgoMgrSlashCommand } from './commands/ego-mgr-slash'; /** Sentinel value injected into every pcexec subprocess */ 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'); } @@ -125,4 +148,5 @@ module.exports.StatusManager = StatusManager; module.exports.createApiServer = createApiServer; module.exports.startApiServer = startApiServer; module.exports.SlashCommandHandler = SlashCommandHandler; +module.exports.EgoMgrSlashCommand = EgoMgrSlashCommand; module.exports.AGENT_VERIFY = AGENT_VERIFY;