import type { ExtensionContext } from "@earendil-works/pi-coding-agent"; import { existsSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { homedir } from "node:os"; export const REGISTRY_REFRESH_TTL_MS = 30_000; export interface RegistryRefreshState { lastRegistryRefreshAt?: number; forceRegistryRefresh?: boolean; } export interface BifrostModeState { enabled: boolean; pinned: boolean; classifierEnabled: boolean; silent: boolean; thinkingMode?: "off" | "advisory" | "apply"; thinkingPinned?: boolean; } export function shouldRefreshRegistry( state: RegistryRefreshState, now = Date.now(), ttlMs = REGISTRY_REFRESH_TTL_MS, ): boolean { if (state.forceRegistryRefresh) return true; if (state.lastRegistryRefreshAt === undefined) return true; return now - state.lastRegistryRefreshAt >= ttlMs; } function statusText(ctx: ExtensionContext, tone: "dim" | "accent" | "success" | "warning" | "error", message: string): string { const bullet = tone === "success" ? ctx.ui.theme.fg("success", "●") : tone === "warning" ? ctx.ui.theme.fg("warning", "●") : tone === "error" ? ctx.ui.theme.fg("error", "●") : tone === "accent" ? ctx.ui.theme.fg("accent", "●") : ctx.ui.theme.fg("dim", "●"); const textColor = tone === "success" ? "success" : tone === "warning" ? "warning" : tone === "error" ? "error" : tone === "accent" ? "accent" : "dim"; return `${bullet}${ctx.ui.theme.fg("dim", " Bifrost · ")}${ctx.ui.theme.fg(textColor, message)}`; } export function setBifrostStatus( ctx: ExtensionContext, message?: string, tone: "dim" | "accent" | "success" | "warning" | "error" = "dim", ): void { if (!ctx.hasUI) return; if (!message) { ctx.ui.setStatus("bifrost-state", undefined); return; } const text = statusText(ctx, tone, message); ctx.ui.setStatus("bifrost-state", text); } export function setBifrostWorkingMessage(ctx: ExtensionContext, message?: string): void { if (!ctx.hasUI) return; ctx.ui.setWorkingMessage(message); } function modeLabel(state: BifrostModeState): { tone: "warning" | "success"; text: string } { let text = "on"; let tone: "warning" | "success" = "success"; if (!state.enabled) { return { tone: "warning", text: "off" }; } if (state.pinned) { text = "pinned"; tone = "warning"; } else if (!state.classifierEnabled) { text = "on · classifier off"; tone = "warning"; } if (state.thinkingPinned) { text += ` · think:pinned`; tone = "warning"; } else if (state.thinkingMode && state.thinkingMode !== "off") { text += ` · think:${state.thinkingMode}`; } return { tone, text }; } export function formatBifrostStatus(state: { enabled: boolean; pinned: boolean; silent: boolean; thinkingMode?: string; thinkingPinned?: boolean }): string { const pin = state.pinned ? "\x1b[33mpinned\x1b[0m" : "\x1b[90munpinned\x1b[0m"; const sil = state.silent ? "\x1b[36msilence\x1b[0m" : "\x1b[32munsilence\x1b[0m"; const think = state.thinkingPinned ? `\x1b[38;5;208mthink:pinned\x1b[0m` : state.thinkingMode && state.thinkingMode !== "off" ? `\x1b[35mthink:${state.thinkingMode}\x1b[0m` : ""; const tilde = "\x1b[90m~\x1b[0m"; const name = state.enabled ? "\x1b[31mb\x1b[38;5;208mi\x1b[33mf\x1b[32mr\x1b[34mo\x1b[38;5;93ms\x1b[35mt\x1b[0m" : "\x1b[90mbifrost\x1b[0m"; return `${name}\x1b[90m:\x1b[0m ${pin} ${tilde} ${sil}${think ? ` ${tilde} ${think}` : ""}`; } export function setBifrostModeStatus(ctx: ExtensionContext, state: BifrostModeState): void { if (!ctx.hasUI) return; if (ctx.cwd) { try { const file = join(homedir(), ".pi", "agent", "statusline.json"); let current: any = {}; if (existsSync(file)) { try { current = JSON.parse(readFileSync(file, "utf-8")); } catch {} } current.bifrost = { enabled: state.enabled, pinned: state.pinned, silent: state.silent, thinkingMode: state.thinkingMode, thinkingPinned: state.thinkingPinned ?? false, }; writeFileSync(file, JSON.stringify(current, null, 2), "utf-8"); } catch {} } const label = modeLabel(state); const text = statusText(ctx, label.tone, label.text); ctx.ui.setStatus("bifrost-state", text); ctx.ui.setStatus("bifrost", formatBifrostStatus(state)); }