105 lines
3.4 KiB
TypeScript
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();
|
|
});
|
|
});
|