@chrrxs/robloxstudio-mcp-inspector 2.21.0 → 2.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -712,11 +712,11 @@ local function computeInstanceId()
|
|
|
712
712
|
return `anon:{fresh}`
|
|
713
713
|
end
|
|
714
714
|
local assignedRole
|
|
715
|
-
local duplicateInstanceRole = false
|
|
716
715
|
local hasVersionMismatch = false
|
|
717
716
|
local lastVersionMismatchWarningKey
|
|
718
717
|
local lastReadyInstanceId
|
|
719
718
|
local readyFailureLogKeys = {}
|
|
719
|
+
local retryingDuplicateReady = false
|
|
720
720
|
-- Cache the published place name from MarketplaceService:GetProductInfo so
|
|
721
721
|
-- /ready can carry a friendly identifier (e.g. "Natural Disasters") distinct
|
|
722
722
|
-- from game.Name (the DataModel name, often "Place1" in edit). We only fetch
|
|
@@ -906,11 +906,12 @@ local function ensureIdentityWatcher(conn)
|
|
|
906
906
|
end
|
|
907
907
|
end
|
|
908
908
|
function sendReady(conn)
|
|
909
|
-
if duplicateInstanceRole then
|
|
910
|
-
return nil
|
|
911
|
-
end
|
|
912
909
|
local now = tick()
|
|
913
|
-
|
|
910
|
+
-- Normal identity refreshes stay conservatively throttled. Once a stale
|
|
911
|
+
-- predecessor causes 409, retry once per second so takeover follows the
|
|
912
|
+
-- server's short inactivity lease without another two seconds of jitter.
|
|
913
|
+
local readyInterval = if retryingDuplicateReady then 1 else 2
|
|
914
|
+
if now - lastReadyPostAt < readyInterval then
|
|
914
915
|
return nil
|
|
915
916
|
end
|
|
916
917
|
lastReadyPostAt = now
|
|
@@ -942,28 +943,39 @@ function sendReady(conn)
|
|
|
942
943
|
local readyRole = detectRole()
|
|
943
944
|
local readyLogKey = `{conn.serverUrl}|{instanceId}|{readyRole}`
|
|
944
945
|
if not readyOk then
|
|
946
|
+
local shouldLog = not (readyFailureLogKeys[readyLogKey] ~= nil)
|
|
945
947
|
readyFailureLogKeys[readyLogKey] = true
|
|
946
|
-
|
|
948
|
+
if shouldLog then
|
|
949
|
+
warn(`[robloxstudio-mcp] /ready failed for {instanceId}/{readyRole}: {HttpDiagnostics.formatRequestFailure(readyUrl, readyOk, readyResult)}`)
|
|
950
|
+
end
|
|
947
951
|
return nil
|
|
948
952
|
end
|
|
949
953
|
if not readyResult.Success then
|
|
950
954
|
local reason = HttpDiagnostics.formatRequestFailure(readyUrl, true, readyResult)
|
|
955
|
+
local shouldLog = not (readyFailureLogKeys[readyLogKey] ~= nil)
|
|
951
956
|
readyFailureLogKeys[readyLogKey] = true
|
|
952
|
-
--
|
|
957
|
+
-- A predecessor can remain registered briefly when Studio exits before
|
|
958
|
+
-- its asynchronous /disconnect completes. Keep polling and retrying
|
|
959
|
+
-- /ready: the server will take us over once the predecessor has stopped
|
|
960
|
+
-- polling, while a genuinely active duplicate continues to hold routing.
|
|
953
961
|
if readyResult.StatusCode == 409 then
|
|
954
|
-
|
|
955
|
-
conn.isActive = false
|
|
962
|
+
retryingDuplicateReady = true
|
|
956
963
|
local ui = UI.getElements()
|
|
957
|
-
ui.statusLabel.Text = "
|
|
958
|
-
ui.statusLabel.TextColor3 = Color3.fromRGB(
|
|
964
|
+
ui.statusLabel.Text = "Waiting for previous instance"
|
|
965
|
+
ui.statusLabel.TextColor3 = Color3.fromRGB(245, 158, 11)
|
|
959
966
|
ui.detailStatusLabel.Text = reason
|
|
960
|
-
ui.detailStatusLabel.TextColor3 = Color3.fromRGB(
|
|
961
|
-
|
|
967
|
+
ui.detailStatusLabel.TextColor3 = Color3.fromRGB(245, 158, 11)
|
|
968
|
+
if shouldLog then
|
|
969
|
+
warn(`[robloxstudio-mcp] /ready rejected for {instanceId}/{readyRole}: {reason}`)
|
|
970
|
+
end
|
|
962
971
|
return nil
|
|
963
972
|
end
|
|
964
|
-
|
|
973
|
+
if shouldLog then
|
|
974
|
+
warn(`[robloxstudio-mcp] /ready rejected for {instanceId}/{readyRole}: {reason}`)
|
|
975
|
+
end
|
|
965
976
|
return nil
|
|
966
977
|
end
|
|
978
|
+
retryingDuplicateReady = false
|
|
967
979
|
local parseOk, readyData = pcall(function()
|
|
968
980
|
return HttpService:JSONDecode(readyResult.Body)
|
|
969
981
|
end)
|
|
@@ -1413,9 +1425,9 @@ local function computeBridgeStamp()
|
|
|
1413
1425
|
for i = 1, #combined do
|
|
1414
1426
|
h = (h * 33 + (string.byte(combined, i))) % 2147483647
|
|
1415
1427
|
end
|
|
1416
|
-
-- "2.
|
|
1428
|
+
-- "2.22.1" is replaced with the package version at package time
|
|
1417
1429
|
-- (scripts/build-plugin.mjs injectVersion), so a release bump also restamps.
|
|
1418
|
-
return `{tostring(h)}-2.
|
|
1430
|
+
return `{tostring(h)}-2.22.1`
|
|
1419
1431
|
end
|
|
1420
1432
|
local BRIDGE_STAMP = computeBridgeStamp()
|
|
1421
1433
|
local function setSource(scriptInst, source)
|
|
@@ -10449,24 +10461,30 @@ local function pushEntry(msg, t, ts)
|
|
|
10449
10461
|
totalBytes += bytes
|
|
10450
10462
|
end
|
|
10451
10463
|
local function seedRuntimeHistory()
|
|
10452
|
-
if not RunService:IsRunning() then
|
|
10453
|
-
return nil
|
|
10454
|
-
end
|
|
10455
10464
|
local ok, history = pcall(function()
|
|
10456
10465
|
return LogService:GetLogHistory()
|
|
10457
10466
|
end)
|
|
10458
10467
|
if not ok then
|
|
10459
10468
|
return nil
|
|
10460
10469
|
end
|
|
10470
|
+
local isEdit = not RunService:IsRunning()
|
|
10471
|
+
-- GetLogHistory timestamps and DateTime.now() share Unix time, while
|
|
10472
|
+
-- os.clock() is elapsed time for this Studio process. Their difference is
|
|
10473
|
+
-- therefore the process launch boundary. Edit-mode history is filtered to
|
|
10474
|
+
-- that boundary so startup errors from this launch are recovered without
|
|
10475
|
+
-- importing history left by an earlier Studio process.
|
|
10476
|
+
local processStartedAt = nowSec() - os.clock()
|
|
10461
10477
|
for _, entry in history do
|
|
10462
10478
|
local _message = entry.message
|
|
10463
10479
|
if not (type(_message) == "string") then
|
|
10464
10480
|
continue
|
|
10465
10481
|
end
|
|
10466
|
-
local _exp = entry.message
|
|
10467
|
-
local _exp_1 = entry.messageType
|
|
10468
10482
|
local _timestamp = entry.timestamp
|
|
10469
|
-
|
|
10483
|
+
local timestamp = if type(_timestamp) == "number" then entry.timestamp else nil
|
|
10484
|
+
if isEdit and (timestamp == nil or timestamp < processStartedAt - 1) then
|
|
10485
|
+
continue
|
|
10486
|
+
end
|
|
10487
|
+
pushEntry(entry.message, entry.messageType, timestamp)
|
|
10470
10488
|
end
|
|
10471
10489
|
end
|
|
10472
10490
|
local function install()
|
|
@@ -10477,9 +10495,9 @@ local function install()
|
|
|
10477
10495
|
return nil
|
|
10478
10496
|
end
|
|
10479
10497
|
installed = true
|
|
10480
|
-
--
|
|
10481
|
-
-- Seed from per-DataModel LogHistory so get_runtime_logs can still see
|
|
10482
|
-
--
|
|
10498
|
+
-- Every peer can emit startup logs before the plugin finishes loading.
|
|
10499
|
+
-- Seed from per-DataModel LogHistory so get_runtime_logs can still see them;
|
|
10500
|
+
-- edit history is bounded to the current Studio process above.
|
|
10483
10501
|
seedRuntimeHistory()
|
|
10484
10502
|
LogService.MessageOut:Connect(function(msg, t)
|
|
10485
10503
|
pushEntry(msg, t)
|
|
@@ -10745,7 +10763,7 @@ return {
|
|
|
10745
10763
|
<Properties>
|
|
10746
10764
|
<string name="Name">State</string>
|
|
10747
10765
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
10748
|
-
local CURRENT_VERSION = "2.
|
|
10766
|
+
local CURRENT_VERSION = "2.22.1"
|
|
10749
10767
|
local PLUGIN_VARIANT = "inspector"
|
|
10750
10768
|
local BASE_PORT = 58741
|
|
10751
10769
|
local function createConnection(port)
|
|
@@ -712,11 +712,11 @@ local function computeInstanceId()
|
|
|
712
712
|
return `anon:{fresh}`
|
|
713
713
|
end
|
|
714
714
|
local assignedRole
|
|
715
|
-
local duplicateInstanceRole = false
|
|
716
715
|
local hasVersionMismatch = false
|
|
717
716
|
local lastVersionMismatchWarningKey
|
|
718
717
|
local lastReadyInstanceId
|
|
719
718
|
local readyFailureLogKeys = {}
|
|
719
|
+
local retryingDuplicateReady = false
|
|
720
720
|
-- Cache the published place name from MarketplaceService:GetProductInfo so
|
|
721
721
|
-- /ready can carry a friendly identifier (e.g. "Natural Disasters") distinct
|
|
722
722
|
-- from game.Name (the DataModel name, often "Place1" in edit). We only fetch
|
|
@@ -906,11 +906,12 @@ local function ensureIdentityWatcher(conn)
|
|
|
906
906
|
end
|
|
907
907
|
end
|
|
908
908
|
function sendReady(conn)
|
|
909
|
-
if duplicateInstanceRole then
|
|
910
|
-
return nil
|
|
911
|
-
end
|
|
912
909
|
local now = tick()
|
|
913
|
-
|
|
910
|
+
-- Normal identity refreshes stay conservatively throttled. Once a stale
|
|
911
|
+
-- predecessor causes 409, retry once per second so takeover follows the
|
|
912
|
+
-- server's short inactivity lease without another two seconds of jitter.
|
|
913
|
+
local readyInterval = if retryingDuplicateReady then 1 else 2
|
|
914
|
+
if now - lastReadyPostAt < readyInterval then
|
|
914
915
|
return nil
|
|
915
916
|
end
|
|
916
917
|
lastReadyPostAt = now
|
|
@@ -942,28 +943,39 @@ function sendReady(conn)
|
|
|
942
943
|
local readyRole = detectRole()
|
|
943
944
|
local readyLogKey = `{conn.serverUrl}|{instanceId}|{readyRole}`
|
|
944
945
|
if not readyOk then
|
|
946
|
+
local shouldLog = not (readyFailureLogKeys[readyLogKey] ~= nil)
|
|
945
947
|
readyFailureLogKeys[readyLogKey] = true
|
|
946
|
-
|
|
948
|
+
if shouldLog then
|
|
949
|
+
warn(`[robloxstudio-mcp] /ready failed for {instanceId}/{readyRole}: {HttpDiagnostics.formatRequestFailure(readyUrl, readyOk, readyResult)}`)
|
|
950
|
+
end
|
|
947
951
|
return nil
|
|
948
952
|
end
|
|
949
953
|
if not readyResult.Success then
|
|
950
954
|
local reason = HttpDiagnostics.formatRequestFailure(readyUrl, true, readyResult)
|
|
955
|
+
local shouldLog = not (readyFailureLogKeys[readyLogKey] ~= nil)
|
|
951
956
|
readyFailureLogKeys[readyLogKey] = true
|
|
952
|
-
--
|
|
957
|
+
-- A predecessor can remain registered briefly when Studio exits before
|
|
958
|
+
-- its asynchronous /disconnect completes. Keep polling and retrying
|
|
959
|
+
-- /ready: the server will take us over once the predecessor has stopped
|
|
960
|
+
-- polling, while a genuinely active duplicate continues to hold routing.
|
|
953
961
|
if readyResult.StatusCode == 409 then
|
|
954
|
-
|
|
955
|
-
conn.isActive = false
|
|
962
|
+
retryingDuplicateReady = true
|
|
956
963
|
local ui = UI.getElements()
|
|
957
|
-
ui.statusLabel.Text = "
|
|
958
|
-
ui.statusLabel.TextColor3 = Color3.fromRGB(
|
|
964
|
+
ui.statusLabel.Text = "Waiting for previous instance"
|
|
965
|
+
ui.statusLabel.TextColor3 = Color3.fromRGB(245, 158, 11)
|
|
959
966
|
ui.detailStatusLabel.Text = reason
|
|
960
|
-
ui.detailStatusLabel.TextColor3 = Color3.fromRGB(
|
|
961
|
-
|
|
967
|
+
ui.detailStatusLabel.TextColor3 = Color3.fromRGB(245, 158, 11)
|
|
968
|
+
if shouldLog then
|
|
969
|
+
warn(`[robloxstudio-mcp] /ready rejected for {instanceId}/{readyRole}: {reason}`)
|
|
970
|
+
end
|
|
962
971
|
return nil
|
|
963
972
|
end
|
|
964
|
-
|
|
973
|
+
if shouldLog then
|
|
974
|
+
warn(`[robloxstudio-mcp] /ready rejected for {instanceId}/{readyRole}: {reason}`)
|
|
975
|
+
end
|
|
965
976
|
return nil
|
|
966
977
|
end
|
|
978
|
+
retryingDuplicateReady = false
|
|
967
979
|
local parseOk, readyData = pcall(function()
|
|
968
980
|
return HttpService:JSONDecode(readyResult.Body)
|
|
969
981
|
end)
|
|
@@ -1413,9 +1425,9 @@ local function computeBridgeStamp()
|
|
|
1413
1425
|
for i = 1, #combined do
|
|
1414
1426
|
h = (h * 33 + (string.byte(combined, i))) % 2147483647
|
|
1415
1427
|
end
|
|
1416
|
-
-- "2.
|
|
1428
|
+
-- "2.22.1" is replaced with the package version at package time
|
|
1417
1429
|
-- (scripts/build-plugin.mjs injectVersion), so a release bump also restamps.
|
|
1418
|
-
return `{tostring(h)}-2.
|
|
1430
|
+
return `{tostring(h)}-2.22.1`
|
|
1419
1431
|
end
|
|
1420
1432
|
local BRIDGE_STAMP = computeBridgeStamp()
|
|
1421
1433
|
local function setSource(scriptInst, source)
|
|
@@ -10449,24 +10461,30 @@ local function pushEntry(msg, t, ts)
|
|
|
10449
10461
|
totalBytes += bytes
|
|
10450
10462
|
end
|
|
10451
10463
|
local function seedRuntimeHistory()
|
|
10452
|
-
if not RunService:IsRunning() then
|
|
10453
|
-
return nil
|
|
10454
|
-
end
|
|
10455
10464
|
local ok, history = pcall(function()
|
|
10456
10465
|
return LogService:GetLogHistory()
|
|
10457
10466
|
end)
|
|
10458
10467
|
if not ok then
|
|
10459
10468
|
return nil
|
|
10460
10469
|
end
|
|
10470
|
+
local isEdit = not RunService:IsRunning()
|
|
10471
|
+
-- GetLogHistory timestamps and DateTime.now() share Unix time, while
|
|
10472
|
+
-- os.clock() is elapsed time for this Studio process. Their difference is
|
|
10473
|
+
-- therefore the process launch boundary. Edit-mode history is filtered to
|
|
10474
|
+
-- that boundary so startup errors from this launch are recovered without
|
|
10475
|
+
-- importing history left by an earlier Studio process.
|
|
10476
|
+
local processStartedAt = nowSec() - os.clock()
|
|
10461
10477
|
for _, entry in history do
|
|
10462
10478
|
local _message = entry.message
|
|
10463
10479
|
if not (type(_message) == "string") then
|
|
10464
10480
|
continue
|
|
10465
10481
|
end
|
|
10466
|
-
local _exp = entry.message
|
|
10467
|
-
local _exp_1 = entry.messageType
|
|
10468
10482
|
local _timestamp = entry.timestamp
|
|
10469
|
-
|
|
10483
|
+
local timestamp = if type(_timestamp) == "number" then entry.timestamp else nil
|
|
10484
|
+
if isEdit and (timestamp == nil or timestamp < processStartedAt - 1) then
|
|
10485
|
+
continue
|
|
10486
|
+
end
|
|
10487
|
+
pushEntry(entry.message, entry.messageType, timestamp)
|
|
10470
10488
|
end
|
|
10471
10489
|
end
|
|
10472
10490
|
local function install()
|
|
@@ -10477,9 +10495,9 @@ local function install()
|
|
|
10477
10495
|
return nil
|
|
10478
10496
|
end
|
|
10479
10497
|
installed = true
|
|
10480
|
-
--
|
|
10481
|
-
-- Seed from per-DataModel LogHistory so get_runtime_logs can still see
|
|
10482
|
-
--
|
|
10498
|
+
-- Every peer can emit startup logs before the plugin finishes loading.
|
|
10499
|
+
-- Seed from per-DataModel LogHistory so get_runtime_logs can still see them;
|
|
10500
|
+
-- edit history is bounded to the current Studio process above.
|
|
10483
10501
|
seedRuntimeHistory()
|
|
10484
10502
|
LogService.MessageOut:Connect(function(msg, t)
|
|
10485
10503
|
pushEntry(msg, t)
|
|
@@ -10745,7 +10763,7 @@ return {
|
|
|
10745
10763
|
<Properties>
|
|
10746
10764
|
<string name="Name">State</string>
|
|
10747
10765
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
10748
|
-
local CURRENT_VERSION = "2.
|
|
10766
|
+
local CURRENT_VERSION = "2.22.1"
|
|
10749
10767
|
local PLUGIN_VARIANT = "main"
|
|
10750
10768
|
local BASE_PORT = 58741
|
|
10751
10769
|
local function createConnection(port)
|
|
@@ -53,11 +53,11 @@ function computeInstanceId(): string {
|
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
let assignedRole: string | undefined;
|
|
56
|
-
let duplicateInstanceRole = false;
|
|
57
56
|
let hasVersionMismatch = false;
|
|
58
57
|
let lastVersionMismatchWarningKey: string | undefined;
|
|
59
58
|
let lastReadyInstanceId: string | undefined;
|
|
60
59
|
const readyFailureLogKeys = new Set<string>();
|
|
60
|
+
let retryingDuplicateReady = false;
|
|
61
61
|
|
|
62
62
|
// Cache the published place name from MarketplaceService:GetProductInfo so
|
|
63
63
|
// /ready can carry a friendly identifier (e.g. "Natural Disasters") distinct
|
|
@@ -248,9 +248,12 @@ function ensureIdentityWatcher(conn: Connection): void {
|
|
|
248
248
|
}
|
|
249
249
|
|
|
250
250
|
function sendReady(conn: Connection): void {
|
|
251
|
-
if (duplicateInstanceRole) return; // stop retrying once the server has rejected us
|
|
252
251
|
const now = tick();
|
|
253
|
-
|
|
252
|
+
// Normal identity refreshes stay conservatively throttled. Once a stale
|
|
253
|
+
// predecessor causes 409, retry once per second so takeover follows the
|
|
254
|
+
// server's short inactivity lease without another two seconds of jitter.
|
|
255
|
+
const readyInterval = retryingDuplicateReady ? 1 : 2;
|
|
256
|
+
if (now - lastReadyPostAt < readyInterval) return;
|
|
254
257
|
lastReadyPostAt = now;
|
|
255
258
|
const instanceId = computeInstanceId();
|
|
256
259
|
task.spawn(() => {
|
|
@@ -278,28 +281,39 @@ function sendReady(conn: Connection): void {
|
|
|
278
281
|
const readyRole = detectRole();
|
|
279
282
|
const readyLogKey = `${conn.serverUrl}|${instanceId}|${readyRole}`;
|
|
280
283
|
if (!readyOk) {
|
|
284
|
+
const shouldLog = !readyFailureLogKeys.has(readyLogKey);
|
|
281
285
|
readyFailureLogKeys.add(readyLogKey);
|
|
282
|
-
|
|
286
|
+
if (shouldLog) {
|
|
287
|
+
warn(`[robloxstudio-mcp] /ready failed for ${instanceId}/${readyRole}: ${HttpDiagnostics.formatRequestFailure(readyUrl, readyOk, readyResult)}`);
|
|
288
|
+
}
|
|
283
289
|
return;
|
|
284
290
|
}
|
|
285
291
|
if (!readyResult.Success) {
|
|
286
292
|
const reason = HttpDiagnostics.formatRequestFailure(readyUrl, true, readyResult);
|
|
293
|
+
const shouldLog = !readyFailureLogKeys.has(readyLogKey);
|
|
287
294
|
readyFailureLogKeys.add(readyLogKey);
|
|
288
|
-
//
|
|
295
|
+
// A predecessor can remain registered briefly when Studio exits before
|
|
296
|
+
// its asynchronous /disconnect completes. Keep polling and retrying
|
|
297
|
+
// /ready: the server will take us over once the predecessor has stopped
|
|
298
|
+
// polling, while a genuinely active duplicate continues to hold routing.
|
|
289
299
|
if (readyResult.StatusCode === 409) {
|
|
290
|
-
|
|
291
|
-
conn.isActive = false;
|
|
300
|
+
retryingDuplicateReady = true;
|
|
292
301
|
const ui = UI.getElements();
|
|
293
|
-
ui.statusLabel.Text = "
|
|
294
|
-
ui.statusLabel.TextColor3 = Color3.fromRGB(
|
|
302
|
+
ui.statusLabel.Text = "Waiting for previous instance";
|
|
303
|
+
ui.statusLabel.TextColor3 = Color3.fromRGB(245, 158, 11);
|
|
295
304
|
ui.detailStatusLabel.Text = reason;
|
|
296
|
-
ui.detailStatusLabel.TextColor3 = Color3.fromRGB(
|
|
297
|
-
|
|
305
|
+
ui.detailStatusLabel.TextColor3 = Color3.fromRGB(245, 158, 11);
|
|
306
|
+
if (shouldLog) {
|
|
307
|
+
warn(`[robloxstudio-mcp] /ready rejected for ${instanceId}/${readyRole}: ${reason}`);
|
|
308
|
+
}
|
|
298
309
|
return;
|
|
299
310
|
}
|
|
300
|
-
|
|
311
|
+
if (shouldLog) {
|
|
312
|
+
warn(`[robloxstudio-mcp] /ready rejected for ${instanceId}/${readyRole}: ${reason}`);
|
|
313
|
+
}
|
|
301
314
|
return;
|
|
302
315
|
}
|
|
316
|
+
retryingDuplicateReady = false;
|
|
303
317
|
const [parseOk, readyData] = pcall(
|
|
304
318
|
() => HttpService.JSONDecode(readyResult.Body) as ReadyResponse,
|
|
305
319
|
);
|
|
@@ -77,14 +77,21 @@ interface LogHistoryEntry {
|
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
function seedRuntimeHistory(): void {
|
|
80
|
-
if (!RunService.IsRunning()) return;
|
|
81
|
-
|
|
82
80
|
const [ok, history] = pcall(() => LogService.GetLogHistory() as LogHistoryEntry[]);
|
|
83
81
|
if (!ok) return;
|
|
82
|
+
const isEdit = !RunService.IsRunning();
|
|
83
|
+
// GetLogHistory timestamps and DateTime.now() share Unix time, while
|
|
84
|
+
// os.clock() is elapsed time for this Studio process. Their difference is
|
|
85
|
+
// therefore the process launch boundary. Edit-mode history is filtered to
|
|
86
|
+
// that boundary so startup errors from this launch are recovered without
|
|
87
|
+
// importing history left by an earlier Studio process.
|
|
88
|
+
const processStartedAt = nowSec() - os.clock();
|
|
84
89
|
|
|
85
90
|
for (const entry of history) {
|
|
86
91
|
if (!typeIs(entry.message, "string")) continue;
|
|
87
|
-
|
|
92
|
+
const timestamp = typeIs(entry.timestamp, "number") ? entry.timestamp : undefined;
|
|
93
|
+
if (isEdit && (timestamp === undefined || timestamp < processStartedAt - 1)) continue;
|
|
94
|
+
pushEntry(entry.message, entry.messageType, timestamp);
|
|
88
95
|
}
|
|
89
96
|
}
|
|
90
97
|
|
|
@@ -92,9 +99,9 @@ function install(): void {
|
|
|
92
99
|
if (installed) return;
|
|
93
100
|
if (!RunService.IsStudio()) return;
|
|
94
101
|
installed = true;
|
|
95
|
-
//
|
|
96
|
-
// Seed from per-DataModel LogHistory so get_runtime_logs can still see
|
|
97
|
-
//
|
|
102
|
+
// Every peer can emit startup logs before the plugin finishes loading.
|
|
103
|
+
// Seed from per-DataModel LogHistory so get_runtime_logs can still see them;
|
|
104
|
+
// edit history is bounded to the current Studio process above.
|
|
98
105
|
seedRuntimeHistory();
|
|
99
106
|
LogService.MessageOut.Connect((msg, t) => {
|
|
100
107
|
pushEntry(msg, t);
|