/** * Main extension wiring. * * Lifecycle: * 1. Create vault controller (loads config, instantiates backend) * 2. Check backend availability * 3. Override managed providers with vault-backed streamSimple wrappers * 4. Register /vault command family * 5. Set status indicator * 6. Expose VaultExtensionApi for inter-extension communication */ import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent"; import { createVaultController } from "./config/controller.js"; import { overrideManagedProviders } from "./providers/override.js"; import { registerVaultCommand } from "./commands/vault-command.js"; import type { VaultExtensionApi } from "./types.js"; export default function vaultExtension(pi: ExtensionAPI): void { const controller = createVaultController(); let overriddenProviders: string[] = []; let lastContext: ExtensionContext | undefined; // ----------------------------------------------------------------------- // Provider state getter -- resolved at call time, not captured at startup // ----------------------------------------------------------------------- function getOverriddenProviders(): readonly string[] { return overriddenProviders; } // ----------------------------------------------------------------------- // Provider overrides (runs at load time) // ----------------------------------------------------------------------- // Override providers immediately so credential resolution is active // before any model selection or request happens. (async () => { const status = await controller.getBackendStatus(); if (status.available) { overriddenProviders = overrideManagedProviders(pi, controller); } else { // Backend unavailable at startup -- fall back silently. // Providers keep their native auth.json resolution. // User can diagnose with /vault verify. } })(); // ----------------------------------------------------------------------- // Commands // ----------------------------------------------------------------------- registerVaultCommand(pi, controller, getOverriddenProviders); // ----------------------------------------------------------------------- // Session events // ----------------------------------------------------------------------- pi.on("session_start", async (_event: unknown, ctx: ExtensionContext) => { lastContext = ctx; // Re-check backend and refresh overrides const status = await controller.getBackendStatus(); if (status.available) { if (overriddenProviders.length === 0) { // Backend became available after startup failure overriddenProviders = overrideManagedProviders(pi, controller); } const backend = controller.getBackend(); const stored = await backend.list(); const providerCount = overriddenProviders.length; ctx.ui.setStatus( "vault", `vault: ${controller.getConfig().backend} (${stored.length} creds, ${providerCount} providers)`, ); } else { ctx.ui.setStatus( "vault", `vault: ${controller.getConfig().backend} [unavailable]`, ); ctx.ui.notify( `Credential vault backend "${controller.getConfig().backend}" is not available. Using native auth.json fallback. Run /vault verify for details.`, "warning", ); } }); pi.on("session_switch", (_event: unknown, ctx: ExtensionContext) => { lastContext = ctx; }); // ----------------------------------------------------------------------- // Inter-extension API // ----------------------------------------------------------------------- const api: VaultExtensionApi = { getBackend() { return controller.getBackend(); }, getConfig() { return controller.getConfig(); }, }; // Expose API via EventBus for other extensions (e.g., multicodex). // Backend plugins register via: pi.events.on("credential-vault:register-backend", ...) // Consumers retrieve via: pi.events.emit("credential-vault:get-api", callback) pi.events.on("credential-vault:get-api", (callback: unknown) => { if (typeof callback === "function") { (callback as (api: VaultExtensionApi) => void)(api); } }); // Listen for backend plugin registrations pi.events.on("credential-vault:register-backend", (_data: unknown) => { // Reserved for future plugin API if (lastContext) { lastContext.ui.notify( "Backend plugin registration is not yet supported.", "info", ); } }); }