"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkLatestDomindsVersionNow = checkLatestDomindsVersionNow;
exports.configureDomindsSelfUpdate = configureDomindsSelfUpdate;
exports.setDomindsSelfUpdateBroadcaster = setDomindsSelfUpdateBroadcaster;
exports.getDomindsSelfUpdateStatus = getDomindsSelfUpdateStatus;
exports.installLatestDominds = installLatestDominds;
exports.restartDomindsIntoLatest = restartDomindsIntoLatest;
exports.forceRestartDominds = forceRestartDominds;
const child_process_1 = require("child_process");
const crypto_1 = require("crypto");
const promises_1 = __importDefault(require("fs/promises"));
const path_1 = __importDefault(require("path"));
const time_1 = require("@longrun-ai/kernel/utils/time");
const log_1 = require("../log");
const rtws_1 = require("../rtws");
const supervisor_protocol_1 = require("../supervisor-protocol");
const dominds_running_version_1 = require("./dominds-running-version");
const log = (0, log_1.createLogger)('dominds-self-update');
const BACKGROUND_CHECK_INTERVAL_MS = 30 * 60 * 1000;
const LATEST_VERSION_CHECK_TIMEOUT_MS = 60_000;
const COMMAND_OUTPUT_LOG_LIMIT = 2_000;
const PROXY_URL_ENV_KEYS = new Set([
    'HTTP_PROXY',
    'http_proxy',
    'HTTPS_PROXY',
    'https_proxy',
    'npm_config_proxy',
    'npm_config_https_proxy',
]);
const IDLE_RESTART_STATE = { kind: 'idle' };
const PROCESS_START_ARGV = [...process.argv];
const PROCESS_START_CWD = (0, rtws_1.domindsRtwsRootAbs)();
let runtimeConfig = null;
let latestObservation = { kind: 'unknown' };
let backgroundCheckTimer = null;
let installPromise = null;
let restartPromise = null;
let restartState = IDLE_RESTART_STATE;
let installFailureObservation = null;
let broadcastStatusUpdate = null;
let activeRestartTraceFile = null;
function normalizeVersionString(value) {
    return value.trim().replace(/^v/i, '');
}
function splitNumericParts(raw) {
    const cleaned = normalizeVersionString(raw).split('+')[0]?.split('-')[0]?.trim() ?? '';
    if (cleaned === '')
        return null;
    const parts = cleaned.split('.');
    const parsed = [];
    for (const part of parts) {
        if (!/^\d+$/.test(part))
            return null;
        parsed.push(Number(part));
    }
    return parsed;
}
function compareVersions(a, b) {
    const aParts = splitNumericParts(a);
    const bParts = splitNumericParts(b);
    if (aParts === null || bParts === null)
        return null;
    const maxLength = Math.max(aParts.length, bParts.length);
    for (let i = 0; i < maxLength; i++) {
        const aValue = aParts[i] ?? 0;
        const bValue = bParts[i] ?? 0;
        if (aValue > bValue)
            return 1;
        if (aValue < bValue)
            return -1;
    }
    return 0;
}
function isVersionGreaterThan(a, b) {
    const comparison = compareVersions(a, b);
    return comparison !== null ? comparison > 0 : a !== b;
}
function isVersionAtLeast(a, b) {
    const comparison = compareVersions(a, b);
    return comparison !== null ? comparison >= 0 : a === b;
}
function detectRunKind(mode) {
    if (mode !== 'production')
        return 'disabled';
    const scriptPath = (PROCESS_START_ARGV[1] ?? '').replace(/\\/g, '/');
    if (scriptPath.includes('/_npx/') && scriptPath.includes('/node_modules/dominds/')) {
        return 'npx';
    }
    return 'global';
}
function normalizeComparablePath(value) {
    const normalized = path_1.default.normalize(value);
    return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
}
function sanitizeDebugFileSegment(value) {
    const sanitized = value.replace(/[^A-Za-z0-9._-]+/g, '_').replace(/^_+|_+$/g, '');
    return sanitized.length > 0 ? sanitized.slice(0, 80) : 'unknown';
}
function buildRestartTraceContext() {
    const capturedAt = (0, time_1.formatUnifiedTimestamp)(new Date());
    const debugDir = path_1.default.resolve((0, rtws_1.domindsRtwsRootAbs)(), '.dialogs', 'debug');
    const traceFile = path_1.default.join(debugDir, [
        'dominds-self-update-restart',
        sanitizeDebugFileSegment(capturedAt),
        String(process.pid),
        `${(0, crypto_1.randomUUID)()}.jsonl`,
    ].join('-'));
    return { debugDir, traceFile };
}
async function appendRestartTrace(trace, event, details = {}) {
    const payload = {
        ...details,
        event,
        capturedAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
        pid: process.pid,
        platform: process.platform,
        rtwsRootAbs: (0, rtws_1.domindsRtwsRootAbs)(),
    };
    await promises_1.default.mkdir(trace.debugDir, { recursive: true });
    await promises_1.default.appendFile(trace.traceFile, `${JSON.stringify(payload)}\n`, 'utf-8');
}
function appendRestartTraceSoon(trace, event, details = {}) {
    void appendRestartTrace(trace, event, details).catch((error) => {
        log.warn('Failed to write Dominds restart trace', error, {
            traceFile: trace.traceFile,
            event,
        });
    });
}
async function appendRestartTraceBestEffort(trace, event, details = {}) {
    try {
        await appendRestartTrace(trace, event, details);
    }
    catch (error) {
        log.warn('Failed to write Dominds restart trace', error, {
            traceFile: trace.traceFile,
            event,
        });
    }
}
async function getComparableRealPath(absPath) {
    try {
        return normalizeComparablePath(await promises_1.default.realpath(absPath));
    }
    catch (error) {
        const code = getStringErrorProp(error, 'code');
        if (code === 'ENOENT')
            return normalizeComparablePath(path_1.default.resolve(absPath));
        throw error;
    }
}
function getCurrentProcessEntrypoint() {
    const entrypoint = PROCESS_START_ARGV[1];
    if (typeof entrypoint !== 'string' || entrypoint.trim() === '') {
        throw new Error('Cannot restart Dominds because the current process entrypoint is unavailable');
    }
    return entrypoint;
}
function getNpxWorkspaceDirAbs() {
    const entrypoint = getCurrentProcessEntrypoint().replace(/\\/g, '/');
    const marker = '/node_modules/dominds/';
    const markerIndex = entrypoint.indexOf(marker);
    if (markerIndex < 0)
        return null;
    const nodeModulesPrefix = entrypoint.slice(0, markerIndex);
    const nodeModulesSuffix = '/node_modules';
    if (!nodeModulesPrefix.endsWith(nodeModulesSuffix))
        return null;
    const workspaceDir = nodeModulesPrefix.slice(0, -nodeModulesSuffix.length);
    if (workspaceDir.trim() === '')
        return null;
    return path_1.default.resolve(PROCESS_START_CWD, workspaceDir);
}
function parseNpxDomindsPackageSpec(spec) {
    if (spec === 'dominds@latest')
        return { kind: 'latest' };
    if (spec === 'dominds')
        return { kind: 'versionless' };
    if (spec.startsWith('dominds@'))
        return { kind: 'fixed', spec };
    return null;
}
async function readNpxDomindsSpec() {
    const workspaceDir = getNpxWorkspaceDirAbs();
    if (workspaceDir === null)
        return null;
    const packageJsonPath = path_1.default.join(workspaceDir, 'package.json');
    const raw = await promises_1.default.readFile(packageJsonPath, 'utf8');
    const parsed = JSON.parse(raw);
    if (!isRecord(parsed))
        throw new Error(`Invalid npx package metadata at ${packageJsonPath}`);
    const npxValue = parsed['_npx'];
    const packagesValue = isRecord(npxValue) ? npxValue['packages'] : undefined;
    if (!Array.isArray(packagesValue))
        return null;
    for (const packageSpec of packagesValue) {
        if (typeof packageSpec !== 'string')
            continue;
        const parsedSpec = parseNpxDomindsPackageSpec(packageSpec.trim());
        if (parsedSpec !== null)
            return parsedSpec;
    }
    return null;
}
function truncateCommandOutput(value) {
    const raw = typeof value === 'string' ? value.trim() : '';
    if (raw.length <= COMMAND_OUTPUT_LOG_LIMIT)
        return raw;
    return `${raw.slice(0, COMMAND_OUTPUT_LOG_LIMIT)}...[truncated ${raw.length - COMMAND_OUTPUT_LOG_LIMIT} chars]`;
}
function formatPathEnvExcerpt(pathEnv) {
    if (pathEnv === null || pathEnv.trim() === '')
        return null;
    const parts = pathEnv.split(path_1.default.delimiter).filter((part) => part.trim() !== '');
    if (parts.length === 0)
        return null;
    const visibleParts = parts.slice(0, 8);
    const preview = visibleParts.join(path_1.default.delimiter);
    if (parts.length <= visibleParts.length)
        return preview;
    return `${preview}${path_1.default.delimiter}...[+${parts.length - visibleParts.length} more]`;
}
function getEnvValue(env, upperKey, lowerKey) {
    const upperValue = env[upperKey];
    if (typeof upperValue === 'string' && upperValue.trim() !== '')
        return upperValue;
    const lowerValue = env[lowerKey];
    if (typeof lowerValue === 'string' && lowerValue.trim() !== '')
        return lowerValue;
    return null;
}
function redactProxyUrl(proxyUrl) {
    try {
        const parsed = new URL(proxyUrl);
        if (parsed.username !== '')
            parsed.username = '***';
        if (parsed.password !== '')
            parsed.password = '***';
        return parsed.toString();
    }
    catch {
        return proxyUrl.replace(/\/\/[^@/]+@/g, '//***@');
    }
}
function buildPackageManagerCommandEnv() {
    const env = { ...process.env };
    const httpProxy = getEnvValue(env, 'HTTP_PROXY', 'http_proxy');
    const httpsProxy = getEnvValue(env, 'HTTPS_PROXY', 'https_proxy');
    const noProxy = getEnvValue(env, 'NO_PROXY', 'no_proxy');
    if (httpProxy !== null) {
        env.HTTP_PROXY = httpProxy;
        env.http_proxy = httpProxy;
        env.npm_config_proxy = httpProxy;
    }
    if (httpsProxy !== null) {
        env.HTTPS_PROXY = httpsProxy;
        env.https_proxy = httpsProxy;
        env.npm_config_https_proxy = httpsProxy;
    }
    if (noProxy !== null) {
        env.NO_PROXY = noProxy;
        env.no_proxy = noProxy;
        env.npm_config_noproxy = noProxy;
    }
    env.npm_config_progress = 'false';
    env.npm_config_update_notifier = 'false';
    env.NO_UPDATE_NOTIFIER = '1';
    return env;
}
function summarizeNpmProxyEnv(env) {
    const summary = {};
    for (const key of [
        'HTTP_PROXY',
        'http_proxy',
        'HTTPS_PROXY',
        'https_proxy',
        'NO_PROXY',
        'no_proxy',
        'npm_config_proxy',
        'npm_config_https_proxy',
        'npm_config_noproxy',
    ]) {
        const value = env[key];
        if (typeof value !== 'string' || value.trim() === '')
            continue;
        summary[key] = PROXY_URL_ENV_KEYS.has(key) ? redactProxyUrl(value) : value;
    }
    return summary;
}
async function resolveNpmCommandSpec() {
    const nodeDir = path_1.default.dirname(process.execPath);
    const candidates = process.platform === 'win32'
        ? [path_1.default.join(nodeDir, 'node_modules', 'npm', 'bin', 'npm-cli.js')]
        : [
            path_1.default.join(nodeDir, '..', 'lib', 'node_modules', 'npm', 'bin', 'npm-cli.js'),
            path_1.default.join(nodeDir, '..', 'share', 'nodejs', 'npm', 'bin', 'npm-cli.js'),
        ];
    for (const candidate of candidates) {
        try {
            await promises_1.default.access(candidate);
            return {
                command: process.execPath,
                args: [candidate],
                display: process.execPath + ' ' + candidate,
            };
        }
        catch {
            continue;
        }
    }
    if (process.platform === 'win32') {
        throw new Error('Cannot find bundled npm CLI at ' +
            candidates.join(', ') +
            '. Dominds self-update avoids npm.cmd so the Windows console title is not hijacked.');
    }
    return { command: 'npm', args: [], display: 'npm' };
}
async function resolveCorepackPnpmCommandSpec() {
    const nodeDir = path_1.default.dirname(process.execPath);
    const candidates = process.platform === 'win32'
        ? [path_1.default.join(nodeDir, 'node_modules', 'corepack', 'dist', 'corepack.js')]
        : [
            path_1.default.join(nodeDir, '..', 'lib', 'node_modules', 'corepack', 'dist', 'corepack.js'),
            path_1.default.join(nodeDir, '..', 'share', 'nodejs', 'corepack', 'dist', 'corepack.js'),
        ];
    for (const candidate of candidates) {
        try {
            await promises_1.default.access(candidate);
            return {
                command: process.execPath,
                args: [candidate, 'pnpm'],
                display: process.execPath + ' ' + candidate + ' pnpm',
            };
        }
        catch {
            continue;
        }
    }
    return null;
}
async function resolvePnpmCommandSpec() {
    const userAgent = process.env.npm_config_user_agent;
    if (typeof userAgent === 'string' && userAgent.startsWith('pnpm/')) {
        const npmExecpath = process.env.npm_execpath;
        if (typeof npmExecpath === 'string' && npmExecpath.trim() !== '') {
            return {
                command: process.execPath,
                args: [npmExecpath],
                display: process.execPath + ' ' + npmExecpath,
            };
        }
    }
    const corepackPnpm = await resolveCorepackPnpmCommandSpec();
    if (corepackPnpm !== null)
        return corepackPnpm;
    if (process.platform === 'win32') {
        throw new Error('Cannot resolve pnpm CLI without pnpm npm_execpath or bundled Corepack on Windows');
    }
    return { command: 'pnpm', args: [], display: 'pnpm' };
}
function getStringOrNumberErrorProp(error, key) {
    const value = getErrorProp(error, key);
    return typeof value === 'string' || typeof value === 'number' ? value : null;
}
function getStringErrorProp(error, key) {
    const value = getErrorProp(error, key);
    return typeof value === 'string' ? value : null;
}
function getBooleanErrorProp(error, key) {
    const value = getErrorProp(error, key);
    return typeof value === 'boolean' ? value : null;
}
function createCommandFailureError(params) {
    const reason = params.timedOut
        ? `Command timed out after ${params.durationMs}ms: ${params.cmd}`
        : params.outputExceeded
            ? `Command exceeded output limit after capturing ${params.capturedOutputChars}/${params.maxBuffer} chars: ${params.cmd}`
            : `Command failed: ${params.cmd}`;
    const error = new Error(reason);
    if (params.cause instanceof Error) {
        error.cause = params.cause;
    }
    error.cmd = params.cmd;
    error.code = params.code;
    error.signal = params.signal;
    error.killed = params.killed;
    error.stdout = params.stdout;
    error.stderr = params.stderr;
    error.domindsDurationMs = params.durationMs;
    error.domindsTimeoutMs = params.timeoutMs;
    error.domindsTimedOut = params.timedOut;
    error.domindsOutputExceeded = params.outputExceeded;
    error.domindsCapturedOutputChars = params.capturedOutputChars;
    error.domindsMaxBuffer = params.maxBuffer;
    error.domindsProxyEnvSummary = params.proxyEnvSummary;
    return error;
}
async function runPackageManagerCommand(commandSpec, args, params) {
    const env = buildPackageManagerCommandEnv();
    const startedAtMs = Date.now();
    const cmd = [commandSpec.display, ...args].join(' ');
    return await new Promise((resolve, reject) => {
        const child = (0, child_process_1.spawn)(commandSpec.command, [...commandSpec.args, ...args], {
            cwd: PROCESS_START_CWD,
            env,
            stdio: ['ignore', 'pipe', 'pipe'],
            windowsHide: true,
            shell: false,
        });
        let stdout = '';
        let stderr = '';
        let settled = false;
        let timedOut = false;
        let outputExceeded = false;
        const finish = (fn) => {
            if (settled)
                return;
            settled = true;
            fn();
        };
        const timer = setTimeout(() => {
            timedOut = true;
            child.kill('SIGTERM');
        }, params.timeoutMs);
        const appendOutput = (stream, chunk) => {
            if (outputExceeded)
                return;
            const next = chunk.toString('utf8');
            if (stream === 'stdout') {
                stdout += next;
            }
            else {
                stderr += next;
            }
            if (stdout.length + stderr.length <= params.maxBuffer)
                return;
            outputExceeded = true;
            child.kill('SIGTERM');
        };
        child.stdout?.on('data', (chunk) => {
            appendOutput('stdout', chunk);
        });
        child.stderr?.on('data', (chunk) => {
            appendOutput('stderr', chunk);
        });
        child.once('error', (error) => {
            clearTimeout(timer);
            finish(() => {
                reject(createCommandFailureError({
                    cmd,
                    durationMs: Date.now() - startedAtMs,
                    timeoutMs: params.timeoutMs,
                    timedOut,
                    outputExceeded,
                    capturedOutputChars: stdout.length + stderr.length,
                    maxBuffer: params.maxBuffer,
                    code: getStringOrNumberErrorProp(error, 'code'),
                    signal: getStringErrorProp(error, 'signal'),
                    killed: getBooleanErrorProp(error, 'killed'),
                    stdout: truncateCommandOutput(stdout),
                    stderr: truncateCommandOutput(stderr),
                    proxyEnvSummary: summarizeNpmProxyEnv(env),
                }));
            });
        });
        child.once('close', (code, signal) => {
            clearTimeout(timer);
            finish(() => {
                if (code === 0) {
                    resolve({ stdout, stderr });
                    return;
                }
                reject(createCommandFailureError({
                    cmd,
                    durationMs: Date.now() - startedAtMs,
                    timeoutMs: params.timeoutMs,
                    timedOut,
                    outputExceeded,
                    capturedOutputChars: stdout.length + stderr.length,
                    maxBuffer: params.maxBuffer,
                    code: code === null ? null : code,
                    signal: signal === null ? null : signal,
                    killed: timedOut || child.killed || null,
                    stdout: truncateCommandOutput(stdout),
                    stderr: truncateCommandOutput(stderr),
                    proxyEnvSummary: summarizeNpmProxyEnv(env),
                }));
            });
        });
    });
}
async function runCurrentDomindsVersionProbe() {
    const startedAtMs = Date.now();
    const timeoutMs = 15_000;
    return await new Promise((resolve, reject) => {
        const child = (0, child_process_1.spawn)(process.execPath, [getCurrentProcessEntrypoint(), '--version'], {
            cwd: PROCESS_START_CWD,
            env: process.env,
            stdio: ['ignore', 'pipe', 'pipe'],
            windowsHide: true,
            shell: false,
        });
        let stdout = '';
        let stderr = '';
        let settled = false;
        let timedOut = false;
        const finish = (fn) => {
            if (settled)
                return;
            settled = true;
            fn();
        };
        const timer = setTimeout(() => {
            timedOut = true;
            child.kill('SIGTERM');
        }, timeoutMs);
        child.stdout?.on('data', (chunk) => {
            stdout += chunk.toString('utf8');
        });
        child.stderr?.on('data', (chunk) => {
            stderr += chunk.toString('utf8');
        });
        child.once('error', (error) => {
            clearTimeout(timer);
            finish(() => reject(error));
        });
        child.once('close', (code, signal) => {
            clearTimeout(timer);
            finish(() => {
                if (code === 0) {
                    resolve({ stdout, stderr });
                    return;
                }
                const detail = timedOut
                    ? `timed out after ${String(Date.now() - startedAtMs)}ms`
                    : `exited with code=${String(code)} signal=${String(signal)}`;
                reject(new Error(`current Dominds entrypoint --version probe ${detail}; stdout=${truncateCommandOutput(stdout)} stderr=${truncateCommandOutput(stderr)}`));
            });
        });
    });
}
function getErrorProp(error, key) {
    if (typeof error !== 'object' || error === null)
        return undefined;
    return error[key];
}
function isRecord(value) {
    return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function stringRecordOnly(value) {
    if (!isRecord(value))
        return {};
    const result = {};
    for (const [key, entry] of Object.entries(value)) {
        if (typeof entry === 'string')
            result[key] = entry;
    }
    return result;
}
function extractCommandFailureDiagnostics(error) {
    const code = getErrorProp(error, 'code');
    const signal = getErrorProp(error, 'signal');
    const killed = getErrorProp(error, 'killed');
    const cmd = getErrorProp(error, 'cmd');
    const durationMs = getErrorProp(error, 'domindsDurationMs');
    const timeoutMs = getErrorProp(error, 'domindsTimeoutMs');
    const timedOut = getErrorProp(error, 'domindsTimedOut');
    const outputExceeded = getErrorProp(error, 'domindsOutputExceeded');
    const capturedOutputChars = getErrorProp(error, 'domindsCapturedOutputChars');
    const maxBuffer = getErrorProp(error, 'domindsMaxBuffer');
    return {
        message: error instanceof Error ? error.message : String(error),
        cmd: typeof cmd === 'string' && cmd.trim() !== '' ? cmd : null,
        code: typeof code === 'string' || typeof code === 'number' ? code : null,
        signal: typeof signal === 'string' && signal.trim() !== '' ? signal : null,
        killed: typeof killed === 'boolean' ? killed : null,
        stdout: truncateCommandOutput(getErrorProp(error, 'stdout')),
        stderr: truncateCommandOutput(getErrorProp(error, 'stderr')),
        cwd: (0, rtws_1.domindsRtwsRootAbs)(),
        pathEnv: typeof process.env.PATH === 'string' ? process.env.PATH : null,
        durationMs: typeof durationMs === 'number' ? durationMs : null,
        timeoutMs: typeof timeoutMs === 'number' ? timeoutMs : null,
        timedOut: typeof timedOut === 'boolean' ? timedOut : false,
        outputExceeded: typeof outputExceeded === 'boolean' ? outputExceeded : false,
        capturedOutputChars: typeof capturedOutputChars === 'number' ? capturedOutputChars : null,
        maxBuffer: typeof maxBuffer === 'number' ? maxBuffer : null,
        proxyEnvSummary: stringRecordOnly(getErrorProp(error, 'domindsProxyEnvSummary')),
    };
}
function formatCommandFailureForUi(diagnostics) {
    const lines = [diagnostics.message];
    if (diagnostics.cmd !== null) {
        lines.push(`cmd: ${diagnostics.cmd}`);
    }
    if (diagnostics.code !== null) {
        lines.push(`exit: ${String(diagnostics.code)}`);
    }
    if (diagnostics.signal !== null) {
        lines.push(`signal: ${diagnostics.signal}`);
    }
    if (diagnostics.durationMs !== null) {
        lines.push(`durationMs: ${diagnostics.durationMs}`);
    }
    if (diagnostics.timeoutMs !== null) {
        lines.push(`timeoutMs: ${diagnostics.timeoutMs}`);
    }
    if (diagnostics.capturedOutputChars !== null) {
        lines.push(`capturedOutputChars: ${diagnostics.capturedOutputChars}`);
    }
    if (diagnostics.maxBuffer !== null) {
        lines.push(`maxBuffer: ${diagnostics.maxBuffer}`);
    }
    lines.push(`cwd: ${diagnostics.cwd}`);
    if (diagnostics.stderr !== '') {
        lines.push(`stderr: ${diagnostics.stderr}`);
        return lines.join('\n');
    }
    if (diagnostics.stdout !== '') {
        lines.push(`stdout: ${diagnostics.stdout}`);
        return lines.join('\n');
    }
    const pathEnvExcerpt = formatPathEnvExcerpt(diagnostics.pathEnv);
    if (pathEnvExcerpt !== null) {
        lines.push(`PATH: ${pathEnvExcerpt}`);
    }
    for (const [key, value] of Object.entries(diagnostics.proxyEnvSummary)) {
        lines.push(`${key}: ${value}`);
    }
    const lowerMessage = diagnostics.message.toLowerCase();
    if (diagnostics.timedOut) {
        lines.push('hint: npm view exceeded the Dominds latest-version check timeout before producing output');
    }
    else if (diagnostics.outputExceeded) {
        lines.push('hint: npm produced more output than Dominds expected for this command');
    }
    else if (diagnostics.code === 'ENOENT' ||
        lowerMessage.includes('not recognized as an internal or external command') ||
        lowerMessage.includes('spawn npm enoent')) {
        lines.push('hint: npm is not available to the server process on PATH');
    }
    else if (lowerMessage.includes('econn') ||
        lowerMessage.includes('etimedout') ||
        lowerMessage.includes('certificate') ||
        lowerMessage.includes('self signed') ||
        lowerMessage.includes('registry')) {
        lines.push('hint: this looks like a registry, network, proxy, or certificate problem');
    }
    else if (diagnostics.code === 1 && diagnostics.stderr === '' && diagnostics.stdout === '') {
        lines.push('hint: npm exited without output; check registry access and npm config in the same shell');
    }
    return lines.join('\n');
}
async function queryLatestVersion() {
    const checkedAt = (0, time_1.formatUnifiedTimestamp)(new Date());
    try {
        const { stdout } = await runPackageManagerCommand(await resolveNpmCommandSpec(), ['view', 'dominds', 'version', '--json'], {
            maxBuffer: 1024 * 1024,
            timeoutMs: LATEST_VERSION_CHECK_TIMEOUT_MS,
        });
        const trimmed = stdout.trim();
        if (trimmed === '') {
            log.warn('Dominds latest-version check returned empty stdout', undefined, { checkedAt });
            return {
                kind: 'error',
                errorText: 'npm view dominds version returned empty stdout',
                checkedAt,
            };
        }
        let parsed;
        try {
            parsed = JSON.parse(trimmed);
        }
        catch {
            parsed = trimmed;
        }
        const latestVersion = typeof parsed === 'string' ? parsed.trim() : '';
        if (latestVersion === '') {
            log.warn('Dominds latest-version check returned an invalid payload', undefined, {
                checkedAt,
                stdout: trimmed,
            });
            return {
                kind: 'error',
                errorText: `npm view dominds version returned non-string output: ${trimmed}`,
                checkedAt,
            };
        }
        return { kind: 'ok', latestVersion, checkedAt };
    }
    catch (error) {
        const diagnostics = extractCommandFailureDiagnostics(error);
        const errorText = formatCommandFailureForUi(diagnostics);
        log.warn('Dominds latest-version check failed', error, { checkedAt, errorText, diagnostics });
        return {
            kind: 'error',
            errorText,
            checkedAt,
        };
    }
}
async function findRunningDomindsPackageRootAbs() {
    const scriptPath = PROCESS_START_ARGV[1];
    if (typeof scriptPath !== 'string' || scriptPath.trim() === '')
        return null;
    const scriptPathAbs = path_1.default.isAbsolute(scriptPath)
        ? scriptPath
        : path_1.default.resolve(PROCESS_START_CWD, scriptPath);
    let currentDir = path_1.default.dirname(scriptPathAbs);
    for (let depth = 0; depth < 8; depth++) {
        const packageJsonPath = path_1.default.join(currentDir, 'package.json');
        try {
            const raw = await promises_1.default.readFile(packageJsonPath, 'utf8');
            const parsed = JSON.parse(raw);
            if (isRecord(parsed) && parsed.name === 'dominds')
                return currentDir;
        }
        catch (error) {
            const code = getStringErrorProp(error, 'code');
            if (code !== 'ENOENT')
                throw error;
        }
        const parentDir = path_1.default.dirname(currentDir);
        if (parentDir === currentDir)
            return null;
        currentDir = parentDir;
    }
    return null;
}
async function readPackageVersionFromPackageJson(packageJsonPath) {
    try {
        const raw = await promises_1.default.readFile(packageJsonPath, 'utf8');
        const parsed = JSON.parse(raw);
        if (!isRecord(parsed) || typeof parsed.version !== 'string' || parsed.version.trim() === '') {
            throw new Error(`Invalid Dominds package metadata at ${packageJsonPath}`);
        }
        return parsed.version.trim();
    }
    catch (error) {
        const code = getStringErrorProp(error, 'code');
        if (code === 'ENOENT')
            return null;
        throw error;
    }
}
async function readCurrentDomindsPackageRootAbs() {
    const runningPackageRoot = await findRunningDomindsPackageRootAbs();
    if (runningPackageRoot === null) {
        throw new Error('Cannot find package.json for the current Dominds process entrypoint');
    }
    return runningPackageRoot;
}
async function readCurrentDomindsPackageVersion() {
    const runningPackageRoot = await readCurrentDomindsPackageRootAbs();
    return await readDomindsPackageVersionAtRootAbs(runningPackageRoot);
}
async function readDomindsPackageVersionAtRootAbs(packageRootAbs) {
    const packageJsonPath = path_1.default.join(packageRootAbs, 'package.json');
    const version = await readPackageVersionFromPackageJson(packageJsonPath);
    if (version === null) {
        throw new Error(`Cannot read Dominds package metadata at ${packageJsonPath}`);
    }
    return version;
}
function parsePackageManagerRoot(stdout) {
    const lines = stdout
        .split(/\r?\n/u)
        .map((line) => line.trim())
        .filter((line) => line !== '');
    const npmRoot = lines[lines.length - 1];
    if (npmRoot === undefined) {
        throw new Error('package manager root -g returned empty stdout');
    }
    return npmRoot;
}
async function readSymlinkTargetAbs(absPath) {
    try {
        const stat = await promises_1.default.lstat(absPath);
        if (!stat.isSymbolicLink())
            return null;
        const target = await promises_1.default.readlink(absPath);
        return path_1.default.isAbsolute(target) ? target : path_1.default.resolve(path_1.default.dirname(absPath), target);
    }
    catch (error) {
        const code = getStringErrorProp(error, 'code');
        if (code === 'ENOENT')
            return null;
        throw error;
    }
}
function isSameOrInsidePath(childAbs, parentAbs) {
    const childComparable = normalizeComparablePath(path_1.default.resolve(childAbs));
    const parentComparable = normalizeComparablePath(path_1.default.resolve(parentAbs));
    if (childComparable === parentComparable)
        return true;
    const relative = path_1.default.relative(parentComparable, childComparable);
    return relative !== '' && !relative.startsWith('..') && !path_1.default.isAbsolute(relative);
}
async function readGlobalRootAbs(commandSpec) {
    const rootResult = await runPackageManagerCommand(commandSpec, ['root', '-g'], {
        maxBuffer: 1024 * 1024,
        timeoutMs: 15_000,
    });
    return path_1.default.resolve(PROCESS_START_CWD, parsePackageManagerRoot(rootResult.stdout));
}
async function verifyGlobalInstallTargetsCurrentPackage() {
    let currentPackageRootAbs = null;
    const checkedTargets = [];
    try {
        currentPackageRootAbs = await readCurrentDomindsPackageRootAbs();
        const currentComparable = await getComparableRealPath(currentPackageRootAbs);
        const managers = ['npm', 'pnpm'];
        for (const manager of managers) {
            try {
                const commandSpec = manager === 'pnpm' ? await resolvePnpmCommandSpec() : await resolveNpmCommandSpec();
                const managerGlobalRootAbs = await readGlobalRootAbs(commandSpec);
                const managerPackageRootAbs = path_1.default.join(managerGlobalRootAbs, 'dominds');
                checkedTargets.push(manager + ':' + managerPackageRootAbs);
                const symlinkTargetAbs = await readSymlinkTargetAbs(managerPackageRootAbs);
                if (symlinkTargetAbs !== null &&
                    !isSameOrInsidePath(symlinkTargetAbs, managerGlobalRootAbs)) {
                    checkedTargets.push(manager + ':linked:' + symlinkTargetAbs);
                    continue;
                }
                const managerComparable = await getComparableRealPath(managerPackageRootAbs);
                if (currentComparable === managerComparable) {
                    return {
                        kind: 'ok',
                        manager,
                        currentPackageRootAbs,
                        managerPackageRootAbs,
                    };
                }
            }
            catch (error) {
                checkedTargets.push(manager + ':error:' + formatErrorForUi(error));
            }
        }
        return {
            kind: 'unsupported',
            currentPackageRootAbs,
            checkedTargets,
            message: 'Automatic global update is disabled because neither npm nor pnpm global install target safely matches the current Dominds process at ' +
                currentPackageRootAbs +
                '. Checked: ' +
                checkedTargets.join('; ') +
                '.',
        };
    }
    catch (error) {
        return {
            kind: 'unsupported',
            currentPackageRootAbs,
            checkedTargets,
            message: 'Automatic global update is disabled because the global install target could not be verified: ' +
                formatErrorForUi(error),
        };
    }
}
function buildGlobalInstallArgs(manager) {
    return manager === 'pnpm' ? ['add', '-g', 'dominds@latest'] : ['i', '-g', 'dominds@latest'];
}
async function buildGlobalInstallCommandSpec(manager) {
    return manager === 'pnpm' ? await resolvePnpmCommandSpec() : await resolveNpmCommandSpec();
}
function parseDomindsVersionProbe(stdout, stderr) {
    const combined = `${stdout}\n${stderr}`;
    const match = /dominds\s+v([^\s]+)/i.exec(combined) ??
        /\bv(\d+(?:\.\d+){1,3}(?:[-+][^\s]+)?)\b/i.exec(combined);
    if (match === null)
        return null;
    return match[1].trim();
}
function formatErrorForUi(error) {
    const diagnostics = extractCommandFailureDiagnostics(error);
    if (diagnostics.cmd !== null || diagnostics.stdout !== '' || diagnostics.stderr !== '') {
        return formatCommandFailureForUi(diagnostics);
    }
    return error instanceof Error ? error.message : String(error);
}
async function verifyInstalledDominds(targetVersion) {
    let packageVersion = null;
    let probeVersion = null;
    try {
        packageVersion = await readCurrentDomindsPackageVersion();
        const probe = await runCurrentDomindsVersionProbe();
        probeVersion = parseDomindsVersionProbe(probe.stdout, probe.stderr);
        if (probeVersion === null) {
            return {
                kind: 'failed',
                message: `Current Dominds entrypoint did not report a parseable version; stdout=${truncateCommandOutput(probe.stdout)} stderr=${truncateCommandOutput(probe.stderr)}`,
                packageVersion,
                probeVersion,
            };
        }
        const packageProbeComparison = compareVersions(packageVersion, probeVersion);
        const versionsMatch = packageProbeComparison !== null
            ? packageProbeComparison === 0
            : packageVersion === probeVersion;
        const packageTargetComparison = compareVersions(packageVersion, targetVersion);
        const probeTargetComparison = compareVersions(probeVersion, targetVersion);
        const packageAtLeastTarget = packageTargetComparison !== null
            ? packageTargetComparison >= 0
            : packageVersion === targetVersion;
        const probeAtLeastTarget = probeTargetComparison !== null ? probeTargetComparison >= 0 : probeVersion === targetVersion;
        if (!versionsMatch || !packageAtLeastTarget || !probeAtLeastTarget) {
            return {
                kind: 'failed',
                message: `Current Dominds entrypoint version verification failed; target=${targetVersion} package=${packageVersion} probe=${probeVersion}`,
                packageVersion,
                probeVersion,
            };
        }
        return { kind: 'ok', packageVersion, probeVersion };
    }
    catch (error) {
        return {
            kind: 'failed',
            message: error instanceof Error ? error.message : String(error),
            packageVersion,
            probeVersion,
        };
    }
}
function clearBackgroundCheckTimer() {
    if (backgroundCheckTimer === null)
        return;
    clearTimeout(backgroundCheckTimer);
    backgroundCheckTimer = null;
}
function scheduleBackgroundCheck(delayMs) {
    clearBackgroundCheckTimer();
    if (runtimeConfig === null || runtimeConfig.mode !== 'production')
        return;
    backgroundCheckTimer = setTimeout(() => {
        void runBackgroundCheck();
    }, delayMs);
}
function getRunningVersion() {
    return dominds_running_version_1.DOMINDS_RUNNING_VERSION;
}
function publishStatusUpdateSoon() {
    if (broadcastStatusUpdate === null)
        return;
    void getDomindsSelfUpdateStatus()
        .then((status) => {
        const publish = broadcastStatusUpdate;
        if (publish === null)
            return;
        publish(status);
    })
        .catch((error) => {
        log.warn('Failed to publish Dominds self-update status', error);
    });
}
async function runBackgroundCheck() {
    try {
        if (runtimeConfig === null || runtimeConfig.mode !== 'production')
            return;
        if (restartState.kind === 'restart_required' || restartState.kind === 'restarting')
            return;
        latestObservation = await queryLatestVersion();
        publishStatusUpdateSoon();
    }
    catch (error) {
        log.error('Dominds background latest-version check failed', error);
    }
    finally {
        scheduleBackgroundCheck(BACKGROUND_CHECK_INTERVAL_MS);
    }
}
async function checkLatestDomindsVersionNow() {
    const cfg = assertRuntimeConfig();
    if (cfg.mode !== 'production') {
        return await getDomindsSelfUpdateStatus();
    }
    if (installPromise !== null || restartPromise !== null) {
        return await getDomindsSelfUpdateStatus();
    }
    if (restartState.kind === 'restart_required' || restartState.kind === 'restarting') {
        return await getDomindsSelfUpdateStatus();
    }
    installFailureObservation = null;
    latestObservation = await queryLatestVersion();
    publishStatusUpdateSoon();
    scheduleBackgroundCheck(BACKGROUND_CHECK_INTERVAL_MS);
    return await getDomindsSelfUpdateStatus();
}
function assertRuntimeConfig() {
    if (runtimeConfig === null) {
        throw new Error('Dominds self-update runtime is not configured');
    }
    return runtimeConfig;
}
function buildStatus(params) {
    return {
        enabled: params.mode === 'production' && params.runKind !== 'disabled',
        mode: params.mode,
        currentVersion: params.currentVersion,
        installedVersion: params.installedVersion,
        latestVersion: params.latestVersion,
        checkedAt: params.checkedAt,
        runKind: params.runKind,
        action: params.action,
        busy: params.busy,
        reason: params.reason,
        message: params.message,
        targetVersion: params.targetVersion,
    };
}
function configureDomindsSelfUpdate(params) {
    runtimeConfig = {
        host: params.host,
        port: params.port,
        mode: params.mode,
        closeWebSocketClients: params.closeWebSocketClients,
        stopServer: params.stopServer,
    };
    latestObservation = { kind: 'unknown' };
    restartState = IDLE_RESTART_STATE;
    installFailureObservation = null;
    clearBackgroundCheckTimer();
    if (params.mode === 'production') {
        scheduleBackgroundCheck(BACKGROUND_CHECK_INTERVAL_MS);
    }
}
function setDomindsSelfUpdateBroadcaster(next) {
    broadcastStatusUpdate = next;
}
async function getDomindsSelfUpdateStatus() {
    const cfg = assertRuntimeConfig();
    const runningVersion = getRunningVersion();
    const runKind = detectRunKind(cfg.mode);
    if (runKind === 'disabled') {
        return buildStatus({
            currentVersion: runningVersion,
            installedVersion: runningVersion,
            latestVersion: null,
            checkedAt: null,
            mode: cfg.mode,
            runKind,
            action: 'none',
            busy: 'idle',
            reason: 'dev_mode',
            message: 'Self-update is disabled in development mode',
            targetVersion: null,
        });
    }
    if (restartState.kind === 'restart_required') {
        return buildStatus({
            currentVersion: runningVersion,
            installedVersion: restartState.installedVersion,
            latestVersion: latestObservation.kind === 'ok'
                ? latestObservation.latestVersion
                : restartState.installedVersion,
            checkedAt: latestObservation.kind === 'unknown' ? null : latestObservation.checkedAt,
            mode: cfg.mode,
            runKind,
            action: 'restart',
            busy: 'idle',
            reason: restartState.reason,
            message: restartState.installReport ?? 'Latest Dominds is installed and waiting for restart',
            targetVersion: restartState.installedVersion,
        });
    }
    if (restartState.kind === 'restarting') {
        return buildStatus({
            currentVersion: runningVersion,
            installedVersion: restartState.targetVersion ?? runningVersion,
            latestVersion: restartState.targetVersion,
            checkedAt: latestObservation.kind === 'unknown' ? null : latestObservation.checkedAt,
            mode: cfg.mode,
            runKind,
            action: 'restart',
            busy: 'restarting',
            reason: 'restart_required',
            message: 'Dominds restart is in progress',
            targetVersion: restartState.targetVersion,
        });
    }
    if (installPromise !== null) {
        return buildStatus({
            currentVersion: runningVersion,
            installedVersion: runningVersion,
            latestVersion: latestObservation.kind === 'ok' ? latestObservation.latestVersion : null,
            checkedAt: latestObservation.kind === 'unknown' ? null : latestObservation.checkedAt,
            mode: cfg.mode,
            runKind,
            action: 'install',
            busy: 'installing',
            reason: 'install_available',
            message: 'Dominds installation is in progress',
            targetVersion: latestObservation.kind === 'ok' ? latestObservation.latestVersion : null,
        });
    }
    if (installFailureObservation !== null) {
        return buildStatus({
            currentVersion: runningVersion,
            installedVersion: runningVersion,
            latestVersion: latestObservation.kind === 'ok' ? latestObservation.latestVersion : null,
            checkedAt: installFailureObservation.failedAt,
            mode: cfg.mode,
            runKind,
            action: latestObservation.kind === 'ok' ? 'install' : 'none',
            busy: 'idle',
            reason: 'install_failed',
            message: installFailureObservation.errorText,
            targetVersion: latestObservation.kind === 'ok' ? latestObservation.latestVersion : null,
        });
    }
    if (restartPromise !== null) {
        const targetVersion = latestObservation.kind === 'ok' ? latestObservation.latestVersion : null;
        return buildStatus({
            currentVersion: runningVersion,
            installedVersion: targetVersion ?? runningVersion,
            latestVersion: targetVersion,
            checkedAt: latestObservation.kind === 'unknown' ? null : latestObservation.checkedAt,
            mode: cfg.mode,
            runKind,
            action: 'restart',
            busy: 'restarting',
            reason: 'restart_required',
            message: 'Dominds restart is in progress',
            targetVersion,
        });
    }
    if (latestObservation.kind === 'unknown') {
        return buildStatus({
            currentVersion: runningVersion,
            installedVersion: runningVersion,
            latestVersion: null,
            checkedAt: null,
            mode: cfg.mode,
            runKind,
            action: 'none',
            busy: 'idle',
            reason: null,
            message: null,
            targetVersion: null,
        });
    }
    if (latestObservation.kind === 'error') {
        return buildStatus({
            currentVersion: runningVersion,
            installedVersion: runningVersion,
            latestVersion: null,
            checkedAt: latestObservation.checkedAt,
            mode: cfg.mode,
            runKind,
            action: 'none',
            busy: 'idle',
            reason: 'latest_check_failed',
            message: latestObservation.errorText,
            targetVersion: null,
        });
    }
    const comparison = compareVersions(runningVersion, latestObservation.latestVersion);
    const hasUpdate = comparison !== null ? comparison < 0 : runningVersion !== latestObservation.latestVersion;
    if (!hasUpdate) {
        return buildStatus({
            currentVersion: runningVersion,
            installedVersion: runningVersion,
            latestVersion: latestObservation.latestVersion,
            checkedAt: latestObservation.checkedAt,
            mode: cfg.mode,
            runKind,
            action: 'none',
            busy: 'idle',
            reason: null,
            message: null,
            targetVersion: null,
        });
    }
    if (runKind === 'npx') {
        const npxSpec = await readNpxDomindsSpec();
        if (npxSpec?.kind === 'latest') {
            return buildStatus({
                currentVersion: runningVersion,
                installedVersion: runningVersion,
                latestVersion: latestObservation.latestVersion,
                checkedAt: latestObservation.checkedAt,
                mode: cfg.mode,
                runKind,
                action: 'restart',
                busy: 'idle',
                reason: 'restart_required',
                message: 'Restart to relaunch Dominds via npx dominds@latest',
                targetVersion: latestObservation.latestVersion,
            });
        }
        const reason = npxSpec?.kind === 'fixed' ? 'npx_fixed_version_unsupported' : 'npx_versionless_unsupported';
        const message = npxSpec?.kind === 'fixed'
            ? `A newer Dominds version is available, but this npx session was launched with ${npxSpec.spec}; use dominds@latest to enable automatic restart updates.`
            : 'A newer Dominds version is available, but this npx session was launched without a verified dominds@latest spec; use dominds@latest to enable automatic restart updates.';
        return buildStatus({
            currentVersion: runningVersion,
            installedVersion: runningVersion,
            latestVersion: latestObservation.latestVersion,
            checkedAt: latestObservation.checkedAt,
            mode: cfg.mode,
            runKind,
            action: 'none',
            busy: 'idle',
            reason,
            message,
            targetVersion: latestObservation.latestVersion,
        });
    }
    const globalTargetVerification = await verifyGlobalInstallTargetsCurrentPackage();
    if (globalTargetVerification.kind === 'unsupported') {
        return buildStatus({
            currentVersion: runningVersion,
            installedVersion: runningVersion,
            latestVersion: latestObservation.latestVersion,
            checkedAt: latestObservation.checkedAt,
            mode: cfg.mode,
            runKind,
            action: 'none',
            busy: 'idle',
            reason: 'global_install_target_unsupported',
            message: globalTargetVerification.message,
            targetVersion: latestObservation.latestVersion,
        });
    }
    const installedVersion = await readDomindsPackageVersionAtRootAbs(globalTargetVerification.managerPackageRootAbs);
    if (isVersionGreaterThan(installedVersion, runningVersion) &&
        isVersionAtLeast(installedVersion, latestObservation.latestVersion)) {
        return buildStatus({
            currentVersion: runningVersion,
            installedVersion,
            latestVersion: latestObservation.latestVersion,
            checkedAt: latestObservation.checkedAt,
            mode: cfg.mode,
            runKind,
            action: 'restart',
            busy: 'idle',
            reason: 'restart_required',
            message: 'Latest Dominds is already installed and waiting for restart',
            targetVersion: installedVersion,
        });
    }
    return buildStatus({
        currentVersion: runningVersion,
        installedVersion,
        latestVersion: latestObservation.latestVersion,
        checkedAt: latestObservation.checkedAt,
        mode: cfg.mode,
        runKind,
        action: 'install',
        busy: 'idle',
        reason: 'install_available',
        message: 'Latest Dominds is available on npm registry',
        targetVersion: latestObservation.latestVersion,
    });
}
async function installLatestDominds() {
    const cfg = assertRuntimeConfig();
    if (cfg.mode !== 'production') {
        throw new Error('Dominds self-update install is disabled in development mode');
    }
    const runKind = detectRunKind(cfg.mode);
    if (runKind !== 'global') {
        throw new Error(`Install action is not supported for runKind=${runKind}`);
    }
    if (restartState.kind === 'restart_required') {
        return await getDomindsSelfUpdateStatus();
    }
    if (restartState.kind === 'restarting') {
        return await getDomindsSelfUpdateStatus();
    }
    if (installPromise !== null) {
        return await getDomindsSelfUpdateStatus();
    }
    installPromise = (async () => {
        installFailureObservation = null;
        const runningVersion = getRunningVersion();
        const previouslyKnownLatest = latestObservation.kind === 'ok' ? latestObservation : null;
        const latest = await queryLatestVersion();
        let targetVersion;
        if (latest.kind === 'ok') {
            latestObservation = latest;
            targetVersion = latest.latestVersion;
        }
        else if (previouslyKnownLatest === null) {
            latestObservation = latest;
            throw new Error(latest.errorText);
        }
        else {
            targetVersion = previouslyKnownLatest.latestVersion;
            log.warn('Dominds install latest-version refresh failed; continuing with previously known update target', undefined, {
                previousLatestVersion: previouslyKnownLatest.latestVersion,
                previousCheckedAt: previouslyKnownLatest.checkedAt,
                refreshErrorText: latest.errorText,
                refreshCheckedAt: latest.checkedAt,
            });
        }
        const comparison = compareVersions(runningVersion, targetVersion);
        const hasUpdate = comparison !== null ? comparison < 0 : runningVersion !== targetVersion;
        if (!hasUpdate) {
            throw new Error('No installable Dominds update is currently available');
        }
        const globalTargetVerification = await verifyGlobalInstallTargetsCurrentPackage();
        if (globalTargetVerification.kind === 'unsupported') {
            throw new Error(globalTargetVerification.message);
        }
        const installedVersion = await readDomindsPackageVersionAtRootAbs(globalTargetVerification.managerPackageRootAbs);
        if (isVersionGreaterThan(installedVersion, runningVersion) &&
            isVersionAtLeast(installedVersion, targetVersion)) {
            restartState = {
                kind: 'restart_required',
                installedVersion,
                installReport: 'Latest Dominds is already installed and waiting for restart',
                reason: 'restart_required',
            };
            installFailureObservation = null;
            return await getDomindsSelfUpdateStatus();
        }
        let installCommandError = null;
        try {
            await runPackageManagerCommand(await buildGlobalInstallCommandSpec(globalTargetVerification.manager), buildGlobalInstallArgs(globalTargetVerification.manager), {
                maxBuffer: 20 * 1024 * 1024,
                timeoutMs: 10 * 60 * 1000,
            });
        }
        catch (error) {
            installCommandError = error;
            log.warn('Dominds install command failed; verifying installed files before reporting failure', error, {
                currentVersion: runningVersion,
                targetVersion,
            });
        }
        const installedVerification = await verifyInstalledDominds(targetVersion);
        if (installedVerification.kind === 'failed') {
            if (installCommandError !== null) {
                throw new Error(`npm install failed and installed Dominds verification failed. npm: ${formatErrorForUi(installCommandError)} verification: ${installedVerification.message}`);
            }
            throw new Error(installedVerification.message);
        }
        if (installCommandError !== null) {
            log.warn('Dominds install command failed but installed files passed verification', installCommandError, {
                targetVersion,
                packageVersion: installedVerification.packageVersion,
                probeVersion: installedVerification.probeVersion,
            });
        }
        const installReport = installCommandError === null
            ? null
            : `Dominds install command reported failure, but the current entrypoint version probe confirmed v${installedVerification.probeVersion}. Restart is available.`;
        restartState = {
            kind: 'restart_required',
            installedVersion: installedVerification.probeVersion,
            installReport,
            reason: installCommandError === null
                ? 'restart_required'
                : 'install_verified_after_command_failure',
        };
        installFailureObservation = null;
        return await getDomindsSelfUpdateStatus();
    })()
        .catch((error) => {
        const latestVersion = latestObservation.kind === 'ok' ? latestObservation.latestVersion : null;
        const checkedAt = latestObservation.kind === 'unknown' ? null : latestObservation.checkedAt;
        installFailureObservation = {
            errorText: formatErrorForUi(error),
            failedAt: (0, time_1.formatUnifiedTimestamp)(new Date()),
        };
        log.error('Dominds version install failed', error, {
            runKind,
            currentVersion: getRunningVersion(),
            latestVersion,
            checkedAt,
        });
        return getDomindsSelfUpdateStatus();
    })
        .finally(() => {
        installPromise = null;
        publishStatusUpdateSoon();
    });
    publishStatusUpdateSoon();
    return await getDomindsSelfUpdateStatus();
}
async function requestSupervisorRestart(params) {
    if (typeof process.send !== 'function') {
        throw new Error('Dominds restart requires the dominds supervisor process; this runner has no supervisor IPC channel');
    }
    const cfg = assertRuntimeConfig();
    const message = {
        type: supervisor_protocol_1.DOMINDS_SUPERVISOR_RESTART_WEBUI,
        cwd: (0, rtws_1.domindsRtwsRootAbs)(),
        host: cfg.host,
        port: cfg.port,
        traceFile: params.trace.traceFile,
        debugDir: params.trace.debugDir,
        currentVersion: params.currentVersion,
        targetVersion: params.targetVersion,
        restartStrategy: params.restartStrategy,
    };
    await new Promise((resolve, reject) => {
        process.send?.(message, (error) => {
            if (error !== null) {
                reject(error);
                return;
            }
            resolve();
        });
    });
}
async function stopAndExitForRestart() {
    const cfg = assertRuntimeConfig();
    const trace = activeRestartTraceFile === null
        ? null
        : { debugDir: path_1.default.dirname(activeRestartTraceFile), traceFile: activeRestartTraceFile };
    let stopSettled = false;
    if (trace !== null) {
        await appendRestartTraceBestEffort(trace, 'parent.stop_and_exit.start', {
            host: cfg.host,
            port: cfg.port,
        });
    }
    const stopPromise = cfg
        .stopServer()
        .then(() => {
        stopSettled = true;
        if (trace !== null) {
            return appendRestartTraceBestEffort(trace, 'parent.stop_server.finish', {
                host: cfg.host,
                port: cfg.port,
            });
        }
        return undefined;
    })
        .catch((error) => {
        stopSettled = true;
        if (trace !== null) {
            return appendRestartTraceBestEffort(trace, 'parent.stop_server.error', {
                host: cfg.host,
                port: cfg.port,
                message: error instanceof Error ? error.message : String(error),
                stack: error instanceof Error ? (error.stack ?? null) : null,
            }).then(() => {
                log.error('Failed to stop Dominds HTTP server during restart grace window', error, {
                    host: cfg.host,
                    port: cfg.port,
                });
            });
        }
        log.error('Failed to stop Dominds HTTP server during restart grace window', error, {
            host: cfg.host,
            port: cfg.port,
        });
        return undefined;
    });
    cfg.closeWebSocketClients();
    await stopPromise;
    if (trace !== null) {
        await appendRestartTraceBestEffort(trace, 'parent.process_exit', {
            host: cfg.host,
            port: cfg.port,
            stopSettled,
            code: 0,
        });
    }
    process.exit(0);
}
async function requestDomindsRestart(params) {
    const cfg = assertRuntimeConfig();
    const runKind = detectRunKind(cfg.mode);
    if (runKind === 'disabled') {
        throw new Error('Dominds restart requires a production run kind');
    }
    restartState = { kind: 'restarting', targetVersion: params.targetVersion };
    publishStatusUpdateSoon();
    const restartCwd = (0, rtws_1.domindsRtwsRootAbs)();
    const trace = buildRestartTraceContext();
    activeRestartTraceFile = trace.traceFile;
    try {
        await appendRestartTraceBestEffort(trace, params.traceEvent, {
            runKind,
            restartStrategy: params.restartStrategy,
            currentVersion: params.currentVersion,
            targetVersion: params.targetVersion,
            restartCwd,
            host: cfg.host,
            port: cfg.port,
            traceFile: trace.traceFile,
        });
        await requestSupervisorRestart({
            trace,
            restartStrategy: params.restartStrategy,
            currentVersion: params.currentVersion,
            targetVersion: params.targetVersion,
        });
        await appendRestartTraceBestEffort(trace, 'parent.supervisor_restart_requested', {
            retiringPid: process.pid,
            host: cfg.host,
            port: cfg.port,
        });
        log.info('Dominds supervisor restart requested', undefined, {
            traceFile: trace.traceFile,
            forced: params.traceEvent === 'parent.force_restart_requested',
            restartStrategy: params.restartStrategy,
        });
        setImmediate(() => {
            void stopAndExitForRestart().catch((error) => {
                void appendRestartTraceBestEffort(trace, 'parent.stop_and_exit.error', {
                    message: error instanceof Error ? error.message : String(error),
                    stack: error instanceof Error ? (error.stack ?? null) : null,
                });
                log.error('Failed to stop Dominds server during restart', error);
                restartState = params.rollbackState;
                activeRestartTraceFile = null;
                publishStatusUpdateSoon();
            });
        });
    }
    catch (error) {
        await appendRestartTraceBestEffort(trace, 'parent.restart_error', {
            message: error instanceof Error ? error.message : String(error),
            stack: error instanceof Error ? (error.stack ?? null) : null,
        });
        restartState = params.rollbackState;
        activeRestartTraceFile = null;
        publishStatusUpdateSoon();
        throw error;
    }
    return await getDomindsSelfUpdateStatus();
}
async function restartDomindsIntoLatest() {
    const cfg = assertRuntimeConfig();
    if (cfg.mode !== 'production') {
        throw new Error('Dominds restart is disabled in development mode');
    }
    if (restartPromise !== null) {
        return await getDomindsSelfUpdateStatus();
    }
    if (restartState.kind === 'restarting') {
        return await getDomindsSelfUpdateStatus();
    }
    restartPromise = (async () => {
        const status = await getDomindsSelfUpdateStatus();
        if (status.action !== 'restart') {
            throw new Error('No restartable Dominds update is currently available');
        }
        const runKind = detectRunKind(cfg.mode);
        if (runKind === 'disabled') {
            throw new Error('Dominds restart requires a production run kind');
        }
        const previousRestartRequiredState = restartState.kind === 'restart_required' ? restartState : null;
        if (previousRestartRequiredState === null && runKind === 'npx') {
            const npxSpec = await readNpxDomindsSpec();
            if (npxSpec?.kind !== 'latest') {
                throw new Error('Dominds npx restart requires launching Dominds with dominds@latest');
            }
        }
        else if (previousRestartRequiredState === null &&
            (status.targetVersion === null ||
                !isVersionGreaterThan(status.targetVersion, status.currentVersion))) {
            throw new Error('Dominds restart requires a completed install or a restartable session');
        }
        return await requestDomindsRestart({
            traceEvent: 'parent.restart_requested',
            restartStrategy: runKind === 'npx' ? 'npx_latest' : 'current_entrypoint',
            currentVersion: status.currentVersion,
            targetVersion: status.targetVersion,
            rollbackState: previousRestartRequiredState ?? IDLE_RESTART_STATE,
        });
    })()
        .catch((error) => {
        const statusSnapshot = restartState.kind === 'restarting' ? restartState : null;
        log.error('Dominds version restart failed', error, {
            runKind: detectRunKind(cfg.mode),
            restartState: statusSnapshot,
        });
        throw error;
    })
        .finally(() => {
        restartPromise = null;
        publishStatusUpdateSoon();
    });
    return await restartPromise;
}
async function forceRestartDominds() {
    const cfg = assertRuntimeConfig();
    if (cfg.mode !== 'production') {
        throw new Error('Dominds force restart is disabled in development mode');
    }
    if (installPromise !== null) {
        throw new Error('Dominds force restart is blocked while installation is in progress');
    }
    if (restartPromise !== null) {
        return await getDomindsSelfUpdateStatus();
    }
    if (restartState.kind === 'restarting') {
        return await getDomindsSelfUpdateStatus();
    }
    restartPromise = (async () => {
        const runningVersion = getRunningVersion();
        return await requestDomindsRestart({
            traceEvent: 'parent.force_restart_requested',
            restartStrategy: 'current_entrypoint',
            currentVersion: runningVersion,
            targetVersion: runningVersion,
            rollbackState: IDLE_RESTART_STATE,
        });
    })()
        .catch((error) => {
        const statusSnapshot = restartState.kind === 'restarting' ? restartState : null;
        log.error('Dominds force restart failed', error, {
            runKind: detectRunKind(cfg.mode),
            restartState: statusSnapshot,
        });
        throw error;
    })
        .finally(() => {
        restartPromise = null;
        publishStatusUpdateSoon();
    });
    return await restartPromise;
}
