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 +13 -0
- package/README.md +1 -1
- package/dist/bridge-daemon.js +9 -0
- package/dist/bridge-daemon.js.map +1 -1
- package/dist/install-plugin.js +84 -5
- package/dist/install-plugin.js.map +1 -1
- package/dist/mcp.js +106 -23
- package/dist/mcp.js.map +1 -1
- package/package.json +6 -2
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.
|
|
71
|
+
"args": ["-y", "-p", "dominus-cli@2.4.1", "dominus-mcp"]
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
}
|
package/dist/bridge-daemon.js
CHANGED
|
@@ -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"]}
|
package/dist/install-plugin.js
CHANGED
|
@@ -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
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
|
|
196
|
-
|
|
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) {
|