@chrrxs/robloxstudio-mcp 2.16.4 → 2.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -16,6 +16,7 @@ local cleanupLegacyEditBridges = _EvalBridges.cleanupLegacyEditBridges
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 BreakpointHandlers = TS.import(script, script, "modules", "handlers", "BreakpointHandlers")
19
20
  local RenderMonitor = TS.import(script, script, "modules", "RenderMonitor")
20
21
  -- Track render-loop liveness so input/screenshot tools can report "window
21
22
  -- minimized / not rendering" instead of silently no-op'ing. No-op in the
@@ -29,7 +30,22 @@ RuntimeLogBuffer.install()
29
30
  -- edit DM (write the flag) and the play-server DM (read+act on the flag) can
30
31
  -- access plugin:SetSetting/GetSetting.
31
32
  StopPlayMonitor.init(plugin)
33
+ BreakpointHandlers.init(plugin)
32
34
  ServerUrlSettings.init(plugin)
35
+ local function applyRememberedServerUrl()
36
+ local rememberedServerUrl = ServerUrlSettings.readServerUrl()
37
+ if rememberedServerUrl == nil then
38
+ return nil
39
+ end
40
+ local conn = State.getActiveConnection()
41
+ conn.serverUrl = rememberedServerUrl
42
+ local port = ServerUrlSettings.extractPort(rememberedServerUrl)
43
+ if port ~= nil then
44
+ conn.port = port
45
+ end
46
+ ClientBroker.setServerUrl(rememberedServerUrl)
47
+ end
48
+ applyRememberedServerUrl()
33
49
  UI.init(plugin)
34
50
  local elements = UI.getElements()
35
51
  local ICON_DISCONNECTED = "rbxassetid://125921838360800"
