From a801f40bdcb9da1642bd77a6f6bdb6e88c3f3c60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E5=B2=A9=E5=B2=A9?= Date: Thu, 30 Jul 2026 17:44:31 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=20Workspace=20?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D=E6=8C=81=E4=B9=85=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/extensions/shared/workspace/index.ts | 97 ++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 src/extensions/shared/workspace/index.ts diff --git a/src/extensions/shared/workspace/index.ts b/src/extensions/shared/workspace/index.ts new file mode 100644 index 0000000..a21a9fb --- /dev/null +++ b/src/extensions/shared/workspace/index.ts @@ -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; + append(role: MessageRole, content: string): Promise; +} + +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); + }, + }; +}