/**
 * GeminiLoopDriver (need-gem-002) — multi-turn re-prompt engine for the Gemini
 * mentee.
 *
 * A one-shot `gemini -p` runs one turn and exits, so a gemini mentee is bounded
 * to one-shot tasks (the #1 maiden-voyage lesson). Unlike Claude/codex — which
 * run as persistent hook-capable sessions and re-prompt via the autonomous Stop
 * hook — gemini runs as one-shots, so the loop is driven EXTERNALLY: each turn
 * re-spawns `gemini -m <model> -r <handle> -p "<nudge>"`, and gemini restores the
 * session's full context natively (empirically proven, gemini-cli 0.25.2). That
 * means the driver re-prompts with only the next instruction — never the
 * accumulated transcript — which is the quota-efficient design that respects the
 * no-overspend / subscription-auth rule.
 *
 * This is increment 1: the pure engine. Every side effect is INJECTED (the
 * gemini spawn, the budget gate, the session-handle capture, sleep, clock), so
 * the loop logic is fully unit-testable with zero real gemini calls / zero
 * quota. Lifecycle invocation (a route / the apprenticeship machinery calling
 * `run()`, plus the real `--list-sessions` handle parser) is increment 2.
 *
 * Spec: docs/specs/gemini-multi-turn-loop-driver.md
 */
import { buildGeminiOneShotArgv, buildGeminiResumeArgv, } from '../providers/adapters/gemini-cli/transport/geminiSpawn.js';
export const DEFAULT_DONE_SENTINEL = 'GEMINI_LOOP_DONE';
const DEFAULT_CONTINUATION = 'Continue toward the goal. If the goal is now fully complete, end your reply ' +
    `with the exact token ${DEFAULT_DONE_SENTINEL} on its own line.`;
export class GeminiLoopDriver {
    spawn;
    captureHandle;
    budgetGate;
    sleep;
    constructor(deps) {
        this.spawn = deps.spawn;
        this.captureHandle = deps.captureHandle;
        this.budgetGate = deps.budgetGate ?? (() => ({ ok: true }));
        this.sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
    }
    /**
     * Drive a multi-turn gemini task to one of: done-sentinel, max-turns,
     * budget-halt, spawn-failure, or handle-capture-failure. Pure orchestration —
     * no I/O except via the injected deps.
     */
    async run(opts) {
        const maxTurns = Math.max(1, Math.floor(opts.maxTurns));
        const doneSentinel = opts.doneSentinel ?? DEFAULT_DONE_SENTINEL;
        const continuation = opts.continuationPrompt ?? DEFAULT_CONTINUATION;
        const minInterval = Math.max(0, opts.minTurnIntervalMs ?? 0);
        const turns = [];
        // --- Turn 1 (one-shot; establishes the session) ---
        const gate0 = this.budgetGate();
        if (!gate0.ok) {
            return {
                sessionHandle: null,
                turns,
                stopReason: 'budget-halt',
                finalOutput: '',
                haltReason: gate0.reason,
            };
        }
        const argv1 = buildGeminiOneShotArgv(opts.model, opts.goalPrompt);
        const r1 = await this.spawn(argv1);
        const done1 = r1.stdout.includes(doneSentinel);
        turns.push({ turn: 1, argv: argv1, ...r1, done: done1 });
        if (r1.exitCode !== 0) {
            return { sessionHandle: null, turns, stopReason: 'spawn-failure', finalOutput: r1.stdout };
        }
        if (done1) {
            return { sessionHandle: null, turns, stopReason: 'done-sentinel', finalOutput: r1.stdout };
        }
        // --- Capture the stable handle (abort, never fall back to 'latest') ---
        const handle = await this.captureHandle(r1);
        if (!handle) {
            return {
                sessionHandle: null,
                turns,
                stopReason: 'handle-capture-failure',
                finalOutput: r1.stdout,
            };
        }
        // --- Turns 2..maxTurns (resume by stable handle) ---
        let lastOutput = r1.stdout;
        for (let turn = 2; turn <= maxTurns; turn++) {
            const gate = this.budgetGate();
            if (!gate.ok) {
                return {
                    sessionHandle: handle,
                    turns,
                    stopReason: 'budget-halt',
                    finalOutput: lastOutput,
                    haltReason: gate.reason,
                };
            }
            if (minInterval > 0) {
                await this.sleep(minInterval);
            }
            const argv = buildGeminiResumeArgv(opts.model, handle, continuation);
            const r = await this.spawn(argv);
            const done = r.stdout.includes(doneSentinel);
            turns.push({ turn, argv, ...r, done });
            lastOutput = r.stdout;
            if (r.exitCode !== 0) {
                return { sessionHandle: handle, turns, stopReason: 'spawn-failure', finalOutput: r.stdout };
            }
            if (done) {
                return { sessionHandle: handle, turns, stopReason: 'done-sentinel', finalOutput: r.stdout };
            }
        }
        return { sessionHandle: handle, turns, stopReason: 'max-turns', finalOutput: lastOutput };
    }
}
//# sourceMappingURL=GeminiLoopDriver.js.map