45bfae6e94
Initial import of all source code, config, and README assets: the packages workspace (cli, core, server, web, docs, landing, skills), build scripts, tooling config, and CI workflows. Includes the data-layout revision made on this branch: the local data root defaults to ~/.penguin/data (PENGUIN_HOME still overrides; the installer keeps its binaries in ~/.penguin), and every Agent lives under <project>/agents/<agent>/ — path helpers, the three agent-enumeration scans, the system prompt, built-in Skills, tests and docs all follow the new layout. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018ihk8iQuo3kv2aPjAYEPuR
123 lines
5.4 KiB
TypeScript
123 lines
5.4 KiB
TypeScript
/**
|
||
* Project built-in Agent provisioning: the only built-in Agent is default_agent
|
||
* (pre-installed with every Skill in the library, empty AGENTS.md, cannot be deleted).
|
||
* Specialized capabilities are now carried by Skills — agent_creator / agent_optimizer
|
||
* are no longer built-in Agents: neither provisioned nor deletion-protected.
|
||
*/
|
||
import fs from "node:fs/promises";
|
||
import path from "node:path";
|
||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||
import { loadLibrarySkills } from "@prismshadow/penguin-skills";
|
||
import type { BenchmarksResponse } from "../src/api/types.js";
|
||
import { apiClient, createTestApp, provisionUser, type TestApp } from "./helpers.js";
|
||
|
||
interface AgentsResponse {
|
||
agents: Array<{ agentId: string; name?: string; description?: string }>;
|
||
}
|
||
interface ProjectsResponse {
|
||
projects: Array<{ projectId: string }>;
|
||
}
|
||
interface ProjectCreateResponse {
|
||
project: { projectId: string };
|
||
}
|
||
|
||
describe("内置 Agent 供给", () => {
|
||
let t: TestApp;
|
||
let owner: ReturnType<typeof apiClient>;
|
||
|
||
beforeEach(async () => {
|
||
t = await createTestApp();
|
||
const reg = await provisionUser(t.app, "owner1");
|
||
owner = apiClient(t.app, reg.cookie);
|
||
});
|
||
afterEach(async () => {
|
||
await t.cleanup();
|
||
});
|
||
|
||
async function expectBuiltinAgents(projectId: string): Promise<void> {
|
||
const list = (await (
|
||
await owner.get(`/api/projects/${projectId}/agents`)
|
||
).json()) as AgentsResponse;
|
||
const ids = list.agents.map((a) => a.agentId);
|
||
// The only built-in Agent: default_agent is listed first with a display name; specialized Agents are no longer provisioned.
|
||
expect(ids[0]).toBe("default_agent");
|
||
expect(ids).not.toContain("agent_creator");
|
||
expect(ids).not.toContain("agent_optimizer");
|
||
expect(list.agents.find((a) => a.agentId === "default_agent")?.name).toBe("General Agent");
|
||
|
||
// Install policy: default_agent is pre-installed with every Skill currently in the library.
|
||
const skillsOf = async (agentId: string) =>
|
||
(
|
||
await fs.readdir(path.join(t.root, projectId, "agents", agentId, "agent_state", "skills"))
|
||
).sort();
|
||
expect(await skillsOf("default_agent")).toEqual(loadLibrarySkills().map((skill) => skill.name));
|
||
|
||
// The default AGENTS.md is empty: it carries no preset guidance (delegation and task
|
||
// conventions live in the default template's Suggested workflows section).
|
||
const defaultMd = await fs.readFile(
|
||
path.join(t.root, projectId, "agents", "default_agent", "agent_state", "AGENTS.md"),
|
||
"utf8",
|
||
);
|
||
expect(defaultMd).toBe("");
|
||
}
|
||
|
||
it("建号时的初始 Project 自带 default_agent", async () => {
|
||
const projects = (await (await owner.get("/api/projects")).json()) as ProjectsResponse;
|
||
await expectBuiltinAgents(projects.projects[0]!.projectId);
|
||
});
|
||
|
||
it("新建 Project 同样自带 default_agent", async () => {
|
||
const created = (await (
|
||
await owner.post("/api/projects", { projectId: "owner1-new", name: "新项目" })
|
||
).json()) as ProjectCreateResponse;
|
||
await expectBuiltinAgents(created.project.projectId);
|
||
});
|
||
|
||
it("default_agent 附带示例 Benchmark:GET /benchmarks 可读(3 evaluations、runs=2、summary 存在)", async () => {
|
||
const projects = (await (await owner.get("/api/projects")).json()) as ProjectsResponse;
|
||
const projectId = projects.projects[0]!.projectId;
|
||
const res = await owner.get(`/api/projects/${projectId}/agents/default_agent/benchmarks`);
|
||
expect(res.status).toBe(200);
|
||
const body = (await res.json()) as BenchmarksResponse;
|
||
const bench = body.benchmarks.find((b) => b.id === "example-benchmark")!;
|
||
expect(bench).toBeDefined();
|
||
expect(bench.title).toBe("Example Benchmark");
|
||
// description explicitly states it's a built-in sample (the whole directory can be deleted or replaced).
|
||
expect(bench.description).toContain("example");
|
||
expect(bench.runs).toBe(2);
|
||
expect(bench.caseCount).toBe(2);
|
||
expect(bench.evaluations).toHaveLength(3);
|
||
for (const evaluation of bench.evaluations) {
|
||
expect(evaluation.summary).toBeTruthy();
|
||
expect(evaluation.cases).toHaveLength(2);
|
||
for (const c of evaluation.cases) {
|
||
expect(c.runs).toHaveLength(2);
|
||
for (const run of c.runs!) {
|
||
expect(run.sessionId).toMatch(/^session-/);
|
||
}
|
||
}
|
||
}
|
||
// The sample data tells an optimization story: scores increase across evaluation rounds (the evaluation center shows a rising curve out of the box).
|
||
const scores = bench.evaluations.map((e) => e.score);
|
||
expect(scores).toEqual([...scores].sort((a, b) => a - b));
|
||
});
|
||
|
||
it("default_agent 不可删除(409)", async () => {
|
||
const projects = (await (await owner.get("/api/projects")).json()) as ProjectsResponse;
|
||
const projectId = projects.projects[0]!.projectId;
|
||
const res = await owner.delete(`/api/projects/${projectId}/agents/default_agent`);
|
||
expect(res.status).toBe(409);
|
||
});
|
||
|
||
it("agent_creator 等旧专用 id 不再受内置保护:可自建、可删除", async () => {
|
||
const projects = (await (await owner.get("/api/projects")).json()) as ProjectsResponse;
|
||
const projectId = projects.projects[0]!.projectId;
|
||
const created = await owner.post(`/api/projects/${projectId}/agents`, {
|
||
agentId: "agent_creator",
|
||
});
|
||
expect(created.status).toBe(201);
|
||
const res = await owner.delete(`/api/projects/${projectId}/agents/agent_creator`);
|
||
expect(res.status).toBe(204);
|
||
});
|
||
});
|