dominus-cli 2.4.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.4.1 (2026-07-22)
4
+
5
+ ### Bridge recovery and live connection state
6
+
7
+ - Controller clients now reconnect automatically with bounded exponential backoff
8
+ after the background bridge daemon restarts, while explicit shutdown still
9
+ cancels pending reconnects and requests.
10
+ - Connection tools and the `dominus://connections` resource now refresh their
11
+ target state from the daemon before reporting sessions, preventing stale or
12
+ missing Studio targets after a reconnect.
13
+ - Added regression coverage for daemon restart recovery and refreshed target
14
+ discovery.
15
+
3
16
  ## 2.4.0 (2026-07-15)
4
17
 
5
18
  ### Native instance metadata and method capabilities
package/README.md CHANGED
@@ -68,7 +68,7 @@ For a version-pinned configuration that does not require a global install:
68
68
  "mcpServers": {
69
69
  "dominus": {
70
70
  "command": "npx",
71
- "args": ["-y", "-p", "dominus-cli@2.4.0", "dominus-mcp"]
71
+ "args": ["-y", "-p", "dominus-cli@2.4.1", "dominus-mcp"]
72
72
  }
73
73
  }
74
74
  }
@@ -63,6 +63,7 @@ var ControllerMsg = {
63
63
  WELCOME: "controller:welcome",
64
64
  RELAY: "controller:relay",
65
65
  TARGET_STATE: "controller:target_state",
66
+ GET_TARGET_STATE: "controller:get_target_state",
66
67
  SET_ACTIVE_CONNECTION: "controller:set_active_connection"
67
68
  };
