dominus-cli 2.1.0 → 2.2.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 +141 -96
- package/README.md +24 -13
- package/dist/install-plugin.js +60 -13
- package/dist/install-plugin.js.map +1 -1
- package/dist/mcp.js +1194 -349
- package/dist/mcp.js.map +1 -1
- package/docs/DOMINUS_2_PLAN.md +12 -6
- package/package.json +5 -3
- package/plugin/Dominus.rbxmx +3674 -0
- package/plugin/src/BridgeConfig.lua +2 -0
- package/plugin/src/ValueCodec.lua +6 -5
- package/plugin/src/WsClient.lua +13 -0
- package/plugin/src/init.server.lua +35 -22
- package/plugin/Dominus.rbxm +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 2.2.0 (2026-07-13)
|
|
4
|
+
|
|
5
|
+
### MCP-native multi-agent coordinator
|
|
6
|
+
|
|
7
|
+
- Added `run_parallel_task` to the published MCP server. It uses the MCP client's
|
|
8
|
+
model through standard Sampling, so Dominus still requires no AI provider key.
|
|
9
|
+
- Added deterministic path-based task partitioning. UI and generic roots are split
|
|
10
|
+
by immediate child branches and distributed across one to five workers.
|
|
11
|
+
- Workers run concurrently and receive bounded hierarchy plus live typed property
|
|
12
|
+
evidence for only their owned scopes. Workers are proposal-only and never receive
|
|
13
|
+
Studio mutation tools.
|
|
14
|
+
- Added strict worker proposal and coordinator decision schemas. Existing instance
|
|
15
|
+
refs must exactly match supplied ID/path pairs, and every mutation must remain
|
|
16
|
+
inside the worker's owned branches.
|
|
17
|
+
- Added ancestor/descendant conflict detection and coordinator-only operation
|
|
18
|
+
ordering. The coordinator may select complete validated proposals but cannot
|
|
19
|
+
invent, rewrite, or partially accept worker operations.
|
|
20
|
+
- Final writes use one atomic `studio_apply` batch. Dominus then reads the root and
|
|
21
|
+
affected properties back, checks creates and moves, and performs at most one
|
|
22
|
+
bounded repair pass before reporting verification evidence.
|
|
23
|
+
- Parallel planning refuses truncated hierarchy evidence, and generic property
|
|
24
|
+
mutations can no longer bypass revision checks by writing script `Source`.
|
|
25
|
+
- Added summary-level MCP logging events for worker starts/completions, proposal
|
|
26
|
+
decisions, application, and verification without exposing full model traces.
|
|
27
|
+
- Clients without Sampling receive a clear capability response and keep the normal
|
|
28
|
+
single-agent inspect-plan-apply-verify workflow.
|
|
29
|
+
|
|
30
|
+
### Studio connection reliability
|
|
31
|
+
|
|
32
|
+
- Made pending WebStream connection attempts cancellable so overlapping retries
|
|
33
|
+
cannot strand a waiting coroutine or deadlock future reconnects.
|
|
34
|
+
- Serialized reconnect attempts and reset failed open transports explicitly.
|
|
35
|
+
- Changed the Dominus 2 toolbar button to always force an immediate reconnect
|
|
36
|
+
instead of toggling automatic reconnection off.
|
|
37
|
+
|
|
38
|
+
### Typed mutations and packaging
|
|
39
|
+
|
|
40
|
+
- Fixed valid Enum values such as `Slate`, `Wood`, `Glass`, and `Ball` being
|
|
41
|
+
rejected by resolving names against the live Enum type's items.
|
|
42
|
+
- Removed the stale binary `.rbxm` build artifact. The package and installer now
|
|
43
|
+
use the single canonical `.rbxmx` plugin build.
|
|
44
|
+
- Corrected documentation for the 17-tool MCP catalog, canonical plugin format,
|
|
45
|
+
and optional Studio restart behavior.
|
|
46
|
+
|
|
3
47
|
## 2.1.0 (2026-07-12)
|
|
4
48
|
|
|
5
49
|
Dominus has been rewritten as an MCP-first Roblox Studio engineering server.
|
|
@@ -8,7 +52,7 @@ Dominus has been rewritten as an MCP-first Roblox Studio engineering server.
|
|
|
8
52
|
|
|
9
53
|
- Removed the Ink/TUI command from the published binaries. Dominus now relies on
|
|
10
54
|
the MCP host's model, context, consent UI, and agent runtime.
|
|
11
|
-
- Replaced the monolithic legacy catalog with
|
|
55
|
+
- Replaced the monolithic legacy catalog with 16 focused tools for connection
|
|
12
56
|
management, inspection, transactional changes, UI, scripts, tests, and docs.
|
|
13
57
|
- Added strict input schemas, output schemas, `structuredContent`, text fallbacks,
|
|
14
58
|
`isError`, and accurate read-only/destructive/idempotent/open-world annotations.
|
|
@@ -20,7 +64,8 @@ Dominus has been rewritten as an MCP-first Roblox Studio engineering server.
|
|
|
20
64
|
### Secure Studio bridge
|
|
21
65
|
|
|
22
66
|
- Bound the WebSocket bridge to `127.0.0.1` instead of every network interface.
|
|
23
|
-
- Added a persistent 256-bit bridge token
|
|
67
|
+
- Added a persistent 256-bit bridge token that is provisioned directly into the
|
|
68
|
+
local plugin by `dominus-install-plugin`; users do not complete a pairing flow.
|
|
24
69
|
- Authenticated relay/controller processes as well as Studio plugin connections.
|
|
25
70
|
- Added one MiB payload limits, socket rate limits, handshake timeouts, bounded
|
|
26
71
|
request timeouts, and strict message-envelope validation.
|
|
@@ -36,7 +81,7 @@ Dominus has been rewritten as an MCP-first Roblox Studio engineering server.
|
|
|
36
81
|
same place ID are now safe and independently selectable.
|
|
37
82
|
- Relay clients carry their selected connection on each request, so separate MCP
|
|
38
83
|
processes cannot overwrite each other's active target.
|
|
39
|
-
- Added `dominus_status
|
|
84
|
+
- Added `dominus_status` and `dominus_select_studio`.
|
|
40
85
|
|
|
41
86
|
### Studio plugin harness
|
|
42
87
|
|
|
@@ -66,7 +111,7 @@ Dominus has been rewritten as an MCP-first Roblox Studio engineering server.
|
|
|
66
111
|
installer implementation.
|
|
67
112
|
- The published runtime dependency set is MCP-only; old CLI dependencies are
|
|
68
113
|
development-only and excluded from the MCP bundle.
|
|
69
|
-
- Added transport tests for auth,
|
|
114
|
+
- Added transport tests for auth, credential rejection, relays, duplicate places, wrong-target
|
|
70
115
|
responses, and disconnect isolation.
|
|
71
116
|
- Added in-memory MCP conformance tests for the tool catalog, structured outputs,
|
|
72
117
|
resources, prompts, routing, and error results.
|
|
@@ -81,95 +126,95 @@ Dominus has been rewritten as an MCP-first Roblox Studio engineering server.
|
|
|
81
126
|
`instanceId` and/or `pathSegments`.
|
|
82
127
|
- Script updates require `expectedRevision` from `studio_read_script`.
|
|
83
128
|
- Existing UI is not replaced unless `replaceExisting: true` is supplied.
|
|
84
|
-
|
|
85
|
-
## 0.5.5 (2026-04-23)
|
|
86
|
-
|
|
87
|
-
### New MCP Tools (30 total, up from 24)
|
|
88
|
-
|
|
89
|
-
- **`move_instance`** — Reparent an instance preserving all descendants. Sets ChangeHistoryService waypoint.
|
|
90
|
-
- **`bulk_set_properties`** — Set properties on multiple instances in one call. Explicit paths or className filter mode.
|
|
91
|
-
- **`find_replace_scripts`** — Find and replace across all scripts with optional Lua pattern matching and dry run preview.
|
|
92
|
-
- **`undo`** — Undo last action (ChangeHistoryService). Like Ctrl+Z.
|
|
93
|
-
- **`redo`** — Redo last undone action. Like Ctrl+Y.
|
|
94
|
-
|
|
95
|
-
### Improvements
|
|
96
|
-
|
|
97
|
-
- **`get_properties` now supports compact mode** — Same filtering as `get_descendants_properties`. Strips read-only, nil, deprecated, and default-valued properties by default. The previous version returned all ~50 properties per instance even though most were noise.
|
|
98
|
-
- **Fixed code injection in `clone_instance`, `group_instances`, `build_multiple`** — Instance names and paths with special characters (quotes, backslashes) no longer break generated Luau code.
|
|
99
|
-
- **`build_multiple` now supports `rgb()` colors and `canCollide`** — Previously only handled `#hex` and BrickColor names; `rgb(255,0,0)` would silently fail.
|
|
100
|
-
- **Better tool descriptions** — More specific descriptions help AI models pick the right tool. `get_properties` disambiguates vs `serialize_ui` and `get_descendants_properties`.
|
|
101
|
-
- **Shared `luaStr()` and `resolvePath()` helpers** — Centralized safe string generation for all code-gen tools.
|
|
102
|
-
|
|
103
|
-
## 0.5.4 (2026-04-23)
|
|
104
|
-
|
|
105
|
-
### Fixes
|
|
106
|
-
|
|
107
|
-
- **Rebuilt plugin** — 0.5.3 shipped a stale `Dominus.rbxm` missing the compact property filtering and protocol changes. This release includes the correctly built plugin.
|
|
108
|
-
|
|
109
|
-
## 0.5.3 (2026-04-23)
|
|
110
|
-
|
|
111
|
-
### Improvements
|
|
112
|
-
|
|
113
|
-
- **Compact `get_descendants_properties`** — Now strips read-only, nil, deprecated, and default-valued properties by default, reducing response size by ~80%. Pass `compact: false` for the full dump.
|
|
114
|
-
- **`serialize_ui` exposed as MCP tool** — Was previously only in the internal tool registry. Now available to all MCP clients (Copilot, Cursor, Claude, etc.) for converting existing Roblox UI into clean JSON trees.
|
|
115
|
-
- **UI → Roact/React conversion guidance** — System prompt and internal memory now teach the agent the full workflow: `serialize_ui` → JSON tree → Roact/React component with correct type mappings (UDim2, Color3, Vector2, enums, FontFace).
|
|
116
|
-
- **Smarter tool selection** — Agent now prefers `serialize_ui` over `get_descendants_properties` for UI conversion tasks, avoiding context bloat.
|
|
117
|
-
|
|
118
|
-
## 0.2.1 (2026-02-24)
|
|
119
|
-
|
|
120
|
-
### New
|
|
121
|
-
|
|
122
|
-
- **Auto-update checker** — On startup, Dominus checks npm for newer versions
|
|
123
|
-
and prints an upgrade notice. Cached for 4 hours, non-blocking, never throws.
|
|
124
|
-
|
|
125
|
-
### Bug Fixes
|
|
126
|
-
|
|
127
|
-
- **MCP server crashes on EADDRINUSE** — `dominus mcp` would crash if port 18088
|
|
128
|
-
was already held by a running `dominus start` (or another MCP instance). The MCP
|
|
129
|
-
server now checks for an existing listener and connects as a relay client instead
|
|
130
|
-
of trying to bind the same port.
|
|
131
|
-
|
|
132
|
-
### New MCP Tools (22 total, up from 14)
|
|
133
|
-
|
|
134
|
-
- **`get_class_info`** — Look up the full schema of any Roblox class via
|
|
135
|
-
ReflectionService (properties, methods, events with types).
|
|
136
|
-
- **`create_ui`** — Create an entire UI tree in one call with a declarative JSON
|
|
137
|
-
spec. Auto-coerces UDim2, Color3, Font, and enums. ~10x faster than `run_code`.
|
|
138
|
-
- **`create_part`** — One-call Part creation with position, size, color, material,
|
|
139
|
-
shape, transparency, anchored, and collision settings.
|
|
140
|
-
- **`clone_instance`** — Clone an instance with optional rename, reparent, and
|
|
141
|
-
position offset.
|
|
142
|
-
- **`group_instances`** — Group multiple instances into a Model or Folder.
|
|
143
|
-
- **`build_multiple`** — Batch-create many parts/instances in a single round-trip.
|
|
144
|
-
- **`execute_run_test`** — Enter Run mode via `StudioTestService:ExecuteRunModeAsync()`
|
|
145
|
-
with pass-through args and result capture.
|
|
146
|
-
- **`execute_play_test`** — Enter Play mode via `StudioTestService:ExecutePlayModeAsync()`
|
|
147
|
-
for full client-server simulation tests.
|
|
148
|
-
|
|
149
|
-
### New CLI Features
|
|
150
|
-
|
|
151
|
-
- **`dominus mcp-install`** — Auto-install MCP config into Cursor, VS Code,
|
|
152
|
-
Claude Desktop, Windsurf, and Claude Code with one command.
|
|
153
|
-
- **`dominus mcp-setup`** — Print manual MCP configuration instructions.
|
|
154
|
-
- **Relay client architecture** — `dominus start` can now connect as a relay
|
|
155
|
-
client to an existing MCP-owned WebSocket server, allowing the TUI and MCP
|
|
156
|
-
to coexist on the same port.
|
|
157
|
-
|
|
158
|
-
### Plugin Improvements
|
|
159
|
-
|
|
160
|
-
- Enhanced `Properties.lua` with broader property type support.
|
|
161
|
-
- Improved `Reflection.lua` for richer class metadata.
|
|
162
|
-
- Updated `TestRunner.lua` with Run/Play mode test support.
|
|
163
|
-
- New `UIBuilder.lua` module for server-side declarative UI construction.
|
|
164
|
-
|
|
165
|
-
### Other Changes
|
|
166
|
-
|
|
167
|
-
- Multi-provider AI config improvements (OpenRouter, xAI, Gemini, custom).
|
|
168
|
-
- TUI setup wizard for first-run API key and provider configuration.
|
|
169
|
-
- Improved system prompt with builder-tool awareness.
|
|
170
|
-
- WebSocket server now supports controller relay connections.
|
|
171
|
-
|
|
172
|
-
## 0.1.0 (2026-02-23)
|
|
173
|
-
|
|
174
|
-
- Initial release — 14 MCP tools, TUI agent, persistent memory, Roblox Studio
|
|
175
|
-
WebSocket bridge.
|
|
129
|
+
|
|
130
|
+
## 0.5.5 (2026-04-23)
|
|
131
|
+
|
|
132
|
+
### New MCP Tools (30 total, up from 24)
|
|
133
|
+
|
|
134
|
+
- **`move_instance`** — Reparent an instance preserving all descendants. Sets ChangeHistoryService waypoint.
|
|
135
|
+
- **`bulk_set_properties`** — Set properties on multiple instances in one call. Explicit paths or className filter mode.
|
|
136
|
+
- **`find_replace_scripts`** — Find and replace across all scripts with optional Lua pattern matching and dry run preview.
|
|
137
|
+
- **`undo`** — Undo last action (ChangeHistoryService). Like Ctrl+Z.
|
|
138
|
+
- **`redo`** — Redo last undone action. Like Ctrl+Y.
|
|
139
|
+
|
|
140
|
+
### Improvements
|
|
141
|
+
|
|
142
|
+
- **`get_properties` now supports compact mode** — Same filtering as `get_descendants_properties`. Strips read-only, nil, deprecated, and default-valued properties by default. The previous version returned all ~50 properties per instance even though most were noise.
|
|
143
|
+
- **Fixed code injection in `clone_instance`, `group_instances`, `build_multiple`** — Instance names and paths with special characters (quotes, backslashes) no longer break generated Luau code.
|
|
144
|
+
- **`build_multiple` now supports `rgb()` colors and `canCollide`** — Previously only handled `#hex` and BrickColor names; `rgb(255,0,0)` would silently fail.
|
|
145
|
+
- **Better tool descriptions** — More specific descriptions help AI models pick the right tool. `get_properties` disambiguates vs `serialize_ui` and `get_descendants_properties`.
|
|
146
|
+
- **Shared `luaStr()` and `resolvePath()` helpers** — Centralized safe string generation for all code-gen tools.
|
|
147
|
+
|
|
148
|
+
## 0.5.4 (2026-04-23)
|
|
149
|
+
|
|
150
|
+
### Fixes
|
|
151
|
+
|
|
152
|
+
- **Rebuilt plugin** — 0.5.3 shipped a stale `Dominus.rbxm` missing the compact property filtering and protocol changes. This release includes the correctly built plugin.
|
|
153
|
+
|
|
154
|
+
## 0.5.3 (2026-04-23)
|
|
155
|
+
|
|
156
|
+
### Improvements
|
|
157
|
+
|
|
158
|
+
- **Compact `get_descendants_properties`** — Now strips read-only, nil, deprecated, and default-valued properties by default, reducing response size by ~80%. Pass `compact: false` for the full dump.
|
|
159
|
+
- **`serialize_ui` exposed as MCP tool** — Was previously only in the internal tool registry. Now available to all MCP clients (Copilot, Cursor, Claude, etc.) for converting existing Roblox UI into clean JSON trees.
|
|
160
|
+
- **UI → Roact/React conversion guidance** — System prompt and internal memory now teach the agent the full workflow: `serialize_ui` → JSON tree → Roact/React component with correct type mappings (UDim2, Color3, Vector2, enums, FontFace).
|
|
161
|
+
- **Smarter tool selection** — Agent now prefers `serialize_ui` over `get_descendants_properties` for UI conversion tasks, avoiding context bloat.
|
|
162
|
+
|
|
163
|
+
## 0.2.1 (2026-02-24)
|
|
164
|
+
|
|
165
|
+
### New
|
|
166
|
+
|
|
167
|
+
- **Auto-update checker** — On startup, Dominus checks npm for newer versions
|
|
168
|
+
and prints an upgrade notice. Cached for 4 hours, non-blocking, never throws.
|
|
169
|
+
|
|
170
|
+
### Bug Fixes
|
|
171
|
+
|
|
172
|
+
- **MCP server crashes on EADDRINUSE** — `dominus mcp` would crash if port 18088
|
|
173
|
+
was already held by a running `dominus start` (or another MCP instance). The MCP
|
|
174
|
+
server now checks for an existing listener and connects as a relay client instead
|
|
175
|
+
of trying to bind the same port.
|
|
176
|
+
|
|
177
|
+
### New MCP Tools (22 total, up from 14)
|
|
178
|
+
|
|
179
|
+
- **`get_class_info`** — Look up the full schema of any Roblox class via
|
|
180
|
+
ReflectionService (properties, methods, events with types).
|
|
181
|
+
- **`create_ui`** — Create an entire UI tree in one call with a declarative JSON
|
|
182
|
+
spec. Auto-coerces UDim2, Color3, Font, and enums. ~10x faster than `run_code`.
|
|
183
|
+
- **`create_part`** — One-call Part creation with position, size, color, material,
|
|
184
|
+
shape, transparency, anchored, and collision settings.
|
|
185
|
+
- **`clone_instance`** — Clone an instance with optional rename, reparent, and
|
|
186
|
+
position offset.
|
|
187
|
+
- **`group_instances`** — Group multiple instances into a Model or Folder.
|
|
188
|
+
- **`build_multiple`** — Batch-create many parts/instances in a single round-trip.
|
|
189
|
+
- **`execute_run_test`** — Enter Run mode via `StudioTestService:ExecuteRunModeAsync()`
|
|
190
|
+
with pass-through args and result capture.
|
|
191
|
+
- **`execute_play_test`** — Enter Play mode via `StudioTestService:ExecutePlayModeAsync()`
|
|
192
|
+
for full client-server simulation tests.
|
|
193
|
+
|
|
194
|
+
### New CLI Features
|
|
195
|
+
|
|
196
|
+
- **`dominus mcp-install`** — Auto-install MCP config into Cursor, VS Code,
|
|
197
|
+
Claude Desktop, Windsurf, and Claude Code with one command.
|
|
198
|
+
- **`dominus mcp-setup`** — Print manual MCP configuration instructions.
|
|
199
|
+
- **Relay client architecture** — `dominus start` can now connect as a relay
|
|
200
|
+
client to an existing MCP-owned WebSocket server, allowing the TUI and MCP
|
|
201
|
+
to coexist on the same port.
|
|
202
|
+
|
|
203
|
+
### Plugin Improvements
|
|
204
|
+
|
|
205
|
+
- Enhanced `Properties.lua` with broader property type support.
|
|
206
|
+
- Improved `Reflection.lua` for richer class metadata.
|
|
207
|
+
- Updated `TestRunner.lua` with Run/Play mode test support.
|
|
208
|
+
- New `UIBuilder.lua` module for server-side declarative UI construction.
|
|
209
|
+
|
|
210
|
+
### Other Changes
|
|
211
|
+
|
|
212
|
+
- Multi-provider AI config improvements (OpenRouter, xAI, Gemini, custom).
|
|
213
|
+
- TUI setup wizard for first-run API key and provider configuration.
|
|
214
|
+
- Improved system prompt with builder-tool awareness.
|
|
215
|
+
- WebSocket server now supports controller relay connections.
|
|
216
|
+
|
|
217
|
+
## 0.1.0 (2026-02-23)
|
|
218
|
+
|
|
219
|
+
- Initial release — 14 MCP tools, TUI agent, persistent memory, Roblox Studio
|
|
220
|
+
WebSocket bridge.
|
package/README.md
CHANGED
|
@@ -9,11 +9,13 @@ Luau execution.
|
|
|
9
9
|
|
|
10
10
|
- MCP-only product: the old Ink chat CLI is no longer published.
|
|
11
11
|
- 17 focused tools with input/output schemas, structured results, and risk hints.
|
|
12
|
+
- MCP-native parallel coordination uses the host's model through Sampling, with
|
|
13
|
+
proposal-only workers and coordinator-owned Studio writes.
|
|
12
14
|
- Stable instance refs survive dots in names, renames, reparenting, and duplicate
|
|
13
15
|
sibling names for the life of the Studio session.
|
|
14
16
|
- Script writes require a revision from `studio_read_script`.
|
|
15
17
|
- Mutation batches and UI replacement are undoable and roll back on failure.
|
|
16
|
-
- The
|
|
18
|
+
- The installer provisions a private bridge token into the local Studio plugin.
|
|
17
19
|
- Multiple Studio windows are selected by unique connection ID, not place ID.
|
|
18
20
|
- Roblox Creator Docs API lookup is built in.
|
|
19
21
|
|
|
@@ -39,7 +41,8 @@ Install or update the Studio plugin:
|
|
|
39
41
|
dominus-install-plugin
|
|
40
42
|
```
|
|
41
43
|
|
|
42
|
-
|
|
44
|
+
Roblox Studio normally reloads the local plugin automatically. Restart Studio
|
|
45
|
+
only if the Dominus 2 toolbar button does not appear or reconnect.
|
|
43
46
|
|
|
44
47
|
## MCP configuration
|
|
45
48
|
|
|
@@ -62,7 +65,7 @@ For a version-pinned configuration that does not require a global install:
|
|
|
62
65
|
"mcpServers": {
|
|
63
66
|
"dominus": {
|
|
64
67
|
"command": "npx",
|
|
65
|
-
"args": ["-y", "-p", "dominus-cli@2.
|
|
68
|
+
"args": ["-y", "-p", "dominus-cli@2.2.0", "dominus-mcp"]
|
|
66
69
|
}
|
|
67
70
|
}
|
|
68
71
|
}
|
|
@@ -72,12 +75,9 @@ Restart the MCP client after changing its configuration.
|
|
|
72
75
|
|
|
73
76
|
## First connection
|
|
74
77
|
|
|
75
|
-
1.
|
|
76
|
-
2.
|
|
77
|
-
3.
|
|
78
|
-
4. Ask the MCP agent to call `dominus_status`, then `dominus_pair_studio` with
|
|
79
|
-
that connection ID and code.
|
|
80
|
-
5. Pairing is saved. Future Studio sessions authenticate automatically.
|
|
78
|
+
1. Run `dominus-install-plugin`; restart Roblox Studio only if it does not reload.
|
|
79
|
+
2. Start the MCP client so `dominus-mcp` is running.
|
|
80
|
+
3. Open Roblox Studio. The plugin connects and authenticates automatically.
|
|
81
81
|
|
|
82
82
|
If several Studio windows are open, call `dominus_select_studio` with the exact
|
|
83
83
|
connection ID before editing. Two windows on the same place ID remain distinct.
|
|
@@ -96,14 +96,24 @@ For UI fidelity work, use `studio_snapshot_ui` after `studio_build_ui` and compa
|
|
|
96
96
|
fonts, UDim2 values, colors, strokes, and hierarchy rather than relying on a
|
|
97
97
|
successful write response alone.
|
|
98
98
|
|
|
99
|
+
For broad tasks with independent branches, use `run_parallel_task`. Dominus
|
|
100
|
+
partitions immediate child scopes, supplies each worker with hierarchy and typed
|
|
101
|
+
property evidence, runs up to five Sampling workers concurrently, validates their
|
|
102
|
+
proposals, and applies only the coordinator-approved atomic plan. Workers never
|
|
103
|
+
receive direct write access. Clients without Sampling continue to use the normal
|
|
104
|
+
single-agent workflow.
|
|
105
|
+
|
|
99
106
|
## Tool catalog
|
|
100
107
|
|
|
101
108
|
Connection tools:
|
|
102
109
|
|
|
103
110
|
- `dominus_status`
|
|
104
|
-
- `dominus_pair_studio`
|
|
105
111
|
- `dominus_select_studio`
|
|
106
112
|
|
|
113
|
+
Parallel coordination:
|
|
114
|
+
|
|
115
|
+
- `run_parallel_task`
|
|
116
|
+
|
|
107
117
|
Studio inspection:
|
|
108
118
|
|
|
109
119
|
- `studio_get_tree`
|
|
@@ -131,9 +141,10 @@ Roblox documentation:
|
|
|
131
141
|
|
|
132
142
|
The MCP transport is stdio. The separate Studio WebSocket bridge binds only to
|
|
133
143
|
`127.0.0.1`, limits messages to one MiB, rate-limits sockets, times out incomplete
|
|
134
|
-
handshakes, and authenticates both Studio plugins and relay controllers.
|
|
135
|
-
|
|
136
|
-
command allowlist and contains no
|
|
144
|
+
handshakes, and authenticates both Studio plugins and relay controllers. The local
|
|
145
|
+
installer injects a user-specific token into the plugin, so no manual pairing flow
|
|
146
|
+
is needed. The plugin only accepts an explicit command allowlist and contains no
|
|
147
|
+
`loadstring` executor.
|
|
137
148
|
|
|
138
149
|
The bridge token protects against accidental or opportunistic access by unrelated
|
|
139
150
|
local processes. Like other user-level credentials, it cannot protect against a
|
package/dist/install-plugin.js
CHANGED
|
@@ -1,30 +1,77 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
2
|
+
import fs3 from 'fs';
|
|
3
|
+
import os2 from 'os';
|
|
4
|
+
import path3 from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
+
import * as crypto from 'crypto';
|
|
6
7
|
|
|
8
|
+
var DOMINUS_DIR = path3.join(os2.homedir(), ".dominus");
|
|
9
|
+
path3.join(DOMINUS_DIR, "config.json");
|
|
10
|
+
path3.join(DOMINUS_DIR, "dominus.db");
|
|
11
|
+
path3.join(DOMINUS_DIR, "rules.json");
|
|
12
|
+
function ensureDir() {
|
|
13
|
+
if (!fs3.existsSync(DOMINUS_DIR)) {
|
|
14
|
+
fs3.mkdirSync(DOMINUS_DIR, { recursive: true });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function getDominusDir() {
|
|
18
|
+
ensureDir();
|
|
19
|
+
return DOMINUS_DIR;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/transport/auth.ts
|
|
23
|
+
var TOKEN_FILE = "bridge-token";
|
|
24
|
+
function loadOrCreateBridgeToken() {
|
|
25
|
+
const tokenPath = path3.join(getDominusDir(), TOKEN_FILE);
|
|
26
|
+
try {
|
|
27
|
+
const existing = fs3.readFileSync(tokenPath, "utf8").trim();
|
|
28
|
+
if (existing.length >= 32) return existing;
|
|
29
|
+
} catch {
|
|
30
|
+
}
|
|
31
|
+
const token = crypto.randomBytes(32).toString("base64url");
|
|
32
|
+
fs3.writeFileSync(tokenPath, `${token}
|
|
33
|
+
`, { encoding: "utf8", mode: 384 });
|
|
34
|
+
try {
|
|
35
|
+
fs3.chmodSync(tokenPath, 384);
|
|
36
|
+
} catch {
|
|
37
|
+
}
|
|
38
|
+
return token;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/install-plugin.ts
|
|
42
|
+
var TOKEN_PLACEHOLDER = "__DOMINUS_BRIDGE_TOKEN__";
|
|
7
43
|
function getPluginDirectory() {
|
|
8
44
|
if (process.platform === "win32") {
|
|
9
45
|
const localAppData = process.env.LOCALAPPDATA;
|
|
10
46
|
if (!localAppData) throw new Error("LOCALAPPDATA is not set");
|
|
11
|
-
return
|
|
47
|
+
return path3.join(localAppData, "Roblox", "Plugins");
|
|
12
48
|
}
|
|
13
49
|
if (process.platform === "darwin") {
|
|
14
|
-
return
|
|
50
|
+
return path3.join(os2.homedir(), "Documents", "Roblox", "Plugins");
|
|
15
51
|
}
|
|
16
|
-
return
|
|
52
|
+
return path3.join(os2.homedir(), ".local", "share", "Roblox", "Plugins");
|
|
17
53
|
}
|
|
18
|
-
var distDirectory =
|
|
19
|
-
var source =
|
|
54
|
+
var distDirectory = path3.dirname(fileURLToPath(import.meta.url));
|
|
55
|
+
var source = path3.resolve(distDirectory, "..", "plugin", "Dominus.rbxmx");
|
|
20
56
|
var destinationDirectory = getPluginDirectory();
|
|
21
|
-
var destination =
|
|
22
|
-
if (!
|
|
57
|
+
var destination = path3.join(destinationDirectory, "Dominus2.rbxmx");
|
|
58
|
+
if (!fs3.existsSync(source)) {
|
|
23
59
|
throw new Error(`Packaged plugin not found at ${source}. Run pnpm build:plugin first.`);
|
|
24
60
|
}
|
|
25
|
-
|
|
26
|
-
|
|
61
|
+
var template = fs3.readFileSync(source, "utf8");
|
|
62
|
+
if (!template.includes(TOKEN_PLACEHOLDER)) {
|
|
63
|
+
throw new Error("Packaged plugin template is missing its bridge token placeholder");
|
|
64
|
+
}
|
|
65
|
+
var personalizedPlugin = template.replaceAll(TOKEN_PLACEHOLDER, loadOrCreateBridgeToken());
|
|
66
|
+
fs3.mkdirSync(destinationDirectory, { recursive: true });
|
|
67
|
+
fs3.writeFileSync(destination, personalizedPlugin, "utf8");
|
|
68
|
+
for (const obsoleteName of ["Dominus2.rbxm", "Dominus.rbxm", "Dominus.rbxmx"]) {
|
|
69
|
+
const obsoletePath = path3.join(destinationDirectory, obsoleteName);
|
|
70
|
+
if (obsoletePath !== destination) fs3.rmSync(obsoletePath, { force: true });
|
|
71
|
+
}
|
|
27
72
|
console.log(`Installed Dominus 2 Studio plugin to ${destination}`);
|
|
28
|
-
console.log(
|
|
73
|
+
console.log(
|
|
74
|
+
"Roblox Studio normally reloads Dominus 2 automatically. Restart Studio only if the toolbar does not reconnect."
|
|
75
|
+
);
|
|
29
76
|
//# sourceMappingURL=install-plugin.js.map
|
|
30
77
|
//# sourceMappingURL=install-plugin.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/install-plugin.ts"],"names":[],"mappings":";;;;;;AAKA,SAAS,kBAAA,GAA6B;AACpC,EAAA,IAAI,OAAA,CAAQ,aAAa,OAAA,EAAS;AAChC,IAAA,MAAM,YAAA,GAAe,QAAQ,GAAA,CAAI,YAAA;AACjC,IAAA,IAAI,CAAC,YAAA,EAAc,MAAM,IAAI,MAAM,yBAAyB,CAAA;AAC5D,IAAA,OAAO,IAAA,CAAK,IAAA,CAAK,YAAA,EAAc,QAAA,EAAU,SAAS,CAAA;AAAA,EACpD;AACA,EAAA,IAAI,OAAA,CAAQ,aAAa,QAAA,EAAU;AACjC,IAAA,OAAO,KAAK,IAAA,CAAK,EAAA,CAAG,SAAQ,EAAG,WAAA,EAAa,UAAU,SAAS,CAAA;AAAA,EACjE;AACA,EAAA,OAAO,IAAA,CAAK,KAAK,EAAA,CAAG,OAAA,IAAW,QAAA,EAAU,OAAA,EAAS,UAAU,SAAS,CAAA;AACvE;AAEA,IAAM,gBAAgB,IAAA,CAAK,OAAA,CAAQ,aAAA,CAAc,MAAA,CAAA,IAAA,CAAY,GAAG,CAAC,CAAA;AACjE,IAAM,SAAS,IAAA,CAAK,OAAA,CAAQ,aAAA,EAAe,IAAA,EAAM,UAAU,cAAc,CAAA;AACzE,IAAM,uBAAuB,kBAAA,EAAmB;AAChD,IAAM,WAAA,GAAc,IAAA,CAAK,IAAA,CAAK,oBAAA,EAAsB,eAAe,CAAA;AAEnE,IAAI,CAAC,EAAA,CAAG,UAAA,CAAW,MAAM,CAAA,EAAG;AAC1B,EAAA,MAAM,IAAI,KAAA,CAAM,CAAA,6BAAA,EAAgC,MAAM,CAAA,8BAAA,CAAgC,CAAA;AACxF;AAEA,EAAA,CAAG,SAAA,CAAU,oBAAA,EAAsB,EAAE,SAAA,EAAW,MAAM,CAAA;AACtD,EAAA,CAAG,YAAA,CAAa,QAAQ,WAAW,CAAA;AACnC,OAAA,CAAQ,GAAA,CAAI,CAAA,qCAAA,EAAwC,WAAW,CAAA,CAAE,CAAA;AACjE,OAAA,CAAQ,IAAI,iGAAiG,CAAA","file":"install-plugin.js","sourcesContent":["import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nfunction getPluginDirectory(): string {\n if (process.platform === 'win32') {\n const localAppData = process.env.LOCALAPPDATA;\n if (!localAppData) throw new Error('LOCALAPPDATA is not set');\n return path.join(localAppData, 'Roblox', 'Plugins');\n }\n if (process.platform === 'darwin') {\n return path.join(os.homedir(), 'Documents', 'Roblox', 'Plugins');\n }\n return path.join(os.homedir(), '.local', 'share', 'Roblox', 'Plugins');\n}\n\nconst distDirectory = path.dirname(fileURLToPath(import.meta.url));\nconst source = path.resolve(distDirectory, '..', 'plugin', 'Dominus.rbxm');\nconst destinationDirectory = getPluginDirectory();\nconst destination = path.join(destinationDirectory, 'Dominus2.rbxm');\n\nif (!fs.existsSync(source)) {\n throw new Error(`Packaged plugin not found at ${source}. Run pnpm build:plugin first.`);\n}\n\nfs.mkdirSync(destinationDirectory, { recursive: true });\nfs.copyFileSync(source, destination);\nconsole.log(`Installed Dominus 2 Studio plugin to ${destination}`);\nconsole.log('Restart Roblox Studio, start dominus-mcp, then approve the pairing code shown in Studio Output.');\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/config/config.ts","../src/transport/auth.ts","../src/install-plugin.ts"],"names":["path","os","fs"],"mappings":"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","file":"install-plugin.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 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 actualBuffer.length === expectedBuffer.length\n && crypto.timingSafeEqual(actualBuffer, expectedBuffer);\n}\n","import fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { loadOrCreateBridgeToken } from './transport/auth.js';\n\nconst TOKEN_PLACEHOLDER = '__DOMINUS_BRIDGE_TOKEN__';\n\nfunction getPluginDirectory(): string {\n if (process.platform === 'win32') {\n const localAppData = process.env.LOCALAPPDATA;\n if (!localAppData) throw new Error('LOCALAPPDATA is not set');\n return path.join(localAppData, 'Roblox', 'Plugins');\n }\n if (process.platform === 'darwin') {\n return path.join(os.homedir(), 'Documents', 'Roblox', 'Plugins');\n }\n return path.join(os.homedir(), '.local', 'share', 'Roblox', 'Plugins');\n}\n\nconst distDirectory = path.dirname(fileURLToPath(import.meta.url));\nconst source = path.resolve(distDirectory, '..', 'plugin', 'Dominus.rbxmx');\nconst destinationDirectory = getPluginDirectory();\nconst destination = path.join(destinationDirectory, 'Dominus2.rbxmx');\n\nif (!fs.existsSync(source)) {\n throw new Error(`Packaged plugin not found at ${source}. Run pnpm build:plugin first.`);\n}\n\nconst template = fs.readFileSync(source, 'utf8');\nif (!template.includes(TOKEN_PLACEHOLDER)) {\n throw new Error('Packaged plugin template is missing its bridge token placeholder');\n}\nconst personalizedPlugin = template.replaceAll(TOKEN_PLACEHOLDER, loadOrCreateBridgeToken());\n\nfs.mkdirSync(destinationDirectory, { recursive: true });\nfs.writeFileSync(destination, personalizedPlugin, 'utf8');\nfor (const obsoleteName of ['Dominus2.rbxm', 'Dominus.rbxm', 'Dominus.rbxmx']) {\n const obsoletePath = path.join(destinationDirectory, obsoleteName);\n if (obsoletePath !== destination) fs.rmSync(obsoletePath, { force: true });\n}\nconsole.log(`Installed Dominus 2 Studio plugin to ${destination}`);\nconsole.log(\n 'Roblox Studio normally reloads Dominus 2 automatically. Restart Studio only if the toolbar does not reconnect.',\n);\n"]}
|