feat(pi): first-class support via pi-subagents + pi-ask-user (#651)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ import type { ClaudePlugin } from "../src/types/claude"
|
||||
const fixtureRoot = path.join(import.meta.dir, "fixtures", "sample-plugin")
|
||||
|
||||
describe("convertClaudeToPi", () => {
|
||||
test("converts commands, skills, extensions, and MCPorter config", async () => {
|
||||
test("converts commands, skills, agents, and MCP servers without shipping a Pi extension", async () => {
|
||||
const plugin = await loadClaudePlugin(fixtureRoot)
|
||||
const bundle = convertClaudeToPi(plugin, {
|
||||
agentMode: "subagent",
|
||||
@@ -28,22 +28,61 @@ describe("convertClaudeToPi", () => {
|
||||
const parsedPrompt = parseFrontmatter(workflowsReview!.content)
|
||||
expect(parsedPrompt.data.description).toBe("Run a multi-agent review workflow")
|
||||
|
||||
// Existing skills are copied and agents are converted into generated Pi skills
|
||||
// Existing skills are copied as skill dirs; Claude agents are converted to
|
||||
// Pi agent files (under bundle.agents, written to .pi/agents/<name>.md) so
|
||||
// that nicobailon/pi-subagents' `subagent` tool can resolve them by name.
|
||||
expect(bundle.skillDirs.some((skill) => skill.name === "skill-one")).toBe(true)
|
||||
expect(bundle.generatedSkills.some((skill) => skill.name === "repo-research-analyst")).toBe(true)
|
||||
expect(bundle.agents.some((agent) => agent.name === "repo-research-analyst")).toBe(true)
|
||||
// Agents no longer leak into generatedSkills — that field is reserved for
|
||||
// commands-as-skills on other targets; Pi keeps it empty.
|
||||
expect(bundle.generatedSkills).toEqual([])
|
||||
|
||||
// Pi compatibility extension is included (with subagent + MCPorter tools)
|
||||
const compatExtension = bundle.extensions.find((extension) => extension.name === "compound-engineering-compat.ts")
|
||||
expect(compatExtension).toBeDefined()
|
||||
expect(compatExtension!.content).toContain('name: "subagent"')
|
||||
expect(compatExtension!.content).toContain('name: "mcporter_call"')
|
||||
// Pi installs now depend on the community pi-subagents and pi-ask-user extensions,
|
||||
// so the converter emits no bundled extension. Legacy cleanup in the Pi writer
|
||||
// removes any prior compound-engineering-compat.ts on upgrade.
|
||||
expect(bundle.extensions).toEqual([])
|
||||
|
||||
// Claude MCP config is translated to MCPorter config
|
||||
expect(bundle.mcporterConfig?.mcpServers.context7?.baseUrl).toBe("https://mcp.context7.com/mcp")
|
||||
expect(bundle.mcporterConfig?.mcpServers["local-tooling"]?.command).toBe("echo")
|
||||
// MCP servers declared in plugin.json are translated to Pi's mcporter.json
|
||||
// shape so plugins with MCP wiring keep their backends after conversion.
|
||||
// The fixture declares both an HTTP url server (context7) and a stdio
|
||||
// command server (local-tooling).
|
||||
expect(bundle.mcporterConfig).toEqual({
|
||||
mcpServers: {
|
||||
context7: {
|
||||
baseUrl: "https://mcp.context7.com/mcp",
|
||||
headers: undefined,
|
||||
},
|
||||
"local-tooling": {
|
||||
command: "echo",
|
||||
args: ["fixture"],
|
||||
env: undefined,
|
||||
headers: undefined,
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test("transforms Task calls, AskUserQuestion, slash commands, and todo tool references", () => {
|
||||
test("omits mcporterConfig when the plugin declares no MCP servers", () => {
|
||||
const plugin: ClaudePlugin = {
|
||||
root: "/tmp/plugin",
|
||||
manifest: { name: "fixture", version: "1.0.0" },
|
||||
agents: [],
|
||||
commands: [],
|
||||
skills: [],
|
||||
hooks: undefined,
|
||||
mcpServers: undefined,
|
||||
}
|
||||
|
||||
const bundle = convertClaudeToPi(plugin, {
|
||||
agentMode: "subagent",
|
||||
inferTemperature: false,
|
||||
permissions: "none",
|
||||
})
|
||||
|
||||
expect(bundle.mcporterConfig).toBeUndefined()
|
||||
})
|
||||
|
||||
test("transforms Task calls, slash commands, and todo tool references; preserves AskUserQuestion", () => {
|
||||
const plugin: ClaudePlugin = {
|
||||
root: "/tmp/plugin",
|
||||
manifest: { name: "fixture", version: "1.0.0" },
|
||||
@@ -79,7 +118,10 @@ describe("convertClaudeToPi", () => {
|
||||
|
||||
expect(parsedPrompt.body).toContain("Run subagent with agent=\"repo-research-analyst\" and task=\"feature_description\".")
|
||||
expect(parsedPrompt.body).toContain("Run subagent with agent=\"learnings-researcher\" and task=\"feature_description\".")
|
||||
expect(parsedPrompt.body).toContain("ask_user_question")
|
||||
// AskUserQuestion is preserved; skill source-side enumerations name each platform's
|
||||
// blocking-question tool (including `ask_user` for Pi via pi-ask-user), so the
|
||||
// converter no longer rewrites the token.
|
||||
expect(parsedPrompt.body).toContain("AskUserQuestion")
|
||||
expect(parsedPrompt.body).toContain("/workflows-work")
|
||||
expect(parsedPrompt.body).toContain("/todo-resolve")
|
||||
expect(parsedPrompt.body).toContain("the platform's task-tracking primitive")
|
||||
@@ -184,32 +226,4 @@ describe("convertClaudeToPi", () => {
|
||||
expect(parsedPrompt.body).not.toContain("()")
|
||||
})
|
||||
|
||||
test("appends MCPorter compatibility note when command references MCP", () => {
|
||||
const plugin: ClaudePlugin = {
|
||||
root: "/tmp/plugin",
|
||||
manifest: { name: "fixture", version: "1.0.0" },
|
||||
agents: [],
|
||||
commands: [
|
||||
{
|
||||
name: "docs",
|
||||
description: "Read MCP docs",
|
||||
body: "Use MCP servers for docs lookup.",
|
||||
sourcePath: "/tmp/plugin/commands/docs.md",
|
||||
},
|
||||
],
|
||||
skills: [],
|
||||
hooks: undefined,
|
||||
mcpServers: undefined,
|
||||
}
|
||||
|
||||
const bundle = convertClaudeToPi(plugin, {
|
||||
agentMode: "subagent",
|
||||
inferTemperature: false,
|
||||
permissions: "none",
|
||||
})
|
||||
|
||||
const parsedPrompt = parseFrontmatter(bundle.prompts[0].content)
|
||||
expect(parsedPrompt.body).toContain("Pi + MCPorter note")
|
||||
expect(parsedPrompt.body).toContain("mcporter_call")
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user