124 lines
4.4 KiB
TypeScript
124 lines
4.4 KiB
TypeScript
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
import { Type } from "typebox";
|
|
import { SubprocessRpcRunner } from "./runner.ts";
|
|
import { Supervisor } from "./supervisor.ts";
|
|
import type { SpawnRequest } from "./types.ts";
|
|
|
|
let supervisor: Supervisor | undefined;
|
|
|
|
export default function subagents(pi: ExtensionAPI) {
|
|
const getSupervisor = (ctx: ExtensionContext): Supervisor => {
|
|
if (!supervisor) supervisor = new Supervisor(new SubprocessRpcRunner(), cwdOf(ctx));
|
|
return supervisor;
|
|
};
|
|
|
|
pi.registerTool({
|
|
name: "subagent_spawn",
|
|
label: "Spawn subagent",
|
|
description: "Start one ad hoc independent subagent and return immediately with its child id",
|
|
parameters: Type.Object({
|
|
prompt: Type.String({ description: "Prompt for the delegated subagent" }),
|
|
context: Type.Optional(Type.Literal("independent")),
|
|
model: Type.Optional(Type.String({ description: "Optional model selector for status metadata" })),
|
|
thinking: Type.Optional(Type.String({ description: "Optional thinking level for status metadata" })),
|
|
}),
|
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
const accepted = getSupervisor(ctx).spawn(params as SpawnRequest);
|
|
ctx.ui?.notify?.(`Started subagent ${accepted.id}`, "info");
|
|
return textResult(accepted);
|
|
},
|
|
});
|
|
|
|
pi.registerTool({
|
|
name: "subagent_list",
|
|
label: "List subagents",
|
|
description: "List active and recent subagents for this parent session",
|
|
parameters: Type.Object({}),
|
|
async execute(_toolCallId, _params, _signal, _onUpdate, ctx) {
|
|
return textResult(getSupervisor(ctx).list());
|
|
},
|
|
});
|
|
|
|
pi.registerTool({
|
|
name: "subagent_status",
|
|
label: "Get subagent status",
|
|
description: "Get current lifecycle status for one subagent",
|
|
parameters: Type.Object({
|
|
id: Type.String({ description: "Subagent id returned by subagent_spawn" }),
|
|
}),
|
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
return textResult(getSupervisor(ctx).status(String((params as { id: unknown }).id)));
|
|
},
|
|
});
|
|
|
|
pi.registerTool({
|
|
name: "subagent_result",
|
|
label: "Get subagent result",
|
|
description: "Return still-running before completion and the final answer after completion",
|
|
parameters: Type.Object({
|
|
id: Type.String({ description: "Subagent id returned by subagent_spawn" }),
|
|
}),
|
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
return textResult(getSupervisor(ctx).result(String((params as { id: unknown }).id)));
|
|
},
|
|
});
|
|
|
|
pi.registerTool({
|
|
name: "subagent_cancel",
|
|
label: "Cancel subagent",
|
|
description: "Cancel a running subagent",
|
|
parameters: Type.Object({
|
|
id: Type.String({ description: "Subagent id returned by subagent_spawn" }),
|
|
}),
|
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
return textResult(await getSupervisor(ctx).cancel(String((params as { id: unknown }).id)));
|
|
},
|
|
});
|
|
|
|
pi.registerCommand("subagent-spawn", {
|
|
description: "Start an ad hoc independent subagent",
|
|
handler: async (args, ctx) => {
|
|
const accepted = getSupervisor(ctx).spawn({ prompt: args });
|
|
ctx.ui.notify(`Started subagent ${accepted.id}`, "info");
|
|
},
|
|
});
|
|
|
|
pi.registerCommand("subagent-list", {
|
|
description: "Show subagent status records",
|
|
handler: async (_args, ctx) => {
|
|
ctx.ui.notify(JSON.stringify(getSupervisor(ctx).list(), null, 2), "info");
|
|
},
|
|
});
|
|
|
|
pi.registerCommand("subagent-status", {
|
|
description: "Show a subagent status by id",
|
|
handler: async (args, ctx) => {
|
|
ctx.ui.notify(JSON.stringify(getSupervisor(ctx).status(args.trim()), null, 2), "info");
|
|
},
|
|
});
|
|
|
|
pi.registerCommand("subagent-result", {
|
|
description: "Show a subagent result by id",
|
|
handler: async (args, ctx) => {
|
|
ctx.ui.notify(JSON.stringify(getSupervisor(ctx).result(args.trim()), null, 2), "info");
|
|
},
|
|
});
|
|
|
|
pi.on("session_shutdown", async () => {
|
|
await supervisor?.shutdown();
|
|
supervisor = undefined;
|
|
});
|
|
}
|
|
|
|
function cwdOf(ctx: ExtensionContext): string {
|
|
const sessionCwd = (ctx as unknown as { sessionManager?: { getCwd?: () => string }; cwd?: string }).sessionManager?.getCwd?.();
|
|
return sessionCwd ?? (ctx as unknown as { cwd?: string }).cwd ?? process.cwd();
|
|
}
|
|
|
|
function textResult(value: unknown) {
|
|
return {
|
|
content: [{ type: "text" as const, text: JSON.stringify(value, null, 2) }],
|
|
details: value,
|
|
};
|
|
}
|