From 16e91bd2c0bdfe13aa04dbbfcc5d6baf172b0ac3 Mon Sep 17 00:00:00 2001 From: XiaoFeng <117837368+Fzhiyu1@users.noreply.github.com> Date: Wed, 31 Dec 2025 09:35:20 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E8=AE=B0=E5=BF=86=E5=90=8C=E6=AD=A5=E5=92=8C=E7=9F=A5=E8=AF=86?= =?UTF-8?q?=E5=9B=BE=E8=B0=B1=E6=81=A2=E5=A4=8D=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 memory_compacted SSE 事件处理 - 添加 CompactedMemory/CompactedMessage 类型定义 - 添加 COMPACTION_SUMMARY 消息类型 - 实现压缩数据存储到 conversation.json - 实现从 conversation.json 构建恢复数据 - 发送请求时附带 knowledgeData 用于恢复知识图谱 --- src/services/dialogService.ts | 60 +++++++++- src/services/sseHandler.ts | 6 + src/types/api.ts | 9 ++ src/types/chatHistory.ts | 17 ++- src/types/memory.ts | 42 +++++++ src/utils/chatHistoryManager.ts | 205 +++++++++++++++++++++++++++++++- 6 files changed, 335 insertions(+), 4 deletions(-) create mode 100644 src/types/memory.ts diff --git a/src/services/dialogService.ts b/src/services/dialogService.ts index ec4551a..7478629 100644 --- a/src/services/dialogService.ts +++ b/src/services/dialogService.ts @@ -3,12 +3,15 @@ * 整合 SSE 通信、工具执行、用户交互 */ import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; import { startStreamDialog, generateTaskId, SSEController, SSECallbacks } from './sseHandler'; import { executeToolCall, createToolExecutorContext, ToolExecutorContext } from './toolExecutor'; import { userInteractionManager } from './userInteraction'; import { getConfig } from '../config/settings'; import type { DialogRequest, ToolCallRequest, AskUserEvent, RunMode, ToolConfirmEvent, PlanConfirmEvent } from '../types/api'; import { submitToolConfirm, submitAnswer } from './apiClient'; +import { ChatHistoryManager } from '../utils/chatHistoryManager'; /** * 消息段落类型 @@ -152,6 +155,39 @@ export class DialogSession { return this.isActive; } + /** + * 加载知识图谱数据 + * 从 .iccoder/knowledge.json 读取 + */ + private loadKnowledgeData(): string | null { + console.log('[DialogSession] loadKnowledgeData 开始执行'); + + const workspaceFolders = vscode.workspace.workspaceFolders; + if (!workspaceFolders || workspaceFolders.length === 0) { + console.log('[DialogSession] 没有工作区文件夹'); + return null; + } + + const workspacePath = workspaceFolders[0].uri.fsPath; + const knowledgePath = path.join(workspacePath, '.iccoder', 'knowledge.json'); + console.log('[DialogSession] 知识图谱路径:', knowledgePath); + + try { + const exists = fs.existsSync(knowledgePath); + console.log('[DialogSession] 文件存在:', exists); + + if (exists) { + const content = fs.readFileSync(knowledgePath, 'utf-8'); + console.log('[DialogSession] 加载知识图谱成功, 长度:', content.length); + return content; + } + } catch (error) { + console.warn('[DialogSession] 加载知识图谱失败:', error); + } + + return null; + } + /** * 获取工具操作描述(用于确认对话框) */ @@ -210,13 +246,29 @@ export class DialogSession { this.currentTextSegment = null; const config = getConfig(); + + // 获取压缩数据和新消息(用于后端重启后恢复) + const historyManager = ChatHistoryManager.getInstance(); + const compactedData = await historyManager.loadCompactedData(this.taskId); + const newMessages = historyManager.getNewMessagesSinceCompaction(); + + // 加载知识图谱数据 + const knowledgeData = this.loadKnowledgeData(); + console.log('[DialogSession] knowledgeData 加载结果:', knowledgeData ? `${knowledgeData.length} 字符` : 'null'); + const request: DialogRequest = { taskId: this.taskId, message, userId: config.userId, - mode: mode || 'agent' + mode: mode || 'agent', + compactedData: compactedData || undefined, + newMessages: newMessages.length > 0 ? newMessages : undefined, + knowledgeData: knowledgeData || undefined }; + // 追踪用户消息 + historyManager.trackUserMessage(message); + const sseCallbacks: SSECallbacks = { onTextDelta: (data) => { this.accumulatedText += data.text; @@ -466,6 +518,12 @@ export class DialogSession { } }, + onMemoryCompacted: async (data) => { + console.log('[DialogSession] onMemoryCompacted:', data.taskId); + // 保存压缩数据到本地 + await historyManager.saveCompactedData(data.compactedData); + }, + onOpen: () => { console.log('[DialogSession] SSE 连接已建立'); }, diff --git a/src/services/sseHandler.ts b/src/services/sseHandler.ts index d150c1f..081ff32 100644 --- a/src/services/sseHandler.ts +++ b/src/services/sseHandler.ts @@ -29,6 +29,7 @@ import type { AgentCompleteEvent, AgentErrorEvent } from '../types/api'; +import type { MemoryCompactedEvent } from '../types/memory'; /** * SSE 事件回调接口 @@ -68,6 +69,8 @@ export interface SSECallbacks { onAgentComplete?: (data: AgentCompleteEvent) => void; /** 子智能体错误 */ onAgentError?: (data: AgentErrorEvent) => void; + /** 记忆压缩完成 */ + onMemoryCompacted?: (data: MemoryCompactedEvent) => void; /** 连接打开 */ onOpen?: () => void; /** 连接关闭 */ @@ -319,6 +322,9 @@ function dispatchEvent( case 'agent_error': callbacks.onAgentError?.(data as AgentErrorEvent); break; + case 'memory_compacted': + callbacks.onMemoryCompacted?.(data as MemoryCompactedEvent); + break; default: console.log(`[SSE] 未知事件类型: ${eventType}`, data); } diff --git a/src/types/api.ts b/src/types/api.ts index 7f3d227..004e5ef 100644 --- a/src/types/api.ts +++ b/src/types/api.ts @@ -3,6 +3,8 @@ * 对应后端 IC Coder Backend 的接口格式 */ +import { CompactedMemory, CompactedMessage } from './memory'; + // ============== 对话请求/响应 ============== /** @@ -27,6 +29,12 @@ export interface DialogRequest { userId: string; /** 运行模式 */ mode: RunMode; + /** 压缩后的记忆数据(用于后端重启后恢复) */ + compactedData?: CompactedMemory; + /** 压缩后产生的新消息 */ + newMessages?: CompactedMessage[]; + /** 知识图谱数据(JSON 字符串,用于恢复知识图谱) */ + knowledgeData?: string; } // ============== SSE 事件类型 ============== @@ -45,6 +53,7 @@ export type SSEEventType = | 'agent_progress' // 子智能体进度 | 'agent_complete' // 子智能体完成 | 'agent_error' // 子智能体错误 + | 'memory_compacted' // 记忆压缩完成 | 'complete' // 对话完成 | 'error' // 错误 | 'warning' // 警告 diff --git a/src/types/chatHistory.ts b/src/types/chatHistory.ts index ebb33dd..d0c4750 100644 --- a/src/types/chatHistory.ts +++ b/src/types/chatHistory.ts @@ -5,7 +5,8 @@ export enum MessageType { SYSTEM = "SYSTEM", USER = "USER", AI = "AI", - TOOL_EXECUTION_RESULT = "TOOL_EXECUTION_RESULT" + TOOL_EXECUTION_RESULT = "TOOL_EXECUTION_RESULT", + COMPACTION_SUMMARY = "COMPACTION_SUMMARY" // 压缩摘要 } /** @@ -69,10 +70,22 @@ export interface ToolExecutionResultMessage extends BaseMessage { text: string; // JSON字符串 } +/** + * 压缩摘要消息 + */ +export interface CompactionSummaryMessage extends BaseMessage { + type: MessageType.COMPACTION_SUMMARY; + summary: string; + version: number; + compactedAt: string; + originalMessageCount: number; + compactedMessageCount: number; +} + /** * 联合消息类型 */ -export type ChatMessage = SystemMessage | UserMessage | AiMessage | ToolExecutionResultMessage; +export type ChatMessage = SystemMessage | UserMessage | AiMessage | ToolExecutionResultMessage | CompactionSummaryMessage; /** * 对话轮次元数据 diff --git a/src/types/memory.ts b/src/types/memory.ts new file mode 100644 index 0000000..31844ad --- /dev/null +++ b/src/types/memory.ts @@ -0,0 +1,42 @@ +/** + * 压缩记忆相关类型定义 + */ + +/** + * 压缩后的记忆数据 + */ +export interface CompactedMemory { + taskId: string; + version: number; + compactedAt: string; + summary: string; + recentMessages: CompactedMessage[]; + originalMessageCount: number; + compactedMessageCount: number; +} + +/** + * 压缩消息格式 + */ +export interface CompactedMessage { + type: 'USER' | 'AI' | 'SYSTEM' | 'TOOL_RESULT'; + content: string; + toolCall?: ToolCallInfo; +} + +/** + * 工具调用信息 + */ +export interface ToolCallInfo { + toolName: string; + toolInput: string; + toolOutput?: string; +} + +/** + * 记忆压缩 SSE 事件 + */ +export interface MemoryCompactedEvent { + taskId: string; + compactedData: CompactedMemory; +} diff --git a/src/utils/chatHistoryManager.ts b/src/utils/chatHistoryManager.ts index 17609e1..9f702d4 100644 --- a/src/utils/chatHistoryManager.ts +++ b/src/utils/chatHistoryManager.ts @@ -9,8 +9,10 @@ import { UserMessage, AiMessage, SystemMessage, - ToolExecutionResultMessage + ToolExecutionResultMessage, + CompactionSummaryMessage } from '../types/chatHistory'; +import { CompactedMemory, CompactedMessage } from '../types/memory'; /** * 会话历史管理器 @@ -23,6 +25,8 @@ export class ChatHistoryManager { private currentProjectPath: string | null = null; // 存储每个面板的任务信息(taskId 和 projectPath) private panelTaskMap: Map = new Map(); + // 追踪压缩后产生的新消息 + private newMessagesSinceCompaction: CompactedMessage[] = []; private constructor() { // 设置存储路径: ~/.iccoder @@ -690,4 +694,203 @@ export class ChatHistoryManager { hasMore: end < total }; } + + // ========== 压缩数据相关方法 ========== + + /** + * 保存压缩数据(存入 conversation.json 作为压缩摘要消息) + */ + public async saveCompactedData(compacted: CompactedMemory): Promise { + // 尝试从多个来源获取 projectPath + let projectPath = this.currentProjectPath; + + if (!projectPath) { + for (const [, taskInfo] of this.panelTaskMap) { + if (taskInfo.taskId === compacted.taskId) { + projectPath = taskInfo.projectPath; + break; + } + } + } + + if (!projectPath) { + console.error('[ChatHistoryManager] 无法保存压缩数据:projectPath 为空'); + return; + } + + // 读取现有对话历史 + const taskDir = this.getTaskDir(projectPath, compacted.taskId); + const conversationPath = path.join(taskDir, 'conversation.json'); + let messages: ChatMessage[] = []; + + try { + const uri = vscode.Uri.file(conversationPath); + const content = await vscode.workspace.fs.readFile(uri); + messages = JSON.parse(Buffer.from(content).toString('utf-8')); + } catch { + // 文件不存在,使用空数组 + } + + // 创建压缩摘要消息 + const summaryMessage: CompactionSummaryMessage = { + type: MessageType.COMPACTION_SUMMARY, + summary: compacted.summary, + version: compacted.version, + compactedAt: compacted.compactedAt, + originalMessageCount: compacted.originalMessageCount, + compactedMessageCount: compacted.compactedMessageCount + }; + + // 添加到对话历史 + messages.push(summaryMessage); + + // 保存 + const uri = vscode.Uri.file(conversationPath); + const content = Buffer.from(JSON.stringify(messages, null, 2), 'utf-8'); + await vscode.workspace.fs.writeFile(uri, content); + + // 重置新消息追踪 + this.newMessagesSinceCompaction = []; + + console.log(`[ChatHistoryManager] 压缩摘要已保存到 conversation.json: taskId=${compacted.taskId}`); + } + + /** + * 加载压缩数据(从 conversation.json 构建) + */ + public async loadCompactedData(taskId: string): Promise { + // 尝试从多个来源获取 projectPath + let projectPath = this.currentProjectPath; + + if (!projectPath) { + for (const [, taskInfo] of this.panelTaskMap) { + if (taskInfo.taskId === taskId) { + projectPath = taskInfo.projectPath; + break; + } + } + } + + if (!projectPath) { + console.log('[ChatHistoryManager] loadCompactedData: projectPath 为空'); + return null; + } + + // 读取 conversation.json + const taskDir = this.getTaskDir(projectPath, taskId); + const conversationPath = path.join(taskDir, 'conversation.json'); + + try { + const uri = vscode.Uri.file(conversationPath); + const content = await vscode.workspace.fs.readFile(uri); + const messages: ChatMessage[] = JSON.parse(Buffer.from(content).toString('utf-8')); + + if (messages.length === 0) { + console.log('[ChatHistoryManager] conversation.json 为空'); + return null; + } + + // 从 conversation.json 构建 CompactedMemory + return this.buildCompactedMemoryFromConversation(taskId, messages); + } catch { + console.log('[ChatHistoryManager] conversation.json 不存在:', conversationPath); + return null; + } + } + + /** + * 从 conversation.json 构建 CompactedMemory + */ + private buildCompactedMemoryFromConversation(taskId: string, messages: ChatMessage[]): CompactedMemory { + // 查找最后一个压缩摘要消息 + let lastSummary: CompactionSummaryMessage | null = null; + let summaryIndex = -1; + + for (let i = messages.length - 1; i >= 0; i--) { + if (messages[i].type === MessageType.COMPACTION_SUMMARY) { + lastSummary = messages[i] as CompactionSummaryMessage; + summaryIndex = i; + break; + } + } + + // 获取摘要后的消息(或全部消息) + const recentMessages = summaryIndex >= 0 + ? messages.slice(summaryIndex + 1) + : messages; + + // 转换为 CompactedMessage 格式 + const compactedMessages: CompactedMessage[] = recentMessages.map(msg => ({ + type: this.mapMessageType(msg.type), + content: this.extractMessageContent(msg) + })); + + return { + taskId, + version: lastSummary?.version || Date.now(), + compactedAt: lastSummary?.compactedAt || new Date().toISOString(), + summary: lastSummary?.summary || '', + recentMessages: compactedMessages, + originalMessageCount: messages.length, + compactedMessageCount: compactedMessages.length + }; + } + + /** + * 映射消息类型 + */ + private mapMessageType(type: MessageType): 'USER' | 'AI' | 'SYSTEM' | 'TOOL_RESULT' { + switch (type) { + case MessageType.USER: return 'USER'; + case MessageType.AI: return 'AI'; + case MessageType.SYSTEM: return 'SYSTEM'; + case MessageType.TOOL_EXECUTION_RESULT: return 'TOOL_RESULT'; + default: return 'USER'; + } + } + + /** + * 提取消息内容 + */ + private extractMessageContent(msg: ChatMessage): string { + switch (msg.type) { + case MessageType.USER: + return (msg as UserMessage).contents?.[0]?.text || ''; + case MessageType.AI: + return (msg as AiMessage).text || ''; + case MessageType.SYSTEM: + return (msg as SystemMessage).text || ''; + case MessageType.TOOL_EXECUTION_RESULT: + return (msg as ToolExecutionResultMessage).text || ''; + default: + return ''; + } + } + + /** + * 获取压缩后产生的新消息 + */ + public getNewMessagesSinceCompaction(): CompactedMessage[] { + return this.newMessagesSinceCompaction; + } + + /** + * 追踪新消息(用户消息) + */ + public trackUserMessage(text: string): void { + this.newMessagesSinceCompaction.push({ + type: 'USER', + content: text + }); + } + + /** + * 追踪新消息(AI消息) + */ + public trackAiMessage(text: string): void { + this.newMessagesSinceCompaction.push({ + type: 'AI', + content: text + }); + } }