feat(pi-workflow): reuse standard agent profiles
This commit is contained in:
@@ -30,6 +30,17 @@ function profileDocument(options: {
|
||||
return `---\nname: ${options.name}\ndescription: ${options.description ?? options.name}\nroles: ${options.roles ?? "[discover]"}\n${options.extra ?? ""}selection:\n${options.selection ?? " stages: [discover]\n anyTags: []\n allTags: []\n pathGlobs: []\n keywords: []\n"}---\n\n${options.body ?? "Follow the assignment."}\n`;
|
||||
}
|
||||
|
||||
function standardAgentDocument(options: {
|
||||
name: string;
|
||||
description?: string;
|
||||
model?: string;
|
||||
tools?: string;
|
||||
thinking?: string;
|
||||
body?: string;
|
||||
}) {
|
||||
return `---\nname: ${options.name}\ndescription: ${options.description ?? options.name}\n${options.model ? `model: ${options.model}\n` : ""}${options.tools ? `tools: ${options.tools}\n` : ""}${options.thinking ? `thinking: ${options.thinking}\n` : ""}subagent_agents: scout, researcher\nsystem-prompt: append\nauto-exit: true\n---\n\n${options.body ?? "Follow the standard assignment."}\n`;
|
||||
}
|
||||
|
||||
async function writeProfile(
|
||||
root: string,
|
||||
file: string,
|
||||
@@ -39,26 +50,197 @@ async function writeProfile(
|
||||
await writeFile(path.join(root, file), profileDocument(options));
|
||||
}
|
||||
|
||||
async function writeStandardAgent(
|
||||
root: string,
|
||||
file: string,
|
||||
options: Parameters<typeof standardAgentDocument>[0],
|
||||
) {
|
||||
await mkdir(root, { recursive: true });
|
||||
await writeFile(path.join(root, file), standardAgentDocument(options));
|
||||
}
|
||||
|
||||
test("bundled profiles form a complete deterministic catalog", async () => {
|
||||
const catalog = await loadAgentProfiles({
|
||||
cwd: process.cwd(),
|
||||
projectTrusted: false,
|
||||
await withTempDir(async (directory) => {
|
||||
const catalog = await loadAgentProfiles({
|
||||
cwd: directory,
|
||||
agentDir: path.join(directory, "agent"),
|
||||
projectTrusted: false,
|
||||
});
|
||||
assert.deepEqual(
|
||||
catalog.profiles.map((profile) => profile.name),
|
||||
[
|
||||
"feature-designer",
|
||||
"feature-implementer",
|
||||
"feature-verifier",
|
||||
"general-discovery",
|
||||
"independent-reviewer",
|
||||
"ui-prototyper",
|
||||
],
|
||||
);
|
||||
assert.equal(catalog.diagnostics.length, 0);
|
||||
assert.ok(
|
||||
catalog.profiles.every((profile) => /^[a-f0-9]{64}$/.test(profile.digest)),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("standard package, global, and trusted project agents use role overlays and precedence", async () => {
|
||||
await withTempDir(async (directory) => {
|
||||
const bundled = path.join(directory, "bundled");
|
||||
const packageRoot = path.join(directory, "package-agents");
|
||||
const agentDir = path.join(directory, "agent");
|
||||
const globalRoot = path.join(agentDir, "agents");
|
||||
const projectRoot = path.join(directory, ".pi", "agents");
|
||||
await writeProfile(bundled, "general.md", {
|
||||
name: "general-discovery",
|
||||
});
|
||||
await writeStandardAgent(packageRoot, "scout.md", {
|
||||
name: "scout",
|
||||
model: "openai-codex/gpt-test",
|
||||
tools: "read, grep, find, ls",
|
||||
thinking: "high",
|
||||
body: "Package scout.",
|
||||
});
|
||||
await writeStandardAgent(packageRoot, "worker.md", {
|
||||
name: "worker",
|
||||
tools: "read, write, edit, bash",
|
||||
body: "Package worker.",
|
||||
});
|
||||
await writeStandardAgent(globalRoot, "scout.md", {
|
||||
name: "scout",
|
||||
body: "Global scout.",
|
||||
});
|
||||
await writeStandardAgent(globalRoot, "unmapped.md", {
|
||||
name: "unmapped",
|
||||
});
|
||||
await writeStandardAgent(projectRoot, "worker.md", {
|
||||
name: "worker",
|
||||
body: "Project worker.",
|
||||
});
|
||||
|
||||
const catalog = await loadAgentProfiles({
|
||||
cwd: directory,
|
||||
agentDir,
|
||||
bundledRoot: bundled,
|
||||
standardPackageRoot: packageRoot,
|
||||
projectRoot,
|
||||
projectTrusted: true,
|
||||
standardAgents: {
|
||||
enabled: true,
|
||||
roles: { scout: ["discover"], worker: ["implement"] },
|
||||
},
|
||||
});
|
||||
|
||||
const scout = catalog.profiles.find((profile) => profile.name === "scout");
|
||||
assert.equal(scout?.source, "standard-global");
|
||||
assert.equal(scout?.instructions, "Global scout.");
|
||||
assert.deepEqual(scout?.roles, ["discover"]);
|
||||
assert.deepEqual(scout?.selection.stages, ["discover"]);
|
||||
|
||||
const worker = catalog.profiles.find((profile) => profile.name === "worker");
|
||||
assert.equal(worker?.source, "standard-project");
|
||||
assert.equal(worker?.instructions, "Project worker.");
|
||||
assert.deepEqual(worker?.roles, ["implement"]);
|
||||
assert.equal(catalog.profiles.some((profile) => profile.name === "unmapped"), false);
|
||||
});
|
||||
});
|
||||
|
||||
test("package and global standard definitions cannot self-declare workflow roles", async () => {
|
||||
await withTempDir(async (directory) => {
|
||||
const bundled = path.join(directory, "bundled");
|
||||
const packageRoot = path.join(directory, "package-agents");
|
||||
await writeProfile(bundled, "general.md", { name: "general-discovery" });
|
||||
await writeProfile(packageRoot, "worker.md", {
|
||||
name: "worker",
|
||||
roles: "[review]",
|
||||
selection:
|
||||
" stages: [review]\n anyTags: []\n allTags: []\n pathGlobs: []\n keywords: []\n",
|
||||
body: "Attempted self-declared reviewer.",
|
||||
});
|
||||
await writeProfile(packageRoot, "unmapped.md", {
|
||||
name: "unmapped",
|
||||
roles: "[review]",
|
||||
selection:
|
||||
" stages: [review]\n anyTags: []\n allTags: []\n pathGlobs: []\n keywords: []\n",
|
||||
});
|
||||
|
||||
const catalog = await loadAgentProfiles({
|
||||
cwd: directory,
|
||||
agentDir: path.join(directory, "agent"),
|
||||
bundledRoot: bundled,
|
||||
standardPackageRoot: packageRoot,
|
||||
projectTrusted: false,
|
||||
standardAgents: {
|
||||
enabled: true,
|
||||
roles: { worker: ["implement"] },
|
||||
},
|
||||
});
|
||||
const worker = catalog.profiles.find((profile) => profile.name === "worker");
|
||||
assert.equal(worker?.source, "standard-package");
|
||||
assert.deepEqual(worker?.roles, ["implement"]);
|
||||
assert.deepEqual(worker?.selection.stages, ["implement"]);
|
||||
assert.equal(catalog.profiles.some((profile) => profile.name === "unmapped"), false);
|
||||
});
|
||||
});
|
||||
|
||||
test("a trusted workflow profile keeps its declared roles when its name also has a standard overlay", async () => {
|
||||
await withTempDir(async (directory) => {
|
||||
const bundled = path.join(directory, "bundled");
|
||||
const packageRoot = path.join(directory, "package-agents");
|
||||
const projectRoot = path.join(directory, ".pi", "agents");
|
||||
await writeProfile(bundled, "general.md", { name: "general-discovery" });
|
||||
await writeStandardAgent(packageRoot, "scout.md", { name: "scout" });
|
||||
await writeProfile(projectRoot, "scout.md", {
|
||||
name: "scout",
|
||||
roles: "[verify]",
|
||||
selection:
|
||||
" stages: [verify]\n anyTags: []\n allTags: []\n pathGlobs: []\n keywords: []\n",
|
||||
body: "Project verifier.",
|
||||
});
|
||||
|
||||
const catalog = await loadAgentProfiles({
|
||||
cwd: directory,
|
||||
agentDir: path.join(directory, "agent"),
|
||||
bundledRoot: bundled,
|
||||
standardPackageRoot: packageRoot,
|
||||
projectRoot,
|
||||
projectTrusted: true,
|
||||
standardAgents: {
|
||||
enabled: true,
|
||||
roles: { scout: ["discover"] },
|
||||
},
|
||||
});
|
||||
const scout = catalog.profiles.find((profile) => profile.name === "scout");
|
||||
assert.equal(scout?.source, "project");
|
||||
assert.deepEqual(scout?.roles, ["verify"]);
|
||||
assert.deepEqual(scout?.selection.stages, ["verify"]);
|
||||
assert.equal(scout?.instructions, "Project verifier.");
|
||||
});
|
||||
});
|
||||
|
||||
test("standard agents are disabled explicitly and ignored for untrusted projects", async () => {
|
||||
await withTempDir(async (directory) => {
|
||||
const bundled = path.join(directory, "bundled");
|
||||
const packageRoot = path.join(directory, "package-agents");
|
||||
const projectRoot = path.join(directory, ".pi", "agents");
|
||||
await writeProfile(bundled, "general.md", { name: "general-discovery" });
|
||||
await writeStandardAgent(packageRoot, "scout.md", { name: "scout" });
|
||||
await writeStandardAgent(projectRoot, "worker.md", { name: "worker" });
|
||||
|
||||
const disabled = await loadAgentProfiles({
|
||||
cwd: directory,
|
||||
agentDir: path.join(directory, "agent"),
|
||||
bundledRoot: bundled,
|
||||
standardPackageRoot: packageRoot,
|
||||
projectRoot,
|
||||
projectTrusted: true,
|
||||
standardAgents: {
|
||||
enabled: false,
|
||||
roles: { scout: ["discover"], worker: ["implement"] },
|
||||
},
|
||||
});
|
||||
assert.deepEqual(disabled.profiles.map((profile) => profile.name), ["general-discovery"]);
|
||||
});
|
||||
assert.deepEqual(
|
||||
catalog.profiles.map((profile) => profile.name),
|
||||
[
|
||||
"feature-designer",
|
||||
"feature-implementer",
|
||||
"feature-verifier",
|
||||
"general-discovery",
|
||||
"independent-reviewer",
|
||||
"ui-prototyper",
|
||||
],
|
||||
);
|
||||
assert.equal(catalog.diagnostics.length, 0);
|
||||
assert.ok(
|
||||
catalog.profiles.every((profile) => /^[a-f0-9]{64}$/.test(profile.digest)),
|
||||
);
|
||||
});
|
||||
|
||||
test("trusted project profiles override bundled profiles while first duplicate wins", async () => {
|
||||
@@ -340,6 +522,7 @@ test("active profile contents are pinned by digest", async () => {
|
||||
function agent(
|
||||
name: string,
|
||||
selection: AgentProfile["selection"],
|
||||
source: AgentProfile["source"] = "bundled",
|
||||
): AgentProfile {
|
||||
return {
|
||||
name,
|
||||
@@ -347,7 +530,7 @@ function agent(
|
||||
roles: ["discover"],
|
||||
selection,
|
||||
instructions: "Discover.",
|
||||
source: "bundled",
|
||||
source,
|
||||
sourcePath: name,
|
||||
resolvedPath: name,
|
||||
digest: name.padEnd(64, "0"),
|
||||
@@ -414,6 +597,28 @@ test("eligibility records exclusion reasons and selects the most specific discov
|
||||
);
|
||||
});
|
||||
|
||||
test("an eligible standard agent replaces the bundled fallback for its role", () => {
|
||||
const emptySelection: AgentProfile["selection"] = {
|
||||
stages: ["discover"],
|
||||
anyTags: [],
|
||||
allTags: [],
|
||||
pathGlobs: [],
|
||||
keywords: [],
|
||||
};
|
||||
const bundled = agent("general-discovery", emptySelection);
|
||||
const scout = agent("scout", emptySelection, "standard-package");
|
||||
|
||||
const result = selectInitialDiscoveryProfile(
|
||||
[bundled, scout],
|
||||
baseFacts,
|
||||
);
|
||||
assert.equal(result.selected.name, "scout");
|
||||
assert.deepEqual(
|
||||
result.evaluations.find((item) => item.profile.name === "general-discovery")?.reasons,
|
||||
["eligible standard agent available for role discover"],
|
||||
);
|
||||
});
|
||||
|
||||
test("eligibility enforces every criterion and uses a code-point name tie-breaker", () => {
|
||||
const emptySelection: AgentProfile["selection"] = {
|
||||
stages: ["discover"],
|
||||
|
||||
Reference in New Issue
Block a user