← back to Kickbacks Ai Upstream

src/reloadSignal.ts

36 lines

import { join } from "node:path";
import { homedir } from "node:os";

export interface ReloadSentinel { version: string; ts: string; }

/** Fixed sentinel path shared by the deploy script and the extension watcher. */
export function reloadSentinelPath(): string {
  return join(homedir(), ".vibe-ads", "reload");
}

/** Parse the sentinel payload. Returns null for any malformed/half-written
 *  input or a payload missing `version` — callers must never throw on this. */
export function parseSentinel(raw: string): ReloadSentinel | null {
  try {
    const o = JSON.parse(raw) as Partial<ReloadSentinel>;
    if (!o || typeof o.version !== "string" || o.version.length === 0) return null;
    return { version: o.version, ts: typeof o.ts === "string" ? o.ts : "" };
  } catch { return null; }
}

export type ReloadDecision = "reload-now" | "nudge" | "none";

/** Pure decision: act only when the sentinel was touched strictly after this
 *  activation (armedAt) AND its version differs from the running build. Both
 *  actionable results ("reload-now"/"nudge") now map to the same soft
 *  ext-host restart in the consumer (no window reload, no prompt); the enum
 *  is kept for back-compat with existing callers/tests. "none" = no-op. */
export function decideReload(p: {
  mtimeMs: number; armedAt: number;
  sentinelVersion: string; runningVersion: string; debug: boolean;
}): ReloadDecision {
  if (p.mtimeMs <= p.armedAt) return "none";
  if (p.sentinelVersion === p.runningVersion) return "none";
  return p.debug ? "reload-now" : "nudge";
}