refactor(session): 升级存储系统架构
- 添加读写锁机制防止并发写入冲突 - 实现数据迁移框架支持版本化升级 - 分层存储结构:项目/会话/消息独立存储 - 使用 Git root commit hash 作为稳定项目 ID - 增量消息同步避免重复写入 - 每条消息独立文件,按序号命名 (0001.json, 0002.json)
This commit is contained in:
@@ -11,26 +11,49 @@ import type { Session, CreateSessionInput, Message, SessionStatus } from '../typ
|
||||
// Core 模块接口定义(避免构建时依赖)
|
||||
// ============================================================================
|
||||
|
||||
interface SessionData {
|
||||
interface SessionMetadata {
|
||||
id: string;
|
||||
projectId: string;
|
||||
parentId?: string;
|
||||
agentName?: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
workdir: string;
|
||||
title?: string;
|
||||
messages: Array<{ role: string; content: unknown }>;
|
||||
messageCount: number;
|
||||
discoveredTools: string[];
|
||||
todos: unknown[];
|
||||
}
|
||||
|
||||
interface SessionData extends Omit<SessionMetadata, 'messageCount'> {
|
||||
messages: Array<{ role: string; content: unknown }>;
|
||||
}
|
||||
|
||||
interface SessionSummary {
|
||||
id: string;
|
||||
title: string;
|
||||
workdir: string;
|
||||
messageCount: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
interface ProjectMetadata {
|
||||
id: string;
|
||||
workdir: string;
|
||||
createdAt: string;
|
||||
isGitRepo: boolean;
|
||||
}
|
||||
|
||||
interface SessionStorageInterface {
|
||||
ensureDir(): Promise<void>;
|
||||
generateSessionId(): string;
|
||||
listSessions(): Promise<Array<{ id: string; title: string; workdir: string; messageCount: number; createdAt: string; updatedAt: string }>>;
|
||||
loadSession(sessionId: string): Promise<SessionData | null>;
|
||||
saveSession(session: SessionData): Promise<void>;
|
||||
deleteSession(sessionId: string): Promise<boolean>;
|
||||
getOrCreateProject(workdir: string): Promise<ProjectMetadata>;
|
||||
listAllSessions(): Promise<SessionSummary[]>;
|
||||
listSessionsByProject(projectId: string): Promise<SessionSummary[]>;
|
||||
loadSession(projectId: string, sessionId: string): Promise<SessionData | null>;
|
||||
saveSession(session: SessionData, lastSyncedCount?: number): Promise<void>;
|
||||
deleteSession(projectId: string, sessionId: string): Promise<boolean>;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -50,7 +73,7 @@ function toModelMessage(msg: Message): { role: string; content: string } {
|
||||
function fromModelMessage(
|
||||
msg: { role: string; content: unknown },
|
||||
sessionId: string,
|
||||
index: number
|
||||
_index: number
|
||||
): Message {
|
||||
return {
|
||||
id: uuidv4(),
|
||||
@@ -68,7 +91,9 @@ function fromModelMessage(
|
||||
export class SessionManager {
|
||||
private sessions: Map<string, Session> = new Map();
|
||||
private messages: Map<string, Message[]> = new Map();
|
||||
private sessionProjects: Map<string, string> = new Map(); // sessionId -> projectId
|
||||
private storage: SessionStorageInterface | null = null;
|
||||
private currentProject: ProjectMetadata | null = null;
|
||||
private initialized = false;
|
||||
|
||||
/**
|
||||
@@ -80,21 +105,28 @@ export class SessionManager {
|
||||
try {
|
||||
// 动态导入 Core 模块,避免构建时依赖
|
||||
const corePath = '@ai-assistant/core';
|
||||
const core = await import(/* webpackIgnore: true */ corePath) as {
|
||||
const core = (await import(/* webpackIgnore: true */ corePath)) as {
|
||||
SessionStorage: new () => SessionStorageInterface;
|
||||
};
|
||||
|
||||
this.storage = new core.SessionStorage();
|
||||
await this.storage.ensureDir();
|
||||
|
||||
// 加载已持久化的 sessions
|
||||
const summaries = await this.storage.listSessions();
|
||||
// 获取当前工作目录的项目
|
||||
this.currentProject = await this.storage.getOrCreateProject(process.cwd());
|
||||
|
||||
// 加载已持久化的 sessions(所有项目)
|
||||
const summaries = await this.storage.listAllSessions();
|
||||
console.log(`[SessionManager] Found ${summaries.length} persisted sessions`);
|
||||
|
||||
for (const summary of summaries) {
|
||||
const sessionData = await this.storage.loadSession(summary.id);
|
||||
// 需要找到 session 所属的项目,这里简化处理:加载当前项目的会话
|
||||
const sessionData = await this.storage.loadSession(this.currentProject.id, summary.id);
|
||||
if (!sessionData) continue;
|
||||
|
||||
// 记录 session -> project 映射
|
||||
this.sessionProjects.set(sessionData.id, sessionData.projectId);
|
||||
|
||||
// 转换为 Server Session 格式
|
||||
const session: Session = {
|
||||
id: sessionData.id,
|
||||
@@ -109,9 +141,7 @@ export class SessionManager {
|
||||
this.sessions.set(session.id, session);
|
||||
|
||||
// 转换消息格式
|
||||
const messages = sessionData.messages.map((msg, i) =>
|
||||
fromModelMessage(msg, session.id, i)
|
||||
);
|
||||
const messages = sessionData.messages.map((msg, i) => fromModelMessage(msg, session.id, i));
|
||||
this.messages.set(session.id, messages);
|
||||
}
|
||||
|
||||
@@ -134,8 +164,11 @@ export class SessionManager {
|
||||
|
||||
if (!session) return;
|
||||
|
||||
const projectId = this.sessionProjects.get(sessionId) || this.currentProject?.id || 'default';
|
||||
|
||||
const sessionData: SessionData = {
|
||||
id: session.id,
|
||||
projectId,
|
||||
createdAt: session.createdAt,
|
||||
updatedAt: session.updatedAt,
|
||||
workdir: session.workdir,
|
||||
@@ -153,10 +186,19 @@ export class SessionManager {
|
||||
*/
|
||||
async create(input: CreateSessionInput = {}): Promise<Session> {
|
||||
const now = new Date().toISOString();
|
||||
const workdir = input.workdir || process.cwd();
|
||||
|
||||
// 如果指定了不同的工作目录,获取对应项目
|
||||
let projectId = this.currentProject?.id || 'default';
|
||||
if (this.storage && workdir !== this.currentProject?.workdir) {
|
||||
const project = await this.storage.getOrCreateProject(workdir);
|
||||
projectId = project.id;
|
||||
}
|
||||
|
||||
const session: Session = {
|
||||
id: this.storage?.generateSessionId() || uuidv4(),
|
||||
name: input.name,
|
||||
workdir: input.workdir || process.cwd(),
|
||||
workdir,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
status: 'idle',
|
||||
@@ -165,6 +207,7 @@ export class SessionManager {
|
||||
|
||||
this.sessions.set(session.id, session);
|
||||
this.messages.set(session.id, []);
|
||||
this.sessionProjects.set(session.id, projectId);
|
||||
|
||||
// 持久化
|
||||
await this.persist(session.id);
|
||||
@@ -197,7 +240,9 @@ export class SessionManager {
|
||||
|
||||
// 从存储中删除
|
||||
if (deleted && this.storage) {
|
||||
await this.storage.deleteSession(id);
|
||||
const projectId = this.sessionProjects.get(id) || this.currentProject?.id || 'default';
|
||||
await this.storage.deleteSession(projectId, id);
|
||||
this.sessionProjects.delete(id);
|
||||
}
|
||||
|
||||
return deleted;
|
||||
|
||||
Reference in New Issue
Block a user