repositories / pi-ext
pi-ext
bugabingas pi extensions
owned by admin
extensions/horst/index.ts
Raw// horst — per-host environment data and tool defaults.
import os from "node:os";
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
import { getHostSection, isHostSnapshotCached } from "./host.ts";
import {
EMPTY_HORST_SETTINGS,
type HorstSettings,
parseHorst,
resolveHorstTools,
} from "./settings.ts";
import { closeDebug, dbg, span } from "./src/debug.ts";
import {
resolveSetting,
type SettingDeclaration,
} from "./src/pi-ext-settings.ts";
// Precedence: trusted project, user, empty; objects deep-merge, project wins.
const HORST_SETTING: SettingDeclaration<HorstSettings> = {
key: "horst",
parse: parseHorst,
default: EMPTY_HORST_SETTINGS,
};
export default function horstExtension(pi: ExtensionAPI) {
let preferredShell: string | undefined;
pi.on("session_start", async (_event, ctx) => {
dbg?.("session.start", { mode: ctx.mode });
const setting = resolveSetting(pi, ctx, HORST_SETTING);
if (!setting.ok)
ctx.ui.notify(`horst: ${setting.error}; settings ignored`, "warning");
const settings = setting.ok ? setting.value : EMPTY_HORST_SETTINGS;
preferredShell = settings.preferredShell;
const apply = span?.("tools.apply");
const delta = resolveHorstTools(settings, process.platform, os.hostname());
if (!delta) {
apply?.("finish", { outcome: "no-match" });
return;
}
const known = new Set(pi.getAllTools().map((tool) => tool.name));
const unknown = [...(delta.add ?? []), ...(delta.remove ?? [])].filter(
(name) => !known.has(name),
);
if (unknown.length > 0)
ctx.ui.notify(`horst: unknown tools: ${unknown.join(", ")}`, "warning");
const active = pi.getActiveTools();
const next = active.filter((name) => !delta.remove?.includes(name));
for (const name of delta.add ?? []) {
if (known.has(name) && !next.includes(name)) next.push(name);
}
const addedCount = next.filter((name) => !active.includes(name)).length;
const removedCount = active.filter((name) => !next.includes(name)).length;
pi.setActiveTools(next);
apply?.("finish", {
outcome: addedCount > 0 || removedCount > 0 ? "updated" : "no-change",
addedCount,
removedCount,
unknownCount: unknown.length,
});
});
pi.on("before_agent_start", async (event) => {
const end = span?.("host.section", { cached: isHostSnapshotCached() });
try {
event.systemPromptOptions.sections.horst =
await getHostSection(preferredShell);
end?.();
} catch (error) {
end?.("error", { type: "host" });
throw error;
}
});
pi.on("session_shutdown", () => {
dbg?.("session.shutdown");
closeDebug();
});
}