@@ -92,17 +108,13 @@ task.delay(2, function()
92
108
  _condition = ClientBroker.DEFAULT_MCP_URL
93
109
  end
94
110
  local inheritedServerUrl = _condition
95
- conn.serverUrl = inheritedServerUrl
96
- elements.urlInput.Text = inheritedServerUrl
97
- local portStr = string.match(conn.serverUrl, ":(%d+)$")
98
- if portStr ~= 0 and portStr == portStr and portStr ~= "" and portStr then
99
- local _condition_1 = tonumber(portStr)
100
- if _condition_1 == nil then
101
- _condition_1 = conn.port
102
- end
103
- conn.port = _condition_1
111
+ conn.serverUrl = ServerUrlSettings.normalizeServerUrl(inheritedServerUrl)
112
+ elements.urlInput.Text = conn.serverUrl
113
+ local port = ServerUrlSettings.extractPort(conn.serverUrl)
114
+ if port ~= nil then
115
+ conn.port = port
104
116
  end
105
- ClientBroker.setServerUrl(inheritedServerUrl)
117
+ ClientBroker.setServerUrl(conn.serverUrl)
106
118
  end
107
119
  -- Defensive default: in invisible play-DM UIs, the input field
108
120
  -- may not be populated by the time we activate.
@@ -147,6 +159,8 @@ local SceneAnalysisHandlers = TS.import(script, script.Parent, "handlers", "Scen
147
159
  local CaptureHandlers = TS.import(script, script.Parent, "handlers", "CaptureHandlers")
148
160
  local InputHandlers = TS.import(script, script.Parent, "handlers", "InputHandlers")
149
161
  local EvalRuntimeHandlers = TS.import(script, script.Parent, "handlers", "EvalRuntimeHandlers")
162
+ local BreakpointHandlers = TS.import(script, script.Parent, "handlers", "BreakpointHandlers")
163
+ local ScriptProfilerHandlers = TS.import(script, script.Parent, "handlers", "ScriptProfilerHandlers")
150
164
  local LuauExec = TS.import(script, script.Parent, "LuauExec")
151
165
  local State = TS.import(script, script.Parent, "State")
152
166
  local HttpDiagnostics = TS.import(script, script.Parent, "HttpDiagnostics")
@@ -215,6 +229,8 @@ local CLIENT_BROKER_ALLOWED_ENDPOINTS = {
215
229
  ["/api/get-runtime-logs"] = true,
216
230
  ["/api/get-memory-breakdown"] = true,
217
231
  ["/api/get-scene-analysis"] = true,
232
+ ["/api/breakpoints"] = true,
233
+ ["/api/capture-script-profiler"] = true,
218
234
  ["/api/multiplayer-test-state"] = true,
219
235
  ["/api/multiplayer-test-leave-client"] = true,
220
236
  ["/api/capture-begin"] = true,
@@ -404,6 +420,12 @@ local function setupClientBroker()
404
420
  if payload and payload.endpoint == "/api/get-scene-analysis" then
405
421
  return SceneAnalysisHandlers.getSceneAnalysis(payload.data or {})
406
422
  end
423
+ if payload and payload.endpoint == "/api/breakpoints" then
424
+ return BreakpointHandlers.breakpoints(payload.data or {})
425
+ end
426
+ if payload and payload.endpoint == "/api/capture-script-profiler" then
427
+ return ScriptProfilerHandlers.captureScriptProfiler(payload.data or {})
428
+ end
407
429
  if payload and payload.endpoint == "/api/multiplayer-test-state" then
408
430
  return handleMultiplayerTestState()
409
431
  end
@@ -650,6 +672,8 @@ local LogHandlers = TS.import(script, script.Parent, "handlers", "LogHandlers")
650
672
  local SerializationHandlers = TS.import(script, script.Parent, "handlers", "SerializationHandlers")
651
673
  local MemoryHandlers = TS.import(script, script.Parent, "handlers", "MemoryHandlers")
652
674
  local SceneAnalysisHandlers = TS.import(script, script.Parent, "handlers", "SceneAnalysisHandlers")
675
+ local BreakpointHandlers = TS.import(script, script.Parent, "handlers", "BreakpointHandlers")
676
+ local ScriptProfilerHandlers = TS.import(script, script.Parent, "handlers", "ScriptProfilerHandlers")
653
677
  local EvalRuntimeHandlers = TS.import(script, script.Parent, "handlers", "EvalRuntimeHandlers")
654
678
  local ClientBroker = TS.import(script, script.Parent, "ClientBroker")
655
679
  local ServerUrlSettings = TS.import(script, script.Parent, "ServerUrlSettings")
@@ -739,7 +763,6 @@ local routeMap = {
739
763
  ["/api/grep-scripts"] = QueryHandlers.grepScripts,
740
764
  ["/api/get-descendants"] = QueryHandlers.getDescendants,
741
765
  ["/api/compare-instances"] = QueryHandlers.compareInstances,
742
- ["/api/get-output-log"] = QueryHandlers.getOutputLog,
743
766
  ["/api/set-property"] = PropertyHandlers.setProperty,
744
767
  ["/api/set-properties"] = PropertyHandlers.setProperties,
745
768
  ["/api/mass-set-property"] = PropertyHandlers.massSetProperty,
@@ -771,13 +794,11 @@ local routeMap = {
771
794
  ["/api/bulk-set-attributes"] = MetadataHandlers.bulkSetAttributes,
772
795
  ["/api/start-playtest"] = TestHandlers.startPlaytest,
773
796
  ["/api/stop-playtest"] = TestHandlers.stopPlaytest,
774
- ["/api/get-playtest-output"] = TestHandlers.getPlaytestOutput,
775
797
  ["/api/multiplayer-test-start"] = TestHandlers.multiplayerTestStart,
776
798
  ["/api/multiplayer-test-state"] = TestHandlers.multiplayerTestState,
777
799
  ["/api/multiplayer-test-add-players"] = TestHandlers.multiplayerTestAddPlayers,
778
800
  ["/api/multiplayer-test-leave-client"] = TestHandlers.multiplayerTestLeaveClient,
779
801
  ["/api/multiplayer-test-end"] = TestHandlers.multiplayerTestEnd,
780
- ["/api/character-navigation"] = TestHandlers.characterNavigation,
781
802
  ["/api/export-build"] = BuildHandlers.exportBuild,
782
803
  ["/api/import-build"] = BuildHandlers.importBuild,
783
804
  ["/api/import-scene"] = BuildHandlers.importScene,
@@ -791,6 +812,8 @@ local routeMap = {
791
812
  ["/api/simulate-keyboard-input"] = InputHandlers.simulateKeyboardInput,
792
813
  ["/api/find-and-replace-in-scripts"] = ScriptHandlers.findAndReplaceInScripts,
793
814
  ["/api/get-runtime-logs"] = LogHandlers.getRuntimeLogs,
815
+ ["/api/breakpoints"] = BreakpointHandlers.breakpoints,
816
+ ["/api/capture-script-profiler"] = ScriptProfilerHandlers.captureScriptProfiler,
794
817
  ["/api/export-rbxm"] = SerializationHandlers.exportRbxm,
795
818
  ["/api/import-rbxm"] = SerializationHandlers.importRbxm,
796
819
  ["/api/get-memory-breakdown"] = MemoryHandlers.getMemoryBreakdown,
@@ -948,6 +971,7 @@ function sendReady(conn)
948
971
  end
949
972
  end
950
973
  lastReadyInstanceId = if _condition then readyData.instanceId else instanceId
974
+ ServerUrlSettings.rememberServerUrl(conn.serverUrl)
951
975
  local _condition_1 = assignedRole
952
976
  if _condition_1 == nil then
953
977
  _condition_1 = detectRole()
@@ -1158,19 +1182,19 @@ local function activatePlugin(connIndex)
1158
1182
  conn.consecutiveFailures = 0
1159
1183
  conn.currentRetryDelay = 0.5
1160
1184
  if idx == State.getActiveTabIndex() then
1161
- conn.serverUrl = ui.urlInput.Text
1162
- local portStr = string.match(conn.serverUrl, ":(%d+)$")
1163
- if portStr ~= 0 and portStr == portStr and portStr ~= "" and portStr then
1164
- local _condition_1 = tonumber(portStr)
1165
- if _condition_1 == nil then
1166
- _condition_1 = conn.port
1167
- end
1168
- conn.port = _condition_1
1185
+ local normalizedUrl = ServerUrlSettings.normalizeServerUrl(ui.urlInput.Text)
1186
+ conn.serverUrl = if normalizedUrl ~= "" then normalizedUrl else conn.serverUrl
1187
+ if conn.serverUrl == "" then
1188
+ conn.serverUrl = ClientBroker.DEFAULT_MCP_URL
1189
+ end
1190
+ ui.urlInput.Text = conn.serverUrl
1191
+ local port = ServerUrlSettings.extractPort(conn.serverUrl)
1192
+ if port ~= nil then
1193
+ conn.port = port
1169
1194
  end
1170
1195
  UI.updateTabLabel(idx)
1171
1196
  UI.updateUIState()
1172
1197
  end
1173
- ServerUrlSettings.rememberServerUrl(conn.serverUrl)
1174
1198
  UI.updateTabDot(idx)
1175
1199
  if not conn.heartbeatConnection then
1176
1200
  conn.heartbeatConnection = RunService.Heartbeat:Connect(function()
@@ -1411,9 +1435,9 @@ local function computeBridgeStamp()
1411
1435
  for i = 1, #combined do
1412
1436
  h = (h * 33 + (string.byte(combined, i))) % 2147483647
1413
1437
  end
1414
- -- "2.16.4" is replaced with the package version at package time
1438
+ -- "2.17.1" is replaced with the package version at package time
1415
1439
  -- (scripts/build-plugin.mjs injectVersion), so a release bump also restamps.
1416
- return `{tostring(h)}-2.16.4`
1440
+ return `{tostring(h)}-2.17.1`
1417
1441
  end
1418
1442
  local BRIDGE_STAMP = computeBridgeStamp()
1419
1443
  local function setSource(scriptInst, source)
@@ -1857,6 +1881,524 @@ return {
1857
1881
  </Properties>
1858
1882
  </Item>
1859
1883
  <Item class="ModuleScript" referent="7">
1884
+ <Properties>
1885
+ <string name="Name">BreakpointHandlers</string>
1886
+ <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
1887
+ local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
1888
+ local Utils = TS.import(script, script.Parent.Parent, "Utils")
1889
+ local _binding = Utils
1890
+ local getInstanceByPath = _binding.getInstanceByPath
1891
+ local HttpService = game:GetService("HttpService")
1892
+ local RunService = game:GetService("RunService")
1893
+ local ServerStorage = game:GetService("ServerStorage")
1894
+ local LOG_PREFIX = "Breakpoint"
1895
+ local REGISTRY_KEY_PREFIX = "MCP_BREAKPOINTS_V1_"
1896
+ local MCP_PLACE_ID_ATTRIBUTE = "__MCPPlaceId"
1897
+ local pluginRef
1898
+ local loadedRegistryKey
1899
+ local loadedRegistryFromSettings = false
1900
+ local breakpoints = {}
1901
+ local function init(p)
1902
+ pluginRef = p
1903
+ end
1904
+ local function breakpointKey(scriptPath, line)
1905
+ return `{scriptPath}:{line}`
1906
+ end
1907
+ local function computeInstanceId()
1908
+ if game.PlaceId ~= 0 then
1909
+ return `place:{tostring(game.PlaceId)}`
1910
+ end
1911
+ local existing = ServerStorage:GetAttribute(MCP_PLACE_ID_ATTRIBUTE)
1912
+ if type(existing) == "string" and existing ~= "" then
1913
+ return `anon:{existing}`
1914
+ end
1915
+ local fresh = HttpService:GenerateGUID(false)
1916
+ pcall(function()
1917
+ return ServerStorage:SetAttribute(MCP_PLACE_ID_ATTRIBUTE, fresh)
1918
+ end)
1919
+ return `anon:{fresh}`
1920
+ end
1921
+ local function detectRole()
1922
+ if not RunService:IsRunning() then
1923
+ return "edit"
1924
+ end
1925
+ if RunService:IsServer() then
1926
+ return "server"
1927
+ end
1928
+ return "client"
1929
+ end
1930
+ local function requestedRole(requestData)
1931
+ local ___mcp_target_role = requestData.__mcp_target_role
1932
+ local _condition = type(___mcp_target_role) == "string"
1933
+ if _condition then
1934
+ _condition = requestData.__mcp_target_role ~= ""
1935
+ end
1936
+ return if _condition then requestData.__mcp_target_role else detectRole()
1937
+ end
1938
+ local function registryScope(requestData)
1939
+ local ___mcp_instance_id = requestData.__mcp_instance_id
1940
+ local _condition = type(___mcp_instance_id) == "string"
1941
+ if _condition then
1942
+ _condition = requestData.__mcp_instance_id ~= ""
1943
+ end
1944
+ local instanceId = if _condition then requestData.__mcp_instance_id else computeInstanceId()
1945
+ local role = requestedRole(requestData)
1946
+ return {
1947
+ key = `{REGISTRY_KEY_PREFIX}{instanceId}:{role}`,
1948
+ }
1949
+ end
1950
+ local function readSetting(key)
1951
+ if not pluginRef then
1952
+ return nil
1953
+ end
1954
+ local ok, value = pcall(function()
1955
+ return pluginRef:GetSetting(key)
1956
+ end)
1957
+ return if ok then value else nil
1958
+ end
1959
+ local function writeSetting(key, value)
1960
+ if not pluginRef then
1961
+ return false
1962
+ end
1963
+ local ok = pcall(function()
1964
+ return pluginRef:SetSetting(key, value)
1965
+ end)
1966
+ return ok
1967
+ end
1968
+ local function decodePersistedBreakpointEntry(value)
1969
+ local _value = value
1970
+ if not (type(_value) == "table") then
1971
+ return nil
1972
+ end
1973
+ local data = value
1974
+ local _script_path = data.script_path
1975
+ local _condition = not (type(_script_path) == "string")
1976
+ if not _condition then
1977
+ _condition = data.script_path == ""
1978
+ end
1979
+ if _condition then
1980
+ return nil
1981
+ end
1982
+ local _line = data.line
1983
+ local _condition_1 = not (type(_line) == "number")
1984
+ if not _condition_1 then
1985
+ _condition_1 = data.line < 1
1986
+ end
1987
+ if _condition_1 then
1988
+ return nil
1989
+ end
1990
+ return {
1991
+ script_path = data.script_path,
1992
+ line = math.floor(data.line),
1993
+ }
1994
+ end
1995
+ local function loadRegistry(requestData)
1996
+ local scope = registryScope(requestData)
1997
+ if loadedRegistryKey ~= scope.key then
1998
+ table.clear(breakpoints)
1999
+ loadedRegistryKey = scope.key
2000
+ loadedRegistryFromSettings = false
2001
+ end
2002
+ if loadedRegistryFromSettings then
2003
+ return scope
2004
+ end
2005
+ loadedRegistryFromSettings = true
2006
+ local stored = readSetting(scope.key)
2007
+ if stored == nil then
2008
+ return scope
2009
+ end
2010
+ local decoded = stored
2011
+ if type(stored) == "string" then
2012
+ local ok, result = pcall(function()
2013
+ return HttpService:JSONDecode(stored)
2014
+ end)
2015
+ if not ok then
2016
+ return scope
2017
+ end
2018
+ decoded = result
2019
+ end
2020
+ local _decoded = decoded
2021
+ if not (type(_decoded) == "table") then
2022
+ return scope
2023
+ end
2024
+ table.clear(breakpoints)
2025
+ for _, item in decoded do
2026
+ local entry = decodePersistedBreakpointEntry(item)
2027
+ if entry then
2028
+ local _arg0 = breakpointKey(entry.script_path, entry.line)
2029
+ breakpoints[_arg0] = entry
2030
+ end
2031
+ end
2032
+ return scope
2033
+ end
2034
+ local function persistRegistry(scope)
2035
+ if not pluginRef then
2036
+ return {
2037
+ ok = false,
2038
+ error = "Plugin settings are unavailable; managed breakpoint registry is memory-only.",
2039
+ }
2040
+ end
2041
+ local out = {}
2042
+ for _, entry in breakpoints do
2043
+ local _arg0 = {
2044
+ script_path = entry.script_path,
2045
+ line = entry.line,
2046
+ }
2047
+ table.insert(out, _arg0)
2048
+ end
2049
+ local encodedOk, encoded = pcall(function()
2050
+ return HttpService:JSONEncode(out)
2051
+ end)
2052
+ if not encodedOk or not (type(encoded) == "string") then
2053
+ return {
2054
+ ok = false,
2055
+ error = `Failed to encode managed breakpoint registry: {tostring(encoded)}`,
2056
+ }
2057
+ end
2058
+ if not writeSetting(scope.key, encoded) then
2059
+ return {
2060
+ ok = false,
2061
+ error = "Failed to persist managed breakpoint registry with plugin:SetSetting.",
2062
+ }
2063
+ end
2064
+ local stored = readSetting(scope.key)
2065
+ if stored ~= encoded then
2066
+ return {
2067
+ ok = false,
2068
+ error = "Failed to verify managed breakpoint registry persistence after plugin:SetSetting.",
2069
+ }
2070
+ end
2071
+ return {
2072
+ ok = true,
2073
+ }
2074
+ end
2075
+ local function attachPersistenceWarning(response, persist)
2076
+ if not persist.ok then
2077
+ response.managed_registry_persisted = false
2078
+ response.registry_error = persist.error
2079
+ end
2080
+ return response
2081
+ end
2082
+ local function serviceError(message)
2083
+ local _object = {
2084
+ error = "script_debugger_unavailable",
2085
+ }
2086
+ local _left = "message"
2087
+ local _condition = message
2088
+ if _condition == nil then
2089
+ _condition = "ScriptDebuggerService is unavailable. Enable the Studio Debugger Luau API beta feature and restart Studio."
2090
+ end
2091
+ _object[_left] = _condition
2092
+ _object.betaFeatureRequired = true
2093
+ return _object
2094
+ end
2095
+ local function operationError(errorCode, operation, raw)
2096
+ return {
2097
+ error = errorCode,
2098
+ message = `{operation} failed. The breakpoints tool requires the Studio Debugger Luau API beta feature. ` .. "Enable it in Studio Beta Features and restart/reload Studio, then retry.",
2099
+ rawMessage = tostring(raw),
2100
+ betaFeatureRequired = true,
2101
+ }
2102
+ end
2103
+ local function getService()
2104
+ local provider = game
2105
+ local ok, service = pcall(function()
2106
+ return provider:GetService("ScriptDebuggerService")
2107
+ end)
2108
+ if not ok or not service then
2109
+ return serviceError(`ScriptDebuggerService unavailable: {tostring(service)}`)
2110
+ end
2111
+ return service
2112
+ end
2113
+ local function luauStringLiteral(value)
2114
+ local escaped = (string.gsub(value, "\\", "\\\\"))
2115
+ escaped = (string.gsub(escaped, "\n", "\\n"))
2116
+ escaped = (string.gsub(escaped, "\r", "\\r"))
2117
+ escaped = (string.gsub(escaped, "\t", "\\t"))
2118
+ escaped = (string.gsub(escaped, '"', '\\"'))
2119
+ return `"{escaped}"`
2120
+ end
2121
+ local function buildLogMessage(scriptPath, line, logMessage)
2122
+ local prefix = { luauStringLiteral(LOG_PREFIX), luauStringLiteral(`{scriptPath}:{line}`) }
2123
+ local _logMessage = logMessage
2124
+ local _condition = type(_logMessage) == "string"
2125
+ if _condition then
2126
+ _condition = logMessage ~= ""
2127
+ end
2128
+ if _condition then
2129
+ local _logMessage_1 = logMessage
2130
+ table.insert(prefix, _logMessage_1)
2131
+ end
2132
+ return table.concat(prefix, ", ")
2133
+ end
2134
+ local function listBreakpoints(requestData)
2135
+ loadRegistry(requestData)
2136
+ local out = {}
2137
+ for _, entry in breakpoints do
2138
+ table.insert(out, entry)
2139
+ end
2140
+ return {
2141
+ breakpoints = out,
2142
+ count = #out,
2143
+ }
2144
+ end
2145
+ local function setBreakpoint(requestData)
2146
+ local scope = loadRegistry(requestData)
2147
+ local serviceOrError = getService()
2148
+ local _value = serviceOrError.IsA
2149
+ if not (_value ~= 0 and _value == _value and _value ~= "" and _value) then
2150
+ return serviceOrError
2151
+ end
2152
+ local service = serviceOrError
2153
+ local scriptPath = requestData.script_path
2154
+ local lineRaw = requestData.line
2155
+ if not (type(scriptPath) == "string") or scriptPath == "" or not (type(lineRaw) == "number") then
2156
+ return {
2157
+ error = "invalid_args",
2158
+ message = "breakpoints action=set requires script_path and line",
2159
+ }
2160
+ end
2161
+ local requestedLine = math.floor(lineRaw)
2162
+ if requestedLine < 1 then
2163
+ return {
2164
+ error = "invalid_line",
2165
+ message = "line must be a 1-based positive number",
2166
+ }
2167
+ end
2168
+ local instance = getInstanceByPath(scriptPath)
2169
+ if not instance then
2170
+ return {
2171
+ error = "script_not_found",
2172
+ script_path = scriptPath,
2173
+ }
2174
+ end
2175
+ if not instance:IsA("LuaSourceContainer") then
2176
+ return {
2177
+ error = "not_a_script",
2178
+ message = `{scriptPath} is {instance.ClassName}, not a LuaSourceContainer`,
2179
+ script_path = scriptPath,
2180
+ }
2181
+ end
2182
+ local _log_message = requestData.log_message
2183
+ local rawLogMessage = if type(_log_message) == "string" then requestData.log_message else nil
2184
+ local hasLogMessage = rawLogMessage ~= nil and rawLogMessage ~= ""
2185
+ local _continue_execution = requestData.continue_execution
2186
+ local continueExecution = if type(_continue_execution) == "boolean" then requestData.continue_execution else hasLogMessage
2187
+ local _enabled = requestData.enabled
2188
+ local enabled = if type(_enabled) == "boolean" then requestData.enabled else true
2189
+ local effectiveLogMessage = if hasLogMessage or continueExecution then buildLogMessage(scriptPath, requestedLine, rawLogMessage) else nil
2190
+ local spec = {
2191
+ Line = requestedLine,
2192
+ Enabled = enabled,
2193
+ ContinueExecution = continueExecution,
2194
+ }
2195
+ local _condition = requestData.condition
2196
+ local _condition_1 = type(_condition) == "string"
2197
+ if _condition_1 then
2198
+ _condition_1 = requestData.condition ~= ""
2199
+ end
2200
+ if _condition_1 then
2201
+ spec.Condition = requestData.condition
2202
+ end
2203
+ if effectiveLogMessage ~= nil then
2204
+ spec.LogMessage = effectiveLogMessage
2205
+ end
2206
+ local ok, result = pcall(function()
2207
+ return service:AddBreakpoint(instance, spec)
2208
+ end)
2209
+ if not ok then
2210
+ return operationError("add_breakpoint_failed", "ScriptDebuggerService:AddBreakpoint", result)
2211
+ end
2212
+ local breakpointResult = result
2213
+ local _line = breakpointResult.Line
2214
+ local actualLine = if type(_line) == "number" then breakpointResult.Line else requestedLine
2215
+ local _verified = breakpointResult.Verified
2216
+ local verified = if type(_verified) == "boolean" then breakpointResult.Verified else nil
2217
+ local _message = breakpointResult.Message
2218
+ local message = if type(_message) == "string" then breakpointResult.Message else nil
2219
+ local entry = {
2220
+ script_path = scriptPath,
2221
+ line = actualLine,
2222
+ requested_line = if actualLine ~= requestedLine then requestedLine else nil,
2223
+ enabled = enabled,
2224
+ condition = spec.Condition,
2225
+ log_message = rawLogMessage,
2226
+ continue_execution = continueExecution,
2227
+ verified = if verified == false then false else nil,
2228
+ message = message,
2229
+ created_at = DateTime.now().UnixTimestampMillis,
2230
+ }
2231
+ local _arg0 = breakpointKey(scriptPath, actualLine)
2232
+ breakpoints[_arg0] = entry
2233
+ return attachPersistenceWarning({
2234
+ ok = true,
2235
+ breakpoint = entry,
2236
+ }, persistRegistry(scope))
2237
+ end
2238
+ local function removeBreakpoint(requestData)
2239
+ local scope = loadRegistry(requestData)
2240
+ local serviceOrError = getService()
2241
+ local _value = serviceOrError.IsA
2242
+ if not (_value ~= 0 and _value == _value and _value ~= "" and _value) then
2243
+ return serviceOrError
2244
+ end
2245
+ local service = serviceOrError
2246
+ local scriptPath = requestData.script_path
2247
+ local lineRaw = requestData.line
2248
+ if not (type(scriptPath) == "string") or scriptPath == "" or not (type(lineRaw) == "number") then
2249
+ return {
2250
+ error = "invalid_args",
2251
+ message = "breakpoints action=remove requires script_path and line",
2252
+ }
2253
+ end
2254
+ local line = math.floor(lineRaw)
2255
+ if line < 1 then
2256
+ return {
2257
+ error = "invalid_line",
2258
+ message = "line must be a 1-based positive number",
2259
+ }
2260
+ end
2261
+ local instance = getInstanceByPath(scriptPath)
2262
+ if not instance then
2263
+ return {
2264
+ error = "script_not_found",
2265
+ script_path = scriptPath,
2266
+ }
2267
+ end
2268
+ if not instance:IsA("LuaSourceContainer") then
2269
+ return {
2270
+ error = "not_a_script",
2271
+ message = `{scriptPath} is {instance.ClassName}, not a LuaSourceContainer`,
2272
+ script_path = scriptPath,
2273
+ }
2274
+ end
2275
+ local ok, removed = pcall(function()
2276
+ return service:RemoveBreakpoint(instance, line)
2277
+ end)
2278
+ if not ok then
2279
+ return operationError("remove_breakpoint_failed", "ScriptDebuggerService:RemoveBreakpoint", removed)
2280
+ end
2281
+ local _arg0 = breakpointKey(scriptPath, line)
2282
+ breakpoints[_arg0] = nil
2283
+ return attachPersistenceWarning({
2284
+ ok = true,
2285
+ removed = removed,
2286
+ script_path = scriptPath,
2287
+ line = line,
2288
+ }, persistRegistry(scope))
2289
+ end
2290
+ local function clearManagedBreakpoints(requestData)
2291
+ local scope = loadRegistry(requestData)
2292
+ local serviceOrError = getService()
2293
+ local _value = serviceOrError.IsA
2294
+ if not (_value ~= 0 and _value == _value and _value ~= "" and _value) then
2295
+ return serviceOrError
2296
+ end
2297
+ local service = serviceOrError
2298
+ local cleared = 0
2299
+ local errors = {}
2300
+ for key, entry in breakpoints do
2301
+ local instance = getInstanceByPath(entry.script_path)
2302
+ if not instance or not instance:IsA("LuaSourceContainer") then
2303
+ breakpoints[key] = nil
2304
+ cleared += 1
2305
+ continue
2306
+ end
2307
+ local ok, removedOrError = pcall(function()
2308
+ return service:RemoveBreakpoint(instance, entry.line)
2309
+ end)
2310
+ if ok then
2311
+ breakpoints[key] = nil
2312
+ cleared += 1
2313
+ else
2314
+ local _arg0 = {
2315
+ script_path = entry.script_path,
2316
+ line = entry.line,
2317
+ error = tostring(removedOrError),
2318
+ }
2319
+ table.insert(errors, _arg0)
2320
+ end
2321
+ end
2322
+ if #errors > 0 then
2323
+ return {
2324
+ ok = false,
2325
+ cleared = cleared,
2326
+ errors = errors,
2327
+ }
2328
+ end
2329
+ return attachPersistenceWarning({
2330
+ ok = true,
2331
+ cleared = cleared,
2332
+ }, persistRegistry(scope))
2333
+ end
2334
+ local function clearAllBreakpoints(requestData)
2335
+ local scope = loadRegistry(requestData)
2336
+ local serviceOrError = getService()
2337
+ local _value = serviceOrError.IsA
2338
+ if not (_value ~= 0 and _value == _value and _value ~= "" and _value) then
2339
+ return serviceOrError
2340
+ end
2341
+ local service = serviceOrError
2342
+ -- ▼ ReadonlyMap.size ▼
2343
+ local _size = 0
2344
+ for _ in breakpoints do
2345
+ _size += 1
2346
+ end
2347
+ -- ▲ ReadonlyMap.size ▲
2348
+ local managedCount = _size
2349
+ local ok, err = pcall(function()
2350
+ return service:ClearBreakpoints()
2351
+ end)
2352
+ if not ok then
2353
+ return operationError("clear_breakpoints_failed", "ScriptDebuggerService:ClearBreakpoints", err)
2354
+ end
2355
+ table.clear(breakpoints)
2356
+ return attachPersistenceWarning({
2357
+ ok = true,
2358
+ cleared_managed = managedCount,
2359
+ }, persistRegistry(scope))
2360
+ end
2361
+ local function clearBreakpoints(requestData)
2362
+ if requestData.clear_all == true then
2363
+ return clearAllBreakpoints(requestData)
2364
+ end
2365
+ return clearManagedBreakpoints(requestData)
2366
+ end
2367
+ local function breakpointsTool(requestData)
2368
+ local action = requestData.action
2369
+ if not (type(action) == "string") or action == "" then
2370
+ return {
2371
+ error = "invalid_args",
2372
+ message = "breakpoints requires action=set|remove|clear|list",
2373
+ }
2374
+ end
2375
+ repeat
2376
+ if action == "set" then
2377
+ return setBreakpoint(requestData)
2378
+ end
2379
+ if action == "remove" then
2380
+ return removeBreakpoint(requestData)
2381
+ end
2382
+ if action == "clear" then
2383
+ return clearBreakpoints(requestData)
2384
+ end
2385
+ if action == "list" then
2386
+ return listBreakpoints(requestData)
2387
+ end
2388
+ return {
2389
+ error = "unknown_action",
2390
+ message = `breakpoints action must be one of: set, remove, clear, list (got {action})`,
2391
+ }
2392
+ until true
2393
+ end
2394
+ return {
2395
+ breakpoints = breakpointsTool,
2396
+ init = init,
2397
+ }
2398
+ ]]></string>
2399
+ </Properties>
2400
+ </Item>
2401
+ <Item class="ModuleScript" referent="8">
1860
2402
  <Properties>
1861
2403
  <string name="Name">BuildHandlers</string>
1862
2404
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -2421,7 +2963,7 @@ return {
2421
2963
  ]]></string>
2422
2964
  </Properties>
2423
2965
  </Item>
2424
- <Item class="ModuleScript" referent="8">
2966
+ <Item class="ModuleScript" referent="9">
2425
2967
  <Properties>
2426
2968
  <string name="Name">CaptureHandlers</string>
2427
2969
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -2636,7 +3178,7 @@ return {
2636
3178
  ]]></string>
2637
3179
  </Properties>
2638
3180
  </Item>
2639
- <Item class="ModuleScript" referent="9">
3181
+ <Item class="ModuleScript" referent="10">
2640
3182
  <Properties>
2641
3183
  <string name="Name">EvalRuntimeHandlers</string>
2642
3184
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -2790,7 +3332,7 @@ return {
2790
3332
  ]]></string>
2791
3333
  </Properties>
2792
3334
  </Item>
2793
- <Item class="ModuleScript" referent="10">
3335
+ <Item class="ModuleScript" referent="11">
2794
3336
  <Properties>
2795
3337
  <string name="Name">InputHandlers</string>
2796
3338
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -2990,7 +3532,7 @@ return {
2990
3532
  ]]></string>
2991
3533
  </Properties>
2992
3534
  </Item>
2993
- <Item class="ModuleScript" referent="11">
3535
+ <Item class="ModuleScript" referent="12">
2994
3536
  <Properties>
2995
3537
  <string name="Name">InstanceHandlers</string>
2996
3538
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -3497,7 +4039,7 @@ return {
3497
4039
  ]]></string>
3498
4040
  </Properties>
3499
4041
  </Item>
3500
- <Item class="ModuleScript" referent="12">
4042
+ <Item class="ModuleScript" referent="13">
3501
4043
  <Properties>
3502
4044
  <string name="Name">LogHandlers</string>
3503
4045
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -3523,7 +4065,7 @@ return {
3523
4065
  ]]></string>
