@chrrxs/robloxstudio-mcp 2.13.0 → 2.15.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/dist/index.js +546 -29
- package/package.json +2 -2
- package/studio-plugin/INSTALLATION.md +1 -0
- package/studio-plugin/MCPInspectorPlugin.rbxmx +1573 -330
- package/studio-plugin/MCPPlugin.rbxmx +633 -27
- package/studio-plugin/src/modules/ClientBroker.ts +77 -2
- package/studio-plugin/src/modules/Communication.ts +7 -0
- package/studio-plugin/src/modules/handlers/SceneAnalysisHandlers.ts +216 -0
- package/studio-plugin/src/modules/handlers/TestHandlers.ts +246 -12
|
@@ -102,9 +102,11 @@ local RunService = _services.RunService
|
|
|
102
102
|
local ServerStorage = _services.ServerStorage
|
|
103
103
|
local RuntimeLogBuffer = TS.import(script, script.Parent, "RuntimeLogBuffer")
|
|
104
104
|
local MemoryHandlers = TS.import(script, script.Parent, "handlers", "MemoryHandlers")
|
|
105
|
+
local SceneAnalysisHandlers = TS.import(script, script.Parent, "handlers", "SceneAnalysisHandlers")
|
|
105
106
|
local CaptureHandlers = TS.import(script, script.Parent, "handlers", "CaptureHandlers")
|
|
106
107
|
local InputHandlers = TS.import(script, script.Parent, "handlers", "InputHandlers")
|
|
107
108
|
local LuauExec = TS.import(script, script.Parent, "LuauExec")
|
|
109
|
+
local StudioTestService = game:GetService("StudioTestService")
|
|
108
110
|
-- Mirror of Communication.computeInstanceId() — duplicated here because the
|
|
109
111
|
-- client broker runs in the play-server DM where it can't easily import from
|
|
110
112
|
-- the edit-side module, and the place identifier must match what the edit-DM
|
|
@@ -158,6 +160,7 @@ end
|
|
|
158
160
|
-- signaling, which works regardless of MCP server state.)
|
|
159
161
|
local MCP_URL = "http://localhost:58741"
|
|
160
162
|
local BROKER_NAME = "__MCPClientBroker"
|
|
163
|
+
local BROKER_OWNER_ATTRIBUTE = "__MCPBrokerOwner"
|
|
161
164
|
-- Endpoints the server-peer broker is allowed to forward to the client peer.
|
|
162
165
|
-- Each requires the client peer's plugin VM (because the buffer / require
|
|
163
166
|
-- cache / etc. lives there) so the server peer alone can't satisfy them.
|
|
@@ -165,6 +168,9 @@ local CLIENT_BROKER_ALLOWED_ENDPOINTS = {
|
|
|
165
168
|
["/api/execute-luau"] = true,
|
|
166
169
|
["/api/get-runtime-logs"] = true,
|
|
167
170
|
["/api/get-memory-breakdown"] = true,
|
|
171
|
+
["/api/get-scene-analysis"] = true,
|
|
172
|
+
["/api/multiplayer-test-state"] = true,
|
|
173
|
+
["/api/multiplayer-test-leave-client"] = true,
|
|
168
174
|
["/api/capture-begin"] = true,
|
|
169
175
|
["/api/simulate-mouse-input"] = true,
|
|
170
176
|
["/api/simulate-keyboard-input"] = true,
|
|
@@ -246,10 +252,81 @@ local function handleGetRuntimeLogs(data)
|
|
|
246
252
|
filter = filter,
|
|
247
253
|
}, "client")
|
|
248
254
|
end
|
|
255
|
+
local function handleMultiplayerTestState()
|
|
256
|
+
local argsOk, args = pcall(function()
|
|
257
|
+
return StudioTestService:GetTestArgs()
|
|
258
|
+
end)
|
|
259
|
+
local canLeaveOk, canLeave = pcall(function()
|
|
260
|
+
return StudioTestService:CanLeaveTest()
|
|
261
|
+
end)
|
|
262
|
+
local _exp = Players:GetPlayers()
|
|
263
|
+
-- ▼ ReadonlyArray.map ▼
|
|
264
|
+
local _newValue = table.create(#_exp)
|
|
265
|
+
local _callback = function(player)
|
|
266
|
+
return {
|
|
267
|
+
name = player.Name,
|
|
268
|
+
userId = player.UserId,
|
|
269
|
+
displayName = player.DisplayName,
|
|
270
|
+
}
|
|
271
|
+
end
|
|
272
|
+
for _k, _v in _exp do
|
|
273
|
+
_newValue[_k] = _callback(_v, _k - 1, _exp)
|
|
274
|
+
end
|
|
275
|
+
-- ▲ ReadonlyArray.map ▲
|
|
276
|
+
local players = _newValue
|
|
277
|
+
table.sort(players, function(a, b)
|
|
278
|
+
return a.name < b.name
|
|
279
|
+
end)
|
|
280
|
+
return {
|
|
281
|
+
success = true,
|
|
282
|
+
peer = "client",
|
|
283
|
+
isRunning = RunService:IsRunning(),
|
|
284
|
+
isRunMode = RunService:IsRunMode(),
|
|
285
|
+
editModeActive = StudioTestService.EditModeActive,
|
|
286
|
+
testArgsOk = argsOk,
|
|
287
|
+
testArgs = if argsOk then args else nil,
|
|
288
|
+
testArgsError = if argsOk then nil else tostring(args),
|
|
289
|
+
players = players,
|
|
290
|
+
playerCount = #players,
|
|
291
|
+
localPlayer = if Players.LocalPlayer then Players.LocalPlayer.Name else nil,
|
|
292
|
+
canLeaveOk = canLeaveOk,
|
|
293
|
+
canLeave = if canLeaveOk then canLeave else false,
|
|
294
|
+
canLeaveError = if canLeaveOk then nil else tostring(canLeave),
|
|
295
|
+
}
|
|
296
|
+
end
|
|
297
|
+
local function handleMultiplayerTestLeaveClient()
|
|
298
|
+
local canLeaveOk, canLeave = pcall(function()
|
|
299
|
+
return StudioTestService:CanLeaveTest()
|
|
300
|
+
end)
|
|
301
|
+
if not canLeaveOk then
|
|
302
|
+
return {
|
|
303
|
+
error = tostring(canLeave),
|
|
304
|
+
canLeaveOk = false,
|
|
305
|
+
}
|
|
306
|
+
end
|
|
307
|
+
if not canLeave then
|
|
308
|
+
return {
|
|
309
|
+
error = "This client cannot leave the current test session.",
|
|
310
|
+
canLeaveOk = true,
|
|
311
|
+
canLeave = false,
|
|
312
|
+
}
|
|
313
|
+
end
|
|
314
|
+
local localPlayer = if Players.LocalPlayer then Players.LocalPlayer.Name else nil
|
|
315
|
+
task.defer(function()
|
|
316
|
+
pcall(function()
|
|
317
|
+
return StudioTestService:LeaveTest()
|
|
318
|
+
end)
|
|
319
|
+
end)
|
|
320
|
+
return {
|
|
321
|
+
success = true,
|
|
322
|
+
message = "Client leave requested.",
|
|
323
|
+
localPlayer = localPlayer,
|
|
324
|
+
}
|
|
325
|
+
end
|
|
249
326
|
local function setupClientBroker()
|
|
250
327
|
local rf = ReplicatedStorage:WaitForChild(BROKER_NAME, 10)
|
|
251
328
|
if not rf or not rf:IsA("RemoteFunction") then
|
|
252
|
-
warn(`[
|
|
329
|
+
warn(`[robloxstudio-mcp] client: {BROKER_NAME} not found`)
|
|
253
330
|
return nil
|
|
254
331
|
end
|
|
255
332
|
rf.OnClientInvoke = function(payload)
|
|
@@ -265,6 +342,15 @@ local function setupClientBroker()
|
|
|
265
342
|
if payload and payload.endpoint == "/api/get-memory-breakdown" then
|
|
266
343
|
return MemoryHandlers.getMemoryBreakdown(payload.data or {})
|
|
267
344
|
end
|
|
345
|
+
if payload and payload.endpoint == "/api/get-scene-analysis" then
|
|
346
|
+
return SceneAnalysisHandlers.getSceneAnalysis(payload.data or {})
|
|
347
|
+
end
|
|
348
|
+
if payload and payload.endpoint == "/api/multiplayer-test-state" then
|
|
349
|
+
return handleMultiplayerTestState()
|
|
350
|
+
end
|
|
351
|
+
if payload and payload.endpoint == "/api/multiplayer-test-leave-client" then
|
|
352
|
+
return handleMultiplayerTestLeaveClient()
|
|
353
|
+
end
|
|
268
354
|
if payload and payload.endpoint == "/api/capture-begin" then
|
|
269
355
|
return CaptureHandlers.captureBegin()
|
|
270
356
|
end
|
|
@@ -282,6 +368,7 @@ local function setupClientBroker()
|
|
|
282
368
|
end
|
|
283
369
|
end
|
|
284
370
|
local proxyByPlayer = {}
|
|
371
|
+
local serverBrokerStarted = false
|
|
285
372
|
local function pollProxy(proxyId, player, rf)
|
|
286
373
|
while true do
|
|
287
374
|
local _condition = player.Parent ~= nil
|
|
@@ -371,7 +458,7 @@ local function registerProxy(player, rf)
|
|
|
371
458
|
isRunning = RunService:IsRunning(),
|
|
372
459
|
})
|
|
373
460
|
if not ok or not res or not res.Success then
|
|
374
|
-
warn(`[
|
|
461
|
+
warn(`[robloxstudio-mcp] proxy register failed for {player.Name}`)
|
|
375
462
|
return nil
|
|
376
463
|
end
|
|
377
464
|
local body = HttpService:JSONDecode(res.Body)
|
|
@@ -393,12 +480,20 @@ end
|
|
|
393
480
|
-- plugin:SetSetting flag consumed by StopPlayMonitor in the play-server DM,
|
|
394
481
|
-- which doesn't depend on MCP server state or peer registration at all.)
|
|
395
482
|
local function setupServerBroker()
|
|
483
|
+
if serverBrokerStarted then
|
|
484
|
+
return nil
|
|
485
|
+
end
|
|
396
486
|
local rf = ReplicatedStorage:FindFirstChild(BROKER_NAME)
|
|
397
487
|
if not rf then
|
|
398
488
|
rf = Instance.new("RemoteFunction")
|
|
399
489
|
rf.Name = BROKER_NAME
|
|
400
490
|
rf.Parent = ReplicatedStorage
|
|
401
491
|
end
|
|
492
|
+
if rf:GetAttribute(BROKER_OWNER_ATTRIBUTE) ~= nil then
|
|
493
|
+
return nil
|
|
494
|
+
end
|
|
495
|
+
rf:SetAttribute(BROKER_OWNER_ATTRIBUTE, HttpService:GenerateGUID(false))
|
|
496
|
+
serverBrokerStarted = true
|
|
402
497
|
local broker = rf
|
|
403
498
|
Players.PlayerAdded:Connect(function(p)
|
|
404
499
|
return registerProxy(p, broker)
|
|
@@ -461,6 +556,7 @@ local InputHandlers = TS.import(script, script.Parent, "handlers", "InputHandler
|
|
|
461
556
|
local LogHandlers = TS.import(script, script.Parent, "handlers", "LogHandlers")
|
|
462
557
|
local SerializationHandlers = TS.import(script, script.Parent, "handlers", "SerializationHandlers")
|
|
463
558
|
local MemoryHandlers = TS.import(script, script.Parent, "handlers", "MemoryHandlers")
|
|
559
|
+
local SceneAnalysisHandlers = TS.import(script, script.Parent, "handlers", "SceneAnalysisHandlers")
|
|
464
560
|
-- Per-plugin-load random GUID. Used as the /poll URL param so the server
|
|
465
561
|
-- can tell our polls apart from any other plugin's polls. Not user-facing —
|
|
466
562
|
-- MCP tools and the LLM operate on instanceId (the place identifier).
|
|
@@ -571,6 +667,11 @@ local routeMap = {
|
|
|
571
667
|
["/api/start-playtest"] = TestHandlers.startPlaytest,
|
|
572
668
|
["/api/stop-playtest"] = TestHandlers.stopPlaytest,
|
|
573
669
|
["/api/get-playtest-output"] = TestHandlers.getPlaytestOutput,
|
|
670
|
+
["/api/multiplayer-test-start"] = TestHandlers.multiplayerTestStart,
|
|
671
|
+
["/api/multiplayer-test-state"] = TestHandlers.multiplayerTestState,
|
|
672
|
+
["/api/multiplayer-test-add-players"] = TestHandlers.multiplayerTestAddPlayers,
|
|
673
|
+
["/api/multiplayer-test-leave-client"] = TestHandlers.multiplayerTestLeaveClient,
|
|
674
|
+
["/api/multiplayer-test-end"] = TestHandlers.multiplayerTestEnd,
|
|
574
675
|
["/api/character-navigation"] = TestHandlers.characterNavigation,
|
|
575
676
|
["/api/export-build"] = BuildHandlers.exportBuild,
|
|
576
677
|
["/api/import-build"] = BuildHandlers.importBuild,
|
|
@@ -588,6 +689,7 @@ local routeMap = {
|
|
|
588
689
|
["/api/export-rbxm"] = SerializationHandlers.exportRbxm,
|
|
589
690
|
["/api/import-rbxm"] = SerializationHandlers.importRbxm,
|
|
590
691
|
["/api/get-memory-breakdown"] = MemoryHandlers.getMemoryBreakdown,
|
|
692
|
+
["/api/get-scene-analysis"] = SceneAnalysisHandlers.getSceneAnalysis,
|
|
591
693
|
}
|
|
592
694
|
local function processRequest(request)
|
|
593
695
|
local endpoint = request.endpoint
|
|
@@ -1161,9 +1263,9 @@ local function computeBridgeStamp()
|
|
|
1161
1263
|
for i = 1, #combined do
|
|
1162
1264
|
h = (h * 33 + (string.byte(combined, i))) % 2147483647
|
|
1163
1265
|
end
|
|
1164
|
-
-- "2.
|
|
1266
|
+
-- "2.15.0" is replaced with the package version at package time
|
|
1165
1267
|
-- (scripts/build-plugin.mjs injectVersion), so a release bump also restamps.
|
|
1166
|
-
return `{tostring(h)}-2.
|
|
1268
|
+
return `{tostring(h)}-2.15.0`
|
|
1167
1269
|
end
|
|
1168
1270
|
local BRIDGE_STAMP = computeBridgeStamp()
|
|
1169
1271
|
local function setSource(scriptInst, source)
|
|
@@ -4975,6 +5077,255 @@ return {
|
|
|
4975
5077
|
</Properties>
|
|
4976
5078
|
</Item>
|
|
4977
5079
|
<Item class="ModuleScript" referent="16">
|
|
5080
|
+
<Properties>
|
|
5081
|
+
<string name="Name">SceneAnalysisHandlers</string>
|
|
5082
|
+
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
5083
|
+
local MODE_CONFIGS = {
|
|
5084
|
+
instance_composition = {
|
|
5085
|
+
method = "GetInstanceCompositionAsync",
|
|
5086
|
+
query = function(service)
|
|
5087
|
+
return service:GetInstanceCompositionAsync()
|
|
5088
|
+
end,
|
|
5089
|
+
},
|
|
5090
|
+
script_memory = {
|
|
5091
|
+
method = "GetScriptMemoryAsync",
|
|
5092
|
+
query = function(service)
|
|
5093
|
+
return service:GetScriptMemoryAsync()
|
|
5094
|
+
end,
|
|
5095
|
+
},
|
|
5096
|
+
unparented_instances = {
|
|
5097
|
+
method = "GetUnparentedInstancesAsync",
|
|
5098
|
+
query = function(service)
|
|
5099
|
+
return service:GetUnparentedInstancesAsync()
|
|
5100
|
+
end,
|
|
5101
|
+
},
|
|
5102
|
+
triangle_composition = {
|
|
5103
|
+
method = "GetTriangleCompositionAsync",
|
|
5104
|
+
query = function(service)
|
|
5105
|
+
return service:GetTriangleCompositionAsync()
|
|
5106
|
+
end,
|
|
5107
|
+
sortByTriangles = true,
|
|
5108
|
+
},
|
|
5109
|
+
animation_memory = {
|
|
5110
|
+
method = "GetAnimationMemoryAsync",
|
|
5111
|
+
query = function(service)
|
|
5112
|
+
return service:GetAnimationMemoryAsync()
|
|
5113
|
+
end,
|
|
5114
|
+
},
|
|
5115
|
+
audio_memory = {
|
|
5116
|
+
method = "GetAudioMemoryAsync",
|
|
5117
|
+
query = function(service)
|
|
5118
|
+
return service:GetAudioMemoryAsync()
|
|
5119
|
+
end,
|
|
5120
|
+
},
|
|
5121
|
+
}
|
|
5122
|
+
local ALL_MODES = { "instance_composition", "script_memory", "unparented_instances", "triangle_composition", "animation_memory", "audio_memory" }
|
|
5123
|
+
local function betaDisabledError()
|
|
5124
|
+
return {
|
|
5125
|
+
error = "scene_analysis_not_enabled",
|
|
5126
|
+
message = "SceneAnalysisService is not enabled. Enable Scene Analysis in Studio Beta Features and restart Studio.",
|
|
5127
|
+
betaFeatureRequired = true,
|
|
5128
|
+
}
|
|
5129
|
+
end
|
|
5130
|
+
local function isBetaDisabledError(value)
|
|
5131
|
+
local _value = value
|
|
5132
|
+
local _condition = type(_value) == "string"
|
|
5133
|
+
if _condition then
|
|
5134
|
+
_condition = (string.find(value, "SceneAnalysisService is not enabled", 1, true)) ~= nil
|
|
5135
|
+
end
|
|
5136
|
+
return _condition
|
|
5137
|
+
end
|
|
5138
|
+
local function getSceneAnalysisService()
|
|
5139
|
+
local provider = game
|
|
5140
|
+
local ok, service = pcall(function()
|
|
5141
|
+
return provider:GetService("SceneAnalysisService")
|
|
5142
|
+
end)
|
|
5143
|
+
if not ok or not service then
|
|
5144
|
+
return {
|
|
5145
|
+
error = "scene_analysis_unavailable",
|
|
5146
|
+
message = `SceneAnalysisService is unavailable: {tostring(service)}`,
|
|
5147
|
+
}
|
|
5148
|
+
end
|
|
5149
|
+
return service
|
|
5150
|
+
end
|
|
5151
|
+
local function normalizeMode(mode)
|
|
5152
|
+
if mode == nil or mode == "all" then
|
|
5153
|
+
return "all"
|
|
5154
|
+
end
|
|
5155
|
+
local _mode = mode
|
|
5156
|
+
local _condition = not (type(_mode) == "string")
|
|
5157
|
+
if not _condition then
|
|
5158
|
+
_condition = MODE_CONFIGS[mode] == nil
|
|
5159
|
+
end
|
|
5160
|
+
if _condition then
|
|
5161
|
+
return {
|
|
5162
|
+
error = "invalid_mode",
|
|
5163
|
+
message = `mode must be one of: all, {table.concat(ALL_MODES, ", ")}`,
|
|
5164
|
+
}
|
|
5165
|
+
end
|
|
5166
|
+
return mode
|
|
5167
|
+
end
|
|
5168
|
+
local function normalizeTopN(topN)
|
|
5169
|
+
local _topN = topN
|
|
5170
|
+
if not (type(_topN) == "number") then
|
|
5171
|
+
return 10
|
|
5172
|
+
end
|
|
5173
|
+
return math.clamp(math.floor(topN), 1, 100)
|
|
5174
|
+
end
|
|
5175
|
+
local function countLeaves(node)
|
|
5176
|
+
local children = node.Children
|
|
5177
|
+
if children and #children > 0 then
|
|
5178
|
+
local total = 0
|
|
5179
|
+
for _, child in children do
|
|
5180
|
+
total += countLeaves(child)
|
|
5181
|
+
end
|
|
5182
|
+
return total
|
|
5183
|
+
end
|
|
5184
|
+
return 1
|
|
5185
|
+
end
|
|
5186
|
+
local function flattenLeaves(node, out)
|
|
5187
|
+
local children = node.Children
|
|
5188
|
+
if children and #children > 0 then
|
|
5189
|
+
for _, child in children do
|
|
5190
|
+
flattenLeaves(child, out)
|
|
5191
|
+
end
|
|
5192
|
+
return nil
|
|
5193
|
+
end
|
|
5194
|
+
local _out = out
|
|
5195
|
+
local _node = node
|
|
5196
|
+
table.insert(_out, _node)
|
|
5197
|
+
end
|
|
5198
|
+
local function compactEntry(node)
|
|
5199
|
+
local entry = {
|
|
5200
|
+
name = node.Name,
|
|
5201
|
+
}
|
|
5202
|
+
if node.Size ~= nil then
|
|
5203
|
+
entry.size = node.Size
|
|
5204
|
+
end
|
|
5205
|
+
if node.Sizes ~= nil then
|
|
5206
|
+
entry.sizes = node.Sizes
|
|
5207
|
+
end
|
|
5208
|
+
if node.AssetId ~= nil then
|
|
5209
|
+
entry.asset_id = node.AssetId
|
|
5210
|
+
end
|
|
5211
|
+
return entry
|
|
5212
|
+
end
|
|
5213
|
+
local function compactRoot(node, leafCount)
|
|
5214
|
+
local children = node.Children
|
|
5215
|
+
local root = {
|
|
5216
|
+
name = node.Name,
|
|
5217
|
+
child_count = if children then #children else 0,
|
|
5218
|
+
leaf_count = leafCount,
|
|
5219
|
+
}
|
|
5220
|
+
if node.Size ~= nil then
|
|
5221
|
+
root.size = node.Size
|
|
5222
|
+
end
|
|
5223
|
+
if node.Sizes ~= nil then
|
|
5224
|
+
root.sizes = node.Sizes
|
|
5225
|
+
end
|
|
5226
|
+
return root
|
|
5227
|
+
end
|
|
5228
|
+
local function metric(node, sortByTriangles)
|
|
5229
|
+
if sortByTriangles then
|
|
5230
|
+
local sizes = node.Sizes
|
|
5231
|
+
local triangles = if sizes then sizes.Triangles else nil
|
|
5232
|
+
local _condition = triangles
|
|
5233
|
+
if _condition == nil then
|
|
5234
|
+
_condition = 0
|
|
5235
|
+
end
|
|
5236
|
+
return _condition
|
|
5237
|
+
end
|
|
5238
|
+
local _condition = node.Size
|
|
5239
|
+
if _condition == nil then
|
|
5240
|
+
_condition = 0
|
|
5241
|
+
end
|
|
5242
|
+
return _condition
|
|
5243
|
+
end
|
|
5244
|
+
local function summarizeMode(mode, config, service, topN, raw)
|
|
5245
|
+
local started = os.clock()
|
|
5246
|
+
local ok, result = pcall(function()
|
|
5247
|
+
return config.query(service)
|
|
5248
|
+
end)
|
|
5249
|
+
local elapsedMs = math.floor((os.clock() - started) * 1000)
|
|
5250
|
+
if not ok then
|
|
5251
|
+
if isBetaDisabledError(result) then
|
|
5252
|
+
return betaDisabledError()
|
|
5253
|
+
end
|
|
5254
|
+
return {
|
|
5255
|
+
error = "scene_analysis_query_failed",
|
|
5256
|
+
mode = mode,
|
|
5257
|
+
method = config.method,
|
|
5258
|
+
message = tostring(result),
|
|
5259
|
+
}
|
|
5260
|
+
end
|
|
5261
|
+
local tree = result
|
|
5262
|
+
local leaves = {}
|
|
5263
|
+
flattenLeaves(tree, leaves)
|
|
5264
|
+
table.sort(leaves, function(a, b)
|
|
5265
|
+
return metric(a, config.sortByTriangles == true) > metric(b, config.sortByTriangles == true)
|
|
5266
|
+
end)
|
|
5267
|
+
local top = {}
|
|
5268
|
+
do
|
|
5269
|
+
local i = 0
|
|
5270
|
+
local _shouldIncrement = false
|
|
5271
|
+
while true do
|
|
5272
|
+
if _shouldIncrement then
|
|
5273
|
+
i += 1
|
|
5274
|
+
else
|
|
5275
|
+
_shouldIncrement = true
|
|
5276
|
+
end
|
|
5277
|
+
if not (i < math.min(topN, #leaves)) then
|
|
5278
|
+
break
|
|
5279
|
+
end
|
|
5280
|
+
local _arg0 = compactEntry(leaves[i + 1])
|
|
5281
|
+
table.insert(top, _arg0)
|
|
5282
|
+
end
|
|
5283
|
+
end
|
|
5284
|
+
local body = {
|
|
5285
|
+
mode = mode,
|
|
5286
|
+
method = config.method,
|
|
5287
|
+
elapsed_ms = elapsedMs,
|
|
5288
|
+
root = compactRoot(tree, #leaves),
|
|
5289
|
+
top = top,
|
|
5290
|
+
}
|
|
5291
|
+
if raw then
|
|
5292
|
+
body.tree = tree
|
|
5293
|
+
end
|
|
5294
|
+
return body
|
|
5295
|
+
end
|
|
5296
|
+
local function getSceneAnalysis(requestData)
|
|
5297
|
+
local mode = normalizeMode(requestData.mode)
|
|
5298
|
+
if not (type(mode) == "string") then
|
|
5299
|
+
return mode
|
|
5300
|
+
end
|
|
5301
|
+
local serviceOrError = getSceneAnalysisService()
|
|
5302
|
+
local _value = serviceOrError.IsA
|
|
5303
|
+
if not (_value ~= 0 and _value == _value and _value ~= "" and _value) then
|
|
5304
|
+
return serviceOrError
|
|
5305
|
+
end
|
|
5306
|
+
local service = serviceOrError
|
|
5307
|
+
local topN = normalizeTopN(requestData.topN)
|
|
5308
|
+
local raw = requestData.raw == true
|
|
5309
|
+
if mode ~= "all" then
|
|
5310
|
+
return summarizeMode(mode, MODE_CONFIGS[mode], service, topN, raw)
|
|
5311
|
+
end
|
|
5312
|
+
local body = {}
|
|
5313
|
+
for _, m in ALL_MODES do
|
|
5314
|
+
local result = summarizeMode(m, MODE_CONFIGS[m], service, topN, raw)
|
|
5315
|
+
if result.error == "scene_analysis_not_enabled" then
|
|
5316
|
+
return result
|
|
5317
|
+
end
|
|
5318
|
+
body[m] = result
|
|
5319
|
+
end
|
|
5320
|
+
return body
|
|
5321
|
+
end
|
|
5322
|
+
return {
|
|
5323
|
+
getSceneAnalysis = getSceneAnalysis,
|
|
5324
|
+
}
|
|
5325
|
+
]]></string>
|
|
5326
|
+
</Properties>
|
|
5327
|
+
</Item>
|
|
5328
|
+
<Item class="ModuleScript" referent="17">
|
|
4978
5329
|
<Properties>
|
|
4979
5330
|
<string name="Name">ScriptHandlers</string>
|
|
4980
5331
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -5670,7 +6021,7 @@ return {
|
|
|
5670
6021
|
]]></string>
|
|
5671
6022
|
</Properties>
|
|
5672
6023
|
</Item>
|
|
5673
|
-
<Item class="ModuleScript" referent="
|
|
6024
|
+
<Item class="ModuleScript" referent="18">
|
|
5674
6025
|
<Properties>
|
|
5675
6026
|
<string name="Name">SerializationHandlers</string>
|
|
5676
6027
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -5856,7 +6207,7 @@ return {
|
|
|
5856
6207
|
]]></string>
|
|
5857
6208
|
</Properties>
|
|
5858
6209
|
</Item>
|
|
5859
|
-
<Item class="ModuleScript" referent="
|
|
6210
|
+
<Item class="ModuleScript" referent="19">
|
|
5860
6211
|
<Properties>
|
|
5861
6212
|
<string name="Name">TestHandlers</string>
|
|
5862
6213
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -5864,6 +6215,7 @@ local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
|
|
|
5864
6215
|
local _services = TS.import(script, script.Parent.Parent.Parent, "node_modules", "@rbxts", "services")
|
|
5865
6216
|
local HttpService = _services.HttpService
|
|
5866
6217
|
local LogService = _services.LogService
|
|
6218
|
+
local Players = _services.Players
|
|
5867
6219
|
local RunService = _services.RunService
|
|
5868
6220
|
local _EvalBridges = TS.import(script, script.Parent.Parent, "EvalBridges")
|
|
5869
6221
|
local installBridges = _EvalBridges.installBridges
|
|
@@ -5885,6 +6237,63 @@ local testResult
|
|
|
5885
6237
|
local testError
|
|
5886
6238
|
local stopListenerScript
|
|
5887
6239
|
local navResultCallback
|
|
6240
|
+
local multiplayerState = {
|
|
6241
|
+
phase = "idle",
|
|
6242
|
+
}
|
|
6243
|
+
local function detectPeerRole()
|
|
6244
|
+
if not RunService:IsRunning() then
|
|
6245
|
+
return "edit"
|
|
6246
|
+
end
|
|
6247
|
+
if RunService:IsServer() then
|
|
6248
|
+
return "server"
|
|
6249
|
+
end
|
|
6250
|
+
return "client"
|
|
6251
|
+
end
|
|
6252
|
+
local function getPlayersSnapshot()
|
|
6253
|
+
local _exp = Players:GetPlayers()
|
|
6254
|
+
-- ▼ ReadonlyArray.map ▼
|
|
6255
|
+
local _newValue = table.create(#_exp)
|
|
6256
|
+
local _callback = function(player)
|
|
6257
|
+
return {
|
|
6258
|
+
name = player.Name,
|
|
6259
|
+
userId = player.UserId,
|
|
6260
|
+
displayName = player.DisplayName,
|
|
6261
|
+
}
|
|
6262
|
+
end
|
|
6263
|
+
for _k, _v in _exp do
|
|
6264
|
+
_newValue[_k] = _callback(_v, _k - 1, _exp)
|
|
6265
|
+
end
|
|
6266
|
+
-- ▲ ReadonlyArray.map ▲
|
|
6267
|
+
local players = _newValue
|
|
6268
|
+
table.sort(players, function(a, b)
|
|
6269
|
+
return a.name < b.name
|
|
6270
|
+
end)
|
|
6271
|
+
return players
|
|
6272
|
+
end
|
|
6273
|
+
local function cloneMultiplayerState()
|
|
6274
|
+
return {
|
|
6275
|
+
phase = multiplayerState.phase,
|
|
6276
|
+
testId = multiplayerState.testId,
|
|
6277
|
+
numPlayers = multiplayerState.numPlayers,
|
|
6278
|
+
testArgs = multiplayerState.testArgs,
|
|
6279
|
+
startedAt = multiplayerState.startedAt,
|
|
6280
|
+
completedAt = multiplayerState.completedAt,
|
|
6281
|
+
ok = multiplayerState.ok,
|
|
6282
|
+
result = multiplayerState.result,
|
|
6283
|
+
error = multiplayerState.error,
|
|
6284
|
+
}
|
|
6285
|
+
end
|
|
6286
|
+
local function normalizeNumPlayers(value)
|
|
6287
|
+
local _value = value
|
|
6288
|
+
if not (type(_value) == "number") then
|
|
6289
|
+
return nil
|
|
6290
|
+
end
|
|
6291
|
+
local n = math.floor(value)
|
|
6292
|
+
if n ~= value or n < 1 or n > 8 then
|
|
6293
|
+
return nil
|
|
6294
|
+
end
|
|
6295
|
+
return n
|
|
6296
|
+
end
|
|
5888
6297
|
local function buildCommandListenerSource()
|
|
5889
6298
|
return `local LogService = game:GetService("LogService")\
|
|
5890
6299
|
local PathfindingService = game:GetService("PathfindingService")\
|
|
@@ -5981,6 +6390,11 @@ local function startPlaytest(requestData)
|
|
|
5981
6390
|
error = 'mode must be "play" or "run"',
|
|
5982
6391
|
}
|
|
5983
6392
|
end
|
|
6393
|
+
if numPlayers ~= nil then
|
|
6394
|
+
return {
|
|
6395
|
+
error = "start_playtest is single-player only. Use multiplayer_test_start for multi-client StudioTestService sessions.",
|
|
6396
|
+
}
|
|
6397
|
+
end
|
|
5984
6398
|
-- Self-heal: if testRunning is stuck true but Studio reports no active
|
|
5985
6399
|
-- playtest, the previous start_playtest's task.spawn was orphaned
|
|
5986
6400
|
-- (plugin reload mid-test, Studio entered some inconsistent state, etc).
|
|
@@ -6045,10 +6459,6 @@ local function startPlaytest(requestData)
|
|
|
6045
6459
|
if not bridgeInstall.installed then
|
|
6046
6460
|
warn(`[MCP] Eval bridge install failed: {bridgeInstall.error}`)
|
|
6047
6461
|
end
|
|
6048
|
-
if numPlayers ~= nil and mode == "run" then
|
|
6049
|
-
local TestService = game:GetService("TestService")
|
|
6050
|
-
TestService.NumberOfPlayers = math.clamp(numPlayers, 1, 8)
|
|
6051
|
-
end
|
|
6052
6462
|
task.spawn(function()
|
|
6053
6463
|
local ok, result = pcall(function()
|
|
6054
6464
|
if mode == "play" then
|
|
@@ -6071,10 +6481,9 @@ local function startPlaytest(requestData)
|
|
|
6071
6481
|
-- clean up here, so the next manual playtest still gets them.
|
|
6072
6482
|
ensureBridgesInstalled()
|
|
6073
6483
|
end)
|
|
6074
|
-
local msg = if numPlayers ~= nil then `Playtest started in {mode} mode with {numPlayers} player(s).` else `Playtest started in {mode} mode.`
|
|
6075
6484
|
local response = {
|
|
6076
6485
|
success = true,
|
|
6077
|
-
message =
|
|
6486
|
+
message = `Playtest started in {mode} mode.`,
|
|
6078
6487
|
}
|
|
6079
6488
|
-- Only mention eval bridges when they failed — when they're fine, the
|
|
6080
6489
|
-- detail is noise. eval_server_runtime / eval_client_runtime will surface
|
|
@@ -6157,6 +6566,198 @@ local function getPlaytestOutput(_requestData)
|
|
|
6157
6566
|
_object.testError = testError
|
|
6158
6567
|
return _object
|
|
6159
6568
|
end
|
|
6569
|
+
local function multiplayerTestStart(requestData)
|
|
6570
|
+
if RunService:IsRunning() then
|
|
6571
|
+
return {
|
|
6572
|
+
error = "multiplayer_test_start must be called on the edit DataModel. Route with target=edit.",
|
|
6573
|
+
}
|
|
6574
|
+
end
|
|
6575
|
+
local numPlayers = normalizeNumPlayers(requestData.numPlayers)
|
|
6576
|
+
if numPlayers == nil then
|
|
6577
|
+
return {
|
|
6578
|
+
error = "numPlayers must be an integer from 1 to 8",
|
|
6579
|
+
}
|
|
6580
|
+
end
|
|
6581
|
+
if multiplayerState.phase == "starting" or multiplayerState.phase == "running" then
|
|
6582
|
+
return {
|
|
6583
|
+
error = "A multiplayer Studio test is already running",
|
|
6584
|
+
state = cloneMultiplayerState(),
|
|
6585
|
+
}
|
|
6586
|
+
end
|
|
6587
|
+
local testArgs = if requestData.testArgs ~= nil then requestData.testArgs else {}
|
|
6588
|
+
local testId = HttpService:GenerateGUID(false)
|
|
6589
|
+
local bridgeInstall = installBridges()
|
|
6590
|
+
if not bridgeInstall.installed then
|
|
6591
|
+
warn(`[MCP] Eval bridge install failed: {bridgeInstall.error}`)
|
|
6592
|
+
end
|
|
6593
|
+
multiplayerState = {
|
|
6594
|
+
phase = "starting",
|
|
6595
|
+
testId = testId,
|
|
6596
|
+
numPlayers = numPlayers,
|
|
6597
|
+
testArgs = testArgs,
|
|
6598
|
+
startedAt = tick(),
|
|
6599
|
+
}
|
|
6600
|
+
task.spawn(function()
|
|
6601
|
+
multiplayerState.phase = "running"
|
|
6602
|
+
local ok, result = pcall(function()
|
|
6603
|
+
return StudioTestService:ExecuteMultiplayerTestAsync(numPlayers, testArgs)
|
|
6604
|
+
end)
|
|
6605
|
+
multiplayerState.completedAt = tick()
|
|
6606
|
+
multiplayerState.ok = ok
|
|
6607
|
+
if ok then
|
|
6608
|
+
multiplayerState.phase = "completed"
|
|
6609
|
+
multiplayerState.result = result
|
|
6610
|
+
multiplayerState.error = nil
|
|
6611
|
+
else
|
|
6612
|
+
multiplayerState.phase = "failed"
|
|
6613
|
+
multiplayerState.result = nil
|
|
6614
|
+
multiplayerState.error = tostring(result)
|
|
6615
|
+
end
|
|
6616
|
+
ensureBridgesInstalled()
|
|
6617
|
+
end)
|
|
6618
|
+
local response = {
|
|
6619
|
+
success = true,
|
|
6620
|
+
message = `Multiplayer Studio test starting with {numPlayers} player(s).`,
|
|
6621
|
+
testId = testId,
|
|
6622
|
+
phase = multiplayerState.phase,
|
|
6623
|
+
numPlayers = numPlayers,
|
|
6624
|
+
testArgs = testArgs,
|
|
6625
|
+
}
|
|
6626
|
+
if not bridgeInstall.installed then
|
|
6627
|
+
response.evalBridgesError = bridgeInstall.error
|
|
6628
|
+
end
|
|
6629
|
+
return response
|
|
6630
|
+
end
|
|
6631
|
+
local function multiplayerTestState(_requestData)
|
|
6632
|
+
local peer = detectPeerRole()
|
|
6633
|
+
local response = {
|
|
6634
|
+
success = true,
|
|
6635
|
+
peer = peer,
|
|
6636
|
+
isRunning = RunService:IsRunning(),
|
|
6637
|
+
isRunMode = RunService:IsRunMode(),
|
|
6638
|
+
editModeActive = StudioTestService.EditModeActive,
|
|
6639
|
+
}
|
|
6640
|
+
if peer == "edit" then
|
|
6641
|
+
response.session = cloneMultiplayerState()
|
|
6642
|
+
return response
|
|
6643
|
+
end
|
|
6644
|
+
local argsOk, args = pcall(function()
|
|
6645
|
+
return StudioTestService:GetTestArgs()
|
|
6646
|
+
end)
|
|
6647
|
+
response.testArgsOk = argsOk
|
|
6648
|
+
response.testArgs = if argsOk then args else nil
|
|
6649
|
+
if not argsOk then
|
|
6650
|
+
response.testArgsError = tostring(args)
|
|
6651
|
+
end
|
|
6652
|
+
local players = getPlayersSnapshot()
|
|
6653
|
+
response.players = players
|
|
6654
|
+
response.playerCount = #players
|
|
6655
|
+
if peer == "client" then
|
|
6656
|
+
response.localPlayer = if Players.LocalPlayer then Players.LocalPlayer.Name else nil
|
|
6657
|
+
local canLeaveOk, canLeave = pcall(function()
|
|
6658
|
+
return StudioTestService:CanLeaveTest()
|
|
6659
|
+
end)
|
|
6660
|
+
response.canLeaveOk = canLeaveOk
|
|
6661
|
+
response.canLeave = if canLeaveOk then canLeave else false
|
|
6662
|
+
if not canLeaveOk then
|
|
6663
|
+
response.canLeaveError = tostring(canLeave)
|
|
6664
|
+
end
|
|
6665
|
+
end
|
|
6666
|
+
return response
|
|
6667
|
+
end
|
|
6668
|
+
local function multiplayerTestAddPlayers(requestData)
|
|
6669
|
+
if not RunService:IsRunning() or not RunService:IsServer() then
|
|
6670
|
+
return {
|
|
6671
|
+
error = "multiplayer_test_add_players must be called on the running server peer. Route with target=server.",
|
|
6672
|
+
}
|
|
6673
|
+
end
|
|
6674
|
+
local numPlayers = normalizeNumPlayers(requestData.numPlayers)
|
|
6675
|
+
if numPlayers == nil then
|
|
6676
|
+
return {
|
|
6677
|
+
error = "numPlayers must be an integer from 1 to 8",
|
|
6678
|
+
}
|
|
6679
|
+
end
|
|
6680
|
+
local before = #Players:GetPlayers()
|
|
6681
|
+
local ok, result = pcall(function()
|
|
6682
|
+
return StudioTestService:AddPlayers(numPlayers)
|
|
6683
|
+
end)
|
|
6684
|
+
if not ok then
|
|
6685
|
+
return {
|
|
6686
|
+
error = tostring(result),
|
|
6687
|
+
}
|
|
6688
|
+
end
|
|
6689
|
+
local _exp = tick()
|
|
6690
|
+
local _condition = (requestData.timeout)
|
|
6691
|
+
if _condition == nil then
|
|
6692
|
+
_condition = 10
|
|
6693
|
+
end
|
|
6694
|
+
local deadline = _exp + _condition
|
|
6695
|
+
while #Players:GetPlayers() < before + numPlayers and tick() < deadline do
|
|
6696
|
+
task.wait(0.1)
|
|
6697
|
+
end
|
|
6698
|
+
local players = getPlayersSnapshot()
|
|
6699
|
+
return {
|
|
6700
|
+
success = true,
|
|
6701
|
+
message = `Requested {numPlayers} additional player(s).`,
|
|
6702
|
+
playerCount = #players,
|
|
6703
|
+
players = players,
|
|
6704
|
+
}
|
|
6705
|
+
end
|
|
6706
|
+
local function multiplayerTestLeaveClient(_requestData)
|
|
6707
|
+
if not RunService:IsRunning() or RunService:IsServer() then
|
|
6708
|
+
return {
|
|
6709
|
+
error = "multiplayer_test_leave_client must be called on a running client peer. Route with target=client-N.",
|
|
6710
|
+
}
|
|
6711
|
+
end
|
|
6712
|
+
local canLeaveOk, canLeave = pcall(function()
|
|
6713
|
+
return StudioTestService:CanLeaveTest()
|
|
6714
|
+
end)
|
|
6715
|
+
if not canLeaveOk then
|
|
6716
|
+
return {
|
|
6717
|
+
error = tostring(canLeave),
|
|
6718
|
+
canLeaveOk = false,
|
|
6719
|
+
}
|
|
6720
|
+
end
|
|
6721
|
+
if not canLeave then
|
|
6722
|
+
return {
|
|
6723
|
+
error = "This client cannot leave the current test session.",
|
|
6724
|
+
canLeaveOk = true,
|
|
6725
|
+
canLeave = false,
|
|
6726
|
+
}
|
|
6727
|
+
end
|
|
6728
|
+
local localPlayer = if Players.LocalPlayer then Players.LocalPlayer.Name else nil
|
|
6729
|
+
task.defer(function()
|
|
6730
|
+
pcall(function()
|
|
6731
|
+
return StudioTestService:LeaveTest()
|
|
6732
|
+
end)
|
|
6733
|
+
end)
|
|
6734
|
+
return {
|
|
6735
|
+
success = true,
|
|
6736
|
+
message = "Client leave requested.",
|
|
6737
|
+
localPlayer = localPlayer,
|
|
6738
|
+
}
|
|
6739
|
+
end
|
|
6740
|
+
local function multiplayerTestEnd(requestData)
|
|
6741
|
+
if not RunService:IsRunning() or not RunService:IsServer() then
|
|
6742
|
+
return {
|
|
6743
|
+
error = "multiplayer_test_end must be called on the running server peer. Route with target=server.",
|
|
6744
|
+
}
|
|
6745
|
+
end
|
|
6746
|
+
local value = if requestData.value ~= nil then requestData.value else "ended_by_mcp"
|
|
6747
|
+
local ok, result = pcall(function()
|
|
6748
|
+
return StudioTestService:EndTest(value)
|
|
6749
|
+
end)
|
|
6750
|
+
if not ok then
|
|
6751
|
+
return {
|
|
6752
|
+
error = tostring(result),
|
|
6753
|
+
}
|
|
6754
|
+
end
|
|
6755
|
+
return {
|
|
6756
|
+
success = true,
|
|
6757
|
+
message = "Multiplayer Studio test end requested.",
|
|
6758
|
+
value = value,
|
|
6759
|
+
}
|
|
6760
|
+
end
|
|
6160
6761
|
local function characterNavigation(requestData)
|
|
6161
6762
|
if not testRunning then
|
|
6162
6763
|
return {
|
|
@@ -6228,13 +6829,18 @@ return {
|
|
|
6228
6829
|
startPlaytest = startPlaytest,
|
|
6229
6830
|
stopPlaytest = stopPlaytest,
|
|
6230
6831
|
getPlaytestOutput = getPlaytestOutput,
|
|
6832
|
+
multiplayerTestStart = multiplayerTestStart,
|
|
6833
|
+
multiplayerTestState = multiplayerTestState,
|
|
6834
|
+
multiplayerTestAddPlayers = multiplayerTestAddPlayers,
|
|
6835
|
+
multiplayerTestLeaveClient = multiplayerTestLeaveClient,
|
|
6836
|
+
multiplayerTestEnd = multiplayerTestEnd,
|
|
6231
6837
|
characterNavigation = characterNavigation,
|
|
6232
6838
|
}
|
|
6233
6839
|
]]></string>
|
|
6234
6840
|
</Properties>
|
|
6235
6841
|
</Item>
|
|
6236
6842
|
</Item>
|
|
6237
|
-
<Item class="ModuleScript" referent="
|
|
6843
|
+
<Item class="ModuleScript" referent="20">
|
|
6238
6844
|
<Properties>
|
|
6239
6845
|
<string name="Name">LuauExec</string>
|
|
6240
6846
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -6552,7 +7158,7 @@ return {
|
|
|
6552
7158
|
]]></string>
|
|
6553
7159
|
</Properties>
|
|
6554
7160
|
</Item>
|
|
6555
|
-
<Item class="ModuleScript" referent="
|
|
7161
|
+
<Item class="ModuleScript" referent="21">
|
|
6556
7162
|
<Properties>
|
|
6557
7163
|
<string name="Name">Recording</string>
|
|
6558
7164
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -6582,7 +7188,7 @@ return {
|
|
|
6582
7188
|
]]></string>
|
|
6583
7189
|
</Properties>
|
|
6584
7190
|
</Item>
|
|
6585
|
-
<Item class="ModuleScript" referent="
|
|
7191
|
+
<Item class="ModuleScript" referent="22">
|
|
6586
7192
|
<Properties>
|
|
6587
7193
|
<string name="Name">RenderMonitor</string>
|
|
6588
7194
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -6650,7 +7256,7 @@ return {
|
|
|
6650
7256
|
]]></string>
|
|
6651
7257
|
</Properties>
|
|
6652
7258
|
</Item>
|
|
6653
|
-
<Item class="ModuleScript" referent="
|
|
7259
|
+
<Item class="ModuleScript" referent="23">
|
|
6654
7260
|
<Properties>
|
|
6655
7261
|
<string name="Name">RuntimeLogBuffer</string>
|
|
6656
7262
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -6831,11 +7437,11 @@ return {
|
|
|
6831
7437
|
]]></string>
|
|
6832
7438
|
</Properties>
|
|
6833
7439
|
</Item>
|
|
6834
|
-
<Item class="ModuleScript" referent="
|
|
7440
|
+
<Item class="ModuleScript" referent="24">
|
|
6835
7441
|
<Properties>
|
|
6836
7442
|
<string name="Name">State</string>
|
|
6837
7443
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
6838
|
-
local CURRENT_VERSION = "2.
|
|
7444
|
+
local CURRENT_VERSION = "2.15.0"
|
|
6839
7445
|
local MAX_CONNECTIONS = 5
|
|
6840
7446
|
local BASE_PORT = 58741
|
|
6841
7447
|
local activeTabIndex = 0
|
|
@@ -6927,7 +7533,7 @@ return {
|
|
|
6927
7533
|
]]></string>
|
|
6928
7534
|
</Properties>
|
|
6929
7535
|
</Item>
|
|
6930
|
-
<Item class="ModuleScript" referent="
|
|
7536
|
+
<Item class="ModuleScript" referent="25">
|
|
6931
7537
|
<Properties>
|
|
6932
7538
|
<string name="Name">StopPlayMonitor</string>
|
|
6933
7539
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -7072,7 +7678,7 @@ return {
|
|
|
7072
7678
|
]]></string>
|
|
7073
7679
|
</Properties>
|
|
7074
7680
|
</Item>
|
|
7075
|
-
<Item class="ModuleScript" referent="
|
|
7681
|
+
<Item class="ModuleScript" referent="26">
|
|
7076
7682
|
<Properties>
|
|
7077
7683
|
<string name="Name">UI</string>
|
|
7078
7684
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -7823,7 +8429,7 @@ return {
|
|
|
7823
8429
|
]]></string>
|
|
7824
8430
|
</Properties>
|
|
7825
8431
|
</Item>
|
|
7826
|
-
<Item class="ModuleScript" referent="
|
|
8432
|
+
<Item class="ModuleScript" referent="27">
|
|
7827
8433
|
<Properties>
|
|
7828
8434
|
<string name="Name">Utils</string>
|
|
7829
8435
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -8353,11 +8959,11 @@ return {
|
|
|
8353
8959
|
</Properties>
|
|
8354
8960
|
</Item>
|
|
8355
8961
|
</Item>
|
|
8356
|
-
<Item class="Folder" referent="
|
|
8962
|
+
<Item class="Folder" referent="31">
|
|
8357
8963
|
<Properties>
|
|
8358
8964
|
<string name="Name">include</string>
|
|
8359
8965
|
</Properties>
|
|
8360
|
-
<Item class="ModuleScript" referent="
|
|
8966
|
+
<Item class="ModuleScript" referent="28">
|
|
8361
8967
|
<Properties>
|
|
8362
8968
|
<string name="Name">Promise</string>
|
|
8363
8969
|
<string name="Source"><![CDATA[--[[
|
|
@@ -10431,7 +11037,7 @@ return Promise
|
|
|
10431
11037
|
]]></string>
|
|
10432
11038
|
</Properties>
|
|
10433
11039
|
</Item>
|
|
10434
|
-
<Item class="ModuleScript" referent="
|
|
11040
|
+
<Item class="ModuleScript" referent="29">
|
|
10435
11041
|
<Properties>
|
|
10436
11042
|
<string name="Name">RuntimeLib</string>
|
|
10437
11043
|
<string name="Source"><![CDATA[local Promise = require(script.Parent.Promise)
|
|
@@ -10698,15 +11304,15 @@ return TS
|
|
|
10698
11304
|
</Properties>
|
|
10699
11305
|
</Item>
|
|
10700
11306
|
</Item>
|
|
10701
|
-
<Item class="Folder" referent="
|
|
11307
|
+
<Item class="Folder" referent="32">
|
|
10702
11308
|
<Properties>
|
|
10703
11309
|
<string name="Name">node_modules</string>
|
|
10704
11310
|
</Properties>
|
|
10705
|
-
<Item class="Folder" referent="
|
|
11311
|
+
<Item class="Folder" referent="33">
|
|
10706
11312
|
<Properties>
|
|
10707
11313
|
<string name="Name">@rbxts</string>
|
|
10708
11314
|
</Properties>
|
|
10709
|
-
<Item class="ModuleScript" referent="
|
|
11315
|
+
<Item class="ModuleScript" referent="30">
|
|
10710
11316
|
<Properties>
|
|
10711
11317
|
<string name="Name">services</string>
|
|
10712
11318
|
<string name="Source"><![CDATA[return setmetatable({}, {
|