@chrrxs/robloxstudio-mcp 2.15.0 → 2.15.2
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 +201 -271
- package/package.json +2 -2
- package/studio-plugin/INSTALLATION.md +13 -3
- package/studio-plugin/MCPInspectorPlugin.rbxmx +388 -92
- package/studio-plugin/MCPPlugin.rbxmx +388 -92
- package/studio-plugin/src/modules/ClientBroker.ts +12 -2
- package/studio-plugin/src/modules/Communication.ts +22 -5
- package/studio-plugin/src/modules/EvalBridges.ts +6 -5
- package/studio-plugin/src/modules/LuauExec.ts +134 -36
- package/studio-plugin/src/modules/RuntimeLogBuffer.ts +9 -9
- package/studio-plugin/src/modules/State.ts +2 -0
- package/studio-plugin/src/modules/UI.ts +20 -0
- package/studio-plugin/src/modules/handlers/EvalRuntimeHandlers.ts +121 -0
- package/studio-plugin/src/modules/handlers/LogHandlers.ts +5 -6
- package/studio-plugin/src/types/index.d.ts +6 -0
|
@@ -105,7 +105,9 @@ local MemoryHandlers = TS.import(script, script.Parent, "handlers", "MemoryHandl
|
|
|
105
105
|
local SceneAnalysisHandlers = TS.import(script, script.Parent, "handlers", "SceneAnalysisHandlers")
|
|
106
106
|
local CaptureHandlers = TS.import(script, script.Parent, "handlers", "CaptureHandlers")
|
|
107
107
|
local InputHandlers = TS.import(script, script.Parent, "handlers", "InputHandlers")
|
|
108
|
+
local EvalRuntimeHandlers = TS.import(script, script.Parent, "handlers", "EvalRuntimeHandlers")
|
|
108
109
|
local LuauExec = TS.import(script, script.Parent, "LuauExec")
|
|
110
|
+
local State = TS.import(script, script.Parent, "State")
|
|
109
111
|
local StudioTestService = game:GetService("StudioTestService")
|
|
110
112
|
-- Mirror of Communication.computeInstanceId() — duplicated here because the
|
|
111
113
|
-- client broker runs in the play-server DM where it can't easily import from
|
|
@@ -166,6 +168,7 @@ local BROKER_OWNER_ATTRIBUTE = "__MCPBrokerOwner"
|
|
|
166
168
|
-- cache / etc. lives there) so the server peer alone can't satisfy them.
|
|
167
169
|
local CLIENT_BROKER_ALLOWED_ENDPOINTS = {
|
|
168
170
|
["/api/execute-luau"] = true,
|
|
171
|
+
["/api/eval-runtime"] = true,
|
|
169
172
|
["/api/get-runtime-logs"] = true,
|
|
170
173
|
["/api/get-memory-breakdown"] = true,
|
|
171
174
|
["/api/get-scene-analysis"] = true,
|
|
@@ -201,6 +204,8 @@ local function reRegisterProxy(proxyId, role)
|
|
|
201
204
|
placeName = resolvePlaceName(),
|
|
202
205
|
dataModelName = game.Name,
|
|
203
206
|
isRunning = RunService:IsRunning(),
|
|
207
|
+
pluginVersion = State.CURRENT_VERSION,
|
|
208
|
+
pluginVariant = State.PLUGIN_VARIANT,
|
|
204
209
|
})
|
|
205
210
|
end)
|
|
206
211
|
end
|
|
@@ -244,8 +249,8 @@ local function handleGetRuntimeLogs(data)
|
|
|
244
249
|
local since = d.since
|
|
245
250
|
local tail = d.tail
|
|
246
251
|
local filter = d.filter
|
|
247
|
-
-- "client" is the generic
|
|
248
|
-
-- the specific role (e.g. "client-1")
|
|
252
|
+
-- "client" is the generic capture tag; MCP-side aggregation overrides it
|
|
253
|
+
-- with the specific role (e.g. "client-1") for capturedBy.
|
|
249
254
|
return RuntimeLogBuffer.query({
|
|
250
255
|
since = since,
|
|
251
256
|
tail = tail,
|
|
@@ -363,6 +368,9 @@ local function setupClientBroker()
|
|
|
363
368
|
if payload and payload.endpoint == "/api/execute-luau" then
|
|
364
369
|
return handleExecuteLuau(payload.data)
|
|
365
370
|
end
|
|
371
|
+
if payload and payload.endpoint == "/api/eval-runtime" then
|
|
372
|
+
return EvalRuntimeHandlers.evalRuntime(payload.data or {})
|
|
373
|
+
end
|
|
366
374
|
-- Legacy: raw execute-luau payload at the top level.
|
|
367
375
|
return handleExecuteLuau(payload)
|
|
368
376
|
end
|
|
@@ -456,6 +464,8 @@ local function registerProxy(player, rf)
|
|
|
456
464
|
placeName = resolvePlaceName(),
|
|
457
465
|
dataModelName = game.Name,
|
|
458
466
|
isRunning = RunService:IsRunning(),
|
|
467
|
+
pluginVersion = State.CURRENT_VERSION,
|
|
468
|
+
pluginVariant = State.PLUGIN_VARIANT,
|
|
459
469
|
})
|
|
460
470
|
if not ok or not res or not res.Success then
|
|
461
471
|
warn(`[robloxstudio-mcp] proxy register failed for {player.Name}`)
|
|
@@ -557,6 +567,7 @@ local LogHandlers = TS.import(script, script.Parent, "handlers", "LogHandlers")
|
|
|
557
567
|
local SerializationHandlers = TS.import(script, script.Parent, "handlers", "SerializationHandlers")
|
|
558
568
|
local MemoryHandlers = TS.import(script, script.Parent, "handlers", "MemoryHandlers")
|
|
559
569
|
local SceneAnalysisHandlers = TS.import(script, script.Parent, "handlers", "SceneAnalysisHandlers")
|
|
570
|
+
local EvalRuntimeHandlers = TS.import(script, script.Parent, "handlers", "EvalRuntimeHandlers")
|
|
560
571
|
-- Per-plugin-load random GUID. Used as the /poll URL param so the server
|
|
561
572
|
-- can tell our polls apart from any other plugin's polls. Not user-facing —
|
|
562
573
|
-- MCP tools and the LLM operate on instanceId (the place identifier).
|
|
@@ -584,6 +595,8 @@ end
|
|
|
584
595
|
local instanceId = computeInstanceId()
|
|
585
596
|
local assignedRole
|
|
586
597
|
local duplicateInstanceRole = false
|
|
598
|
+
local hasVersionMismatch = false
|
|
599
|
+
local lastVersionMismatchWarningKey
|
|
587
600
|
-- Cache the published place name from MarketplaceService:GetProductInfo so
|
|
588
601
|
-- /ready can carry a friendly identifier (e.g. "Natural Disasters") distinct
|
|
589
602
|
-- from game.Name (the DataModel name, often "Place1" in edit). We only fetch
|
|
@@ -661,6 +674,7 @@ local routeMap = {
|
|
|
661
674
|
["/api/get-tagged"] = MetadataHandlers.getTagged,
|
|
662
675
|
["/api/get-selection"] = MetadataHandlers.getSelection,
|
|
663
676
|
["/api/execute-luau"] = MetadataHandlers.executeLuau,
|
|
677
|
+
["/api/eval-runtime"] = EvalRuntimeHandlers.evalRuntime,
|
|
664
678
|
["/api/undo"] = MetadataHandlers.undo,
|
|
665
679
|
["/api/redo"] = MetadataHandlers.redo,
|
|
666
680
|
["/api/bulk-set-attributes"] = MetadataHandlers.bulkSetAttributes,
|
|
@@ -783,6 +797,8 @@ function sendReady(conn)
|
|
|
783
797
|
placeName = resolvePlaceName(),
|
|
784
798
|
dataModelName = game.Name,
|
|
785
799
|
isRunning = RunService:IsRunning(),
|
|
800
|
+
pluginVersion = State.CURRENT_VERSION,
|
|
801
|
+
pluginVariant = State.PLUGIN_VARIANT,
|
|
786
802
|
pluginReady = true,
|
|
787
803
|
timestamp = tick(),
|
|
788
804
|
}),
|
|
@@ -848,6 +864,23 @@ local function pollForRequests(connIndex)
|
|
|
848
864
|
local mcpConnected = data.mcpConnected == true
|
|
849
865
|
conn.lastHttpOk = true
|
|
850
866
|
conn.lastMcpOk = mcpConnected
|
|
867
|
+
local _condition = data.serverVersion
|
|
868
|
+
if _condition == nil then
|
|
869
|
+
_condition = "unknown"
|
|
870
|
+
end
|
|
871
|
+
local serverVersion = _condition
|
|
872
|
+
if data.versionMismatch == true then
|
|
873
|
+
hasVersionMismatch = true
|
|
874
|
+
local warningKey = `{State.CURRENT_VERSION}:{serverVersion}`
|
|
875
|
+
if lastVersionMismatchWarningKey ~= warningKey then
|
|
876
|
+
lastVersionMismatchWarningKey = warningKey
|
|
877
|
+
warn(`[MCPPlugin] Version mismatch: Studio plugin v{State.CURRENT_VERSION} / MCP v{serverVersion}. Run npx -y @chrrxs/robloxstudio-mcp@latest --auto-install-plugin and restart Studio.`)
|
|
878
|
+
end
|
|
879
|
+
UI.showBanner("version-mismatch", `Plugin v{State.CURRENT_VERSION} / MCP v{serverVersion} mismatch`)
|
|
880
|
+
elseif hasVersionMismatch then
|
|
881
|
+
hasVersionMismatch = false
|
|
882
|
+
UI.hideBanner("version-mismatch")
|
|
883
|
+
end
|
|
851
884
|
-- Server tells us when its in-memory instances map doesn't have us
|
|
852
885
|
-- (e.g. after an MCP process restart). Re-issue /ready immediately so
|
|
853
886
|
-- target=server/client-N start routing again. The throttle inside
|
|
@@ -860,12 +893,12 @@ local function pollForRequests(connIndex)
|
|
|
860
893
|
local el = ui
|
|
861
894
|
el.step1Dot.BackgroundColor3 = Color3.fromRGB(34, 197, 94)
|
|
862
895
|
el.step1Label.Text = "HTTP server (OK)"
|
|
863
|
-
local
|
|
864
|
-
if
|
|
896
|
+
local _condition_1 = mcpConnected
|
|
897
|
+
if _condition_1 then
|
|
865
898
|
local _value = (string.find(el.statusLabel.Text, "Connected"))
|
|
866
|
-
|
|
899
|
+
_condition_1 = not (_value ~= 0 and _value == _value and _value)
|
|
867
900
|
end
|
|
868
|
-
if
|
|
901
|
+
if _condition_1 then
|
|
869
902
|
el.statusLabel.Text = "Connected"
|
|
870
903
|
el.statusLabel.TextColor3 = Color3.fromRGB(34, 197, 94)
|
|
871
904
|
el.statusIndicator.BackgroundColor3 = Color3.fromRGB(34, 197, 94)
|
|
@@ -896,11 +929,11 @@ local function pollForRequests(connIndex)
|
|
|
896
929
|
conn.mcpWaitStartTime = tick()
|
|
897
930
|
end
|
|
898
931
|
local _exp = tick()
|
|
899
|
-
local
|
|
900
|
-
if
|
|
901
|
-
|
|
932
|
+
local _condition_2 = conn.mcpWaitStartTime
|
|
933
|
+
if _condition_2 == nil then
|
|
934
|
+
_condition_2 = tick()
|
|
902
935
|
end
|
|
903
|
-
local elapsed = _exp -
|
|
936
|
+
local elapsed = _exp - _condition_2
|
|
904
937
|
el.troubleshootLabel.Visible = elapsed > 8
|
|
905
938
|
UI.startPulseAnimation()
|
|
906
939
|
end
|
|
@@ -1109,11 +1142,9 @@ local function checkForUpdates()
|
|
|
1109
1142
|
if _condition ~= "" and _condition then
|
|
1110
1143
|
local latestVersion = data.version
|
|
1111
1144
|
if Utils.compareVersions(State.CURRENT_VERSION, latestVersion) < 0 then
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
ui.contentFrame.Position = UDim2.new(0, 8, 0, 92)
|
|
1116
|
-
ui.contentFrame.Size = UDim2.new(1, -16, 1, -100)
|
|
1145
|
+
if not hasVersionMismatch then
|
|
1146
|
+
UI.showBanner("update", `v{latestVersion} available - github.com/chrrxs/robloxstudio-mcp`)
|
|
1147
|
+
end
|
|
1117
1148
|
end
|
|
1118
1149
|
end
|
|
1119
1150
|
end
|
|
@@ -1218,9 +1249,10 @@ bf.Archivable = false\
|
|
|
1218
1249
|
bf.Parent = ServerScriptService\
|
|
1219
1250
|
bf.OnInvoke = function(payload)\
|
|
1220
1251
|
if typeof(payload) ~= "Instance" or not payload:IsA("ModuleScript") then\
|
|
1221
|
-
return false, "payload must be a ModuleScript instance"\
|
|
1252
|
+
return \{ ok = false, value = "payload must be a ModuleScript instance" \}\
|
|
1222
1253
|
end\
|
|
1223
|
-
|
|
1254
|
+
local ok, value = pcall(require, payload)\
|
|
1255
|
+
return \{ ok = ok, value = value \}\
|
|
1224
1256
|
end\
|
|
1225
1257
|
`
|
|
1226
1258
|
local CLIENT_BRIDGE_SOURCE = `\
|
|
@@ -1244,9 +1276,10 @@ bf.Archivable = false\
|
|
|
1244
1276
|
bf.Parent = ReplicatedStorage\
|
|
1245
1277
|
bf.OnInvoke = function(payload)\
|
|
1246
1278
|
if typeof(payload) ~= "Instance" or not payload:IsA("ModuleScript") then\
|
|
1247
|
-
return false, "payload must be a ModuleScript instance"\
|
|
1279
|
+
return \{ ok = false, value = "payload must be a ModuleScript instance" \}\
|
|
1248
1280
|
end\
|
|
1249
|
-
|
|
1281
|
+
local ok, value = pcall(require, payload)\
|
|
1282
|
+
return \{ ok = ok, value = value \}\
|
|
1250
1283
|
end\
|
|
1251
1284
|
`
|
|
1252
1285
|
-- Stamp written onto each installed bridge Script so we can tell whether the
|
|
@@ -1263,9 +1296,9 @@ local function computeBridgeStamp()
|
|
|
1263
1296
|
for i = 1, #combined do
|
|
1264
1297
|
h = (h * 33 + (string.byte(combined, i))) % 2147483647
|
|
1265
1298
|
end
|
|
1266
|
-
-- "2.15.
|
|
1299
|
+
-- "2.15.2" is replaced with the package version at package time
|
|
1267
1300
|
-- (scripts/build-plugin.mjs injectVersion), so a release bump also restamps.
|
|
1268
|
-
return `{tostring(h)}-2.15.
|
|
1301
|
+
return `{tostring(h)}-2.15.2`
|
|
1269
1302
|
end
|
|
1270
1303
|
local BRIDGE_STAMP = computeBridgeStamp()
|
|
1271
1304
|
local function setSource(scriptInst, source)
|
|
@@ -2442,6 +2475,132 @@ return {
|
|
|
2442
2475
|
</Properties>
|
|
2443
2476
|
</Item>
|
|
2444
2477
|
<Item class="ModuleScript" referent="9">
|
|
2478
|
+
<Properties>
|
|
2479
|
+
<string name="Name">EvalRuntimeHandlers</string>
|
|
2480
|
+
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
2481
|
+
local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
|
|
2482
|
+
local _services = TS.import(script, script.Parent.Parent.Parent, "node_modules", "@rbxts", "services")
|
|
2483
|
+
local LogService = _services.LogService
|
|
2484
|
+
local ReplicatedStorage = _services.ReplicatedStorage
|
|
2485
|
+
local RunService = _services.RunService
|
|
2486
|
+
local ServerScriptService = _services.ServerScriptService
|
|
2487
|
+
local BRIDGE_NAMES = TS.import(script, script.Parent.Parent, "EvalBridges").BRIDGE_NAMES
|
|
2488
|
+
local LuauExec = TS.import(script, script.Parent.Parent, "LuauExec")
|
|
2489
|
+
local PAYLOAD_INSTANCE_NAME = "__MCPEvalPayload"
|
|
2490
|
+
local function getBridgeConfig()
|
|
2491
|
+
if not RunService:IsRunning() then
|
|
2492
|
+
return {
|
|
2493
|
+
error = "eval_*_runtime requires a running playtest.",
|
|
2494
|
+
}
|
|
2495
|
+
end
|
|
2496
|
+
if RunService:IsServer() then
|
|
2497
|
+
return {
|
|
2498
|
+
service = ServerScriptService,
|
|
2499
|
+
bridgeName = BRIDGE_NAMES.serverLocal,
|
|
2500
|
+
missingError = "ServerEvalBridge not found. The bridge runs inside the play DM, so a playtest must be running. The bridge installs automatically (including for manually-started playtests); if a playtest is running and you still see this, reconnect the plugin in the edit window so the bridge reinstalls, then start the playtest again.",
|
|
2501
|
+
}
|
|
2502
|
+
end
|
|
2503
|
+
return {
|
|
2504
|
+
service = ReplicatedStorage,
|
|
2505
|
+
bridgeName = BRIDGE_NAMES.clientLocal,
|
|
2506
|
+
missingError = "ClientEvalBridge not found. The bridge runs inside the play DM, so a playtest must be running. The bridge installs automatically (including for manually-started playtests); if a playtest is running and you still see this, reconnect the plugin in the edit window so the bridge reinstalls, then start the playtest again.",
|
|
2507
|
+
}
|
|
2508
|
+
end
|
|
2509
|
+
local function evalRuntime(requestData)
|
|
2510
|
+
local code = requestData.code
|
|
2511
|
+
if not (code ~= "" and code) or code == "" then
|
|
2512
|
+
return {
|
|
2513
|
+
error = "Code is required",
|
|
2514
|
+
}
|
|
2515
|
+
end
|
|
2516
|
+
local config = getBridgeConfig()
|
|
2517
|
+
if config.error ~= nil then
|
|
2518
|
+
return {
|
|
2519
|
+
bridge = "missing",
|
|
2520
|
+
error = config.error,
|
|
2521
|
+
}
|
|
2522
|
+
end
|
|
2523
|
+
local bridge = config.service:FindFirstChild(config.bridgeName)
|
|
2524
|
+
if not bridge or not bridge:IsA("BindableFunction") then
|
|
2525
|
+
return {
|
|
2526
|
+
bridge = "missing",
|
|
2527
|
+
error = config.missingError,
|
|
2528
|
+
}
|
|
2529
|
+
end
|
|
2530
|
+
local m = Instance.new("ModuleScript")
|
|
2531
|
+
m.Name = PAYLOAD_INSTANCE_NAME
|
|
2532
|
+
local userLines = LuauExec.countLines(code)
|
|
2533
|
+
local wrapped = LuauExec.buildWrapper(code, PAYLOAD_INSTANCE_NAME)
|
|
2534
|
+
local okSet, setErr = pcall(function()
|
|
2535
|
+
m.Source = wrapped
|
|
2536
|
+
end)
|
|
2537
|
+
if not okSet then
|
|
2538
|
+
m:Destroy()
|
|
2539
|
+
return {
|
|
2540
|
+
bridge = "ok",
|
|
2541
|
+
ok = false,
|
|
2542
|
+
error = `ModuleScript Source set failed: {tostring(setErr)}`,
|
|
2543
|
+
}
|
|
2544
|
+
end
|
|
2545
|
+
m.Parent = game:GetService("Workspace")
|
|
2546
|
+
local historyStart = #LogService:GetLogHistory()
|
|
2547
|
+
local invokeOk, invokeResult = pcall(function()
|
|
2548
|
+
return bridge:Invoke(m)
|
|
2549
|
+
end)
|
|
2550
|
+
m:Destroy()
|
|
2551
|
+
if not invokeOk then
|
|
2552
|
+
return {
|
|
2553
|
+
bridge = "ok",
|
|
2554
|
+
ok = false,
|
|
2555
|
+
error = tostring(invokeResult),
|
|
2556
|
+
}
|
|
2557
|
+
end
|
|
2558
|
+
if not (type(invokeResult) == "table") then
|
|
2559
|
+
return {
|
|
2560
|
+
bridge = "ok",
|
|
2561
|
+
ok = false,
|
|
2562
|
+
error = `Eval bridge returned invalid result: {tostring(invokeResult)}`,
|
|
2563
|
+
}
|
|
2564
|
+
end
|
|
2565
|
+
local bridgeResult = invokeResult
|
|
2566
|
+
if bridgeResult.ok ~= true then
|
|
2567
|
+
return {
|
|
2568
|
+
bridge = "ok",
|
|
2569
|
+
ok = false,
|
|
2570
|
+
error = LuauExec.recoverPayloadRequireError(bridgeResult.value, userLines, PAYLOAD_INSTANCE_NAME, historyStart),
|
|
2571
|
+
}
|
|
2572
|
+
end
|
|
2573
|
+
local inner = bridgeResult.value
|
|
2574
|
+
if not (type(inner) == "table") then
|
|
2575
|
+
return {
|
|
2576
|
+
bridge = "ok",
|
|
2577
|
+
ok = true,
|
|
2578
|
+
result = if inner == nil then nil else LuauExec.formatReturnValue(inner),
|
|
2579
|
+
}
|
|
2580
|
+
end
|
|
2581
|
+
local r = inner
|
|
2582
|
+
local ok = r.ok == true
|
|
2583
|
+
local _object = {
|
|
2584
|
+
bridge = "ok",
|
|
2585
|
+
ok = ok,
|
|
2586
|
+
result = if ok and r.value ~= nil then LuauExec.formatReturnValue(r.value) else nil,
|
|
2587
|
+
error = if not ok then tostring(r.value) else nil,
|
|
2588
|
+
}
|
|
2589
|
+
local _left = "output"
|
|
2590
|
+
local _condition = r.output
|
|
2591
|
+
if _condition == nil then
|
|
2592
|
+
_condition = {}
|
|
2593
|
+
end
|
|
2594
|
+
_object[_left] = _condition
|
|
2595
|
+
return _object
|
|
2596
|
+
end
|
|
2597
|
+
return {
|
|
2598
|
+
evalRuntime = evalRuntime,
|
|
2599
|
+
}
|
|
2600
|
+
]]></string>
|
|
2601
|
+
</Properties>
|
|
2602
|
+
</Item>
|
|
2603
|
+
<Item class="ModuleScript" referent="10">
|
|
2445
2604
|
<Properties>
|
|
2446
2605
|
<string name="Name">InputHandlers</string>
|
|
2447
2606
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -2641,7 +2800,7 @@ return {
|
|
|
2641
2800
|
]]></string>
|
|
2642
2801
|
</Properties>
|
|
2643
2802
|
</Item>
|
|
2644
|
-
<Item class="ModuleScript" referent="
|
|
2803
|
+
<Item class="ModuleScript" referent="11">
|
|
2645
2804
|
<Properties>
|
|
2646
2805
|
<string name="Name">InstanceHandlers</string>
|
|
2647
2806
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -3148,7 +3307,7 @@ return {
|
|
|
3148
3307
|
]]></string>
|
|
3149
3308
|
</Properties>
|
|
3150
3309
|
</Item>
|
|
3151
|
-
<Item class="ModuleScript" referent="
|
|
3310
|
+
<Item class="ModuleScript" referent="12">
|
|
3152
3311
|
<Properties>
|
|
3153
3312
|
<string name="Name">LogHandlers</string>
|
|
3154
3313
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -3158,16 +3317,15 @@ local function getRuntimeLogs(requestData)
|
|
|
3158
3317
|
local since = requestData.since
|
|
3159
3318
|
local tail = requestData.tail
|
|
3160
3319
|
local filter = requestData.filter
|
|
3161
|
-
--
|
|
3162
|
-
--
|
|
3163
|
-
--
|
|
3164
|
-
|
|
3165
|
-
local peer = RuntimeLogBuffer.detectPeer()
|
|
3320
|
+
-- This is the buffer that captured the LogService event, not necessarily
|
|
3321
|
+
-- the script-origin peer. Ordinary playtests share/reflect logs across
|
|
3322
|
+
-- edit/server/client LogService buffers.
|
|
3323
|
+
local capturedBy = RuntimeLogBuffer.detectPeer()
|
|
3166
3324
|
return RuntimeLogBuffer.query({
|
|
3167
3325
|
since = since,
|
|
3168
3326
|
tail = tail,
|
|
3169
3327
|
filter = filter,
|
|
3170
|
-
},
|
|
3328
|
+
}, capturedBy)
|
|
3171
3329
|
end
|
|
3172
3330
|
return {
|
|
3173
3331
|
getRuntimeLogs = getRuntimeLogs,
|
|
@@ -3175,7 +3333,7 @@ return {
|
|
|
3175
3333
|
]]></string>
|
|
3176
3334
|
</Properties>
|
|
3177
3335
|
</Item>
|
|
3178
|
-
<Item class="ModuleScript" referent="
|
|
3336
|
+
<Item class="ModuleScript" referent="13">
|
|
3179
3337
|
<Properties>
|
|
3180
3338
|
<string name="Name">MemoryHandlers</string>
|
|
3181
3339
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -3243,7 +3401,7 @@ return {
|
|
|
3243
3401
|
]]></string>
|
|
3244
3402
|
</Properties>
|
|
3245
3403
|
</Item>
|
|
3246
|
-
<Item class="ModuleScript" referent="
|
|
3404
|
+
<Item class="ModuleScript" referent="14">
|
|
3247
3405
|
<Properties>
|
|
3248
3406
|
<string name="Name">MetadataHandlers</string>
|
|
3249
3407
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -3782,7 +3940,7 @@ return {
|
|
|
3782
3940
|
]]></string>
|
|
3783
3941
|
</Properties>
|
|
3784
3942
|
</Item>
|
|
3785
|
-
<Item class="ModuleScript" referent="
|
|
3943
|
+
<Item class="ModuleScript" referent="15">
|
|
3786
3944
|
<Properties>
|
|
3787
3945
|
<string name="Name">PropertyHandlers</string>
|
|
3788
3946
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -4034,7 +4192,7 @@ return {
|
|
|
4034
4192
|
]]></string>
|
|
4035
4193
|
</Properties>
|
|
4036
4194
|
</Item>
|
|
4037
|
-
<Item class="ModuleScript" referent="
|
|
4195
|
+
<Item class="ModuleScript" referent="16">
|
|
4038
4196
|
<Properties>
|
|
4039
4197
|
<string name="Name">QueryHandlers</string>
|
|
4040
4198
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -5076,7 +5234,7 @@ return {
|
|
|
5076
5234
|
]]></string>
|
|
5077
5235
|
</Properties>
|
|
5078
5236
|
</Item>
|
|
5079
|
-
<Item class="ModuleScript" referent="
|
|
5237
|
+
<Item class="ModuleScript" referent="17">
|
|
5080
5238
|
<Properties>
|
|
5081
5239
|
<string name="Name">SceneAnalysisHandlers</string>
|
|
5082
5240
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -5325,7 +5483,7 @@ return {
|
|
|
5325
5483
|
]]></string>
|
|
5326
5484
|
</Properties>
|
|
5327
5485
|
</Item>
|
|
5328
|
-
<Item class="ModuleScript" referent="
|
|
5486
|
+
<Item class="ModuleScript" referent="18">
|
|
5329
5487
|
<Properties>
|
|
5330
5488
|
<string name="Name">ScriptHandlers</string>
|
|
5331
5489
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -6021,7 +6179,7 @@ return {
|
|
|
6021
6179
|
]]></string>
|
|
6022
6180
|
</Properties>
|
|
6023
6181
|
</Item>
|
|
6024
|
-
<Item class="ModuleScript" referent="
|
|
6182
|
+
<Item class="ModuleScript" referent="19">
|
|
6025
6183
|
<Properties>
|
|
6026
6184
|
<string name="Name">SerializationHandlers</string>
|
|
6027
6185
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -6207,7 +6365,7 @@ return {
|
|
|
6207
6365
|
]]></string>
|
|
6208
6366
|
</Properties>
|
|
6209
6367
|
</Item>
|
|
6210
|
-
<Item class="ModuleScript" referent="
|
|
6368
|
+
<Item class="ModuleScript" referent="20">
|
|
6211
6369
|
<Properties>
|
|
6212
6370
|
<string name="Name">TestHandlers</string>
|
|
6213
6371
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -6840,7 +6998,7 @@ return {
|
|
|
6840
6998
|
</Properties>
|
|
6841
6999
|
</Item>
|
|
6842
7000
|
</Item>
|
|
6843
|
-
<Item class="ModuleScript" referent="
|
|
7001
|
+
<Item class="ModuleScript" referent="21">
|
|
6844
7002
|
<Properties>
|
|
6845
7003
|
<string name="Name">LuauExec</string>
|
|
6846
7004
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -6849,10 +7007,12 @@ return {
|
|
|
6849
7007
|
-- and the play-client peer (ClientBroker.handleExecuteLuau). Three things this
|
|
6850
7008
|
-- module owns:
|
|
6851
7009
|
--
|
|
6852
|
-
-- 1. The IIFE wrapper that captures print/warn,
|
|
6853
|
-
--
|
|
6854
|
-
-- always returns
|
|
6855
|
-
--
|
|
7010
|
+
-- 1. The IIFE wrapper that captures print/warn, wraps require() so nested
|
|
7011
|
+
-- ModuleScript load failures can recover the real LogService diagnostic,
|
|
7012
|
+
-- runs user code in xpcall, and always returns { ok, value, output } so
|
|
7013
|
+
-- the ModuleScript itself always returns exactly one value (otherwise
|
|
7014
|
+
-- `print("hi")` with no return would fail with "Module code did not
|
|
7015
|
+
-- return exactly one value").
|
|
6856
7016
|
--
|
|
6857
7017
|
-- 2. The loadstring-then-ModuleScript-require fallback, with the parse-error
|
|
6858
7018
|
-- recovery hack that pulls the real diagnostic from LogService.
|
|
@@ -6870,14 +7030,14 @@ return {
|
|
|
6870
7030
|
local HttpService = game:GetService("HttpService")
|
|
6871
7031
|
local LogService = game:GetService("LogService")
|
|
6872
7032
|
local PAYLOAD_INSTANCE_NAME = "__MCPExecLuauPayload"
|
|
6873
|
-
local
|
|
7033
|
+
local REQUIRE_GENERIC_ERROR = "Requested module experienced an error while loading"
|
|
6874
7034
|
-- Number of lines the wrapper emits BEFORE the first line of user code.
|
|
6875
7035
|
-- Used both inside the wrapper (Luau __mcp_LINE_OFFSET) and on the TS side
|
|
6876
7036
|
-- (remapPayloadLines, for compile errors recovered from LogService) so user
|
|
6877
7037
|
-- code errors report user-relative line numbers instead of the inflated
|
|
6878
|
-
-- "line
|
|
6879
|
-
-- prefix lines, update this constant
|
|
6880
|
-
local WRAPPER_LINE_OFFSET =
|
|
7038
|
+
-- "line 49" the wrapper would otherwise expose. If you reorder buildWrapper's
|
|
7039
|
+
-- prefix lines, update this constant.
|
|
7040
|
+
local WRAPPER_LINE_OFFSET = 84
|
|
6881
7041
|
-- Count source lines so the wrapper can filter traceback frames that fall
|
|
6882
7042
|
-- outside the user code range (the wrapper's own preamble/postamble lines).
|
|
6883
7043
|
local function countLines(s)
|
|
@@ -6902,20 +7062,31 @@ local function countLines(s)
|
|
|
6902
7062
|
end
|
|
6903
7063
|
return n
|
|
6904
7064
|
end
|
|
6905
|
-
local function
|
|
7065
|
+
local function luaPatternEscape(s)
|
|
7066
|
+
local escaped = string.gsub(s, "([^%w])", "%%%1")
|
|
7067
|
+
return escaped
|
|
7068
|
+
end
|
|
7069
|
+
local function buildWrapper(code, payloadInstanceName)
|
|
7070
|
+
if payloadInstanceName == nil then
|
|
7071
|
+
payloadInstanceName = PAYLOAD_INSTANCE_NAME
|
|
7072
|
+
end
|
|
6906
7073
|
-- If you reorder the prefix lines below, update WRAPPER_LINE_OFFSET to
|
|
6907
7074
|
-- match the number of lines emitted BEFORE the ${code} substitution.
|
|
6908
7075
|
-- The constant is mirrored inside the wrapper (__mcp_LINE_OFFSET) and
|
|
6909
7076
|
-- used by remapPayloadLines on the TS side.
|
|
6910
7077
|
local userLines = countLines(code)
|
|
7078
|
+
local payloadPattern = luaPatternEscape(payloadInstanceName)
|
|
6911
7079
|
return `return ((function()\
|
|
6912
7080
|
\tlocal __mcp_traceback\
|
|
6913
7081
|
\tlocal __mcp_remap\
|
|
6914
7082
|
\tlocal __mcp_LINE_OFFSET = {WRAPPER_LINE_OFFSET}\
|
|
6915
7083
|
\tlocal __mcp_USER_LINES = {userLines}\
|
|
7084
|
+
\tlocal __mcp_LogService = game:GetService("LogService")\
|
|
7085
|
+
\tlocal __mcp_REQUIRE_GENERIC = "{REQUIRE_GENERIC_ERROR}"\
|
|
6916
7086
|
\tlocal __mcp_output = \{\}\
|
|
6917
7087
|
\tlocal __mcp_real_print = print\
|
|
6918
7088
|
\tlocal __mcp_real_warn = warn\
|
|
7089
|
+
\tlocal __mcp_real_require = require\
|
|
6919
7090
|
\tlocal print = function(...)\
|
|
6920
7091
|
\t\t__mcp_real_print(...)\
|
|
6921
7092
|
\t\tlocal args = \{...\}\
|
|
@@ -6930,6 +7101,64 @@ local function buildWrapper(code)
|
|
|
6930
7101
|
\t\tfor i, a in ipairs(args) do parts[i] = tostring(a) end\
|
|
6931
7102
|
\t\ttable.insert(__mcp_output, "[warn] " .. table.concat(parts, "\\t"))\
|
|
6932
7103
|
\tend\
|
|
7104
|
+
\tlocal function __mcp_is_stack_noise(msg)\
|
|
7105
|
+
\t\treturn msg == "Stack Begin" or msg == "Stack End" or string.sub(msg, 1, 8) == "Script '"\
|
|
7106
|
+
\tend\
|
|
7107
|
+
\tlocal function __mcp_is_actionable_require_log(entry)\
|
|
7108
|
+
\t\tif not entry or entry.messageType ~= Enum.MessageType.MessageError then return false end\
|
|
7109
|
+
\t\tlocal msg = tostring(entry.message)\
|
|
7110
|
+
\t\treturn msg ~= __mcp_REQUIRE_GENERIC and not __mcp_is_stack_noise(msg)\
|
|
7111
|
+
\tend\
|
|
7112
|
+
\tlocal function __mcp_entry_mentions_module(entry, module_path)\
|
|
7113
|
+
\t\tif not entry or not module_path or module_path == "" then return false end\
|
|
7114
|
+
\t\treturn string.find(tostring(entry.message), module_path, 1, true) ~= nil\
|
|
7115
|
+
\tend\
|
|
7116
|
+
\tlocal function __mcp_prior_module_error(hist, module_path)\
|
|
7117
|
+
\t\tif not module_path or module_path == "" then return nil end\
|
|
7118
|
+
\t\tfor i = #hist, 1, -1 do\
|
|
7119
|
+
\t\t\tlocal entry = hist[i]\
|
|
7120
|
+
\t\t\tif __mcp_entry_mentions_module(entry, module_path) then\
|
|
7121
|
+
\t\t\t\tif __mcp_is_actionable_require_log(entry) then\
|
|
7122
|
+
\t\t\t\t\treturn tostring(entry.message)\
|
|
7123
|
+
\t\t\t\tend\
|
|
7124
|
+
\t\t\t\tfor j = i - 1, math.max(1, i - 6), -1 do\
|
|
7125
|
+
\t\t\t\t\tlocal previous = hist[j]\
|
|
7126
|
+
\t\t\t\t\tif __mcp_is_actionable_require_log(previous) then\
|
|
7127
|
+
\t\t\t\t\t\treturn tostring(previous.message)\
|
|
7128
|
+
\t\t\t\t\tend\
|
|
7129
|
+
\t\t\t\tend\
|
|
7130
|
+
\t\t\tend\
|
|
7131
|
+
\t\tend\
|
|
7132
|
+
\t\treturn nil\
|
|
7133
|
+
\tend\
|
|
7134
|
+
\tlocal function __mcp_recover_require_error(err, history_start, module)\
|
|
7135
|
+
\t\tlocal err_msg = tostring(err)\
|
|
7136
|
+
\t\tif err_msg ~= __mcp_REQUIRE_GENERIC then return err_msg end\
|
|
7137
|
+
\t\tlocal module_path\
|
|
7138
|
+
\t\tif typeof(module) == "Instance" then\
|
|
7139
|
+
\t\t\tlocal ok_path, path = pcall(function()\
|
|
7140
|
+
\t\t\t\treturn module:GetFullName()\
|
|
7141
|
+
\t\t\tend)\
|
|
7142
|
+
\t\t\tif ok_path then module_path = path end\
|
|
7143
|
+
\t\tend\
|
|
7144
|
+
\t\ttask.wait(0.05)\
|
|
7145
|
+
\t\tlocal hist = __mcp_LogService:GetLogHistory()\
|
|
7146
|
+
\t\tfor i = #hist, history_start + 1, -1 do\
|
|
7147
|
+
\t\t\tlocal entry = hist[i]\
|
|
7148
|
+
\t\t\tif __mcp_is_actionable_require_log(entry) then\
|
|
7149
|
+
\t\t\t\treturn tostring(entry.message)\
|
|
7150
|
+
\t\t\tend\
|
|
7151
|
+
\t\tend\
|
|
7152
|
+
\t\tlocal prior = __mcp_prior_module_error(hist, module_path)\
|
|
7153
|
+
\t\tif prior then return prior end\
|
|
7154
|
+
\t\treturn err_msg\
|
|
7155
|
+
\tend\
|
|
7156
|
+
\tlocal function require(module)\
|
|
7157
|
+
\t\tlocal history_start = #__mcp_LogService:GetLogHistory()\
|
|
7158
|
+
\t\tlocal ok, value = pcall(__mcp_real_require, module)\
|
|
7159
|
+
\t\tif ok then return value end\
|
|
7160
|
+
\t\terror(__mcp_recover_require_error(value, history_start, module), 0)\
|
|
7161
|
+
\tend\
|
|
6933
7162
|
\tlocal function __mcp_run()\
|
|
6934
7163
|
{code}\
|
|
6935
7164
|
\tend\
|
|
@@ -6940,15 +7169,20 @@ local function buildWrapper(code)
|
|
|
6940
7169
|
\t\t-- Subtract LINE_OFFSET to get the user-relative number, then clamp.\
|
|
6941
7170
|
\t\t-- Clamping matters for unclosed constructs ("local x = (") where the\
|
|
6942
7171
|
\t\t-- parser keeps reading into wrapper postamble and reports a payload\
|
|
6943
|
-
\t\t-- line past user EOF. Without clamping
|
|
6944
|
-
\t\t--
|
|
7172
|
+
\t\t-- line past user EOF. Without clamping, that frames wrapper postamble\
|
|
7173
|
+
\t\t-- as user code.\
|
|
6945
7174
|
\t\tlocal function __mcp_user_line(payload_n)\
|
|
6946
7175
|
\t\t\tlocal user_n = payload_n - __mcp_LINE_OFFSET\
|
|
6947
7176
|
\t\t\tif user_n < 1 then return "1" end\
|
|
6948
7177
|
\t\t\tif user_n > __mcp_USER_LINES then return tostring(__mcp_USER_LINES) .. " (at end of input)" end\
|
|
6949
7178
|
\t\t\treturn tostring(user_n)\
|
|
6950
7179
|
\t\tend\
|
|
6951
|
-
\t\ts = string.gsub(s, "
|
|
7180
|
+
\t\ts = string.gsub(s, "Workspace%.{payloadPattern}:(%d+)", function(num)\
|
|
7181
|
+
\t\t\tlocal n = tonumber(num)\
|
|
7182
|
+
\t\t\tif n then return "user_code:" .. __mcp_user_line(n) end\
|
|
7183
|
+
\t\t\treturn "user_code:" .. num\
|
|
7184
|
+
\t\tend)\
|
|
7185
|
+
\t\ts = string.gsub(s, "{payloadPattern}:(%d+)", function(num)\
|
|
6952
7186
|
\t\t\tlocal n = tonumber(num)\
|
|
6953
7187
|
\t\t\tif n then return "user_code:" .. __mcp_user_line(n) end\
|
|
6954
7188
|
\t\t\treturn "user_code:" .. num\
|
|
@@ -6998,7 +7232,10 @@ end
|
|
|
6998
7232
|
-- pulling the real compile-error diagnostic out of LogService — that error
|
|
6999
7233
|
-- references the payload module's line number directly, and never passes
|
|
7000
7234
|
-- through the IIFE's runtime wrapper.
|
|
7001
|
-
local function remapPayloadLines(s, userLines)
|
|
7235
|
+
local function remapPayloadLines(s, userLines, payloadInstanceName)
|
|
7236
|
+
if payloadInstanceName == nil then
|
|
7237
|
+
payloadInstanceName = PAYLOAD_INSTANCE_NAME
|
|
7238
|
+
end
|
|
7002
7239
|
-- Mirror of the Lua __mcp_remap inside the wrapper, for paths that
|
|
7003
7240
|
-- don't pass through the IIFE (compile errors recovered from
|
|
7004
7241
|
-- LogService, the immediate loadstring compileError surface). Same
|
|
@@ -7016,8 +7253,9 @@ local function remapPayloadLines(s, userLines)
|
|
|
7016
7253
|
end
|
|
7017
7254
|
return tostring(u)
|
|
7018
7255
|
end
|
|
7256
|
+
local payloadPattern = luaPatternEscape(payloadInstanceName)
|
|
7019
7257
|
local out = s
|
|
7020
|
-
local a = string.gsub(out,
|
|
7258
|
+
local a = string.gsub(out, `Workspace%.{payloadPattern}:(%d+)`, function(num)
|
|
7021
7259
|
local n = tonumber(num)
|
|
7022
7260
|
if n ~= nil then
|
|
7023
7261
|
return `user_code:{userLine(n)}`
|
|
@@ -7025,7 +7263,7 @@ local function remapPayloadLines(s, userLines)
|
|
|
7025
7263
|
return `user_code:{num}`
|
|
7026
7264
|
end)
|
|
7027
7265
|
out = a
|
|
7028
|
-
local b = string.gsub(out,
|
|
7266
|
+
local b = string.gsub(out, `{payloadPattern}:(%d+)`, function(num)
|
|
7029
7267
|
local n = tonumber(num)
|
|
7030
7268
|
if n ~= nil then
|
|
7031
7269
|
return `user_code:{userLine(n)}`
|
|
@@ -7033,8 +7271,16 @@ local function remapPayloadLines(s, userLines)
|
|
|
7033
7271
|
return `user_code:{num}`
|
|
7034
7272
|
end)
|
|
7035
7273
|
out = b
|
|
7036
|
-
|
|
7274
|
+
local c = string.gsub(out, '%[string "[^"]+"%]:(%d+)', function(num)
|
|
7275
|
+
local n = tonumber(num)
|
|
7276
|
+
if n ~= nil then
|
|
7277
|
+
return `user_code:{userLine(n)}`
|
|
7278
|
+
end
|
|
7279
|
+
return `user_code:{num}`
|
|
7280
|
+
end)
|
|
7281
|
+
return c
|
|
7037
7282
|
end
|
|
7283
|
+
local recoverPayloadRequireError
|
|
7038
7284
|
local function runViaModuleScript(wrapped, userLines)
|
|
7039
7285
|
local m = Instance.new("ModuleScript")
|
|
7040
7286
|
m.Name = PAYLOAD_INSTANCE_NAME
|
|
@@ -7054,26 +7300,11 @@ local function runViaModuleScript(wrapped, userLines)
|
|
|
7054
7300
|
end)
|
|
7055
7301
|
m:Destroy()
|
|
7056
7302
|
if not okReq then
|
|
7057
|
-
local errMsg = tostring(reqResult)
|
|
7058
|
-
-- pcall(require, m) collapses parse/compile failures into the canned
|
|
7059
|
-
-- engine string. The real diagnostic was emitted to LogService on the
|
|
7060
|
-
-- next engine frame — give it ~50ms to land then scan backward.
|
|
7061
|
-
if errMsg == "Requested module experienced an error while loading" then
|
|
7062
|
-
task.wait(0.05)
|
|
7063
|
-
local hist = LogService:GetLogHistory()
|
|
7064
|
-
for i = #hist - 1, 0, -1 do
|
|
7065
|
-
local e = hist[i + 1]
|
|
7066
|
-
if e.messageType == Enum.MessageType.MessageError and string.sub(e.message, 1, #PAYLOAD_PATH_PREFIX) == PAYLOAD_PATH_PREFIX then
|
|
7067
|
-
errMsg = e.message
|
|
7068
|
-
break
|
|
7069
|
-
end
|
|
7070
|
-
end
|
|
7071
|
-
end
|
|
7072
7303
|
-- Compile errors reference the payload module's line number directly
|
|
7073
7304
|
-- — remap + clamp to user-relative line numbers so `local x = 1 +`
|
|
7074
7305
|
-- reports :1: instead of :23:, and reports the clamp annotation
|
|
7075
7306
|
-- when the parser ran off the end of user code into wrapper code.
|
|
7076
|
-
error(
|
|
7307
|
+
error(recoverPayloadRequireError(reqResult, userLines, PAYLOAD_INSTANCE_NAME), 0)
|
|
7077
7308
|
end
|
|
7078
7309
|
return reqResult
|
|
7079
7310
|
end
|
|
@@ -7100,6 +7331,44 @@ local function formatReturnValue(value)
|
|
|
7100
7331
|
end
|
|
7101
7332
|
return tostring(value)
|
|
7102
7333
|
end
|
|
7334
|
+
function recoverPayloadRequireError(err, userLines, payloadInstanceName, historyStart)
|
|
7335
|
+
if payloadInstanceName == nil then
|
|
7336
|
+
payloadInstanceName = PAYLOAD_INSTANCE_NAME
|
|
7337
|
+
end
|
|
7338
|
+
if historyStart == nil then
|
|
7339
|
+
historyStart = 0
|
|
7340
|
+
end
|
|
7341
|
+
local errMsg = tostring(err)
|
|
7342
|
+
-- pcall(require, m) collapses parse/compile failures into the canned
|
|
7343
|
+
-- engine string. The real diagnostic is emitted to LogService on the
|
|
7344
|
+
-- next engine frame — give it ~50ms to land then scan backward.
|
|
7345
|
+
if errMsg == REQUIRE_GENERIC_ERROR then
|
|
7346
|
+
task.wait(0.05)
|
|
7347
|
+
local payloadPathPrefix = `Workspace.{payloadInstanceName}:`
|
|
7348
|
+
local hist = LogService:GetLogHistory()
|
|
7349
|
+
local start = math.max(0, historyStart)
|
|
7350
|
+
do
|
|
7351
|
+
local i = #hist - 1
|
|
7352
|
+
local _shouldIncrement = false
|
|
7353
|
+
while true do
|
|
7354
|
+
if _shouldIncrement then
|
|
7355
|
+
i -= 1
|
|
7356
|
+
else
|
|
7357
|
+
_shouldIncrement = true
|
|
7358
|
+
end
|
|
7359
|
+
if not (i >= start) then
|
|
7360
|
+
break
|
|
7361
|
+
end
|
|
7362
|
+
local e = hist[i + 1]
|
|
7363
|
+
if e.messageType == Enum.MessageType.MessageError and string.sub(e.message, 1, #payloadPathPrefix) == payloadPathPrefix then
|
|
7364
|
+
errMsg = e.message
|
|
7365
|
+
break
|
|
7366
|
+
end
|
|
7367
|
+
end
|
|
7368
|
+
end
|
|
7369
|
+
end
|
|
7370
|
+
return remapPayloadLines(errMsg, userLines, payloadInstanceName)
|
|
7371
|
+
end
|
|
7103
7372
|
local function execute(code)
|
|
7104
7373
|
if not (code ~= "" and code) or code == "" then
|
|
7105
7374
|
return {
|
|
@@ -7153,12 +7422,17 @@ local function execute(code)
|
|
|
7153
7422
|
}
|
|
7154
7423
|
end
|
|
7155
7424
|
return {
|
|
7425
|
+
buildWrapper = buildWrapper,
|
|
7426
|
+
countLines = countLines,
|
|
7156
7427
|
execute = execute,
|
|
7428
|
+
formatReturnValue = formatReturnValue,
|
|
7429
|
+
recoverPayloadRequireError = recoverPayloadRequireError,
|
|
7430
|
+
remapPayloadLines = remapPayloadLines,
|
|
7157
7431
|
}
|
|
7158
7432
|
]]></string>
|
|
7159
7433
|
</Properties>
|
|
7160
7434
|
</Item>
|
|
7161
|
-
<Item class="ModuleScript" referent="
|
|
7435
|
+
<Item class="ModuleScript" referent="22">
|
|
7162
7436
|
<Properties>
|
|
7163
7437
|
<string name="Name">Recording</string>
|
|
7164
7438
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -7188,7 +7462,7 @@ return {
|
|
|
7188
7462
|
]]></string>
|
|
7189
7463
|
</Properties>
|
|
7190
7464
|
</Item>
|
|
7191
|
-
<Item class="ModuleScript" referent="
|
|
7465
|
+
<Item class="ModuleScript" referent="23">
|
|
7192
7466
|
<Properties>
|
|
7193
7467
|
<string name="Name">RenderMonitor</string>
|
|
7194
7468
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -7256,12 +7530,12 @@ return {
|
|
|
7256
7530
|
]]></string>
|
|
7257
7531
|
</Properties>
|
|
7258
7532
|
</Item>
|
|
7259
|
-
<Item class="ModuleScript" referent="
|
|
7533
|
+
<Item class="ModuleScript" referent="24">
|
|
7260
7534
|
<Properties>
|
|
7261
7535
|
<string name="Name">RuntimeLogBuffer</string>
|
|
7262
7536
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
7263
7537
|
local TS = require(script.Parent.Parent.include.RuntimeLib)
|
|
7264
|
-
-- Per-
|
|
7538
|
+
-- Per-capture in-memory ring buffer for LogService.MessageOut events.
|
|
7265
7539
|
-- Powers the get_runtime_logs MCP tool. Replaces the out-of-tree LogBuffer
|
|
7266
7540
|
-- primitives + StringValue approach from chrrxs/roblox-mcp-primitives.
|
|
7267
7541
|
--
|
|
@@ -7271,12 +7545,12 @@ local TS = require(script.Parent.Parent.include.RuntimeLib)
|
|
|
7271
7545
|
-- DataModel. The buffer is bounded by a message-byte budget; oldest entries
|
|
7272
7546
|
-- drop when over budget.
|
|
7273
7547
|
--
|
|
7274
|
-
--
|
|
7548
|
+
-- Capture caveat: returned entries reflect which plugin buffer CAPTURED the
|
|
7275
7549
|
-- entry, NOT which peer's script originated the print. LogService reflects
|
|
7276
|
-
-- prints across peers in Studio Play (a server print
|
|
7277
|
-
-- server and client LogService:GetLogHistory())
|
|
7278
|
-
--
|
|
7279
|
-
--
|
|
7550
|
+
-- prints across peers in ordinary Studio Play (a server print can appear in
|
|
7551
|
+
-- server and client LogService:GetLogHistory()). The MCP-side aggregator
|
|
7552
|
+
-- exposes that as capturedBy, and only promotes it to origin peer in
|
|
7553
|
+
-- StudioTestService multiplayer sessions where peer attribution is reliable.
|
|
7280
7554
|
local _services = TS.import(script, script.Parent.Parent, "node_modules", "@rbxts", "services")
|
|
7281
7555
|
local LogService = _services.LogService
|
|
7282
7556
|
local RunService = _services.RunService
|
|
@@ -7340,7 +7614,7 @@ local function detectPeer()
|
|
|
7340
7614
|
end
|
|
7341
7615
|
return "client"
|
|
7342
7616
|
end
|
|
7343
|
-
local function query(opts,
|
|
7617
|
+
local function query(opts, capturedBy)
|
|
7344
7618
|
local _result
|
|
7345
7619
|
if opts.since ~= nil then
|
|
7346
7620
|
-- ▼ ReadonlyArray.filter ▼
|
|
@@ -7411,7 +7685,7 @@ local function query(opts, peer)
|
|
|
7411
7685
|
end
|
|
7412
7686
|
local last = if #entries > 0 then entries[#entries] else nil
|
|
7413
7687
|
local _object = {
|
|
7414
|
-
|
|
7688
|
+
capturedBy = capturedBy,
|
|
7415
7689
|
entries = result,
|
|
7416
7690
|
totalDropped = totalDropped,
|
|
7417
7691
|
}
|
|
@@ -7437,11 +7711,12 @@ return {
|
|
|
7437
7711
|
]]></string>
|
|
7438
7712
|
</Properties>
|
|
7439
7713
|
</Item>
|
|
7440
|
-
<Item class="ModuleScript" referent="
|
|
7714
|
+
<Item class="ModuleScript" referent="25">
|
|
7441
7715
|
<Properties>
|
|
7442
7716
|
<string name="Name">State</string>
|
|
7443
7717
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
7444
|
-
local CURRENT_VERSION = "2.15.
|
|
7718
|
+
local CURRENT_VERSION = "2.15.2"
|
|
7719
|
+
local PLUGIN_VARIANT = "inspector"
|
|
7445
7720
|
local MAX_CONNECTIONS = 5
|
|
7446
7721
|
local BASE_PORT = 58741
|
|
7447
7722
|
local activeTabIndex = 0
|
|
@@ -7519,6 +7794,7 @@ local function getConnections()
|
|
|
7519
7794
|
end
|
|
7520
7795
|
return {
|
|
7521
7796
|
CURRENT_VERSION = CURRENT_VERSION,
|
|
7797
|
+
PLUGIN_VARIANT = PLUGIN_VARIANT,
|
|
7522
7798
|
MAX_CONNECTIONS = MAX_CONNECTIONS,
|
|
7523
7799
|
BASE_PORT = BASE_PORT,
|
|
7524
7800
|
connections = connections,
|
|
@@ -7533,7 +7809,7 @@ return {
|
|
|
7533
7809
|
]]></string>
|
|
7534
7810
|
</Properties>
|
|
7535
7811
|
</Item>
|
|
7536
|
-
<Item class="ModuleScript" referent="
|
|
7812
|
+
<Item class="ModuleScript" referent="26">
|
|
7537
7813
|
<Properties>
|
|
7538
7814
|
<string name="Name">StopPlayMonitor</string>
|
|
7539
7815
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -7678,7 +7954,7 @@ return {
|
|
|
7678
7954
|
]]></string>
|
|
7679
7955
|
</Properties>
|
|
7680
7956
|
</Item>
|
|
7681
|
-
<Item class="ModuleScript" referent="
|
|
7957
|
+
<Item class="ModuleScript" referent="27">
|
|
7682
7958
|
<Properties>
|
|
7683
7959
|
<string name="Name">UI</string>
|
|
7684
7960
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -7691,6 +7967,7 @@ local buttonHover = false
|
|
|
7691
7967
|
local toolbarButton
|
|
7692
7968
|
local toolbarIcons
|
|
7693
7969
|
local lastToolbarIcon
|
|
7970
|
+
local activeBannerKind
|
|
7694
7971
|
local updateToolbarIcon
|
|
7695
7972
|
local function setToolbarButton(btn, icons)
|
|
7696
7973
|
toolbarButton = btn
|
|
@@ -7721,6 +7998,23 @@ local TWEEN_QUICK = TweenInfo.new(0.15, Enum.EasingStyle.Quad, Enum.EasingDirect
|
|
|
7721
7998
|
local function tweenProp(instance, props)
|
|
7722
7999
|
TweenService:Create(instance, TWEEN_QUICK, props):Play()
|
|
7723
8000
|
end
|
|
8001
|
+
local function showBanner(kind, text)
|
|
8002
|
+
activeBannerKind = kind
|
|
8003
|
+
elements.updateBannerText.Text = text
|
|
8004
|
+
elements.updateBanner.Visible = true
|
|
8005
|
+
elements.contentFrame.Position = UDim2.new(0, 8, 0, 92)
|
|
8006
|
+
elements.contentFrame.Size = UDim2.new(1, -16, 1, -100)
|
|
8007
|
+
end
|
|
8008
|
+
local function hideBanner(kind)
|
|
8009
|
+
if kind ~= nil and activeBannerKind ~= kind then
|
|
8010
|
+
return nil
|
|
8011
|
+
end
|
|
8012
|
+
activeBannerKind = nil
|
|
8013
|
+
elements.updateBanner.Visible = false
|
|
8014
|
+
elements.updateBannerText.Text = ""
|
|
8015
|
+
elements.contentFrame.Position = UDim2.new(0, 8, 0, 66)
|
|
8016
|
+
elements.contentFrame.Size = UDim2.new(1, -16, 1, -74)
|
|
8017
|
+
end
|
|
7724
8018
|
local C = {
|
|
7725
8019
|
bg = Color3.fromRGB(14, 14, 14),
|
|
7726
8020
|
card = Color3.fromRGB(22, 22, 22),
|
|
@@ -8422,6 +8716,8 @@ return {
|
|
|
8422
8716
|
startPulseAnimation = startPulseAnimation,
|
|
8423
8717
|
setToolbarButton = setToolbarButton,
|
|
8424
8718
|
updateToolbarIcon = updateToolbarIcon,
|
|
8719
|
+
showBanner = showBanner,
|
|
8720
|
+
hideBanner = hideBanner,
|
|
8425
8721
|
getElements = function()
|
|
8426
8722
|
return elements
|
|
8427
8723
|
end,
|
|
@@ -8429,7 +8725,7 @@ return {
|
|
|
8429
8725
|
]]></string>
|
|
8430
8726
|
</Properties>
|
|
8431
8727
|
</Item>
|
|
8432
|
-
<Item class="ModuleScript" referent="
|
|
8728
|
+
<Item class="ModuleScript" referent="28">
|
|
8433
8729
|
<Properties>
|
|
8434
8730
|
<string name="Name">Utils</string>
|
|
8435
8731
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
@@ -8959,11 +9255,11 @@ return {
|
|
|
8959
9255
|
</Properties>
|
|
8960
9256
|
</Item>
|
|
8961
9257
|
</Item>
|
|
8962
|
-
<Item class="Folder" referent="
|
|
9258
|
+
<Item class="Folder" referent="32">
|
|
8963
9259
|
<Properties>
|
|
8964
9260
|
<string name="Name">include</string>
|
|
8965
9261
|
</Properties>
|
|
8966
|
-
<Item class="ModuleScript" referent="
|
|
9262
|
+
<Item class="ModuleScript" referent="29">
|
|
8967
9263
|
<Properties>
|
|
8968
9264
|
<string name="Name">Promise</string>
|
|
8969
9265
|
<string name="Source"><![CDATA[--[[
|
|
@@ -11037,7 +11333,7 @@ return Promise
|
|
|
11037
11333
|
]]></string>
|
|
11038
11334
|
</Properties>
|
|
11039
11335
|
</Item>
|
|
11040
|
-
<Item class="ModuleScript" referent="
|
|
11336
|
+
<Item class="ModuleScript" referent="30">
|
|
11041
11337
|
<Properties>
|
|
11042
11338
|
<string name="Name">RuntimeLib</string>
|
|
11043
11339
|
<string name="Source"><![CDATA[local Promise = require(script.Parent.Promise)
|
|
@@ -11304,15 +11600,15 @@ return TS
|
|
|
11304
11600
|
</Properties>
|
|
11305
11601
|
</Item>
|
|
11306
11602
|
</Item>
|
|
11307
|
-
<Item class="Folder" referent="
|
|
11603
|
+
<Item class="Folder" referent="33">
|
|
11308
11604
|
<Properties>
|
|
11309
11605
|
<string name="Name">node_modules</string>
|
|
11310
11606
|
</Properties>
|
|
11311
|
-
<Item class="Folder" referent="
|
|
11607
|
+
<Item class="Folder" referent="34">
|
|
11312
11608
|
<Properties>
|
|
11313
11609
|
<string name="Name">@rbxts</string>
|
|
11314
11610
|
</Properties>
|
|
11315
|
-
<Item class="ModuleScript" referent="
|
|
11611
|
+
<Item class="ModuleScript" referent="31">
|
|
11316
11612
|
<Properties>
|
|
11317
11613
|
<string name="Name">services</string>
|
|
11318
11614
|
<string name="Source"><![CDATA[return setmetatable({}, {
|