3524
4066
  </Properties>
3525
4067
  </Item>
3526
- <Item class="ModuleScript" referent="13">
4068
+ <Item class="ModuleScript" referent="14">
3527
4069
  <Properties>
3528
4070
  <string name="Name">MemoryHandlers</string>
3529
4071
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -3591,7 +4133,7 @@ return {
3591
4133
  ]]></string>
3592
4134
  </Properties>
3593
4135
  </Item>
3594
- <Item class="ModuleScript" referent="14">
4136
+ <Item class="ModuleScript" referent="15">
3595
4137
  <Properties>
3596
4138
  <string name="Name">MetadataHandlers</string>
3597
4139
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -4130,7 +4672,7 @@ return {
4130
4672
  ]]></string>
4131
4673
  </Properties>
4132
4674
  </Item>
4133
- <Item class="ModuleScript" referent="15">
4675
+ <Item class="ModuleScript" referent="16">
4134
4676
  <Properties>
4135
4677
  <string name="Name">PropertyHandlers</string>
4136
4678
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -4382,7 +4924,7 @@ return {
4382
4924
  ]]></string>
4383
4925
  </Properties>
4384
4926
  </Item>
4385
- <Item class="ModuleScript" referent="16">
4927
+ <Item class="ModuleScript" referent="17">
4386
4928
  <Properties>
4387
4929
  <string name="Name">QueryHandlers</string>
4388
4930
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -5347,64 +5889,6 @@ local function compareInstances(requestData)
5347
5889
  onlyB = onlyB,
5348
5890
  }
5349
5891
  end