68
69
  function createMessage(type, payload) {
@@ -317,6 +318,14 @@ var DominusServer = class extends EventEmitter {
317
318
  this.sendSafe(target.ws, request);
318
319
  return;
319
320
  }
321
+ if (msg.type === ControllerMsg.GET_TARGET_STATE) {
322
+ this.sendResponse(controllerWs, msg.id, {
323
+ success: true,
324
+ connections: this.listConnections(),
325
+ defaultActiveConnectionId: this.activeConnectionId
326
+ });
327
+ return;
328
+ }
320
329
  if (msg.type === ControllerMsg.SET_ACTIVE_CONNECTION) {
321
330
  const payload = msg.payload;
322
331
  const connectionId = typeof payload.connectionId === "string" ? payload.connectionId : null;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config/config.ts","../src/transport/protocol.ts","../src/transport/auth.ts","../src/transport/ws-server.ts","../src/transport/bridge-daemon.ts"],"names":["path","fs"],"mappings":"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","file":"bridge-daemon.js","sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport type { ConfigAccess } from '../tools/types.js';\n\nconst DOMINUS_DIR = path.join(os.homedir(), '.dominus');\nconst CONFIG_PATH = path.join(DOMINUS_DIR, 'config.json');\nconst DB_PATH = path.join(DOMINUS_DIR, 'dominus.db');\nconst RULES_PATH = path.join(DOMINUS_DIR, 'rules.json');\n\nexport type AIProviderType =\n | 'openai'\n | 'anthropic'\n | 'gemini'\n | 'xai'\n | 'openrouter'\n | 'custom';\n\nexport interface ProviderConfig {\n name: string;\n baseUrl: string;\n envVar: string;\n defaultModel: string;\n}\n\nexport const PROVIDERS: Record<AIProviderType, ProviderConfig> = {\n openai: {\n name: 'OpenAI',\n baseUrl: 'https://api.openai.com/v1',\n envVar: 'OPENAI_API_KEY',\n defaultModel: 'gpt-5.3-codex',\n },\n anthropic: {\n name: 'Anthropic',\n baseUrl: 'https://api.anthropic.com/v1/',\n envVar: 'ANTHROPIC_API_KEY',\n defaultModel: 'claude-sonnet-4-6',\n },\n gemini: {\n name: 'Google Gemini',\n baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai',\n envVar: 'GEMINI_API_KEY',\n defaultModel: 'gemini-3.1-pro',\n },\n xai: {\n name: 'xAI (Grok)',\n baseUrl: 'https://api.x.ai/v1',\n envVar: 'XAI_API_KEY',\n defaultModel: 'grok-4.2',\n },\n openrouter: {\n name: 'OpenRouter',\n baseUrl: 'https://openrouter.ai/api/v1',\n envVar: 'OPENROUTER_API_KEY',\n defaultModel: 'anthropic/claude-sonnet-4-6',\n },\n custom: {\n name: 'Custom',\n baseUrl: '',\n envVar: '',\n defaultModel: '',\n },\n};\n\nexport interface DominusConfig {\n provider: AIProviderType;\n apiKey?: string;\n apiBaseUrl: string;\n model: string;\n port: number;\n maxToolIterations: number;\n maxVerifyRetries: number;\n tokenBudget: number;\n autoIndex: boolean;\n autoLearn: boolean;\n userName: string;\n openCloudApiKey?: string;\n}\n\nconst DEFAULTS: DominusConfig = {\n provider: 'openai',\n apiBaseUrl: '',\n model: '',\n port: 18088,\n maxToolIterations: 25,\n maxVerifyRetries: 3,\n tokenBudget: 128000,\n autoIndex: true,\n autoLearn: true,\n userName: 'Developer',\n};\n\n/**\n * Auto-detect provider from environment variables.\n * Returns the first provider whose env var is set, or null.\n */\nexport function detectProviderFromEnv(): { provider: AIProviderType; apiKey: string } | null {\n const priority: AIProviderType[] = ['openai', 'anthropic', 'gemini', 'xai', 'openrouter'];\n for (const p of priority) {\n const key = process.env[PROVIDERS[p].envVar];\n if (key) return { provider: p, apiKey: key };\n }\n return null;\n}\n\n/**\n * Resolve the effective base URL and model for a provider config.\n */\nexport function resolveProviderSettings(config: DominusConfig): {\n baseUrl: string;\n model: string;\n} {\n const providerInfo = PROVIDERS[config.provider];\n return {\n baseUrl: config.apiBaseUrl || providerInfo?.baseUrl || PROVIDERS.openrouter.baseUrl,\n model: config.model || providerInfo?.defaultModel || 'gpt-4o',\n };\n}\n\nexport interface DominusRules {\n do: string[];\n dont: string[];\n conventions: string[];\n context: string;\n}\n\nconst DEFAULT_RULES: DominusRules = {\n do: [],\n dont: [],\n conventions: [],\n context: '',\n};\n\nfunction ensureDir(): void {\n if (!fs.existsSync(DOMINUS_DIR)) {\n fs.mkdirSync(DOMINUS_DIR, { recursive: true });\n }\n}\n\nfunction readJson<T>(filePath: string, defaults: T): T {\n try {\n if (fs.existsSync(filePath)) {\n const raw = fs.readFileSync(filePath, 'utf-8');\n return { ...defaults, ...JSON.parse(raw) };\n }\n } catch {\n // Corrupted file; reset to defaults\n }\n return { ...defaults };\n}\n\nfunction writeJson(filePath: string, data: unknown): void {\n ensureDir();\n fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');\n}\n\nexport function loadConfig(): DominusConfig {\n ensureDir();\n return readJson(CONFIG_PATH, DEFAULTS);\n}\n\nexport function saveConfig(config: DominusConfig): void {\n writeJson(CONFIG_PATH, config);\n}\n\nexport function getConfigValue<K extends keyof DominusConfig>(key: K): DominusConfig[K] {\n const config = loadConfig();\n return config[key];\n}\n\nexport function setConfigValue<K extends keyof DominusConfig>(\n key: K,\n value: DominusConfig[K],\n): void {\n const config = loadConfig();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (config as any)[key] = value;\n saveConfig(config);\n}\n\nexport function loadRules(): DominusRules {\n ensureDir();\n return readJson(RULES_PATH, DEFAULT_RULES);\n}\n\nexport function saveRules(rules: DominusRules): void {\n writeJson(RULES_PATH, rules);\n}\n\nexport function getDominusDir(): string {\n ensureDir();\n return DOMINUS_DIR;\n}\n\nexport function getDbPath(): string {\n ensureDir();\n return DB_PATH;\n}\n\nexport function createConfigAccess(): ConfigAccess {\n return {\n get<T>(key: string): T | undefined {\n const config = loadConfig() as unknown as Record<string, unknown>;\n return config[key] as T | undefined;\n },\n set(key: string, value: unknown): void {\n const config = loadConfig() as unknown as Record<string, unknown>;\n config[key] = value;\n saveConfig(config as unknown as DominusConfig);\n },\n getAll(): Record<string, unknown> {\n return loadConfig() as unknown as Record<string, unknown>;\n },\n };\n}\n","import { nanoid } from 'nanoid';\nimport { z } from 'zod';\n\nexport const DOMINUS_PROTOCOL_VERSION = 2;\nexport const MAX_MESSAGE_BYTES = 1024 * 1024;\n\nexport interface WsMessage<T = unknown> {\n id: string;\n type: string;\n payload: T;\n ts: number;\n}\n\nexport const StudioMsg = {\n HELLO: 'studio:hello',\n CONNECTED: 'studio:connected',\n DISCONNECTED: 'studio:disconnected',\n EXPLORER: 'studio:explorer',\n OUTPUT: 'studio:output',\n ERROR: 'studio:error',\n SELECTION: 'studio:selection',\n SCRIPT_CONTENT: 'studio:script:content',\n SCRIPT_CHANGED: 'studio:script:changed',\n RESPONSE: 'studio:response',\n REFLECTION: 'studio:reflection',\n TEST_RESULTS: 'studio:test:results',\n AUTHENTICATED: 'server:authenticated',\n AUTH_REJECTED: 'server:auth_rejected',\n} as const;\n\nexport const ControllerMsg = {\n HELLO: 'controller:hello',\n WELCOME: 'controller:welcome',\n RELAY: 'controller:relay',\n TARGET_STATE: 'controller:target_state',\n SET_ACTIVE_CONNECTION: 'controller:set_active_connection',\n} as const;\n\nexport const CliMsg = {\n GET_EXPLORER: 'cli:get:explorer',\n GET_SCRIPT: 'cli:get:script',\n SET_SCRIPT: 'cli:set:script',\n RUN_CODE: 'cli:run',\n GET_PROPERTIES: 'cli:get:properties',\n GET_DESCENDANTS_PROPERTIES: 'cli:get:descendants:properties',\n SET_PROPERTIES: 'cli:set:properties',\n INSERT_INSTANCE: 'cli:insert',\n DELETE_INSTANCE: 'cli:delete',\n GET_SELECTION: 'cli:get:selection',\n SELECT: 'cli:select',\n SEARCH_SCRIPTS: 'cli:search:scripts',\n GET_OUTPUT: 'cli:get:output',\n RUN_TESTS: 'cli:run:tests',\n EXECUTE_RUN_TEST: 'cli:test:run',\n EXECUTE_PLAY_TEST: 'cli:test:play',\n END_TEST: 'cli:test:end',\n BUILD_UI: 'cli:build:ui',\n GET_REFLECTION: 'cli:get:reflection',\n UPLOAD_ASSET: 'cli:upload:asset',\n SERIALIZE_UI: 'cli:serialize:ui',\n MOVE_INSTANCE: 'cli:move:instance',\n UNDO: 'cli:undo',\n REDO: 'cli:redo',\n BULK_SET_PROPERTIES: 'cli:bulk:set:properties',\n FIND_REPLACE_SCRIPTS: 'cli:find:replace:scripts',\n V2_GET_TREE: 'studio:v2:get_tree',\n V2_INSPECT: 'studio:v2:inspect',\n V2_GET_SELECTION: 'studio:v2:get_selection',\n V2_READ_SCRIPT: 'studio:v2:read_script',\n V2_UPDATE_SCRIPT: 'studio:v2:update_script',\n V2_APPLY: 'studio:v2:apply',\n V2_CREATE_PARTS: 'studio:v2:create_parts',\n V2_CLONE_INSTANCES: 'studio:v2:clone_instances',\n V2_GROUP_INSTANCES: 'studio:v2:group_instances',\n V2_UNGROUP_INSTANCES: 'studio:v2:ungroup_instances',\n V2_TRANSFORM_INSTANCES: 'studio:v2:transform_instances',\n V2_CREATE_WELDS: 'studio:v2:create_welds',\n V2_QUERY_PARTS: 'studio:v2:query_parts',\n V2_SET_SELECTION: 'studio:v2:set_selection',\n V2_GET_METADATA: 'studio:v2:get_metadata',\n V2_SET_ATTRIBUTES: 'studio:v2:set_attributes',\n V2_UPDATE_TAGS: 'studio:v2:update_tags',\n V2_LIST_CALLABLE_METHODS: 'studio:v2:list_callable_methods',\n V2_INVOKE_METHODS: 'studio:v2:invoke_methods',\n V2_DELETE: 'studio:v2:delete',\n V2_BUILD_UI: 'studio:v2:build_ui',\n V2_SNAPSHOT_UI: 'studio:v2:snapshot_ui',\n V2_RUN_TEST: 'studio:v2:run_test',\n V2_GET_OUTPUT: 'studio:v2:get_output',\n V2_GET_REFLECTION: 'studio:v2:get_reflection',\n} as const;\n\nexport type StudioMsgType = (typeof StudioMsg)[keyof typeof StudioMsg];\nexport type CliMsgType = (typeof CliMsg)[keyof typeof CliMsg];\n\nexport interface ExplorerNode {\n name: string;\n className: string;\n path: string;\n ref?: InstanceRef;\n children?: ExplorerNode[];\n}\n\nexport interface InstanceRef {\n instanceId?: string;\n pathSegments?: string[];\n}\n\nexport interface StudioConnectedPayload {\n studioVersion: string;\n protocolVersion?: number;\n clientId?: string;\n token?: string;\n connectionId?: string;\n placeId?: number;\n placeName?: string;\n engineVersion?: string;\n}\n\nexport interface ScriptContentPayload {\n path: string;\n className: 'Script' | 'LocalScript' | 'ModuleScript';\n source: string;\n lineCount: number;\n}\n\nexport interface ScriptChangedPayload {\n path: string;\n source: string;\n}\n\nexport interface OutputPayload {\n message: string;\n level: 'info' | 'warn' | 'error';\n timestamp: number;\n source?: string;\n}\n\nexport interface SelectionPayload {\n paths: string[];\n classNames: string[];\n}\n\nexport interface PropertyEntry {\n name: string;\n value: string;\n type: string;\n category?: string;\n}\n\nexport interface TestResultPayload {\n name: string;\n passed: boolean;\n duration: number;\n error?: string;\n}\n\nexport interface StudioTestPayload {\n args?: unknown;\n}\n\nexport interface StudioTestResultPayload {\n success: boolean;\n result?: unknown;\n error?: string;\n duration?: number;\n}\n\nexport interface ReflectionPayload {\n className: string;\n properties: { name: string; type: string; category: string }[];\n methods: { name: string; parameters: string[] }[];\n events: { name: string; parameters: string[] }[];\n}\n\nexport interface RunCodePayload {\n code: string;\n}\n\nexport interface RunCodeResponsePayload {\n output: string;\n error?: string;\n duration: number;\n}\n\nexport interface SearchScriptsPayload {\n query: string;\n maxResults?: number;\n}\n\nexport interface SearchResultPayload {\n results: {\n path: string;\n className: string;\n matches: { line: number; text: string }[];\n }[];\n}\n\nexport interface InsertInstancePayload {\n className: string;\n parentPath: string;\n name: string;\n properties?: Record<string, unknown>;\n}\n\nexport interface SetPropertiesPayload {\n path: string;\n properties: Record<string, unknown>;\n}\n\nexport function createMessage<T>(type: string, payload: T): WsMessage<T> {\n return { id: nanoid(), type, payload, ts: Date.now() };\n}\n\nexport function createRequest<T>(type: string, payload: T): WsMessage<T> {\n return createMessage(type, payload);\n}\n\nexport function parseMessage(raw: string): WsMessage {\n if (Buffer.byteLength(raw, 'utf8') > MAX_MESSAGE_BYTES) {\n throw new Error(`WebSocket message exceeds ${MAX_MESSAGE_BYTES} bytes`);\n }\n try {\n const parsed = JSON.parse(raw) as unknown;\n return wsMessageSchema.parse(parsed) as WsMessage;\n } catch {\n throw new Error(`Failed to parse WebSocket message: ${raw.slice(0, 100)}`);\n }\n}\n\nexport function serializeMessage(msg: WsMessage): string {\n const serialized = JSON.stringify(msg);\n if (Buffer.byteLength(serialized, 'utf8') > MAX_MESSAGE_BYTES) {\n throw new Error(`WebSocket message exceeds ${MAX_MESSAGE_BYTES} bytes`);\n }\n return serialized;\n}\n\nconst wsMessageSchema = z\n .object({\n id: z.string().min(1).max(128),\n type: z.string().min(1).max(128),\n payload: z.unknown(),\n ts: z.number().finite(),\n })\n .strict();\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport * as crypto from 'node:crypto';\nimport { getDominusDir } from '../config/config.js';\n\nconst TOKEN_FILE = 'bridge-token';\n\nexport function loadOrCreateBridgeToken(): string {\n const tokenPath = path.join(getDominusDir(), TOKEN_FILE);\n try {\n const existing = fs.readFileSync(tokenPath, 'utf8').trim();\n if (existing.length >= 32) return existing;\n } catch {\n // First run or an unreadable token is repaired below.\n }\n\n const token = crypto.randomBytes(32).toString('base64url');\n fs.writeFileSync(tokenPath, `${token}\\n`, { encoding: 'utf8', mode: 0o600 });\n try {\n fs.chmodSync(tokenPath, 0o600);\n } catch {\n // Windows ACLs do not map directly to POSIX modes.\n }\n return token;\n}\n\nexport function tokensEqual(actual: unknown, expected: string): boolean {\n if (typeof actual !== 'string') return false;\n const actualBuffer = Buffer.from(actual);\n const expectedBuffer = Buffer.from(expected);\n return (\n actualBuffer.length === expectedBuffer.length &&\n crypto.timingSafeEqual(actualBuffer, expectedBuffer)\n );\n}\n","import { randomUUID } from 'node:crypto';\nimport { EventEmitter } from 'node:events';\nimport { WebSocketServer, WebSocket, type RawData } from 'ws';\nimport {\n ControllerMsg,\n DOMINUS_PROTOCOL_VERSION,\n MAX_MESSAGE_BYTES,\n StudioMsg,\n createMessage,\n parseMessage,\n serializeMessage,\n type StudioConnectedPayload,\n type WsMessage,\n} from './protocol.js';\nimport { loadOrCreateBridgeToken, tokensEqual } from './auth.js';\n\nconst HANDSHAKE_TIMEOUT_MS = 8_000;\nconst RATE_WINDOW_MS = 10_000;\nconst RATE_LIMIT = 250;\n\ntype SocketRole = 'unknown' | 'studio' | 'controller';\n\nexport interface StudioConnection {\n ws: WebSocket;\n connectionId: string;\n clientId: string;\n studioVersion?: string;\n placeId?: number;\n placeName?: string;\n connectedAt: number;\n}\n\nexport interface PlaceInfo {\n placeId: number;\n connectionId: string;\n clientId: string;\n placeName?: string;\n studioVersion?: string;\n connectedAt: number;\n active: boolean;\n}\n\nexport interface StudioBridge extends EventEmitter {\n sendRequest<T = unknown>(\n type: string,\n payload: unknown,\n timeoutMs?: number,\n ): Promise<WsMessage<T>>;\n sendNotification(type: string, payload: unknown): void;\n isConnected(): boolean;\n getConnectionInfo(): StudioConnection | null;\n getPort(): number;\n stop(): Promise<void>;\n listConnections(): PlaceInfo[];\n getActiveConnectionId(): string | null;\n setActiveConnectionId(connectionId: string | null): Promise<void>;\n getActivePlaceId(): number;\n setActivePlaceId(placeId: number): void;\n}\n\ninterface RelayRequest {\n controllerWs: WebSocket;\n targetWs: WebSocket;\n}\n\ninterface ServerOptions {\n token?: string;\n host?: string;\n maxPayloadBytes?: number;\n handshakeTimeoutMs?: number;\n}\n\nexport class DominusServer extends EventEmitter implements StudioBridge {\n private wss: WebSocketServer | null = null;\n private readonly studioClients = new Map<string, StudioConnection>();\n private readonly wsToConnectionId = new Map<WebSocket, string>();\n private readonly controllers = new Set<WebSocket>();\n private readonly socketRoles = new Map<WebSocket, SocketRole>();\n private readonly pendingRequests = new Map<\n string,\n {\n resolve: (msg: WsMessage) => void;\n reject: (err: Error) => void;\n timer: ReturnType<typeof setTimeout>;\n targetWs: WebSocket;\n }\n >();\n private readonly relayRequests = new Map<string, RelayRequest>();\n private readonly rateState = new Map<WebSocket, { startedAt: number; count: number }>();\n private readonly token: string;\n private readonly host: string;\n private readonly maxPayloadBytes: number;\n private readonly handshakeTimeoutMs: number;\n private activeConnectionId: string | null = null;\n private port: number;\n\n constructor(port = 18088, options: ServerOptions = {}) {\n super();\n this.port = port;\n this.token = options.token ?? loadOrCreateBridgeToken();\n this.host = options.host ?? '127.0.0.1';\n this.maxPayloadBytes = options.maxPayloadBytes ?? MAX_MESSAGE_BYTES;\n this.handshakeTimeoutMs = options.handshakeTimeoutMs ?? HANDSHAKE_TIMEOUT_MS;\n }\n\n start(maxRetries = 0): Promise<void> {\n return new Promise((resolve, reject) => {\n const tryPort = (port: number, attempt: number) => {\n const wss = new WebSocketServer({\n port,\n host: this.host,\n maxPayload: this.maxPayloadBytes,\n perMessageDeflate: false,\n });\n\n wss.on('listening', () => {\n this.wss = wss;\n this.port = port;\n wss.on('connection', (ws) => this.handleConnection(ws));\n this.emit('server:started', { port, host: this.host });\n resolve();\n });\n\n wss.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE' && attempt < maxRetries) {\n wss.close();\n tryPort(port + 1, attempt + 1);\n return;\n }\n this.emit('server:error', err);\n reject(err);\n });\n };\n\n tryPort(this.port, 0);\n });\n }\n\n private handleConnection(ws: WebSocket): void {\n this.socketRoles.set(ws, 'unknown');\n this.rateState.set(ws, { startedAt: Date.now(), count: 0 });\n\n const handshakeTimer = setTimeout(() => {\n if (this.socketRoles.get(ws) === 'unknown') {\n ws.close(1008, 'Dominus handshake timed out');\n }\n }, this.handshakeTimeoutMs);\n\n ws.on('message', (raw) => {\n try {\n if (!this.consumeRateBudget(ws) || rawByteLength(raw) > this.maxPayloadBytes) {\n ws.close(1009, 'Dominus message limit exceeded');\n return;\n }\n const msg = parseMessage(raw.toString());\n const role = this.socketRoles.get(ws) ?? 'unknown';\n\n if (role === 'unknown') {\n clearTimeout(handshakeTimer);\n this.handleHandshake(ws, msg);\n } else if (role === 'controller') {\n this.handleControllerMessage(ws, msg);\n } else if (role === 'studio') {\n this.handleStudioMessage(ws, msg);\n }\n } catch (err) {\n this.emit('server:error', err);\n this.sendSafe(\n ws,\n createMessage(StudioMsg.AUTH_REJECTED, {\n error: err instanceof Error ? err.message : String(err),\n }),\n );\n }\n });\n\n ws.on('close', () => {\n clearTimeout(handshakeTimer);\n this.cleanupSocket(ws);\n });\n\n ws.on('error', (err) => this.emit('server:error', err));\n }\n\n private consumeRateBudget(ws: WebSocket): boolean {\n const now = Date.now();\n const state = this.rateState.get(ws) ?? { startedAt: now, count: 0 };\n if (now - state.startedAt >= RATE_WINDOW_MS) {\n state.startedAt = now;\n state.count = 0;\n }\n state.count += 1;\n this.rateState.set(ws, state);\n return state.count <= RATE_LIMIT;\n }\n\n private handleHandshake(ws: WebSocket, msg: WsMessage): void {\n if (msg.type === StudioMsg.HELLO) {\n const hello = validateStudioHello(msg.payload);\n if (tokensEqual(hello.token, this.token)) {\n this.authorizeStudio(ws, hello, randomUUID());\n } else {\n this.sendSafe(\n ws,\n createMessage(StudioMsg.AUTH_REJECTED, {\n error: 'Studio credentials are missing or stale. Run dominus-install-plugin.',\n }),\n );\n ws.close(1008, 'Unauthorized Studio plugin');\n }\n return;\n }\n\n if (msg.type === ControllerMsg.HELLO) {\n const payload = msg.payload as { token?: unknown; protocolVersion?: unknown };\n if (\n payload.protocolVersion !== DOMINUS_PROTOCOL_VERSION ||\n !tokensEqual(payload.token, this.token)\n ) {\n this.sendSafe(\n ws,\n createMessage(StudioMsg.AUTH_REJECTED, {\n error: 'Invalid Dominus controller credentials',\n }),\n );\n ws.close(1008, 'Unauthorized controller');\n return;\n }\n this.socketRoles.set(ws, 'controller');\n this.controllers.add(ws);\n this.sendSafe(\n ws,\n createMessage(ControllerMsg.WELCOME, {\n protocolVersion: DOMINUS_PROTOCOL_VERSION,\n port: this.port,\n connections: this.listConnections(),\n activeConnectionId: this.activeConnectionId,\n }),\n );\n return;\n }\n\n this.sendSafe(\n ws,\n createMessage(StudioMsg.AUTH_REJECTED, {\n error: `Expected ${StudioMsg.HELLO} or ${ControllerMsg.HELLO}`,\n }),\n );\n ws.close(1008, 'Invalid Dominus handshake');\n }\n\n private authorizeStudio(\n ws: WebSocket,\n hello: StudioConnectedPayload,\n connectionId: string,\n ): StudioConnection {\n const connection: StudioConnection = {\n ws,\n connectionId,\n clientId: hello.clientId!,\n connectedAt: Date.now(),\n studioVersion: hello.studioVersion,\n placeId: hello.placeId ?? 0,\n placeName: hello.placeName,\n };\n this.studioClients.set(connectionId, connection);\n this.wsToConnectionId.set(ws, connectionId);\n this.socketRoles.set(ws, 'studio');\n\n if (!this.activeConnectionId || !this.studioClients.has(this.activeConnectionId)) {\n this.activeConnectionId = connectionId;\n }\n\n this.sendSafe(\n ws,\n createMessage(StudioMsg.AUTHENTICATED, {\n protocolVersion: DOMINUS_PROTOCOL_VERSION,\n connectionId,\n token: this.token,\n }),\n );\n const info = this.toPlaceInfo(connection);\n this.emit('studio:connected', info);\n this.broadcastToControllers(StudioMsg.CONNECTED, info);\n this.broadcastTargetState();\n return connection;\n }\n\n private handleControllerMessage(controllerWs: WebSocket, msg: WsMessage): void {\n if (msg.type === ControllerMsg.RELAY) {\n const payload = msg.payload as { connectionId?: unknown; request?: unknown };\n const request = parseMessage(JSON.stringify(payload.request));\n const target = this.resolveConnection(\n typeof payload.connectionId === 'string' ? payload.connectionId : null,\n );\n if (!target) {\n this.sendResponse(controllerWs, request.id, { error: this.getTargetError() });\n return;\n }\n this.relayRequests.set(request.id, { controllerWs, targetWs: target.ws });\n this.sendSafe(target.ws, request);\n return;\n }\n\n if (msg.type === ControllerMsg.SET_ACTIVE_CONNECTION) {\n const payload = msg.payload as { connectionId?: unknown };\n const connectionId = typeof payload.connectionId === 'string' ? payload.connectionId : null;\n if (connectionId && !this.studioClients.has(connectionId)) {\n this.sendResponse(controllerWs, msg.id, {\n success: false,\n error: `Studio connection ${connectionId} is not connected`,\n });\n return;\n }\n this.sendResponse(controllerWs, msg.id, { success: true, connectionId });\n return;\n }\n\n this.sendResponse(controllerWs, msg.id, {\n error: `Unsupported controller message: ${msg.type}`,\n });\n }\n\n private handleStudioMessage(ws: WebSocket, msg: WsMessage): void {\n if (msg.type === StudioMsg.RESPONSE) {\n const relay = this.relayRequests.get(msg.id);\n if (relay) {\n if (relay.targetWs !== ws) {\n this.emit(\n 'server:error',\n new Error(`Ignored relayed response ${msg.id} from the wrong Studio session`),\n );\n return;\n }\n this.sendSafe(relay.controllerWs, msg);\n this.relayRequests.delete(msg.id);\n return;\n }\n\n const pending = this.pendingRequests.get(msg.id);\n if (pending) {\n if (pending.targetWs !== ws) {\n this.emit(\n 'server:error',\n new Error(`Ignored response ${msg.id} from the wrong Studio session`),\n );\n return;\n }\n clearTimeout(pending.timer);\n this.pendingRequests.delete(msg.id);\n pending.resolve(msg);\n }\n return;\n }\n\n this.emit(msg.type, msg.payload);\n this.emit('message', msg);\n }\n\n private cleanupSocket(ws: WebSocket): void {\n const role = this.socketRoles.get(ws);\n this.socketRoles.delete(ws);\n this.rateState.delete(ws);\n\n if (role === 'controller') {\n this.controllers.delete(ws);\n for (const [id, relay] of this.relayRequests) {\n if (relay.controllerWs === ws) this.relayRequests.delete(id);\n }\n return;\n }\n\n const connectionId = this.wsToConnectionId.get(ws);\n if (!connectionId) return;\n const connection = this.studioClients.get(connectionId);\n this.wsToConnectionId.delete(ws);\n this.studioClients.delete(connectionId);\n\n for (const [id, pending] of this.pendingRequests) {\n if (pending.targetWs === ws) {\n clearTimeout(pending.timer);\n pending.reject(new Error(`Studio disconnected (${connectionId})`));\n this.pendingRequests.delete(id);\n }\n }\n for (const [id, relay] of this.relayRequests) {\n if (relay.targetWs === ws) {\n this.sendResponse(relay.controllerWs, id, {\n error: `Studio disconnected (${connectionId})`,\n });\n this.relayRequests.delete(id);\n }\n }\n\n if (this.activeConnectionId === connectionId) {\n this.activeConnectionId =\n this.studioClients.size === 1 ? (this.studioClients.keys().next().value ?? null) : null;\n }\n const info = connection ? this.toPlaceInfo(connection) : { connectionId };\n this.emit('studio:disconnected', info);\n this.broadcastToControllers(StudioMsg.DISCONNECTED, info);\n this.broadcastTargetState();\n }\n\n private resolveConnection(\n connectionId: string | null = this.activeConnectionId,\n ): StudioConnection | null {\n if (connectionId) {\n const connection = this.studioClients.get(connectionId);\n return connection?.ws.readyState === WebSocket.OPEN ? connection : null;\n }\n if (this.studioClients.size === 1) {\n const connection = this.studioClients.values().next().value as StudioConnection | undefined;\n return connection?.ws.readyState === WebSocket.OPEN ? connection : null;\n }\n return null;\n }\n\n private getTargetError(): string {\n if (this.studioClients.size === 0) return 'No authenticated Roblox Studio connection';\n if (this.activeConnectionId)\n return `Studio connection ${this.activeConnectionId} is unavailable`;\n return 'Multiple Studio sessions are connected. Call dominus_select_studio with a connectionId.';\n }\n\n private toPlaceInfo(connection: StudioConnection): PlaceInfo {\n return {\n placeId: connection.placeId ?? 0,\n connectionId: connection.connectionId,\n clientId: connection.clientId,\n placeName: connection.placeName,\n studioVersion: connection.studioVersion,\n connectedAt: connection.connectedAt,\n active: connection.connectionId === this.activeConnectionId,\n };\n }\n\n private broadcastToControllers(type: string, payload: unknown): void {\n const message = createMessage(type, payload);\n for (const controller of this.controllers) this.sendSafe(controller, message);\n }\n\n private broadcastTargetState(): void {\n this.broadcastToControllers(ControllerMsg.TARGET_STATE, {\n connections: this.listConnections(),\n defaultActiveConnectionId: this.activeConnectionId,\n });\n }\n\n private sendResponse(ws: WebSocket, id: string, payload: unknown): void {\n this.sendSafe(ws, { id, type: StudioMsg.RESPONSE, payload, ts: Date.now() });\n }\n\n private sendSafe(ws: WebSocket, message: WsMessage): void {\n if (ws.readyState !== WebSocket.OPEN) return;\n try {\n ws.send(serializeMessage(message));\n } catch (err) {\n this.emit('server:error', err);\n }\n }\n\n sendRequest<T = unknown>(\n type: string,\n payload: unknown,\n timeoutMs = 15_000,\n ): Promise<WsMessage<T>> {\n return new Promise((resolve, reject) => {\n const target = this.resolveConnection();\n if (!target) {\n reject(new Error(this.getTargetError()));\n return;\n }\n const message = createMessage(type, payload);\n const timer = setTimeout(() => {\n this.pendingRequests.delete(message.id);\n reject(new Error(`Studio request timed out: ${type}`));\n }, timeoutMs);\n this.pendingRequests.set(message.id, {\n resolve: resolve as (msg: WsMessage) => void,\n reject,\n timer,\n targetWs: target.ws,\n });\n this.sendSafe(target.ws, message);\n });\n }\n\n sendNotification(type: string, payload: unknown): void {\n const target = this.resolveConnection();\n if (target) this.sendSafe(target.ws, createMessage(type, payload));\n }\n\n isConnected(): boolean {\n return this.resolveConnection() !== null;\n }\n\n getConnectionInfo(): StudioConnection | null {\n return this.resolveConnection();\n }\n\n listConnections(): PlaceInfo[] {\n return [...this.studioClients.values()]\n .filter(({ ws }) => ws.readyState === WebSocket.OPEN)\n .map((connection) => this.toPlaceInfo(connection));\n }\n\n getActiveConnectionId(): string | null {\n return this.activeConnectionId;\n }\n\n async setActiveConnectionId(connectionId: string | null): Promise<void> {\n if (connectionId && !this.studioClients.has(connectionId)) {\n throw new Error(`Studio connection ${connectionId} is not connected`);\n }\n this.activeConnectionId = connectionId;\n this.broadcastTargetState();\n }\n\n getActivePlaceId(): number {\n return this.resolveConnection()?.placeId ?? 0;\n }\n\n setActivePlaceId(placeId: number): void {\n if (placeId === 0) {\n this.activeConnectionId = null;\n return;\n }\n const matches = [...this.studioClients.values()].filter(\n (connection) => connection.placeId === placeId,\n );\n if (matches.length !== 1) {\n throw new Error(\n matches.length === 0\n ? `Place ${placeId} is not connected`\n : `Place ${placeId} has multiple Studio sessions; select a connectionId instead`,\n );\n }\n this.activeConnectionId = matches[0].connectionId;\n }\n\n getPort(): number {\n return this.port;\n }\n\n stop(): Promise<void> {\n return new Promise((resolve) => {\n for (const pending of this.pendingRequests.values()) {\n clearTimeout(pending.timer);\n pending.reject(new Error('Dominus bridge is shutting down'));\n }\n this.pendingRequests.clear();\n this.relayRequests.clear();\n for (const ws of this.socketRoles.keys()) ws.close(1001, 'Dominus bridge shutdown');\n this.studioClients.clear();\n this.controllers.clear();\n this.socketRoles.clear();\n this.wsToConnectionId.clear();\n if (this.wss) this.wss.close(() => resolve());\n else resolve();\n });\n }\n}\n\nfunction validateStudioHello(payload: unknown): StudioConnectedPayload {\n if (!payload || typeof payload !== 'object') throw new Error('Invalid Studio hello payload');\n const hello = payload as StudioConnectedPayload;\n if (hello.protocolVersion !== DOMINUS_PROTOCOL_VERSION) {\n throw new Error(\n `Studio plugin protocol ${hello.protocolVersion ?? 'unknown'} is incompatible; expected ${DOMINUS_PROTOCOL_VERSION}`,\n );\n }\n if (\n typeof hello.clientId !== 'string' ||\n hello.clientId.length < 8 ||\n hello.clientId.length > 128\n ) {\n throw new Error('Studio hello is missing a valid clientId');\n }\n if (typeof hello.studioVersion !== 'string' || hello.studioVersion.length > 128) {\n throw new Error('Studio hello is missing studioVersion');\n }\n if (hello.placeName && hello.placeName.length > 256)\n throw new Error('Studio placeName is too long');\n return hello;\n}\n\nfunction rawByteLength(raw: RawData): number {\n if (typeof raw === 'string') return Buffer.byteLength(raw);\n if (Array.isArray(raw)) return raw.reduce((total, item) => total + item.byteLength, 0);\n return raw.byteLength;\n}\n","import { loadConfig } from '../config/config.js';\nimport { DominusServer } from './ws-server.js';\n\nconst configuredPort = Number(process.env.DOMINUS_BRIDGE_PORT ?? loadConfig().port);\nif (!Number.isInteger(configuredPort) || configuredPort < 1 || configuredPort > 65535) {\n throw new Error(`Invalid Dominus bridge port: ${configuredPort}`);\n}\n\nconst bridge = new DominusServer(configuredPort);\nawait bridge.start();\n\nlet stopping = false;\nasync function stop(): Promise<void> {\n if (stopping) return;\n stopping = true;\n await bridge.stop();\n}\n\nprocess.once('SIGINT', () => void stop().finally(() => process.exit(0)));\nprocess.once('SIGTERM', () => void stop().finally(() => process.exit(0)));\n"]}
1
+ {"version":3,"sources":["../src/config/config.ts","../src/transport/protocol.ts","../src/transport/auth.ts","../src/transport/ws-server.ts","../src/transport/bridge-daemon.ts"],"names":["path","fs"],"mappings":"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","file":"bridge-daemon.js","sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport os from 'node:os';\nimport type { ConfigAccess } from '../tools/types.js';\n\nconst DOMINUS_DIR = path.join(os.homedir(), '.dominus');\nconst CONFIG_PATH = path.join(DOMINUS_DIR, 'config.json');\nconst DB_PATH = path.join(DOMINUS_DIR, 'dominus.db');\nconst RULES_PATH = path.join(DOMINUS_DIR, 'rules.json');\n\nexport type AIProviderType =\n | 'openai'\n | 'anthropic'\n | 'gemini'\n | 'xai'\n | 'openrouter'\n | 'custom';\n\nexport interface ProviderConfig {\n name: string;\n baseUrl: string;\n envVar: string;\n defaultModel: string;\n}\n\nexport const PROVIDERS: Record<AIProviderType, ProviderConfig> = {\n openai: {\n name: 'OpenAI',\n baseUrl: 'https://api.openai.com/v1',\n envVar: 'OPENAI_API_KEY',\n defaultModel: 'gpt-5.3-codex',\n },\n anthropic: {\n name: 'Anthropic',\n baseUrl: 'https://api.anthropic.com/v1/',\n envVar: 'ANTHROPIC_API_KEY',\n defaultModel: 'claude-sonnet-4-6',\n },\n gemini: {\n name: 'Google Gemini',\n baseUrl: 'https://generativelanguage.googleapis.com/v1beta/openai',\n envVar: 'GEMINI_API_KEY',\n defaultModel: 'gemini-3.1-pro',\n },\n xai: {\n name: 'xAI (Grok)',\n baseUrl: 'https://api.x.ai/v1',\n envVar: 'XAI_API_KEY',\n defaultModel: 'grok-4.2',\n },\n openrouter: {\n name: 'OpenRouter',\n baseUrl: 'https://openrouter.ai/api/v1',\n envVar: 'OPENROUTER_API_KEY',\n defaultModel: 'anthropic/claude-sonnet-4-6',\n },\n custom: {\n name: 'Custom',\n baseUrl: '',\n envVar: '',\n defaultModel: '',\n },\n};\n\nexport interface DominusConfig {\n provider: AIProviderType;\n apiKey?: string;\n apiBaseUrl: string;\n model: string;\n port: number;\n maxToolIterations: number;\n maxVerifyRetries: number;\n tokenBudget: number;\n autoIndex: boolean;\n autoLearn: boolean;\n userName: string;\n openCloudApiKey?: string;\n}\n\nconst DEFAULTS: DominusConfig = {\n provider: 'openai',\n apiBaseUrl: '',\n model: '',\n port: 18088,\n maxToolIterations: 25,\n maxVerifyRetries: 3,\n tokenBudget: 128000,\n autoIndex: true,\n autoLearn: true,\n userName: 'Developer',\n};\n\n/**\n * Auto-detect provider from environment variables.\n * Returns the first provider whose env var is set, or null.\n */\nexport function detectProviderFromEnv(): { provider: AIProviderType; apiKey: string } | null {\n const priority: AIProviderType[] = ['openai', 'anthropic', 'gemini', 'xai', 'openrouter'];\n for (const p of priority) {\n const key = process.env[PROVIDERS[p].envVar];\n if (key) return { provider: p, apiKey: key };\n }\n return null;\n}\n\n/**\n * Resolve the effective base URL and model for a provider config.\n */\nexport function resolveProviderSettings(config: DominusConfig): {\n baseUrl: string;\n model: string;\n} {\n const providerInfo = PROVIDERS[config.provider];\n return {\n baseUrl: config.apiBaseUrl || providerInfo?.baseUrl || PROVIDERS.openrouter.baseUrl,\n model: config.model || providerInfo?.defaultModel || 'gpt-4o',\n };\n}\n\nexport interface DominusRules {\n do: string[];\n dont: string[];\n conventions: string[];\n context: string;\n}\n\nconst DEFAULT_RULES: DominusRules = {\n do: [],\n dont: [],\n conventions: [],\n context: '',\n};\n\nfunction ensureDir(): void {\n if (!fs.existsSync(DOMINUS_DIR)) {\n fs.mkdirSync(DOMINUS_DIR, { recursive: true });\n }\n}\n\nfunction readJson<T>(filePath: string, defaults: T): T {\n try {\n if (fs.existsSync(filePath)) {\n const raw = fs.readFileSync(filePath, 'utf-8');\n return { ...defaults, ...JSON.parse(raw) };\n }\n } catch {\n // Corrupted file; reset to defaults\n }\n return { ...defaults };\n}\n\nfunction writeJson(filePath: string, data: unknown): void {\n ensureDir();\n fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');\n}\n\nexport function loadConfig(): DominusConfig {\n ensureDir();\n return readJson(CONFIG_PATH, DEFAULTS);\n}\n\nexport function saveConfig(config: DominusConfig): void {\n writeJson(CONFIG_PATH, config);\n}\n\nexport function getConfigValue<K extends keyof DominusConfig>(key: K): DominusConfig[K] {\n const config = loadConfig();\n return config[key];\n}\n\nexport function setConfigValue<K extends keyof DominusConfig>(\n key: K,\n value: DominusConfig[K],\n): void {\n const config = loadConfig();\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (config as any)[key] = value;\n saveConfig(config);\n}\n\nexport function loadRules(): DominusRules {\n ensureDir();\n return readJson(RULES_PATH, DEFAULT_RULES);\n}\n\nexport function saveRules(rules: DominusRules): void {\n writeJson(RULES_PATH, rules);\n}\n\nexport function getDominusDir(): string {\n ensureDir();\n return DOMINUS_DIR;\n}\n\nexport function getDbPath(): string {\n ensureDir();\n return DB_PATH;\n}\n\nexport function createConfigAccess(): ConfigAccess {\n return {\n get<T>(key: string): T | undefined {\n const config = loadConfig() as unknown as Record<string, unknown>;\n return config[key] as T | undefined;\n },\n set(key: string, value: unknown): void {\n const config = loadConfig() as unknown as Record<string, unknown>;\n config[key] = value;\n saveConfig(config as unknown as DominusConfig);\n },\n getAll(): Record<string, unknown> {\n return loadConfig() as unknown as Record<string, unknown>;\n },\n };\n}\n","import { nanoid } from 'nanoid';\nimport { z } from 'zod';\n\nexport const DOMINUS_PROTOCOL_VERSION = 2;\nexport const MAX_MESSAGE_BYTES = 1024 * 1024;\n\nexport interface WsMessage<T = unknown> {\n id: string;\n type: string;\n payload: T;\n ts: number;\n}\n\nexport const StudioMsg = {\n HELLO: 'studio:hello',\n CONNECTED: 'studio:connected',\n DISCONNECTED: 'studio:disconnected',\n EXPLORER: 'studio:explorer',\n OUTPUT: 'studio:output',\n ERROR: 'studio:error',\n SELECTION: 'studio:selection',\n SCRIPT_CONTENT: 'studio:script:content',\n SCRIPT_CHANGED: 'studio:script:changed',\n RESPONSE: 'studio:response',\n REFLECTION: 'studio:reflection',\n TEST_RESULTS: 'studio:test:results',\n AUTHENTICATED: 'server:authenticated',\n AUTH_REJECTED: 'server:auth_rejected',\n} as const;\n\nexport const ControllerMsg = {\n HELLO: 'controller:hello',\n WELCOME: 'controller:welcome',\n RELAY: 'controller:relay',\n TARGET_STATE: 'controller:target_state',\n GET_TARGET_STATE: 'controller:get_target_state',\n SET_ACTIVE_CONNECTION: 'controller:set_active_connection',\n} as const;\n\nexport const CliMsg = {\n GET_EXPLORER: 'cli:get:explorer',\n GET_SCRIPT: 'cli:get:script',\n SET_SCRIPT: 'cli:set:script',\n RUN_CODE: 'cli:run',\n GET_PROPERTIES: 'cli:get:properties',\n GET_DESCENDANTS_PROPERTIES: 'cli:get:descendants:properties',\n SET_PROPERTIES: 'cli:set:properties',\n INSERT_INSTANCE: 'cli:insert',\n DELETE_INSTANCE: 'cli:delete',\n GET_SELECTION: 'cli:get:selection',\n SELECT: 'cli:select',\n SEARCH_SCRIPTS: 'cli:search:scripts',\n GET_OUTPUT: 'cli:get:output',\n RUN_TESTS: 'cli:run:tests',\n EXECUTE_RUN_TEST: 'cli:test:run',\n EXECUTE_PLAY_TEST: 'cli:test:play',\n END_TEST: 'cli:test:end',\n BUILD_UI: 'cli:build:ui',\n GET_REFLECTION: 'cli:get:reflection',\n UPLOAD_ASSET: 'cli:upload:asset',\n SERIALIZE_UI: 'cli:serialize:ui',\n MOVE_INSTANCE: 'cli:move:instance',\n UNDO: 'cli:undo',\n REDO: 'cli:redo',\n BULK_SET_PROPERTIES: 'cli:bulk:set:properties',\n FIND_REPLACE_SCRIPTS: 'cli:find:replace:scripts',\n V2_GET_TREE: 'studio:v2:get_tree',\n V2_INSPECT: 'studio:v2:inspect',\n V2_GET_SELECTION: 'studio:v2:get_selection',\n V2_READ_SCRIPT: 'studio:v2:read_script',\n V2_UPDATE_SCRIPT: 'studio:v2:update_script',\n V2_APPLY: 'studio:v2:apply',\n V2_CREATE_PARTS: 'studio:v2:create_parts',\n V2_CLONE_INSTANCES: 'studio:v2:clone_instances',\n V2_GROUP_INSTANCES: 'studio:v2:group_instances',\n V2_UNGROUP_INSTANCES: 'studio:v2:ungroup_instances',\n V2_TRANSFORM_INSTANCES: 'studio:v2:transform_instances',\n V2_CREATE_WELDS: 'studio:v2:create_welds',\n V2_QUERY_PARTS: 'studio:v2:query_parts',\n V2_SET_SELECTION: 'studio:v2:set_selection',\n V2_GET_METADATA: 'studio:v2:get_metadata',\n V2_SET_ATTRIBUTES: 'studio:v2:set_attributes',\n V2_UPDATE_TAGS: 'studio:v2:update_tags',\n V2_LIST_CALLABLE_METHODS: 'studio:v2:list_callable_methods',\n V2_INVOKE_METHODS: 'studio:v2:invoke_methods',\n V2_DELETE: 'studio:v2:delete',\n V2_BUILD_UI: 'studio:v2:build_ui',\n V2_SNAPSHOT_UI: 'studio:v2:snapshot_ui',\n V2_RUN_TEST: 'studio:v2:run_test',\n V2_GET_OUTPUT: 'studio:v2:get_output',\n V2_GET_REFLECTION: 'studio:v2:get_reflection',\n} as const;\n\nexport type StudioMsgType = (typeof StudioMsg)[keyof typeof StudioMsg];\nexport type CliMsgType = (typeof CliMsg)[keyof typeof CliMsg];\n\nexport interface ExplorerNode {\n name: string;\n className: string;\n path: string;\n ref?: InstanceRef;\n children?: ExplorerNode[];\n}\n\nexport interface InstanceRef {\n instanceId?: string;\n pathSegments?: string[];\n}\n\nexport interface StudioConnectedPayload {\n studioVersion: string;\n protocolVersion?: number;\n clientId?: string;\n token?: string;\n connectionId?: string;\n placeId?: number;\n placeName?: string;\n engineVersion?: string;\n}\n\nexport interface ScriptContentPayload {\n path: string;\n className: 'Script' | 'LocalScript' | 'ModuleScript';\n source: string;\n lineCount: number;\n}\n\nexport interface ScriptChangedPayload {\n path: string;\n source: string;\n}\n\nexport interface OutputPayload {\n message: string;\n level: 'info' | 'warn' | 'error';\n timestamp: number;\n source?: string;\n}\n\nexport interface SelectionPayload {\n paths: string[];\n classNames: string[];\n}\n\nexport interface PropertyEntry {\n name: string;\n value: string;\n type: string;\n category?: string;\n}\n\nexport interface TestResultPayload {\n name: string;\n passed: boolean;\n duration: number;\n error?: string;\n}\n\nexport interface StudioTestPayload {\n args?: unknown;\n}\n\nexport interface StudioTestResultPayload {\n success: boolean;\n result?: unknown;\n error?: string;\n duration?: number;\n}\n\nexport interface ReflectionPayload {\n className: string;\n properties: { name: string; type: string; category: string }[];\n methods: { name: string; parameters: string[] }[];\n events: { name: string; parameters: string[] }[];\n}\n\nexport interface RunCodePayload {\n code: string;\n}\n\nexport interface RunCodeResponsePayload {\n output: string;\n error?: string;\n duration: number;\n}\n\nexport interface SearchScriptsPayload {\n query: string;\n maxResults?: number;\n}\n\nexport interface SearchResultPayload {\n results: {\n path: string;\n className: string;\n matches: { line: number; text: string }[];\n }[];\n}\n\nexport interface InsertInstancePayload {\n className: string;\n parentPath: string;\n name: string;\n properties?: Record<string, unknown>;\n}\n\nexport interface SetPropertiesPayload {\n path: string;\n properties: Record<string, unknown>;\n}\n\nexport function createMessage<T>(type: string, payload: T): WsMessage<T> {\n return { id: nanoid(), type, payload, ts: Date.now() };\n}\n\nexport function createRequest<T>(type: string, payload: T): WsMessage<T> {\n return createMessage(type, payload);\n}\n\nexport function parseMessage(raw: string): WsMessage {\n if (Buffer.byteLength(raw, 'utf8') > MAX_MESSAGE_BYTES) {\n throw new Error(`WebSocket message exceeds ${MAX_MESSAGE_BYTES} bytes`);\n }\n try {\n const parsed = JSON.parse(raw) as unknown;\n return wsMessageSchema.parse(parsed) as WsMessage;\n } catch {\n throw new Error(`Failed to parse WebSocket message: ${raw.slice(0, 100)}`);\n }\n}\n\nexport function serializeMessage(msg: WsMessage): string {\n const serialized = JSON.stringify(msg);\n if (Buffer.byteLength(serialized, 'utf8') > MAX_MESSAGE_BYTES) {\n throw new Error(`WebSocket message exceeds ${MAX_MESSAGE_BYTES} bytes`);\n }\n return serialized;\n}\n\nconst wsMessageSchema = z\n .object({\n id: z.string().min(1).max(128),\n type: z.string().min(1).max(128),\n payload: z.unknown(),\n ts: z.number().finite(),\n })\n .strict();\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport * as crypto from 'node:crypto';\nimport { getDominusDir } from '../config/config.js';\n\nconst TOKEN_FILE = 'bridge-token';\n\nexport function loadOrCreateBridgeToken(): string {\n const tokenPath = path.join(getDominusDir(), TOKEN_FILE);\n try {\n const existing = fs.readFileSync(tokenPath, 'utf8').trim();\n if (existing.length >= 32) return existing;\n } catch {\n // First run or an unreadable token is repaired below.\n }\n\n const token = crypto.randomBytes(32).toString('base64url');\n fs.writeFileSync(tokenPath, `${token}\\n`, { encoding: 'utf8', mode: 0o600 });\n try {\n fs.chmodSync(tokenPath, 0o600);\n } catch {\n // Windows ACLs do not map directly to POSIX modes.\n }\n return token;\n}\n\nexport function tokensEqual(actual: unknown, expected: string): boolean {\n if (typeof actual !== 'string') return false;\n const actualBuffer = Buffer.from(actual);\n const expectedBuffer = Buffer.from(expected);\n return (\n actualBuffer.length === expectedBuffer.length &&\n crypto.timingSafeEqual(actualBuffer, expectedBuffer)\n );\n}\n","import { randomUUID } from 'node:crypto';\nimport { EventEmitter } from 'node:events';\nimport { WebSocketServer, WebSocket, type RawData } from 'ws';\nimport {\n ControllerMsg,\n DOMINUS_PROTOCOL_VERSION,\n MAX_MESSAGE_BYTES,\n StudioMsg,\n createMessage,\n parseMessage,\n serializeMessage,\n type StudioConnectedPayload,\n type WsMessage,\n} from './protocol.js';\nimport { loadOrCreateBridgeToken, tokensEqual } from './auth.js';\n\nconst HANDSHAKE_TIMEOUT_MS = 8_000;\nconst RATE_WINDOW_MS = 10_000;\nconst RATE_LIMIT = 250;\n\ntype SocketRole = 'unknown' | 'studio' | 'controller';\n\nexport interface StudioConnection {\n ws: WebSocket;\n connectionId: string;\n clientId: string;\n studioVersion?: string;\n placeId?: number;\n placeName?: string;\n connectedAt: number;\n}\n\nexport interface PlaceInfo {\n placeId: number;\n connectionId: string;\n clientId: string;\n placeName?: string;\n studioVersion?: string;\n connectedAt: number;\n active: boolean;\n}\n\nexport interface StudioBridge extends EventEmitter {\n sendRequest<T = unknown>(\n type: string,\n payload: unknown,\n timeoutMs?: number,\n ): Promise<WsMessage<T>>;\n sendNotification(type: string, payload: unknown): void;\n isConnected(): boolean;\n getConnectionInfo(): StudioConnection | null;\n getPort(): number;\n stop(): Promise<void>;\n listConnections(): PlaceInfo[];\n refreshConnections?(): Promise<PlaceInfo[]>;\n getActiveConnectionId(): string | null;\n setActiveConnectionId(connectionId: string | null): Promise<void>;\n getActivePlaceId(): number;\n setActivePlaceId(placeId: number): void;\n}\n\ninterface RelayRequest {\n controllerWs: WebSocket;\n targetWs: WebSocket;\n}\n\ninterface ServerOptions {\n token?: string;\n host?: string;\n maxPayloadBytes?: number;\n handshakeTimeoutMs?: number;\n}\n\nexport class DominusServer extends EventEmitter implements StudioBridge {\n private wss: WebSocketServer | null = null;\n private readonly studioClients = new Map<string, StudioConnection>();\n private readonly wsToConnectionId = new Map<WebSocket, string>();\n private readonly controllers = new Set<WebSocket>();\n private readonly socketRoles = new Map<WebSocket, SocketRole>();\n private readonly pendingRequests = new Map<\n string,\n {\n resolve: (msg: WsMessage) => void;\n reject: (err: Error) => void;\n timer: ReturnType<typeof setTimeout>;\n targetWs: WebSocket;\n }\n >();\n private readonly relayRequests = new Map<string, RelayRequest>();\n private readonly rateState = new Map<WebSocket, { startedAt: number; count: number }>();\n private readonly token: string;\n private readonly host: string;\n private readonly maxPayloadBytes: number;\n private readonly handshakeTimeoutMs: number;\n private activeConnectionId: string | null = null;\n private port: number;\n\n constructor(port = 18088, options: ServerOptions = {}) {\n super();\n this.port = port;\n this.token = options.token ?? loadOrCreateBridgeToken();\n this.host = options.host ?? '127.0.0.1';\n this.maxPayloadBytes = options.maxPayloadBytes ?? MAX_MESSAGE_BYTES;\n this.handshakeTimeoutMs = options.handshakeTimeoutMs ?? HANDSHAKE_TIMEOUT_MS;\n }\n\n start(maxRetries = 0): Promise<void> {\n return new Promise((resolve, reject) => {\n const tryPort = (port: number, attempt: number) => {\n const wss = new WebSocketServer({\n port,\n host: this.host,\n maxPayload: this.maxPayloadBytes,\n perMessageDeflate: false,\n });\n\n wss.on('listening', () => {\n this.wss = wss;\n this.port = port;\n wss.on('connection', (ws) => this.handleConnection(ws));\n this.emit('server:started', { port, host: this.host });\n resolve();\n });\n\n wss.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE' && attempt < maxRetries) {\n wss.close();\n tryPort(port + 1, attempt + 1);\n return;\n }\n this.emit('server:error', err);\n reject(err);\n });\n };\n\n tryPort(this.port, 0);\n });\n }\n\n private handleConnection(ws: WebSocket): void {\n this.socketRoles.set(ws, 'unknown');\n this.rateState.set(ws, { startedAt: Date.now(), count: 0 });\n\n const handshakeTimer = setTimeout(() => {\n if (this.socketRoles.get(ws) === 'unknown') {\n ws.close(1008, 'Dominus handshake timed out');\n }\n }, this.handshakeTimeoutMs);\n\n ws.on('message', (raw) => {\n try {\n if (!this.consumeRateBudget(ws) || rawByteLength(raw) > this.maxPayloadBytes) {\n ws.close(1009, 'Dominus message limit exceeded');\n return;\n }\n const msg = parseMessage(raw.toString());\n const role = this.socketRoles.get(ws) ?? 'unknown';\n\n if (role === 'unknown') {\n clearTimeout(handshakeTimer);\n this.handleHandshake(ws, msg);\n } else if (role === 'controller') {\n this.handleControllerMessage(ws, msg);\n } else if (role === 'studio') {\n this.handleStudioMessage(ws, msg);\n }\n } catch (err) {\n this.emit('server:error', err);\n this.sendSafe(\n ws,\n createMessage(StudioMsg.AUTH_REJECTED, {\n error: err instanceof Error ? err.message : String(err),\n }),\n );\n }\n });\n\n ws.on('close', () => {\n clearTimeout(handshakeTimer);\n this.cleanupSocket(ws);\n });\n\n ws.on('error', (err) => this.emit('server:error', err));\n }\n\n private consumeRateBudget(ws: WebSocket): boolean {\n const now = Date.now();\n const state = this.rateState.get(ws) ?? { startedAt: now, count: 0 };\n if (now - state.startedAt >= RATE_WINDOW_MS) {\n state.startedAt = now;\n state.count = 0;\n }\n state.count += 1;\n this.rateState.set(ws, state);\n return state.count <= RATE_LIMIT;\n }\n\n private handleHandshake(ws: WebSocket, msg: WsMessage): void {\n if (msg.type === StudioMsg.HELLO) {\n const hello = validateStudioHello(msg.payload);\n if (tokensEqual(hello.token, this.token)) {\n this.authorizeStudio(ws, hello, randomUUID());\n } else {\n this.sendSafe(\n ws,\n createMessage(StudioMsg.AUTH_REJECTED, {\n error: 'Studio credentials are missing or stale. Run dominus-install-plugin.',\n }),\n );\n ws.close(1008, 'Unauthorized Studio plugin');\n }\n return;\n }\n\n if (msg.type === ControllerMsg.HELLO) {\n const payload = msg.payload as { token?: unknown; protocolVersion?: unknown };\n if (\n payload.protocolVersion !== DOMINUS_PROTOCOL_VERSION ||\n !tokensEqual(payload.token, this.token)\n ) {\n this.sendSafe(\n ws,\n createMessage(StudioMsg.AUTH_REJECTED, {\n error: 'Invalid Dominus controller credentials',\n }),\n );\n ws.close(1008, 'Unauthorized controller');\n return;\n }\n this.socketRoles.set(ws, 'controller');\n this.controllers.add(ws);\n this.sendSafe(\n ws,\n createMessage(ControllerMsg.WELCOME, {\n protocolVersion: DOMINUS_PROTOCOL_VERSION,\n port: this.port,\n connections: this.listConnections(),\n activeConnectionId: this.activeConnectionId,\n }),\n );\n return;\n }\n\n this.sendSafe(\n ws,\n createMessage(StudioMsg.AUTH_REJECTED, {\n error: `Expected ${StudioMsg.HELLO} or ${ControllerMsg.HELLO}`,\n }),\n );\n ws.close(1008, 'Invalid Dominus handshake');\n }\n\n private authorizeStudio(\n ws: WebSocket,\n hello: StudioConnectedPayload,\n connectionId: string,\n ): StudioConnection {\n const connection: StudioConnection = {\n ws,\n connectionId,\n clientId: hello.clientId!,\n connectedAt: Date.now(),\n studioVersion: hello.studioVersion,\n placeId: hello.placeId ?? 0,\n placeName: hello.placeName,\n };\n this.studioClients.set(connectionId, connection);\n this.wsToConnectionId.set(ws, connectionId);\n this.socketRoles.set(ws, 'studio');\n\n if (!this.activeConnectionId || !this.studioClients.has(this.activeConnectionId)) {\n this.activeConnectionId = connectionId;\n }\n\n this.sendSafe(\n ws,\n createMessage(StudioMsg.AUTHENTICATED, {\n protocolVersion: DOMINUS_PROTOCOL_VERSION,\n connectionId,\n token: this.token,\n }),\n );\n const info = this.toPlaceInfo(connection);\n this.emit('studio:connected', info);\n this.broadcastToControllers(StudioMsg.CONNECTED, info);\n this.broadcastTargetState();\n return connection;\n }\n\n private handleControllerMessage(controllerWs: WebSocket, msg: WsMessage): void {\n if (msg.type === ControllerMsg.RELAY) {\n const payload = msg.payload as { connectionId?: unknown; request?: unknown };\n const request = parseMessage(JSON.stringify(payload.request));\n const target = this.resolveConnection(\n typeof payload.connectionId === 'string' ? payload.connectionId : null,\n );\n if (!target) {\n this.sendResponse(controllerWs, request.id, { error: this.getTargetError() });\n return;\n }\n this.relayRequests.set(request.id, { controllerWs, targetWs: target.ws });\n this.sendSafe(target.ws, request);\n return;\n }\n\n if (msg.type === ControllerMsg.GET_TARGET_STATE) {\n this.sendResponse(controllerWs, msg.id, {\n success: true,\n connections: this.listConnections(),\n defaultActiveConnectionId: this.activeConnectionId,\n });\n return;\n }\n\n if (msg.type === ControllerMsg.SET_ACTIVE_CONNECTION) {\n const payload = msg.payload as { connectionId?: unknown };\n const connectionId = typeof payload.connectionId === 'string' ? payload.connectionId : null;\n if (connectionId && !this.studioClients.has(connectionId)) {\n this.sendResponse(controllerWs, msg.id, {\n success: false,\n error: `Studio connection ${connectionId} is not connected`,\n });\n return;\n }\n this.sendResponse(controllerWs, msg.id, { success: true, connectionId });\n return;\n }\n\n this.sendResponse(controllerWs, msg.id, {\n error: `Unsupported controller message: ${msg.type}`,\n });\n }\n\n private handleStudioMessage(ws: WebSocket, msg: WsMessage): void {\n if (msg.type === StudioMsg.RESPONSE) {\n const relay = this.relayRequests.get(msg.id);\n if (relay) {\n if (relay.targetWs !== ws) {\n this.emit(\n 'server:error',\n new Error(`Ignored relayed response ${msg.id} from the wrong Studio session`),\n );\n return;\n }\n this.sendSafe(relay.controllerWs, msg);\n this.relayRequests.delete(msg.id);\n return;\n }\n\n const pending = this.pendingRequests.get(msg.id);\n if (pending) {\n if (pending.targetWs !== ws) {\n this.emit(\n 'server:error',\n new Error(`Ignored response ${msg.id} from the wrong Studio session`),\n );\n return;\n }\n clearTimeout(pending.timer);\n this.pendingRequests.delete(msg.id);\n pending.resolve(msg);\n }\n return;\n }\n\n this.emit(msg.type, msg.payload);\n this.emit('message', msg);\n }\n\n private cleanupSocket(ws: WebSocket): void {\n const role = this.socketRoles.get(ws);\n this.socketRoles.delete(ws);\n this.rateState.delete(ws);\n\n if (role === 'controller') {\n this.controllers.delete(ws);\n for (const [id, relay] of this.relayRequests) {\n if (relay.controllerWs === ws) this.relayRequests.delete(id);\n }\n return;\n }\n\n const connectionId = this.wsToConnectionId.get(ws);\n if (!connectionId) return;\n const connection = this.studioClients.get(connectionId);\n this.wsToConnectionId.delete(ws);\n this.studioClients.delete(connectionId);\n\n for (const [id, pending] of this.pendingRequests) {\n if (pending.targetWs === ws) {\n clearTimeout(pending.timer);\n pending.reject(new Error(`Studio disconnected (${connectionId})`));\n this.pendingRequests.delete(id);\n }\n }\n for (const [id, relay] of this.relayRequests) {\n if (relay.targetWs === ws) {\n this.sendResponse(relay.controllerWs, id, {\n error: `Studio disconnected (${connectionId})`,\n });\n this.relayRequests.delete(id);\n }\n }\n\n if (this.activeConnectionId === connectionId) {\n this.activeConnectionId =\n this.studioClients.size === 1 ? (this.studioClients.keys().next().value ?? null) : null;\n }\n const info = connection ? this.toPlaceInfo(connection) : { connectionId };\n this.emit('studio:disconnected', info);\n this.broadcastToControllers(StudioMsg.DISCONNECTED, info);\n this.broadcastTargetState();\n }\n\n private resolveConnection(\n connectionId: string | null = this.activeConnectionId,\n ): StudioConnection | null {\n if (connectionId) {\n const connection = this.studioClients.get(connectionId);\n return connection?.ws.readyState === WebSocket.OPEN ? connection : null;\n }\n if (this.studioClients.size === 1) {\n const connection = this.studioClients.values().next().value as StudioConnection | undefined;\n return connection?.ws.readyState === WebSocket.OPEN ? connection : null;\n }\n return null;\n }\n\n private getTargetError(): string {\n if (this.studioClients.size === 0) return 'No authenticated Roblox Studio connection';\n if (this.activeConnectionId)\n return `Studio connection ${this.activeConnectionId} is unavailable`;\n return 'Multiple Studio sessions are connected. Call dominus_select_studio with a connectionId.';\n }\n\n private toPlaceInfo(connection: StudioConnection): PlaceInfo {\n return {\n placeId: connection.placeId ?? 0,\n connectionId: connection.connectionId,\n clientId: connection.clientId,\n placeName: connection.placeName,\n studioVersion: connection.studioVersion,\n connectedAt: connection.connectedAt,\n active: connection.connectionId === this.activeConnectionId,\n };\n }\n\n private broadcastToControllers(type: string, payload: unknown): void {\n const message = createMessage(type, payload);\n for (const controller of this.controllers) this.sendSafe(controller, message);\n }\n\n private broadcastTargetState(): void {\n this.broadcastToControllers(ControllerMsg.TARGET_STATE, {\n connections: this.listConnections(),\n defaultActiveConnectionId: this.activeConnectionId,\n });\n }\n\n private sendResponse(ws: WebSocket, id: string, payload: unknown): void {\n this.sendSafe(ws, { id, type: StudioMsg.RESPONSE, payload, ts: Date.now() });\n }\n\n private sendSafe(ws: WebSocket, message: WsMessage): void {\n if (ws.readyState !== WebSocket.OPEN) return;\n try {\n ws.send(serializeMessage(message));\n } catch (err) {\n this.emit('server:error', err);\n }\n }\n\n sendRequest<T = unknown>(\n type: string,\n payload: unknown,\n timeoutMs = 15_000,\n ): Promise<WsMessage<T>> {\n return new Promise((resolve, reject) => {\n const target = this.resolveConnection();\n if (!target) {\n reject(new Error(this.getTargetError()));\n return;\n }\n const message = createMessage(type, payload);\n const timer = setTimeout(() => {\n this.pendingRequests.delete(message.id);\n reject(new Error(`Studio request timed out: ${type}`));\n }, timeoutMs);\n this.pendingRequests.set(message.id, {\n resolve: resolve as (msg: WsMessage) => void,\n reject,\n timer,\n targetWs: target.ws,\n });\n this.sendSafe(target.ws, message);\n });\n }\n\n sendNotification(type: string, payload: unknown): void {\n const target = this.resolveConnection();\n if (target) this.sendSafe(target.ws, createMessage(type, payload));\n }\n\n isConnected(): boolean {\n return this.resolveConnection() !== null;\n }\n\n getConnectionInfo(): StudioConnection | null {\n return this.resolveConnection();\n }\n\n listConnections(): PlaceInfo[] {\n return [...this.studioClients.values()]\n .filter(({ ws }) => ws.readyState === WebSocket.OPEN)\n .map((connection) => this.toPlaceInfo(connection));\n }\n\n getActiveConnectionId(): string | null {\n return this.activeConnectionId;\n }\n\n async setActiveConnectionId(connectionId: string | null): Promise<void> {\n if (connectionId && !this.studioClients.has(connectionId)) {\n throw new Error(`Studio connection ${connectionId} is not connected`);\n }\n this.activeConnectionId = connectionId;\n this.broadcastTargetState();\n }\n\n getActivePlaceId(): number {\n return this.resolveConnection()?.placeId ?? 0;\n }\n\n setActivePlaceId(placeId: number): void {\n if (placeId === 0) {\n this.activeConnectionId = null;\n return;\n }\n const matches = [...this.studioClients.values()].filter(\n (connection) => connection.placeId === placeId,\n );\n if (matches.length !== 1) {\n throw new Error(\n matches.length === 0\n ? `Place ${placeId} is not connected`\n : `Place ${placeId} has multiple Studio sessions; select a connectionId instead`,\n );\n }\n this.activeConnectionId = matches[0].connectionId;\n }\n\n getPort(): number {\n return this.port;\n }\n\n stop(): Promise<void> {\n return new Promise((resolve) => {\n for (const pending of this.pendingRequests.values()) {\n clearTimeout(pending.timer);\n pending.reject(new Error('Dominus bridge is shutting down'));\n }\n this.pendingRequests.clear();\n this.relayRequests.clear();\n for (const ws of this.socketRoles.keys()) ws.close(1001, 'Dominus bridge shutdown');\n this.studioClients.clear();\n this.controllers.clear();\n this.socketRoles.clear();\n this.wsToConnectionId.clear();\n if (this.wss) this.wss.close(() => resolve());\n else resolve();\n });\n }\n}\n\nfunction validateStudioHello(payload: unknown): StudioConnectedPayload {\n if (!payload || typeof payload !== 'object') throw new Error('Invalid Studio hello payload');\n const hello = payload as StudioConnectedPayload;\n if (hello.protocolVersion !== DOMINUS_PROTOCOL_VERSION) {\n throw new Error(\n `Studio plugin protocol ${hello.protocolVersion ?? 'unknown'} is incompatible; expected ${DOMINUS_PROTOCOL_VERSION}`,\n );\n }\n if (\n typeof hello.clientId !== 'string' ||\n hello.clientId.length < 8 ||\n hello.clientId.length > 128\n ) {\n throw new Error('Studio hello is missing a valid clientId');\n }\n if (typeof hello.studioVersion !== 'string' || hello.studioVersion.length > 128) {\n throw new Error('Studio hello is missing studioVersion');\n }\n if (hello.placeName && hello.placeName.length > 256)\n throw new Error('Studio placeName is too long');\n return hello;\n}\n\nfunction rawByteLength(raw: RawData): number {\n if (typeof raw === 'string') return Buffer.byteLength(raw);\n if (Array.isArray(raw)) return raw.reduce((total, item) => total + item.byteLength, 0);\n return raw.byteLength;\n}\n","import { loadConfig } from '../config/config.js';\nimport { DominusServer } from './ws-server.js';\n\nconst configuredPort = Number(process.env.DOMINUS_BRIDGE_PORT ?? loadConfig().port);\nif (!Number.isInteger(configuredPort) || configuredPort < 1 || configuredPort > 65535) {\n throw new Error(`Invalid Dominus bridge port: ${configuredPort}`);\n}\n\nconst bridge = new DominusServer(configuredPort);\nawait bridge.start();\n\nlet stopping = false;\nasync function stop(): Promise<void> {\n if (stopping) return;\n stopping = true;\n await bridge.stop();\n}\n\nprocess.once('SIGINT', () => void stop().finally(() => process.exit(0)));\nprocess.once('SIGTERM', () => void stop().finally(() => process.exit(0)));\n"]}
@@ -72,6 +72,7 @@ var ControllerMsg = {
72
72
  WELCOME: "controller:welcome",
73
73
  RELAY: "controller:relay",
74
74
  TARGET_STATE: "controller:target_state",
75
+ GET_TARGET_STATE: "controller:get_target_state",
75
76
  SET_ACTIVE_CONNECTION: "controller:set_active_connection"
76
77
  };
77
78
  function createMessage(type, payload) {
@@ -125,16 +126,45 @@ var DominusClient = class extends EventEmitter {
125
126
  port;
126
127
  token;
127
128
  connectTimeoutMs;
129
+ autoReconnect;
130
+ reconnectDelayMs;
131
+ maxReconnectDelayMs;
128
132
  connections = /* @__PURE__ */ new Map();
129
133
  activeConnectionId = null;
134
+ connectPromise = null;
135
+ reconnectTimer = null;
136
+ nextReconnectDelayMs;
137
+ hasAuthenticated = false;
138
+ stopping = false;
130
139
  pendingRequests = /* @__PURE__ */ new Map();
131
140
  constructor(port = 18088, options = {}) {
132
141
  super();
133
142
  this.port = port;
134
143
  this.token = options.token ?? loadOrCreateBridgeToken();
135
144
  this.connectTimeoutMs = options.connectTimeoutMs ?? 8e3;
145
+ this.autoReconnect = options.autoReconnect ?? true;
146
+ this.reconnectDelayMs = options.reconnectDelayMs ?? 250;
147
+ this.maxReconnectDelayMs = options.maxReconnectDelayMs ?? 5e3;
148
+ this.nextReconnectDelayMs = this.reconnectDelayMs;
136
149
  }
137
150
  connect() {
151
+ if (this.ws?.readyState === WebSocket.OPEN) return Promise.resolve();
152
+ if (this.connectPromise) return this.connectPromise;
153
+ this.stopping = false;
154
+ this.clearReconnectTimer();
155
+ const attempt = this.openConnection();
156
+ this.connectPromise = attempt;
157
+ void attempt.then(
158
+ () => {
159
+ if (this.connectPromise === attempt) this.connectPromise = null;
160
+ },
161
+ () => {
162
+ if (this.connectPromise === attempt) this.connectPromise = null;
163
+ }
164
+ );
165
+ return attempt;
166
+ }
167
+ openConnection() {
138
168
  return new Promise((resolve, reject) => {
139
169
  const ws = new WebSocket(`ws://127.0.0.1:${this.port}`, {
140
170
  perMessageDeflate: false,
@@ -166,6 +196,8 @@ var DominusClient = class extends EventEmitter {
166
196
  settled = true;
167
197
  clearTimeout(timer);
168
198
  this.applyWelcome(msg.payload);
199
+ this.hasAuthenticated = true;
200
+ this.nextReconnectDelayMs = this.reconnectDelayMs;
169
201
  this.emit("server:started", { port: this.port, relay: true });
170
202
  resolve();
171
203
  return;
@@ -185,15 +217,21 @@ var DominusClient = class extends EventEmitter {
185
217
  });
186
218
  ws.on("close", () => {
187
219
  clearTimeout(timer);
188
- this.ws = null;
189
- this.connections.clear();
190
- this.activeConnectionId = null;
220
+ const wasCurrentSocket = this.ws === ws;
221
+ if (wasCurrentSocket) {
222
+ this.ws = null;
223
+ this.connections.clear();
224
+ this.activeConnectionId = null;
225
+ }
191
226
  if (!settled) {
192
227
  settled = true;
193
228
  reject(new Error("Dominus bridge closed before authentication completed"));
194
229
  }
195
- this.rejectPending(new Error("Dominus relay connection lost"));
196
- this.emit("studio:disconnected");
230
+ if (wasCurrentSocket) {
231
+ this.rejectPending(new Error("Dominus relay connection lost"));
232
+ this.emit("studio:disconnected");
233
+ this.scheduleReconnect();
234
+ }
197
235
  });
198
236
  ws.on("error", (err) => {
199
237
  if (!settled) {
@@ -205,6 +243,26 @@ var DominusClient = class extends EventEmitter {
205
243
  });
206
244
  });
207
245
  }
246
+ scheduleReconnect() {
247
+ if (!this.autoReconnect || !this.hasAuthenticated || this.stopping || this.reconnectTimer || this.ws?.readyState === WebSocket.OPEN) {
248
+ return;
249
+ }
250
+ const delayMs = this.nextReconnectDelayMs;
251
+ this.nextReconnectDelayMs = Math.min(delayMs * 2, this.maxReconnectDelayMs);
252
+ const timer = setTimeout(() => {
253
+ if (this.reconnectTimer !== timer) return;
254
+ this.reconnectTimer = null;
255
+ if (this.stopping) return;
256
+ void this.connect().catch(() => this.scheduleReconnect());
257
+ }, delayMs);
258
+ timer.unref?.();
259
+ this.reconnectTimer = timer;
260
+ }
261
+ clearReconnectTimer() {
262
+ if (!this.reconnectTimer) return;
263
+ clearTimeout(this.reconnectTimer);
264
+ this.reconnectTimer = null;
265
+ }
208
266
  applyWelcome(payload) {
209
267
  const welcome = payload;
210
268
  this.replaceConnections(welcome.connections ?? []);
@@ -357,10 +415,28 @@ var DominusClient = class extends EventEmitter {
357
415
  active: connection.connectionId === this.activeConnectionId
358
416
  }));
359
417
  }
418
+ async refreshConnections() {
419
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) await this.connect();
420
+ const result = await this.sendControlRequest(ControllerMsg.GET_TARGET_STATE, {});
421
+ this.replaceConnections(result.connections ?? []);
422
+ if (this.activeConnectionId && !this.connections.has(this.activeConnectionId)) {
423
+ this.activeConnectionId = null;
424
+ }
425
+ if (!this.activeConnectionId && result.defaultActiveConnectionId && this.connections.has(result.defaultActiveConnectionId)) {
426
+ this.activeConnectionId = result.defaultActiveConnectionId;
427
+ }
428
+ if (!this.activeConnectionId && this.connections.size === 1) {
429
+ this.activeConnectionId = this.connections.keys().next().value ?? null;
430
+ }
431
+ return this.listConnections();
432
+ }
360
433
  getActiveConnectionId() {
361
434
  return this.activeConnectionId;
362
435
  }
363
436
  async setActiveConnectionId(connectionId) {
437
+ if (connectionId && !this.connections.has(connectionId)) {
438
+ await this.refreshConnections();
439
+ }
364
440
  if (connectionId && !this.connections.has(connectionId)) {
365
441
  throw new Error(`Studio connection ${connectionId} is not connected`);
366
442
  }
@@ -394,6 +470,9 @@ var DominusClient = class extends EventEmitter {
394
470
  return this.port;
395
471
  }
396
472
  stop() {
473
+ this.stopping = true;
474
+ this.hasAuthenticated = false;
475
+ this.clearReconnectTimer();
397
476
  this.rejectPending(new Error("Dominus client is shutting down"));
398
477
  return new Promise((resolve) => {
399
478
  if (!this.ws) {