dominus-cli 2.0.0 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +195 -0
- package/README.md +162 -248
- package/dist/install-plugin.js +75 -0
- package/dist/install-plugin.js.map +1 -0
- package/dist/mcp.js +1394 -4764
- package/dist/mcp.js.map +1 -1
- package/docs/DOMINUS_2_PLAN.md +75 -0
- package/package.json +27 -26
- package/plugin/Dominus.rbxmx +3674 -0
- package/plugin/src/BridgeConfig.lua +2 -0
- package/plugin/src/CommandRouter.lua +94 -0
- package/plugin/src/Explorer.lua +530 -373
- package/plugin/src/InstanceRegistry.lua +122 -0
- package/plugin/src/Mutation.lua +135 -0
- package/plugin/src/Properties.lua +859 -785
- package/plugin/src/Protocol.lua +30 -30
- package/plugin/src/Reflection.lua +91 -59
- package/plugin/src/TestRunner.lua +69 -141
- package/plugin/src/UIBuilder.lua +857 -727
- package/plugin/src/ValueCodec.lua +231 -0
- package/plugin/src/Watcher.lua +85 -85
- package/plugin/src/WsClient.lua +166 -81
- package/plugin/src/init.server.lua +191 -365
- package/dist/index.js +0 -7998
- package/dist/index.js.map +0 -1
- package/plugin/Dominus.rbxm +0 -0
- package/plugin/src/Executor.lua +0 -117
|
@@ -1,412 +1,238 @@
|
|
|
1
|
-
|
|
2
|
-
Dominus - Roblox Studio Plugin
|
|
3
|
-
AI-powered development agent bridge via WebSocket
|
|
1
|
+
local HttpService = game:GetService("HttpService")
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
]]
|
|
8
|
-
|
|
9
|
-
local toolbar = plugin:CreateToolbar("Dominus")
|
|
10
|
-
local connectButton = toolbar:CreateButton("Connect", "Connect to Dominus CLI agent", "rbxassetid://12974519994")
|
|
11
|
-
|
|
12
|
-
local WsClient = require(script.WsClient)
|
|
3
|
+
local EmbeddedBridgeToken = require(script.BridgeConfig)
|
|
4
|
+
local CommandRouter = require(script.CommandRouter)
|
|
13
5
|
local Protocol = require(script.Protocol)
|
|
14
|
-
local Explorer = require(script.Explorer)
|
|
15
|
-
local Executor = require(script.Executor)
|
|
16
6
|
local Watcher = require(script.Watcher)
|
|
17
|
-
local
|
|
18
|
-
|
|
19
|
-
local
|
|
20
|
-
local
|
|
7
|
+
local WsClient = require(script.WsClient)
|
|
8
|
+
|
|
9
|
+
local PROTOCOL_VERSION = 2
|
|
10
|
+
local DEFAULT_PORT = 18088
|
|
11
|
+
local MAX_MESSAGE_BYTES = 1024 * 1024
|
|
12
|
+
|
|
13
|
+
local toolbar = plugin:CreateToolbar("Dominus 2")
|
|
14
|
+
local connectButton = toolbar:CreateButton(
|
|
15
|
+
"Dominus 2",
|
|
16
|
+
"Connect this Studio session to the Dominus 2 MCP server",
|
|
17
|
+
"rbxassetid://12974519994"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
local authenticated = false
|
|
21
|
+
local transportOpen = false
|
|
22
|
+
local shouldReconnect = true
|
|
23
|
+
local reconnectScheduled = false
|
|
24
|
+
local reconnectDelay = 1
|
|
25
|
+
local shuttingDown = false
|
|
26
|
+
local connecting = false
|
|
27
|
+
|
|
28
|
+
local function getSetting(name, default)
|
|
29
|
+
local ok, value = pcall(function()
|
|
30
|
+
return plugin:GetSetting(name)
|
|
31
|
+
end)
|
|
32
|
+
if ok and value ~= nil then
|
|
33
|
+
return value
|
|
34
|
+
end
|
|
35
|
+
return default
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
local clientId = getSetting("Dominus2ClientId", nil)
|
|
39
|
+
if type(clientId) ~= "string" then
|
|
40
|
+
clientId = HttpService:GenerateGUID(false)
|
|
41
|
+
pcall(function()
|
|
42
|
+
plugin:SetSetting("Dominus2ClientId", clientId)
|
|
43
|
+
end)
|
|
44
|
+
end
|
|
45
|
+
local bridgeToken = getSetting("Dominus2BridgeToken", nil)
|
|
46
|
+
if type(EmbeddedBridgeToken) == "string" and EmbeddedBridgeToken ~= "__DOMINUS_BRIDGE_TOKEN__" then
|
|
47
|
+
bridgeToken = EmbeddedBridgeToken
|
|
48
|
+
pcall(function()
|
|
49
|
+
plugin:SetSetting("Dominus2BridgeToken", bridgeToken)
|
|
50
|
+
end)
|
|
51
|
+
end
|
|
52
|
+
local port = tonumber(getSetting("Dominus2Port", DEFAULT_PORT)) or DEFAULT_PORT
|
|
21
53
|
|
|
22
|
-
local
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
local
|
|
54
|
+
local function log(message)
|
|
55
|
+
print("[Dominus 2] " .. message)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
local function sendEnvelope(message)
|
|
59
|
+
local encodedOk, encoded = pcall(Protocol.encode, message)
|
|
60
|
+
if not encodedOk then
|
|
61
|
+
return false, tostring(encoded)
|
|
62
|
+
end
|
|
63
|
+
if #encoded > MAX_MESSAGE_BYTES then
|
|
64
|
+
return false, "Message exceeds one MiB"
|
|
65
|
+
end
|
|
66
|
+
return WsClient.send(encoded)
|
|
67
|
+
end
|
|
27
68
|
|
|
28
|
-
local function
|
|
29
|
-
|
|
69
|
+
local function sendResponse(id, payload)
|
|
70
|
+
return sendEnvelope({ id = id, type = "studio:response", payload = payload, ts = os.time() })
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
local scheduleReconnect
|
|
74
|
+
|
|
75
|
+
local function stopSession()
|
|
76
|
+
authenticated = false
|
|
77
|
+
transportOpen = false
|
|
78
|
+
Watcher.stop()
|
|
79
|
+
connectButton:SetActive(false)
|
|
30
80
|
end
|
|
31
81
|
|
|
32
82
|
local function handleMessage(rawMessage)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
83
|
+
if #rawMessage > MAX_MESSAGE_BYTES then
|
|
84
|
+
warn("[Dominus 2] Rejected an oversized server message")
|
|
85
|
+
WsClient.disconnect()
|
|
86
|
+
return
|
|
87
|
+
end
|
|
88
|
+
local decodedOk, message = pcall(Protocol.decode, rawMessage)
|
|
89
|
+
if not decodedOk or type(message) ~= "table" then
|
|
90
|
+
warn("[Dominus 2] Rejected an invalid server message")
|
|
36
91
|
return
|
|
37
92
|
end
|
|
38
93
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
local tree = Explorer.getTree(payload.rootPath, payload.maxDepth or 3)
|
|
46
|
-
WsClient.send(Protocol.encode({
|
|
47
|
-
id = msgId,
|
|
48
|
-
type = "studio:response",
|
|
49
|
-
payload = { tree = tree },
|
|
50
|
-
ts = os.clock(),
|
|
51
|
-
}))
|
|
52
|
-
elseif msgType == "cli:get:script" then
|
|
53
|
-
local result = Explorer.getScriptSource(payload.path)
|
|
54
|
-
WsClient.send(Protocol.encode({
|
|
55
|
-
id = msgId,
|
|
56
|
-
type = "studio:response",
|
|
57
|
-
payload = result,
|
|
58
|
-
ts = os.clock(),
|
|
59
|
-
}))
|
|
60
|
-
elseif msgType == "cli:set:script" then
|
|
61
|
-
local result = Explorer.setScriptSource(payload.path, payload.source)
|
|
62
|
-
WsClient.send(Protocol.encode({
|
|
63
|
-
id = msgId,
|
|
64
|
-
type = "studio:response",
|
|
65
|
-
payload = result,
|
|
66
|
-
ts = os.clock(),
|
|
67
|
-
}))
|
|
68
|
-
elseif msgType == "cli:run" then
|
|
69
|
-
local result = Executor.run(payload.code)
|
|
70
|
-
WsClient.send(Protocol.encode({
|
|
71
|
-
id = msgId,
|
|
72
|
-
type = "studio:response",
|
|
73
|
-
payload = result,
|
|
74
|
-
ts = os.clock(),
|
|
75
|
-
}))
|
|
76
|
-
elseif msgType == "cli:get:properties" then
|
|
77
|
-
local compact = payload.compact
|
|
78
|
-
if compact == nil then
|
|
79
|
-
compact = true
|
|
94
|
+
if message.type == "server:authenticated" then
|
|
95
|
+
local payload = message.payload or {}
|
|
96
|
+
if payload.protocolVersion ~= PROTOCOL_VERSION or type(payload.connectionId) ~= "string" then
|
|
97
|
+
warn("[Dominus 2] Server returned an invalid authentication response")
|
|
98
|
+
WsClient.disconnect()
|
|
99
|
+
return
|
|
80
100
|
end
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
ts = os.clock(),
|
|
87
|
-
}))
|
|
88
|
-
elseif msgType == "cli:get:descendants:properties" then
|
|
89
|
-
local compact = payload.compact
|
|
90
|
-
if compact == nil then
|
|
91
|
-
compact = true
|
|
92
|
-
end -- default to compact
|
|
93
|
-
local result = Properties.getDescendants(payload.path, compact)
|
|
94
|
-
WsClient.send(Protocol.encode({
|
|
95
|
-
id = msgId,
|
|
96
|
-
type = "studio:response",
|
|
97
|
-
payload = result,
|
|
98
|
-
ts = os.clock(),
|
|
99
|
-
}))
|
|
100
|
-
elseif msgType == "cli:set:properties" then
|
|
101
|
-
local result = Properties.set(payload.path, payload.properties)
|
|
102
|
-
WsClient.send(Protocol.encode({
|
|
103
|
-
id = msgId,
|
|
104
|
-
type = "studio:response",
|
|
105
|
-
payload = result,
|
|
106
|
-
ts = os.clock(),
|
|
107
|
-
}))
|
|
108
|
-
elseif msgType == "cli:insert" then
|
|
109
|
-
local result = Explorer.insertInstance(payload.className, payload.parentPath, payload.name, payload.properties)
|
|
110
|
-
WsClient.send(Protocol.encode({
|
|
111
|
-
id = msgId,
|
|
112
|
-
type = "studio:response",
|
|
113
|
-
payload = result,
|
|
114
|
-
ts = os.clock(),
|
|
115
|
-
}))
|
|
116
|
-
elseif msgType == "cli:delete" then
|
|
117
|
-
local result = Explorer.deleteInstance(payload.path)
|
|
118
|
-
WsClient.send(Protocol.encode({
|
|
119
|
-
id = msgId,
|
|
120
|
-
type = "studio:response",
|
|
121
|
-
payload = result,
|
|
122
|
-
ts = os.clock(),
|
|
123
|
-
}))
|
|
124
|
-
elseif msgType == "cli:get:selection" then
|
|
125
|
-
local Selection = game:GetService("Selection")
|
|
126
|
-
local selected = Selection:Get()
|
|
127
|
-
local paths = {}
|
|
128
|
-
local classNames = {}
|
|
129
|
-
for _, obj in selected do
|
|
130
|
-
table.insert(paths, Explorer.getPath(obj))
|
|
131
|
-
table.insert(classNames, obj.ClassName)
|
|
101
|
+
if type(payload.token) == "string" then
|
|
102
|
+
bridgeToken = payload.token
|
|
103
|
+
pcall(function()
|
|
104
|
+
plugin:SetSetting("Dominus2BridgeToken", bridgeToken)
|
|
105
|
+
end)
|
|
132
106
|
end
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
elseif msgType == "cli:search:scripts" then
|
|
140
|
-
local result = Explorer.searchScripts(payload.query, payload.maxResults or 20)
|
|
141
|
-
WsClient.send(Protocol.encode({
|
|
142
|
-
id = msgId,
|
|
143
|
-
type = "studio:response",
|
|
144
|
-
payload = result,
|
|
145
|
-
ts = os.clock(),
|
|
146
|
-
}))
|
|
147
|
-
elseif msgType == "cli:get:output" then
|
|
148
|
-
local result = Watcher.getRecentOutput(payload.limit or 50, payload.level)
|
|
149
|
-
WsClient.send(Protocol.encode({
|
|
150
|
-
id = msgId,
|
|
151
|
-
type = "studio:response",
|
|
152
|
-
payload = result,
|
|
153
|
-
ts = os.clock(),
|
|
154
|
-
}))
|
|
155
|
-
elseif msgType == "cli:run:tests" then
|
|
156
|
-
local result = TestRunner.run(payload.testName)
|
|
157
|
-
WsClient.send(Protocol.encode({
|
|
158
|
-
id = msgId,
|
|
159
|
-
type = "studio:response",
|
|
160
|
-
payload = result,
|
|
161
|
-
ts = os.clock(),
|
|
162
|
-
}))
|
|
163
|
-
elseif msgType == "cli:test:run" then
|
|
164
|
-
-- ExecuteRunModeAsync yields until EndTest is called, so run in a thread
|
|
165
|
-
task.spawn(function()
|
|
166
|
-
local result = TestRunner.executeRunMode(payload.args)
|
|
167
|
-
WsClient.send(Protocol.encode({
|
|
168
|
-
id = msgId,
|
|
169
|
-
type = "studio:response",
|
|
170
|
-
payload = result,
|
|
171
|
-
ts = os.clock(),
|
|
172
|
-
}))
|
|
173
|
-
end)
|
|
174
|
-
elseif msgType == "cli:test:play" then
|
|
175
|
-
-- ExecutePlayModeAsync yields until EndTest is called, so run in a thread
|
|
176
|
-
task.spawn(function()
|
|
177
|
-
local result = TestRunner.executePlayMode(payload.args)
|
|
178
|
-
WsClient.send(Protocol.encode({
|
|
179
|
-
id = msgId,
|
|
180
|
-
type = "studio:response",
|
|
181
|
-
payload = result,
|
|
182
|
-
ts = os.clock(),
|
|
183
|
-
}))
|
|
184
|
-
end)
|
|
185
|
-
elseif msgType == "cli:test:end" then
|
|
186
|
-
local result = TestRunner.endTest(payload.value)
|
|
187
|
-
WsClient.send(Protocol.encode({
|
|
188
|
-
id = msgId,
|
|
189
|
-
type = "studio:response",
|
|
190
|
-
payload = result,
|
|
191
|
-
ts = os.clock(),
|
|
192
|
-
}))
|
|
193
|
-
elseif msgType == "cli:build:ui" then
|
|
194
|
-
local result = UIBuilder.build(payload)
|
|
195
|
-
WsClient.send(Protocol.encode({
|
|
196
|
-
id = msgId,
|
|
197
|
-
type = "studio:response",
|
|
198
|
-
payload = result,
|
|
199
|
-
ts = os.clock(),
|
|
200
|
-
}))
|
|
201
|
-
elseif msgType == "cli:get:reflection" then
|
|
202
|
-
local result = Reflection.getClassInfo(payload.className)
|
|
203
|
-
WsClient.send(Protocol.encode({
|
|
204
|
-
id = msgId,
|
|
205
|
-
type = "studio:response",
|
|
206
|
-
payload = result,
|
|
207
|
-
ts = os.clock(),
|
|
208
|
-
}))
|
|
209
|
-
elseif msgType == "cli:serialize:ui" then
|
|
210
|
-
local result = UIBuilder.serialize(payload)
|
|
211
|
-
WsClient.send(Protocol.encode({
|
|
212
|
-
id = msgId,
|
|
213
|
-
type = "studio:response",
|
|
214
|
-
payload = result,
|
|
215
|
-
ts = os.clock(),
|
|
216
|
-
}))
|
|
217
|
-
elseif msgType == "cli:move:instance" then
|
|
218
|
-
local instance = Explorer.resolveInstance(payload.path)
|
|
219
|
-
if not instance then
|
|
220
|
-
WsClient.send(Protocol.encode({
|
|
221
|
-
id = msgId,
|
|
222
|
-
type = "studio:response",
|
|
223
|
-
payload = { success = false, error = "Instance not found: " .. payload.path },
|
|
224
|
-
ts = os.clock(),
|
|
225
|
-
}))
|
|
226
|
-
else
|
|
227
|
-
local newParent = Explorer.resolveInstance(payload.newParent)
|
|
228
|
-
if not newParent then
|
|
229
|
-
local svcOk, svc = pcall(function()
|
|
230
|
-
return game:GetService(payload.newParent)
|
|
231
|
-
end)
|
|
232
|
-
if svcOk then
|
|
233
|
-
newParent = svc
|
|
234
|
-
end
|
|
235
|
-
end
|
|
236
|
-
if not newParent then
|
|
237
|
-
WsClient.send(Protocol.encode({
|
|
238
|
-
id = msgId,
|
|
239
|
-
type = "studio:response",
|
|
240
|
-
payload = { success = false, error = "New parent not found: " .. payload.newParent },
|
|
241
|
-
ts = os.clock(),
|
|
242
|
-
}))
|
|
243
|
-
else
|
|
244
|
-
local ChangeHistoryService = game:GetService("ChangeHistoryService")
|
|
245
|
-
local recording = ChangeHistoryService:TryBeginRecording("Dominus: Move " .. instance.Name)
|
|
246
|
-
local ok, err = pcall(function()
|
|
247
|
-
instance.Parent = newParent
|
|
248
|
-
end)
|
|
249
|
-
if recording then
|
|
250
|
-
if ok then
|
|
251
|
-
ChangeHistoryService:FinishRecording(recording, Enum.FinishRecordingOperation.Commit)
|
|
252
|
-
else
|
|
253
|
-
ChangeHistoryService:FinishRecording(recording, Enum.FinishRecordingOperation.Cancel)
|
|
254
|
-
end
|
|
255
|
-
end
|
|
256
|
-
if ok then
|
|
257
|
-
WsClient.send(Protocol.encode({
|
|
258
|
-
id = msgId,
|
|
259
|
-
type = "studio:response",
|
|
260
|
-
payload = { success = true, newPath = Explorer.getPath(instance) },
|
|
261
|
-
ts = os.clock(),
|
|
262
|
-
}))
|
|
263
|
-
else
|
|
264
|
-
WsClient.send(Protocol.encode({
|
|
265
|
-
id = msgId,
|
|
266
|
-
type = "studio:response",
|
|
267
|
-
payload = { success = false, error = "Move failed: " .. tostring(err) },
|
|
268
|
-
ts = os.clock(),
|
|
269
|
-
}))
|
|
270
|
-
end
|
|
107
|
+
authenticated = true
|
|
108
|
+
connectButton:SetActive(true)
|
|
109
|
+
log("Authenticated as Studio session " .. payload.connectionId)
|
|
110
|
+
Watcher.start(function(entry)
|
|
111
|
+
if authenticated then
|
|
112
|
+
sendEnvelope({ id = Protocol.generateId(), type = "studio:output", payload = entry, ts = os.time() })
|
|
271
113
|
end
|
|
272
|
-
end
|
|
273
|
-
elseif msgType == "cli:undo" then
|
|
274
|
-
local ChangeHistoryService = game:GetService("ChangeHistoryService")
|
|
275
|
-
local ok, err = pcall(function()
|
|
276
|
-
ChangeHistoryService:Undo()
|
|
277
114
|
end)
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
local ChangeHistoryService = game:GetService("ChangeHistoryService")
|
|
286
|
-
local ok, err = pcall(function()
|
|
287
|
-
ChangeHistoryService:Redo()
|
|
288
|
-
end)
|
|
289
|
-
WsClient.send(Protocol.encode({
|
|
290
|
-
id = msgId,
|
|
291
|
-
type = "studio:response",
|
|
292
|
-
payload = ok and { success = true } or { success = false, error = tostring(err) },
|
|
293
|
-
ts = os.clock(),
|
|
294
|
-
}))
|
|
295
|
-
elseif msgType == "cli:bulk:set:properties" then
|
|
296
|
-
local result = Properties.bulkSet(payload)
|
|
297
|
-
WsClient.send(Protocol.encode({
|
|
298
|
-
id = msgId,
|
|
299
|
-
type = "studio:response",
|
|
300
|
-
payload = result,
|
|
301
|
-
ts = os.clock(),
|
|
302
|
-
}))
|
|
303
|
-
elseif msgType == "cli:find:replace:scripts" then
|
|
304
|
-
local result = Explorer.findReplaceScripts(payload)
|
|
305
|
-
WsClient.send(Protocol.encode({
|
|
306
|
-
id = msgId,
|
|
307
|
-
type = "studio:response",
|
|
308
|
-
payload = result,
|
|
309
|
-
ts = os.clock(),
|
|
310
|
-
}))
|
|
311
|
-
else
|
|
312
|
-
warn("[Dominus] Unknown message type:", msgType)
|
|
115
|
+
return
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
if message.type == "server:auth_rejected" then
|
|
119
|
+
warn("[Dominus 2] Authentication failed. Run dominus-install-plugin and restart Studio.")
|
|
120
|
+
WsClient.disconnect()
|
|
121
|
+
return
|
|
313
122
|
end
|
|
314
|
-
end
|
|
315
123
|
|
|
316
|
-
|
|
317
|
-
|
|
124
|
+
if not authenticated then
|
|
125
|
+
warn("[Dominus 2] Ignored a command before authentication")
|
|
126
|
+
return
|
|
127
|
+
end
|
|
128
|
+
CommandRouter.handle(message, sendResponse)
|
|
129
|
+
end
|
|
318
130
|
|
|
319
|
-
|
|
131
|
+
local function connect()
|
|
132
|
+
if shuttingDown or transportOpen then
|
|
133
|
+
return
|
|
134
|
+
end
|
|
135
|
+
if connecting then
|
|
136
|
+
if shouldReconnect then
|
|
137
|
+
scheduleReconnect()
|
|
138
|
+
end
|
|
139
|
+
return
|
|
140
|
+
end
|
|
141
|
+
connecting = true
|
|
142
|
+
reconnectScheduled = false
|
|
143
|
+
local success = WsClient.connect("ws://127.0.0.1:" .. port, {
|
|
320
144
|
onOpen = function()
|
|
321
|
-
|
|
322
|
-
|
|
145
|
+
transportOpen = true
|
|
146
|
+
reconnectDelay = 1
|
|
323
147
|
connectButton:SetActive(true)
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
local studioVersion = game:GetService("RobloxPluginGuiService") and "Studio" or "Unknown"
|
|
327
|
-
local placeName = "Unknown"
|
|
328
|
-
pcall(function()
|
|
329
|
-
placeName = game:GetService("MarketplaceService"):GetProductInfo(game.PlaceId).Name
|
|
330
|
-
end)
|
|
331
|
-
|
|
332
|
-
WsClient.send(Protocol.encode({
|
|
148
|
+
local sent, sendErr = sendEnvelope({
|
|
333
149
|
id = Protocol.generateId(),
|
|
334
|
-
type = "studio:
|
|
150
|
+
type = "studio:hello",
|
|
335
151
|
payload = {
|
|
336
|
-
|
|
152
|
+
protocolVersion = PROTOCOL_VERSION,
|
|
153
|
+
clientId = clientId,
|
|
154
|
+
token = bridgeToken,
|
|
155
|
+
studioVersion = version(),
|
|
337
156
|
placeId = game.PlaceId,
|
|
338
|
-
placeName =
|
|
157
|
+
placeName = game.Name,
|
|
339
158
|
},
|
|
340
|
-
ts = os.
|
|
341
|
-
})
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
type = "studio:output",
|
|
348
|
-
payload = entry,
|
|
349
|
-
ts = os.clock(),
|
|
350
|
-
}))
|
|
351
|
-
end
|
|
352
|
-
end)
|
|
159
|
+
ts = os.time(),
|
|
160
|
+
})
|
|
161
|
+
if not sent then
|
|
162
|
+
warn("[Dominus 2] Could not send handshake: " .. tostring(sendErr))
|
|
163
|
+
stopSession()
|
|
164
|
+
WsClient.disconnect()
|
|
165
|
+
end
|
|
353
166
|
end,
|
|
354
|
-
|
|
355
167
|
onMessage = function(message)
|
|
356
168
|
task.spawn(handleMessage, message)
|
|
357
169
|
end,
|
|
358
|
-
|
|
359
170
|
onClose = function()
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
171
|
+
local wasOpen = transportOpen
|
|
172
|
+
stopSession()
|
|
173
|
+
if wasOpen and not shuttingDown then
|
|
174
|
+
log("Studio bridge disconnected")
|
|
175
|
+
end
|
|
176
|
+
if shouldReconnect then
|
|
177
|
+
scheduleReconnect()
|
|
178
|
+
end
|
|
364
179
|
end,
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
180
|
+
onError = function(statusCode, errorMessage)
|
|
181
|
+
if transportOpen then
|
|
182
|
+
warn("[Dominus 2] Bridge error " .. tostring(statusCode) .. ": " .. tostring(errorMessage))
|
|
183
|
+
stopSession()
|
|
184
|
+
WsClient.disconnect()
|
|
185
|
+
if shouldReconnect then
|
|
186
|
+
scheduleReconnect()
|
|
187
|
+
end
|
|
188
|
+
end
|
|
368
189
|
end,
|
|
369
|
-
})
|
|
370
|
-
|
|
371
|
-
|
|
190
|
+
}, 4)
|
|
191
|
+
connecting = false
|
|
192
|
+
if not success then
|
|
193
|
+
stopSession()
|
|
194
|
+
if shouldReconnect then
|
|
195
|
+
scheduleReconnect()
|
|
196
|
+
end
|
|
197
|
+
end
|
|
372
198
|
end
|
|
373
199
|
|
|
374
|
-
|
|
375
|
-
if
|
|
376
|
-
|
|
377
|
-
WsClient.disconnect()
|
|
378
|
-
connected = false
|
|
200
|
+
scheduleReconnect = function()
|
|
201
|
+
if reconnectScheduled or shuttingDown or not shouldReconnect then
|
|
202
|
+
return
|
|
379
203
|
end
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
return
|
|
204
|
+
reconnectScheduled = true
|
|
205
|
+
local delaySeconds = reconnectDelay
|
|
206
|
+
reconnectDelay = math.min(reconnectDelay * 2, 30)
|
|
207
|
+
task.delay(delaySeconds, function()
|
|
208
|
+
reconnectScheduled = false
|
|
209
|
+
if shouldReconnect and not shuttingDown then
|
|
210
|
+
task.spawn(connect)
|
|
388
211
|
end
|
|
389
|
-
end
|
|
390
|
-
|
|
391
|
-
warn("[Dominus] No Dominus server found. Is `dominus` or `dominus mcp` running?")
|
|
212
|
+
end)
|
|
392
213
|
end
|
|
393
214
|
|
|
394
215
|
connectButton.Click:Connect(function()
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
216
|
+
shouldReconnect = true
|
|
217
|
+
reconnectDelay = 1
|
|
218
|
+
stopSession()
|
|
219
|
+
WsClient.disconnect()
|
|
220
|
+
log("Reconnecting to the Dominus 2 MCP server")
|
|
221
|
+
task.spawn(function()
|
|
222
|
+
while connecting and not shuttingDown do
|
|
223
|
+
task.wait()
|
|
224
|
+
end
|
|
400
225
|
connect()
|
|
401
|
-
end
|
|
226
|
+
end)
|
|
402
227
|
end)
|
|
403
228
|
|
|
404
|
-
-- Auto-connect on plugin load
|
|
405
229
|
task.delay(1, function()
|
|
406
|
-
connect
|
|
230
|
+
task.spawn(connect)
|
|
407
231
|
end)
|
|
408
232
|
|
|
409
233
|
plugin.Unloading:Connect(function()
|
|
410
|
-
|
|
234
|
+
shuttingDown = true
|
|
235
|
+
shouldReconnect = false
|
|
236
|
+
stopSession()
|
|
411
237
|
WsClient.disconnect()
|
|
412
238
|
end)
|