Luigit
repositories / pi-ext

pi-ext

bugabingas pi extensions

owned by admin

extensions/the-system/index.ts

Raw
import path from "node:path";
import {
	type ExtensionAPI,
	type ExtensionContext,
	isToolCallEventType,
} from "@earendil-works/pi-coding-agent";
import { loadStartupNotice, loadSystem, loadSystemCommand } from "./loaders.js";
import { closeDebug, dbg, span } from "./src/debug.ts";
import { isStaleContextError } from "./src/pi-ext-stale-context.ts";
import { systemArgumentCompletions } from "./system-command-meta.js";

type BoardRuntime = ReturnType<
	typeof import("./board-runtime.js")["createBoardRuntime"]
>;
type BundleSlotTool = ReturnType<
	typeof import("./bundle-slot-tool.js")["createBundleSlotTool"]
>;

interface OperationalRuntime {
	board: BoardRuntime;
	bundleSlot: BundleSlotTool;
}

const OPERATIONAL_TOOL_NAMES = new Set([
	"system_bundle_slot",
	"system_board_propose",
	"system_board_update",
]);

/** Skills exposed only inside a valid initialized System project: the
 * governing contracts (loaded once on demand instead of with every /system
 * stage message) and the document format skills. */
const PROJECT_SKILLS = [
	"the-system-documents",
	"the-system-markdown",
	"the-system-board",
	"the-system-exec",
	"the-system-gfm",
	"the-system-tex",
	"the-system-mermaid",
] as const;

