import { randomUUID } from "node:crypto";
import { ensureOutDir } from "../artifacts/artifacts.js";
import { buildFileInstructions, outboxPath, stageFile, } from "../artifacts/attachments.js";
import { CODEX_AGENT_CAPABILITIES, agentLabel, agentReasoningLabel, agentReasoningOptions, } from "../agents/shared/agent.js";
import { getExternalSnapshotForSession } from "../agents/shared/agent-activity.js";
import { listAgentAdapterDescriptors } from "../agents/shared/agent-adapter.js";
import { findLaunchProfileRecord } from "../agents/shared/agent-launch-profiles.js";
import { AgentUpdateManager } from "../agents/shared/agent-updates.js";
import { createAgentSessionService, enabledAgents } from "../agents/shared/agent-factory.js";
import { AuditLogStore } from "../access/audit-log.js";
import { BotPreferencesStore } from "../state/bot-preferences.js";
import { ChannelCommandService } from "../channels/shared/channel-command-service.js";
import { ChannelTurnService } from "../channels/shared/channel-turn-service.js";
import { activeSessionSourceForContextKey, ChannelMirrorRegistry } from "../channels/shared/channel-mirror-registry.js";
import { listThreads as listCodexThreads } from "../agents/codex/codex-state.js";
import { cursorPage, normalizeCursorLimit } from "../core/pagination.js";
import { friendlyErrorText } from "../core/error-messages.js";
import { clearLogFile, getAgentUpdateLogPath, getConnectorHealth, getConnectorLogPath, getPackageVersion, getUpdateLogPath, getVersionChecks, readConnectorState, readFormattedLogTail, spawnConnectorRestart, spawnSelfUpdate } from "../support/operations.js";
import { PromptStore, toPromptEnvelope } from "../state/prompt-store.js";
import { UnifiedJobStore } from "../state/job-store.js";
import { buildRuntimeMetrics } from "./metrics.js";
import { RelayArtifactService } from "./relay-artifact-service.js";
import { RelayAuthService } from "./relay-auth-service.js";
import { RelayExternalActivityMonitor } from "./relay-external-activity-monitor.js";
import { RelayQueueService } from "./relay-queue-service.js";
import { RuntimeSnapshotCache } from "./runtime-cache.js";
import { activeSessionPriority, activityToUnifiedJob, agentUpdateStatusToUnified, dedupeJobs, hostLoginCommand, hostLogoutCommand, isExternalSnapshotSuppressedByManagedAbort, isPromptTerminalActivity, normalizeMimeType, promptActivityToUnifiedJob, shouldRefreshActiveSessions, taskToUnifiedJob, uploadFileDtos, } from "./relay-runtime-helpers.js";
import { RelayDashboardService } from "./relay-dashboard-service.js";
import { capabilitiesOf } from "../channels/shared/bot-rendering.js";
import { SessionLockStore } from "../access/session-locks.js";
import { SessionRegistry } from "../state/session-registry.js";
import { createSupportBundle } from "../support/support-bundle.js";
import { transcribeAudio } from "../artifacts/voice.js";
import { WebActivityStore, WebChatStore, } from "../web/web-state.js";
import { evaluateWorkspacePolicy, filterAllowedWorkspaces } from "../core/workspace-policy.js";
import { externalSnapshotMessages, mergeSessionDetailMessages } from "./relay-runtime-session-detail.js";
export { relayRuntimeSessionDetail, relayRuntimeSetSessionName } from "./relay-runtime-session-detail.js";
export const WEB_CONTEXT_KEY = "web:dashboard";
const ACTIVE_CODEX_DISCOVERY_LIMIT = 200;
const ACTIVE_ACTIVITY_TTL_MS = 6 * 60 * 60 * 1000;
const MAX_CHAT_HISTORY = 250;
export function relayRuntimeLocks(runtime) {
    return runtime.lockStore.list();
}
export function relayRuntimeLockWebSession(runtime, ownerName = "Web dashboard", actor) {
    const label = ownerName || actor?.label || "Web dashboard";
    const lock = runtime.lockStore.set(runtime.contextKey, {
        userId: actor?.id ?? "web",
        label,
        channel: "web",
    }, runtime.config.sessionLockTtlMs);
    runtime.appendActivity({
        source: "web",
        status: "info",
        type: "lock_created",
        threadId: null,
        workspace: runtime.config.workspace,
        actor,
        detail: `locked by ${label}`,
    });
    runtime.appendAudit({
        action: "lock_updated",
        status: "ok",
        contextKey: runtime.contextKey,
        actor,
        description: "lock",
        detail: `locked by ${label}`,
    });
    return lock;
}
export function relayRuntimeUnlockWebSession(runtime, actor) {
    const removed = runtime.lockStore.clear(runtime.contextKey);
    runtime.appendActivity({
        source: "web",
        status: "info",
        type: "lock_removed",
        threadId: null,
        workspace: runtime.config.workspace,
        actor,
        detail: removed ? "unlocked" : "no lock",
    });
    runtime.appendAudit({
        action: "lock_updated",
        status: "ok",
        contextKey: runtime.contextKey,
        actor,
        description: "unlock",
        detail: removed ? "unlocked" : "no lock",
    });
    return { removed, locks: runtime.locks() };
}
export async function relayRuntimeControlOptions(runtime, agentId) {
    const { session, dispose } = await runtime.getControlSession(agentId);
    try {
        const info = runtime.publicInfo(session);
        const capabilities = info.capabilities ?? CODEX_AGENT_CAPABILITIES;
        if (capabilities.modelSelection) {
            await session.refreshModels().catch((error) => {
                console.warn(`Failed to refresh ${agentLabel(info.agentId)} models: ${error instanceof Error ? error.message : String(error)}`);
            });
        }
        return {
            models: capabilities.modelSelection ? session.listModels() : [],
            reasoningLabel: agentReasoningLabel(info.agentId),
            reasoningOptions: agentReasoningOptions(info.agentId),
            launchProfiles: capabilities.launchProfiles ? session.listLaunchProfiles() : [],
            workspaces: filterAllowedWorkspaces(session.listWorkspaces(), runtime.config),
            capabilities,
        };
    }
    finally {
        if (dispose) {
            session.dispose();
        }
    }
}
export async function relayRuntimeAuthStatus(runtime, agentId) {
    const { session, dispose } = await runtime.getControlSession(agentId);
    try {
        const info = runtime.publicInfo(session);
        const capabilities = info.capabilities ?? CODEX_AGENT_CAPABILITIES;
        if (!capabilities.auth) {
            return {
                agentId: info.agentId,
                agentLabel: info.agentLabel,
                supported: false,
                authenticated: null,
                detail: `${info.agentLabel} authentication is managed outside NordRelay.`,
                loginSupported: false,
                logoutSupported: false,
                hostLoginCommand: hostLoginCommand(info, runtime.config),
                hostLogoutCommand: hostLogoutCommand(info, runtime.config),
            };
        }
        const status = await runtime.authService.check(info);
        return {
            agentId: info.agentId,
            agentLabel: info.agentLabel,
            supported: true,
            authenticated: status.authenticated,
            method: status.method,
            detail: status.detail,
            loginSupported: capabilities.login,
            logoutSupported: capabilities.logout,
            hostLoginCommand: hostLoginCommand(info, runtime.config),
            hostLogoutCommand: hostLogoutCommand(info, runtime.config),
        };
    }
    finally {
        if (dispose) {
            session.dispose();
        }
    }
}
export async function relayRuntimeLogin(runtime, agentId, actor) {
    const { session, dispose } = await runtime.getControlSession(agentId);
    try {
        const info = runtime.publicInfo(session);
        const capabilities = info.capabilities ?? CODEX_AGENT_CAPABILITIES;
        if (!capabilities.login) {
            return {
                ...(await runtime.authStatus(info.agentId)),
                result: {
                    success: false,
                    message: `${info.agentLabel} login is not managed by NordRelay. Run ${hostLoginCommand(info, runtime.config)} on the host.`,
                },
            };
        }
        if (!runtime.config.enableTelegramLogin) {
            return {
                ...(await runtime.authStatus(info.agentId)),
                result: {
                    success: false,
                    message: `Remote login is disabled. Run ${hostLoginCommand(info, runtime.config)} on the host.`,
                },
            };
        }
        const result = await runtime.authService.startLogin(info);
        runtime.appendActivity({
            source: "web",
            status: result.success ? "info" : "failed",
            type: result.success ? "login_started" : "login_failed",
            threadId: info.threadId,
            workspace: info.workspace,
            agentId: info.agentId,
            actor,
            detail: result.message,
        });
        runtime.appendAudit({
            action: "command",
            status: result.success ? "ok" : "failed",
            contextKey: runtime.contextKey,
            agentId: info.agentId,
            threadId: info.threadId,
            workspace: info.workspace,
            actor,
            description: "login",
            detail: result.message,
        });
        return { ...(await runtime.authStatus(info.agentId)), result };
    }
    finally {
        if (dispose) {
            session.dispose();
        }
    }
}
export async function relayRuntimeLogout(runtime, agentId, actor) {
    const { session, dispose } = await runtime.getControlSession(agentId);
    try {
        const info = runtime.publicInfo(session);
        const capabilities = info.capabilities ?? CODEX_AGENT_CAPABILITIES;
        if (!capabilities.logout) {
            return {
                ...(await runtime.authStatus(info.agentId)),
                result: {
                    success: false,
                    message: `${info.agentLabel} logout is not managed by NordRelay. Run ${hostLogoutCommand(info, runtime.config)} on the host.`,
                },
            };
        }
        if (!runtime.config.enableTelegramLogin) {
            return {
                ...(await runtime.authStatus(info.agentId)),
                result: {
                    success: false,
                    message: `Remote auth management is disabled. Run ${hostLogoutCommand(info, runtime.config)} on the host.`,
                },
            };
        }
        const current = await runtime.authService.check(info);
        if (current.method === "api-key") {
            return {
                ...(await runtime.authStatus(info.agentId)),
                result: {
                    success: false,
                    message: "Cannot logout while API-key authentication is configured. Remove the API key from .env to use CLI auth.",
                },
            };
        }
        const result = await runtime.authService.startLogout(info);
        runtime.appendActivity({
            source: "web",
            status: result.success ? "info" : "failed",
            type: result.success ? "logout_completed" : "logout_failed",
            threadId: info.threadId,
            workspace: info.workspace,
            agentId: info.agentId,
            actor,
            detail: result.message,
        });
        runtime.appendAudit({
            action: "command",
            status: result.success ? "ok" : "failed",
            contextKey: runtime.contextKey,
            agentId: info.agentId,
            threadId: info.threadId,
            workspace: info.workspace,
            actor,
            description: "logout",
            detail: result.message,
        });
        return { ...(await runtime.authStatus(info.agentId)), result };
    }
    finally {
        if (dispose) {
            session.dispose();
        }
    }
}
export async function relayRuntimeChatHistory(runtime, limit = 200) {
    const session = await runtime.getSession(true);
    const info = runtime.publicInfo(session);
    const webMessages = runtime.chatStore.list(info.threadId, limit);
    if (!info.threadId) {
        return webMessages;
    }
    const external = getExternalSnapshotForSession(session, runtime.config, { maxEvents: limit });
    return mergeSessionDetailMessages(webMessages, externalSnapshotMessages(external, info.threadId), limit);
}
export async function relayRuntimeChatHistoryPage(runtime, options = {}) {
    const limit = normalizeCursorLimit(options.limit, 80, MAX_CHAT_HISTORY);
    const messages = await relayRuntimeChatHistory(runtime, MAX_CHAT_HISTORY);
    const newestFirst = [...messages].sort((left, right) => Date.parse(right.timestamp) - Date.parse(left.timestamp));
    const page = cursorPage(newestFirst, options.cursor, limit, (message) => message.id);
    return {
        items: page.items.sort((left, right) => Date.parse(left.timestamp) - Date.parse(right.timestamp)),
        pagination: page.pagination,
    };
}
export async function relayRuntimeWebMirrorPreference(runtime, argument = "", actor) {
    const session = await runtime.getSession(true);
    runtime.registry.updateMetadata(runtime.contextKey, session);
    const info = runtime.publicInfo(session);
    const response = new ChannelCommandService(runtime.config).renderMirrorPreference({
        source: "web",
        contextKey: runtime.contextKey,
        argument,
        preferencesStore: runtime.preferencesStore,
        cliMirrorSupported: capabilitiesOf(info).cliMirror,
        agentLabel: info.agentLabel,
    });
    const mode = runtime.preferencesStore.get(runtime.contextKey).mirrorMode ?? runtime.config.webMirrorMode;
    const changed = argument.trim() && response.plain.startsWith("CLI mirroring:");
    if (changed) {
        runtime.appendActivity({
            source: "web",
            status: "info",
            type: "mirror_mode_changed",
            threadId: info.threadId,
            workspace: info.workspace,
            agentId: info.agentId,
            actor,
            detail: mode,
        });
        runtime.appendAudit({
            action: "command",
            status: "ok",
            contextKey: runtime.contextKey,
            agentId: info.agentId,
            threadId: info.threadId,
            workspace: info.workspace,
            actor,
            description: `mirror ${mode}`,
        });
        runtime.externalActivityMonitor.reset();
        void runtime.externalActivityMonitor.monitorSafe();
    }
    return {
        mode,
        minInterval: runtime.config.webMirrorMinUpdateMs,
        response,
    };
}
export async function relayRuntimeClearChatHistory(runtime, actor) {
    const session = await runtime.getSession(true);
    const info = runtime.publicInfo(session);
    const removed = runtime.chatStore.clear(info.threadId);
    const messages = await runtime.chatHistory();
    runtime.broadcast({ type: "chat_history", messages });
    runtime.appendActivity({
        source: "web",
        status: "info",
        type: "chat_history_cleared",
        threadId: info.threadId,
        workspace: info.workspace,
        agentId: info.agentId,
        actor,
        detail: `${removed} messages removed.`,
    });
    return { removed, messages };
}
export function relayRuntimeActivity(runtime, options = {}) {
    const currentInfo = runtime.registry.get(runtime.contextKey)?.getInfo();
    return runtime.activityStore.list(options).map((event) => runtime.enrichActivityEvent(event, currentInfo));
}
export function relayRuntimeActivityPage(runtime, options = {}) {
    const currentInfo = runtime.registry.get(runtime.contextKey)?.getInfo();
    const page = runtime.activityStore.listPage(options);
    return {
        ...page,
        items: page.items.map((event) => runtime.enrichActivityEvent(event, currentInfo)),
    };
}
export async function relayRuntimeRetry(runtime, actor) {
    const cached = runtime.queueService.getLastPrompt();
    if (!cached) {
        throw new Error("Nothing to retry. Send a message first.");
    }
    runtime.appendAudit({
        action: "command",
        status: "ok",
        contextKey: runtime.contextKey,
        actor,
        description: "retry",
        detail: cached.description,
    });
    return runtime.sendEnvelope({ ...cached, activityActor: cached.activityActor ?? actor }, actor);
}
export async function relayRuntimeSync(runtime, actor) {
    const session = await runtime.getSession(true);
    const info = runtime.publicInfo(session);
    if (!(info.capabilities ?? CODEX_AGENT_CAPABILITIES).externalActivity) {
        throw new Error(`${info.agentLabel} has no external state watcher to sync.`);
    }
    const result = session.syncFromAgentState({ reattach: true });
    if (result.changed) {
        runtime.updateSession(session);
    }
    runtime.appendActivity({
        source: "web",
        status: "info",
        type: "session_sync",
        threadId: result.info.threadId,
        workspace: result.info.workspace,
        agentId: result.info.agentId,
        actor,
        detail: result.changedFields.length > 0 ? result.changedFields.join(", ") : "already in sync",
    });
    runtime.appendAudit({
        action: "command",
        status: "ok",
        contextKey: runtime.contextKey,
        agentId: result.info.agentId,
        threadId: result.info.threadId,
        workspace: result.info.workspace,
        actor,
        description: "sync",
        detail: result.changedFields.join(", ") || "none",
    });
    return result;
}
export async function relayRuntimeListModels(runtime) {
    const session = await runtime.getSession(true);
    const info = runtime.publicInfo(session);
    await session.refreshModels({ force: true }).catch((error) => {
        console.warn(`Failed to refresh ${agentLabel(info.agentId)} models: ${error instanceof Error ? error.message : String(error)}`);
    });
    return session.listModels();
}
export async function relayRuntimeSetAgent(runtime, agentId, actor) {
    if (!enabledAgents(runtime.config).includes(agentId)) {
        throw new Error(`Agent is not enabled: ${agentId}`);
    }
    const session = await runtime.registry.switchAgent(runtime.contextKey, agentId);
    runtime.updateSession(session);
    const info = runtime.publicInfo(session);
    runtime.appendActivity({
        source: "web",
        status: "info",
        type: "agent_switch",
        threadId: info.threadId,
        workspace: info.workspace,
        agentId: info.agentId,
        actor,
        detail: `Dashboard switched agent to ${info.agentLabel}.`,
    });
    return runtime.publicInfo(session);
}
export async function relayRuntimeNewSession(runtime, options = {}, actor) {
    const session = options.agentId ? await runtime.registry.switchAgent(runtime.contextKey, options.agentId) : await runtime.getSession(true);
    runtime.ensureIdle(session);
    const requestedMode = options.workspaceMode ?? runtime.config.sessionWorkspaceMode;
    const sourceWorkspace = options.workspace ?? session.getInfo().workspace ?? runtime.config.workspace;
    assertRuntimeWorkspaceAllowed(runtime, sourceWorkspace);
    let worktree;
    let effectiveWorkspace = options.workspace;
    if (requestedMode === "worktree") {
        worktree = runtime.worktreeService.create({
            agentId: session.getInfo().agentId,
            contextKey: runtime.contextKey,
            sourceWorkspace,
        });
        effectiveWorkspace = worktree.worktreePath;
    }
    else if (effectiveWorkspace) {
        assertRuntimeWorkspaceAllowed(runtime, effectiveWorkspace);
    }
    if (options.reasoningEffort) {
        const reasoningOptions = agentReasoningOptions(session.getInfo().agentId);
        if (!reasoningOptions.includes(options.reasoningEffort)) {
            throw new Error(`Invalid ${agentReasoningLabel(session.getInfo().agentId)} value: ${options.reasoningEffort}`);
        }
        session.setReasoningEffort(options.reasoningEffort);
    }
    if (options.launchProfileId && (session.getInfo().capabilities ?? CODEX_AGENT_CAPABILITIES).launchProfiles) {
        session.setLaunchProfile(options.launchProfileId);
    }
    if (typeof options.fastMode === "boolean" && (session.getInfo().capabilities ?? CODEX_AGENT_CAPABILITIES).fastMode) {
        session.setFastMode(options.fastMode);
    }
    const info = await session.newThread(effectiveWorkspace, options.model);
    if (worktree) {
        worktree = runtime.worktreeService.linkThread(worktree.id, info.threadId, info.agentId, runtime.contextKey);
    }
    runtime.updateSession(session);
    runtime.appendActivity({
        source: "web",
        status: "info",
        type: "session_new",
        threadId: info.threadId,
        workspace: info.workspace,
        agentId: info.agentId,
        actor,
        detail: worktree ? `New isolated session worktree ${worktree.branchName} created.` : "New dashboard session created.",
    });
    return runtime.publicInfo(session);
}
export async function relayRuntimeSwitchSession(runtime, threadId, actor) {
    const session = await runtime.getSession(true);
    const record = session.getSessionRecord(threadId);
    if (record?.cwd) {
        assertRuntimeWorkspaceAllowed(runtime, record.cwd);
    }
    const info = await session.switchSession(threadId);
    assertRuntimeWorkspaceAllowed(runtime, info.workspace);
    runtime.updateSession(session);
    runtime.broadcast({ type: "chat_history", messages: await runtime.chatHistory() });
    runtime.appendActivity({
        source: "web",
        status: "info",
        type: "session_switch",
        threadId: info.threadId,
        workspace: info.workspace,
        agentId: info.agentId,
        actor,
        detail: "Dashboard switched session.",
    });
    return runtime.publicInfo(session);
}
export async function relayRuntimeAttachSession(runtime, threadId, actor) {
    return runtime.switchSession(threadId, actor);
}
function assertRuntimeWorkspaceAllowed(runtime, workspace) {
    if (!workspace)
        return;
    const policy = evaluateWorkspacePolicy(workspace, runtime.config);
    if (!policy.allowed) {
        throw new Error(policy.warning ?? `Workspace is outside WORKSPACE_ALLOWED_ROOTS: ${workspace}`);
    }
}
export async function relayRuntimeSetModel(runtime, model, actor) {
    const session = await runtime.getSession(true);
    runtime.ensureIdle(session);
    await session.setModelForCurrentSession(model);
    runtime.updateSession(session);
    const info = runtime.publicInfo(session);
    runtime.appendActivity({ source: "web", status: "info", type: "model_changed", threadId: info.threadId, workspace: info.workspace, agentId: info.agentId, actor, detail: model });
    return info;
}
export async function relayRuntimeSetReasoningEffort(runtime, effort, actor) {
    const session = await runtime.getSession(true);
    runtime.ensureIdle(session);
    const options = agentReasoningOptions(session.getInfo().agentId);
    if (!options.includes(effort)) {
        throw new Error(`Invalid ${agentReasoningLabel(session.getInfo().agentId)} value: ${effort}`);
    }
    await session.setReasoningEffortForCurrentSession(effort);
    runtime.updateSession(session);
    const info = runtime.publicInfo(session);
    runtime.appendActivity({ source: "web", status: "info", type: "reasoning_changed", threadId: info.threadId, workspace: info.workspace, agentId: info.agentId, actor, detail: effort });
    return info;
}
export async function relayRuntimeSetFastMode(runtime, enabled, actor) {
    const session = await runtime.getSession(true);
    runtime.ensureIdle(session);
    if (!(session.getInfo().capabilities ?? CODEX_AGENT_CAPABILITIES).fastMode) {
        throw new Error(`Fast mode is not supported for ${agentLabel(session.getInfo().agentId)}.`);
    }
    session.setFastMode(enabled);
    runtime.updateSession(session);
    const info = runtime.publicInfo(session);
    runtime.appendActivity({ source: "web", status: "info", type: "fast_mode_changed", threadId: info.threadId, workspace: info.workspace, agentId: info.agentId, actor, detail: enabled ? "on" : "off" });
    return info;
}
export async function relayRuntimeSetLaunchProfile(runtime, profileId, actor, options = {}) {
    const session = await runtime.getSession(true);
    runtime.ensureIdle(session);
    if (options.confirmUnsafe !== undefined) {
        const info = session.getInfo();
        const profile = findLaunchProfileRecord(info.agentId, profileId, session.listLaunchProfiles());
        if (profile?.unsafe && !options.confirmUnsafe) {
            throw new Error(`Unsafe launch profile "${profile.label}" requires explicit confirmation.`);
        }
    }
    if (options.applyToCurrent) {
        const external = getExternalSnapshotForSession(session, runtime.config, { maxEvents: 0 });
        if (external?.activity.active &&
            !session.isProcessing() &&
            !isExternalSnapshotSuppressedByManagedAbort(external, runtime.activityStore.list({ threadId: external.threadId, limit: 50 }))) {
            throw new Error(`Cannot apply launch profile while the external ${external.agentLabel} CLI task is still running.`);
        }
    }
    const result = options.applyToCurrent && session.setLaunchProfileForCurrentSession
        ? await session.setLaunchProfileForCurrentSession(profileId)
        : { value: session.setLaunchProfile(profileId).id, appliedToActiveThread: false };
    runtime.updateSession(session);
    const info = runtime.publicInfo(session);
    runtime.appendActivity({
        source: "web",
        status: "info",
        type: result.appliedToActiveThread ? "launch_profile_applied" : "launch_profile_changed",
        threadId: info.threadId,
        workspace: info.workspace,
        agentId: info.agentId,
        actor,
        detail: info.launchProfileLabel ?? result.value ?? profileId,
    });
    return info;
}
export async function relayRuntimeHandback(runtime, actor) {
    const session = await runtime.getSession(true);
    runtime.ensureIdle(session);
    const result = session.handback();
    runtime.updateSession(session);
    const info = runtime.publicInfo(session);
    runtime.appendActivity({ source: "web", status: "info", type: "handback", threadId: result.threadId, workspace: result.workspace, agentId: info.agentId, actor, detail: result.command ?? "Thread handed back." });
    return result;
}
export async function relayRuntimeAbort(runtime, actor) {
    const session = await runtime.getSession(true);
    const snapshot = getExternalSnapshotForSession(session, runtime.config, { maxEvents: 0 });
    if (snapshot?.activity.active && !session.isProcessing()) {
        if (isExternalSnapshotSuppressedByManagedAbort(snapshot, runtime.activityStore.list({ threadId: snapshot.threadId, limit: 50 }))) {
            runtime.broadcast({ type: "status", level: "info", message: "Abort already sent.", at: new Date().toISOString() });
            runtime.scheduleActiveSessionsBroadcast();
            await runtime.drainQueue();
            return;
        }
        runtime.broadcast({
            type: "status",
            level: "warn",
            message: `Cannot abort the external ${snapshot.agentLabel} CLI task from NordRelay. Stop it in the terminal where it is running.`,
            at: new Date().toISOString(),
        });
        const info = runtime.publicInfo(session);
        runtime.appendActivity({ source: "web", status: "aborted", type: "prompt_abort_rejected", threadId: info.threadId, workspace: info.workspace, agentId: info.agentId, actor, detail: `External ${snapshot.agentLabel} CLI task is active.` });
        return;
    }
    await session.abort();
    const info = runtime.publicInfo(session);
    runtime.appendActivity({ source: "web", status: "aborted", type: "prompt_aborted", threadId: info.threadId, workspace: info.workspace, agentId: info.agentId, actor, detail: "Current operation aborted." });
    runtime.broadcast({ type: "status", level: "warn", message: "Current operation aborted.", at: new Date().toISOString() });
    runtime.scheduleActiveSessionsBroadcast();
    await runtime.drainQueue();
}
export async function relayRuntimeGetControlSession(runtime, agentId) {
    const active = await runtime.getSession(true);
    const activeInfo = runtime.publicInfo(active);
    if (!agentId || agentId === activeInfo.agentId) {
        return { session: active, dispose: false };
    }
    if (!enabledAgents(runtime.config).includes(agentId)) {
        throw new Error(`Agent is not enabled: ${agentId}`);
    }
    const session = await createAgentSessionService(runtime.config, agentId, {
        deferThreadStart: true,
        workspace: activeInfo.workspace,
    });
    return { session, dispose: true };
}
export function relayRuntimeCliPathOptions(runtime) {
    return {
        piCliPath: runtime.config.piCliPath,
        hermesCliPath: runtime.config.hermesCliPath,
        openClawCliPath: runtime.config.openClawCliPath,
        claudeCodeCliPath: runtime.config.claudeCodeCliPath,
    };
}
