@chrrxs/robloxstudio-mcp 3.0.4 → 3.0.5
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/package.json +1 -1
- package/studio-plugin/MCPPlugin.rbxmx +85 -22
package/package.json
CHANGED
|
@@ -16,6 +16,7 @@ local cleanupEditBridgeArtifacts = _EvalBridges.cleanupEditBridgeArtifacts
|
|
|
16
16
|
local ensureRuntimeBridgeInstalled = _EvalBridges.ensureRuntimeBridgeInstalled
|
|
17
17
|
local RuntimeLogBuffer = TS.import(script, script, "modules", "RuntimeLogBuffer")
|
|
18
18
|
local StopPlayMonitor = TS.import(script, script, "modules", "StopPlayMonitor")
|
|
19
|
+
local StudioEventStream = TS.import(script, script, "modules", "StudioEventStream")
|
|
19
20
|
local BreakpointHandlers = TS.import(script, script, "modules", "handlers", "BreakpointHandlers")
|
|
20
21
|
local RenderMonitor = TS.import(script, script, "modules", "RenderMonitor")
|
|
21
22
|
-- Track render-loop liveness so input/screenshot tools can report "window
|
|
@@ -32,8 +33,9 @@ RuntimeLogBuffer.install()
|
|
|
32
33
|
StopPlayMonitor.init(plugin)
|
|
33
34
|
BreakpointHandlers.init(plugin)
|
|
34
35
|
ServerUrlSettings.init(plugin)
|
|
36
|
+
local startupRole = ClientBroker.forkRole()
|
|
35
37
|
local function applyRememberedServerUrl()
|
|
36
|
-
if
|
|
38
|
+
if startupRole == "client" then
|
|
37
39
|
return nil
|
|
38
40
|
end
|
|
39
41
|
local rememberedServerUrl = ServerUrlSettings.readServerUrl()
|
|
@@ -49,6 +51,15 @@ local function applyRememberedServerUrl()
|
|
|
49
51
|
ClientBroker.setServerUrl(rememberedServerUrl)
|
|
50
52
|
end
|
|
51
53
|
applyRememberedServerUrl()
|
|
54
|
+
-- Play DataModels do not reliably fire Plugin.Unloading before Studio tears
|
|
55
|
+
-- down their VM. Close the persistent WebStreamClient while BindToClose still
|
|
56
|
+
-- gives this server peer a live execution context; otherwise each play cycle
|
|
57
|
+
-- can retain one native event stream until Studio itself is restarted. The
|
|
58
|
+
-- transport releases that native stream before yielding to unregister the
|
|
59
|
+
-- logical server peer.
|
|
60
|
+
if startupRole == "server" then
|
|
61
|
+
game:BindToClose(StudioEventStream.suspendForShutdown)
|
|
62
|
+
end
|
|
52
63
|
UI.init(plugin)
|
|
53
64
|
local elements = UI.getElements()
|
|
54
65
|
local ICON_DISCONNECTED = "rbxassetid://75876056391496"
|
|
@@ -91,7 +102,7 @@ task.delay(TOOLBAR_REGISTRATION_DELAY_SECONDS, registerToolbarButton)
|
|
|
91
102
|
-- play peers' plugin instances load without ever registering. Run after a
|
|
92
103
|
-- short delay so the UI/State have a chance to initialize first.
|
|
93
104
|
task.delay(2, function()
|
|
94
|
-
local role =
|
|
105
|
+
local role = startupRole
|
|
95
106
|
if role == "edit" then
|
|
96
107
|
cleanupEditBridgeArtifacts()
|
|
97
108
|
else
|
|
@@ -133,7 +144,10 @@ task.delay(2, function()
|
|
|
133
144
|
-- The play-server DM is the only one where StudioTestService:EndTest is
|
|
134
145
|
-- legal, so the stop-play monitor lives here. It consumes tokenized
|
|
135
146
|
-- stop requests from plugin settings and acknowledges EndTest results.
|
|
136
|
-
StopPlayMonitor.startMonitor(
|
|
147
|
+
StopPlayMonitor.startMonitor({
|
|
148
|
+
beforeEndTest = StudioEventStream.suspendForShutdown,
|
|
149
|
+
afterEndTestFailure = StudioEventStream.resumeAfterShutdownFailure,
|
|
150
|
+
})
|
|
137
151
|
elseif role == "client" then
|
|
138
152
|
ClientBroker.setupClientBroker()
|
|
139
153
|
end
|
|
@@ -1348,9 +1362,9 @@ local function computeBridgeStamp()
|
|
|
1348
1362
|
for i = 1, #combined do
|
|
1349
1363
|
h = (h * 33 + (string.byte(combined, i))) % 2147483647
|
|
1350
1364
|
end
|
|
1351
|
-
-- "3.0.
|
|
1365
|
+
-- "3.0.5" is replaced with the package version at package time
|
|
1352
1366
|
-- (scripts/build-plugin.mjs injectVersion), so a release bump also restamps.
|
|
1353
|
-
return `{tostring(h)}-3.0.
|
|
1367
|
+
return `{tostring(h)}-3.0.5`
|
|
1354
1368
|
end
|
|
1355
1369
|
local BRIDGE_STAMP = computeBridgeStamp()
|
|
1356
1370
|
local function setSource(scriptInst, source)
|
|
@@ -9566,7 +9580,7 @@ return {
|
|
|
9566
9580
|
<Properties>
|
|
9567
9581
|
<string name="Name">State</string>
|
|
9568
9582
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
9569
|
-
local CURRENT_VERSION = "3.0.
|
|
9583
|
+
local CURRENT_VERSION = "3.0.5"
|
|
9570
9584
|
local PLUGIN_VARIANT = "main"
|
|
9571
9585
|
local BASE_PORT = 58741
|
|
9572
9586
|
local function createConnection(port)
|
|
@@ -9646,6 +9660,7 @@ local WAIT_POLL_SEC = 0.1
|
|
|
9646
9660
|
local REQUEST_TTL_SEC = 12.0
|
|
9647
9661
|
local pluginRef
|
|
9648
9662
|
local endTestIssued = false
|
|
9663
|
+
local transportLifecycle
|
|
9649
9664
|
local function init(p)
|
|
9650
9665
|
pluginRef = p
|
|
9651
9666
|
end
|
|
@@ -9789,15 +9804,29 @@ local function handleStopRequest(key, request)
|
|
|
9789
9804
|
return nil
|
|
9790
9805
|
end
|
|
9791
9806
|
endTestIssued = true
|
|
9807
|
+
local lifecycle = transportLifecycle
|
|
9808
|
+
if lifecycle ~= nil then
|
|
9809
|
+
local closeOk, closeError = pcall(lifecycle.beforeEndTest)
|
|
9810
|
+
if not closeOk then
|
|
9811
|
+
warn(`[robloxstudio-mcp] Failed to close the play-server transport before EndTest: {tostring(closeError)}`)
|
|
9812
|
+
end
|
|
9813
|
+
end
|
|
9792
9814
|
local endOk, endErr = pcall(function()
|
|
9793
9815
|
return StudioTestService:EndTest("stopped_by_mcp")
|
|
9794
9816
|
end)
|
|
9795
9817
|
writeResult(key, request, endOk, if endOk then nil else tostring(endErr))
|
|
9796
9818
|
if not endOk then
|
|
9797
9819
|
endTestIssued = false
|
|
9820
|
+
if lifecycle ~= nil then
|
|
9821
|
+
local restoreOk, restoreError = pcall(lifecycle.afterEndTestFailure)
|
|
9822
|
+
if not restoreOk then
|
|
9823
|
+
warn(`[robloxstudio-mcp] Failed to restore the play-server transport after EndTest failed: {tostring(restoreError)}`)
|
|
9824
|
+
end
|
|
9825
|
+
end
|
|
9798
9826
|
end
|
|
9799
9827
|
end
|
|
9800
|
-
local function startMonitor()
|
|
9828
|
+
local function startMonitor(lifecycle)
|
|
9829
|
+
transportLifecycle = lifecycle
|
|
9801
9830
|
if not pluginRef then
|
|
9802
9831
|
warn("[robloxstudio-mcp] StopPlayMonitor.startMonitor called before init; skipping")
|
|
9803
9832
|
return nil
|
|
@@ -9903,6 +9932,7 @@ local MAX_TERMINAL_RESPONSES = 256
|
|
|
9903
9932
|
local STREAM_SILENCE_TIMEOUT_SECONDS = 20
|
|
9904
9933
|
local options
|
|
9905
9934
|
local active = false
|
|
9935
|
+
local shutdownSuspended = false
|
|
9906
9936
|
local generation = 0
|
|
9907
9937
|
local reconnectAttempt = 0
|
|
9908
9938
|
local streamClient
|
|
@@ -10035,6 +10065,21 @@ local function closeCurrentStream()
|
|
|
10035
10065
|
end)
|
|
10036
10066
|
end
|
|
10037
10067
|
end
|
|
10068
|
+
local function disconnectSession(currentOptions)
|
|
10069
|
+
pcall(function()
|
|
10070
|
+
return HttpService:RequestAsync({
|
|
10071
|
+
Url = `{currentOptions.serverUrl}/disconnect`,
|
|
10072
|
+
Method = "POST",
|
|
10073
|
+
Headers = {
|
|
10074
|
+
["Content-Type"] = "application/json",
|
|
10075
|
+
},
|
|
10076
|
+
Body = HttpService:JSONEncode({
|
|
10077
|
+
pluginSessionId = PluginSession.id,
|
|
10078
|
+
timestamp = tick(),
|
|
10079
|
+
}),
|
|
10080
|
+
})
|
|
10081
|
+
end)
|
|
10082
|
+
end
|
|
10038
10083
|
local function responseRetryDelay(attempt)
|
|
10039
10084
|
return math.min(INITIAL_RESPONSE_RETRY_DELAY_SECONDS * math.pow(2, math.max(attempt - 1, 0)), MAX_RESPONSE_RETRY_DELAY_SECONDS)
|
|
10040
10085
|
end
|
|
@@ -10523,11 +10568,12 @@ function connect(expectedGeneration)
|
|
|
10523
10568
|
end
|
|
10524
10569
|
local stop
|
|
10525
10570
|
local function start(newOptions)
|
|
10526
|
-
if active then
|
|
10571
|
+
if active or shutdownSuspended then
|
|
10527
10572
|
stop()
|
|
10528
10573
|
end
|
|
10529
10574
|
options = newOptions
|
|
10530
10575
|
active = true
|
|
10576
|
+
shutdownSuspended = false
|
|
10531
10577
|
reconnectAttempt = 0
|
|
10532
10578
|
generation += 1
|
|
10533
10579
|
connect(generation)
|
|
@@ -10541,12 +10587,39 @@ function refresh()
|
|
|
10541
10587
|
reconnectAttempt = 0
|
|
10542
10588
|
connect(generation)
|
|
10543
10589
|
end
|
|
10590
|
+
-- EndTest can tear down a play DataModel without giving Plugin.Unloading a
|
|
10591
|
+
-- usable execution window. Release the native WebStreamClient before the
|
|
10592
|
+
-- yielding unregister request, but retain local request state and connection
|
|
10593
|
+
-- options so a failed EndTest can register again and reconnect.
|
|
10594
|
+
local function suspendForShutdown()
|
|
10595
|
+
local currentOptions = options
|
|
10596
|
+
if not active or currentOptions == nil then
|
|
10597
|
+
return nil
|
|
10598
|
+
end
|
|
10599
|
+
active = false
|
|
10600
|
+
shutdownSuspended = true
|
|
10601
|
+
generation += 1
|
|
10602
|
+
reconnectAttempt = 0
|
|
10603
|
+
closeCurrentStream()
|
|
10604
|
+
disconnectSession(currentOptions)
|
|
10605
|
+
end
|
|
10606
|
+
local function resumeAfterShutdownFailure()
|
|
10607
|
+
if not shutdownSuspended or options == nil then
|
|
10608
|
+
return nil
|
|
10609
|
+
end
|
|
10610
|
+
shutdownSuspended = false
|
|
10611
|
+
active = true
|
|
10612
|
+
generation += 1
|
|
10613
|
+
reconnectAttempt = 0
|
|
10614
|
+
connect(generation)
|
|
10615
|
+
end
|
|
10544
10616
|
function stop()
|
|
10545
|
-
if not active then
|
|
10617
|
+
if not active and not shutdownSuspended then
|
|
10546
10618
|
return nil
|
|
10547
10619
|
end
|
|
10548
10620
|
local currentOptions = options
|
|
10549
10621
|
active = false
|
|
10622
|
+
shutdownSuspended = false
|
|
10550
10623
|
generation += 1
|
|
10551
10624
|
for _, inFlight in inFlightRequests do
|
|
10552
10625
|
inFlight.cancelled = true
|
|
@@ -10558,24 +10631,14 @@ function stop()
|
|
|
10558
10631
|
options = nil
|
|
10559
10632
|
reconnectAttempt = 0
|
|
10560
10633
|
if currentOptions ~= nil then
|
|
10561
|
-
|
|
10562
|
-
return HttpService:RequestAsync({
|
|
10563
|
-
Url = `{currentOptions.serverUrl}/disconnect`,
|
|
10564
|
-
Method = "POST",
|
|
10565
|
-
Headers = {
|
|
10566
|
-
["Content-Type"] = "application/json",
|
|
10567
|
-
},
|
|
10568
|
-
Body = HttpService:JSONEncode({
|
|
10569
|
-
pluginSessionId = PluginSession.id,
|
|
10570
|
-
timestamp = tick(),
|
|
10571
|
-
}),
|
|
10572
|
-
})
|
|
10573
|
-
end)
|
|
10634
|
+
disconnectSession(currentOptions)
|
|
10574
10635
|
end
|
|
10575
10636
|
end
|
|
10576
10637
|
return {
|
|
10577
10638
|
start = start,
|
|
10578
10639
|
refresh = refresh,
|
|
10640
|
+
suspendForShutdown = suspendForShutdown,
|
|
10641
|
+
resumeAfterShutdownFailure = resumeAfterShutdownFailure,
|
|
10579
10642
|
stop = stop,
|
|
10580
10643
|
}
|
|
10581
10644
|
]]></string>
|