Files

105 lines
3.4 KiB
TypeScript

import { afterEach, beforeEach, describe, expect, it } from "bun:test";
import { mkdtemp, readFile, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import {
executeMemoryManager,
executeSkillManager,
} from "../../src/learning/toolManagers.js";
import { MemoryStore } from "../../src/memory/store.js";
import {
getRuntimeSessionContext,
removeRuntimeSessionContext,
setRuntimeSessionContext,
type RuntimeSessionContext,
} from "../../src/runtime/sessionContext.js";
import { SkillStore } from "../../src/skills/store.js";
describe("main-process learning tool managers", () => {
let tempDir: string;
let memoryStore: MemoryStore;
let skillStore: SkillStore;
let context: RuntimeSessionContext;
beforeEach(async () => {
tempDir = await mkdtemp(join(tmpdir(), "tjwater-learning-tools-"));
memoryStore = new MemoryStore(
join(tempDir, "memory"),
join(tempDir, "backup", "memory"),
);
skillStore = new SkillStore(
join(tempDir, "skills"),
join(tempDir, "backup", "skills"),
);
await memoryStore.initialize();
context = {
actorKey: "actor-1",
allowLearningWrite: true,
clientSessionId: "client-session-1",
projectKey: "project-1",
sessionId: "session-1",
traceId: "trace-1",
};
setRuntimeSessionContext(context);
});
afterEach(async () => {
removeRuntimeSessionContext(context.sessionId);
await rm(tempDir, { force: true, recursive: true });
});
it("enforces list-before-add using the canonical runtime context", async () => {
const rejected = await executeMemoryManager(memoryStore, context, {
action: "add",
content: "用户偏好查看压力单位为 MPa",
scope: "user",
});
expect(rejected.decision).toBe("rejected");
await executeMemoryManager(memoryStore, context, {
action: "list",
scope: "user",
});
const refreshedContext = getRuntimeSessionContext(context.sessionId)!;
const accepted = await executeMemoryManager(memoryStore, refreshedContext, {
action: "add",
content: "用户偏好查看压力单位为 MPa",
scope: "user",
});
expect(accepted.decision).toBe("accepted");
expect(await memoryStore.list("user", context.actorKey)).toHaveLength(1);
});
it("writes and removes skills through the shared store", async () => {
const content = [
"---",
"name: pressure-review",
"description: Pressure review workflow.",
"---",
"",
"# Pressure Review",
].join("\n");
const written = await executeSkillManager(skillStore, context, {
action: "write_skill",
content,
skill_path: "workflow/pressure-review",
});
expect(written.decision).toBe("accepted");
expect("target" in written).toBe(true);
if (!("target" in written)) throw new Error("write returned no target");
await expect(readFile(written.target, "utf8")).resolves.toContain(
"# Pressure Review\n",
);
const removed = await executeSkillManager(skillStore, context, {
action: "remove_skill",
skill_path: "workflow/pressure-review",
});
expect(removed.decision).toBe("accepted");
expect("target" in removed).toBe(true);
if (!("target" in removed)) throw new Error("remove returned no target");
await expect(readFile(removed.target, "utf8")).rejects.toThrow();
});
});