5350
- local function getOutputLog(requestData)
5351
- local _condition = (requestData.maxEntries)
5352
- if _condition == nil then
5353
- _condition = 100
5354
- end
5355
- local maxEntries = _condition
5356
- local messageTypeFilter = requestData.messageType
5357
- local success, result = pcall(function()
5358
- local LogService = game:GetService("LogService")
5359
- local history = LogService:GetLogHistory()
5360
- local allEntries = {}
5361
- for _, entry in history do
5362
- local msgType = tostring(entry.messageType)
5363
- local _condition_1 = messageTypeFilter
5364
- if _condition_1 ~= "" and _condition_1 then
5365
- _condition_1 = msgType ~= messageTypeFilter
5366
- end
5367
- if _condition_1 ~= "" and _condition_1 then
5368
- continue
5369
- end
5370
- local _arg0 = {
5371
- message = entry.message,
5372
- messageType = msgType,
5373
- timestamp = entry.timestamp,
5374
- }
5375
- table.insert(allEntries, _arg0)
5376
- end
5377
- local startIdx = math.max(0, #allEntries - maxEntries)
5378
- local finalEntries = {}
5379
- do
5380
- local i = startIdx
5381
- local _shouldIncrement = false
5382
- while true do
5383
- if _shouldIncrement then
5384
- i += 1
5385
- else
5386
- _shouldIncrement = true
5387
- end
5388
- if not (i < #allEntries) then
5389
- break
5390
- end
5391
- local _arg0 = allEntries[i + 1]
5392
- table.insert(finalEntries, _arg0)
5393
- end
5394
- end
5395
- return {
5396
- entries = finalEntries,
5397
- count = #finalEntries,
5398
- totalAvailable = #allEntries,
5399
- }
5400
- end)
5401
- if success then
5402
- return result
5403
- end
5404
- return {
5405
- error = `Failed to get output log: {result}`,
5406
- }
5407
- end
5408
5892
  return {
5409
5893
  getFileTree = getFileTree,
5410
5894
  searchFiles = searchFiles,
@@ -5419,12 +5903,11 @@ return {
5419
5903
  grepScripts = grepScripts,
5420
5904
  getDescendants = getDescendants,
5421
5905
  compareInstances = compareInstances,
5422
- getOutputLog = getOutputLog,
5423
5906
  }
5424
5907
  ]]></string>
5425
5908
  </Properties>
5426
5909
  </Item>
5427
- <Item class="ModuleScript" referent="17">
5910
+ <Item class="ModuleScript" referent="18">
5428
5911
  <Properties>
5429
5912
  <string name="Name">SceneAnalysisHandlers</string>
5430
5913
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -5673,7 +6156,7 @@ return {
5673
6156
  ]]></string>
5674
6157
  </Properties>
5675
6158
  </Item>
5676
- <Item class="ModuleScript" referent="18">
6159
+ <Item class="ModuleScript" referent="19">
5677
6160
  <Properties>
5678
6161
  <string name="Name">ScriptHandlers</string>
5679
6162
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -5859,6 +6342,9 @@ local function setScriptSource(requestData)
5859
6342
  ScriptEditorService:UpdateSourceAsync(instance, function()
5860
6343
  return sourceToSet
5861
6344
  end)