export default function registerTheSystem(pi: ExtensionAPI): void {
	let generation = 0;
	let warningNotified = false;
	let missingDiscoveryWarningNotified = false;
	let bundleSlotRunActive = false;
	let bundleSlotUsed = false;
	let boardProposalRunActive = false;
	let boardUpdateRunActive = false;
	let bundleMigrationRunActive = false;
	// Load deferred tools only when /system starts governed work, keeping their
	// runtime graph out of routine Pi startup.
	let operational: OperationalRuntime | undefined;
	let operationalPromise: Promise<OperationalRuntime> | undefined;
	const loadOperationalRuntime = (): Promise<OperationalRuntime> => {
		if (operational) return Promise.resolve(operational);
		if (operationalPromise) return operationalPromise;
		operationalPromise = Promise.all([
			import("./board-runtime.js"),
			import("./bundle-slot-tool.js"),
		]).then(([{ createBoardRuntime }, { createBundleSlotTool }]) => {
			const runtime = {
				board: createBoardRuntime(),
				bundleSlot: createBundleSlotTool(() => {
					bundleSlotUsed = true;
				}),
			};
			pi.registerTool(runtime.bundleSlot);
			pi.registerTool(runtime.board.tool);
			pi.registerTool(runtime.board.updateTool);
			operational = runtime;
			return runtime;
		});
		return operationalPromise;
	};
	const activateBundleSlot = () => {
		if (!operational) throw new Error("The System tools are not loaded");
		bundleSlotRunActive = true;
		bundleSlotUsed = false;
	};
	const deactivateBundleSlot = () => {
		bundleSlotRunActive = false;
		bundleSlotUsed = false;
	};
	const activateBoardProposal = async (cwd: string) => {
		const board = operational?.board;
		if (!board) throw new Error("The System tools are not loaded");
		await board.beginProposal(cwd);
		boardProposalRunActive = true;
	};
	const deactivateBoardProposal = () => {
		operational?.board.endProposal();
		boardProposalRunActive = false;
	};
	const activateBoardUpdate = async (cwd: string) => {
		const board = operational?.board;
		if (!board) throw new Error("The System tools are not loaded");
		await board.beginUpdate(cwd);
		boardUpdateRunActive = true;
	};
	const deactivateBoardUpdate = () => {
		operational?.board.endUpdate();
		boardUpdateRunActive = false;
	};
	const warnMissingDiscovery = (ctx: ExtensionContext) => {
		if (missingDiscoveryWarningNotified) return;
		const active = pi.getActiveTools();
		if (
			[...OPERATIONAL_TOOL_NAMES].every((name) => active.includes(name)) ||
			active.includes("codemode") ||
			active.includes("tool_search")
		)
			return;
		missingDiscoveryWarningNotified = true;
		ctx.ui.notify(
			"System scoped tools are registered but unavailable to Pi. Enable codemode or tool_search to discover them.",
			"warning",
		);
	};
	const activateBundleMigration = () => {
		bundleMigrationRunActive = true;
	};
	const beginLifecycle = () => {
		if (bundleSlotRunActive) deactivateBundleSlot();
		if (boardProposalRunActive) deactivateBoardProposal();
		if (boardUpdateRunActive) deactivateBoardUpdate();
	};
	const notifyFailure = (
		ctx: ExtensionContext,
		label: string,
		error: unknown,
	) => {
		if (ctx.hasUI)
			ctx.ui.notify(
				`${label}: ${error instanceof Error ? error.message : String(error)}`,
				"warning",
			);
	};

	const syncRuntimeIndicator = async (
		ctx: ExtensionContext,
		ticket: number,
	) => {
		if (ticket !== generation) return;
		try {
			const { startupRuntimeState } = await loadStartupNotice();
			if (ticket !== generation) return;
			const runtime = await startupRuntimeState(ctx.cwd);
			if (ticket !== generation) return;
			if (ctx.hasUI) {
				ctx.ui.setStatus(
					"the-system",
					runtime.active
						? ctx.ui.theme.fg(
								"accent",
								` ${runtime.prefix ?? "?"}${runtime.phaseLabel ? ` · ${runtime.phaseLabel}` : ""}`,
							)
						: undefined,
				);
				if (runtime.notice && !warningNotified) {
					warningNotified = true;
					ctx.ui.notify(runtime.notice, "warning");
				}
			}
		} catch (error) {
			if (!isStaleContextError(error)) throw error;
		}
	};

	pi.on("resources_discover", async (event) => {
		const root = path.join(import.meta.dirname, "skills");
		const skillPaths = [path.join(root, "vertical-slices")];
		const { startupRuntimeState } = await loadStartupNotice();
		const runtime = await startupRuntimeState(event.cwd);
		if (runtime.active && !runtime.notice)
			skillPaths.push(...PROJECT_SKILLS.map((name) => path.join(root, name)));
		return { skillPaths };
	});
	pi.on("session_start", (_event, ctx) => {
		dbg?.("session.start");
		deactivateBundleSlot();
		deactivateBoardProposal();
		deactivateBoardUpdate();
		bundleMigrationRunActive = false;
		const ticket = ++generation;
		warningNotified = false;
		missingDiscoveryWarningNotified = false;
		const timer = setTimeout(() => {
			if (ticket === generation)
				void syncRuntimeIndicator(ctx, ticket).catch((error) =>
					notifyFailure(ctx, "The System startup check failed", error),
				);
		}, 0);
		timer.unref?.();
	});
	pi.on("session_shutdown", () => {
		dbg?.("session.shutdown");
		closeDebug();
		generation++;
		warningNotified = false;
		missingDiscoveryWarningNotified = false;
		bundleSlotRunActive = false;
		bundleSlotUsed = false;
		boardProposalRunActive = false;
		boardUpdateRunActive = false;
		bundleMigrationRunActive = false;
		operational?.board.shutdown();
	});
	pi.on("before_agent_start", async (event, ctx) => {
		void syncRuntimeIndicator(ctx, generation).catch((error) =>
			notifyFailure(ctx, "The System startup check failed", error),
		);
		const { composeSystemSection } = await loadSystem();
		const section = composeSystemSection(ctx.cwd);
		if (section) event.systemPromptOptions.sections["the_system"] = section;
	});
	pi.on("agent_settled", (_event, ctx) => {
		void syncRuntimeIndicator(ctx, generation).catch((error) =>
			notifyFailure(ctx, "The System status refresh failed", error),
		);
		if (bundleSlotRunActive && bundleSlotUsed) deactivateBundleSlot();
		if (boardProposalRunActive && operational?.board.proposalComplete())
			deactivateBoardProposal();
		if (boardUpdateRunActive && operational?.board.updateComplete())
			deactivateBoardUpdate();
		bundleMigrationRunActive = false;
	});
	pi.on("tool_call", async (event, ctx) => {
		if (
			!isToolCallEventType("write", event) &&
			!isToolCallEventType("edit", event)
		)
			return;
		const { preflightFileMutation } = await loadSystem();
		const ui = {
			hasUI: ctx.hasUI,
			select: (title: string, items: string[]) => ctx.ui.select(title, items),
		};
		const options = { bundleMigration: bundleMigrationRunActive };
		const result = isToolCallEventType("write", event)
			? await preflightFileMutation(ctx.cwd, "write", event.input, ui, options)
			: await preflightFileMutation(ctx.cwd, "edit", event.input, ui, options);
		return result ? { block: true, reason: result.reason } : undefined;
	});

	pi.registerCommand("system", {
		description: "The System project governance",
		getArgumentCompletions: systemArgumentCompletions,
		handler: async (args, ctx) => {
			const finish = span?.("command.handle");
			try {
				const [{ createSystemCommand }, runtime] = await Promise.all([
					loadSystemCommand(),
					loadOperationalRuntime(),
				]);
				warnMissingDiscovery(ctx);
				await createSystemCommand(pi, {
					beginLifecycle,
					activateBundleTool: activateBundleSlot,
					activateBoardProposal,
					activateBoardUpdate,
					activateBundleMigration,
					openBoard: runtime.board.open,
				}).handler(args, ctx);
				finish?.();
			} catch (error) {
				finish?.("error");
				throw error;
			}
		},
	});
}