feat: 添加系统命令支持 (:clear)

- 新增系统命令模块 (core/system-commands)
  - 支持 :clear/:cls/:c 清空对话历史
  - 命令注册表支持别名
  - 可扩展的命令执行器

- Server 端支持
  - 新增 /api/system-commands API
  - WebSocket 处理系统命令消息
  - 会话清空 API 端点

- UI 端支持
  - 新增 SystemCommandMenu 组件
  - 输入 : 时显示命令建议菜单
  - 键盘导航和选择
  - 底部提示添加 : 快捷键
This commit is contained in:
2025-12-17 19:25:42 +08:00
parent 4fc6b61134
commit e0444a966f
21 changed files with 1109 additions and 9 deletions
+13
View File
@@ -643,3 +643,16 @@ export function submitUserInput(toolCallId: string, answer: string): boolean {
const userInputWaiter = getUserInputWaiter();
return userInputWaiter.submitInput(toolCallId, answer);
}
/**
* 清空 Agent 的对话历史(内存中)
*
* 用于系统命令 :clear
*/
export function clearAgentHistory(sessionId: string): void {
const agent = agentCache.get(sessionId);
if (agent) {
agent.clearHistory();
console.log(`[Agent] Cleared history for session ${sessionId}`);
}
}
+2
View File
@@ -17,6 +17,8 @@ export {
compressContext,
// 用户输入响应
submitUserInput,
// 系统命令支持
clearAgentHistory,
// 类型导出
type TokenUsage,
type CompressionResult,
+2 -1
View File
@@ -9,7 +9,7 @@ import { cors } from 'hono/cors';
import { logger } from 'hono/logger';
import { createBunWebSocket } from 'hono/bun';
import { sessionsRouter, toolsRouter, configRouter, filesRouter, commandsRouter, mcpRouter, hooksRouter, agentsRouter, checkpointsRouter, providersRouter, servicesRouter, contextRouter, lspRouter } from './routes/index.js';
import { sessionsRouter, toolsRouter, configRouter, filesRouter, commandsRouter, mcpRouter, hooksRouter, agentsRouter, checkpointsRouter, providersRouter, servicesRouter, contextRouter, lspRouter, systemCommandsRouter } from './routes/index.js';
import {
handleWebSocket,
handleWebSocketMessage,
@@ -90,6 +90,7 @@ api.route('/checkpoints', checkpointsRouter);
api.route('/providers', providersRouter);
api.route('/services', servicesRouter);
api.route('/lsp', lspRouter);
api.route('/system-commands', systemCommandsRouter);
// 上下文压缩相关(挂载到根路径,内部路由包含 /sessions/:id/context
api.route('/', contextRouter);
+1
View File
@@ -17,3 +17,4 @@ export { providersRouter } from './providers.js';
export { servicesRouter } from './services.js';
export { contextRouter } from './context.js';
export { lspRouter } from './lsp.js';
export { systemCommandsRouter } from './system-commands.js';
+41
View File
@@ -104,6 +104,47 @@ sessionsRouter.delete('/:id', async (c) => {
});
});
/**
* POST /sessions/:id/clear - 清空会话消息
*
* 删除会话的所有消息和相关数据,但保留会话本身
*/
sessionsRouter.post('/:id/clear', async (c) => {
const id = c.req.param('id');
if (!sessionManager.exists(id)) {
return c.json(
{
success: false,
error: 'Session not found',
},
404
);
}
try {
// 删除会话的所有消息
await MessageStorage.removeBySession(id);
// 重置会话状态
sessionManager.updateStatus(id, 'idle');
return c.json({
success: true,
message: 'Session messages cleared',
});
} catch (error) {
console.error('[Sessions] Failed to clear messages:', error);
return c.json(
{
success: false,
error: error instanceof Error ? error.message : 'Failed to clear messages',
},
500
);
}
});
/**
* GET /sessions/:id/messages - 获取会话消息
*
@@ -0,0 +1,57 @@
/**
* System Commands API Routes
*
* 系统命令(: 前缀)相关的 REST API
*/
import { Hono } from 'hono';
import { getSystemCommandRegistry } from '@ai-assistant/core';
export const systemCommandsRouter = new Hono();
/**
* GET /system-commands - 列出所有系统命令
*/
systemCommandsRouter.get('/', (c) => {
const registry = getSystemCommandRegistry();
const commands = registry.list();
return c.json({
success: true,
data: {
commands: commands.map((cmd) => ({
name: cmd.name,
description: cmd.description,
aliases: cmd.aliases || [],
})),
},
});
});
/**
* GET /system-commands/:name - 获取单个系统命令
*/
systemCommandsRouter.get('/:name', (c) => {
const name = c.req.param('name');
const registry = getSystemCommandRegistry();
const command = registry.get(name);
if (!command) {
return c.json(
{
success: false,
error: 'System command not found',
},
404
);
}
return c.json({
success: true,
data: {
name: command.name,
description: command.description,
aliases: command.aliases || [],
},
});
});
+4 -2
View File
@@ -82,7 +82,7 @@ export type AgentModeType = 'build' | 'plan';
// 客户端发送的消息
export interface ClientMessage {
type: 'message' | 'cancel' | 'tool_response' | 'permission_response' | 'config_update' | 'mode_switch' | 'user_input_response';
type: 'message' | 'cancel' | 'tool_response' | 'permission_response' | 'config_update' | 'mode_switch' | 'user_input_response' | 'system_command';
sessionId: string;
payload?: {
content?: string;
@@ -122,7 +122,9 @@ export interface ServerMessage {
| 'subagent:end' // 子 Agent 执行结束
| 'subagent:stream' // 子 Agent 流式输出
| 'subagent:tool_start' // 子 Agent 工具调用开始
| 'subagent:tool_end'; // 子 Agent 工具调用结束
| 'subagent:tool_end' // 子 Agent 工具调用结束
// 系统命令响应
| 'system_command_result'; // 系统命令执行结果
sessionId: string;
payload?: unknown;
}
+58 -1
View File
@@ -6,9 +6,18 @@
import type { WSContext } from 'hono/ws';
import { getSessionManager } from './session/manager.js';
import { processMessage, cancelProcessing, getOrCreateAgent, submitUserInput } from './agent/index.js';
import { processMessage, cancelProcessing, getOrCreateAgent, submitUserInput, clearAgentHistory } from './agent/index.js';
import { handlePermissionResponse, setSessionAutoApprove } from './permission/handler.js';
import type { ClientMessage, ServerMessage } from './types.js';
import {
isSystemCommand,
executeSystemCommand,
initializeSystemCommands,
MessageStorage,
} from '@ai-assistant/core';
// 初始化系统命令
initializeSystemCommands();
// 存储活跃的 WebSocket 连接
const connections: Map<string, Set<WSContext>> = new Map();
@@ -109,6 +118,12 @@ export async function handleWebSocketMessage(
const agentMode = message.payload?.agentMode as 'build' | 'plan' | undefined;
const autoApprove = message.payload?.autoApprove as boolean | undefined;
// 检测系统命令(: 前缀)
if (isSystemCommand(content)) {
await handleSystemCommand(sessionId, content);
break;
}
// 将 @filepath 转换为 ./filepath 格式(方便 AI 识别为文件路径)
content = content.replace(/@([\w./-]+)/g, './$1');
@@ -281,3 +296,45 @@ export function getConnectionStats(): { sessions: number; connections: number }
connections: totalConnections,
};
}
/**
* 处理系统命令(: 前缀)
*/
async function handleSystemCommand(sessionId: string, content: string): Promise<void> {
const sessionManager = getSessionManager();
console.log(`[WS] System command: ${content}`);
// 执行系统命令
const result = await executeSystemCommand(content, { sessionId });
// 处理特殊操作
if (result.success && result.action) {
switch (result.action.type) {
case 'clear_messages': {
// 清空存储的消息
await MessageStorage.removeBySession(sessionId);
// 清空 Agent 内存中的对话历史
clearAgentHistory(sessionId);
// 重置会话状态
sessionManager.updateStatus(sessionId, 'idle');
break;
}
// 可以在这里添加其他操作类型的处理
}
}
// 发送结果给客户端
broadcastToSession(sessionId, {
type: 'system_command_result',
sessionId,
payload: {
success: result.success,
message: result.message,
error: result.error,
action: result.action,
},
});
}