import http from 'http';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { buildFeatureRegistry } from '../engines/feature-registry.js';
import { RepoTreeGenerator } from '../engines/repo-tree.js';
import { DASHBOARD_COMPATIBILITY_CAPABILITIES, buildRuntimeHealthEnvelope, createEngineHealthStatus, } from '../engines/runtime-health.js';
import { RuntimeRegistry, resolveNexusStateDir } from '../engines/runtime-registry.js';
import { resolveWorkspaceContext } from '../engines/workspace-resolver.js';
import { invokerRegistry } from '../invokers/registry.js';
import { handleAssetRoutes } from './routes/assets.js';
import { handleEventRoutes } from './routes/events.js';
import { handleGovernanceRoutes } from './routes/governance.js';
import { handleHealthRoutes } from './routes/health.js';
import { handleMemoryRoutes } from './routes/memory.js';
import { handleRuntimeRoutes } from './routes/runtime.js';
import { handleSurfaceRoutes } from './routes/surfaces.js';
import { handleSummaryRoutes } from './routes/summary.js';
import { handleLicenseRoutes } from './routes/license.js';
import { handleAuthoringRoutes } from './routes/authoring.js';
import { handleArchitectsRoutes } from './routes/architects.js';
import { handleWorkforceRoutes } from './routes/workforce.js';
import { handleGraphRoutes } from './routes/graph.js';
import { DASHBOARD_API_VERSION } from './contract.js';
import { SseBroker } from './stream/sse-broker.js';
import { nexusEventBus } from '../engines/event-bus.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const HOST = process.env.NEXUS_DASHBOARD_HOST || '127.0.0.1';
const DEFAULT_PORT = parseInt(process.env.NEXUS_DASHBOARD_PORT || '3377', 10);
const MAX_PORT_SCAN = 24;
const DASHBOARD_PROBE_TIMEOUT_MS = 3000;
const DASHBOARD_PROBE_ATTEMPTS = 2;
const DASHBOARD_SCHEMA_VERSION = 1;
const DASHBOARD_PRETTY_JSON = process.env.NEXUS_DASHBOARD_PRETTY_JSON === '1';
const CORE_CAPABILITIES = {
    runs: true,
    memory: true,
    pod: true,
    clients: true,
    events: true,
    stream: true,
    tokens: true,
    tokenSources: true,
    lifetimeTokens: true,
};
function isFilesystemRootPath(candidate) {
    if (!candidate)
        return false;
    const resolved = path.resolve(candidate);
    return resolved === path.parse(resolved).root;
}
function stringifyDashboardJson(data) {
    const stack = [];
    return JSON.stringify(data, function (_key, value) {
        if (typeof value === 'bigint') {
            return value.toString();
        }
        if (value instanceof Error) {
            return {
                name: value.name,
                message: value.message,
                stack: value.stack,
            };
        }
        if (value instanceof Map) {
            return Object.fromEntries(value);
        }
        if (value instanceof Set) {
            return Array.from(value);
        }
        if (value && typeof value === 'object') {
            while (stack.length > 0 && stack[stack.length - 1] !== this) {
                stack.pop();
            }
            if (stack.includes(value)) {
                return '[Circular]';
            }
            stack.push(value);
        }
        return value;
    }, DASHBOARD_PRETTY_JSON ? 2 : 0);
}
function resolveDashboardAppFile(relativePath) {
    const roots = [
        path.join(__dirname, 'app'),
        path.resolve(__dirname, '..', '..', 'src', 'dashboard', 'app'),
    ];
    for (const root of roots) {
        const resolved = path.resolve(root, relativePath);
        if (resolved !== root && !resolved.startsWith(root + path.sep))
            continue;
        if (fs.existsSync(resolved))
            return resolved;
    }
    return null;
}
export class DashboardServer {
    server;
    runtimeProvider;
    orchestratorProvider;
    memoryProvider;
    adaptersProvider;
    clientRegistryProvider;
    synapseProvider;
    architectsProvider;
    runtimeHealthProvider;
    workspaceContextProvider;
    synapseInitErrorProvider;
    reinitSynapseProvider;
    repoRoot;
    runtimeRegistry;
    dashboardUrl = null;
    dashboardMode = 'idle';
    activePort = null;
    started = false;
    initializePromise = null;
    stopping = false;
    activeProbeControllers = new Set();
    endpointCache = new Map();
    repoTreeGenerator;
    gitUser;
    sseBroker = new SseBroker();
    unsubscribeWorkspaceChanged = null;
    constructor(options = {}) {
        this.runtimeProvider = options.runtimeProvider;
        this.orchestratorProvider = options.orchestratorProvider;
        this.memoryProvider = options.memoryProvider;
        this.adaptersProvider = options.adaptersProvider;
        this.clientRegistryProvider = options.clientRegistryProvider;
        this.synapseProvider = options.synapseProvider;
        this.architectsProvider = options.architectsProvider;
        this.runtimeHealthProvider = options.runtimeHealthProvider;
        this.workspaceContextProvider = options.workspaceContextProvider;
        this.synapseInitErrorProvider = options.synapseInitErrorProvider;
        this.reinitSynapseProvider = options.reinitSynapseProvider;
        this.repoRoot = options.repoRoot ?? options.workspaceContextProvider?.().repoRoot ?? resolveWorkspaceContext().repoRoot;
        this.runtimeRegistry = new RuntimeRegistry();
        this.repoTreeGenerator = new RepoTreeGenerator(this.repoRoot);
        this.gitUser = process.env.GIT_AUTHOR_NAME || process.env.GIT_COMMITTER_NAME || '';
        this.server = http.createServer((req, res) => {
            this.requestHandler(req, res).catch((err) => {
                console.error('[Dashboard] requestHandler unhandled error:', err instanceof Error ? err.message : String(err));
                if (!res.headersSent && res.writable) {
                    try {
                        res.writeHead(500, { 'Content-Type': 'text/plain' });
                        res.end('Internal Server Error');
                    }
                    catch { /* socket already dead */ }
                }
            });
        });
        this.server.on('error', (error) => {
            if (this.dashboardMode === 'bound') {
                console.error('[Dashboard] Server error:', error.message);
            }
        });
        // Bust workspace-scoped caches whenever the authoritative workspace root
        // is promoted (e.g. from a nexus_session_bootstrap file hint). Without this
        // the dashboard keeps serving the stale "/" → "workspace" fallback for
        // up to 30s after the real repo is discovered.
        this.unsubscribeWorkspaceChanged = nexusEventBus.on('workspace.changed', (payload) => {
            try {
                this.repoRoot = payload.repoRoot || this.repoRoot;
                this.repoTreeGenerator = new RepoTreeGenerator(this.repoRoot);
                for (const key of [...this.endpointCache.keys()]) {
                    if (key.startsWith('json:workspace') || key.startsWith('json:repo-tree') || key.startsWith('json:runtime-snapshot')) {
                        this.endpointCache.delete(key);
                    }
                }
                // SseBroker auto-subscribes to nexusEventBus, so workspace.changed is already
                // broadcast to connected dashboard clients without a manual publish.
            }
            catch (err) {
                console.error('[Dashboard] workspace.changed handler failed:', err instanceof Error ? err.message : String(err));
            }
        });
    }
    start() {
        if (process.env.NEXUS_DASHBOARD_DISABLED === '1') {
            console.error('[Dashboard] Disabled by NEXUS_DASHBOARD_DISABLED=1');
            return;
        }
        if (this.started) {
            return;
        }
        this.started = true;
        this.stopping = false;
        this.initializePromise = this.initialize().catch((error) => {
            this.started = false;
            this.dashboardMode = 'idle';
            this.dashboardUrl = null;
            this.activePort = null;
            if (!this.stopping && !this.isProbeAbortError(error)) {
                console.error('[Dashboard] Failed to start dashboard:', error instanceof Error ? error.message : String(error));
            }
        });
    }
    stop() {
        this.stopping = true;
        for (const controller of this.activeProbeControllers) {
            controller.abort();
        }
        this.activeProbeControllers.clear();
        if (this.unsubscribeWorkspaceChanged) {
            this.unsubscribeWorkspaceChanged();
            this.unsubscribeWorkspaceChanged = null;
        }
        if (this.dashboardMode === 'bound') {
            this.sseBroker.stop();
            this.server.close();
        }
        this.dashboardMode = 'idle';
        this.dashboardUrl = null;
        this.activePort = null;
        this.started = false;
    }
    getAddress() {
        return this.dashboardUrl;
    }
    getSkillDistributionManifest() {
        const runtime = this.getRuntime();
        const clientRegistry = this.getClientRegistry();
        const skills = (runtime?.listSkills() ?? []).map((skill) => ({
            skillId: skill.skillId,
            name: skill.name,
            scope: skill.scope,
            rolloutStatus: skill.rolloutStatus,
            riskClass: skill.riskClass,
        }));
        if (!clientRegistry) {
            const distributableSkills = skills.filter((skill) => skill.rolloutStatus === 'promoted' && (skill.scope === 'global' || skill.scope === 'base'));
            return {
                generatedAt: Date.now(),
                serverGoverned: true,
                policy: {
                    allowedScopes: ['base', 'global'],
                    excludedClientIds: ['mcp'],
                    eligibleStates: ['primaryActive', 'active', 'idle', 'installed'],
                },
                clients: [],
                skills: distributableSkills.map((skill) => ({
                    ...skill,
                    distributableClientIds: [],
                })),
                summary: {
                    totalClients: 0,
                    eligibleClients: 0,
                    totalSkills: skills.length,
                    distributableSkills: distributableSkills.length,
                },
            };
        }
        return clientRegistry.buildSkillDistributionManifest(skills, this.getAdapters());
    }
    migrateDashboardState() {
        const statePath = path.join(resolveNexusStateDir(), 'dashboard-state.json');
        if (!fs.existsSync(path.dirname(statePath))) {
            try {
                fs.mkdirSync(path.dirname(statePath), { recursive: true });
            }
            catch (err) {
                console.error('[Dashboard] Cannot create state dir:', err?.message);
                return;
            }
        }
        let state = { schemaVersion: 0 };
        if (fs.existsSync(statePath)) {
            try {
                state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
            }
            catch {
                // ignore
            }
        }
        if ((state.schemaVersion || 0) < DASHBOARD_SCHEMA_VERSION) {
            console.error(`[Dashboard] Migrating state from v${state.schemaVersion || 0} to v${DASHBOARD_SCHEMA_VERSION}`);
            state.schemaVersion = DASHBOARD_SCHEMA_VERSION;
            try {
                fs.writeFileSync(statePath, JSON.stringify(state, null, 2));
            }
            catch (err) {
                console.error('[Dashboard] Cannot write state migration:', err?.message);
            }
        }
    }
    async initialize() {
        this.migrateDashboardState();
        const targetPort = process.env.NEXUS_DASHBOARD_PORT ? parseInt(process.env.NEXUS_DASHBOARD_PORT, 10) : DEFAULT_PORT;
        const probe = await this.probeDashboard(targetPort);
        if (this.stopping)
            return;
        if (probe.status === 'compatible') {
            this.dashboardMode = 'reused';
            this.dashboardUrl = probe.url;
            this.activePort = targetPort;
            console.error(`[Dashboard] Reusing compatible dashboard at ${probe.url}`);
            return;
        }
        const startPort = probe.status === 'incompatible' ? targetPort + 1 : targetPort;
        const fallbackPort = await this.bindFirstAvailablePort(startPort, targetPort + MAX_PORT_SCAN);
        if (this.stopping) {
            this.server.close();
            return;
        }
        this.dashboardMode = 'bound';
        this.activePort = fallbackPort;
        this.dashboardUrl = this.buildUrl(fallbackPort);
        this.sseBroker.start();
        // Warm the invoker availability cache non-blocking so /api/runtimes is
        // fast on first request (same pattern as n-gram pre-warm from Phase 0.7).
        setImmediate(() => invokerRegistry.warmCache());
        if (probe.status === 'incompatible') {
            console.error(`[Dashboard] Incompatible dashboard detected at ${probe.url} (${probe.reason || 'missing compatibility contract'}). New dashboard started at ${this.dashboardUrl}`);
            return;
        }
        if (probe.status === 'indeterminate') {
            console.error(`[Dashboard] Probe at ${probe.url} stayed indeterminate after ${probe.attempts ?? DASHBOARD_PROBE_ATTEMPTS} attempt(s) (${probe.reason || 'probe-timeout'}). New dashboard started at ${this.dashboardUrl}`);
            return;
        }
        console.error(`[Dashboard] Topology console listening at ${this.dashboardUrl}`);
    }
    async requestHandler(req, res) {
        try {
            const url = new URL(req.url || '/', this.dashboardUrl ?? this.buildUrl(this.activePort ?? DEFAULT_PORT));
            if (req.method === 'OPTIONS') {
                this.respondOptions(res);
                return;
            }
            if (req.method === 'GET' && url.pathname === '/favicon.ico') {
                res.writeHead(204);
                res.end();
                return;
            }
            // LATTICE: new modular ES-module dashboard — default at /.
            if (req.method === 'GET' && (url.pathname === '/' || url.pathname === '/index.html')) {
                this.serveAppIndex(res);
                return;
            }
            // Serve app/** static assets (ES modules, CSS, fonts) for the LATTICE dashboard.
            if (req.method === 'GET' && url.pathname.startsWith('/app/')) {
                this.serveAppAsset(res, url.pathname);
                return;
            }
            // Root-level aliases so relative paths in index.html work when served at /
            // (e.g. ./styles/tokens.css → /styles/tokens.css → here).
            // /app/* kept for backward compat (bookmarks, devtools, any hardcoded refs).
            const ROOT_APP_PREFIXES = ['/styles/', '/views/', '/widgets/'];
            const ROOT_APP_FILES = new Set(['/main.js', '/api.js', '/router.js', '/sse.js', '/state.js', '/vocabulary.js']);
            if (req.method === 'GET' && (ROOT_APP_PREFIXES.some(p => url.pathname.startsWith(p)) ||
                ROOT_APP_FILES.has(url.pathname))) {
                this.serveAppAsset(res, '/app' + url.pathname);
                return;
            }
            if (req.method === 'GET' && url.pathname === '/welcome') {
                this.serveStaticHtml(res, path.join(__dirname, 'welcome.html'));
                return;
            }
            const ctx = this.createRouteContext();
            const handlers = [
                handleEventRoutes,
                handleHealthRoutes,
                handleLicenseRoutes,
                handleSummaryRoutes,
                handleSurfaceRoutes,
                handleRuntimeRoutes,
                handleGraphRoutes,
                handleMemoryRoutes,
                handleAssetRoutes,
                handleGovernanceRoutes,
                handleAuthoringRoutes,
                handleArchitectsRoutes,
                handleWorkforceRoutes,
            ];
            for (const handler of handlers) {
                if (await handler(ctx, req, res, url)) {
                    return;
                }
            }
            this.respondJson(res, { error: 'not-found', path: url.pathname }, 404);
        }
        catch (error) {
            if (!res.headersSent) {
                if (error instanceof Error && error.message === 'Request body too large') {
                    this.respondJson(res, { error: 'request-body-too-large' }, 413);
                }
                else {
                    this.respondJson(res, {
                        error: 'internal-server-error',
                        message: error instanceof Error ? error.message : String(error),
                    }, 500);
                }
            }
        }
    }
    createRouteContext() {
        const runtimes = this.runtimeRegistry.list();
        const runtimeKeyFor = (url) => this.resolveRequestedRuntimeId(url, runtimes) || '__default__';
        const runtimeSnapshotCache = new Map();
        const usageSnapshotCache = new Map();
        const repoIdentityCache = new Map();
        const architectsWorklistCache = new Map();
        const resolveRuntimeSnapshot = (url) => {
            const key = runtimeKeyFor(url);
            if (runtimeSnapshotCache.has(key)) {
                return runtimeSnapshotCache.get(key);
            }
            const snapshot = this.resolveRuntimeSnapshot(url, runtimes);
            runtimeSnapshotCache.set(key, snapshot);
            return snapshot;
        };
        const collectUsageSnapshot = (url) => {
            const key = runtimeKeyFor(url);
            if (usageSnapshotCache.has(key)) {
                return usageSnapshotCache.get(key);
            }
            const usage = this.collectUsageSnapshot(url, runtimes, resolveRuntimeSnapshot(url));
            usageSnapshotCache.set(key, usage);
            return usage;
        };
        const getSelectedRepoIdentity = (url) => {
            const key = runtimeKeyFor(url);
            if (repoIdentityCache.has(key)) {
                return repoIdentityCache.get(key);
            }
            const repoIdentity = this.getSelectedRepoIdentity(url, resolveRuntimeSnapshot(url));
            repoIdentityCache.set(key, repoIdentity);
            return repoIdentity;
        };
        const getPreferredArchitectsWorklist = (url, worklistId, strikeTeamId) => {
            const key = `${runtimeKeyFor(url)}:${String(worklistId ?? '')}:${String(strikeTeamId ?? '')}`;
            if (architectsWorklistCache.has(key)) {
                return architectsWorklistCache.get(key);
            }
            const worklist = this.getPreferredArchitectsWorklist(url, worklistId, strikeTeamId);
            architectsWorklistCache.set(key, worklist);
            return worklist;
        };
        return {
            repoRoot: this.repoRoot,
            runtimeRegistry: this.runtimeRegistry,
            workspaceContextProvider: this.workspaceContextProvider,
            repoTreeGenerator: this.repoTreeGenerator,
            getRuntime: () => this.getRuntime(),
            getOrchestrator: () => this.getOrchestrator(),
            getMemory: () => this.getMemory(),
            getAdapters: () => this.getAdapters(),
            getClientRegistry: () => this.getClientRegistry(),
            getSynapse: () => this.getSynapse(),
            getArchitects: () => this.getArchitects(),
            getSelectedRepoIdentity,
            getPreferredArchitectsWorklist,
            getSkillDistributionManifest: () => this.getSkillDistributionManifest(),
            buildFeatureRegistry: () => buildFeatureRegistry(this.repoRoot),
            resolveRuntimeSnapshot,
            collectUsageSnapshot,
            collectTokenOptimization: (snapshot, usage) => this.collectTokenOptimization(snapshot, usage),
            collectGateSummary: (run) => this.collectGateSummary(run),
            collectInterpretationIssues: (input) => this.collectInterpretationIssues(input),
            collectMemoryQualitySummary: (memoryHealth, containers) => this.collectMemoryQualitySummary(memoryHealth, containers),
            buildDashboardAlerts: (payload) => this.buildDashboardAlerts(payload),
            listDashboardMemories: (options) => this.listDashboardMemories(options),
            listCrossProjectMemories: (options) => this.listCrossProjectMemories(options),
            listCrossProjectProjects: (limit) => this.listCrossProjectProjects(limit),
            getCachedValue: (key, ttlMs, producer) => this.getCachedValue(key, ttlMs, producer),
            collectHealth: () => this.collectHealth(),
            collectCompatibilityHealth: () => this.collectCompatibilityHealth(),
            resolveDashboardEntity: (kind, id, url) => this.resolveDashboardEntity(kind, id, url),
            getAddress: () => this.getAddress(),
            getCorsOrigin: () => this.getCorsOrigin(),
            respondJson: (res, data, statusCode) => this.respondJson(res, data, statusCode),
            respondCachedJson: (res, key, ttlMs, producer, statusCode) => this.respondCachedJson(res, key, ttlMs, producer, statusCode),
            readJsonBody: (req) => this.readJsonBody(req),
            serveSse: (req, res) => this.sseBroker.serve(req, res, this.getCorsOrigin()),
            getSynapseInitError: () => this.synapseInitErrorProvider?.() ?? null,
            getReinitSynapse: () => this.reinitSynapseProvider ?? null,
        };
    }
    /**
     * Serve the LATTICE modular dashboard shell at /.
     * Uses a strict CSP (no unsafe-inline) since this tree uses only external
     * ES modules and linked CSS — no inline scripts or styles.
     */
    serveAppIndex(res) {
        const strictHeaders = {
            'Content-Type': 'text/html',
            'Cache-Control': 'no-cache, must-revalidate',
            'Content-Security-Policy': [
                "default-src 'self'",
                "style-src 'self' 'unsafe-inline'",
                "font-src 'self'",
                "script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net",
                "connect-src 'self'",
                "img-src 'self' data:",
            ].join('; '),
            'X-Content-Type-Options': 'nosniff',
            'X-Frame-Options': 'DENY',
        };
        const htmlPath = resolveDashboardAppFile('index.html');
        if (!htmlPath) {
            res.writeHead(500);
            res.end('Error loading LATTICE dashboard HTML: missing app/index.html');
            return;
        }
        fs.readFile(htmlPath, 'utf8', (err, data) => {
            if (err) {
                res.writeHead(500);
                res.end('Error loading LATTICE dashboard HTML');
                return;
            }
            res.writeHead(200, strictHeaders);
            res.end(data);
        });
    }
    serveAppAsset(res, pathname) {
        const EXT_TYPES = {
            '.css': 'text/css; charset=utf-8',
            '.js': 'application/javascript; charset=utf-8',
            '.html': 'text/html; charset=utf-8',
            '.woff2': 'font/woff2',
            '.woff': 'font/woff',
            '.svg': 'image/svg+xml',
            '.png': 'image/png',
            '.ico': 'image/x-icon',
        };
        // Prevent path traversal
        const rel = pathname.slice('/app/'.length);
        if (rel.includes('..') || rel.includes('\0')) {
            res.writeHead(400);
            res.end('Bad request');
            return;
        }
        const filePath = resolveDashboardAppFile(rel);
        if (!filePath) {
            res.writeHead(404);
            res.end('Not found');
            return;
        }
        const ext = path.extname(filePath).toLowerCase();
        const ct = EXT_TYPES[ext] ?? 'application/octet-stream';
        fs.readFile(filePath, (err, data) => {
            if (err) {
                res.writeHead(404);
                res.end('Not found');
                return;
            }
            res.writeHead(200, {
                'Content-Type': ct,
                'Cache-Control': 'no-cache, must-revalidate',
                'X-Content-Type-Options': 'nosniff',
            });
            res.end(data);
        });
    }
    serveStaticHtml(res, htmlPath) {
        const securityHeaders = { 'Content-Type': 'text/html; charset=utf-8' };
        fs.readFile(htmlPath, 'utf8', (err, data) => {
            if (err) {
                res.writeHead(404);
                res.end('Page not found');
                return;
            }
            res.writeHead(200, securityHeaders);
            res.end(data);
        });
    }
    getCorsOrigin() {
        return this.dashboardUrl || `http://${HOST}:${this.activePort || DEFAULT_PORT}`;
    }
    respondOptions(res) {
        res.writeHead(204, {
            'Access-Control-Allow-Origin': this.getCorsOrigin(),
            'Access-Control-Allow-Methods': 'GET,POST,OPTIONS',
            'Access-Control-Allow-Headers': 'Content-Type',
        });
        res.end();
    }
    respondJsonBody(res, body, statusCode = 200) {
        res.writeHead(statusCode, {
            'Content-Type': 'application/json',
            'Cache-Control': 'no-cache',
            'Access-Control-Allow-Origin': this.getCorsOrigin(),
        });
        res.end(body);
    }
    respondJson(res, data, statusCode = 200) {
        this.respondJsonBody(res, stringifyDashboardJson(data), statusCode);
    }
    async respondCachedJson(res, key, ttlMs, producer, statusCode = 200) {
        const body = await this.getCachedValue(`json:${key}`, ttlMs, async () => stringifyDashboardJson(await producer()));
        this.respondJsonBody(res, body, statusCode);
    }
    async readJsonBody(req) {
        const MAX_BODY = 1024 * 1024;
        const chunks = [];
        let totalLength = 0;
        for await (const chunk of req) {
            const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
            totalLength += buf.length;
            if (totalLength > MAX_BODY) {
                throw new Error('Request body too large');
            }
            chunks.push(buf);
        }
        if (!chunks.length)
            return {};
        try {
            return JSON.parse(Buffer.concat(chunks).toString('utf8'));
        }
        catch {
            return {};
        }
    }
    getRuntime() {
        return this.runtimeProvider?.();
    }
    resolveRequestedRuntimeId(url, runtimes = this.runtimeRegistry.list()) {
        const requested = url.searchParams.get('runtimeId');
        if (requested)
            return requested;
        const currentRuntimeId = this.getRuntime()?.getRuntimeId();
        const currentSnapshot = currentRuntimeId
            ? (runtimes.find((runtime) => runtime.runtimeId === currentRuntimeId) ?? this.runtimeRegistry.read(currentRuntimeId))
            : null;
        const currentRepoRoot = currentSnapshot?.repoIdentity?.repoRoot;
        if (currentRuntimeId && currentRepoRoot === this.repoRoot)
            return currentRuntimeId;
        const repoRuntime = runtimes.find((runtime) => runtime?.repoIdentity?.repoRoot === this.repoRoot);
        return repoRuntime?.runtimeId
            || currentRuntimeId
            || runtimes[0]?.runtimeId
            || null;
    }
    resolveRuntimeSnapshot(url, runtimes = this.runtimeRegistry.list()) {
        const runtimeId = this.resolveRequestedRuntimeId(url, runtimes);
        if (!runtimeId)
            return undefined;
        return runtimes.find((runtime) => runtime.runtimeId === runtimeId) ?? this.runtimeRegistry.read(runtimeId);
    }
    getOrchestrator() {
        return this.orchestratorProvider?.();
    }
    getSynapse() {
        return this.synapseProvider?.();
    }
    getArchitects() {
        return this.architectsProvider?.();
    }
    getSelectedRepoIdentity(url, snapshot = this.resolveRuntimeSnapshot(url)) {
        const workspace = this.resolveCanonicalWorkspaceContext(snapshot?.repoIdentity);
        return {
            repoRoot: workspace.repoRoot,
            workspaceRoot: workspace.workspaceRoot,
            repoName: workspace.repoName,
            workspaceSource: workspace.workspaceSource,
            workspaceStateKey: workspace.stateKey,
            remoteUrl: workspace.remoteUrl,
        };
    }
    resolveCanonicalWorkspaceContext(preferredRepoIdentity) {
        const fallback = resolveWorkspaceContext({ workspaceRoot: this.repoRoot });
        const provided = this.workspaceContextProvider?.();
        const providerLooksBroken = Boolean(provided)
            && (isFilesystemRootPath(provided?.repoRoot)
                || isFilesystemRootPath(provided?.workspaceRoot)
                || !String(provided?.repoName || '').trim())
            && !isFilesystemRootPath(fallback.repoRoot);
        const base = providerLooksBroken ? fallback : (provided ?? fallback);
        if (!preferredRepoIdentity || typeof preferredRepoIdentity !== 'object') {
            return base;
        }
        const preferredRepoRoot = typeof preferredRepoIdentity.repoRoot === 'string'
            ? preferredRepoIdentity.repoRoot.trim()
            : '';
        const preferredWorkspaceRoot = typeof preferredRepoIdentity.workspaceRoot === 'string'
            ? preferredRepoIdentity.workspaceRoot.trim()
            : preferredRepoRoot;
        if (!preferredRepoRoot || isFilesystemRootPath(preferredRepoRoot)) {
            return base;
        }
        return {
            ...base,
            repoRoot: preferredRepoRoot,
            workspaceRoot: preferredWorkspaceRoot || preferredRepoRoot,
            repoName: typeof preferredRepoIdentity.repoName === 'string' && preferredRepoIdentity.repoName.trim()
                ? preferredRepoIdentity.repoName.trim()
                : base.repoName,
            workspaceSource: typeof preferredRepoIdentity.workspaceSource === 'string' && preferredRepoIdentity.workspaceSource.trim()
                ? preferredRepoIdentity.workspaceSource.trim()
                : base.workspaceSource,
            stateKey: typeof preferredRepoIdentity.workspaceStateKey === 'string' && preferredRepoIdentity.workspaceStateKey.trim()
                ? preferredRepoIdentity.workspaceStateKey.trim()
                : base.stateKey,
            remoteUrl: typeof preferredRepoIdentity.remoteUrl === 'string'
                ? preferredRepoIdentity.remoteUrl
                : base.remoteUrl,
        };
    }
    getPreferredArchitectsWorklist(url, worklistId, strikeTeamId) {
        const architects = this.getArchitects();
        if (!architects) {
            return {
                worklist: null,
                items: [],
                idle: true,
                notReady: true,
                pillar: 'Architects',
                reason: 'Architects runtime is not attached to this dashboard process — see CLAUDE.md §3.',
            };
        }
        let resolvedWorklistId = String(worklistId ?? '').trim();
        let resolvedStrikeTeamId = String(strikeTeamId ?? '').trim();
        if (!resolvedWorklistId) {
            if (!resolvedStrikeTeamId) {
                const teams = this.getSynapse()?.getStrikeTeamStatus?.();
                const firstTeam = Array.isArray(teams) ? teams[0] : teams;
                resolvedStrikeTeamId = String(firstTeam?.id ?? '');
            }
            if (resolvedStrikeTeamId) {
                resolvedWorklistId = architects.getWorklistForStrikeTeam?.(resolvedStrikeTeamId) ?? '';
            }
        }
        if (!resolvedWorklistId) {
            return {
                worklist: null,
                items: [],
                idle: true,
                reason: 'No Synapse-linked worklist is active for the selected runtime.',
            };
        }
        const worklist = architects.getWorklist(resolvedWorklistId);
        return {
            worklist: worklist?.worklist ?? null,
            items: worklist?.items ?? [],
            worklistId: resolvedWorklistId,
            strikeTeamId: resolvedStrikeTeamId || null,
            idle: false,
            reason: '',
        };
    }
    getMemory() {
        return this.memoryProvider?.();
    }
    getAdapters() {
        return this.adaptersProvider?.() ?? [];
    }
    getClientRegistry() {
        return this.clientRegistryProvider?.();
    }
    async getCachedValue(key, ttlMs, producer) {
        const now = Date.now();
        const cached = this.endpointCache.get(key);
        if (cached?.value !== undefined && cached.expiresAt > now) {
            return cached.value;
        }
        if (cached?.refresh) {
            return cached.value !== undefined ? cached.value : cached.refresh;
        }
        const refresh = producer()
            .then((value) => {
            this.endpointCache.set(key, {
                value,
                expiresAt: Date.now() + ttlMs,
            });
            return value;
        })
            .finally(() => {
            const latest = this.endpointCache.get(key);
            if (latest?.refresh) {
                this.endpointCache.set(key, {
                    value: latest.value,
                    expiresAt: latest.expiresAt,
                });
            }
        });
        this.endpointCache.set(key, {
            value: cached?.value,
            expiresAt: cached?.expiresAt ?? 0,
            refresh,
        });
        return cached?.value !== undefined ? cached.value : refresh;
    }
    async readJsonIfExists(target) {
        try {
            const raw = await fs.promises.readFile(target, 'utf8');
            return JSON.parse(raw);
        }
        catch {
            return null;
        }
    }
    async fileExists(target) {
        try {
            await fs.promises.access(target);
            return true;
        }
        catch {
            return false;
        }
    }
    collectUsageSnapshot(url, runtimes = this.runtimeRegistry.list(), selected = this.resolveRuntimeSnapshot(url, runtimes)) {
        const requestedRuntimeId = this.resolveRequestedRuntimeId(url, runtimes);
        return selected ?? {
            runtimeId: requestedRuntimeId ?? null,
            health: 'stale',
            usage: {},
            libraries: {},
            latestRun: null,
        };
    }
    normalizeDashboardRepoToken(value) {
        return String(value ?? '').trim().replace(/^#/, '').toLowerCase();
    }
    dashboardMemoryMatchesRepoName(memory, repoName) {
        const expected = this.normalizeDashboardRepoToken(repoName);
        if (!expected)
            return true;
        const tags = Array.isArray(memory.tags) ? memory.tags : [];
        const provenance = memory.provenance ?? {};
        const candidates = [
            ...tags,
            ...(Array.isArray(provenance.tags) ? provenance.tags : []),
            ...(Array.isArray(provenance.containerTags) ? provenance.containerTags : []),
            provenance.repoName,
            provenance.repoId,
            provenance.workspaceId,
            provenance.projectId,
        ];
        return candidates.some((candidate) => {
            const token = this.normalizeDashboardRepoToken(candidate);
            if (!token)
                return false;
            if (token === expected)
                return true;
            if (token === `repo:${expected}`)
                return true;
            return token.startsWith('repo:') && token.slice(5) === expected;
        });
    }
    listDashboardMemories(options = {}) {
        const limit = Math.max(1, Number(options.limit || 40));
        const raw = this.getMemory()?.listSnapshots(Math.min(limit * 3, 200), {
            tier: options.tier,
            tag: options.tag,
            linkedType: options.linkedType,
            recencyMs: options.recencyMs,
            state: options.includeHidden || options.includeInactive || options.lane === 'inbox' ? undefined : 'active',
            lane: options.lane,
            repoId: options.repoId,
            workspaceId: options.workspaceId,
            projectId: options.projectId,
            includeHidden: options.includeHidden,
        }) ?? [];
        const visible = raw
            .filter((memory) => {
            const tags = Array.isArray(memory.tags) ? memory.tags : [];
            if (!options.includeHidden && tags.includes('#system-hidden'))
                return false;
            if (!options.includeHidden && tags.includes('#auto') && Number(memory.priority ?? 0) < 0.4)
                return false;
            if (tags.includes('#quarantine') && options.lane !== 'inbox')
                return false;
            if (!options.showPhantom && (tags.includes('#phantom-learning') || tags.includes('#swarm')))
                return false;
            if (tags.includes('#repo-profile') && options.lane === 'profile')
                return true;
            if (!options.includeHidden && (tags.includes('#hidden') || tags.includes('#repo-profile') || tags.includes('#system-hidden')))
                return false;
            return true;
        });
        const scoped = options.repoName
            ? visible.filter((memory) => this.dashboardMemoryMatchesRepoName(memory, options.repoName))
            : visible;
        return (options.repoName && scoped.length === 0 ? visible : scoped).slice(0, limit);
    }
    listCrossProjectMemories(options) {
        const memory = this.getMemory();
        if (!memory) {
            return {
                scope: options.scope,
                items: [],
                governance: {
                    defaultScope: 'shared',
                    allowedScopes: ['shared', 'promoted'],
                    requestedScope: options.scope,
                    sharedOnlyDefault: true,
                },
            };
        }
        const normalizedTags = options.tags
            .map((tag) => tag.trim())
            .filter(Boolean)
            .map((tag) => tag.startsWith('#') ? tag.toLowerCase() : `#${tag.toLowerCase()}`);
        const rawMatches = (options.q || normalizedTags.length > 0)
            ? memory.searchByTagsAndContent(options.q, normalizedTags, {
                fuzzy: options.fuzzy,
                scope: options.scope,
                limit: options.limit * 3,
            })
            : memory.listSnapshots(options.limit * 3, {
                scope: options.scope,
                state: 'active',
                includeHidden: false,
            }).map((item) => ({
                id: item.id,
                content: item.excerpt ?? item.content ?? '',
                score: Number(item.importanceScore || item.relevanceScore || item.priority || 0),
                tags: item.tags ?? [],
                scope: item.scope,
                state: item.state,
                provenance: item.provenance ?? {},
                highlights: [],
            }));
        const filtered = rawMatches
            .filter((item) => !options.repoId || item.provenance?.repoId === options.repoId)
            .slice(0, options.limit)
            .map((item) => ({
            id: item.id,
            excerpt: String(item.content || '').slice(0, 220),
            score: Number(item.score || 0),
            tags: Array.isArray(item.tags) ? item.tags : [],
            scope: item.scope,
            state: item.state,
            provenance: item.provenance,
            highlights: Array.isArray(item.highlights) ? item.highlights : [],
            projectKey: `${item.provenance?.repoId || 'unknown'}:${item.provenance?.projectId || 'none'}`,
        }));
        return {
            scope: options.scope,
            query: {
                q: options.q,
                tags: normalizedTags,
                fuzzy: options.fuzzy,
                repoId: options.repoId ?? null,
                limit: options.limit,
            },
            governance: {
                defaultScope: 'shared',
                allowedScopes: ['shared', 'promoted'],
                requestedScope: options.scope,
                sharedOnlyDefault: true,
            },
            items: filtered,
        };
    }
    listCrossProjectProjects(limit) {
        const memory = this.getMemory();
        if (!memory)
            return { generatedAt: Date.now(), projects: [] };
        const snapshots = memory.listSnapshots(Math.max(limit * 6, 300), {
            includeHidden: false,
            state: 'active',
        });
        const grouped = new Map();
        for (const item of snapshots) {
            const repoId = String(item.provenance?.repoId || '');
            if (!repoId)
                continue;
            const projectId = item.provenance?.projectId ? String(item.provenance.projectId) : null;
            const workspaceId = item.provenance?.workspaceId ? String(item.provenance.workspaceId) : null;
            const key = `${repoId}:${projectId || ''}:${workspaceId || ''}`;
            const current = grouped.get(key) ?? { repoId, projectId, workspaceId, count: 0 };
            current.count += 1;
            grouped.set(key, current);
        }
        return {
            generatedAt: Date.now(),
            projects: [...grouped.values()]
                .sort((a, b) => b.count - a.count)
                .slice(0, limit),
        };
    }
    collectTokenOptimization(snapshot, usage) {
        const tokens = snapshot?.tokens ?? this.getRuntime()?.getTokenTelemetrySummary?.() ?? {};
        const budget = usage?.sourceAwareTokenBudget ?? {};
        const autoApplied = Boolean(usage?.tokenAutoApplied);
        const savedTokens = Number(tokens?.savedTokens ?? tokens?.totalSavedTokens ?? tokens?.saved ?? 0);
        const forwardedTokens = Number(tokens?.forwardedTokens ?? tokens?.tokensForwarded ?? tokens?.compressedTokens ?? 0);
        const grossInputTokens = Number(tokens?.grossInputTokens ?? tokens?.totalGrossInputTokens ?? tokens?.gross ?? 0);
        const compressionPct = Number.isFinite(Number(tokens?.compressionPct))
            ? Number(tokens.compressionPct)
            : (grossInputTokens > 0 ? Math.round((savedTokens / grossInputTokens) * 100) : 0);
        return {
            applied: Boolean(usage?.tokenOptimizationApplied || budget?.applied || autoApplied),
            autoApplied,
            savedTokens,
            forwardedTokens,
            grossInputTokens,
            compressionPct,
            estimatedSavings: Number(budget?.estimatedSavings ?? savedTokens ?? 0),
            candidateFiles: Array.isArray(budget?.candidateFiles) ? budget.candidateFiles : [],
            selectedFiles: Array.isArray(budget?.selectedFiles) ? budget.selectedFiles : [],
            reason: budget?.reason || 'Token optimization has not reported a source-aware budget yet.',
            dominantSource: budget?.dominantSource || null,
            bySource: budget?.bySource ?? {},
            byStage: budget?.byStage ?? {},
            totalBudget: Number(budget?.totalBudget ?? 0),
            codeBlocks: budget?.codeBlocks ?? null,
            qualityGates: Array.isArray(budget?.qualityGates) ? budget.qualityGates : [],
            recommendations: Array.isArray(budget?.recommendations) ? budget.recommendations : [],
            dropped: Array.isArray(budget?.dropped) ? budget.dropped : [],
        };
    }
    collectGateSummary(run) {
        const gates = Array.isArray(run?.plannerState?.reviewGates)
            ? run.plannerState.reviewGates
            : Array.isArray(run?.plannerResult?.reviewGates)
                ? run.plannerResult.reviewGates
                : [];
        const counts = gates.reduce((acc, gate) => {
            const key = String(gate?.status || 'planned');
            acc[key] = (acc[key] || 0) + 1;
            return acc;
        }, {});
        return {
            total: gates.length,
            ready: Number(counts.ready || 0),
            blocked: Number(counts.blocked || 0),
            planned: Number(counts.planned || 0),
            skipped: Number(counts.skipped || 0),
            items: gates,
        };
    }
    collectInterpretationIssues(input) {
        const issues = [];
        const selectionAudit = input.usage?.artifactSelectionAudit ?? {};
        const rejected = Array.isArray(selectionAudit?.rejected) ? selectionAudit.rejected : [];
        const latestRunResult = String(input.latestRun?.result || '');
        const lowConfidenceRejected = rejected.filter((entry) => entry?.source === 'scorer' && entry?.confidence === 'low');
        if (lowConfidenceRejected.length > 0) {
            issues.push({
                id: 'selection-low-confidence',
                severity: 'warn',
                summary: `${lowConfidenceRejected.length} low-confidence scorer suggestion(s) were rejected to avoid cross-domain misrouting.`,
            });
        }
        if (/review gate .* remains blocked/i.test(latestRunResult)) {
            issues.push({
                id: 'gate-blocked',
                severity: 'bad',
                summary: latestRunResult,
            });
        }
        if (Number(input.memoryHealth?.quarantined || 0) > 0) {
            issues.push({
                id: 'memory-quarantine',
                severity: 'info',
                summary: `${input.memoryHealth.quarantined} memory entr${input.memoryHealth.quarantined === 1 ? 'y is' : 'ies are'} quarantined or inboxed for review.`,
            });
        }
        return issues;
    }
    collectMemoryQualitySummary(memoryHealth, containers) {
        return {
            active: Number(memoryHealth?.active || 0),
            quarantined: Number(memoryHealth?.quarantined || 0),
            workspace: Number(containers?.byLane?.workspace || 0),
            profile: Number(containers?.byLane?.profile || 0),
            shared: Number(containers?.byLane?.shared || 0),
            inbox: Number(containers?.byLane?.inbox || 0),
            hiddenCount: Number(containers?.hiddenCount || 0),
            topTags: Array.isArray(memoryHealth?.topTags) ? memoryHealth.topTags : [],
        };
    }
    buildDashboardAlerts(payload) {
        const alerts = [];
        const healthMemory = (payload.health.memory || {});
        const healthConnection = (payload.health.connection || {});
        if (payload.usage?.health === 'stale') {
            alerts.push({
                id: 'runtime-stale',
                tone: 'warn',
                title: 'Selected runtime is stale',
                summary: 'Run mutations and deploy controls stay guarded until a fresh runtime heartbeat appears.',
            });
        }
        if (healthMemory.storage?.fallbackApplied) {
            alerts.push({
                id: 'memory-fallback',
                tone: 'warn',
                title: 'Memory storage fallback active',
                summary: `Using ${healthMemory.storage.activeDbPath} because the requested database path was not writable.`,
            });
        }
        if (Number(payload.memoryHealth?.quarantined || 0) > 0) {
            alerts.push({
                id: 'memory-quarantine',
                tone: 'info',
                title: 'Quarantined memory needs review',
                summary: `${payload.memoryHealth.quarantined} memory entr${payload.memoryHealth.quarantined === 1 ? 'y' : 'ies'} are quarantined in the trust layer.`,
            });
        }
        if (healthConnection.stream !== 'connected') {
            alerts.push({
                id: 'stream-idle',
                tone: 'info',
                title: 'Live stream not attached',
                summary: 'The dashboard is polling snapshots instead of consuming the live operator stream.',
            });
        }
        return alerts;
    }
    resolveDashboardEntity(kind, id, url) {
        if (kind === 'run')
            return this.getRuntime()?.getRun?.(id);
        if (kind === 'memory')
            return this.getMemory()?.trace?.(id) ?? this.getMemory()?.getDetail?.(id);
        if (kind === 'skill')
            return this.getRuntime()?.listSkills?.().find((item) => item.skillId === id);
        if (kind === 'workflow')
            return this.getRuntime()?.listWorkflows?.().find((item) => item.workflowId === id);
        if (kind === 'hook')
            return this.getRuntime()?.listHooks?.().find((item) => item.hookId === id);
        if (kind === 'automation')
            return this.getRuntime()?.listAutomations?.().find((item) => item.automationId === id);
        if (kind === 'specialist')
            return this.getRuntime()?.listSpecialists?.().find((item) => item.specialistId === id);
        if (kind === 'crew')
            return this.getRuntime()?.listCrews?.().find((item) => item.crewId === id);
        if (kind === 'client') {
            const snapshot = this.resolveRuntimeSnapshot(url);
            const clients = snapshot?.clients?.detected?.length ? snapshot.clients.detected : this.getClientRegistry()?.listClients(this.getAdapters()) ?? [];
            return clients.find((client) => client.clientId === id);
        }
        if (kind === 'federation-peer') {
            const peers = this.getRuntime()?.getNetworkStatus?.();
            return peers?.knownPeers?.find((peer) => peer.peerId === id);
        }
        return null;
    }
    collectCompatibilityHealth() {
        const capabilities = this.buildAdvertisedCapabilities();
        const compatibility = this.buildCompatibilityStatus(capabilities);
        const runtimeEnvelope = this.buildProbeRuntimeEnvelope();
        const workspace = this.resolveCanonicalWorkspaceContext();
        return {
            dashboardApiVersion: DASHBOARD_API_VERSION,
            projectName: workspace.repoName,
            repoIdentity: {
                repoName: workspace.repoName,
                repoRoot: workspace.repoRoot,
                workspaceRoot: workspace.workspaceRoot,
                workspaceSource: workspace.workspaceSource,
                workspaceStateKey: workspace.stateKey,
                remoteUrl: workspace.remoteUrl ?? null,
                currentRepoId: null,
            },
            scanContext: {
                repoRoot: workspace.repoRoot,
                workspaceRoot: workspace.workspaceRoot,
                workspaceSource: workspace.workspaceSource,
                workspaceStateKey: workspace.stateKey,
                notes: ['Probe snapshot uses the lightweight compatibility contract.'],
            },
            capabilities,
            compatibility,
            dashboardUrl: this.getAddress(),
            dashboardMode: this.dashboardMode,
            connection: {
                stream: this.sseBroker.getClientCount() > 0 ? 'connected' : 'idle',
                subscribers: this.sseBroker.getClientCount(),
            },
            runtimeEnvelope,
            runtime: {
                runtimeId: null,
                mode: 'probe',
            },
            memory: {
                storage: null,
                mode: 'probe',
            },
            pod: {
                workers: 0,
            },
            clients: {
                total: 0,
                primaryClientId: null,
            },
            release: {},
            docs: {},
            ci: {},
            skillDistribution: {
                serverGoverned: true,
                eligibleClients: 0,
                distributableSkills: 0,
            },
        };
    }
    async collectHealth() {
        const packageJsonPath = path.join(this.repoRoot, 'package.json');
        const workflowPath = path.join(this.repoRoot, '.github', 'workflows', 'pages.yml');
        const docsDir = path.join(this.repoRoot, 'docs');
        const runtime = this.getRuntime();
        const memory = this.getMemory();
        const clientRegistry = this.getClientRegistry();
        const podSnapshot = (await import('../engines/pod-network.js')).podNetwork.getDashboardSnapshot(20);
        const runtimes = this.runtimeRegistry.list();
        const skillDistribution = this.getSkillDistributionManifest();
        let packageVersion = 'unknown';
        const ownPkgPath = path.join(__dirname, '..', 'package.json');
        const ownPkgPath2 = path.join(__dirname, '..', '..', 'package.json');
        for (const candidate of [ownPkgPath, ownPkgPath2, packageJsonPath]) {
            if (packageVersion !== 'unknown')
                break;
            const pkg = await this.readJsonIfExists(candidate);
            const version = typeof pkg?.version === 'string' ? pkg.version : '';
            if (version) {
                packageVersion = version;
            }
        }
        if (!this.gitUser) {
            const gitConfigPath = path.join(this.repoRoot, '.git', 'config');
            if (await this.fileExists(gitConfigPath)) {
                try {
                    const gitConfig = await fs.promises.readFile(gitConfigPath, 'utf8');
                    const match = gitConfig.match(/name\s*=\s*(.+)/);
                    this.gitUser = match?.[1]?.trim() || '';
                }
                catch {
                    this.gitUser = '';
                }
            }
        }
        let pagesWorkflowValid = false;
        try {
            const raw = await fs.promises.readFile(workflowPath, 'utf8');
            pagesWorkflowValid = raw.includes('steps.deployment.outputs.page_url');
        }
        catch {
            // Keep the workflow flag false when the file is missing or unreadable.
        }
        const clients = clientRegistry?.listClients(this.getAdapters()) ?? [];
        const primaryClient = clientRegistry?.getPrimaryClient(this.getAdapters());
        const docsPresent = await this.fileExists(docsDir);
        const packagePresent = await this.fileExists(packageJsonPath);
        const workspace = this.resolveCanonicalWorkspaceContext();
        const synapseStatus = this.getSynapse()?.getStatus?.() ?? createEngineHealthStatus({
            name: 'synapse',
            available: Boolean(this.synapseProvider),
            ready: Boolean(this.getSynapse()),
            repoRoot: workspace.repoRoot,
            reason: this.getSynapse() ? undefined : 'Synapse runtime is not attached to this dashboard.',
        });
        const architectsStatus = this.getArchitects()?.getStatus?.() ?? createEngineHealthStatus({
            name: 'architects',
            available: Boolean(this.architectsProvider),
            ready: Boolean(this.getArchitects()),
            repoRoot: workspace.repoRoot,
            reason: this.getArchitects() ? undefined : 'Architects runtime is not attached to this dashboard.',
        });
        const runtimeEnvelope = this.runtimeHealthProvider?.() ?? buildRuntimeHealthEnvelope({
            workspace,
            runtime,
            memory,
            orchestrator: this.getOrchestrator(),
            clients,
            primaryClient,
            synapse: synapseStatus,
            architects: architectsStatus,
        });
        const repoIdentity = {
            repoName: workspace.repoName,
            repoRoot: workspace.repoRoot,
            workspaceRoot: workspace.workspaceRoot,
            workspaceSource: workspace.workspaceSource,
            workspaceStateKey: workspace.stateKey,
            remoteUrl: workspace.remoteUrl ?? null,
            currentRepoId: memory?.getCurrentRepoId?.() ?? null,
        };
        const scanContext = {
            repoRoot: runtimeEnvelope.workspace.repoRoot,
            workspaceRoot: runtimeEnvelope.workspace.workspaceRoot,
            workspaceSource: runtimeEnvelope.workspace.source,
            workspaceStateKey: workspace.stateKey,
            notes: Array.isArray(runtimeEnvelope.workspace.notes) ? runtimeEnvelope.workspace.notes.slice(0, 6) : [],
        };
        const capabilities = this.buildAdvertisedCapabilities();
        const compatibility = this.buildCompatibilityStatus(capabilities);
        return {
            dashboardApiVersion: DASHBOARD_API_VERSION,
            projectName: workspace.repoName,
            currentRepoId: repoIdentity.currentRepoId,
            repoIdentity,
            scanContext,
            capabilities,
            compatibility,
            dashboardUrl: this.getAddress(),
            dashboardMode: this.dashboardMode,
            connection: {
                stream: this.sseBroker.getClientCount() > 0 ? 'connected' : 'idle',
                subscribers: this.sseBroker.getClientCount(),
            },
            runtimeEnvelope,
            runtime: {
                ...(runtime?.getHealth?.() ?? { runtime: 'unavailable' }),
                runtimeCount: runtimes.length,
                selectedRuntimeId: runtime?.getRuntimeId?.() ?? runtimes[0]?.runtimeId ?? null,
            },
            memory: {
                ...(memory?.getStats?.() ?? {
                    schemaVersion: 0,
                    prefrontal: 0,
                    hippocampus: 0,
                    cortex: 0,
                    totalLinks: 0,
                    oldestEntry: null,
                    purgeEligible: 0,
                    quarantineBacklog: 0,
                    lastHygieneAt: null,
                    topTags: [],
                }),
                storage: memory?.getStorageStatus?.() ?? null,
            },
            pod: {
                workers: podSnapshot.activeWorkers.length,
                lastMessageTimestamp: podSnapshot.lastMessageTimestamp,
                confidenceBands: podSnapshot.confidenceBands,
            },
            clients: {
                total: clients.length,
                active: clients.filter((client) => client.state === 'primaryActive' || client.state === 'active').length,
                installed: clients.filter((client) => client.state === 'installed').length,
                primary: primaryClient ? {
                    clientId: primaryClient.clientId,
                    displayName: primaryClient.displayName,
                    state: primaryClient.state,
                    source: primaryClient.source,
                } : null,
            },
            release: {
                packageVersion,
                gitUser: this.gitUser,
            },
            docs: {
                present: docsPresent,
                pagesWorkflowValid,
            },
            ci: {
                lintScriptPresent: packagePresent,
                eventHistory: (await import('../engines/event-bus.js')).nexusEventBus.getHistory().length,
            },
            skillDistribution: {
                serverGoverned: skillDistribution.serverGoverned,
                eligibleClients: skillDistribution.summary.eligibleClients,
                distributableSkills: skillDistribution.summary.distributableSkills,
            },
        };
    }
    buildAdvertisedCapabilities() {
        return {
            ...CORE_CAPABILITIES,
            hooks: true,
            automations: true,
            federation: true,
            specialists: true,
            crews: true,
            planner: true,
            orchestration: true,
            clientPrimary: true,
            instructionPacket: true,
            orchestrationLedger: true,
            knowledgeFabric: true,
            ragCollections: true,
            patterns: true,
            modelTiers: true,
            workerPlan: true,
            artifactOutcomes: true,
            memoryTrace: true,
            memoryShared: true,
            worktreeHealth: true,
            featureRegistry: true,
            synapse: true,
            architects: true,
            dashboardSummary: true,
            dashboardSurfaces: true,
            nexusLayer: true,
            skillDistribution: true,
            runtimeHealth: true,
        };
    }
    buildCompatibilityStatus(capabilities) {
        const requiredCapabilities = [...DASHBOARD_COMPATIBILITY_CAPABILITIES];
        const missingCapabilities = requiredCapabilities.filter((capability) => capabilities[capability] !== true);
        return {
            schemaVersion: DASHBOARD_SCHEMA_VERSION,
            requiredCapabilities,
            missingCapabilities,
            status: missingCapabilities.length === 0 ? 'compatible' : 'incompatible',
        };
    }
    buildProbeRuntimeEnvelope() {
        const stateRoot = resolveNexusStateDir();
        return {
            schemaVersion: 1,
            status: 'healthy',
            workspace: {
                workspaceRoot: this.repoRoot,
                repoRoot: this.repoRoot,
                stateRoot,
                source: 'cwd',
                notes: ['Probe snapshot uses the lightweight compatibility contract.'],
            },
            engines: {
                synapse: createEngineHealthStatus({
                    name: 'synapse',
                    available: true,
                    ready: true,
                    repoRoot: this.repoRoot,
                }),
                architects: createEngineHealthStatus({
                    name: 'architects',
                    available: true,
                    ready: true,
                    repoRoot: this.repoRoot,
                }),
            },
            runtime: {
                available: true,
                runtimeId: null,
                latestRunId: null,
                runsTracked: 0,
            },
            memory: {
                available: true,
                activeDbPath: null,
                fallbackApplied: false,
            },
            clients: {
                primaryClientId: null,
                total: 0,
                active: 0,
            },
            worktree: {
                status: 'Probe snapshot ready.',
                actions: [],
            },
            selection: {
                summary: 'Probe snapshot omits selection hydration.',
                selected: 0,
                rejected: 0,
            },
            compatibility: {
                requiredCapabilities: [...DASHBOARD_COMPATIBILITY_CAPABILITIES],
                missingCapabilities: [],
                ready: true,
            },
            degradedReasons: [],
        };
    }
    buildUrl(port) {
        return `http://${HOST}:${port}`;
    }
    async probeDashboard(port) {
        const url = this.buildUrl(port);
        for (let attempt = 1; attempt <= DASHBOARD_PROBE_ATTEMPTS; attempt += 1) {
            try {
                const response = await this.requestProbe(`${url}/api/health?probe=1`);
                if (response.statusCode !== 200) {
                    return {
                        status: 'incompatible',
                        url,
                        reason: `health-status-${response.statusCode}`,
                        attempts: attempt,
                    };
                }
                let payload = null;
                try {
                    payload = JSON.parse(response.body);
                }
                catch {
                    return {
                        status: 'incompatible',
                        url,
                        reason: 'health-invalid-json',
                        attempts: attempt,
                    };
                }
                if (this.isCompatibleHealth(payload)) {
                    return {
                        status: 'compatible',
                        url,
                        health: payload,
                        attempts: attempt,
                    };
                }
                return {
                    status: 'incompatible',
                    url,
                    health: payload,
                    reason: 'health-incompatible',
                    attempts: attempt,
                };
            }
            catch (error) {
                if (this.isFreePortProbeError(error)) {
                    return {
                        status: 'free',
                        url,
                        reason: error instanceof Error ? error.message : 'connection-refused',
                        attempts: attempt,
                    };
                }
                if (this.isProbeAbortError(error)) {
                    throw error;
                }
                if (this.isProbeTimeoutError(error)) {
                    if (attempt < DASHBOARD_PROBE_ATTEMPTS) {
                        continue;
                    }
                    return {
                        status: 'indeterminate',
                        url,
                        reason: error instanceof Error ? error.message : 'probe-timeout',
                        attempts: attempt,
                    };
                }
                return {
                    status: 'incompatible',
                    url,
                    reason: error instanceof Error ? error.message : 'probe-failed',
                    attempts: attempt,
                };
            }
        }
        return {
            status: 'indeterminate',
            url,
            reason: 'probe-timeout',
            attempts: DASHBOARD_PROBE_ATTEMPTS,
        };
    }
    isCompatibleHealth(payload) {
        if (!payload || payload.dashboardApiVersion !== DASHBOARD_API_VERSION) {
            return false;
        }
        if (payload.compatibility?.status !== 'compatible') {
            return false;
        }
        const stateRoot = payload.runtimeEnvelope?.workspace?.stateRoot;
        if (!stateRoot || path.resolve(stateRoot) !== path.resolve(resolveNexusStateDir())) {
            return false;
        }
        const required = payload.compatibility?.requiredCapabilities?.length
            ? payload.compatibility.requiredCapabilities
            : [...DASHBOARD_COMPATIBILITY_CAPABILITIES];
        return required.every((capability) => payload.capabilities?.[capability] === true);
    }
    isFreePortProbeError(error) {
        const code = typeof error === 'object' && error && 'code' in error ? String(error.code) : '';
        return code === 'ECONNREFUSED' || code === 'ECONNRESET' || code === 'EHOSTUNREACH' || code === 'ENOTFOUND';
    }
    isProbeTimeoutError(error) {
        const code = typeof error === 'object' && error && 'code' in error ? String(error.code) : '';
        const message = error instanceof Error ? error.message : String(error);
        return code === 'ETIMEDOUT' || message === 'probe-timeout';
    }
    isProbeAbortError(error) {
        const code = typeof error === 'object' && error && 'code' in error ? String(error.code) : '';
        const name = error instanceof Error ? error.name : '';
        return code === 'ABORT_ERR' || name === 'AbortError';
    }
    requestProbe(url) {
        return new Promise((resolve, reject) => {
            const controller = new AbortController();
            this.activeProbeControllers.add(controller);
            const cleanup = () => {
                this.activeProbeControllers.delete(controller);
            };
            const req = http.get(url, { timeout: DASHBOARD_PROBE_TIMEOUT_MS, signal: controller.signal }, (res) => {
                const chunks = [];
                res.on('data', (chunk) => {
                    chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
                });
                res.on('end', () => {
                    cleanup();
                    resolve({
                        statusCode: res.statusCode ?? 0,
                        body: Buffer.concat(chunks).toString('utf8'),
                    });
                });
                res.on('error', (error) => {
                    cleanup();
                    reject(error);
                });
            });
            req.on('timeout', () => {
                req.destroy(new Error('probe-timeout'));
            });
            req.on('error', (error) => {
                cleanup();
                reject(error);
            });
        });
    }
    async bindFirstAvailablePort(startPort, endPort) {
        let lastError = null;
        for (let port = startPort; port <= endPort; port += 1) {
            try {
                await this.listenOnPort(port);
                return port;
            }
            catch (error) {
                lastError = error;
                const code = typeof error === 'object' && error && 'code' in error ? String(error.code) : '';
                if (code !== 'EADDRINUSE') {
                    throw error;
                }
            }
        }
        throw lastError instanceof Error
            ? lastError
            : new Error(`No free dashboard port found in range ${startPort}-${endPort}`);
    }
    listenOnPort(port) {
        return new Promise((resolve, reject) => {
            const handleListening = () => {
                cleanup();
                resolve();
            };
            const handleError = (error) => {
                cleanup();
                reject(error);
            };
            const cleanup = () => {
                this.server.off('listening', handleListening);
                this.server.off('error', handleError);
            };
            this.server.once('listening', handleListening);
            this.server.once('error', handleError);
            this.server.listen(port, HOST);
        });
    }
}
