Files
penguin-harness/packages/core/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

142 lines
5.9 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.
/**
* Built-in agent provisioning and skill library install policy: the sole built-in agent
* default_agent comes pre-installed with every skill in the library, an ordinary newly created
* agent starts with zero skills, and the default AGENTS.md is an empty file; provisionProjectAgents
* is idempotent and never overwrites existing config.
*/
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { librarySkill, loadLibrarySkills } from "@prismshadow/penguin-skills";
import {
agentsMdPath,
assembleSystemPrompt,
BUILTIN_AGENT_IDS,
DEFAULT_AGENT_ID,
DEFAULT_PROJECT_ID,
listInstalledSkills,
loadOrInitAgentState,
provisionProjectAgents,
skillsDir,
} from "../src/state/index.js";
let tmpRoot: string;
let prevHome: string | undefined;
beforeEach(async () => {
prevHome = process.env.PENGUIN_HOME;
tmpRoot = await fs.mkdtemp(path.join(os.tmpdir(), "penguin-builtin-"));
process.env.PENGUIN_HOME = tmpRoot;
});
afterEach(async () => {
if (prevHome === undefined) {
delete process.env.PENGUIN_HOME;
} else {
process.env.PENGUIN_HOME = prevHome;
}
await fs.rm(tmpRoot, { recursive: true, force: true });
});
const skillMdPath = (agentId: string, skillName: string): string =>
path.join(skillsDir(tmpRoot, DEFAULT_PROJECT_ID, agentId), skillName, "SKILL.md");
describe("Skill 安装策略", () => {
it("普通新建 Agent 不预装任何 Skill,AGENTS.md 为空文件(指导在模板 Suggested workflows)", async () => {
const state = await loadOrInitAgentState({ agentId: "some_agent" });
expect(await listInstalledSkills(tmpRoot, DEFAULT_PROJECT_ID, "some_agent")).toEqual([]);
// The default AGENTS.md is empty: it carries no preset guidance (delegation and task
// conventions live in the default template's Suggested workflows section, and skill
// metadata is injected via {{SKILL_METADATA}}).
expect(state.agentsMd).toBe("");
const onDisk = await fs.readFile(
agentsMdPath(tmpRoot, DEFAULT_PROJECT_ID, "some_agent"),
"utf8",
);
expect(onDisk).toBe("");
});
it("无 preset 直建的 default_agent(如 CLI 首次运行)同样预装库内全部 Skill", async () => {
await loadOrInitAgentState({ agentId: DEFAULT_AGENT_ID });
const names = (await listInstalledSkills(tmpRoot, DEFAULT_PROJECT_ID, DEFAULT_AGENT_ID)).map(
(s) => s.name,
);
expect(names).toEqual(loadLibrarySkills().map((s) => s.name));
});
});
describe("provisionProjectAgents", () => {
it("唯一内置 Agent default_agent:装库内全部 Skill、AGENTS.md 为空", async () => {
const ids = await provisionProjectAgents();
expect(ids).toEqual([DEFAULT_AGENT_ID]);
expect(BUILTIN_AGENT_IDS).toEqual([DEFAULT_AGENT_ID]);
// name/description are written into system_config; AGENTS.md is an empty file.
const state = await loadOrInitAgentState({ agentId: DEFAULT_AGENT_ID });
expect(state.systemConfig.name).toBe("General Agent");
expect(state.systemConfig.description).toBeTruthy();
expect(state.agentsMd).toBe("");
const installed = await listInstalledSkills(tmpRoot, DEFAULT_PROJECT_ID, DEFAULT_AGENT_ID);
expect(installed.map((s) => s.name).sort()).toEqual(loadLibrarySkills().map((s) => s.name));
// On-disk content matches the library's SKILL.md verbatim (install copies the full text).
const sdkMd = await fs.readFile(skillMdPath(DEFAULT_AGENT_ID, "penguin-sdk"), "utf8");
expect(sdkMd).toBe(librarySkill("penguin-sdk")!.content);
});
it("provision 幂等:重复执行不改变结果", async () => {
await provisionProjectAgents();
const ids = await provisionProjectAgents();
expect(ids).toEqual([DEFAULT_AGENT_ID]);
const md = await fs.readFile(
agentsMdPath(tmpRoot, DEFAULT_PROJECT_ID, DEFAULT_AGENT_ID),
"utf8",
);
expect(md).toBe("");
const installed = await listInstalledSkills(tmpRoot, DEFAULT_PROJECT_ID, DEFAULT_AGENT_ID);
expect(installed.map((s) => s.name).sort()).toEqual(loadLibrarySkills().map((s) => s.name));
});
it("已存在的 Agent 不被覆盖(preset 仅初始化生效)", async () => {
const custom = "# AGENTS.md\n\n用户自己改过的内容\n";
await loadOrInitAgentState({ agentId: DEFAULT_AGENT_ID });
await fs.writeFile(agentsMdPath(tmpRoot, DEFAULT_PROJECT_ID, DEFAULT_AGENT_ID), custom, "utf8");
await provisionProjectAgents();
const after = await fs.readFile(
agentsMdPath(tmpRoot, DEFAULT_PROJECT_ID, DEFAULT_AGENT_ID),
"utf8",
);
expect(after).toBe(custom);
});
});
describe("Project Dir / Agent ID 占位符", () => {
it("assembleSystemPrompt 注入 Project Dir 与 Agent ID(Skill 定位改走项目相对路径,不依赖 .penguin)", async () => {
const state = await loadOrInitAgentState({ agentId: "env_agent" });
const prompt = assembleSystemPrompt(state, {
sessionId: "session-x",
cwd: "/tmp/ws",
agentId: "env_agent",
projectDir: "/tmp/proj",
platform: "linux",
osVersion: "test",
date: "2026-07-08",
});
expect(prompt).toContain("Agent ID: env_agent");
expect(prompt).toContain("Project Dir: /tmp/proj");
expect(prompt).not.toContain("{{AGENT_ID}}");
expect(prompt).not.toContain("{{PROJECT_DIR}}");
// Skill execution conventions are built from project-relative paths (no longer reference .penguin).
expect(prompt).not.toContain(".penguin");
// Agent State, scratchpad and Skills are addressed under the Project's agents/ container.
expect(prompt).toContain("<project_dir>/agents/<agent_id>/agent_state/");
expect(prompt).toContain("<project_dir>/agents/<agent_id>/agent_state/skills/");
// The pre-agents/ layout (an agent directly under the Project dir) must never be handed to the model.
expect(prompt).not.toContain("<project_dir>/<agent_id>/");
});
});