6345
+ if readScriptSource(instance) ~= sourceToSet then
6346
+ error("UpdateSourceAsync completed without updating the script source")
6347
+ end
5862
6348
  return {
5863
6349
  success = true,
5864
6350
  instancePath = instancePath,
@@ -6335,41 +6821,504 @@ local function findAndReplaceInScripts(requestData)
6335
6821
  table.insert(changes, _arg0)
6336
6822
  end
6337
6823
  end
6338
- for _, child in instance:GetChildren() do
6339
- if hitLimit then
6340
- return nil
6341
- end
6342
- processInstance(child)
6824
+ for _, child in instance:GetChildren() do
6825
+ if hitLimit then
6826
+ return nil
6827
+ end
6828
+ processInstance(child)
6829
+ end
6830
+ end
6831
+ processInstance(startInstance)
6832
+ if recordingId ~= nil then
6833
+ finishRecording(recordingId, #changes > 0)
6834
+ end
6835
+ return {
6836
+ success = true,
6837
+ dryRun = dryRun,
6838
+ pattern = searchPattern,
6839
+ replacement = replacement,
6840
+ totalReplacements = totalReplacements,
6841
+ scriptsSearched = scriptsSearched,
6842
+ scriptsModified = #changes,
6843
+ changes = changes,
6844
+ truncated = hitLimit,
6845
+ }
6846
+ end
6847
+ return {
6848
+ getScriptSource = getScriptSource,
6849
+ setScriptSource = setScriptSource,
6850
+ editScriptLines = editScriptLines,
6851
+ insertScriptLines = insertScriptLines,
6852
+ deleteScriptLines = deleteScriptLines,
6853
+ findAndReplaceInScripts = findAndReplaceInScripts,
6854
+ }
6855
+ ]]></string>
6856
+ </Properties>
6857
+ </Item>
6858
+ <Item class="ModuleScript" referent="20">
6859
+ <Properties>
6860
+ <string name="Name">ScriptProfilerHandlers</string>
6861
+ <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
6862
+ local HttpService = game:GetService("HttpService")
6863
+ local Players = game:GetService("Players")
6864
+ local RunService = game:GetService("RunService")
6865
+ local DEFAULT_DURATION_MS = 1000
6866
+ local MIN_DURATION_MS = 100
6867
+ local MAX_DURATION_MS = 15000
6868
+ local DEFAULT_FREQUENCY = 1000
6869
+ local DEFAULT_MAX_FUNCTIONS = 20
6870
+ local function getProfilerService()
6871
+ local provider = game
6872
+ local ok, service = pcall(function()
6873
+ return provider:GetService("ScriptProfilerService")
6874
+ end)
6875
+ if not ok or not service then
6876
+ return {
6877
+ error = "script_profiler_unavailable",
6878
+ message = `ScriptProfilerService is unavailable: {tostring(service)}`,
6879
+ }
6880
+ end
6881
+ return service
6882
+ end
6883
+ local function normalizeDurationMs(value)
6884
+ local _value = value
6885
+ if not (type(_value) == "number") then
6886
+ return DEFAULT_DURATION_MS
6887
+ end
6888
+ return math.clamp(math.floor(value), MIN_DURATION_MS, MAX_DURATION_MS)
6889
+ end
6890
+ local function normalizeFrequency(value)
6891
+ local _value = value
6892
+ if not (type(_value) == "number") then
6893
+ return DEFAULT_FREQUENCY
6894
+ end
6895
+ return math.clamp(math.floor(value), 1, 10000)
6896
+ end
6897
+ local function normalizeMaxFunctions(value)
6898
+ local _value = value
6899
+ if not (type(_value) == "number") then
6900
+ return DEFAULT_MAX_FUNCTIONS
6901
+ end
6902
+ return math.clamp(math.floor(value), 1, 100)
6903
+ end
6904
+ local function normalizeMinTotalUs(requestData)
6905
+ local value = requestData.min_total_us
6906
+ if type(value) == "number" then
6907
+ return math.max(0, value)
6908
+ end
6909
+ return 0
6910
+ end
6911
+ local function stringContains(haystack, needle)
6912
+ return (string.find(string.lower(haystack), string.lower(needle), 1, true)) ~= nil
6913
+ end
6914
+ local function localPlayer()
6915
+ local player = Players.LocalPlayer
6916
+ local started = tick()
6917
+ while not player and tick() - started < 5 do
6918
+ task.wait(0.05)
6919
+ player = Players.LocalPlayer
6920
+ end
6921
+ return player
6922
+ end
6923
+ local function functionDisplayName(func)
6924
+ local _name = func.Name
6925
+ local _condition = type(_name) == "string"
6926
+ if _condition then
6927
+ _condition = func.Name ~= ""
6928
+ end
6929
+ if _condition then
6930
+ return func.Name
6931
+ end
6932
+ local _source = func.Source
6933
+ local _condition_1 = type(_source) == "string"
6934
+ if _condition_1 then
6935
+ _condition_1 = func.Source ~= ""
6936
+ end
6937
+ if _condition_1 then
6938
+ local _line = func.Line
6939
+ local _condition_2 = type(_line) == "number"
6940
+ if _condition_2 then
6941
+ _condition_2 = func.Line > 0
6942
+ end
6943
+ if _condition_2 then
6944
+ return `{func.Source}:{func.Line}`
6945
+ end
6946
+ return func.Source
6947
+ end
6948
+ return "<anonymous>"
6949
+ end
6950
+ local function flagsOf(func)
6951
+ local _flags = func.Flags
6952
+ return if type(_flags) == "number" then func.Flags else 0
6953
+ end
6954
+ local function isNativeFunction(func)
6955
+ return bit32.band(flagsOf(func), 1) ~= 0
6956
+ end
6957
+ local function isPluginFunction(func)
6958
+ if bit32.band(flagsOf(func), 2) ~= 0 then
6959
+ return true
6960
+ end
6961
+ local _source = func.Source
6962
+ local _condition = type(_source) == "string"
6963
+ if _condition then
6964
+ _condition = (string.find(func.Source, "MCPPlugin", 1, true)) ~= nil
6965
+ end
6966
+ return _condition
6967
+ end
6968
+ local function isDebugLabel(func, filter)
6969
+ if filter == nil then
6970
+ return false
6971
+ end
6972
+ local _name = func.Name
6973
+ local _condition = not (type(_name) == "string")
6974
+ if not _condition then
6975
+ _condition = func.Name == ""
6976
+ end
6977
+ if _condition then
6978
+ return false
6979
+ end
6980
+ local _source = func.Source
6981
+ local _condition_1 = not (type(_source) == "string")
6982
+ if not _condition_1 then
6983
+ _condition_1 = func.Source == "" or func.Source == "[C]" or func.Source == "GC"
6984
+ end
6985
+ if _condition_1 then
6986
+ return false
6987
+ end
6988
+ if func.Line ~= nil and func.Line ~= 0 then
6989
+ return false
6990
+ end
6991
+ if isNativeFunction(func) or isPluginFunction(func) then
6992
+ return false
6993
+ end
6994
+ return stringContains(func.Name, filter) or stringContains(func.Source, filter)
6995
+ end
6996
+ local function pctOfCapture(row, durationMs)
6997
+ local captureUs = durationMs * 1000
6998
+ if captureUs <= 0 then
6999
+ return nil
7000
+ end
7001
+ return math.floor((row.total_us / captureUs) * 10000 + 0.5) / 100
7002
+ end
7003
+ local function compactFunction(row, rank, durationMs)
7004
+ local out = {
7005
+ rank = rank,
7006
+ function_index = row.function_index,
7007
+ name = row.name,
7008
+ total_us = math.floor(row.total_us + 0.5),
7009
+ }
7010
+ local pct = pctOfCapture(row, durationMs)
7011
+ if pct ~= nil then
7012
+ out.pct_of_capture = pct
7013
+ end
7014
+ if row.source ~= nil then
7015
+ out.source = row.source
7016
+ end
7017
+ if row.line ~= nil then
7018
+ out.line = row.line
7019
+ end
7020
+ if row.is_native == true then
7021
+ out.is_native = true
7022
+ end
7023
+ if row.is_plugin == true then
7024
+ out.is_plugin = true
7025
+ end
7026
+ if row.is_debug_label == true then
7027
+ out.is_debug_label = true
7028
+ end
7029
+ return out
7030
+ end
7031
+ local function summarizeProfile(rawJson, profile, requestData, durationMs, frequency, eventPlayerName)
7032
+ local _functions = profile.Functions
7033
+ local funcs = if type(_functions) == "table" then profile.Functions else {}
7034
+ local _nodes = profile.Nodes
7035
+ local nodes = if type(_nodes) == "table" then profile.Nodes else {}
7036
+ local _categories = profile.Categories
7037
+ local categories = if type(_categories) == "table" then profile.Categories else {}
7038
+ local maxFunctions = normalizeMaxFunctions(requestData.max_functions)
7039
+ local minTotalUs = normalizeMinTotalUs(requestData)
7040
+ local includeNative = requestData.include_native == true
7041
+ local includePlugin = requestData.include_plugin == true
7042
+ local _filter = requestData.filter
7043
+ local _condition = type(_filter) == "string"
7044
+ if _condition then
7045
+ _condition = requestData.filter ~= ""
7046
+ end
7047
+ local filter = if _condition then requestData.filter else nil
7048
+ local rows = {}
7049
+ local debugRows = {}
7050
+ local omittedNative = 0
7051
+ local omittedPlugin = 0
7052
+ local omittedBelowThreshold = 0
7053
+ local omittedByFilter = 0
7054
+ for i = 0, #funcs - 1 do
7055
+ local func = funcs[i + 1]
7056
+ if not (type(func) == "table") then
7057
+ continue
7058
+ end
7059
+ local info = func
7060
+ local _totalDuration = info.TotalDuration
7061
+ local totalUs = if type(_totalDuration) == "number" then info.TotalDuration else 0
7062
+ local name = functionDisplayName(info)
7063
+ local _object = {
7064
+ function_index = i + 1,
7065
+ name = name,
7066
+ }
7067
+ local _left = "source"
7068
+ local _source = info.Source
7069
+ _object[_left] = if type(_source) == "string" then info.Source else nil
7070
+ local _left_1 = "line"
7071
+ local _line = info.Line
7072
+ _object[_left_1] = if type(_line) == "number" then info.Line else nil
7073
+ _object.total_us = totalUs
7074
+ _object.is_native = if isNativeFunction(info) then true else nil
7075
+ _object.is_plugin = if isPluginFunction(info) then true else nil
7076
+ _object.is_debug_label = if isDebugLabel(info, filter) then true else nil
7077
+ local row = _object
7078
+ if not includeNative and row.is_native == true then
7079
+ omittedNative += 1
7080
+ continue
7081
+ end
7082
+ if not includePlugin and row.is_plugin == true then
7083
+ omittedPlugin += 1
7084
+ continue
7085
+ end
7086
+ if totalUs < minTotalUs then
7087
+ omittedBelowThreshold += 1
7088
+ continue
7089
+ end
7090
+ if filter ~= nil then
7091
+ local _exp = row.name
7092
+ local _condition_1 = row.source
7093
+ if _condition_1 == nil then
7094
+ _condition_1 = ""
7095
+ end
7096
+ local text = `{_exp} {_condition_1}`
7097
+ if not stringContains(text, filter) then
7098
+ omittedByFilter += 1
7099
+ continue
7100
+ end
7101
+ end
7102
+ table.insert(rows, row)
7103
+ if row.is_debug_label == true then
7104
+ table.insert(debugRows, row)
7105
+ end
7106
+ end
7107
+ table.sort(rows, function(a, b)
7108
+ return a.total_us > b.total_us
7109
+ end)
7110
+ table.sort(debugRows, function(a, b)
7111
+ return a.total_us > b.total_us
7112
+ end)
7113
+ local topFunctions = {}
7114
+ do
7115
+ local i = 0
7116
+ local _shouldIncrement = false
7117
+ while true do
7118
+ if _shouldIncrement then
7119
+ i += 1
7120
+ else
7121
+ _shouldIncrement = true
7122
+ end
7123
+ if not (i < math.min(maxFunctions, #rows)) then
7124
+ break
7125
+ end
7126
+ local _arg0 = compactFunction(rows[i + 1], i + 1, durationMs)
7127
+ table.insert(topFunctions, _arg0)
7128
+ end
7129
+ end
7130
+ local debugLabels = {}
7131
+ do
7132
+ local i = 0
7133
+ local _shouldIncrement = false
7134
+ while true do
7135
+ if _shouldIncrement then
7136
+ i += 1
7137
+ else
7138
+ _shouldIncrement = true
7139
+ end
7140
+ if not (i < math.min(maxFunctions, #debugRows)) then
7141
+ break
7142
+ end
7143
+ local _arg0 = compactFunction(debugRows[i + 1], i + 1, durationMs)
7144
+ table.insert(debugLabels, _arg0)
7145
+ end
7146
+ end
7147
+ local categoryNames = {}
7148
+ for i = 0, #categories - 1 do
7149
+ local category = categories[i + 1]
7150
+ if type(category) == "table" then
7151
+ local name = category.Name
7152
+ if type(name) == "string" then
7153
+ table.insert(categoryNames, name)
7154
+ end
7155
+ end
7156
+ end
7157
+ local omitted = {}
7158
+ local hasOmitted = false
7159
+ if omittedNative > 0 then
7160
+ omitted.native = omittedNative
7161
+ hasOmitted = true
7162
+ end
7163
+ if omittedPlugin > 0 then
7164
+ omitted.plugin = omittedPlugin
7165
+ hasOmitted = true
7166
+ end
7167
+ if omittedBelowThreshold > 0 then
7168
+ omitted.below_min_total_us = omittedBelowThreshold
7169
+ hasOmitted = true
7170
+ end
7171
+ if omittedByFilter > 0 then
7172
+ omitted.filtered_out = omittedByFilter
7173
+ hasOmitted = true
7174
+ end
7175
+ local _object = {
7176
+ ok = true,
7177
+ duration_ms = durationMs,
7178
+ frequency = frequency,
7179
+ }
7180
+ local _left = "applied"
7181
+ local _object_1 = {}
7182
+ local _left_1 = "filter"
7183
+ local _condition_1 = filter
7184
+ if _condition_1 == nil then
7185
+ _condition_1 = nil
7186
+ end
7187
+ _object_1[_left_1] = _condition_1
7188
+ _object_1.min_total_us = minTotalUs
7189
+ _object_1.include_native = includeNative
7190
+ _object_1.include_plugin = includePlugin
7191
+ _object_1.max_functions = maxFunctions
7192
+ _object_1.sort = "total_us_desc"
7193
+ _object[_left] = _object_1
7194
+ _object.json_bytes = #rawJson
7195
+ _object.counts = {
7196
+ categories = #categories,
7197
+ nodes = #nodes,
7198
+ functions = #funcs,
7199
+ }
7200
+ _object.top_functions = topFunctions
7201
+ _object.debug_labels = debugLabels
7202
+ local result = _object
7203
+ if #categoryNames > 0 then
7204
+ result.categories = categoryNames
7205
+ end
7206
+ if hasOmitted then
7207
+ result.omitted = omitted
7208
+ end
7209
+ if profile.Version ~= nil then
7210
+ result.version = profile.Version
7211
+ end
7212
+ if profile.SessionStartTime ~= nil or profile.SessionEndTime ~= nil then
7213
+ result.session = {
7214
+ start_time = profile.SessionStartTime,
7215
+ end_time = profile.SessionEndTime,
7216
+ }
7217
+ end
7218
+ if eventPlayerName ~= nil then
7219
+ result.player = eventPlayerName
7220
+ end
7221
+ if requestData.__mcp_include_raw_json == true then
7222
+ result.raw_json = rawJson
7223
+ end
7224
+ return result
7225
+ end
7226
+ local function captureScriptProfiler(requestData)
7227
+ if not RunService:IsRunning() then
7228
+ return {
7229
+ error = "runtime_target_required",
7230
+ message = 'Script profiler capture requires a running playtest target such as target=\"server\" or target=\"client-1\".',
7231
+ }
7232
+ end
7233
+ local serviceOrError = getProfilerService()
7234
+ local _value = serviceOrError.IsA
7235
+ if not (_value ~= 0 and _value == _value and _value ~= "" and _value) then
7236
+ return serviceOrError
7237
+ end
7238
+ local service = serviceOrError
7239
+ local durationMs = normalizeDurationMs(requestData.duration_ms)
7240
+ local frequency = normalizeFrequency(requestData.frequency)
7241
+ local isServer = RunService:IsServer()
7242
+ local isClient = RunService:IsClient() and not isServer
7243
+ local player = if isClient then localPlayer() else nil
7244
+ if not isServer and not player then
7245
+ return {
7246
+ error = "client_player_unavailable",
7247
+ message = "Could not resolve Players.LocalPlayer for client profiling.",
7248
+ }
7249
+ end
7250
+ local rawJson
7251
+ local eventPlayerName
7252
+ local connection = service.OnNewData:Connect(function(playerArg, jsonString)
7253
+ if rawJson ~= nil then
7254
+ return nil
7255
+ end
7256
+ rawJson = jsonString
7257
+ if playerArg then
7258
+ eventPlayerName = playerArg.Name
7259
+ end
7260
+ end)
7261
+ local startOk, startErr = pcall(function()
7262
+ if isServer then
7263
+ service:ServerStart(frequency)
7264
+ else
7265
+ service:ClientStart(player, frequency)
7266
+ end
7267
+ end)
7268
+ if not startOk then
7269
+ connection:Disconnect()
7270
+ return {
7271
+ error = "script_profiler_start_failed",
7272
+ message = tostring(startErr),
7273
+ }
7274
+ end
7275
+ task.wait(durationMs / 1000)
7276
+ local stopOk, stopErr = pcall(function()
7277
+ if isServer then
7278
+ service:ServerStop()
7279
+ service:ServerRequestData()
7280
+ else
7281
+ service:ClientStop(player)
7282
+ service:ClientRequestData(player)
6343
7283
  end
7284
+ end)
7285
+ if not stopOk then
7286
+ connection:Disconnect()
7287
+ return {
7288
+ error = "script_profiler_stop_failed",
7289
+ message = tostring(stopErr),
7290
+ }
6344
7291
  end
6345
- processInstance(startInstance)
6346
- if recordingId ~= nil then
6347
- finishRecording(recordingId, #changes > 0)
7292
+ local requestedAt = tick()
7293
+ while rawJson == nil and tick() - requestedAt < 5 do
7294
+ task.wait(0.05)
6348
7295
  end
6349
- return {
6350
- success = true,
6351
- dryRun = dryRun,
6352
- pattern = searchPattern,
6353
- replacement = replacement,
6354
- totalReplacements = totalReplacements,
6355
- scriptsSearched = scriptsSearched,
6356
- scriptsModified = #changes,
6357
- changes = changes,
6358
- truncated = hitLimit,
6359
- }
7296
+ connection:Disconnect()
7297
+ if rawJson == nil then
7298
+ return {
7299
+ error = "script_profiler_data_timeout",
7300
+ message = "ScriptProfilerService did not emit OnNewData after requesting profiler data.",
7301
+ }
7302
+ end
7303
+ local decodeOk, decoded = pcall(function()
7304
+ return HttpService:JSONDecode(rawJson)
7305
+ end)
7306
+ if not decodeOk then
7307
+ return {
7308
+ error = "script_profiler_decode_failed",
7309
+ message = tostring(decoded),
7310
+ json_bytes = #rawJson,
7311
+ }
7312
+ end
7313
+ return summarizeProfile(rawJson, decoded, requestData, durationMs, frequency, eventPlayerName)
6360
7314
  end
6361
7315
  return {
6362
- getScriptSource = getScriptSource,
6363
- setScriptSource = setScriptSource,
6364
- editScriptLines = editScriptLines,
6365
- insertScriptLines = insertScriptLines,
6366
- deleteScriptLines = deleteScriptLines,
6367
- findAndReplaceInScripts = findAndReplaceInScripts,
7316
+ captureScriptProfiler = captureScriptProfiler,
6368
7317
  }
6369
7318
  ]]></string>
6370
7319
  </Properties>
6371
7320
  </Item>
6372
- <Item class="ModuleScript" referent="19">
7321
+ <Item class="ModuleScript" referent="21">
6373
7322
  <Properties>
6374
7323
  <string name="Name">SerializationHandlers</string>
6375
7324
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -6555,33 +7504,18 @@ return {
6555
7504
  ]]></string>
6556
7505
  </Properties>
6557
7506
  </Item>
6558
- <Item class="ModuleScript" referent="20">
7507
+ <Item class="ModuleScript" referent="22">
6559
7508
  <Properties>
6560
7509
  <string name="Name">TestHandlers</string>
6561
7510
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
6562
7511
  local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
6563
7512
  local _services = TS.import(script, script.Parent.Parent.Parent, "node_modules", "@rbxts", "services")
6564
7513
  local HttpService = _services.HttpService
6565
- local LogService = _services.LogService
6566
7514
  local Players = _services.Players
6567
7515
  local RunService = _services.RunService
6568
7516
  local StopPlayMonitor = TS.import(script, script.Parent.Parent, "StopPlayMonitor")
6569
7517
  local StudioTestService = game:GetService("StudioTestService")
6570
- local ServerScriptService = game:GetService("ServerScriptService")
6571
- local ScriptEditorService = game:GetService("ScriptEditorService")
6572
- -- NAV_SIGNAL flows from the edit DM to the play-server DM via the injected
6573
- -- __MCP_CommandListener Script + LogService.MessageOut. Stop signaling moved
6574
- -- off this path entirely (see StopPlayMonitor) because cross-DM MessageOut
6575
- -- reflection from edit -> play-server does not work in practice.
6576
- local NAV_SIGNAL = "__MCP_NAV__"
6577
- local NAV_RESULT = "__MCP_NAV_RESULT__"
6578
7518
  local testRunning = false
6579
- local outputBuffer = {}
6580
- local logConnection
6581
- local testResult
6582
- local testError
6583
- local stopListenerScript
6584
- local navResultCallback
6585
7519
  local multiplayerState = {
6586
7520
  phase = "idle",
6587
7521
  }
@@ -6639,94 +7573,6 @@ local function normalizeNumPlayers(value)
6639
7573
  end
6640
7574
  return n
6641
7575
  end
6642
- local function buildCommandListenerSource()
6643
- return `local LogService = game:GetService("LogService")\
6644
- local PathfindingService = game:GetService("PathfindingService")\
6645
- local Players = game:GetService("Players")\
6646
- local HttpService = game:GetService("HttpService")\
6647
- local NAV_SIG = "{NAV_SIGNAL}"\
6648
- local NAV_RES = "{NAV_RESULT}"\
6649
- LogService.MessageOut:Connect(function(msg)\
6650
- if string.sub(msg, 1, #NAV_SIG + 1) == NAV_SIG .. ":" then\
6651
- local json = string.sub(msg, #NAV_SIG + 2)\
6652
- task.spawn(function()\
6653
- local ok, d = pcall(function() return HttpService:JSONDecode(json) end)\
6654
- if not ok or not d then\
6655
- print(NAV_RES .. ':\{"success":false,"error":"parse_error"\}')\
6656
- return\
6657
- end\
6658
- local ps = Players:GetPlayers()\
6659
- if #ps == 0 then\
6660
- print(NAV_RES .. ':\{"success":false,"error":"no_players"\}')\
6661
- return\
6662
- end\
6663
- local char = ps[1].Character or ps[1].CharacterAdded:Wait()\
6664
- local hum = char:FindFirstChildOfClass("Humanoid")\
6665
- local root = char:FindFirstChild("HumanoidRootPart")\
6666
- if not hum or not root then\
6667
- print(NAV_RES .. ':\{"success":false,"error":"no_humanoid"\}')\
6668
- return\
6669
- end\
6670
- local target\
6671
- if d.instancePath then\
6672
- local parts = string.split(d.instancePath, ".")\
6673
- local cur = game\
6674
- for i = 2, #parts do\
6675
- cur = cur:FindFirstChild(parts[i])\
6676
- if not cur then\
6677
- print(NAV_RES .. ':\{"success":false,"error":"instance_not_found"\}')\
6678
- return\
6679
- end\
6680
- end\
6681
- if cur:IsA("BasePart") then target = cur.Position\
6682
- elseif cur:IsA("Model") and cur.PrimaryPart then target = cur.PrimaryPart.Position\
6683
- else target = cur:GetPivot().Position end\
6684
- else\
6685
- target = Vector3.new(d.x or 0, d.y or 0, d.z or 0)\
6686
- end\
6687
- local path = PathfindingService:CreatePath(\{AgentRadius=2,AgentHeight=5,AgentCanJump=true\})\
6688
- local pok = pcall(function() path:ComputeAsync(root.Position, target) end)\
6689
- local method = "direct"\
6690
- if pok and path.Status == Enum.PathStatus.Success then\
6691
- method = "pathfinding"\
6692
- for _, wp in ipairs(path:GetWaypoints()) do\
6693
- hum:MoveTo(wp.Position)\
6694
- if wp.Action == Enum.PathWaypointAction.Jump then hum.Jump = true end\
6695
- hum.MoveToFinished:Wait()\
6696
- end\
6697
- else\
6698
- hum:MoveTo(target)\
6699
- hum.MoveToFinished:Wait()\
6700
- end\
6701
- local fp = root.Position\
6702
- print(NAV_RES .. ':\{"success":true,"method":"' .. method .. '","position":[' .. fp.X .. ',' .. fp.Y .. ',' .. fp.Z .. ']\}')\
6703
- end)\
6704
- end\
6705
- end)`
6706
- end
6707
- local function injectStopListener()
6708
- local listener = Instance.new("Script")
6709
- listener.Name = "__MCP_CommandListener"
6710
- listener.Parent = ServerScriptService
6711
- local source = buildCommandListenerSource()
6712
- local seOk = pcall(function()
6713
- ScriptEditorService:UpdateSourceAsync(listener, function()
6714
- return source
6715
- end)
6716
- end)
6717
- if not seOk then
6718
- listener.Source = source
6719
- end
6720
- stopListenerScript = listener
6721
- end
6722
- local function cleanupStopListener()
6723
- if stopListenerScript then
6724
- pcall(function()
6725
- return stopListenerScript:Destroy()
6726
- end)
6727
- stopListenerScript = nil
6728
- end
6729
- end
6730
7576
  local function startPlaytest(requestData)
6731
7577
  local mode = requestData.mode
6732
7578
  local numPlayers = requestData.numPlayers
@@ -6746,11 +7592,6 @@ local function startPlaytest(requestData)
6746
7592
  -- Reset it so subsequent starts don't hit a false "already running".
6747
7593
  if testRunning and not RunService:IsRunning() then
6748
7594
  testRunning = false
6749
- if logConnection then
6750
- logConnection:Disconnect()
6751
- logConnection = nil
6752
- end
6753
- cleanupStopListener()
6754
7595
  -- Runtime eval bridges are created by the play server/client plugin
6755
7596
  -- peers and disappear with the play DataModels.
6756
7597
  end
@@ -6760,41 +7601,6 @@ local function startPlaytest(requestData)
6760
7601
  }
6761
7602
  end
6762
7603
  testRunning = true
6763
- outputBuffer = {}
6764
- testResult = nil
6765
- testError = nil
6766
- cleanupStopListener()
6767
- logConnection = LogService.MessageOut:Connect(function(message, messageType)
6768
- local _message = message
6769
- local _arg1 = #NAV_SIGNAL
6770
- if string.sub(_message, 1, _arg1) == NAV_SIGNAL then
6771
- return nil
6772
- end
6773
- local _message_1 = message
6774
- local _arg1_1 = #NAV_RESULT + 1
6775
- if string.sub(_message_1, 1, _arg1_1) == `{NAV_RESULT}:` then
6776
- if navResultCallback then
6777
- local _fn = navResultCallback
6778
- local _message_2 = message
6779
- local _arg0 = #NAV_RESULT + 2
6780
- _fn(string.sub(_message_2, _arg0))
6781
- end
6782
- return nil
6783
- end
6784
- local _outputBuffer = outputBuffer
6785
- local _arg0 = {
6786
- message = message,
6787
- messageType = messageType.Name,
6788
- timestamp = tick(),
6789
- }
6790
- table.insert(_outputBuffer, _arg0)
6791
- end)
6792
- local injected, injErr = pcall(function()
6793
- return injectStopListener()
6794
- end)
6795
- if not injected then
6796
- warn(`[robloxstudio-mcp] Failed to inject stop listener: {injErr}`)
6797
- end
6798
7604
  task.spawn(function()
6799
7605
  local ok, result = pcall(function()
6800
7606
  if mode == "play" then
@@ -6802,17 +7608,10 @@ local function startPlaytest(requestData)
6802
7608
  end
6803
7609
  return StudioTestService:ExecuteRunModeAsync({})
6804
7610
  end)
6805
- if ok then
6806
- testResult = result
6807
- else
6808
- testError = tostring(result)
6809
- end
6810
- if logConnection then
6811
- logConnection:Disconnect()
6812
- logConnection = nil
7611
+ if not ok then
7612
+ warn(`[robloxstudio-mcp] Playtest ended with error: {result}`)
6813
7613
  end
6814
7614
  testRunning = false
6815
- cleanupStopListener()
6816
7615
  end)
6817
7616
  local response = {
6818
7617
  success = true,
@@ -6887,20 +7686,6 @@ local function stopPlaytest(_requestData)
6887
7686
  message = "Playtest stopped.",
6888
7687
  }
6889
7688
  end
6890
- local function getPlaytestOutput(_requestData)
6891
- local _object = {
6892
- isRunning = testRunning,
6893
- }
6894
- local _left = "output"
6895
- local _array = {}
6896
- local _length = #_array
6897
- table.move(outputBuffer, 1, #outputBuffer, _length + 1, _array)
6898
- _object[_left] = _array
6899
- _object.outputCount = #outputBuffer
6900
- _object.testResult = if testResult ~= nil then tostring(testResult) else nil
6901
- _object.testError = testError
6902
- return _object
6903
- end
6904
7689
  local function multiplayerTestStart(requestData)
6905
7690
  if RunService:IsRunning() then
6906
7691
  return {
@@ -7085,89 +7870,20 @@ local function multiplayerTestEnd(requestData)
7085
7870
  value = value,
7086
7871
  }
7087
7872
  end
7088
- local function characterNavigation(requestData)
7089
- if not testRunning then
7090
- return {
7091
- error = "Playtest must be running. Start a playtest in 'play' mode first.",
7092
- }
7093
- end
7094
- local position = requestData.position
7095
- local instancePath = requestData.instancePath
7096
- local _condition = (requestData.waitForCompletion)
7097
- if _condition == nil then
7098
- _condition = true
7099
- end
7100
- local waitForCompletion = _condition
7101
- local _condition_1 = (requestData.timeout)
7102
- if _condition_1 == nil then
7103
- _condition_1 = 25
7104
- end
7105
- local timeout = _condition_1
7106
- if not position and not (instancePath ~= "" and instancePath) then
7107
- return {
7108
- error = "Either position [x, y, z] or instancePath is required",
7109
- }
7110
- end
7111
- local navData
7112
- if position then
7113
- navData = HttpService:JSONEncode({
7114
- x = position[1],
7115
- y = position[2],
7116
- z = position[3],
7117
- })
7118
- else
7119
- navData = HttpService:JSONEncode({
7120
- instancePath = instancePath,
7121
- })
7122
- end
7123
- warn(`{NAV_SIGNAL}:{navData}`)
7124
- if not waitForCompletion then
7125
- return {
7126
- success = true,
7127
- message = "Navigation command sent",
7128
- }
7129
- end
7130
- local result
7131
- navResultCallback = function(json)
7132
- result = json
7133
- end
7134
- local startTime = tick()
7135
- while not (result ~= "" and result) and tick() - startTime < timeout do
7136
- task.wait(0.2)
7137
- end
7138
- navResultCallback = nil
7139
- if result ~= "" and result then
7140
- local ok, parsed = pcall(function()
7141
- return HttpService:JSONDecode(result)
7142
- end)
7143
- if ok then
7144
- return parsed
7145
- end
7146
- return {
7147
- success = true,
7148
- rawResult = result,
7149
- }
7150
- end
7151
- return {
7152
- error = `Navigation timed out after {timeout} seconds`,
7153
- }
7154
- end
7155
7873
  return {
7156
7874
  startPlaytest = startPlaytest,
7157
7875
  stopPlaytest = stopPlaytest,
7158
- getPlaytestOutput = getPlaytestOutput,
7159
7876
  multiplayerTestStart = multiplayerTestStart,
7160
7877
  multiplayerTestState = multiplayerTestState,
7161
7878
  multiplayerTestAddPlayers = multiplayerTestAddPlayers,
7162
7879
  multiplayerTestLeaveClient = multiplayerTestLeaveClient,
7163
7880
  multiplayerTestEnd = multiplayerTestEnd,
7164
- characterNavigation = characterNavigation,
7165
7881
  }
7166
7882
  ]]></string>
