/**
 * Chat-surface policy for engine diagnostics. Heartbeat-watchdog gaps stay
 * internal regardless of callback attribution: the TUI remains responsive and
 * the diagnostic is operational noise rather than a concrete engine failure.
 * Concrete engine failures (termination, RPC errors) always surface.
 */
export function shouldRenderEngineDiagnosticAsChatError(diagnostic) {
    return diagnostic.level === "unresponsive" && diagnostic.source !== "watchdog";
}
const DEFAULT_THRESHOLDS = {
    diagnosticMs: 250,
    unresponsiveMs: 1_000,
    pollMs: 25,
};
export class ActivityWatchdog {
    constructor(options) {
        this.activities = new Map();
        this.emittedBlocking = false;
        this.emittedUnresponsive = false;
        this.now = options.now ?? performance.now.bind(performance);
        this.onDiagnostic = options.onDiagnostic;
        this.thresholds = { ...DEFAULT_THRESHOLDS, ...options.thresholds };
        this.lastHeartbeatAt = this.now();
    }
    start() {
        if (this.timer)
            return;
        this.timer = setInterval(() => this.inspect(), this.thresholds.pollMs);
        this.timer.unref?.();
    }
    stop() {
        if (this.timer)
            clearInterval(this.timer);
        this.timer = undefined;
        this.activities.clear();
    }
    heartbeat() {
        this.lastHeartbeatAt = this.now();
        this.emittedBlocking = false;
        this.emittedUnresponsive = false;
    }
    activityStarted(activity) {
        this.activities.delete(activity.id);
        this.activities.set(activity.id, activity);
    }
    activityFinished(activityId) {
        this.activities.delete(activityId);
    }
    inspect() {
        const elapsedMs = this.now() - this.lastHeartbeatAt;
        if (elapsedMs >= this.thresholds.unresponsiveMs && !this.emittedUnresponsive) {
            this.emittedUnresponsive = true;
            this.emit("unresponsive", elapsedMs);
            return;
        }
        if (elapsedMs >= this.thresholds.diagnosticMs && !this.emittedBlocking) {
            this.emittedBlocking = true;
            this.emit("blocking", elapsedMs);
        }
    }
    emit(level, elapsedMs) {
        const activity = [...this.activities.values()].at(-1);
        const label = activity ? `${activity.kind} ${activity.name}` : "unknown callback";
        const suffix = level === "unresponsive" ? "Esc interrupt · Ctrl+C terminate" : "the TUI remains responsive";
        this.onDiagnostic({
            activity,
            elapsedMs,
            level,
            message: `Engine callback ${label} has not yielded for ${Math.round(elapsedMs)} ms; ${suffix}`,
            source: "watchdog",
        });
    }
}
//# sourceMappingURL=activity-watchdog.js.map