export const PI_COMPAT_EXTENSION_SOURCE = `import fs from "node:fs" import os from "node:os" import path from "node:path" import { fileURLToPath } from "node:url" import type { ExtensionAPI } from "@mariozechner/pi-coding-agent" import { Type } from "@sinclair/typebox" const MAX_BYTES = 50 * 1024 const DEFAULT_SUBAGENT_TIMEOUT_MS = 10 * 60 * 1000 const MAX_PARALLEL_SUBAGENTS = 8 type SubagentTask = { agent: string task: string cwd?: string } type SubagentResult = { agent: string task: string cwd: string exitCode: number output: string stderr: string } function truncate(value: string): string { const input = value ?? "" if (Buffer.byteLength(input, "utf8") <= MAX_BYTES) return input const head = input.slice(0, MAX_BYTES) return head + "\\n\\n[Output truncated to 50KB]" } function shellEscape(value: string): string { return "'" + value.replace(/'/g, "'\\"'\\"'") + "'" } function normalizeName(value: string): string { return String(value || "") .trim() .toLowerCase() .replace(/[^a-z0-9_-]+/g, "-") .replace(/-+/g, "-") .replace(/^-+|-+$/g, "") } function resolveBundledMcporterConfigPath(): string | undefined { try { const extensionDir = path.dirname(fileURLToPath(import.meta.url)) const candidates = [ path.join(extensionDir, "..", "pi-resources", "compound-engineering", "mcporter.json"), path.join(extensionDir, "..", "compound-engineering", "mcporter.json"), ] for (const candidate of candidates) { if (fs.existsSync(candidate)) return candidate } } catch { // noop: bundled path is best-effort fallback } return undefined } function resolveMcporterConfigPath(cwd: string, explicit?: string): string | undefined { if (explicit && explicit.trim()) { return path.resolve(explicit) } const projectPath = path.join(cwd, ".pi", "compound-engineering", "mcporter.json") if (fs.existsSync(projectPath)) return projectPath const globalPath = path.join(os.homedir(), ".pi", "agent", "compound-engineering", "mcporter.json") if (fs.existsSync(globalPath)) return globalPath return resolveBundledMcporterConfigPath() } function resolveTaskCwd(baseCwd: string, taskCwd?: string): string { if (!taskCwd || !taskCwd.trim()) return baseCwd const expanded = taskCwd === "~" ? os.homedir() : taskCwd.startsWith("~" + path.sep) ? path.join(os.homedir(), taskCwd.slice(2)) : taskCwd return path.resolve(baseCwd, expanded) } async function runSingleSubagent( pi: ExtensionAPI, baseCwd: string, task: SubagentTask, signal?: AbortSignal, timeoutMs = DEFAULT_SUBAGENT_TIMEOUT_MS, ): Promise { const agent = normalizeName(task.agent) if (!agent) { throw new Error("Subagent task is missing a valid agent name") } const taskText = String(task.task ?? "").trim() if (!taskText) { throw new Error("Subagent task for " + agent + " is empty") } const cwd = resolveTaskCwd(baseCwd, task.cwd) const prompt = "/skill:" + agent + " " + taskText const script = "cd " + shellEscape(cwd) + " && pi --no-session -p " + shellEscape(prompt) const result = await pi.exec("bash", ["-lc", script], { signal, timeout: timeoutMs }) return { agent, task: taskText, cwd, exitCode: result.code, output: truncate(result.stdout || ""), stderr: truncate(result.stderr || ""), } } async function runParallelSubagents( pi: ExtensionAPI, baseCwd: string, tasks: SubagentTask[], signal?: AbortSignal, timeoutMs = DEFAULT_SUBAGENT_TIMEOUT_MS, maxConcurrency = 4, onProgress?: (completed: number, total: number) => void, ): Promise { const safeConcurrency = Math.max(1, Math.min(maxConcurrency, MAX_PARALLEL_SUBAGENTS, tasks.length)) const results: SubagentResult[] = new Array(tasks.length) let nextIndex = 0 let completed = 0 const workers = Array.from({ length: safeConcurrency }, async () => { while (true) { const current = nextIndex nextIndex += 1 if (current >= tasks.length) return results[current] = await runSingleSubagent(pi, baseCwd, tasks[current], signal, timeoutMs) completed += 1 onProgress?.(completed, tasks.length) } }) await Promise.all(workers) return results } function formatSubagentSummary(results: SubagentResult[]): string { if (results.length === 0) return "No subagent work was executed." const success = results.filter((result) => result.exitCode === 0).length const failed = results.length - success const header = failed === 0 ? "Subagent run completed: " + success + "/" + results.length + " succeeded." : "Subagent run completed: " + success + "/" + results.length + " succeeded, " + failed + " failed." const lines = results.map((result) => { const status = result.exitCode === 0 ? "ok" : "error" const body = result.output || result.stderr || "(no output)" const preview = body.split("\\n").slice(0, 6).join("\\n") return "\\n[" + status + "] " + result.agent + "\\n" + preview }) return header + lines.join("\\n") } export default function (pi: ExtensionAPI) { pi.registerTool({ name: "ask_user_question", label: "Ask User Question", description: "Ask the user a question with optional choices.", parameters: Type.Object({ question: Type.String({ description: "Question shown to the user" }), options: Type.Optional(Type.Array(Type.String(), { description: "Selectable options" })), allowCustom: Type.Optional(Type.Boolean({ default: true })), }), async execute(_toolCallId, params, _signal, _onUpdate, ctx) { if (!ctx.hasUI) { return { isError: true, content: [{ type: "text", text: "UI is unavailable in this mode." }], details: {}, } } const options = params.options ?? [] const allowCustom = params.allowCustom ?? true if (options.length === 0) { const answer = await ctx.ui.input(params.question) if (!answer) { return { content: [{ type: "text", text: "User cancelled." }], details: { answer: null }, } } return { content: [{ type: "text", text: "User answered: " + answer }], details: { answer, mode: "input" }, } } const customLabel = "Other (type custom answer)" const selectable = allowCustom ? [...options, customLabel] : options const selected = await ctx.ui.select(params.question, selectable) if (!selected) { return { content: [{ type: "text", text: "User cancelled." }], details: { answer: null }, } } if (selected === customLabel) { const custom = await ctx.ui.input("Your answer") if (!custom) { return { content: [{ type: "text", text: "User cancelled." }], details: { answer: null }, } } return { content: [{ type: "text", text: "User answered: " + custom }], details: { answer: custom, mode: "custom" }, } } return { content: [{ type: "text", text: "User selected: " + selected }], details: { answer: selected, mode: "select" }, } }, }) const subagentTaskSchema = Type.Object({ agent: Type.String({ description: "Skill/agent name to invoke" }), task: Type.String({ description: "Task instructions for that skill" }), cwd: Type.Optional(Type.String({ description: "Optional working directory for this task" })), }) pi.registerTool({ name: "subagent", label: "Subagent", description: "Run one or more skill-based subagent tasks. Supports single, parallel, and chained execution.", parameters: Type.Object({ agent: Type.Optional(Type.String({ description: "Single subagent name" })), task: Type.Optional(Type.String({ description: "Single subagent task" })), cwd: Type.Optional(Type.String({ description: "Working directory for single mode" })), tasks: Type.Optional(Type.Array(subagentTaskSchema, { description: "Parallel subagent tasks" })), chain: Type.Optional(Type.Array(subagentTaskSchema, { description: "Sequential tasks; supports {previous} placeholder" })), maxConcurrency: Type.Optional(Type.Number({ default: 4 })), timeoutMs: Type.Optional(Type.Number({ default: DEFAULT_SUBAGENT_TIMEOUT_MS })), }), async execute(_toolCallId, params, signal, onUpdate, ctx) { const hasSingle = Boolean(params.agent && params.task) const hasTasks = Boolean(params.tasks && params.tasks.length > 0) const hasChain = Boolean(params.chain && params.chain.length > 0) const modeCount = Number(hasSingle) + Number(hasTasks) + Number(hasChain) if (modeCount !== 1) { return { isError: true, content: [{ type: "text", text: "Provide exactly one mode: single (agent+task), tasks, or chain." }], details: {}, } } const timeoutMs = Number(params.timeoutMs || DEFAULT_SUBAGENT_TIMEOUT_MS) try { if (hasSingle) { const result = await runSingleSubagent( pi, ctx.cwd, { agent: params.agent!, task: params.task!, cwd: params.cwd }, signal, timeoutMs, ) const body = formatSubagentSummary([result]) return { isError: result.exitCode !== 0, content: [{ type: "text", text: body }], details: { mode: "single", results: [result] }, } } if (hasTasks) { const tasks = params.tasks as SubagentTask[] const maxConcurrency = Number(params.maxConcurrency || 4) const results = await runParallelSubagents( pi, ctx.cwd, tasks, signal, timeoutMs, maxConcurrency, (completed, total) => { onUpdate?.({ content: [{ type: "text", text: "Subagent progress: " + completed + "/" + total }], details: { mode: "parallel", completed, total }, }) }, ) const body = formatSubagentSummary(results) const hasFailure = results.some((result) => result.exitCode !== 0) return { isError: hasFailure, content: [{ type: "text", text: body }], details: { mode: "parallel", results }, } } const chain = params.chain as SubagentTask[] const results: SubagentResult[] = [] let previous = "" for (const step of chain) { const resolvedTask = step.task.replace(/\\{previous\\}/g, previous) const result = await runSingleSubagent( pi, ctx.cwd, { agent: step.agent, task: resolvedTask, cwd: step.cwd }, signal, timeoutMs, ) results.push(result) previous = result.output || result.stderr onUpdate?.({ content: [{ type: "text", text: "Subagent chain progress: " + results.length + "/" + chain.length }], details: { mode: "chain", completed: results.length, total: chain.length }, }) if (result.exitCode !== 0) break } const body = formatSubagentSummary(results) const hasFailure = results.some((result) => result.exitCode !== 0) return { isError: hasFailure, content: [{ type: "text", text: body }], details: { mode: "chain", results }, } } catch (error) { return { isError: true, content: [{ type: "text", text: error instanceof Error ? error.message : String(error) }], details: {}, } } }, }) pi.registerTool({ name: "mcporter_list", label: "MCPorter List", description: "List tools on an MCP server through MCPorter.", parameters: Type.Object({ server: Type.String({ description: "Configured MCP server name" }), allParameters: Type.Optional(Type.Boolean({ default: false })), json: Type.Optional(Type.Boolean({ default: true })), configPath: Type.Optional(Type.String({ description: "Optional mcporter config path" })), }), async execute(_toolCallId, params, signal, _onUpdate, ctx) { const args = ["list", params.server] if (params.allParameters) args.push("--all-parameters") if (params.json ?? true) args.push("--json") const configPath = resolveMcporterConfigPath(ctx.cwd, params.configPath) if (configPath) { args.push("--config", configPath) } const result = await pi.exec("mcporter", args, { signal }) const output = truncate(result.stdout || result.stderr || "") return { isError: result.code !== 0, content: [{ type: "text", text: output || "(no output)" }], details: { exitCode: result.code, command: "mcporter " + args.join(" "), configPath, }, } }, }) pi.registerTool({ name: "mcporter_call", label: "MCPorter Call", description: "Call a specific MCP tool through MCPorter.", parameters: Type.Object({ call: Type.Optional(Type.String({ description: "Function-style call, e.g. linear.list_issues(limit: 5)" })), server: Type.Optional(Type.String({ description: "Server name (if call is omitted)" })), tool: Type.Optional(Type.String({ description: "Tool name (if call is omitted)" })), args: Type.Optional(Type.Record(Type.String(), Type.Any(), { description: "JSON arguments object" })), configPath: Type.Optional(Type.String({ description: "Optional mcporter config path" })), }), async execute(_toolCallId, params, signal, _onUpdate, ctx) { const args = ["call"] if (params.call && params.call.trim()) { args.push(params.call.trim()) } else { if (!params.server || !params.tool) { return { isError: true, content: [{ type: "text", text: "Provide either call, or server + tool." }], details: {}, } } args.push(params.server + "." + params.tool) if (params.args) { args.push("--args", JSON.stringify(params.args)) } } args.push("--output", "json") const configPath = resolveMcporterConfigPath(ctx.cwd, params.configPath) if (configPath) { args.push("--config", configPath) } const result = await pi.exec("mcporter", args, { signal }) const output = truncate(result.stdout || result.stderr || "") return { isError: result.code !== 0, content: [{ type: "text", text: output || "(no output)" }], details: { exitCode: result.code, command: "mcporter " + args.join(" "), configPath, }, } }, }) } `