7167
7883
  </Properties>
7168
7884
  </Item>
7169
7885
  </Item>
7170
- <Item class="ModuleScript" referent="21">
7886
+ <Item class="ModuleScript" referent="23">
7171
7887
  <Properties>
7172
7888
  <string name="Name">HttpDiagnostics</string>
7173
7889
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -7248,7 +7964,7 @@ return {
7248
7964
  ]]></string>
7249
7965
  </Properties>
7250
7966
  </Item>
7251
- <Item class="ModuleScript" referent="22">
7967
+ <Item class="ModuleScript" referent="24">
7252
7968
  <Properties>
7253
7969
  <string name="Name">LuauExec</string>
7254
7970
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -7682,7 +8398,7 @@ return {
7682
8398
  ]]></string>
7683
8399
  </Properties>
7684
8400
  </Item>
7685
- <Item class="ModuleScript" referent="23">
8401
+ <Item class="ModuleScript" referent="25">
7686
8402
  <Properties>
7687
8403
  <string name="Name">Recording</string>
7688
8404
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -7712,7 +8428,7 @@ return {
7712
8428
  ]]></string>
7713
8429
  </Properties>
7714
8430
  </Item>
7715
- <Item class="ModuleScript" referent="24">
8431
+ <Item class="ModuleScript" referent="26">
7716
8432
  <Properties>
7717
8433
  <string name="Name">RenderMonitor</string>
