/**
 * PromiseProgressTracker — Goal-proximity signal for agent loops.
 *
 * Implements the Promise/Progress distinction from AgentPRM (arXiv:2511.08325):
 *   - Promise  = long-term proximity to goal (cosine similarity to goal embedding)
 *   - Progress = immediate step-over-step improvement (delta of promise values)
 *
 * The tracker embeds the goal text lazily (on first `observe` call) to keep
 * the constructor synchronous. Plateau detection flags runs that appear stuck
 * near the goal without converging.
 */
import { cosineSimilarity } from "../safety/immune.js";
// ─── Implementation ───────────────────────────────────────────────────────────
const DEFAULT_PLATEAU_EPSILON = 0.02;
const DEFAULT_PLATEAU_WINDOW = 5;
export class PromiseProgressTracker {
    embedFn;
    epsilon;
    windowSize;
    goalText;
    /** Resolved lazily on first observe(). */
    goalEmbedding = null;
    /** All observed promise values (cosine to goal). */
    promiseHistory = [];
    /**
     * Sliding deque of the last `windowSize` progress deltas.
     * Oldest at index 0; newest appended at the end.
     */
    deltaWindow = [];
    constructor(goal, embedFn, config) {
        this.goalText = goal;
        this.embedFn = embedFn;
        this.epsilon = config?.plateauEpsilon ?? DEFAULT_PLATEAU_EPSILON;
        this.windowSize = config?.plateauWindow ?? DEFAULT_PLATEAU_WINDOW;
    }
    /**
     * Embed `output`, compute cosine similarity to the goal, update internal
     * state, and return a snapshot.
     *
     * First call triggers lazy embedding of the goal text.
     */
    async observe(output) {
        // Lazy-initialise goal embedding.
        if (this.goalEmbedding === null) {
            this.goalEmbedding = await this.embedFn(this.goalText);
        }
        const outputEmbedding = await this.embedFn(output);
        const currentPromise = cosineSimilarity(this.goalEmbedding, outputEmbedding);
        // Compute step delta against the previous observation (0 on first step).
        const previousPromise = this.promiseHistory.length > 0
            ? this.promiseHistory[this.promiseHistory.length - 1]
            : currentPromise;
        const delta = currentPromise - previousPromise;
        this.promiseHistory.push(currentPromise);
        // Maintain a fixed-size deque for plateau detection.
        this.deltaWindow.push(delta);
        if (this.deltaWindow.length > this.windowSize) {
            this.deltaWindow.shift();
        }
        return this._buildSnapshot(currentPromise, delta);
    }
    /** Return the latest snapshot without recording a new observation. */
    snapshot() {
        const lastPromise = this.promiseHistory.length > 0
            ? this.promiseHistory[this.promiseHistory.length - 1]
            : 0;
        const lastDelta = this.deltaWindow.length > 0
            ? this.deltaWindow[this.deltaWindow.length - 1]
            : 0;
        return this._buildSnapshot(lastPromise, lastDelta);
    }
    /**
     * Re-baseline the tracker with a new goal (e.g. after a re-plan).
     * If `newGoal` is omitted the existing goal text is re-embedded (rare, but
     * useful after the embedding model changes).
     */
    async reset(newGoal) {
        if (newGoal !== undefined) {
            this.goalText = newGoal;
        }
        // Force re-embedding on next observe().
        this.goalEmbedding = null;
        this.promiseHistory = [];
        this.deltaWindow = [];
    }
    // ─── Private helpers ───────────────────────────────────────────────────────
    _buildSnapshot(currentPromise, delta) {
        return {
            promise: currentPromise,
            progress: delta,
            isPlateau: this._detectPlateau(),
            observations: this.promiseHistory.length,
        };
    }
    /**
     * Plateau = last `windowSize` deltas all satisfy |delta| < epsilon.
     * Requires a full window before declaring stall.
     */
    _detectPlateau() {
        if (this.deltaWindow.length < this.windowSize)
            return false;
        return this.deltaWindow.every((d) => Math.abs(d) < this.epsilon);
    }
}
//# sourceMappingURL=promise-progress.js.map