dominus-cli 2.3.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,46 @@
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
+
16
+ ## 2.4.0 (2026-07-15)
17
+
18
+ ### Native instance metadata and method capabilities
19
+
20
+ - Added `studio_get_metadata`, `studio_set_attributes`, and
21
+ `studio_update_tags`. Dominus can now read and persist real typed Roblox
22
+ attributes and tags in edit mode without generating runtime bootstrap scripts
23
+ or maintaining attribute mirrors.
24
+ - Added `studio_list_callable_methods` and `studio_invoke_methods`. Agents can
25
+ discover and invoke a guarded set of common hierarchy, metadata, pivot, scale,
26
+ and bounding-box methods using typed arguments and typed return values.
27
+ - Method invocation is protected twice: the MCP schema rejects unknown method
28
+ names and the Studio plugin independently enforces class requirements,
29
+ argument types, item limits, and a default-deny method policy.
30
+ - Extended atomic `studio_apply` and multi-agent worker proposals with
31
+ `setAttributes` and `updateTags`. The coordinator now checks scope conflicts,
32
+ reads metadata back, reports exact mismatches, and includes failed metadata in
33
+ its single bounded repair pass.
34
+
35
+ ### Safety and reliability
36
+
37
+ - Metadata and method writes run through dedicated allowlisted plugin commands and one
38
+ ChangeHistoryService recording, with stable-ref validation, item limits,
39
+ rollback, and read-back-ready refs. Arbitrary `run_code` remains unavailable.
40
+ - Added tests for the 30-tool MCP catalog, clone routing, batch part creation,
41
+ metadata routing, method rejection, transform validation, coordinated
42
+ metadata proposals, and packaged command hardening.
43
+
3
44
  ## 2.3.0 (2026-07-15)
4
45
 
5
46
  ### Roblox building toolkit
@@ -22,7 +63,7 @@
22
63
 
23
64
  ### Safety and reliability
24
65
 
25
- - All new writes run through dedicated allowlisted plugin commands and one
66
+ - All building writes run through dedicated allowlisted plugin commands and one
26
67
  ChangeHistoryService recording, with stable-ref validation, item limits,
27
68
  rollback, and read-back-ready refs. Arbitrary `run_code` remains unavailable.
28
69
  - Added tests for the 25-tool MCP catalog, clone routing, batch part creation,
package/README.md CHANGED
@@ -8,7 +8,7 @@ Luau execution.
8
8
  ## What changed
9
9
 
10
10
  - MCP-only product: the old Ink chat CLI is no longer published.
11
- - 25 focused tools with input/output schemas, structured results, and risk hints.
11
+ - 30 focused tools with input/output schemas, structured results, and risk hints.
12
12
  - MCP-native parallel coordination uses the host's model through Sampling, with
13
13
  proposal-only workers and coordinator-owned Studio writes.
14
14
  - Stable instance refs survive dots in names, renames, reparenting, and duplicate
@@ -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.3.0", "dominus-mcp"]
71
+ "args": ["-y", "-p", "dominus-cli@2.4.1", "dominus-mcp"]
72
72
  }
73
73
  }
74
74
  }
@@ -109,6 +109,14 @@ parts can be moved safely with `studio_transform_instances`, organized with the
109
109
  grouping tools, connected with `studio_create_welds`, and discovered spatially
110
110
  with `studio_query_parts`.
111
111
 
112
+ For saved instance metadata, use `studio_get_metadata`,
113
+ `studio_set_attributes`, and `studio_update_tags`. Dominus writes real Studio
114
+ attributes and tags directly, so agents should not create Play-mode bootstrap
115
+ scripts or duplicate tags into attributes. When reflection reveals a useful
116
+ instance method that has no dedicated tool, `studio_list_callable_methods`
117
+ reports the safe capabilities for that target and `studio_invoke_methods` calls
118
+ them with typed arguments and results.
119
+
112
120
  For broad tasks with independent branches, use `run_parallel_task`. Dominus
113
121
  partitions immediate child scopes, supplies each worker with hierarchy and typed
114
122
  property evidence, runs up to five Sampling workers concurrently, validates their
@@ -136,6 +144,8 @@ Studio inspection:
136
144
  - `studio_snapshot_ui`
137
145
  - `studio_get_output`
138
146
  - `studio_get_reflection`
147
+ - `studio_get_metadata`
148
+ - `studio_list_callable_methods`
139
149
  - `studio_query_parts`
140
150
 
141
151
  Studio changes and verification:
@@ -149,6 +159,9 @@ Studio changes and verification:
149
159
  - `studio_ungroup_instances`
150
160
  - `studio_create_welds`
151
161
  - `studio_set_selection`
162
+ - `studio_set_attributes`
163
+ - `studio_update_tags`
164
+ - `studio_invoke_methods`
152
165
  - `studio_delete_instances`
153
166
  - `studio_build_ui`
154
167
  - `studio_run_test`
@@ -166,7 +179,9 @@ incomplete handshakes, and authenticates both Studio plugins and MCP controllers
166
179
  The distributed plugin contains only a credential placeholder;
167
180
  `dominus-install-plugin` writes the user-specific token into the local copy. The
168
181
  plugin only accepts an explicit command allowlist and contains no `loadstring`
169
- executor.
182
+ executor. Reflected methods are not automatically executable: method calls must
183
+ also pass a plugin-side capability policy that excludes arbitrary, destructive,
184
+ yielding, network, and signal operations.
170
185
 
171
186
  The bridge token protects against accidental or opportunistic access by unrelated
172
187
  local processes. Like other user-level credentials, it cannot protect against a
@@ -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_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) {