forked from bai/curriculum-project-hub
feat: make agent roles and skills dynamic
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { afterAll, beforeEach, describe, expect, it } from "vitest";
|
||||
import { OrganizationAgentConfiguration } from "../../src/agent/configuration.js";
|
||||
import { DEFAULT_ORG_ID, prisma, resetDb, seedTestOrganization } from "./helpers.js";
|
||||
|
||||
describe("Organization Agent configuration management", () => {
|
||||
let root: string;
|
||||
let configuration: OrganizationAgentConfiguration;
|
||||
|
||||
beforeEach(async () => {
|
||||
await resetDb();
|
||||
root = await mkdtemp(join(tmpdir(), "cph-agent-config-"));
|
||||
configuration = new OrganizationAgentConfiguration(prisma, join(root, "store"));
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await prisma.$disconnect();
|
||||
});
|
||||
|
||||
it("installs versioned skills and selects them as part of a dynamic role bundle", async () => {
|
||||
const typst = await makeSkill(root, "typst");
|
||||
const outline = await makeSkill(root, "outline");
|
||||
await configuration.installSkill({ organizationId: DEFAULT_ORG_ID, sourceDir: typst, version: "0.15.0" });
|
||||
await configuration.installSkill({ organizationId: DEFAULT_ORG_ID, sourceDir: outline, version: "1" });
|
||||
await configuration.upsertRole({
|
||||
organizationId: DEFAULT_ORG_ID,
|
||||
roleId: "draft",
|
||||
label: "课程草稿",
|
||||
defaultModel: "anthropic/claude-sonnet-5",
|
||||
systemPrompt: "write carefully",
|
||||
tools: ["read_file", "write_file", "cph_build"],
|
||||
sortOrder: 10,
|
||||
});
|
||||
await prisma.project.create({
|
||||
data: { id: "project-a", organizationId: DEFAULT_ORG_ID, name: "A", workspaceDir: "/tmp/a" },
|
||||
});
|
||||
await prisma.agentSession.create({
|
||||
data: {
|
||||
id: "session-old-role-config",
|
||||
projectId: "project-a",
|
||||
provider: "openrouter",
|
||||
roleId: "draft",
|
||||
model: "anthropic/claude-sonnet-5",
|
||||
metadata: {},
|
||||
},
|
||||
});
|
||||
await configuration.setRoleSkills({
|
||||
organizationId: DEFAULT_ORG_ID,
|
||||
roleId: "draft",
|
||||
skillNames: ["outline", "typst"],
|
||||
});
|
||||
|
||||
const role = await prisma.organizationAgentRole.findUniqueOrThrow({
|
||||
where: { organizationId_roleId: { organizationId: DEFAULT_ORG_ID, roleId: "draft" } },
|
||||
include: { skillBindings: { orderBy: { sortOrder: "asc" }, include: { skill: true } } },
|
||||
});
|
||||
expect(role).toMatchObject({ label: "课程草稿", systemPrompt: "write carefully" });
|
||||
expect(role.tools).toEqual(["read_file", "write_file", "cph_build"]);
|
||||
expect(role.skillBindings.map((binding) => binding.skill.name)).toEqual(["outline", "typst"]);
|
||||
await expect(prisma.agentSession.findUniqueOrThrow({ where: { id: "session-old-role-config" } }))
|
||||
.resolves.toMatchObject({ archivedAt: expect.any(Date) });
|
||||
});
|
||||
|
||||
it("rejects unknown, disabled and cross-Organization skills", async () => {
|
||||
await seedTestOrganization("org_other", "other");
|
||||
const typst = await makeSkill(root, "typst");
|
||||
await configuration.installSkill({ organizationId: "org_other", sourceDir: typst, version: "1" });
|
||||
await configuration.upsertRole({
|
||||
organizationId: DEFAULT_ORG_ID,
|
||||
roleId: "draft",
|
||||
label: "Draft",
|
||||
tools: [],
|
||||
});
|
||||
|
||||
await expect(configuration.setRoleSkills({
|
||||
organizationId: DEFAULT_ORG_ID,
|
||||
roleId: "draft",
|
||||
skillNames: ["typst"],
|
||||
})).rejects.toThrow("active skills not found in organization");
|
||||
});
|
||||
|
||||
async function makeSkill(parent: string, name: string): Promise<string> {
|
||||
const source = join(parent, "sources", name);
|
||||
await mkdir(source, { recursive: true });
|
||||
await writeFile(join(source, "SKILL.md"), `---\nname: ${name}\ndescription: ${name} skill\n---\n# ${name}\n`);
|
||||
return source;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user