feat(pi): add subagent tracer bullet
This commit is contained in:
123
modules/agents/pi/extensions/subagents/index.ts
Normal file
123
modules/agents/pi/extensions/subagents/index.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user