refactor: unify agent session persistence
This commit is contained in:
@@ -5,7 +5,7 @@ import {
|
||||
generateSessionTitle,
|
||||
shouldGenerateSessionTitle,
|
||||
} from "../../src/routes/chatSession.js";
|
||||
import { type SessionTurnRecord } from "../../src/history/store.js";
|
||||
import { type SessionTurnRecord } from "../../src/sessions/transcriptStore.js";
|
||||
import { type MemoryStore } from "../../src/memory/store.js";
|
||||
import { type OpencodeRuntimeAdapter } from "../../src/runtime/opencode.js";
|
||||
|
||||
|
||||
@@ -3,15 +3,15 @@ import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { ConversationStore } from "../../src/conversations/store.js";
|
||||
import { SessionMetadataStore } from "../../src/sessions/metadataStore.js";
|
||||
|
||||
describe("ConversationStore", () => {
|
||||
describe("SessionMetadataStore", () => {
|
||||
let tempDir: string;
|
||||
let store: ConversationStore;
|
||||
let store: SessionMetadataStore;
|
||||
|
||||
beforeEach(async () => {
|
||||
tempDir = await mkdtemp(join(tmpdir(), "tjwater-conversation-"));
|
||||
store = new ConversationStore(tempDir);
|
||||
tempDir = await mkdtemp(join(tmpdir(), "tjwater-session-"));
|
||||
store = new SessionMetadataStore(tempDir);
|
||||
await store.initialize();
|
||||
});
|
||||
|
||||
@@ -19,16 +19,17 @@ describe("ConversationStore", () => {
|
||||
await rm(tempDir, { force: true, recursive: true });
|
||||
});
|
||||
|
||||
it("issues backend-managed session ids when absent", async () => {
|
||||
it("persists the provided opencode session id", async () => {
|
||||
const { record, created } = await store.ensure({
|
||||
actorKey: "actor-1",
|
||||
projectId: "project-1",
|
||||
projectKey: "project-key-1",
|
||||
sessionId: "opencode-session-1",
|
||||
userId: "user-1",
|
||||
});
|
||||
|
||||
expect(created).toBe(true);
|
||||
expect(record.sessionId).toStartWith("chat-");
|
||||
expect(record.sessionId).toBe("opencode-session-1");
|
||||
expect(record.ownerUserId).toBe("user-1");
|
||||
expect(record.status).toBe("active");
|
||||
});
|
||||
@@ -44,11 +45,9 @@ describe("ConversationStore", () => {
|
||||
|
||||
const touched = await store.touch(record, {
|
||||
title: "新标题",
|
||||
opencodeSessionId: "opencode-session-1",
|
||||
});
|
||||
|
||||
expect(touched.title).toBe("新标题");
|
||||
expect(touched.opencodeSessionId).toBe("opencode-session-1");
|
||||
expect(touched.updatedAt >= record.updatedAt).toBe(true);
|
||||
|
||||
const fetched = await store.get(
|
||||
@@ -61,6 +60,5 @@ describe("ConversationStore", () => {
|
||||
"existing-session",
|
||||
);
|
||||
expect(fetched?.title).toBe("新标题");
|
||||
expect(fetched?.opencodeSessionId).toBe("opencode-session-1");
|
||||
});
|
||||
});
|
||||
@@ -3,15 +3,15 @@ import { mkdtemp, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { ToolSessionContextStore } from "../../src/session/toolContextStore.js";
|
||||
import { SessionRuntimeContextStore } from "../../src/sessions/runtimeContextStore.js";
|
||||
|
||||
describe("ToolSessionContextStore", () => {
|
||||
describe("SessionRuntimeContextStore", () => {
|
||||
let tempDir: string;
|
||||
let store: ToolSessionContextStore;
|
||||
let store: SessionRuntimeContextStore;
|
||||
|
||||
beforeEach(async () => {
|
||||
tempDir = await mkdtemp(join(tmpdir(), "tjwater-tool-context-"));
|
||||
store = new ToolSessionContextStore(tempDir);
|
||||
store = new SessionRuntimeContextStore(tempDir);
|
||||
await store.initialize();
|
||||
});
|
||||
|
||||
@@ -3,15 +3,15 @@ import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { SessionHistoryStore } from "../../src/history/store.js";
|
||||
import { SessionTranscriptStore } from "../../src/sessions/transcriptStore.js";
|
||||
|
||||
describe("SessionHistoryStore", () => {
|
||||
describe("SessionTranscriptStore", () => {
|
||||
let tempDir: string;
|
||||
let store: SessionHistoryStore;
|
||||
let store: SessionTranscriptStore;
|
||||
|
||||
beforeEach(async () => {
|
||||
tempDir = await mkdtemp(join(tmpdir(), "tjwater-history-"));
|
||||
store = new SessionHistoryStore(tempDir);
|
||||
tempDir = await mkdtemp(join(tmpdir(), "tjwater-transcript-"));
|
||||
store = new SessionTranscriptStore(tempDir);
|
||||
await store.initialize();
|
||||
});
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
|
||||
import { mkdir, mkdtemp, readFile, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { SkillStore } from "../../src/skills/store.js";
|
||||
|
||||
describe("SkillStore", () => {
|
||||
let originalCwd: string;
|
||||
let tempDir: string;
|
||||
let alternateCwd: string;
|
||||
let skillsRoot: string;
|
||||
let backupRoot: string;
|
||||
let store: SkillStore;
|
||||
|
||||
beforeEach(async () => {
|
||||
originalCwd = process.cwd();
|
||||
tempDir = await mkdtemp(join(tmpdir(), "tjwater-skills-"));
|
||||
alternateCwd = join(tempDir, "runtime-cwd");
|
||||
skillsRoot = join(tempDir, "project", ".opencode", "skills");
|
||||
backupRoot = join(tempDir, "backup", "skills");
|
||||
store = new SkillStore(skillsRoot, backupRoot);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
process.chdir(originalCwd);
|
||||
await rm(tempDir, { force: true, recursive: true });
|
||||
});
|
||||
|
||||
it("writes scripts under the configured skills root regardless of process cwd", async () => {
|
||||
await mkdir(alternateCwd, { recursive: true });
|
||||
process.chdir(alternateCwd);
|
||||
|
||||
const result = await store.writeScript(
|
||||
"workflow/hydraulic-bottleneck-analysis",
|
||||
"scripts/analyze.py",
|
||||
"print('ok')\n",
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
changed: true,
|
||||
detail: "script written",
|
||||
target: join(
|
||||
skillsRoot,
|
||||
"workflow",
|
||||
"hydraulic-bottleneck-analysis",
|
||||
"scripts",
|
||||
"analyze.py",
|
||||
),
|
||||
});
|
||||
await expect(readFile(result.target, "utf8")).resolves.toBe("print('ok')\n");
|
||||
});
|
||||
|
||||
it("rejects script paths outside scripts/*.py", async () => {
|
||||
const result = await store.writeScript(
|
||||
"workflow/hydraulic-bottleneck-analysis",
|
||||
"analyze.ts",
|
||||
"console.log('ok')\n",
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
changed: false,
|
||||
detail: "invalid script file_path",
|
||||
target: "",
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user