/**
 * Codex Server Manager (Singleton)
 *
 * Manages the lifecycle of a single shared Codex app-server process.
 * Multiple sessions share one server; ref counting shuts it down
 * when the last session releases.
 *
 * Communicates via JSON-RPC 2.0 over JSONL stdio.
 *
 * @module
 */
import { spawn } from 'node:child_process';
import { CodexJsonRpcClient } from './jsonrpc-client.js';
/**
 * Manages a single shared Codex app-server instance.
 *
 * - `acquire()` starts or reuses the server, increments ref count
 * - `release()` decrements ref count, stops server at zero
 * - Concurrent acquire() calls are coalesced into a single startup
 */
class CodexServerManager {
    client = null;
    process = null;
    rpcClient = null;
    refCount = 0;
    startPromise = null;
    async acquire(config) {
        this.refCount++;
        if (this.client) {
            return this.client;
        }
        // Coalesce concurrent startup requests
        if (this.startPromise) {
            return this.startPromise;
        }
        this.startPromise = this.startServer(config);
        try {
            const client = await this.startPromise;
            return client;
        }
        catch (error) {
            this.refCount--;
            this.startPromise = null;
            throw error;
        }
    }
    release() {
        this.refCount = Math.max(0, this.refCount - 1);
        if (this.refCount === 0) {
            this.shutdown();
        }
    }
    shutdown() {
        if (this.rpcClient) {
            this.rpcClient.close();
            this.rpcClient = null;
        }
        if (this.process) {
            this.process.kill();
            this.process = null;
        }
        this.client = null;
        this.startPromise = null;
        this.refCount = 0;
    }
    async startServer(config) {
        const env = {
            ...process.env,
        };
        if (config?.stoneforgeRoot) {
            env.STONEFORGE_ROOT = config.stoneforgeRoot;
        }
        const child = spawn('codex', ['app-server'], {
            stdio: ['pipe', 'pipe', 'pipe'],
            cwd: config?.cwd,
            env,
        });
        // Wait for the process to confirm stdio is ready, or fail with a
        // descriptive error if the binary is missing / not executable.
        // Note: ENOENT can mean the binary OR the cwd doesn't exist.
        await new Promise((resolve, reject) => {
            child.on('error', (err) => {
                const detail = config?.cwd ? ` (cwd: ${config.cwd})` : '';
                reject(new Error(`Failed to spawn codex app-server${detail}: ${err.message}`));
            });
            child.on('spawn', () => resolve());
        });
        if (!child.stdin || !child.stdout) {
            child.kill();
            throw new Error('Failed to spawn codex app-server: stdio not available');
        }
        const rpcClient = new CodexJsonRpcClient(child.stdin, child.stdout);
        // Pipe stderr to debug logging
        child.stderr?.on('data', () => {
            // Debug: stderr output consumed to prevent backpressure
        });
        // Monitor process exit
        child.on('exit', () => {
            rpcClient.close();
            this.process = null;
            this.rpcClient = null;
            this.client = null;
            this.startPromise = null;
        });
        // Send initialize handshake
        await rpcClient.request('initialize', {
            clientInfo: { name: 'stoneforge', title: 'Stoneforge', version: '0.1.0' },
        });
        // Send initialized notification
        rpcClient.notify('initialized');
        // Register auto-approval handler for requestApproval server requests
        rpcClient.onServerRequest((id, method, _params) => {
            if (method === 'requestApproval') {
                rpcClient.respond(id, { decision: 'accept' });
            }
        });
        this.process = child;
        this.rpcClient = rpcClient;
        // Build typed client facade
        const notificationHandlers = new Set();
        rpcClient.onNotification((method, params) => {
            for (const handler of notificationHandlers) {
                handler(method, params);
            }
        });
        const client = {
            model: {
                list: (params) => rpcClient.request('model/list', params ?? {}),
            },
            thread: {
                start: (params) => rpcClient.request('thread/start', params),
                resume: (params) => rpcClient.request('thread/resume', params),
                read: (params) => rpcClient.request('thread/read', params),
            },
            turn: {
                start: (params) => rpcClient.request('turn/start', params),
                interrupt: (params) => rpcClient.request('turn/interrupt', params),
            },
            onNotification: (handler) => {
                notificationHandlers.add(handler);
                return () => { notificationHandlers.delete(handler); };
            },
            respondToServer: (id, result) => rpcClient.respond(id, result),
            close: () => rpcClient.close(),
        };
        this.client = client;
        this.startPromise = null;
        return client;
    }
}
/** Singleton instance */
export const serverManager = new CodexServerManager();
//# sourceMappingURL=server-manager.js.map