feat: 添加系统命令支持 (:clear)
- 新增系统命令模块 (core/system-commands) - 支持 :clear/:cls/:c 清空对话历史 - 命令注册表支持别名 - 可扩展的命令执行器 - Server 端支持 - 新增 /api/system-commands API - WebSocket 处理系统命令消息 - 会话清空 API 端点 - UI 端支持 - 新增 SystemCommandMenu 组件 - 输入 : 时显示命令建议菜单 - 键盘导航和选择 - 底部提示添加 : 快捷键
This commit is contained in:
@@ -150,6 +150,25 @@ export type {
|
||||
CommandOperationResult,
|
||||
} from './commands/index.js';
|
||||
|
||||
// System Commands (: 前缀)
|
||||
export {
|
||||
getSystemCommandRegistry,
|
||||
resetSystemCommandRegistry,
|
||||
SystemCommandExecutor,
|
||||
isSystemCommand,
|
||||
parseSystemCommand,
|
||||
executeSystemCommand,
|
||||
initializeSystemCommands,
|
||||
builtinSystemCommands,
|
||||
} from './system-commands/index.js';
|
||||
export type {
|
||||
SystemCommand,
|
||||
SystemCommandContext,
|
||||
SystemCommandResult,
|
||||
SystemCommandAction,
|
||||
SystemCommandInput,
|
||||
} from './system-commands/index.js';
|
||||
|
||||
// Checkpoint
|
||||
export {
|
||||
CheckpointManager,
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* :clear 系统命令
|
||||
*
|
||||
* 清空当前会话的历史记录
|
||||
*/
|
||||
|
||||
import type { SystemCommand } from '../types.js';
|
||||
|
||||
export const clearCommand: SystemCommand = {
|
||||
name: 'clear',
|
||||
description: '清空当前会话的历史记录',
|
||||
aliases: ['cls', 'c'],
|
||||
execute: async (_context) => {
|
||||
// 实际的清空操作由 Server 层处理
|
||||
// 这里只返回操作指令
|
||||
return {
|
||||
success: true,
|
||||
message: '会话已清空',
|
||||
action: { type: 'clear_messages' },
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 内置系统命令
|
||||
*/
|
||||
|
||||
import type { SystemCommand } from '../types.js';
|
||||
import { clearCommand } from './clear.js';
|
||||
|
||||
/**
|
||||
* 所有内置系统命令
|
||||
*/
|
||||
export const builtinSystemCommands: SystemCommand[] = [
|
||||
clearCommand,
|
||||
];
|
||||
|
||||
export { clearCommand };
|
||||
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* System Command 执行器
|
||||
*
|
||||
* 解析和执行系统命令
|
||||
*/
|
||||
|
||||
import type {
|
||||
SystemCommandContext,
|
||||
SystemCommandInput,
|
||||
SystemCommandResult,
|
||||
} from './types.js';
|
||||
import { getSystemCommandRegistry } from './registry.js';
|
||||
|
||||
/**
|
||||
* System Command 执行器
|
||||
*/
|
||||
export class SystemCommandExecutor {
|
||||
/**
|
||||
* 检查输入是否为系统命令
|
||||
*/
|
||||
static isSystemCommand(input: string): boolean {
|
||||
return input.trim().startsWith(':');
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析系统命令输入
|
||||
*/
|
||||
static parseInput(input: string): SystemCommandInput | null {
|
||||
const trimmed = input.trim();
|
||||
|
||||
if (!trimmed.startsWith(':')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 移除 : 前缀
|
||||
const content = trimmed.slice(1);
|
||||
|
||||
// 分割命令和参数
|
||||
const spaceIndex = content.indexOf(' ');
|
||||
|
||||
if (spaceIndex === -1) {
|
||||
return {
|
||||
command: content.toLowerCase(),
|
||||
arguments: '',
|
||||
args: [],
|
||||
};
|
||||
}
|
||||
|
||||
const command = content.slice(0, spaceIndex).toLowerCase();
|
||||
const argsString = content.slice(spaceIndex + 1).trim();
|
||||
|
||||
return {
|
||||
command,
|
||||
arguments: argsString,
|
||||
args: argsString ? argsString.split(/\s+/) : [],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行系统命令
|
||||
*/
|
||||
static async execute(
|
||||
input: string,
|
||||
context: Partial<SystemCommandContext> = {}
|
||||
): Promise<SystemCommandResult> {
|
||||
const parsed = this.parseInput(input);
|
||||
|
||||
if (!parsed) {
|
||||
return {
|
||||
success: false,
|
||||
error: '无效的系统命令格式',
|
||||
};
|
||||
}
|
||||
|
||||
const registry = getSystemCommandRegistry();
|
||||
const command = registry.get(parsed.command);
|
||||
|
||||
if (!command) {
|
||||
// 列出可用命令作为提示
|
||||
const available = registry
|
||||
.list()
|
||||
.map((c) => `:${c.name}`)
|
||||
.join(', ');
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: `未知的系统命令: :${parsed.command}`,
|
||||
message: available ? `可用的系统命令: ${available}` : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await command.execute({
|
||||
arguments: parsed.arguments,
|
||||
args: parsed.args,
|
||||
sessionId: context.sessionId,
|
||||
workdir: context.workdir,
|
||||
});
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error:
|
||||
error instanceof Error ? error.message : '系统命令执行失败',
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 便捷函数:检查是否为系统命令
|
||||
*/
|
||||
export function isSystemCommand(input: string): boolean {
|
||||
return SystemCommandExecutor.isSystemCommand(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* 便捷函数:解析系统命令
|
||||
*/
|
||||
export function parseSystemCommand(
|
||||
input: string
|
||||
): SystemCommandInput | null {
|
||||
return SystemCommandExecutor.parseInput(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* 便捷函数:执行系统命令
|
||||
*/
|
||||
export async function executeSystemCommand(
|
||||
input: string,
|
||||
context?: Partial<SystemCommandContext>
|
||||
): Promise<SystemCommandResult> {
|
||||
return SystemCommandExecutor.execute(input, context);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* System Commands 模块
|
||||
*
|
||||
* 提供系统命令(: 前缀)的所有功能导出
|
||||
*/
|
||||
|
||||
// 类型
|
||||
export type {
|
||||
SystemCommand,
|
||||
SystemCommandContext,
|
||||
SystemCommandResult,
|
||||
SystemCommandAction,
|
||||
SystemCommandInput,
|
||||
} from './types.js';
|
||||
|
||||
// 注册表
|
||||
export {
|
||||
SystemCommandRegistry,
|
||||
getSystemCommandRegistry,
|
||||
resetSystemCommandRegistry,
|
||||
} from './registry.js';
|
||||
|
||||
// 执行器
|
||||
export {
|
||||
SystemCommandExecutor,
|
||||
isSystemCommand,
|
||||
parseSystemCommand,
|
||||
executeSystemCommand,
|
||||
} from './executor.js';
|
||||
|
||||
// 内置命令
|
||||
export { builtinSystemCommands, clearCommand } from './builtin/index.js';
|
||||
|
||||
// 初始化函数
|
||||
import { getSystemCommandRegistry } from './registry.js';
|
||||
import { builtinSystemCommands } from './builtin/index.js';
|
||||
|
||||
/**
|
||||
* 初始化系统命令注册表
|
||||
*
|
||||
* 注册所有内置系统命令
|
||||
*/
|
||||
export function initializeSystemCommands(): void {
|
||||
const registry = getSystemCommandRegistry();
|
||||
registry.registerAll(builtinSystemCommands);
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* System Command 注册表
|
||||
*
|
||||
* 管理所有系统命令的注册和查找
|
||||
*/
|
||||
|
||||
import type { SystemCommand } from './types.js';
|
||||
|
||||
/**
|
||||
* System Command 注册表
|
||||
*/
|
||||
export class SystemCommandRegistry {
|
||||
private commands = new Map<string, SystemCommand>();
|
||||
private aliases = new Map<string, string>();
|
||||
|
||||
/**
|
||||
* 注册系统命令
|
||||
*/
|
||||
register(command: SystemCommand): void {
|
||||
this.commands.set(command.name, command);
|
||||
|
||||
// 注册别名
|
||||
if (command.aliases) {
|
||||
for (const alias of command.aliases) {
|
||||
this.aliases.set(alias, command.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量注册系统命令
|
||||
*/
|
||||
registerAll(commands: SystemCommand[]): void {
|
||||
for (const command of commands) {
|
||||
this.register(command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统命令
|
||||
*/
|
||||
get(name: string): SystemCommand | undefined {
|
||||
// 先尝试直接查找
|
||||
const command = this.commands.get(name);
|
||||
if (command) {
|
||||
return command;
|
||||
}
|
||||
|
||||
// 再尝试别名查找
|
||||
const aliasTarget = this.aliases.get(name);
|
||||
if (aliasTarget) {
|
||||
return this.commands.get(aliasTarget);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查命令是否存在
|
||||
*/
|
||||
has(name: string): boolean {
|
||||
return this.commands.has(name) || this.aliases.has(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有命令
|
||||
*/
|
||||
list(): SystemCommand[] {
|
||||
return Array.from(this.commands.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空注册表
|
||||
*/
|
||||
clear(): void {
|
||||
this.commands.clear();
|
||||
this.aliases.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// 全局单例
|
||||
let globalRegistry: SystemCommandRegistry | null = null;
|
||||
|
||||
/**
|
||||
* 获取全局 System Command 注册表
|
||||
*/
|
||||
export function getSystemCommandRegistry(): SystemCommandRegistry {
|
||||
if (!globalRegistry) {
|
||||
globalRegistry = new SystemCommandRegistry();
|
||||
}
|
||||
return globalRegistry;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置全局注册表(用于测试)
|
||||
*/
|
||||
export function resetSystemCommandRegistry(): void {
|
||||
globalRegistry = null;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* System Command 系统类型定义
|
||||
*
|
||||
* System Command 是以 : 开头的系统操作命令。
|
||||
* 与普通 Command 不同,System Command 不调用 Agent,
|
||||
* 而是直接执行系统操作(如清空会话、切换模型等)。
|
||||
*/
|
||||
|
||||
/**
|
||||
* System Command 定义
|
||||
*/
|
||||
export interface SystemCommand {
|
||||
/** 命令名称(不含 : 前缀) */
|
||||
name: string;
|
||||
/** 命令描述 */
|
||||
description: string;
|
||||
/** 命令别名 */
|
||||
aliases?: string[];
|
||||
/** 执行函数 */
|
||||
execute: (context: SystemCommandContext) => Promise<SystemCommandResult>;
|
||||
}
|
||||
|
||||
/**
|
||||
* System Command 执行上下文
|
||||
*/
|
||||
export interface SystemCommandContext {
|
||||
/** 原始参数字符串 */
|
||||
arguments: string;
|
||||
/** 解析后的参数数组 */
|
||||
args: string[];
|
||||
/** 当前会话 ID */
|
||||
sessionId?: string;
|
||||
/** 当前工作目录 */
|
||||
workdir?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* System Command 执行结果
|
||||
*/
|
||||
export interface SystemCommandResult {
|
||||
/** 是否成功 */
|
||||
success: boolean;
|
||||
/** 返回消息(显示给用户) */
|
||||
message?: string;
|
||||
/** 错误信息 */
|
||||
error?: string;
|
||||
/** 附加数据 */
|
||||
data?: Record<string, unknown>;
|
||||
/** 特殊操作指令 */
|
||||
action?: SystemCommandAction;
|
||||
}
|
||||
|
||||
/**
|
||||
* System Command 操作指令
|
||||
*
|
||||
* 用于通知前端执行特定操作
|
||||
*/
|
||||
export type SystemCommandAction =
|
||||
| { type: 'clear_messages' }
|
||||
| { type: 'new_session'; sessionId?: string }
|
||||
| { type: 'switch_model'; model: string }
|
||||
| { type: 'reload' };
|
||||
|
||||
/**
|
||||
* System Command 输入解析结果
|
||||
*/
|
||||
export interface SystemCommandInput {
|
||||
/** 命令名称(不含 : 前缀) */
|
||||
command: string;
|
||||
/** 原始参数字符串 */
|
||||
arguments: string;
|
||||
/** 解析后的参数数组 */
|
||||
args: string[];
|
||||
}
|
||||
Reference in New Issue
Block a user