/** * GodotBridge - WebSocket server for communication with Godot. * * Two kinds of connections are supported: * - editor : the addon running inside the Godot editor process * - runtime : an MCPRuntime autoload running inside the user's launched game * * The first hello message (godot_ready) carries a `role` field. We accept * exactly one of each role at a time. Tool calls are routed by name: * - RUNTIME_ONLY_TOOLS → runtime connection * - list_signal_connections w/ source=runtime → runtime connection * - everything else → editor connection */ /** Close code for "you are the wrong project". Distinct from 4000/4001 (slot * already taken) so the addon can tell a collision from a misconfiguration. */ export declare const CLOSE_WRONG_PROJECT = 4002; /** Close code for "your godot_ready did not carry the expected secret". */ export declare const CLOSE_BAD_SECRET = 4003; /** Receives editor-activity events pushed by the addon. */ export type EditorActivityCallback = (event: Record) => void; /** * Reject any WebSocket handshake that carries an `Origin` header. * * The bridge binds to 127.0.0.1, which stops remote hosts but NOT the browser * on this machine: WebSocket connections are not subject to the same-origin * policy, so any page the developer happens to visit while the editor is open * can open ws://127.0.0.1:6505 and start issuing tool calls — which include * writing files into the project and `game_eval`. That is arbitrary code * execution reachable from a web page. * * Browsers always send `Origin` on a WebSocket handshake and cannot be told not * to. Godot's WebSocketPeer does not send one. So refusing any request that has * the header closes the browser vector completely, costs nothing, and needs no * configuration. A non-browser local process can still forge the absence of the * header — that is what the optional shared secret below is for. */ export declare function isBrowserOrigin(headers: Record): boolean; /** Constant-time compare of two secrets, safe on length mismatch. */ export declare function secretsMatch(expected: string, received: string): boolean; /** Preserve POSIX case while accepting Windows drive/UNC path spelling. */ export declare function normalizeProjectPath(p: string): string; export declare const RUNTIME_ONLY_TOOLS: Set; interface GodotInfo { projectPath?: string; connectedAt: Date; role: 'editor' | 'runtime'; /** plugin.cfg version of the addon that connected, when it reports one. */ addonVersion?: string; } type ConnectionCallback = (connected: boolean, info?: GodotInfo) => void; type RuntimeStatusCallback = (connected: boolean) => void; export declare class GodotBridge { private wss; private _listening; private editor; private runtime; private pendingRequests; private authenticatedSockets; private pingInterval; /** Injectable so the watchdog's behaviour can be tested in milliseconds. */ private pingIntervalMs; private connectionCallbacks; private runtimeStatusCallbacks; private port; private timeout; private expectedProjectPath; private expectedSecret; private editorActivityCallbacks; constructor(port?: number, timeout?: number, expectedProjectPath?: string | null, expectedSecret?: string | null, pingIntervalMs?: number); start(): Promise; stop(): void; /** * Decide whether a tool invocation should be sent to the runtime connection. * For list_signal_connections we look at args.source to allow either editor * (scene_file) or runtime routing. */ routeIsRuntime(toolName: string, args: Record | undefined): boolean; private handleConnection; /** Is a tool call still in flight on this slot? */ private hasPending; private startPingLoop; /** * Tell the caller when a tool ran long enough to be worth knowing about. * * An editor-side tool holds Godot's main thread for its whole duration: the * UI is frozen for the developer sitting in front of it, and until the ping * watchdog was taught to wait for in-flight calls, a slow one got its own * socket terminated. Both failures were invisible from the answer, which is * how get_project_statistics spent 120s per call without anyone noticing it * was the reason the editor kept "disconnecting". A number in the payload is * what makes the next one obvious on the first call instead of the tenth. */ private withSlowCallNote; private failPending; private handleMessage; private handleToolResult; /** * Compile a game_eval snippet in the EDITOR before letting the game near it. * * game_eval is the escape hatch, so agents get its snippets wrong routinely. * A snippet that does not parse is reported through the engine's global error * handler, and in a game launched from the editor that handler makes the * debugger break: the game PAUSES mid-call (it does not die — measured), the * helper stops answering, and the typo surfaces ~25s later as "Runtime helper * is not connected". Nothing is debugging the editor, so compiling there first * turns that back into an immediate, accurate error. * * Best-effort by design. No editor connected (a game launched straight from * the CLI) means no debugger either, which is the case that never broke — and * an addon too old to have the handler must not make game_eval unusable. */ private precheckEvalSnippet; invokeTool(toolName: string, args: Record): Promise; /** Notify the editor of the current AI client count. */ sendClientStatus(count: number): void; /** Push the current runtime helper status to the editor connection. */ private sendRuntimeStatusToEditor; private sendTo; isListening(): boolean; isConnected(): boolean; isRuntimeConnected(): boolean; getStatus(): { connected: boolean; runtimeConnected: boolean; projectPath?: string; connectedAt?: Date; addonVersion?: string; pendingRequests: number; port: number; }; onConnectionChange(callback: ConnectionCallback): void; offConnectionChange(callback: ConnectionCallback): void; onRuntimeStatusChange(callback: RuntimeStatusCallback): void; /** Called for each editor-activity event the addon pushes. */ onEditorActivity(callback: EditorActivityCallback): void; offEditorActivity(callback: EditorActivityCallback): void; private notifyEditorActivity; private notifyConnectionChange; private notifyRuntimeStatus; private log; } export declare function getDefaultBridge(): GodotBridge; /** * Attach the elapsed time to a slow tool's own answer. Below the threshold the * result is handed back untouched, and a non-object result (a bare string, an * array) is never rewrapped — an agent parsing it should not have to care that * the call was slow. */ export declare function slowCallNote(result: unknown, toolName: string, elapsedMs: number, thresholdMs: number): unknown; export declare function createBridge(port?: number, timeout?: number, expectedProjectPath?: string | null, expectedSecret?: string | null, pingIntervalMs?: number): GodotBridge; export {}; //# sourceMappingURL=godot-bridge.d.ts.map