refactor(storage): 采用 OpenCode 风格三层存储结构
重构消息存储系统,从"每条消息一个文件"改为分层存储:
- Session → Message → Parts 三层结构
- 12 种 Part 类型(TextPart, ToolPart, ReasoningPart 等)
- ToolPart 状态机(pending → running → completed/error)
- 通用 Storage API(read/write/list/remove)
新增文件:
- parts.ts: Part 类型定义(Zod schema)
- message.ts: MessageInfo 类型定义
- id.ts: ID 生成器
- storage/: 分层存储实现
删除旧文件:
- storage.ts, types.ts, migration.ts
存储路径:
~/.local/share/ai-assist/
├── session/{projectId}/{sessionId}.json
├── message/{sessionId}/{messageId}.json
├── part/{messageId}/{partId}.json
└── todo/{sessionId}.json
This commit is contained in:
@@ -115,17 +115,8 @@ sessionsRouter.get('/:id/messages', async (c) => {
|
||||
);
|
||||
}
|
||||
|
||||
// 从 Core Storage 读取消息
|
||||
const storage = sessionManager.getStorage();
|
||||
if (!storage) {
|
||||
return c.json({
|
||||
success: true,
|
||||
data: [],
|
||||
});
|
||||
}
|
||||
|
||||
const projectId = sessionManager.getProjectId(id);
|
||||
const sessionData = await storage.loadSession(projectId, id);
|
||||
// 从 Core 存储读取消息
|
||||
const sessionData = await sessionManager.loadSessionData(id);
|
||||
|
||||
if (!sessionData) {
|
||||
return c.json({
|
||||
|
||||
@@ -41,15 +41,16 @@ interface SessionData {
|
||||
todos: unknown[];
|
||||
}
|
||||
|
||||
interface SessionStorageInterface {
|
||||
ensureDir(): Promise<void>;
|
||||
generateSessionId(): string;
|
||||
getOrCreateProject(workdir: string): Promise<ProjectMetadata>;
|
||||
interface SessionManagerInterface {
|
||||
init(workdir: string): Promise<SessionData>;
|
||||
getSession(): SessionData | null;
|
||||
getProject(): ProjectMetadata | null;
|
||||
listSessions(): Promise<SessionSummary[]>;
|
||||
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>;
|
||||
deleteSession(sessionId: string): Promise<boolean>;
|
||||
newSession(workdir?: string): Promise<SessionData>;
|
||||
restoreSession(sessionId: string): Promise<SessionData | null>;
|
||||
getSessionId(): string | undefined;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -59,15 +60,15 @@ interface SessionStorageInterface {
|
||||
export class SessionManager {
|
||||
private sessions: Map<string, Session> = new Map();
|
||||
private sessionProjects: Map<string, string> = new Map(); // sessionId -> projectId
|
||||
private storage: SessionStorageInterface | null = null;
|
||||
private coreManager: SessionManagerInterface | null = null;
|
||||
private currentProject: ProjectMetadata | null = null;
|
||||
private initialized = false;
|
||||
|
||||
/**
|
||||
* 获取 storage 实例(供外部使用)
|
||||
* 获取 Core SessionManager 实例(供外部使用)
|
||||
*/
|
||||
getStorage(): SessionStorageInterface | null {
|
||||
return this.storage;
|
||||
getCoreManager(): SessionManagerInterface | null {
|
||||
return this.coreManager;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,7 +79,7 @@ export class SessionManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化:加载 Core 模块的 SessionStorage 并恢复已有 sessions
|
||||
* 初始化:加载 Core 模块的 SessionManager 并恢复已有 sessions
|
||||
*/
|
||||
async init(): Promise<void> {
|
||||
if (this.initialized) return;
|
||||
@@ -87,39 +88,35 @@ export class SessionManager {
|
||||
// 动态导入 Core 模块,避免构建时依赖
|
||||
const corePath = '@ai-assistant/core';
|
||||
const core = (await import(/* webpackIgnore: true */ corePath)) as {
|
||||
SessionStorage: new () => SessionStorageInterface;
|
||||
SessionManager: new (storageDir?: string) => SessionManagerInterface;
|
||||
};
|
||||
|
||||
this.storage = new core.SessionStorage();
|
||||
await this.storage.ensureDir();
|
||||
this.coreManager = new core.SessionManager();
|
||||
await this.coreManager.init(process.cwd());
|
||||
|
||||
// 获取当前工作目录的项目
|
||||
this.currentProject = await this.storage.getOrCreateProject(process.cwd());
|
||||
this.currentProject = this.coreManager.getProject();
|
||||
|
||||
// 加载已持久化的 sessions(所有项目)
|
||||
const summaries = await this.storage.listAllSessions();
|
||||
const summaries = await this.coreManager.listAllSessions();
|
||||
console.log(`[SessionManager] Found ${summaries.length} persisted sessions`);
|
||||
|
||||
for (const summary of summaries) {
|
||||
// 需要找到 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,
|
||||
name: sessionData.title,
|
||||
workdir: sessionData.workdir,
|
||||
createdAt: sessionData.createdAt,
|
||||
updatedAt: sessionData.updatedAt,
|
||||
id: summary.id,
|
||||
name: summary.title,
|
||||
workdir: summary.workdir,
|
||||
createdAt: summary.createdAt,
|
||||
updatedAt: summary.updatedAt,
|
||||
status: 'idle',
|
||||
messageCount: sessionData.messages.length,
|
||||
messageCount: summary.messageCount,
|
||||
};
|
||||
|
||||
this.sessions.set(session.id, session);
|
||||
// 假设所有会话都属于当前项目(简化处理)
|
||||
if (this.currentProject) {
|
||||
this.sessionProjects.set(session.id, this.currentProject.id);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[SessionManager] Loaded ${this.sessions.size} sessions from storage`);
|
||||
@@ -130,36 +127,6 @@ export class SessionManager {
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 持久化单个 session 的元数据
|
||||
* 注意:消息存储由 Core Agent 负责,这里只更新会话元数据
|
||||
*/
|
||||
private async persistMetadata(sessionId: string): Promise<void> {
|
||||
if (!this.storage) return;
|
||||
|
||||
const session = this.sessions.get(sessionId);
|
||||
if (!session) return;
|
||||
|
||||
const projectId = this.sessionProjects.get(sessionId) || this.currentProject?.id || 'default';
|
||||
|
||||
// 先加载现有的 session 数据(保留消息)
|
||||
const existingData = await this.storage.loadSession(projectId, sessionId);
|
||||
|
||||
const sessionData: SessionData = {
|
||||
id: session.id,
|
||||
projectId,
|
||||
createdAt: session.createdAt,
|
||||
updatedAt: session.updatedAt,
|
||||
workdir: session.workdir,
|
||||
title: session.name,
|
||||
messages: existingData?.messages || [],
|
||||
discoveredTools: existingData?.discoveredTools || [],
|
||||
todos: existingData?.todos || [],
|
||||
};
|
||||
|
||||
await this.storage.saveSession(sessionData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建新会话
|
||||
*/
|
||||
@@ -167,15 +134,20 @@ export class SessionManager {
|
||||
const now = new Date().toISOString();
|
||||
const workdir = input.workdir || process.cwd();
|
||||
|
||||
// 如果指定了不同的工作目录,获取对应项目
|
||||
let sessionId: string;
|
||||
let projectId = this.currentProject?.id || 'default';
|
||||
if (this.storage && workdir !== this.currentProject?.workdir) {
|
||||
const project = await this.storage.getOrCreateProject(workdir);
|
||||
projectId = project.id;
|
||||
|
||||
if (this.coreManager) {
|
||||
// 使用 Core SessionManager 创建会话
|
||||
const sessionData = await this.coreManager.newSession(workdir);
|
||||
sessionId = sessionData.id;
|
||||
projectId = sessionData.projectId;
|
||||
} else {
|
||||
sessionId = uuidv4();
|
||||
}
|
||||
|
||||
const session: Session = {
|
||||
id: this.storage?.generateSessionId() || uuidv4(),
|
||||
id: sessionId,
|
||||
name: input.name,
|
||||
workdir,
|
||||
createdAt: now,
|
||||
@@ -187,9 +159,6 @@ export class SessionManager {
|
||||
this.sessions.set(session.id, session);
|
||||
this.sessionProjects.set(session.id, projectId);
|
||||
|
||||
// 持久化空会话
|
||||
await this.persistMetadata(session.id);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
@@ -216,9 +185,8 @@ export class SessionManager {
|
||||
const deleted = this.sessions.delete(id);
|
||||
|
||||
// 从存储中删除
|
||||
if (deleted && this.storage) {
|
||||
const projectId = this.sessionProjects.get(id) || this.currentProject?.id || 'default';
|
||||
await this.storage.deleteSession(projectId, id);
|
||||
if (deleted && this.coreManager) {
|
||||
await this.coreManager.deleteSession(id);
|
||||
this.sessionProjects.delete(id);
|
||||
}
|
||||
|
||||
@@ -259,9 +227,6 @@ export class SessionManager {
|
||||
session.name = name;
|
||||
session.updatedAt = new Date().toISOString();
|
||||
|
||||
// 持久化元数据
|
||||
await this.persistMetadata(sessionId);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
@@ -278,6 +243,17 @@ export class SessionManager {
|
||||
exists(id: string): boolean {
|
||||
return this.sessions.has(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载会话数据(从 Core 存储)
|
||||
*/
|
||||
async loadSessionData(sessionId: string): Promise<SessionData | null> {
|
||||
if (!this.coreManager) return null;
|
||||
|
||||
// 先恢复会话
|
||||
const data = await this.coreManager.restoreSession(sessionId);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
// 单例
|
||||
|
||||
Reference in New Issue
Block a user