feat: 实现 Workspace 对话持久化

This commit is contained in:
李岩岩 2026-07-30 17:44:31 +08:00 committed by liyy
parent d87dad27db
commit a801f40bdc

View File

@ -0,0 +1,97 @@
import { createHash } from "node:crypto";
import { appendFile, mkdir, readFile, writeFile } from "node:fs/promises";
import { homedir } from "node:os";
import { join, resolve } from "node:path";
import type { Extension } from "../../../kernel";
import { ExtensionId, Hook } from "../../catalog";
export type MessageRole = "system" | "user" | "assistant";
export interface ChatMessage {
role: MessageRole;
content: string;
createdAt: string;
}
export interface WorkspaceService {
home: string;
projectPath: string;
workspaceId: string;
conversationId: string;
messages(): Promise<ChatMessage[]>;
append(role: MessageRole, content: string): Promise<ChatMessage>;
}
export interface WorkspaceOptions {
home?: string;
projectPath?: string;
conversationId?: string;
}
export function createWorkspaceExtension(options: WorkspaceOptions = {}): Extension {
const home = resolve(
options.home ?? process.env.LLM_TO_AGENT_HOME ?? join(homedir(), ".llm-to-agent"),
);
const projectPath = resolve(options.projectPath ?? process.cwd());
const workspaceId = createHash("sha256").update(projectPath).digest("hex").slice(0, 16);
const conversationId = options.conversationId ?? "default";
const workspaceDirectory = join(home, "workspaces", workspaceId);
const conversationDirectory = join(workspaceDirectory, "conversations", conversationId);
const messagesFile = join(conversationDirectory, "messages.jsonl");
const workspace: WorkspaceService = {
home,
projectPath,
workspaceId,
conversationId,
async messages() {
try {
const content = await readFile(messagesFile, "utf8");
return content
.split("\n")
.filter(Boolean)
.map((line) => JSON.parse(line) as ChatMessage);
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "ENOENT") return [];
throw error;
}
},
async append(role, content) {
const message = {
role,
content,
createdAt: new Date().toISOString(),
};
await appendFile(messagesFile, `${JSON.stringify(message)}\n`, "utf8");
return message;
},
};
return {
id: ExtensionId.Workspace,
async setup(context) {
await mkdir(conversationDirectory, { recursive: true });
await writeFile(
join(workspaceDirectory, "workspace.json"),
`${JSON.stringify(
{
id: workspaceId,
kind: "linked",
projectPath,
activeConversationId: conversationId,
},
null,
2,
)}\n`,
"utf8",
);
context.add(Hook.Workspace, workspace);
},
};
}