/** Heartbeat emitter — wraps long-running ops with periodic ticks. Replaces backend `with_heartbeat`. */
import { AgentLoopEventType } from './core-types.js';
/** 5s tick, 15s warn, 30s stalled — match backend `agent/config.py`. */
export const HEARTBEAT_INTERVAL_MS = 5_000;
export const HEARTBEAT_WARN_AFTER_MS = 15_000;
export const HEARTBEAT_STALLED_AFTER_MS = 30_000;
const realClock = {
    now: () => Date.now(),
    setInterval: (handler, ms) => setInterval(handler, ms),
    clearInterval: (handle) => clearInterval(handle)
};
function classifyTerminalStatus(error) {
    if (error instanceof Error) {
        if (error.name === 'AbortError' || /abort|cancel/i.test(error.message))
            return 'cancelled';
    }
    return 'failed';
}
export function createHeartbeatEmitter(emit, options = {}) {
    const intervalMs = options.intervalMs ?? HEARTBEAT_INTERVAL_MS;
    const clock = options.clock ?? realClock;
    const tickers = new Map();
    let nextOpId = 0;
    return {
        async wrap(promise, kind, meta = {}) {
            const opId = `op_${++nextOpId}`;
            const startedAt = clock.now();
            emit({ type: AgentLoopEventType.OpStart, opId, kind, meta, startedAt });
            tickers.set(opId, clock.setInterval(() => {
                emit({
                    type: AgentLoopEventType.HeartbeatTick,
                    opId,
                    kind,
                    meta,
                    startedAt,
                    elapsedMs: clock.now() - startedAt
                });
            }, intervalMs));
            try {
                const value = await promise;
                emit({
                    type: AgentLoopEventType.OpEnd,
                    opId,
                    kind,
                    meta,
                    startedAt,
                    endedAt: clock.now(),
                    status: 'completed'
                });
                return value;
            }
            catch (error) {
                emit({
                    type: AgentLoopEventType.OpEnd,
                    opId,
                    kind,
                    meta,
                    startedAt,
                    endedAt: clock.now(),
                    status: classifyTerminalStatus(error),
                    errorMessage: error instanceof Error ? error.message : String(error)
                });
                throw error;
            }
            finally {
                const handle = tickers.get(opId);
                if (handle !== undefined) {
                    clock.clearInterval(handle);
                    tickers.delete(opId);
                }
            }
        },
        dispose() {
            for (const handle of tickers.values())
                clock.clearInterval(handle);
            tickers.clear();
        }
    };
}
//# sourceMappingURL=heartbeat.js.map