feat: implement pass_mgr, pcexec, and safe-restart modules
- Add pass_mgr Go binary with AES-256-GCM encryption - Add pcexec TypeScript tool with password sanitization - Add safe-restart module with state machine and API - Add slash command handler with cooldown support - Update README with usage documentation
This commit is contained in:
182
safe-restart/src/slash-commands.ts
Normal file
182
safe-restart/src/slash-commands.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
import { StatusManager } from './status-manager';
|
||||
|
||||
export interface SlashCommandOptions {
|
||||
statusManager: StatusManager;
|
||||
/** List of authorized user IDs */
|
||||
authorizedUsers: string[];
|
||||
/** Cooldown duration in seconds */
|
||||
cooldownSeconds?: number;
|
||||
/** Callback for replies */
|
||||
onReply: (message: string) => Promise<void>;
|
||||
}
|
||||
|
||||
interface CommandState {
|
||||
passMgrEnabled: boolean;
|
||||
safeRestartEnabled: boolean;
|
||||
lastToggle: {
|
||||
'pass-mgr': number;
|
||||
'safe-restart': number;
|
||||
};
|
||||
}
|
||||
|
||||
export class SlashCommandHandler {
|
||||
private statusManager: StatusManager;
|
||||
private authorizedUsers: string[];
|
||||
private cooldownMs: number;
|
||||
private onReply: (message: string) => Promise<void>;
|
||||
private state: CommandState;
|
||||
|
||||
constructor(options: SlashCommandOptions) {
|
||||
this.statusManager = options.statusManager;
|
||||
this.authorizedUsers = options.authorizedUsers;
|
||||
this.cooldownMs = (options.cooldownSeconds || 10) * 1000;
|
||||
this.onReply = options.onReply;
|
||||
|
||||
this.state = {
|
||||
passMgrEnabled: true,
|
||||
safeRestartEnabled: true,
|
||||
lastToggle: {
|
||||
'pass-mgr': 0,
|
||||
'safe-restart': 0,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a slash command
|
||||
*/
|
||||
async handle(command: string, userId: string): Promise<void> {
|
||||
// Check authorization
|
||||
if (!this.authorizedUsers.includes(userId)) {
|
||||
await this.onReply('❌ 无权执行此命令');
|
||||
return;
|
||||
}
|
||||
|
||||
const parts = command.trim().split(/\s+/);
|
||||
const subcommand = parts[1];
|
||||
const feature = parts[2] as 'pass-mgr' | 'safe-restart';
|
||||
|
||||
switch (subcommand) {
|
||||
case 'status':
|
||||
await this.handleStatus();
|
||||
break;
|
||||
case 'enable':
|
||||
await this.handleEnable(feature);
|
||||
break;
|
||||
case 'disable':
|
||||
await this.handleDisable(feature);
|
||||
break;
|
||||
default:
|
||||
await this.onReply(
|
||||
'用法:\n' +
|
||||
'`/padded-cell-ctrl status` - 查看状态\n' +
|
||||
'`/padded-cell-ctrl enable pass-mgr|safe-restart` - 启用功能\n' +
|
||||
'`/padded-cell-ctrl disable pass-mgr|safe-restart` - 禁用功能'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private async handleStatus(): Promise<void> {
|
||||
const global = this.statusManager.getGlobalStatus();
|
||||
const agents = this.statusManager.getAllAgents();
|
||||
|
||||
const lines = [
|
||||
'**PaddedCell 状态**',
|
||||
'',
|
||||
`🔐 密码管理: ${this.state.passMgrEnabled ? '✅ 启用' : '❌ 禁用'}`,
|
||||
`🔄 安全重启: ${this.state.safeRestartEnabled ? '✅ 启用' : '❌ 禁用'}`,
|
||||
'',
|
||||
'**Agent 状态:**',
|
||||
];
|
||||
|
||||
for (const agent of agents) {
|
||||
const emoji = this.getStateEmoji(agent.state);
|
||||
lines.push(`${emoji} ${agent.agentId}: ${agent.state}`);
|
||||
}
|
||||
|
||||
if (agents.length === 0) {
|
||||
lines.push('(暂无 agent 注册)');
|
||||
}
|
||||
|
||||
if (global.restartStatus !== 'idle') {
|
||||
lines.push('');
|
||||
lines.push(`⚠️ 重启状态: ${global.restartStatus}`);
|
||||
if (global.restartScheduledBy) {
|
||||
lines.push(` 由 ${global.restartScheduledBy} 发起`);
|
||||
}
|
||||
}
|
||||
|
||||
await this.onReply(lines.join('\n'));
|
||||
}
|
||||
|
||||
private async handleEnable(feature: 'pass-mgr' | 'safe-restart'): Promise<void> {
|
||||
if (!this.isValidFeature(feature)) {
|
||||
await this.onReply('❌ 未知功能。可用选项: pass-mgr, safe-restart');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isOnCooldown(feature)) {
|
||||
await this.onReply('⏳ 该功能最近刚被修改过,请稍后再试');
|
||||
return;
|
||||
}
|
||||
|
||||
if (feature === 'pass-mgr') {
|
||||
this.state.passMgrEnabled = true;
|
||||
} else {
|
||||
this.state.safeRestartEnabled = true;
|
||||
}
|
||||
|
||||
this.state.lastToggle[feature] = Date.now();
|
||||
await this.onReply(`✅ 已启用 ${feature}`);
|
||||
}
|
||||
|
||||
private async handleDisable(feature: 'pass-mgr' | 'safe-restart'): Promise<void> {
|
||||
if (!this.isValidFeature(feature)) {
|
||||
await this.onReply('❌ 未知功能。可用选项: pass-mgr, safe-restart');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isOnCooldown(feature)) {
|
||||
await this.onReply('⏳ 该功能最近刚被修改过,请稍后再试');
|
||||
return;
|
||||
}
|
||||
|
||||
if (feature === 'pass-mgr') {
|
||||
this.state.passMgrEnabled = false;
|
||||
} else {
|
||||
this.state.safeRestartEnabled = false;
|
||||
}
|
||||
|
||||
this.state.lastToggle[feature] = Date.now();
|
||||
await this.onReply(`✅ 已禁用 ${feature}`);
|
||||
}
|
||||
|
||||
private isValidFeature(feature: string): feature is 'pass-mgr' | 'safe-restart' {
|
||||
return feature === 'pass-mgr' || feature === 'safe-restart';
|
||||
}
|
||||
|
||||
private isOnCooldown(feature: 'pass-mgr' | 'safe-restart'): boolean {
|
||||
const lastToggle = this.state.lastToggle[feature];
|
||||
return Date.now() - lastToggle < this.cooldownMs;
|
||||
}
|
||||
|
||||
private getStateEmoji(state: string): string {
|
||||
switch (state) {
|
||||
case 'idle': return '💤';
|
||||
case 'busy': return '⚡';
|
||||
case 'focus': return '🎯';
|
||||
case 'freeze': return '🧊';
|
||||
case 'pre-freeze': return '⏳';
|
||||
case 'pre-freeze-focus': return '📝';
|
||||
default: return '❓';
|
||||
}
|
||||
}
|
||||
|
||||
isPassMgrEnabled(): boolean {
|
||||
return this.state.passMgrEnabled;
|
||||
}
|
||||
|
||||
isSafeRestartEnabled(): boolean {
|
||||
return this.state.safeRestartEnabled;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user