66ad1a1ec9
将三个超过 700 行的大型文件重构为模块化架构: Agent (1033 → ~400 行): - agent-tool-executor: 工具获取、过滤和执行 - agent-message-handler: 消息构建、流式处理 - agent-mode-manager: 模式切换和权限检查 - agent-vision-handler: 视觉处理委托 CheckpointManager (1015 → ~620 行): - checkpoint-store: 检查点 CRUD 操作 - checkpoint-rollback: 回滚和撤销操作 - checkpoint-session: 会话跟踪 - checkpoint-events: 事件发射系统 SessionManager (768 → 356 行): - message-converter: Part ↔ ModelMessage 转换 - session-store: 会话 CRUD 操作 - project-manager: 项目管理 - session-auto-save: 自动保存功能 重构原则: 单一职责、编排器模式、向后兼容 API
237 lines
5.9 KiB
TypeScript
237 lines
5.9 KiB
TypeScript
/**
|
|
* 会话存储管理
|
|
* 负责会话的 CRUD 操作
|
|
*/
|
|
|
|
import type { ModelMessage } from 'ai';
|
|
import { SessionStorage, MessageStorage, PartStorage, TodoStorage } from './storage/index.js';
|
|
import type { SessionInfo, TodoItem } from './storage/index.js';
|
|
import { MessageConverter } from './message-converter.js';
|
|
import { generateSessionId } from './id.js';
|
|
|
|
/**
|
|
* 会话摘要(用于列表展示)
|
|
*/
|
|
export interface SessionSummary {
|
|
id: string;
|
|
title: string;
|
|
workdir: string;
|
|
messageCount: number;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
/**
|
|
* 运行时会话数据
|
|
*/
|
|
export interface SessionData {
|
|
id: string;
|
|
projectId: string;
|
|
parentId?: string;
|
|
agentName?: string;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
workdir: string;
|
|
title?: string;
|
|
messages: ModelMessage[];
|
|
discoveredTools: string[];
|
|
todos: TodoItem[];
|
|
}
|
|
|
|
/**
|
|
* 会话存储管理
|
|
*/
|
|
export class SessionStore {
|
|
private messageConverter: MessageConverter;
|
|
|
|
constructor() {
|
|
this.messageConverter = new MessageConverter();
|
|
}
|
|
|
|
/**
|
|
* 创建新会话
|
|
*/
|
|
async create(projectId: string, workdir: string): Promise<SessionData> {
|
|
const sessionInfo = await SessionStorage.create(projectId, workdir);
|
|
|
|
return {
|
|
id: sessionInfo.id,
|
|
projectId: sessionInfo.projectId,
|
|
createdAt: new Date(sessionInfo.createdAt).toISOString(),
|
|
updatedAt: new Date(sessionInfo.updatedAt).toISOString(),
|
|
workdir: sessionInfo.workdir,
|
|
title: sessionInfo.title,
|
|
messages: [],
|
|
discoveredTools: sessionInfo.discoveredTools,
|
|
todos: [],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 加载会话(从存储重建)
|
|
*/
|
|
async load(projectId: string, sessionId: string): Promise<SessionData | null> {
|
|
const sessionInfo = await SessionStorage.get(projectId, sessionId);
|
|
if (!sessionInfo) return null;
|
|
|
|
// 加载消息
|
|
const messages = await this.messageConverter.loadFromStorage(sessionId);
|
|
|
|
// 加载 todos
|
|
const todoList = await TodoStorage.get(sessionId);
|
|
|
|
return {
|
|
id: sessionInfo.id,
|
|
projectId: sessionInfo.projectId,
|
|
parentId: sessionInfo.parentId,
|
|
agentName: sessionInfo.agentName,
|
|
createdAt: new Date(sessionInfo.createdAt).toISOString(),
|
|
updatedAt: new Date(sessionInfo.updatedAt).toISOString(),
|
|
workdir: sessionInfo.workdir,
|
|
title: sessionInfo.title,
|
|
messages,
|
|
discoveredTools: sessionInfo.discoveredTools,
|
|
todos: todoList?.items || [],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 保存会话信息
|
|
*/
|
|
async save(session: SessionData): Promise<void> {
|
|
const sessionInfo: SessionInfo = {
|
|
id: session.id,
|
|
projectId: session.projectId,
|
|
parentId: session.parentId,
|
|
agentName: session.agentName,
|
|
createdAt: new Date(session.createdAt).getTime(),
|
|
updatedAt: Date.now(),
|
|
workdir: session.workdir,
|
|
title: session.title,
|
|
discoveredTools: session.discoveredTools,
|
|
stats: {
|
|
messageCount: session.messages.length,
|
|
inputTokens: 0,
|
|
outputTokens: 0,
|
|
},
|
|
};
|
|
|
|
await SessionStorage.save(sessionInfo);
|
|
}
|
|
|
|
/**
|
|
* 同步消息到存储
|
|
*/
|
|
async syncMessages(sessionId: string, messages: ModelMessage[]): Promise<void> {
|
|
await this.messageConverter.syncToStorage(sessionId, messages);
|
|
}
|
|
|
|
/**
|
|
* 更新待办事项
|
|
*/
|
|
async setTodos(
|
|
sessionId: string,
|
|
todos: Array<{ content: string; status: 'pending' | 'in_progress' | 'completed' }>
|
|
): Promise<TodoItem[]> {
|
|
const todoList = await TodoStorage.replace(sessionId, todos);
|
|
return todoList.items;
|
|
}
|
|
|
|
/**
|
|
* 列出项目的所有会话
|
|
*/
|
|
async listByProject(projectId: string): Promise<SessionSummary[]> {
|
|
const sessions = await SessionStorage.listByProject(projectId);
|
|
return this.toSummaries(sessions);
|
|
}
|
|
|
|
/**
|
|
* 列出所有会话
|
|
*/
|
|
async listAll(): Promise<SessionSummary[]> {
|
|
const sessions = await SessionStorage.listAll();
|
|
return this.toSummaries(sessions);
|
|
}
|
|
|
|
/**
|
|
* 删除会话
|
|
*/
|
|
async delete(projectId: string, sessionId: string): Promise<boolean> {
|
|
try {
|
|
// 删除会话的消息和 Parts
|
|
const messageInfos = await MessageStorage.listBySession(sessionId);
|
|
for (const msg of messageInfos) {
|
|
await PartStorage.removeByMessage(msg.id);
|
|
}
|
|
await MessageStorage.removeBySession(sessionId);
|
|
|
|
// 删除 todos
|
|
await TodoStorage.removeBySession(sessionId);
|
|
|
|
// 删除会话信息
|
|
await SessionStorage.remove(projectId, sessionId);
|
|
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 创建子会话(用于 Task 工具)
|
|
*/
|
|
createChildSession(
|
|
projectId: string,
|
|
parentId: string,
|
|
agentName: string,
|
|
workdir: string,
|
|
title?: string
|
|
): SessionData {
|
|
return {
|
|
id: generateSessionId(),
|
|
projectId,
|
|
parentId,
|
|
agentName,
|
|
createdAt: new Date().toISOString(),
|
|
updatedAt: new Date().toISOString(),
|
|
workdir,
|
|
title: title || `子任务 (@${agentName})`,
|
|
messages: [],
|
|
discoveredTools: [],
|
|
todos: [],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 保存子会话
|
|
*/
|
|
async saveChildSession(session: SessionData): Promise<void> {
|
|
const sessionInfo: SessionInfo = {
|
|
id: session.id,
|
|
projectId: session.projectId,
|
|
parentId: session.parentId,
|
|
agentName: session.agentName,
|
|
createdAt: new Date(session.createdAt).getTime(),
|
|
updatedAt: Date.now(),
|
|
workdir: session.workdir,
|
|
title: session.title,
|
|
discoveredTools: session.discoveredTools,
|
|
};
|
|
await SessionStorage.save(sessionInfo);
|
|
}
|
|
|
|
/**
|
|
* 转换为摘要列表
|
|
*/
|
|
private toSummaries(sessions: SessionInfo[]): SessionSummary[] {
|
|
return sessions.map((s) => ({
|
|
id: s.id,
|
|
title: s.title || `会话 ${s.id}`,
|
|
workdir: s.workdir,
|
|
messageCount: s.stats?.messageCount || 0,
|
|
createdAt: new Date(s.createdAt).toISOString(),
|
|
updatedAt: new Date(s.updatedAt).toISOString(),
|
|
}));
|
|
}
|
|
}
|