dominus-cli 2.2.1 → 2.4.0

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,63 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.4.0 (2026-07-15)
4
+
5
+ ### Native instance metadata and method capabilities
6
+
7
+ - Added `studio_get_metadata`, `studio_set_attributes`, and
8
+ `studio_update_tags`. Dominus can now read and persist real typed Roblox
9
+ attributes and tags in edit mode without generating runtime bootstrap scripts
10
+ or maintaining attribute mirrors.
11
+ - Added `studio_list_callable_methods` and `studio_invoke_methods`. Agents can
12
+ discover and invoke a guarded set of common hierarchy, metadata, pivot, scale,
13
+ and bounding-box methods using typed arguments and typed return values.
14
+ - Method invocation is protected twice: the MCP schema rejects unknown method
15
+ names and the Studio plugin independently enforces class requirements,
16
+ argument types, item limits, and a default-deny method policy.
17
+ - Extended atomic `studio_apply` and multi-agent worker proposals with
18
+ `setAttributes` and `updateTags`. The coordinator now checks scope conflicts,
19
+ reads metadata back, reports exact mismatches, and includes failed metadata in
20
+ its single bounded repair pass.
21
+
22
+ ### Safety and reliability
23
+
24
+ - Metadata and method writes run through dedicated allowlisted plugin commands and one
25
+ ChangeHistoryService recording, with stable-ref validation, item limits,
26
+ rollback, and read-back-ready refs. Arbitrary `run_code` remains unavailable.
27
+ - Added tests for the 30-tool MCP catalog, clone routing, batch part creation,
28
+ metadata routing, method rejection, transform validation, coordinated
29
+ metadata proposals, and packaged command hardening.
30
+
31
+ ## 2.3.0 (2026-07-15)
32
+
33
+ ### Roblox building toolkit
34
+
35
+ - Added `studio_clone_instances` for safe deep cloning of Parts, Models, UI,
36
+ scripts, and arbitrary instance trees. Clones support renaming, reparenting,
37
+ root property overrides, repeated copies, and spatial offsets without arbitrary
38
+ Luau execution.
39
+ - Added `studio_create_parts` for building up to 200 Parts, Wedges, MeshParts,
40
+ Seats, SpawnLocations, and related BaseParts in one batch, with optional Model
41
+ or Folder grouping.
42
+ - Added `studio_transform_instances` using `PVInstance:PivotTo()`, so Models and
43
+ BaseParts support exact CFrames plus world/local position and rotation offsets.
44
+ - Added `studio_group_instances`, `studio_ungroup_instances`,
45
+ `studio_create_welds`, `studio_query_parts`, and `studio_set_selection` for
46
+ hierarchy organization, rigid assemblies, spatial inspection, and Studio UX.
47
+ - Extended atomic `studio_apply` and parallel worker proposals with a typed
48
+ `clone` operation. Coordinators validate clone source and destination scopes,
49
+ detect destination conflicts, and verify the resulting hierarchy.
50
+
51
+ ### Safety and reliability
52
+
53
+ - All building writes run through dedicated allowlisted plugin commands and one
54
+ ChangeHistoryService recording, with stable-ref validation, item limits,
55
+ rollback, and read-back-ready refs. Arbitrary `run_code` remains unavailable.
56
+ - Added tests for the 25-tool MCP catalog, clone routing, batch part creation,
57
+ transform validation, and packaged building command hardening.
58
+ - Pinned Rojo `7.6.1` with Rokit so plugin builds use a current reflection database
59
+ and consistently preserve `RunContext.Plugin`.
60
+
3
61
  ## 2.2.1 (2026-07-13)
4
62
 
5
63
  ### Stable Studio bridge architecture
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
- - 17 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.2.1", "dominus-mcp"]
71
+ "args": ["-y", "-p", "dominus-cli@2.4.0", "dominus-mcp"]
72
72
  }
73
73
  }
74
74
  }
@@ -103,6 +103,20 @@ For UI fidelity work, use `studio_snapshot_ui` after `studio_build_ui` and compa
103
103
  fonts, UDim2 values, colors, strokes, and hierarchy rather than relying on a
104
104
  successful write response alone.
105
105
 
106
+ For 3D building, use `studio_create_parts` for batches and
107
+ `studio_clone_instances` when an existing object is the template. Models and
108
+ parts can be moved safely with `studio_transform_instances`, organized with the
109
+ grouping tools, connected with `studio_create_welds`, and discovered spatially
110
+ with `studio_query_parts`.
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
+
106
120
  For broad tasks with independent branches, use `run_parallel_task`. Dominus
107
121
  partitions immediate child scopes, supplies each worker with hierarchy and typed
108
122
  property evidence, runs up to five Sampling workers concurrently, validates their
@@ -130,11 +144,24 @@ Studio inspection:
130
144
  - `studio_snapshot_ui`
131
145
  - `studio_get_output`
132
146
  - `studio_get_reflection`
147
+ - `studio_get_metadata`
148
+ - `studio_list_callable_methods`
149
+ - `studio_query_parts`
133
150
 
134
151
  Studio changes and verification:
135
152
 
136
153
  - `studio_update_script`
137
154
  - `studio_apply`
155
+ - `studio_create_parts`
156
+ - `studio_clone_instances`
157
+ - `studio_transform_instances`
158
+ - `studio_group_instances`
159
+ - `studio_ungroup_instances`
160
+ - `studio_create_welds`
161
+ - `studio_set_selection`
162
+ - `studio_set_attributes`
163
+ - `studio_update_tags`
164
+ - `studio_invoke_methods`
138
165
  - `studio_delete_instances`
139
166
  - `studio_build_ui`
140
167
  - `studio_run_test`
@@ -152,7 +179,9 @@ incomplete handshakes, and authenticates both Studio plugins and MCP controllers
152
179
  The distributed plugin contains only a credential placeholder;
153
180
  `dominus-install-plugin` writes the user-specific token into the local copy. The
154
181
  plugin only accepts an explicit command allowlist and contains no `loadstring`
155
- 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.
156
185
 
157
186
  The bridge token protects against accidental or opportunistic access by unrelated
158
187
  local processes. Like other user-level credentials, it cannot protect against a
@@ -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_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 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"]}