Files
qiming/qimingclaw/crates/agent-electron-client/docs/v2/04-SESSION-CHAT.md

608 lines
22 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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 层面的 sessioncreate/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-engineclaude-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<string, unknown>;
}
| {
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<SessionV2>;
/** 获取会话列表(支持搜索/过滤/分页) */
listSessions(
filter?: SessionFilter,
): Promise<{ sessions: SessionV2[]; total: number }>;
/** 获取会话详情 */
getSession(id: string): Promise<SessionV2>;
/** 更新会话(标题/标签/文件夹等) */
updateSession(id: string, updates: Partial<SessionV2>): Promise<SessionV2>;
/** 删除会话 */
deleteSession(id: string): Promise<void>;
/** 归档会话 */
archiveSession(id: string): Promise<void>;
/** 置顶/取消置顶 */
pinSession(id: string, pinned: boolean): Promise<void>;
/** 分支会话(从某条消息开始新分支) */
forkSession(id: string, messageId: string): Promise<SessionV2>;
// ==================== 消息管理 ====================
/** 获取会话消息列表 */
getMessages(
sessionId: string,
options?: MessageQueryOptions,
): Promise<MessageV2[]>;
/** 获取消息树(分支结构) */
getMessageTree(sessionId: string): Promise<MessageTreeNode[]>;
// ==================== 对话 ====================
/** 发送消息并获取流式响应 */
chat(sessionId: string, message: UserInput): AsyncIterable<ChatStreamEvent>;
/** 中止当前生成 */
abort(sessionId: string): Promise<void>;
/** 重新生成最后一条回复 */
regenerate(
sessionId: string,
options?: RegenerateOptions,
): AsyncIterable<ChatStreamEvent>;
/** 编辑并重新发送用户消息 */
editAndResend(
sessionId: string,
messageId: string,
newContent: string,
): AsyncIterable<ChatStreamEvent>;
// ==================== 搜索 ====================
/** 全文搜索消息内容 */
searchMessages(
query: string,
options?: SearchOptions,
): Promise<SearchResult[]>;
}
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<SyncResult> {
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<MessageInfo[]>(
"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<SyncResult> {
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<ChatStreamEvent> {
// 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)