From 9d4e5486e967a79a4b3ca7583e4224f5a9ed1747 Mon Sep 17 00:00:00 2001 From: Huarch Date: Sun, 7 Jun 2026 17:07:14 +0800 Subject: [PATCH] refactor: keep runtime context in memory --- .opencode/tools/memory_manager.ts | 98 +++++---- .opencode/tools/skill_manager.ts | 241 ++++++++++++--------- src/chat/sessionBridge.ts | 12 +- src/config.ts | 2 - src/learning/orchestrator.ts | 19 +- src/runtime/sessionContext.ts | 27 +++ src/server.ts | 10 +- src/sessions/runtimeContextStore.ts | 46 ---- tests/opencode/skillManagerTool.test.ts | 14 +- tests/runtime/sessionContext.test.ts | 32 +++ tests/sessions/runtimeContextStore.test.ts | 41 ---- 11 files changed, 273 insertions(+), 269 deletions(-) create mode 100644 src/runtime/sessionContext.ts delete mode 100644 src/sessions/runtimeContextStore.ts create mode 100644 tests/runtime/sessionContext.test.ts delete mode 100644 tests/sessions/runtimeContextStore.test.ts diff --git a/.opencode/tools/memory_manager.ts b/.opencode/tools/memory_manager.ts index db719e8..6de8faf 100644 --- a/.opencode/tools/memory_manager.ts +++ b/.opencode/tools/memory_manager.ts @@ -1,13 +1,12 @@ import { tool } from "@opencode-ai/plugin"; import { MemoryStore } from "../../src/memory/store.js"; -import { SessionRuntimeContextStore } from "../../src/sessions/runtimeContextStore.js"; +import { + getRuntimeSessionContext, + setRuntimeSessionContext, +} from "../../src/runtime/sessionContext.js"; const memoryStore = new MemoryStore(); -const toolContextStore = new SessionRuntimeContextStore(); -const initializePromise = Promise.all([ - memoryStore.initialize(), - toolContextStore.initialize(), -]); +const initializePromise = memoryStore.initialize(); export default tool({ description: @@ -37,7 +36,7 @@ export default tool({ }, async execute(args, context) { await initializePromise; - const sessionContext = await toolContextStore.read(context.sessionID); + const sessionContext = getRuntimeSessionContext(context.sessionID); if (!sessionContext) { throw new Error(`session context not found for ${context.sessionID}`); } @@ -57,10 +56,10 @@ export default tool({ } if (sessionContext.allowLearningWrite === false && args.action !== "list") { return JSON.stringify({ - ok: true, - kind: "memory", - decision: "rejected", - detail: "memory writes are disabled for this session", + ok: true, + kind: "memory", + decision: "rejected", + detail: "memory writes are disabled for this session", }); } @@ -71,17 +70,17 @@ export default tool({ ...(sessionContext.memoryListReadScopes ?? {}), [scope]: true, }; - await toolContextStore.write({ + setRuntimeSessionContext({ ...sessionContext, memoryListReadScopes: readScopes, }); return JSON.stringify({ - ok: true, - kind: "memory", - decision: "accepted", - detail: "memory listed", - items: await memoryStore.list(scope, scopeKey), - target: scope, + ok: true, + kind: "memory", + decision: "accepted", + detail: "memory listed", + items: await memoryStore.list(scope, scopeKey), + target: scope, }); } @@ -96,46 +95,55 @@ export default tool({ }); } const result = await memoryStore.upsert(scope, scopeKey, { - content: args.content ?? "", - sessionId: sessionContext.clientSessionId, - source: "tool", - traceId: sessionContext.traceId, + content: args.content ?? "", + sessionId: sessionContext.clientSessionId, + source: "tool", + traceId: sessionContext.traceId, }); if (!result.entry) { + return JSON.stringify({ + ok: true, + kind: "memory", + decision: "rejected", + detail: "content rejected by persistence policy", + }); + } return JSON.stringify({ ok: true, kind: "memory", - decision: "rejected", - detail: "content rejected by persistence policy", - }); - } - return JSON.stringify({ - ok: true, - kind: "memory", - decision: result.changed ? "accepted" : "deduped", - detail: result.detail, - entry: result.entry, - target: scope, + decision: result.changed ? "accepted" : "deduped", + detail: result.detail, + entry: result.entry, + target: scope, }); } if (args.action === "replace") { - const result = await memoryStore.replace(scope, scopeKey, args.target_id ?? "", { - content: args.content ?? "", - sessionId: sessionContext.clientSessionId, - source: "tool", - traceId: sessionContext.traceId, - }); + const result = await memoryStore.replace( + scope, + scopeKey, + args.target_id ?? "", + { + content: args.content ?? "", + sessionId: sessionContext.clientSessionId, + source: "tool", + traceId: sessionContext.traceId, + }, + ); return JSON.stringify({ - ok: true, - kind: "memory", - decision: result.changed ? "accepted" : "rejected", - detail: result.detail, - target: scope, + ok: true, + kind: "memory", + decision: result.changed ? "accepted" : "rejected", + detail: result.detail, + target: scope, }); } - const result = await memoryStore.remove(scope, scopeKey, args.target_id ?? ""); + const result = await memoryStore.remove( + scope, + scopeKey, + args.target_id ?? "", + ); return JSON.stringify({ ok: true, kind: "memory", diff --git a/.opencode/tools/skill_manager.ts b/.opencode/tools/skill_manager.ts index adf6fc3..740e902 100644 --- a/.opencode/tools/skill_manager.ts +++ b/.opencode/tools/skill_manager.ts @@ -1,123 +1,154 @@ import { tool } from "@opencode-ai/plugin"; import { SkillStore } from "../../src/skills/store.js"; -import { SessionRuntimeContextStore } from "../../src/sessions/runtimeContextStore.js"; +import { + getRuntimeSessionContext, + type RuntimeSessionContext, +} from "../../src/runtime/sessionContext.js"; + +type ToolContextReader = { + read(sessionId: string): RuntimeSessionContext | null; +}; + +const runtimeContextReader: ToolContextReader = { + read: getRuntimeSessionContext, +}; export const createSkillManagerTool = ( skillStore = new SkillStore(), - toolContextStore = new SessionRuntimeContextStore(), - initializePromise: Promise = toolContextStore.initialize(), -) => tool({ - description: - "维护已验证、可复用、非敏感的 workflow 或方法模式。支持 list、write_skill、remove_skill、append_pattern、remove_pattern、write_reference、remove_reference、write_script、remove_script。", - args: { - action: tool.schema - .enum([ - "list", - "write_skill", - "remove_skill", - "append_pattern", - "remove_pattern", - "write_reference", - "remove_reference", - "write_script", - "remove_script", - ]) - .describe("Skill maintenance operation."), - reason: tool.schema - .string() - .describe( - "Why this skill maintenance action is justified for future reuse.", - ), - skill_path: tool.schema - .string() - .describe( - "Target skill directory path relative to .opencode/skills. Use 'workflow' for the workflow index, or '__root__' for the root skills index.", - ), - pattern: tool.schema - .string() - .optional() - .describe("Pattern text used by append_pattern."), - target_id: tool.schema - .string() - .optional() - .describe("Stable learned pattern id used by remove_pattern."), - file_path: tool.schema - .string() - .optional() - .describe("Asset file path. For references use references/*.md; for scripts use scripts/*.py."), - content: tool.schema - .string() - .optional() - .describe("Content used by write_skill, write_reference, or write_script."), - }, - async execute(args, context) { - await initializePromise; - const sessionContext = await toolContextStore.read(context.sessionID); - if (!sessionContext) { - throw new Error(`session context not found for ${context.sessionID}`); - } - if (sessionContext.allowLearningWrite === false && args.action !== "list") { - return JSON.stringify({ - ok: true, - kind: "skill", - decision: "rejected", - detail: "skill writes are disabled for this session", - }); - } - if (args.action === "list") { - const result = await skillStore.list(args.skill_path); - if (!result) { + toolContextStore: ToolContextReader = runtimeContextReader, + initializePromise: Promise = Promise.resolve(), +) => + tool({ + description: + "维护已验证、可复用、非敏感的 workflow 或方法模式。支持 list、write_skill、remove_skill、append_pattern、remove_pattern、write_reference、remove_reference、write_script、remove_script。", + args: { + action: tool.schema + .enum([ + "list", + "write_skill", + "remove_skill", + "append_pattern", + "remove_pattern", + "write_reference", + "remove_reference", + "write_script", + "remove_script", + ]) + .describe("Skill maintenance operation."), + reason: tool.schema + .string() + .describe( + "Why this skill maintenance action is justified for future reuse.", + ), + skill_path: tool.schema + .string() + .describe( + "Target skill directory path relative to .opencode/skills. Use 'workflow' for the workflow index, or '__root__' for the root skills index.", + ), + pattern: tool.schema + .string() + .optional() + .describe("Pattern text used by append_pattern."), + target_id: tool.schema + .string() + .optional() + .describe("Stable learned pattern id used by remove_pattern."), + file_path: tool.schema + .string() + .optional() + .describe( + "Asset file path. For references use references/*.md; for scripts use scripts/*.py.", + ), + content: tool.schema + .string() + .optional() + .describe( + "Content used by write_skill, write_reference, or write_script.", + ), + }, + async execute(args, context) { + await initializePromise; + const sessionContext = toolContextStore.read(context.sessionID); + if (!sessionContext) { + throw new Error(`session context not found for ${context.sessionID}`); + } + if ( + sessionContext.allowLearningWrite === false && + args.action !== "list" + ) { return JSON.stringify({ ok: true, kind: "skill", decision: "rejected", - detail: - "invalid skill_path; expected a relative path under .opencode/skills", + detail: "skill writes are disabled for this session", }); } + if (args.action === "list") { + const result = await skillStore.list(args.skill_path); + if (!result) { + return JSON.stringify({ + ok: true, + kind: "skill", + decision: "rejected", + detail: + "invalid skill_path; expected a relative path under .opencode/skills", + }); + } + return JSON.stringify({ + ok: true, + kind: "skill", + decision: "accepted", + detail: "skill listed", + ...result, + }); + } + + const result = + args.action === "write_skill" + ? await skillStore.writeSkill(args.skill_path, args.content ?? "") + : args.action === "remove_skill" + ? await skillStore.removeSkill(args.skill_path) + : args.action === "append_pattern" + ? await skillStore.appendPattern( + args.skill_path, + args.pattern ?? "", + ) + : args.action === "remove_pattern" + ? await skillStore.removePattern( + args.skill_path, + args.target_id ?? "", + ) + : args.action === "write_reference" + ? await skillStore.writeReference( + args.skill_path, + args.file_path ?? "", + args.content ?? "", + ) + : args.action === "remove_reference" + ? await skillStore.removeReference( + args.skill_path, + args.file_path ?? "", + ) + : args.action === "write_script" + ? await skillStore.writeScript( + args.skill_path, + args.file_path ?? "", + args.content ?? "", + ) + : await skillStore.removeScript( + args.skill_path, + args.file_path ?? "", + ); + return JSON.stringify({ ok: true, kind: "skill", - decision: "accepted", - detail: "skill listed", - ...result, + decision: result.changed ? "accepted" : "rejected", + detail: result.detail, + target: result.target, }); - } - - const result = - args.action === "write_skill" - ? await skillStore.writeSkill(args.skill_path, args.content ?? "") - : args.action === "remove_skill" - ? await skillStore.removeSkill(args.skill_path) - : args.action === "append_pattern" - ? await skillStore.appendPattern(args.skill_path, args.pattern ?? "") - : args.action === "remove_pattern" - ? await skillStore.removePattern(args.skill_path, args.target_id ?? "") - : args.action === "write_reference" - ? await skillStore.writeReference( - args.skill_path, - args.file_path ?? "", - args.content ?? "", - ) - : args.action === "remove_reference" - ? await skillStore.removeReference(args.skill_path, args.file_path ?? "") - : args.action === "write_script" - ? await skillStore.writeScript( - args.skill_path, - args.file_path ?? "", - args.content ?? "", - ) - : await skillStore.removeScript(args.skill_path, args.file_path ?? ""); - - return JSON.stringify({ - ok: true, - kind: "skill", - decision: result.changed ? "accepted" : "rejected", - detail: result.detail, - target: result.target, - }); - }, -}); + }, + }); export default createSkillManagerTool(); diff --git a/src/chat/sessionBridge.ts b/src/chat/sessionBridge.ts index 56601a3..36ee347 100644 --- a/src/chat/sessionBridge.ts +++ b/src/chat/sessionBridge.ts @@ -2,7 +2,10 @@ import { randomUUID } from "node:crypto"; import { logger } from "../logger.js"; import { type OpencodeRuntimeAdapter } from "../runtime/opencode.js"; -import { SessionRuntimeContextStore } from "../sessions/runtimeContextStore.js"; +import { + removeRuntimeSessionContext, + setRuntimeSessionContext, +} from "../runtime/sessionContext.js"; import { toActorKey, toProjectKey } from "../utils/fileStore.js"; export type SessionBinding = { @@ -26,7 +29,6 @@ export type ChatRequestContext = SessionContext & { export class ChatSessionBridge { private readonly abortControllers = new Map(); - private readonly sessionRuntimeContextStore = new SessionRuntimeContextStore(); constructor(private readonly runtime: OpencodeRuntimeAdapter) {} @@ -61,7 +63,7 @@ export class ChatSessionBridge { sessionId, startedAt: Date.now(), }; - await this.sessionRuntimeContextStore.write({ + setRuntimeSessionContext({ accessToken: requestContext.accessToken, actorKey: requestContext.actorKey, allowLearningWrite: true, @@ -133,9 +135,7 @@ export class ChatSessionBridge { "failed while waiting for runtime session to become idle", ); }); - await this.sessionRuntimeContextStore.remove(sessionId).catch((error) => { - logger.debug({ sessionId, err: error }, "failed to cleanup runtime tool context"); - }); + removeRuntimeSessionContext(sessionId); } private buildRequestContext(context: { diff --git a/src/config.ts b/src/config.ts index 6aaac1d..d91fa91 100644 --- a/src/config.ts +++ b/src/config.ts @@ -47,8 +47,6 @@ const envSchema = z OPENCODE_SKILLS_ROOT_DIR: z.string().default("./.opencode/skills"), // client 模式下,目标 opencode server 的基础地址。 OPENCODE_CLIENT_BASE_URL: z.string().url().optional(), - // 提供给本地 opencode tools 读取的会话上下文目录。 - SESSION_RUNTIME_CONTEXT_STORAGE_DIR: z.string().default("./data/session-runtime-contexts"), // tjwater-cli 可执行文件路径。 TJWATER_CLI_PATH: z.string().default("./cli/tjwater-cli"), // TJWater 后端 API 的基础地址。 diff --git a/src/learning/orchestrator.ts b/src/learning/orchestrator.ts index 645d130..8b03074 100644 --- a/src/learning/orchestrator.ts +++ b/src/learning/orchestrator.ts @@ -9,7 +9,10 @@ import { SessionLearningStateStore } from "./sessionStateStore.js"; import { MemoryStore, type MemoryScope } from "../memory/store.js"; import { type OpencodeRuntimeAdapter } from "../runtime/opencode.js"; import { SkillStore } from "../skills/store.js"; -import { SessionRuntimeContextStore } from "../sessions/runtimeContextStore.js"; +import { + removeRuntimeSessionContext, + setRuntimeSessionContext, +} from "../runtime/sessionContext.js"; import { sanitizePersistentDocument, sanitizePersistentLine, @@ -76,7 +79,6 @@ export class LearningOrchestrator { private readonly activeReviews = new Set(); private readonly sessionLearningStateStore = new SessionLearningStateStore(); private readonly skillStore = new SkillStore(); - private readonly sessionRuntimeContextStore = new SessionRuntimeContextStore(); constructor( private readonly runtime: OpencodeRuntimeAdapter, @@ -85,10 +87,7 @@ export class LearningOrchestrator { ) {} async initialize() { - await Promise.all([ - this.sessionLearningStateStore.initialize(), - this.sessionRuntimeContextStore.initialize(), - ]); + await this.sessionLearningStateStore.initialize(); } async onTurnCompleted(input: TurnReviewInput) { @@ -147,7 +146,7 @@ export class LearningOrchestrator { `learning-gate-${input.requestContext.clientSessionId}`, ); gateSessionId = gateSession.id; - await this.sessionRuntimeContextStore.write({ + setRuntimeSessionContext({ actorKey: input.requestContext.actorKey, allowLearningWrite: false, clientSessionId: `gate-${input.requestContext.clientSessionId}`, @@ -215,7 +214,7 @@ export class LearningOrchestrator { }); } finally { if (gateSessionId) { - await this.sessionRuntimeContextStore.remove(gateSessionId).catch(() => undefined); + removeRuntimeSessionContext(gateSessionId); await this.runtime.abortSession(gateSessionId).catch(() => undefined); } } @@ -235,7 +234,7 @@ export class LearningOrchestrator { const reviewSession = await this.runtime.createSession( `learning-review-${input.requestContext.clientSessionId}`, ); - await this.sessionRuntimeContextStore.write({ + setRuntimeSessionContext({ actorKey: input.requestContext.actorKey, allowLearningWrite: false, clientSessionId: `review-${input.requestContext.clientSessionId}`, @@ -283,7 +282,7 @@ export class LearningOrchestrator { traceId: input.requestContext.traceId, }); } finally { - await this.sessionRuntimeContextStore.remove(reviewSession.id).catch(() => undefined); + removeRuntimeSessionContext(reviewSession.id); await this.runtime.abortSession(reviewSession.id).catch(() => undefined); } } diff --git a/src/runtime/sessionContext.ts b/src/runtime/sessionContext.ts new file mode 100644 index 0000000..940fb4c --- /dev/null +++ b/src/runtime/sessionContext.ts @@ -0,0 +1,27 @@ +export type RuntimeSessionContext = { + accessToken?: string; + actorKey: string; + allowLearningWrite?: boolean; + clientSessionId: string; + learningMode?: "interactive" | "review"; + memoryListReadScopes?: Partial>; + projectId?: string; + projectKey: string; + sessionId: string; + traceId: string; +}; + +const contexts = new Map(); + +export const setRuntimeSessionContext = (context: RuntimeSessionContext) => { + contexts.set(context.sessionId, { ...context }); +}; + +export const getRuntimeSessionContext = (sessionId: string) => { + const context = contexts.get(sessionId); + return context ? { ...context } : null; +}; + +export const removeRuntimeSessionContext = (sessionId: string) => { + contexts.delete(sessionId); +}; diff --git a/src/server.ts b/src/server.ts index b89dcf2..f67696e 100644 --- a/src/server.ts +++ b/src/server.ts @@ -18,7 +18,7 @@ import { } from "./results/store.js"; import { buildChatRouter } from "./routes/chat.js"; import { opencodeRuntime } from "./runtime/opencode.js"; -import { SessionRuntimeContextStore } from "./sessions/runtimeContextStore.js"; +import { getRuntimeSessionContext } from "./runtime/sessionContext.js"; const app = express(); @@ -28,7 +28,6 @@ const sessionMetadataStore = new SessionMetadataStore(); const sessionUiStateStore = new SessionUiStateStore(); const memoryStore = new MemoryStore(); const sessionTranscriptStore = new SessionTranscriptStore(); -const sessionRuntimeContextStore = new SessionRuntimeContextStore(); const learningOrchestrator = new LearningOrchestrator( opencodeRuntime, memoryStore, @@ -71,7 +70,7 @@ app.post("/internal/tools/tjwater-cli-call", async (req, res) => { const sessionId = typeof req.body?.session_id === "string" ? req.body.session_id.trim() : ""; - const context = sessionId ? await sessionRuntimeContextStore.read(sessionId) : null; + const context = sessionId ? getRuntimeSessionContext(sessionId) : null; if (!context) { res.status(404).json({ message: "session context not found", @@ -175,7 +174,7 @@ app.post("/internal/tools/store-render-ref", async (req, res) => { const sessionId = typeof req.body?.session_id === "string" ? req.body.session_id.trim() : ""; const filePath = typeof req.body?.file_path === "string" ? req.body.file_path.trim() : ""; - const context = sessionId ? await sessionRuntimeContextStore.read(sessionId) : null; + const context = sessionId ? getRuntimeSessionContext(sessionId) : null; if (!context) { res.status(404).json({ message: "session context not found", @@ -225,7 +224,7 @@ app.post("/internal/tools/session-search", async (req, res) => { const sessionId = typeof req.body?.session_id === "string" ? req.body.session_id.trim() : ""; const query = typeof req.body?.query === "string" ? req.body.query : ""; - const context = sessionId ? await sessionRuntimeContextStore.read(sessionId) : null; + const context = sessionId ? getRuntimeSessionContext(sessionId) : null; if (!context) { res.status(404).json({ message: "session context not found", @@ -273,7 +272,6 @@ const bootstrap = async () => { memoryStore.initialize(), resultReferenceStore.initialize(), sessionTranscriptStore.initialize(), - sessionRuntimeContextStore.initialize(), ]); resultReferenceStore.startCleanupLoop(); }; diff --git a/src/sessions/runtimeContextStore.ts b/src/sessions/runtimeContextStore.ts deleted file mode 100644 index d10eb60..0000000 --- a/src/sessions/runtimeContextStore.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { join } from "node:path"; - -import { config } from "../config.js"; -import { - atomicWriteJson, - ensureDirectory, - readJsonFile, - removeFileIfExists, -} from "../utils/fileStore.js"; - -export type SessionRuntimeContext = { - accessToken?: string; - actorKey: string; - allowLearningWrite?: boolean; - clientSessionId: string; - learningMode?: "interactive" | "review"; - memoryListReadScopes?: Partial>; - projectId?: string; - projectKey: string; - sessionId: string; - traceId: string; -}; - -export class SessionRuntimeContextStore { - constructor(private readonly baseDir = config.SESSION_RUNTIME_CONTEXT_STORAGE_DIR) {} - - async initialize() { - await ensureDirectory(this.baseDir); - } - - async write(context: SessionRuntimeContext) { - await atomicWriteJson(this.filePath(context.sessionId), context); - } - - async read(sessionId: string) { - return await readJsonFile(this.filePath(sessionId)); - } - - async remove(sessionId: string) { - await removeFileIfExists(this.filePath(sessionId)); - } - - private filePath(sessionId: string) { - return join(this.baseDir, `${sessionId}.json`); - } -} diff --git a/tests/opencode/skillManagerTool.test.ts b/tests/opencode/skillManagerTool.test.ts index b73aa42..a453f5d 100644 --- a/tests/opencode/skillManagerTool.test.ts +++ b/tests/opencode/skillManagerTool.test.ts @@ -4,13 +4,13 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import { createSkillManagerTool } from "../../.opencode/tools/skill_manager.js"; -import { SessionRuntimeContextStore } from "../../src/sessions/runtimeContextStore.js"; +import { type RuntimeSessionContext } from "../../src/runtime/sessionContext.js"; import { SkillStore } from "../../src/skills/store.js"; describe("skill_manager tool", () => { let tempDir: string; let skillStore: SkillStore; - let contextStore: SessionRuntimeContextStore; + let context: RuntimeSessionContext; const toolContext = { abort: new AbortController().signal, @@ -39,16 +39,14 @@ describe("skill_manager tool", () => { join(tempDir, "skills"), join(tempDir, "backup", "skills"), ); - contextStore = new SessionRuntimeContextStore(join(tempDir, "contexts")); - await contextStore.initialize(); - await contextStore.write({ + context = { actorKey: "actor-1", allowLearningWrite: true, clientSessionId: "client-session-1", projectKey: "project-1", sessionId: "session-1", traceId: "trace-1", - }); + }; }); afterEach(async () => { @@ -58,7 +56,7 @@ describe("skill_manager tool", () => { it("dispatches skill-level write, overwrite, and remove actions", async () => { const tool = createSkillManagerTool( skillStore, - contextStore, + { read: () => context }, Promise.resolve(), ); @@ -111,7 +109,7 @@ describe("skill_manager tool", () => { it("writes the root skills index through the reserved alias", async () => { const tool = createSkillManagerTool( skillStore, - contextStore, + { read: () => context }, Promise.resolve(), ); diff --git a/tests/runtime/sessionContext.test.ts b/tests/runtime/sessionContext.test.ts new file mode 100644 index 0000000..ab37d5c --- /dev/null +++ b/tests/runtime/sessionContext.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from "bun:test"; + +import { + getRuntimeSessionContext, + removeRuntimeSessionContext, + setRuntimeSessionContext, +} from "../../src/runtime/sessionContext.js"; + +describe("runtime session context", () => { + it("stores authentication context in process memory", () => { + setRuntimeSessionContext({ + accessToken: "token-1", + actorKey: "actor-1", + allowLearningWrite: true, + clientSessionId: "chat-session-1", + learningMode: "interactive", + projectId: "project-id-1", + projectKey: "project-1", + sessionId: "runtime-session-1", + traceId: "trace-1", + }); + + const runtimeContext = getRuntimeSessionContext("runtime-session-1"); + + expect(runtimeContext?.accessToken).toBe("token-1"); + expect(runtimeContext?.clientSessionId).toBe("chat-session-1"); + expect(runtimeContext?.sessionId).toBe("runtime-session-1"); + + removeRuntimeSessionContext("runtime-session-1"); + expect(getRuntimeSessionContext("runtime-session-1")).toBeNull(); + }); +}); diff --git a/tests/sessions/runtimeContextStore.test.ts b/tests/sessions/runtimeContextStore.test.ts deleted file mode 100644 index 4d0c06b..0000000 --- a/tests/sessions/runtimeContextStore.test.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { afterEach, beforeEach, describe, expect, it } from "bun:test"; -import { mkdtemp, rm } from "node:fs/promises"; -import { tmpdir } from "node:os"; -import { join } from "node:path"; - -import { SessionRuntimeContextStore } from "../../src/sessions/runtimeContextStore.js"; - -describe("SessionRuntimeContextStore", () => { - let tempDir: string; - let store: SessionRuntimeContextStore; - - beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), "tjwater-tool-context-")); - store = new SessionRuntimeContextStore(tempDir); - await store.initialize(); - }); - - afterEach(async () => { - await rm(tempDir, { force: true, recursive: true }); - }); - - it("writes and reads runtime session context by opencode session id", async () => { - await store.write({ - accessToken: "token-1", - actorKey: "actor-1", - allowLearningWrite: true, - clientSessionId: "chat-session-1", - learningMode: "interactive", - projectId: "project-id-1", - projectKey: "project-1", - sessionId: "runtime-session-1", - traceId: "trace-1", - }); - - const runtimeContext = await store.read("runtime-session-1"); - - expect(runtimeContext?.accessToken).toBe("token-1"); - expect(runtimeContext?.clientSessionId).toBe("chat-session-1"); - expect(runtimeContext?.sessionId).toBe("runtime-session-1"); - }); -});