7718
8434
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -7780,7 +8496,7 @@ return {
7780
8496
  ]]></string>
7781
8497
  </Properties>
7782
8498
  </Item>
7783
- <Item class="ModuleScript" referent="25">
8499
+ <Item class="ModuleScript" referent="27">
7784
8500
  <Properties>
7785
8501
  <string name="Name">RuntimeLogBuffer</string>
7786
8502
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -7833,6 +8549,43 @@ local function dropOldestUntilFits(incomingBytes)
7833
8549
  totalDropped += 1
7834
8550
  end
7835
8551
  end
8552
+ local function pushEntry(msg, t, ts)
8553
+ if ts == nil then
8554
+ ts = nowSec()
8555
+ end
8556
+ local bytes = #msg
8557
+ dropOldestUntilFits(bytes)
8558
+ local _arg0 = {
8559
+ seq = nextSeq,
8560
+ ts = ts,
8561
+ level = levelTag(t),
8562
+ message = msg,
8563
+ }
8564
+ table.insert(entries, _arg0)
8565
+ nextSeq += 1
8566
+ totalBytes += bytes
8567
+ end
8568
+ local function seedRuntimeHistory()
8569
+ if not RunService:IsRunning() then
8570
+ return nil
8571
+ end
8572
+ local ok, history = pcall(function()
8573
+ return LogService:GetLogHistory()
8574
+ end)
8575
+ if not ok then
8576
+ return nil
8577
+ end
8578
+ for _, entry in history do
8579
+ local _message = entry.message
8580
+ if not (type(_message) == "string") then
8581
+ continue
8582
+ end
8583
+ local _exp = entry.message
8584
+ local _exp_1 = entry.messageType
8585
+ local _timestamp = entry.timestamp
8586
+ pushEntry(_exp, _exp_1, if type(_timestamp) == "number" then entry.timestamp else nil)
8587
+ end
8588
+ end
7836
8589
  local function install()
7837
8590
  if installed then
7838
8591
  return nil
@@ -7841,18 +8594,12 @@ local function install()
7841
8594
  return nil
7842
8595
  end
7843
8596
  installed = true
8597
+ -- Play peers can emit startup logs before the plugin finishes loading.
8598
+ -- Seed from per-DataModel LogHistory so get_runtime_logs can still see
8599
+ -- those early messages; skip edit mode to avoid stale prior-session logs.
8600
+ seedRuntimeHistory()
7844
8601
  LogService.MessageOut:Connect(function(msg, t)
7845
- local bytes = #msg
7846
- dropOldestUntilFits(bytes)
7847
- local _arg0 = {
7848
- seq = nextSeq,
7849
- ts = nowSec(),
7850
- level = levelTag(t),
7851
- message = msg,
7852
- }
7853
- table.insert(entries, _arg0)
7854
- nextSeq += 1
7855
- totalBytes += bytes
8602
+ pushEntry(msg, t)
7856
8603
  end)
7857
8604
  end
7858
8605
  local function detectPeer()
@@ -7961,7 +8708,7 @@ return {
7961
8708
  ]]></string>
7962
8709
  </Properties>
7963
8710
  </Item>
7964
- <Item class="ModuleScript" referent="26">
8711
+ <Item class="ModuleScript" referent="28">
7965
8712
  <Properties>
7966
8713
  <string name="Name">ServerUrlSettings</string>
7967
8714
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -7969,11 +8716,37 @@ local TS = require(script.Parent.Parent.include.RuntimeLib)
7969
8716
  local _services = TS.import(script, script.Parent.Parent, "node_modules", "@rbxts", "services")
7970
8717
  local HttpService = _services.HttpService
7971
8718
  local ServerStorage = _services.ServerStorage
7972
- local SETTING_KEY_PREFIX = "MCP_SERVER_URL_"
8719
+ local LEGACY_SETTING_KEY_PREFIX = "MCP_SERVER_URL_"
8720
+ local SETTING_KEY_PREFIX = "MCP_LAST_SUCCESSFUL_SERVER_URL_"
8721
+ local GLOBAL_SETTING_KEY = "MCP_LAST_SUCCESSFUL_SERVER_URL_GLOBAL_V1"
7973
8722
  local pluginRef
7974
8723
  local function init(p)
7975
8724
  pluginRef = p
7976
8725
  end
8726
+ local function normalizeServerUrl(serverUrl)
8727
+ local _condition = serverUrl
8728
+ if _condition == nil then
8729
+ _condition = ""
8730
+ end
8731
+ local normalized = (string.gsub((string.gsub(_condition, "^%s+", "")), "%s+$", ""))
8732
+ if normalized == "" then
8733
+ return ""
8734
+ end
8735
+ if (string.match(normalized, "^%a[%w+.-]*://")) == nil then
8736
+ normalized = `http://{normalized}`
8737
+ end
8738
+ while #normalized > 0 and string.sub(normalized, -1) == "/" and (string.match(normalized, "^%a[%w+.-]*://$")) == nil do
8739
+ normalized = string.sub(normalized, 1, -2)
8740
+ end
8741
+ return normalized
8742
+ end
8743
+ local function extractPort(serverUrl)
8744
+ local portStr = string.match(serverUrl, ":(%d+)$")
8745
+ if portStr == nil then
8746
+ return nil
8747
+ end
8748
+ return tonumber(portStr)
8749
+ end
7977
8750
  local function addUnique(values, value)
7978
8751
  local _values = values
7979
8752
  local _value = value
@@ -8003,15 +8776,39 @@ end
8003
8776
  local function settingKey(instanceId)
8004
8777
  return SETTING_KEY_PREFIX .. instanceId
8005
8778
  end
8779
+ local function legacySettingKey(instanceId)
8780
+ return LEGACY_SETTING_KEY_PREFIX .. instanceId
8781
+ end
8782
+ local function readSettingString(key)
8783
+ if not pluginRef then
8784
+ return nil
8785
+ end
8786
+ local ok, value = pcall(function()
8787
+ return pluginRef:GetSetting(key)
8788
+ end)
8789
+ if not ok or not (type(value) == "string") then
8790
+ return nil
8791
+ end
8792
+ local normalized = normalizeServerUrl(value)
8793
+ return if normalized ~= "" then normalized else nil
8794
+ end
8795
+ local function writeSettingString(key, serverUrl)
8796
+ if not pluginRef then
8797
+ return nil
8798
+ end
8799
+ pcall(function()
8800
+ return pluginRef:SetSetting(key, serverUrl)
8801
+ end)
8802
+ end
8006
8803
  local function rememberServerUrl(serverUrl)
8007
- if not pluginRef or serverUrl == "" then
8804
+ local normalized = normalizeServerUrl(serverUrl)
8805
+ if not pluginRef or normalized == "" then
8008
8806
  return nil
8009
8807
  end
8808
+ writeSettingString(GLOBAL_SETTING_KEY, normalized)
8010
8809
  for _, instanceId in computeInstanceIds() do
8011
- local key = settingKey(instanceId)
8012
- pcall(function()
8013
- return pluginRef:SetSetting(key, serverUrl)
8014
- end)
8810
+ writeSettingString(settingKey(instanceId), normalized)
8811
+ writeSettingString(legacySettingKey(instanceId), normalized)
8015
8812
  end
8016
8813
  end
8017
8814
  local function readServerUrl()
@@ -8019,29 +8816,38 @@ local function readServerUrl()
8019
8816
  return nil
8020
8817
  end
8021
8818
  for _, instanceId in computeInstanceIds() do
8022
- local key = settingKey(instanceId)
8023
- local ok, value = pcall(function()
8024
- return pluginRef:GetSetting(key)
8025
- end)
8026
- if ok and type(value) == "string" and value ~= "" then
8027
- return value
8819
+ local remembered = readSettingString(settingKey(instanceId))
8820
+ if remembered ~= nil then
8821
+ return remembered
8822
+ end
8823
+ end
8824
+ local globalRemembered = readSettingString(GLOBAL_SETTING_KEY)
8825
+ if globalRemembered ~= nil then
8826
+ return globalRemembered
8827
+ end
8828
+ for _, instanceId in computeInstanceIds() do
8829
+ local legacyRemembered = readSettingString(legacySettingKey(instanceId))
8830
+ if legacyRemembered ~= nil then
8831
+ return legacyRemembered
8028
8832
  end
8029
8833
  end
8030
8834
  return nil
8031
8835
  end
8032
8836
  return {
8033
8837
  init = init,
8838
+ normalizeServerUrl = normalizeServerUrl,
8839
+ extractPort = extractPort,
8034
8840
  rememberServerUrl = rememberServerUrl,
8035
8841
  readServerUrl = readServerUrl,
8036
8842
  }
8037
8843
  ]]></string>
8038
8844
  </Properties>
8039
8845
  </Item>
8040
- <Item class="ModuleScript" referent="27">
8846
+ <Item class="ModuleScript" referent="29">
8041
8847
  <Properties>
8042
8848
  <string name="Name">State</string>
8043
8849
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
8044
- local CURRENT_VERSION = "2.16.4"
8850
+ local CURRENT_VERSION = "2.17.1"
8045
8851
  local PLUGIN_VARIANT = "inspector"
8046
8852
  local MAX_CONNECTIONS = 5
8047
8853
  local BASE_PORT = 58741
@@ -8135,7 +8941,7 @@ return {
8135
8941
  ]]></string>
8136
8942
  </Properties>
8137
8943
  </Item>
8138
- <Item class="ModuleScript" referent="28">
8944
+ <Item class="ModuleScript" referent="30">
8139
8945
  <Properties>
8140
8946
  <string name="Name">StopPlayMonitor</string>
8141
8947
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -8433,13 +9239,14 @@ return {
8433
9239
  ]]></string>
8434
9240
  </Properties>
8435
9241
  </Item>
8436
- <Item class="ModuleScript" referent="29">
9242
+ <Item class="ModuleScript" referent="31">
8437
9243
  <Properties>
8438
9244
  <string name="Name">UI</string>
8439
9245
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
8440
9246
  local TS = require(script.Parent.Parent.include.RuntimeLib)
8441
9247
  local TweenService = TS.import(script, script.Parent.Parent, "node_modules", "@rbxts", "services").TweenService
8442
9248
  local State = TS.import(script, script.Parent, "State")
9249
+ local ServerUrlSettings = TS.import(script, script.Parent, "ServerUrlSettings")
8443
9250
  local elements = nil
8444
9251
  local pulseAnimation
8445
9252
  local buttonHover = false
@@ -8878,7 +9685,7 @@ local function init(pluginRef)
8878
9685
  urlInput.Size = UDim2.new(1, 0, 0, 26)
8879
9686
  urlInput.BackgroundColor3 = C.bg
8880
9687
  urlInput.BorderSizePixel = 0
8881
- urlInput.Text = "http://localhost:58741"
9688
+ urlInput.Text = State.getActiveConnection().serverUrl
8882
9689
  urlInput.TextColor3 = C.label
8883
9690
  urlInput.TextSize = 11
8884
9691
  urlInput.Font = Enum.Font.GothamMedium
@@ -8899,14 +9706,16 @@ local function init(pluginRef)
8899
9706
  if not conn or conn.isActive then
8900
9707
  return nil
8901
9708
  end
8902
- conn.serverUrl = urlInput.Text
8903
- local portStr = string.match(conn.serverUrl, ":(%d+)$")
8904
- if portStr ~= 0 and portStr == portStr and portStr ~= "" and portStr then
8905
- local _condition = tonumber(portStr)
8906
- if _condition == nil then
8907
- _condition = conn.port
8908
- end
8909
- conn.port = _condition
9709
+ local normalizedUrl = ServerUrlSettings.normalizeServerUrl(urlInput.Text)
9710
+ if normalizedUrl == "" then
9711
+ urlInput.Text = conn.serverUrl
9712
+ return nil
9713
+ end
9714
+ conn.serverUrl = normalizedUrl
9715
+ urlInput.Text = normalizedUrl
9716
+ local port = ServerUrlSettings.extractPort(conn.serverUrl)
9717
+ if port ~= nil then
9718
+ conn.port = port
8910
9719
  end
8911
9720
  updateTabLabel(State.getActiveTabIndex())
8912
9721
  end)
@@ -9204,11 +10013,37 @@ return {
9204
10013
  ]]></string>
9205
10014
  </Properties>
9206
10015
  </Item>
