/**
 * Filter wake cycles to remove noise: compaction-only cycles, cycles with
 * only a finish tool call, cycles that ended in API errors, and pure heartbeat
 * cycles (no user interrupts).  Heartbeat cycles with user interrupts pass
 * through to the main output since they contain interactive work.
 */
// ---------------------------------------------------------------------------
// Filter predicates (operate on raw WakeCycle before record building)
// ---------------------------------------------------------------------------
/**
 * A wake cycle whose only purpose was a compaction operation — the wake
 * context includes a `<local-command-stdout>Compacted` user message.
 */
function isCompactionOnlyCycle(cycle) {
    return cycle.entries.some((e) => (e.category === "send" || e.category === "incoming") &&
        e.message.includes("<local-command-stdout>Compacted"));
}
/**
 * A wake cycle that has no real tool calls — only `mcp__visionclaw__finish`
 * (or no tool calls at all).
 */
function lacksRealToolCallCycle(cycle) {
    return !cycle.entries.some((e) => e.category === "tool_call" &&
        e.message !== "mcp__visionclaw__finish");
}
/**
 * A wake cycle where the agent hit an API error (retryable or fatal) and
 * produced no useful work.  Only filtered when the cycle has errors AND
 * lacks any real tool calls — cycles that did meaningful work before the
 * error are kept.
 */
function isApiErrorOnlyCycle(cycle) {
    const hasApiError = cycle.entries.some((e) => e.category === "error" &&
        (e.message.includes("Agent loop error") ||
            e.message.includes("API Error")));
    return hasApiError && lacksRealToolCallCycle(cycle);
}
export function emptyFilterStats() {
    return { input: 0, compactionOnly: 0, lacksRealToolCall: 0, apiError: 0, heartbeat: 0, output: 0 };
}
export function mergeFilterStats(a, b) {
    return {
        input: a.input + b.input,
        compactionOnly: a.compactionOnly + b.compactionOnly,
        lacksRealToolCall: a.lacksRealToolCall + b.lacksRealToolCall,
        apiError: a.apiError + b.apiError,
        heartbeat: a.heartbeat + b.heartbeat,
        output: a.output + b.output,
    };
}
/**
 * Filter a list of wake cycles, removing noise and pure heartbeat cycles.
 * Heartbeat cycles with user interrupts stay in `filtered` (they contain
 * interactive work).  Pure heartbeat cycles are returned separately.
 */
export function filterCycles(cycles) {
    const stats = {
        input: cycles.length,
        compactionOnly: 0,
        lacksRealToolCall: 0,
        apiError: 0,
        heartbeat: 0,
        output: 0,
    };
    const filtered = [];
    const heartbeatCycles = [];
    for (const c of cycles) {
        if (isCompactionOnlyCycle(c)) {
            stats.compactionOnly++;
            continue;
        }
        if (isApiErrorOnlyCycle(c)) {
            stats.apiError++;
            continue;
        }
        if (lacksRealToolCallCycle(c)) {
            stats.lacksRealToolCall++;
            continue;
        }
        if (c.trigger === "heartbeat" && !c.hasInterrupts) {
            stats.heartbeat++;
            heartbeatCycles.push(c);
            continue;
        }
        filtered.push(c);
    }
    stats.output = filtered.length;
    return { filtered, heartbeatCycles, stats };
}
//# sourceMappingURL=filter.js.map