Luigit
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();
	});
}