/** * REST API server for the Milady Control UI. * * Exposes HTTP endpoints that the UI frontend expects, backed by the * elizaOS AgentRuntime. Default port: 2138. In dev mode, the Vite UI * dev server proxies /api and /ws here (see scripts/dev-ui.mjs). */ import http from "node:http"; import { type AgentRuntime, ChannelType, createMessageMemory, type Media, type UUID } from "@elizaos/core"; import { type MiladyConfig } from "../config/config"; import { AppManager } from "../services/app-manager"; import type { SandboxManager } from "../services/sandbox-manager"; import { type CloudRouteState } from "./cloud-routes"; import { ConnectorHealthMonitor } from "./connector-health"; import { DropService } from "./drop-service"; import { RegistryService } from "./registry-service"; import type { TrainingServiceWithRuntime } from "./training-service-like"; /** A connector-registered route handler. Returns `true` if the request was handled. */ type ConnectorRouteHandler = (req: http.IncomingMessage, res: http.ServerResponse, pathname: string, method: string) => Promise; /** Metadata for a web-chat conversation. */ export interface ConversationMeta { id: string; title: string; roomId: UUID; createdAt: string; updatedAt: string; } interface AgentStartupDiagnostics { phase: string; attempt: number; lastError?: string; lastErrorAt?: number; nextRetryAt?: number; } interface ServerState { runtime: AgentRuntime | null; config: MiladyConfig; agentState: "not_started" | "starting" | "running" | "paused" | "stopped" | "restarting" | "error"; agentName: string; model: string | undefined; startedAt: number | undefined; startup: AgentStartupDiagnostics; plugins: PluginEntry[]; skills: SkillEntry[]; logBuffer: LogEntry[]; eventBuffer: StreamEventEnvelope[]; nextEventId: number; chatRoomId: UUID | null; chatUserId: UUID | null; chatConnectionReady: { userId: UUID; roomId: UUID; worldId: UUID; } | null; chatConnectionPromise: Promise | null; adminEntityId: UUID | null; /** Conversation metadata by conversation id. */ conversations: Map; /** Pending restore of persisted conversations into the in-memory map. */ conversationRestorePromise: Promise | null; /** Tombstones for conversation IDs explicitly deleted by the user. */ deletedConversationIds: Set; /** Cloud manager for Eliza Cloud integration (null when cloud is disabled). */ cloudManager: CloudRouteState["cloudManager"]; sandboxManager: SandboxManager | null; /** App manager for launching and managing elizaOS apps. */ appManager: AppManager; /** Fine-tuning/training orchestration service. */ trainingService: TrainingServiceLike | null; /** ERC-8004 registry service (null when not configured). */ registryService: RegistryService | null; /** Drop/mint service (null when not configured). */ dropService: DropService | null; /** In-memory queue for share ingest items. */ shareIngestQueue: ShareIngestItem[]; /** Broadcast current agent status to all WebSocket clients. Set by startApiServer. */ broadcastStatus: (() => void) | null; /** Broadcast an arbitrary JSON message to all WebSocket clients. Set by startApiServer. */ broadcastWs: ((data: Record) => void) | null; /** Broadcast a JSON payload to WebSocket clients bound to a specific client id. */ broadcastWsToClientId: ((clientId: string, data: Record) => number) | null; /** Currently active conversation ID from the frontend (sent via WS). */ activeConversationId: string | null; /** Transient OAuth flow state for subscription auth. */ _anthropicFlow?: import("../auth/anthropic").AnthropicFlow; _codexFlow?: import("../auth/openai-codex").CodexFlow; _codexFlowTimer?: ReturnType; /** System permission states (cached from Electron IPC). */ permissionStates?: Record; /** Whether shell access is enabled (can be toggled in UI). */ shellEnabled?: boolean; /** Agent automation permission mode for self-directed config changes. */ agentAutomationMode?: AgentAutomationMode; /** Wallet trade execution permission mode (user-sign/manual/agent-auto). */ tradePermissionMode?: TradePermissionMode; /** Reasons a restart is pending. Empty array = no restart needed. */ pendingRestartReasons: string[]; /** Route handlers registered by connector plugins (loaded dynamically). */ connectorRouteHandlers: ConnectorRouteHandler[]; /** Connector health monitor for detecting dead connectors. */ connectorHealthMonitor: ConnectorHealthMonitor | null; /** Active WhatsApp pairing sessions (QR code flow). */ whatsappPairingSessions?: Map; /** Active Signal pairing sessions (device linking flow). */ signalPairingSessions?: Map; } interface ShareIngestItem { id: string; source: string; title?: string; url?: string; text?: string; suggestedPrompt: string; receivedAt: number; } interface PluginParamDef { key: string; type: string; description: string; required: boolean; sensitive: boolean; default?: string; /** Predefined options for dropdown selection (e.g. model names). */ options?: string[]; /** Current value from process.env (masked if sensitive). */ currentValue: string | null; /** Whether a value is currently set in the environment. */ isSet: boolean; } interface PluginEntry { id: string; name: string; description: string; tags: string[]; enabled: boolean; configured: boolean; envKey: string | null; category: "ai-provider" | "connector" | "streaming" | "database" | "app" | "feature"; /** Where the plugin comes from: "bundled" (ships with Milady) or "store" (user-installed from registry). */ source: "bundled" | "store"; configKeys: string[]; parameters: PluginParamDef[]; validationErrors: Array<{ field: string; message: string; }>; validationWarnings: Array<{ field: string; message: string; }>; npmName?: string; version?: string; pluginDeps?: string[]; /** Whether this plugin is currently active in the runtime. */ isActive?: boolean; /** Error message when plugin is enabled/installed but failed to load. */ loadError?: string; /** Server-provided UI hints for plugin configuration fields. */ configUiHints?: Record>; /** Optional icon URL or emoji for the plugin card header. */ icon?: string | null; homepage?: string; repository?: string; setupGuideUrl?: string; } interface SkillEntry { id: string; name: string; description: string; enabled: boolean; /** Set automatically when a scan report exists for this skill. */ scanStatus?: "clean" | "warning" | "critical" | "blocked" | null; } interface LogEntry { timestamp: number; level: string; message: string; source: string; tags: string[]; } type StreamEventType = "agent_event" | "heartbeat_event" | "training_event"; interface StreamEventEnvelope { type: StreamEventType; version: 1; eventId: string; ts: number; runId?: string; seq?: number; stream?: string; sessionKey?: string; agentId?: string; roomId?: UUID; payload: object; } export declare function findOwnPackageRoot(startDir: string): string; /** * Top-level config keys accepted by `PUT /api/config`. * Keep this in sync with MiladyConfig root fields and include both modern and * legacy aliases (e.g. `connectors` + `channels`). */ export declare const CONFIG_WRITE_ALLOWED_TOP_KEYS: Set; /** * Stream names accepted by `POST /api/agent/event`. * Plugins emit events to these streams for the StreamView UI. */ export declare const AGENT_EVENT_ALLOWED_STREAMS: Set; /** * Discover user-installed plugins from the Store (not bundled in the manifest). * Reads from config.plugins.installs and tries to enrich with package.json metadata. */ export declare function discoverInstalledPlugins(config: MiladyConfig, bundledIds: Set): PluginEntry[]; /** * Discover available plugins from the bundled plugins.json manifest. * Falls back to filesystem scanning for monorepo development. */ export declare function discoverPluginsFromManifest(): PluginEntry[]; export declare function resolvePluginSetupGuideUrl(id: string): string | undefined; export declare function normalizeRepositoryUrl(repository: string | { type?: string; url?: string; } | null | undefined): string | undefined; type StreamableServerResponse = Pick & { writableEnded?: boolean; destroyed?: boolean; }; export declare function fetchWithTimeoutGuard(input: string | URL, init: RequestInit, timeoutMs: number): Promise; /** * Stream a web Response body to an HTTP response while enforcing a strict byte cap. * Returns the number of bytes forwarded. */ export declare function streamResponseBodyWithByteLimit(upstream: Response, res: StreamableServerResponse, maxBytes: number, timeoutMs?: number): Promise; /** * Serve built dashboard assets from apps/app/dist with SPA fallback. * Returns true when the request is handled. */ export declare function injectApiBaseIntoHtml(html: Buffer, externalBase?: string | null): Buffer; interface ChatImageAttachment { /** Base64-encoded image data (no data URL prefix). */ data: string; mimeType: string; name: string; } /** Returns an error message string, or null if valid. Exported for unit tests. */ export declare function validateChatImages(images: unknown): string | null; /** * Extension of the core Media attachment shape that carries raw image bytes for * action handlers (e.g. POST_TWEET) while the message is in-memory. The * extra fields are intentionally stripped before the message is persisted. * * Note: `_data`/`_mimeType` survive only because elizaOS passes the * `userMessage` object reference directly to action handlers without * deep-cloning or serializing it. If that ever changes, action handlers * that read these fields will silently receive `undefined`. */ export interface ChatAttachmentWithData extends Media { /** Raw base64 image data — never written to the database. */ _data: string; /** MIME type corresponding to `_data`. */ _mimeType: string; } /** * Builds in-memory and compact (DB-persisted) attachment arrays from * validated images. Exported so it can be unit-tested independently. */ export declare function buildChatAttachments(images: ChatImageAttachment[] | undefined): { /** In-memory attachments that include `_data`/`_mimeType` for action handlers. */ attachments: ChatAttachmentWithData[] | undefined; /** Persistence-safe attachments with `_data`/`_mimeType` stripped. */ compactAttachments: Media[] | undefined; }; type MessageMemory = ReturnType; /** * Constructs the in-memory user message (with image data for action handlers) * and the persistence-safe counterpart (image data stripped). Extracted to * avoid duplicating this logic across the stream and non-stream chat endpoints. */ export declare function buildUserMessages(params: { images: ChatImageAttachment[] | undefined; prompt: string; userId: UUID; agentId: UUID; roomId: UUID; channelType: ChannelType; conversationMode?: "simple" | "power"; }): { userMessage: MessageMemory; messageToStore: MessageMemory; }; export declare function cloneWithoutBlockedObjectKeys(value: T): T; export declare function validateMcpServerConfig(config: Record): Promise; export declare function resolveMcpServersRejection(servers: Record): Promise; export type TradePermissionMode = "user-sign-only" | "manual-local-key" | "agent-auto"; /** * Resolve the active trade permission mode from config. * Falls back to "user-sign-only" when not configured. */ export declare function resolveTradePermissionMode(config: MiladyConfig): TradePermissionMode; /** * Returns true if local-key execution is permitted for the given actor. * @param mode The resolved trade permission mode. * @param isAgent True when the caller is the agent (autonomous), false for user-initiated flows. */ export declare function canUseLocalTradeExecution(mode: TradePermissionMode, isAgent: boolean): boolean; type AgentAutomationMode = "connectors-only" | "full"; type TrainingServiceLike = TrainingServiceWithRuntime; export declare function resolveMcpTerminalAuthorizationRejection(req: Pick, servers: Record, body: { terminalToken?: string; }): TerminalRunRejection | null; export declare function isAllowedHost(req: http.IncomingMessage): boolean; export declare function resolveCorsOrigin(origin?: string): string | null; export declare function extractAuthToken(req: http.IncomingMessage): string | null; export declare function normalizeWsClientId(value: unknown): string | null; export declare function resolveTerminalRunClientId(req: Pick, body: { clientId?: unknown; } | null | undefined): string | null; /** * Resolve Authorization for Hyperscape API relays. * * Security: never forward the incoming request Authorization header * (which typically carries MILADY_API_TOKEN for this API). Hyperscape relay * auth must come from the dedicated HYPERSCAPE_AUTH_TOKEN secret instead. */ export declare function resolveHyperscapeAuthorizationHeader(req: Pick): string | null; export declare function ensureApiTokenForBindHost(host: string): void; export declare function isAuthorized(req: http.IncomingMessage): boolean; export interface PluginConfigMutationRejection { field: string; message: string; } export declare function resolvePluginConfigMutationRejections(pluginParams: Array<{ key: string; }>, config: Record): PluginConfigMutationRejection[]; interface WalletExportRequestBody { confirm?: boolean; exportToken?: string; } export interface WalletExportRejection { status: 401 | 403; reason: string; } export declare function resolveWalletExportRejection(req: http.IncomingMessage, body: WalletExportRequestBody): WalletExportRejection | null; interface TerminalRunRequestBody { terminalToken?: string; } export interface TerminalRunRejection { status: 401 | 403; reason: string; } export declare function resolveTerminalRunRejection(req: http.IncomingMessage, body: TerminalRunRequestBody): TerminalRunRejection | null; export interface WebSocketUpgradeRejection { status: 401 | 403 | 404; reason: string; } export declare function resolveWebSocketUpgradeRejection(req: http.IncomingMessage, wsUrl: URL): WebSocketUpgradeRejection | null; export declare function isSafeResetStateDir(resolvedState: string, homeDir: string): boolean; type ConversationRoomTitleRef = Pick; export declare function persistConversationRoomTitle(runtime: Pick | null | undefined, conversation: ConversationRoomTitleRef): Promise; /** * Route non-conversation text output to the user's active conversation. * Stores the message as a Memory in the conversation room and broadcasts * a `proactive-message` WS event to the frontend. */ export declare function routeAutonomyTextToUser(state: ServerState, responseText: string, source?: string): Promise; /** * Handle swarm completion by synthesizing a summary via the LLM. * Extracted from wireCodingAgentSwarmSynthesis for testability. * * Paths: (A) LLM returns synthesis → route to user, * (B) LLM returns empty → warn, * (C) LLM throws → fallback generic message. */ export declare function handleSwarmSynthesis(st: { runtime: AgentRuntime | null; }, payload: { tasks: Array<{ sessionId: string; label: string; agentType: string; originalTask: string; status: string; completionSummary: string; }>; total: number; completed: number; stopped: number; errored: number; }, routeMessage?: (text: string, source: string) => Promise): Promise; import { type captureEarlyLogs } from "./early-logs"; export type { captureEarlyLogs }; export declare function startApiServer(opts?: { port?: number; runtime?: AgentRuntime; /** Initial state when starting without a runtime (e.g. embedded bootstrapping). */ initialAgentState?: "not_started" | "starting" | "stopped" | "error"; /** * Called when the UI requests a restart via `POST /api/agent/restart`. * Should stop the current runtime, create a new one, and return it. * If omitted the endpoint returns 501 (not supported in this mode). */ onRestart?: () => Promise; }): Promise<{ port: number; close: () => Promise; updateRuntime: (rt: AgentRuntime) => void; updateStartup: (update: Partial & { phase?: string; attempt?: number; state?: ServerState["agentState"]; }) => void; }>; //# sourceMappingURL=server.d.ts.map