9207
- <Item class="ModuleScript" referent="30">
10016
+ <Item class="ModuleScript" referent="32">
9208
10017
  <Properties>
9209
10018
  <string name="Name">Utils</string>
9210
10019
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
9211
10020
  local ScriptEditorService = game:GetService("ScriptEditorService")
10021
+ local LUAU_KEYWORDS = {
10022
+ ["and"] = true,
10023
+ ["break"] = true,
10024
+ ["continue"] = true,
10025
+ ["do"] = true,
10026
+ ["else"] = true,
10027
+ ["elseif"] = true,
10028
+ ["end"] = true,
10029
+ ["export"] = true,
10030
+ ["false"] = true,
10031
+ ["for"] = true,
10032
+ ["function"] = true,
10033
+ ["if"] = true,
10034
+ ["in"] = true,
10035
+ ["local"] = true,
10036
+ ["nil"] = true,
10037
+ ["not"] = true,
10038
+ ["or"] = true,
10039
+ ["repeat"] = true,
10040
+ ["return"] = true,
10041
+ ["then"] = true,
10042
+ ["true"] = true,
10043
+ ["type"] = true,
10044
+ ["until"] = true,
10045
+ ["while"] = true,
10046
+ }
9212
10047
  local function safeCall(func, ...)
9213
10048
  local args = { ... }
9214
10049
  local success, result = pcall(func, unpack(args))
@@ -9219,6 +10054,194 @@ local function safeCall(func, ...)
9219
10054
  return nil
9220
10055
  end
9221
10056
  end
10057
+ local function isSimplePathSegment(segment)
10058
+ local _condition = (string.match(segment, "^[%a_][%w_]*$")) ~= nil
10059
+ if _condition then
10060
+ local _segment = segment
10061
+ _condition = not (LUAU_KEYWORDS[_segment] ~= nil)
10062
+ end
10063
+ return _condition
10064
+ end
10065
+ local function quotePathSegment(segment)
10066
+ local escaped = (string.gsub(segment, "\\", "\\\\"))
10067
+ escaped = (string.gsub(escaped, "\n", "\\n"))
10068
+ escaped = (string.gsub(escaped, "\r", "\\r"))
10069
+ escaped = (string.gsub(escaped, "\t", "\\t"))
10070
+ escaped = (string.gsub(escaped, '"', '\\"'))
10071
+ return `"{escaped}"`
10072
+ end
10073
+ local function unescapePathSegment(segment)
10074
+ local chars = {}
10075
+ local i = 1
10076
+ while i <= #segment do
10077
+ local _segment = segment
10078
+ local _i = i
10079
+ local _i_1 = i
10080
+ local ch = string.sub(_segment, _i, _i_1)
10081
+ if ch == "\\" and i < #segment then
10082
+ local _segment_1 = segment
10083
+ local _arg0 = i + 1
10084
+ local _arg1 = i + 1
10085
+ local nextChar = string.sub(_segment_1, _arg0, _arg1)
10086
+ if nextChar == "n" then
10087
+ table.insert(chars, "\n")
10088
+ elseif nextChar == "r" then
10089
+ table.insert(chars, "\r")
10090
+ elseif nextChar == "t" then
10091
+ table.insert(chars, "\t")
10092
+ else
10093
+ table.insert(chars, nextChar)
10094
+ end
10095
+ i += 2
10096
+ else
10097
+ table.insert(chars, ch)
10098
+ i += 1
10099
+ end
10100
+ end
10101
+ return table.concat(chars, "")
10102
+ end
10103
+ local function isCanonicalBracketStart(path, index)
10104
+ local _path = path
10105
+ local _arg0 = index + 1
10106
+ local _arg1 = index + 1
10107
+ local quote = string.sub(_path, _arg0, _arg1)
10108
+ local _condition = (quote == '"' or quote == "'")
10109
+ if _condition then
10110
+ local _path_1 = path
10111
+ local _arg0_1 = index - 1
10112
+ local _arg1_1 = index - 1
10113
+ _condition = string.sub(_path_1, _arg0_1, _arg1_1) ~= "."
10114
+ end
10115
+ return _condition
10116
+ end
10117
+ local function parseInstancePath(path)
10118
+ local i = 1
10119
+ local len = #path
10120
+ local parts = {}
10121
+ local current = ""
10122
+ if path == "" or path == "game" then
10123
+ return parts
10124
+ end
10125
+ if string.sub(path, 1, 5) == "game." then
10126
+ i = 6
10127
+ elseif string.sub(path, 1, 5) == "game[" then
10128
+ i = 5
10129
+ end
10130
+ while i <= len do
10131
+ local _path = path
10132
+ local _i = i
10133
+ local _i_1 = i
10134
+ local ch = string.sub(_path, _i, _i_1)
10135
+ if ch == "." then
10136
+ if current ~= "" then
10137
+ local _current = current
10138
+ table.insert(parts, _current)
10139
+ current = ""
10140
+ i += 1
10141
+ else
10142
+ local _condition = i > 1
10143
+ if _condition then
10144
+ local _path_1 = path
10145
+ local _arg0 = i - 1
10146
+ local _arg1 = i - 1
10147
+ _condition = string.sub(_path_1, _arg0, _arg1) == "."
10148
+ if _condition then
10149
+ _condition = i < len
10150
+ if _condition then
10151
+ local _path_2 = path
10152
+ local _arg0_1 = i + 1
10153
+ local _arg1_1 = i + 1
10154
+ _condition = string.sub(_path_2, _arg0_1, _arg1_1) ~= "["
10155
+ end
10156
+ end
10157
+ end
10158
+ if _condition then
10159
+ -- Back-compat for previously emitted paths such as
10160
+ -- game.ServerScriptService..dir.ReproScript, where ".dir"
10161
+ -- was an actual instance name.
10162
+ current = "."
10163
+ i += 1
10164
+ else
10165
+ i += 1
10166
+ end
10167
+ end
10168
+ elseif ch == "[" and i < len and isCanonicalBracketStart(path, i) then
10169
+ if current ~= "" then
10170
+ local _current = current
10171
+ table.insert(parts, _current)
10172
+ current = ""
10173
+ end
10174
+ local _path_1 = path
10175
+ local _arg0 = i + 1
10176
+ local _arg1 = i + 1
10177
+ local quote = string.sub(_path_1, _arg0, _arg1)
10178
+ if quote ~= '"' and quote ~= "'" then
10179
+ return nil
10180
+ end
10181
+ local j = i + 2
10182
+ local raw = ""
10183
+ while j <= len do
10184
+ local _path_2 = path
10185
+ local _j = j
10186
+ local _j_1 = j
10187
+ local c = string.sub(_path_2, _j, _j_1)
10188
+ if c == "\\" then
10189
+ if j >= len then
10190
+ return nil
10191
+ end
10192
+ local _path_3 = path
10193
+ local _arg0_1 = j + 1
10194
+ local _arg1_1 = j + 1
10195
+ raw ..= c .. string.sub(_path_3, _arg0_1, _arg1_1)
10196
+ j += 2
10197
+ elseif c == quote then
10198
+ break
10199
+ else
10200
+ raw ..= c
10201
+ j += 1
10202
+ end
10203
+ end
10204
+ local _condition = j > len
10205
+ if not _condition then
10206
+ local _path_2 = path
10207
+ local _j = j
10208
+ local _j_1 = j
10209
+ _condition = string.sub(_path_2, _j, _j_1) ~= quote
10210
+ if not _condition then
10211
+ local _path_3 = path
10212
+ local _arg0_1 = j + 1
10213
+ local _arg1_1 = j + 1
10214
+ _condition = string.sub(_path_3, _arg0_1, _arg1_1) ~= "]"
10215
+ end
10216
+ end
10217
+ if _condition then
10218
+ return nil
10219
+ end
10220
+ local _arg0_1 = unescapePathSegment(raw)
10221
+ table.insert(parts, _arg0_1)
10222
+ i = j + 2
10223
+ else
10224
+ current ..= ch
10225
+ i += 1
10226
+ end
10227
+ end
10228
+ if current ~= "" then
10229
+ local _current = current
10230
+ table.insert(parts, _current)
10231
+ end
10232
+ return parts
10233
+ end
10234
+ local function getRootSegment(instance)
10235
+ if instance.Parent == game then
10236
+ local ok, service = pcall(function()
10237
+ return game:GetService(instance.ClassName)
10238
+ end)
10239
+ if ok and service == instance then
10240
+ return instance.ClassName
10241
+ end
10242
+ end
10243
+ return instance.Name
10244
+ end
9222
10245
  local function getInstancePath(instance)
9223
10246
  if not instance or instance == game then
9224
10247
  return "game"
@@ -9226,23 +10249,40 @@ local function getInstancePath(instance)
9226
10249
  local pathParts = {}
9227
10250
  local current = instance
9228
10251
  while current and current ~= game do
9229
- local _name = current.Name
9230
- table.insert(pathParts, 1, _name)
10252
+ local _arg0 = getRootSegment(current)
10253
+ table.insert(pathParts, 1, _arg0)
9231
10254
  current = current.Parent
9232
10255
  end
9233
- return `game.{table.concat(pathParts, ".")}`
10256
+ local path = "game"
10257
+ for _, part in pathParts do
10258
+ if isSimplePathSegment(part) then
10259
+ path ..= `.{part}`
10260
+ else
10261
+ path ..= `[{quotePathSegment(part)}]`
10262
+ end
10263
+ end
10264
+ return path
10265
+ end
10266
+ local function getRootInstance(segment)
10267
+ local ok, service = pcall(function()
10268
+ return game:GetService(segment)
10269
+ end)
10270
+ if ok and service then
10271
+ return service
10272
+ end
10273
+ return game:FindFirstChild(segment)
9234
10274
  end
9235
10275
  local function getInstanceByPath(path)
9236
- if path == "game" or path == "" then
9237
- return game
10276
+ local parts = parseInstancePath(path)
10277
+ if parts == nil then
10278
+ return nil
9238
10279
  end
9239
- local cleaned = (string.gsub(path, "^game%.", ""))
9240
- local parts = {}
9241
- for part in string.gmatch(cleaned, "[^%.]+") do
9242
- table.insert(parts, part)
10280
+ if #parts == 0 then
10281
+ return game
9243
10282
  end
9244
- local current = game
9245
- for _, part in parts do
10283
+ local current = getRootInstance(parts[1])
10284
+ for i = 1, #parts - 1 do
10285
+ local part = parts[i + 1]
9246
10286
  if not current then
9247
10287
  return nil
9248
10288
  end
@@ -9734,11 +10774,11 @@ return {
9734
10774
  </Properties>
9735
10775
  </Item>
9736
10776
  </Item>
9737
- <Item class="Folder" referent="34">
10777
+ <Item class="Folder" referent="36">
9738
10778
  <Properties>
9739
10779
  <string name="Name">include</string>
9740
10780
  </Properties>
9741
- <Item class="ModuleScript" referent="31">
10781
+ <Item class="ModuleScript" referent="33">
9742
10782
  <Properties>
9743
10783
  <string name="Name">Promise</string>
9744
10784
  <string name="Source"><![CDATA[--[[
@@ -11812,7 +12852,7 @@ return Promise
11812
12852
  ]]></string>
11813
12853
  </Properties>
11814
12854
  </Item>
11815
- <Item class="ModuleScript" referent="32">
12855
+ <Item class="ModuleScript" referent="34">
11816
12856
  <Properties>
11817
12857
  <string name="Name">RuntimeLib</string>
11818
12858
  <string name="Source"><![CDATA[local Promise = require(script.Parent.Promise)
@@ -12079,15 +13119,15 @@ return TS
12079
13119
  </Properties>
12080
13120
  </Item>
12081
13121
  </Item>
12082
- <Item class="Folder" referent="35">
13122
+ <Item class="Folder" referent="37">
12083
13123
  <Properties>
12084
13124
  <string name="Name">node_modules</string>
12085
13125
  </Properties>
12086
- <Item class="Folder" referent="36">
13126
+ <Item class="Folder" referent="38">
12087
13127
  <Properties>
12088
13128
  <string name="Name">@rbxts</string>
12089
13129
  </Properties>
12090
- <Item class="ModuleScript" referent="33">
13130
+ <Item class="ModuleScript" referent="35">
12091
13131
  <Properties>
12092
13132
  <string name="Name">services</string>
12093
13133
  <string name="Source"><![CDATA[return setmetatable({}, {