feat: background_agent tool, detached launcher, status counts, extension integration, prompts/docs

This commit is contained in:
pi
2026-04-12 14:01:38 +01:00
parent 0c18d021df
commit 6c87a9a1a2
5 changed files with 274 additions and 5 deletions

View File

@@ -67,6 +67,46 @@ export default function subagentsExtension(pi: ExtensionAPI) {
monitorRun,
});
// background registry and helpers
const registry = (typeof createBackgroundRegistry === 'function') ? createBackgroundRegistry() : undefined;
let latestUi: { notify(message: string, type?: "info" | "warning" | "error"): void; setStatus(key: string, text: string | undefined): void } | undefined;
function renderCounts() {
if (!registry) return undefined;
const counts = registry.getCounts();
return counts.total === 0 ? undefined : `bg: ${counts.running} running / ${counts.total} total`;
}
function updateStatus() {
latestUi?.setStatus("pi-subagents", renderCounts());
}
async function watchBackgroundRun(runId: string) {
if (!registry) return;
const run = registry.getRun(runId);
if (!run || !run.paths || !run.paths.eventsPath || !run.paths.resultPath) return;
try {
const result = await monitorRun({ eventsPath: run.paths.eventsPath, resultPath: run.paths.resultPath });
const status = result.stopReason === "aborted" ? "aborted" : result.exitCode === 0 ? "completed" : "failed";
registry.recordUpdate(runId, { status, exitCode: result.exitCode, finalText: result.finalText, stopReason: result.stopReason });
try {
pi.appendEntry("pi-subagents:bg-update", { runId, status, finalText: result.finalText, exitCode: result.exitCode });
} catch (e) {}
updateStatus();
latestUi?.notify(`Background agent ${run.preset} finished: ${result.finalText || status}`, status === "completed" ? "info" : "error");
try {
pi.sendMessage({
customType: "pi-subagents:bg-complete",
content: `Background agent ${run.preset} completed (${runId}): ${result.finalText || status}`,
display: true,
details: { runId, status },
}, { triggerTurn: false });
} catch (e) {}
} catch (e) {
// monitorRun errors are non-fatal here
}
}
const runSingleTask = createConfiguredRunSingleTask({
loadConfig: (cwd) => loadSubagentsConfig(cwd),
processRunner,
@@ -98,6 +138,24 @@ export default function subagentsExtension(pi: ExtensionAPI) {
runSingleTask,
}),
);
// also register background tools when models available
try {
pi.registerTool(createBackgroundStatusTool({ registry }));
pi.registerTool(createBackgroundAgentTool({
parameters: createBackgroundAgentSchema(availableModels),
discoverSubagentPresets,
launchDetachedTask: processRunner.launchDetachedTask,
registerBackgroundRun(entry: any) {
if (!registry) return { running: 0, completed: 0, failed: 0, aborted: 0, total: 0 };
registry.recordLaunch({ runId: entry.runId, preset: entry.preset, task: entry.task, requestedModel: entry.requestedModel, resolvedModel: entry.resolvedModel, paths: entry.paths, meta: entry.meta });
return registry.getCounts();
},
watchBackgroundRun(runId: string) {
void watchBackgroundRun(runId);
},
}));
} catch (e) {}
};
const syncSubagentTool = (ctx: { modelRegistry: { getAvailable(): Array<{ provider: string; id: string }> } }) => {
@@ -105,10 +163,32 @@ export default function subagentsExtension(pi: ExtensionAPI) {
};
pi.on("session_start", (_event, ctx) => {
latestUi = ctx.ui;
// replay persisted runs if session manager provides entries
try {
const entries = ctx.sessionManager?.getEntries?.() ?? [];
const bgEntries = entries.filter((e: any) => e.type === "pi-subagents:bg-run" || e.type === "pi-subagents:bg-update");
// convert to registry runs if possible
const runs = bgEntries.map((be: any) => be.data?.run).filter(Boolean);
if (registry && runs.length) registry.replay(runs);
} catch (e) {}
updateStatus();
// reattach watchers for running runs
try {
if (registry) {
for (const r of registry.getSnapshot({ includeCompleted: true })) {
if (r.status === "running") void watchBackgroundRun(r.runId);
}
}
} catch (e) {}
syncSubagentTool(ctx);
});
pi.on("before_agent_start", (_event, ctx) => {
latestUi = ctx.ui;
syncSubagentTool(ctx);
});
}