--- version: 2.0 last-updated: 2026-03-09 status: design --- # 04 — 会话管理与 Chat 流程 ## 一、现状分析 ### Qiming 服务端已有能力 会话管理 API(`workspace/qiming/src/services/agentConfig.ts`): | 端点 | 能力 | | ------------------------------------------- | ---------------- | | `GET /api/agent/conversation/{id}` | 查询会话 | | `POST /api/agent/conversation/message/list` | 查询会话消息列表 | | `POST /api/agent/conversation/chat/stop` | 停止会话 | | `POST /api/agent/conversation/create` | 创建会话 | | `POST /api/agent/conversation/list` | 会话列表 | Chat API(`workspace/qiming/src/services/appDev.ts`): | 端点 | 能力 | | ----------------------------------------- | ------------ | | `POST /api/computer/chat` | 发送聊天消息 | | `GET /api/computer/progress/{sessionId}` | SSE 进度流 | | `POST /api/computer/cancel` | 取消任务 | **目标**:客户端会话与服务器**双向同步**,支持跨设备会话连续性。 ### V1 现有实现 - **本地数据库**:SQLite 仅有 `settings` 表(key-value 存储,无会话/消息表) - **会话存储**:会话数据完全由 ACP Engine(子进程)管理,不在 Electron 主数据库中 - **Chat 流程**: - 通过 `computerServer.ts` 的 `/computer/chat` HTTP 端点 - 内部调用 `UnifiedAgentService.chat()` → ACP Engine - SSE 推送进度事件给前端 - **ACP SDK 会话**:`UnifiedAgentService` 管理 ACP 层面的 session(create/list/get/delete/fork) **问题**: 1. 会话依赖 ACP Engine,无法直接对话普通 LLM API 2. 本地无会话/消息持久化(仅 ACP 子进程内部管理) 3. SSE 事件通过 HTTP Server 中转,链路过长 4. 无会话搜索/标签/归档功能 --- ## 二、本地会话引擎设计 ### 2.1 架构 ``` ┌──────────────────────────────────────────────────────────────────┐ │ ChatEngine (会话引擎) │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ SessionMgr │ │ MessageStore │ │ StreamRelay │ │ │ │ 会话管理 │ │ 消息持久化 │ │ 流式中继 │ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ │ ┌──────▼─────────────────▼─────────────────▼──────────────────┐ │ │ │ ChatPipeline (对话管道) │ │ │ │ │ │ │ │ ┌─────────┐ ┌──────────┐ ┌─────────┐ ┌──────────────┐ │ │ │ │ │ PreProc │→│ Provider │→│ PostProc │→│ MemoryEncode │ │ │ │ │ │ (预处理) │ │ (调用LLM) │ │ (后处理) │ │ (记忆编码) │ │ │ │ │ └─────────┘ └──────────┘ └─────────┘ └──────────────┘ │ │ │ └──────────────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────────────────┐ │ │ │ 调用层 (Routing) │ │ │ │ ModelGateway.createChatClient() │ │ │ │ OR UnifiedAgentService (ACP Mode) │ │ │ └──────────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────┘ ``` ### 2.2 双模式运行 | 模式 | 描述 | Provider 类型 | | ------------ | ----------------------------------- | ------------------------------------- | | **直接模式** | ChatEngine 直接调用 LLM API | openai-compat / anthropic / ollama | | **ACP 模式** | ChatEngine 委托 UnifiedAgentService | acp-engine(claude-code / qimingcode) | ACP 模式下,ChatEngine 负责会话管理和消息持久化, 实际推理仍然走 ACP SDK → 子进程引擎。 --- ## 三、数据模型 ### 3.1 会话表 (sessions) ```sql CREATE TABLE sessions_v2 ( id TEXT PRIMARY KEY, title TEXT NOT NULL DEFAULT 'New Chat', summary TEXT, -- AI 生成的摘要 provider_id TEXT NOT NULL, -- 使用的 Provider model_id TEXT NOT NULL, -- 使用的模型 system_prompt TEXT, -- 系统提示 mode TEXT NOT NULL DEFAULT 'direct', -- 'direct' | 'acp' acp_session_id TEXT, -- ACP 模式下的引擎会话 ID -- 分类 tags TEXT, -- JSON 数组 folder TEXT, -- 文件夹/分组 is_pinned INTEGER DEFAULT 0, -- 是否置顶 is_archived INTEGER DEFAULT 0, -- 是否归档 -- 统计 message_count INTEGER DEFAULT 0, total_tokens INTEGER DEFAULT 0, total_cost REAL DEFAULT 0, -- 估算费用 -- Channel 关联 channel_id TEXT, -- 来源 Channel (如飞书/钉钉) channel_meta TEXT, -- Channel 元数据 JSON -- 时间 created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL, last_message_at INTEGER ); CREATE INDEX idx_sessions_updated ON sessions_v2(updated_at DESC); CREATE INDEX idx_sessions_folder ON sessions_v2(folder); CREATE INDEX idx_sessions_channel ON sessions_v2(channel_id); ``` ### 3.2 消息表 (messages) ```sql CREATE TABLE messages_v2 ( id TEXT PRIMARY KEY, session_id TEXT NOT NULL REFERENCES sessions_v2(id) ON DELETE CASCADE, parent_id TEXT, -- 支持消息树(分支对话) role TEXT NOT NULL, -- 'system' | 'user' | 'assistant' | 'tool' content_type TEXT NOT NULL DEFAULT 'text', -- 'text' | 'multipart' -- 文本内容 text_content TEXT, -- 纯文本消息 -- 多部件内容 (multipart) parts TEXT, -- JSON: ContentPart[] -- Tool Call (assistant 发起) tool_calls TEXT, -- JSON: ToolCall[] -- Tool Result (tool role) tool_call_id TEXT, -- 对应的 tool_call ID tool_name TEXT, -- 工具名称 -- 元数据 model_id TEXT, -- 实际使用的模型 usage TEXT, -- JSON: { inputTokens, outputTokens } latency_ms INTEGER, -- 响应耗时 -- ACP 特有 acp_message_id TEXT, -- ACP 消息 ID acp_parts TEXT, -- ACP Part[] 原始数据 -- 状态 status TEXT DEFAULT 'completed', -- 'pending' | 'streaming' | 'completed' | 'error' | 'aborted' error TEXT, -- 错误信息 -- 时间 created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL ); CREATE INDEX idx_messages_session ON messages_v2(session_id, created_at); CREATE INDEX idx_messages_parent ON messages_v2(parent_id); ``` ### 3.3 ContentPart 类型 ```typescript type ContentPart = | { type: "text"; text: string } | { type: "image"; url?: string; base64?: string; mimeType: string } | { type: "file"; uri: string; mimeType?: string } | { type: "code"; language: string; code: string } | { type: "tool_use"; id: string; name: string; input: Record; } | { type: "tool_result"; toolUseId: string; content: string; isError?: boolean; } | { type: "thinking"; text: string } // reasoning / chain-of-thought | { type: "step_start"; stepId: string; title?: string } | { type: "step_finish"; stepId: string; result?: unknown } | { type: "diff"; filePath: string; hunks: unknown[] }; // 代码差异 ``` --- ## 四、ChatPipeline(对话管道) ### 4.1 管道流程 ``` 用户消息 │ ▼ ┌─── PreProcessor ───────────────────────────┐ │ 1. 注入 system prompt │ │ 2. 注入 Soul.md 上下文(自我进化) │ │ 3. 注入 Memory 相关记忆 │ │ 4. 注入 Tools schema(如果模型支持 FC) │ │ 5. 上下文窗口管理(截断/摘要) │ │ 6. Channel 适配(IM 消息格式转换) │ └────────────────────────────┬────────────────┘ ▼ ┌─── Provider Call ──────────────────────────┐ │ 直接模式: │ │ ChatClient.chatStream(messages) │ │ ACP 模式: │ │ UnifiedAgentService.chat(session, msg) │ └────────────────────────────┬────────────────┘ ▼ ┌─── PostProcessor ──────────────────────────┐ │ 1. Tool Call 处理(调用 ToolRegistry) │ │ 2. 权限检查(需要用户确认的操作) │ │ 3. 消息持久化到 messages_v2 │ │ 4. 更新会话统计(token/cost) │ │ 5. Channel 回复(如果来自 IM) │ └────────────────────────────┬────────────────┘ ▼ ┌─── MemoryEncoder ──────────────────────────┐ │ 1. 判断是否需要编码为记忆 │ │ 2. 提取 task/outcome/context │ │ 3. 异步写入 Memory 系统 │ └────────────────────────────────────────────┘ ``` ### 4.2 流式输出 ```typescript interface ChatStreamEvent { type: ChatStreamEventType; sessionId: string; messageId: string; timestamp: number; // 具体事件数据 text?: string; // 文本增量 toolCall?: { // Tool Call 开始 id: string; name: string; arguments: string; }; toolResult?: { // Tool 执行结果 toolCallId: string; output: string; isError: boolean; }; thinking?: string; // 推理过程 usage?: { // Token 统计 inputTokens: number; outputTokens: number; }; error?: string; // 错误 } type ChatStreamEventType = | "stream_start" | "text_delta" | "thinking_delta" | "tool_call_start" | "tool_call_delta" | "tool_result" | "usage" | "stream_end" | "error" | "aborted"; ``` ### 4.3 上下文窗口管理 ```typescript interface ContextWindowManager { /** 根据模型上下文窗口大小,智能截断历史消息 */ truncateHistory( messages: ChatMessage[], maxTokens: number, strategy: TruncationStrategy, ): ChatMessage[]; } type TruncationStrategy = | "sliding-window" // 保留最近 N 条 | "summarize-old" // 对旧消息生成摘要 | "smart-select"; // 智能选择重要消息 ``` --- ## 五、ChatEngine 接口 ```typescript class ChatEngine { private sessionMgr: SessionManager; private messageStore: MessageStore; private pipeline: ChatPipeline; private modelGateway: ModelGateway; private unifiedAgent: UnifiedAgentService; // ==================== 会话管理 ==================== /** 创建新会话 */ createSession(options: CreateSessionOptions): Promise; /** 获取会话列表(支持搜索/过滤/分页) */ listSessions( filter?: SessionFilter, ): Promise<{ sessions: SessionV2[]; total: number }>; /** 获取会话详情 */ getSession(id: string): Promise; /** 更新会话(标题/标签/文件夹等) */ updateSession(id: string, updates: Partial): Promise; /** 删除会话 */ deleteSession(id: string): Promise; /** 归档会话 */ archiveSession(id: string): Promise; /** 置顶/取消置顶 */ pinSession(id: string, pinned: boolean): Promise; /** 分支会话(从某条消息开始新分支) */ forkSession(id: string, messageId: string): Promise; // ==================== 消息管理 ==================== /** 获取会话消息列表 */ getMessages( sessionId: string, options?: MessageQueryOptions, ): Promise; /** 获取消息树(分支结构) */ getMessageTree(sessionId: string): Promise; // ==================== 对话 ==================== /** 发送消息并获取流式响应 */ chat(sessionId: string, message: UserInput): AsyncIterable; /** 中止当前生成 */ abort(sessionId: string): Promise; /** 重新生成最后一条回复 */ regenerate( sessionId: string, options?: RegenerateOptions, ): AsyncIterable; /** 编辑并重新发送用户消息 */ editAndResend( sessionId: string, messageId: string, newContent: string, ): AsyncIterable; // ==================== 搜索 ==================== /** 全文搜索消息内容 */ searchMessages( query: string, options?: SearchOptions, ): Promise; } interface CreateSessionOptions { title?: string; providerId?: string; // 默认使用全局默认 modelId?: string; systemPrompt?: string; mode?: "direct" | "acp"; tags?: string[]; folder?: string; } interface SessionFilter { search?: string; // 标题/摘要搜索 folder?: string; tags?: string[]; isArchived?: boolean; isPinned?: boolean; channelId?: string; dateRange?: { from: number; to: number }; limit?: number; offset?: number; sortBy?: "updated_at" | "created_at" | "last_message_at"; } interface UserInput { content: string | ContentPart[]; attachments?: { name: string; data: Buffer; mimeType: string }[]; } interface RegenerateOptions { modelId?: string; // 可以切换模型重新生成 providerId?: string; temperature?: number; } ``` --- ## 六、IPC 接口设计 ```typescript // Session 'chat:session:create' → SessionV2 'chat:session:list' → { sessions: SessionV2[]; total: number } 'chat:session:get' → SessionV2 'chat:session:update' → SessionV2 'chat:session:delete' → { success: boolean } 'chat:session:archive' → { success: boolean } 'chat:session:pin' → { success: boolean } 'chat:session:fork' → SessionV2 // Messages 'chat:messages:get' → MessageV2[] 'chat:messages:tree' → MessageTreeNode[] 'chat:messages:search' → SearchResult[] // Chat 'chat:send' → { messageId: string } // 异步启动 'chat:abort' → { success: boolean } 'chat:regenerate' → { messageId: string } 'chat:editResend' → { messageId: string } // Stream Events (Main → Renderer) 'chat:stream' Event: ChatStreamEvent ``` ### IPC 事件流 ``` Renderer Main Process │ │ │ chat:send(sessionId, input) │ │ ────────────────────────────────→│ │ │ │ ← { messageId } │ (立即返回) │ │ │ chat:stream event │ (持续推送) │ ←────────────────────────────── │ │ { type: 'text_delta', ... } │ │ ←────────────────────────────── │ │ { type: 'tool_call_start',..} │ │ ←────────────────────────────── │ │ { type: 'tool_result', ... } │ │ ←────────────────────────────── │ │ { type: 'text_delta', ... } │ │ ←────────────────────────────── │ │ { type: 'stream_end', ... } │ │ ←────────────────────────────── │ ``` --- ## 七、与 Qiming 服务器同步 ### 7.1 ConversationSyncAdapter ```typescript class ConversationSyncAdapter { constructor( private qimingClient: QimingApiClient, private chatEngine: ChatEngine, ) {} /** 从服务器拉取会话列表 */ async pullConversations(agentId: number): Promise { const serverConversations = await this.qimingClient.request< ConversationInfo[] >("POST", "/api/agent/conversation/list", { agentId }); let pulled = 0; for (const sc of serverConversations) { const exists = await this.chatEngine.getSession(String(sc.id)); if (!exists || sc.updatedAt > exists.updatedAt) { const messages = await this.qimingClient.request( "POST", "/api/agent/conversation/message/list", { conversationId: sc.id }, ); await this.chatEngine.importSession(sc, messages); pulled++; } } return { success: true, pulled, pushed: 0, conflicts: [] }; } /** 推送本地会话到服务器 */ async pushConversations(): Promise { const localOnly = await this.chatEngine.getUnsynced(); let pushed = 0; for (const session of localOnly) { const messages = await this.chatEngine.getMessages(session.id); await this.qimingClient.request("POST", "/api/agent/conversation/create", { agentId: session.agentId, title: session.title, messages: messages.map((m) => ({ role: m.role, content: m.text_content, })), }); await this.chatEngine.markSynced(session.id); pushed++; } return { success: true, pulled: 0, pushed, conflicts: [] }; } } ``` ### 7.2 直接转发到服务器 当配置的服务 URL(如 `https://testagent.xspaceagi.com`)可达时, Chat 可以直接转发到服务器的 `/api/computer/chat` + SSE 端点: ```typescript async function* chatViaServer( serviceUrl: string, message: string, sessionId: string, ): AsyncIterable { // 1. POST 到服务器 await fetch(`${serviceUrl}/api/computer/chat`, { method: "POST", body: JSON.stringify({ user_id: userId, prompt: message, session_id: sessionId, }), }); // 2. 建立 SSE 连接接收进度 const eventSource = new EventSource( `${serviceUrl}/api/computer/progress/${sessionId}`, ); // 3. 转换并 yield 事件 for await (const event of eventSource) { yield mapServerEventToLocal(event); } } ``` ### 7.3 服务器侧完整 Conversation API 服务器实际提供的 Conversation API 比基本同步所需更丰富,客户端可按需集成: | 端点 | 能力 | 客户端集成优先级 | | ----------------------------------------------- | -------- | ---------------- | | `POST /api/agent/conversation/create` | 创建会话 | P0 | | `POST /api/agent/conversation/list` | 会话列表 | P0 | | `GET /api/agent/conversation/{id}` | 查询会话 | P0 | | `POST /api/agent/conversation/message/list` | 消息列表 | P0 | | `POST /api/agent/conversation/update` | 更新会话 | P1 | | `POST /api/agent/conversation/delete/{id}` | 删除会话 | P1 | | `POST /api/agent/conversation/chat/stop` | 停止会话 | P0 | | `POST /api/agent/conversation/share` | 分享会话 | P2 | | `POST /api/agent/conversation/chat/suggest` | AI 建议 | P2 | | `POST /api/agent/conversation/chat/page/result` | 分页结果 | P2 | ### 7.4 V1 兼容 保留 `computerServer.ts` 的 `/computer/chat` 端点,内部改为调用 `ChatEngine`。 > ❗ 注意:V1 本地数据库(`db.ts`)仅有 `settings` 表,无 sessions/messages 数据可迁移。 > 历史会话数据需从 ACP SDK 的内部存储导出(通过 `UnifiedAgentService.listSessions()`), > 或直接从服务器拉取历史会话,不做本地迁移。 --- ## 相关文档 - [总体架构](./01-ARCHITECTURE.md) - [模型配置](./02-MODEL-CONFIG.md) - [Skills & MCP](./03-SKILLS-MCP.md) - [Channel 接入](./05-CHANNELS.md)