/** * Wire protocol shared by the MCP server, the standalone bridge and the * Aseprite Lua extension. * * Design constraints that shape this file: * - Aseprite's Lua `WebSocket` is a *client only*; something else must listen. * That listener is the bridge (see docs/ARCHITECTURE.md). * - The MCP server process is owned by the agent host and may restart or be * duplicated, so the bridge must outlive it and accept N control clients. * - Everything on the wire is newline-free JSON, one frame per message. */ export declare const PROTOCOL_VERSION = 1; /** Default port the Aseprite extension dials. */ export declare const DEFAULT_PLUGIN_PORT = 9931; /** Default port MCP servers dial as control clients. Always plugin port + 1. */ export declare const DEFAULT_CONTROL_PORT: number; /** Separator used to namespace a request id per control client. */ export declare const ID_SEPARATOR = "::"; export interface CommandFrame { id: string; cmd: string; args?: Record; } export interface ResultFrame { id: string; ok: boolean; data?: unknown; error?: WireError; } export interface WireError { code: ErrorCode; message: string; /** Machine-readable remediation hint; never free-form prose the agent must parse. */ details?: Record; } export type ErrorCode = "not_connected" | "no_active_sprite" | "unsupported_command" | "invalid_args" | "aseprite_error" | "timeout" | "refused" | "too_large" | "headless_unavailable" | "headless_exited" | "file_open_in_editor"; /** Sent by the extension right after it connects, and on demand. */ export interface HelloFrame { type: "hello"; protocol: number; extensionVersion: string; asepriteVersion: string; features: string[]; } /** Broadcast by the bridge to every control client whenever liveness changes. */ export interface BridgeStateFrame { type: "bridge_state"; pluginConnected: boolean; hello: HelloFrame | null; bridgeVersion: string; since: number; } export type PluginFrame = ResultFrame | HelloFrame | { type: string; [k: string]: unknown; }; export declare function makeNamespacedId(clientId: number, originalId: string): string; export declare function splitNamespacedId(id: string): { clientId: number; originalId: string; } | null; export declare function isHello(frame: unknown): frame is HelloFrame; export declare function isBridgeState(frame: unknown): frame is BridgeStateFrame; export declare function isResult(frame: unknown): frame is ResultFrame; export declare class LiveError extends Error { readonly code: ErrorCode; readonly details: Record; constructor(code: ErrorCode, message: string, details?: Record); toWire(): WireError; } /** * The one error every mutating tool raises when Aseprite is not reachable. * * `doNotFallBackToDisk` is load-bearing: an agent that silently retargets a * failed live edit at the .aseprite file on disk produces changes the user * never sees in their open window, and that later get overwritten by a save. */ export declare function notConnected(detail?: string): LiveError; /** * Fields a caller declared it would read (`expect`) that the reply lacks. * `null` counts as present: several commands legitimately answer * `{sprite: null}` for "nothing is open". Absent is the failure — the field * the caller was told to expect never arrived. */ export declare function missingFields(value: unknown, expect: readonly string[]): string[];