Files
penguin-harness/packages/server/test/builtin-agents.test.ts
T
Yaowei Zheng 45bfae6e94 Initialize repository with harness code and assets
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
2026-07-19 14:06:53 +08:00

123 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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);
});
});