@chrrxs/robloxstudio-mcp-inspector 2.22.2 → 2.22.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -889,6 +889,7 @@ function requiredClosedLineRange(body, toolName) {
|
|
|
889
889
|
}
|
|
890
890
|
function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
891
891
|
const app = express();
|
|
892
|
+
const studioLifecycleCallable = !allowedTools || allowedTools.has("manage_instance");
|
|
892
893
|
let mcpServerActive = false;
|
|
893
894
|
let lastMCPActivity = 0;
|
|
894
895
|
let mcpServerStartTime = 0;
|
|
@@ -968,8 +969,15 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
968
969
|
res.json({
|
|
969
970
|
status: "ok",
|
|
970
971
|
service: "robloxstudio-mcp",
|
|
972
|
+
serverName: serverConfig?.name ?? "robloxstudio-mcp",
|
|
971
973
|
version: serverConfig?.version,
|
|
972
974
|
serverVersion: serverConfig?.version,
|
|
975
|
+
capabilities: studioLifecycleCallable ? {
|
|
976
|
+
studioLifecycle: {
|
|
977
|
+
protocolVersion: 1,
|
|
978
|
+
endpoint: "/mcp/manage_instance"
|
|
979
|
+
}
|
|
980
|
+
} : {},
|
|
973
981
|
pluginConnected: instances.length > 0,
|
|
974
982
|
instanceCount: instances.length,
|
|
975
983
|
instances: publicInstances,
|
|
@@ -3426,6 +3434,65 @@ function toStudioLaunchArg(arg) {
|
|
|
3426
3434
|
function powershellStringLiteral(value) {
|
|
3427
3435
|
return `'${value.replace(/'/g, "''")}'`;
|
|
3428
3436
|
}
|
|
3437
|
+
function parseStudioProcessEnvironmentPatch(value) {
|
|
3438
|
+
if (value === void 0)
|
|
3439
|
+
return void 0;
|
|
3440
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
3441
|
+
throw new Error("process_environment must be an object when provided.");
|
|
3442
|
+
}
|
|
3443
|
+
const raw = value;
|
|
3444
|
+
const unsupported = Object.keys(raw).filter((key) => key !== "set" && key !== "remove");
|
|
3445
|
+
if (unsupported.length > 0) {
|
|
3446
|
+
throw new Error(`process_environment contains unsupported field(s): ${unsupported.join(", ")}.`);
|
|
3447
|
+
}
|
|
3448
|
+
let set;
|
|
3449
|
+
if (raw.set !== void 0) {
|
|
3450
|
+
if (raw.set === null || typeof raw.set !== "object" || Array.isArray(raw.set)) {
|
|
3451
|
+
throw new Error("process_environment.set must be an object mapping names to string values.");
|
|
3452
|
+
}
|
|
3453
|
+
set = {};
|
|
3454
|
+
for (const [name, setting] of Object.entries(raw.set)) {
|
|
3455
|
+
if (!ENVIRONMENT_VARIABLE_NAME.test(name)) {
|
|
3456
|
+
throw new Error(`Invalid process environment variable name "${name}".`);
|
|
3457
|
+
}
|
|
3458
|
+
if (typeof setting !== "string") {
|
|
3459
|
+
throw new Error(`process_environment.set.${name} must be a string.`);
|
|
3460
|
+
}
|
|
3461
|
+
if (setting.includes("\0")) {
|
|
3462
|
+
throw new Error(`process_environment.set.${name} must not contain a null character.`);
|
|
3463
|
+
}
|
|
3464
|
+
set[name] = setting;
|
|
3465
|
+
}
|
|
3466
|
+
}
|
|
3467
|
+
let remove;
|
|
3468
|
+
if (raw.remove !== void 0) {
|
|
3469
|
+
if (!Array.isArray(raw.remove) || raw.remove.some((name) => typeof name !== "string")) {
|
|
3470
|
+
throw new Error("process_environment.remove must be an array of environment variable names.");
|
|
3471
|
+
}
|
|
3472
|
+
remove = [...new Set(raw.remove)];
|
|
3473
|
+
for (const name of remove) {
|
|
3474
|
+
if (!ENVIRONMENT_VARIABLE_NAME.test(name)) {
|
|
3475
|
+
throw new Error(`Invalid process environment variable name "${name}".`);
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
return { set, remove };
|
|
3480
|
+
}
|
|
3481
|
+
function patchedProcessEnvironment(patch) {
|
|
3482
|
+
const environment = { ...process.env };
|
|
3483
|
+
for (const name of patch.remove ?? [])
|
|
3484
|
+
delete environment[name];
|
|
3485
|
+
for (const [name, value] of Object.entries(patch.set ?? {}))
|
|
3486
|
+
environment[name] = value;
|
|
3487
|
+
return environment;
|
|
3488
|
+
}
|
|
3489
|
+
function powershellEnvironmentPatchStatements(patch) {
|
|
3490
|
+
const target = "[EnvironmentVariableTarget]::Process";
|
|
3491
|
+
return [
|
|
3492
|
+
...(patch.remove ?? []).map((name) => `[Environment]::SetEnvironmentVariable(${powershellStringLiteral(name)}, $null, ${target})`),
|
|
3493
|
+
...Object.entries(patch.set ?? {}).map(([name, value]) => `[Environment]::SetEnvironmentVariable(${powershellStringLiteral(name)}, ${powershellStringLiteral(value)}, ${target})`)
|
|
3494
|
+
];
|
|
3495
|
+
}
|
|
3429
3496
|
function quoteWindowsCommandLineArg(value) {
|
|
3430
3497
|
if (value.length > 0 && !/[\s"]/u.test(value))
|
|
3431
3498
|
return value;
|
|
@@ -3446,10 +3513,12 @@ function quoteWindowsCommandLineArg(value) {
|
|
|
3446
3513
|
}
|
|
3447
3514
|
return `${quoted}${"\\".repeat(backslashes * 2)}"`;
|
|
3448
3515
|
}
|
|
3449
|
-
function buildWindowsStudioStartScript(exe, args) {
|
|
3516
|
+
function buildWindowsStudioStartScript(exe, args, processEnvironment) {
|
|
3517
|
+
const environmentPatch = parseStudioProcessEnvironmentPatch(processEnvironment);
|
|
3450
3518
|
const windowsExe = toStudioLaunchArg(exe);
|
|
3451
3519
|
const commandLine = args.map(quoteWindowsCommandLineArg).join(" ");
|
|
3452
3520
|
return [
|
|
3521
|
+
...environmentPatch ? powershellEnvironmentPatchStatements(environmentPatch) : [],
|
|
3453
3522
|
"$psi = New-Object System.Diagnostics.ProcessStartInfo",
|
|
3454
3523
|
`$psi.FileName = ${powershellStringLiteral(windowsExe)}`,
|
|
3455
3524
|
`$psi.Arguments = ${powershellStringLiteral(commandLine)}`,
|
|
@@ -3463,8 +3532,8 @@ function buildWindowsStudioStartScript(exe, args) {
|
|
|
3463
3532
|
'if ($null -eq $studio) { throw "Roblox Studio process did not start." }'
|
|
3464
3533
|
].join("; ");
|
|
3465
3534
|
}
|
|
3466
|
-
function spawnWindowsStudioFromWsl(exe, args) {
|
|
3467
|
-
const script = buildWindowsStudioStartScript(exe, args);
|
|
3535
|
+
function spawnWindowsStudioFromWsl(exe, args, processEnvironment) {
|
|
3536
|
+
const script = buildWindowsStudioStartScript(exe, args, processEnvironment);
|
|
3468
3537
|
const output = powershell(`${script}; [PSCustomObject]@{ pid = $studio.Id; started = $studio.StartTime.ToUniversalTime().ToFileTimeUtc().ToString() } | ConvertTo-Json -Compress`);
|
|
3469
3538
|
const parsed = JSON.parse(output);
|
|
3470
3539
|
const nativePid = Number(parsed.pid);
|
|
@@ -3674,7 +3743,7 @@ function delay(ms) {
|
|
|
3674
3743
|
function basenameAny(filePath) {
|
|
3675
3744
|
return path2.basename(filePath.replace(/\\/g, "/"));
|
|
3676
3745
|
}
|
|
3677
|
-
var BASEPLATE_TEMP_DIR, BASEPLATE_TEMP_NAME, BASEPLATE_TEMPLATE_NAME, STALE_BASEPLATE_MAX_AGE_MS, BASEPLATE_TEMP_SWEEP_NAME, StudioInstanceManager;
|
|
3746
|
+
var BASEPLATE_TEMP_DIR, BASEPLATE_TEMP_NAME, BASEPLATE_TEMPLATE_NAME, ENVIRONMENT_VARIABLE_NAME, STALE_BASEPLATE_MAX_AGE_MS, BASEPLATE_TEMP_SWEEP_NAME, StudioInstanceManager;
|
|
3678
3747
|
var init_studio_instance_manager = __esm({
|
|
3679
3748
|
"../core/dist/studio-instance-manager.js"() {
|
|
3680
3749
|
"use strict";
|
|
@@ -3682,6 +3751,7 @@ var init_studio_instance_manager = __esm({
|
|
|
3682
3751
|
BASEPLATE_TEMP_DIR = path2.join(os2.tmpdir(), "robloxstudio-mcp-baseplates");
|
|
3683
3752
|
BASEPLATE_TEMP_NAME = /^Baseplate-\d+-\d+\.rbxl$/;
|
|
3684
3753
|
BASEPLATE_TEMPLATE_NAME = "Baseplate.rbxl";
|
|
3754
|
+
ENVIRONMENT_VARIABLE_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/u;
|
|
3685
3755
|
STALE_BASEPLATE_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
|
|
3686
3756
|
BASEPLATE_TEMP_SWEEP_NAME = /^Baseplate-(\d+)-\d+\.rbxl(\.lock)?$/;
|
|
3687
3757
|
StudioInstanceManager = class {
|
|
@@ -3782,22 +3852,27 @@ var init_studio_instance_manager = __esm({
|
|
|
3782
3852
|
}
|
|
3783
3853
|
async launch(options) {
|
|
3784
3854
|
this.sweepRegistry();
|
|
3855
|
+
const processEnvironment = parseStudioProcessEnvironmentPatch(options.processEnvironment);
|
|
3856
|
+
if (options.studioExecutable !== void 0 && (typeof options.studioExecutable !== "string" || options.studioExecutable.length === 0 || options.studioExecutable.includes("\0"))) {
|
|
3857
|
+
throw new Error("studio_executable must be a non-empty string without null characters.");
|
|
3858
|
+
}
|
|
3785
3859
|
const preparedOptions = prepareStudioLaunchOptions(options);
|
|
3786
3860
|
const bootId = this.getCurrentBootId();
|
|
3787
3861
|
const before = new Set(this.listStudioProcesses().map((proc2) => proc2.Id));
|
|
3788
|
-
const exe = this.processAdapter.resolveStudioExe?.() ?? resolveStudioExe();
|
|
3862
|
+
const exe = preparedOptions.studioExecutable ?? this.processAdapter.resolveStudioExe?.() ?? resolveStudioExe();
|
|
3789
3863
|
const args = buildStudioLaunchArgs(preparedOptions).map(toStudioLaunchArg);
|
|
3790
3864
|
const spawnOptions = {
|
|
3791
3865
|
cwd: isWsl() && existsSync2("/mnt/c/Windows") ? "/mnt/c/Windows" : process.cwd(),
|
|
3792
3866
|
detached: true,
|
|
3793
|
-
stdio: "ignore"
|
|
3867
|
+
stdio: "ignore",
|
|
3868
|
+
...processEnvironment ? { env: patchedProcessEnvironment(processEnvironment) } : {}
|
|
3794
3869
|
};
|
|
3795
3870
|
let proc;
|
|
3796
3871
|
try {
|
|
3797
3872
|
if (this.processAdapter.spawnStudio) {
|
|
3798
3873
|
proc = this.processAdapter.spawnStudio(exe, args, spawnOptions);
|
|
3799
3874
|
} else if (isWsl()) {
|
|
3800
|
-
proc = spawnWindowsStudioFromWsl(exe, args);
|
|
3875
|
+
proc = spawnWindowsStudioFromWsl(exe, args, processEnvironment);
|
|
3801
3876
|
} else {
|
|
3802
3877
|
const child = spawn(exe, args, spawnOptions);
|
|
3803
3878
|
proc = {
|
|
@@ -8730,6 +8805,7 @@ ${code}`
|
|
|
8730
8805
|
}
|
|
8731
8806
|
return this._textResult({
|
|
8732
8807
|
...this._managedStatus(record2),
|
|
8808
|
+
close_status: closeResult2.status,
|
|
8733
8809
|
message: closeResult2.status === "already_closed" ? "Studio instance was already closed." : "Studio instance closed."
|
|
8734
8810
|
});
|
|
8735
8811
|
}
|
|
@@ -8743,6 +8819,7 @@ ${code}`
|
|
|
8743
8819
|
const closedRecord = recordBeforeClose ?? (managedClose.launchId ? this.instanceManager.getByLaunchId(managedClose.launchId) : void 0);
|
|
8744
8820
|
return this._textResult({
|
|
8745
8821
|
...closedRecord ? this._managedStatus(closedRecord) : { instance_id },
|
|
8822
|
+
close_status: managedClose.status,
|
|
8746
8823
|
message: managedClose.status === "already_closed" ? "Studio instance was already closed." : "Studio instance closed."
|
|
8747
8824
|
});
|
|
8748
8825
|
}
|
|
@@ -8766,6 +8843,7 @@ ${code}`
|
|
|
8766
8843
|
await this.bridge.unregisterInstanceIdEverywhere(instance_id);
|
|
8767
8844
|
return this._textResult({
|
|
8768
8845
|
instance_id,
|
|
8846
|
+
close_status: "closed",
|
|
8769
8847
|
message: "Studio instance closed."
|
|
8770
8848
|
});
|
|
8771
8849
|
} else {
|
|
@@ -8790,6 +8868,7 @@ ${code}`
|
|
|
8790
8868
|
}
|
|
8791
8869
|
return this._textResult({
|
|
8792
8870
|
...this._managedStatus(record2),
|
|
8871
|
+
close_status: closeResult.status,
|
|
8793
8872
|
message: closeResult.status === "already_closed" ? "Studio instance was already closed." : "Studio instance closed."
|
|
8794
8873
|
});
|
|
8795
8874
|
}
|
|
@@ -8801,6 +8880,14 @@ ${code}`
|
|
|
8801
8880
|
const placeId = launchSource === "published_place" || launchSource === "place_revision" ? this._positiveInteger(request.place_id, "place_id") : void 0;
|
|
8802
8881
|
const placeVersion = launchSource === "place_revision" ? this._positiveInteger(request.place_version, "place_version") : void 0;
|
|
8803
8882
|
const localPlaceFile = typeof request.local_place_file === "string" ? request.local_place_file : void 0;
|
|
8883
|
+
let studioExecutable;
|
|
8884
|
+
if (request.studio_executable !== void 0) {
|
|
8885
|
+
if (typeof request.studio_executable !== "string" || request.studio_executable.length === 0) {
|
|
8886
|
+
throw new Error("studio_executable must be a non-empty string when provided.");
|
|
8887
|
+
}
|
|
8888
|
+
studioExecutable = request.studio_executable;
|
|
8889
|
+
}
|
|
8890
|
+
const processEnvironment = parseStudioProcessEnvironmentPatch(request.process_environment);
|
|
8804
8891
|
if (launchSource === "published_place" && placeId !== void 0 && this._isLatestPublishedPlaceOpen(placeId)) {
|
|
8805
8892
|
return this._textResult({
|
|
8806
8893
|
error: "Place is already open.",
|
|
@@ -8817,7 +8904,9 @@ ${code}`
|
|
|
8817
8904
|
placeId,
|
|
8818
8905
|
universeId,
|
|
8819
8906
|
placeVersion,
|
|
8820
|
-
connectionTimeoutMs: timeoutMs
|
|
8907
|
+
connectionTimeoutMs: timeoutMs,
|
|
8908
|
+
studioExecutable,
|
|
8909
|
+
processEnvironment
|
|
8821
8910
|
});
|
|
8822
8911
|
if (!waitForConnection) {
|
|
8823
8912
|
return this._textResult({
|
|
@@ -11874,6 +11963,35 @@ var init_definitions = __esm({
|
|
|
11874
11963
|
type: "number",
|
|
11875
11964
|
description: 'For action="launch": max milliseconds for plugin connection (default 120000). The deadline also applies asynchronously when wait_for_connection=false.'
|
|
11876
11965
|
},
|
|
11966
|
+
studio_executable: {
|
|
11967
|
+
type: "string",
|
|
11968
|
+
description: 'For action="launch": exact Roblox Studio executable to launch instead of auto-discovering a version.'
|
|
11969
|
+
},
|
|
11970
|
+
process_environment: {
|
|
11971
|
+
type: "object",
|
|
11972
|
+
description: 'For action="launch": process-scoped environment patch applied only while creating Studio. Values are never retained in the managed-instance registry.',
|
|
11973
|
+
properties: {
|
|
11974
|
+
set: {
|
|
11975
|
+
type: "object",
|
|
11976
|
+
description: "Environment variables to set for the Studio process.",
|
|
11977
|
+
propertyNames: {
|
|
11978
|
+
pattern: "^[A-Za-z_][A-Za-z0-9_]*$"
|
|
11979
|
+
},
|
|
11980
|
+
additionalProperties: {
|
|
11981
|
+
type: "string"
|
|
11982
|
+
}
|
|
11983
|
+
},
|
|
11984
|
+
remove: {
|
|
11985
|
+
type: "array",
|
|
11986
|
+
description: "Environment variables to remove from the Studio process environment.",
|
|
11987
|
+
items: {
|
|
11988
|
+
type: "string",
|
|
11989
|
+
pattern: "^[A-Za-z_][A-Za-z0-9_]*$"
|
|
11990
|
+
}
|
|
11991
|
+
}
|
|
11992
|
+
},
|
|
11993
|
+
additionalProperties: false
|
|
11994
|
+
},
|
|
11877
11995
|
max_page_size: {
|
|
11878
11996
|
type: "number",
|
|
11879
11997
|
description: 'For action="list_place_versions": number of versions to return, clamped to 1-50 (default 10).'
|
|
@@ -12243,7 +12361,7 @@ var init_definitions = __esm({
|
|
|
12243
12361
|
{
|
|
12244
12362
|
name: "get_runtime_logs",
|
|
12245
12363
|
category: "read",
|
|
12246
|
-
description: "Read the in-memory log buffers captured by Studio plugin peers. Each buffer captures ~64 KB of recent LogService output; runtime peers seed from LogService:GetLogHistory() at plugin load so early startup logs emitted before the plugin finishes loading can still be returned, then continue capturing LogService.MessageOut entries. Oldest entries drop when over budget. Entries include capturedBy for the plugin buffer that observed the log. In ordinary Studio play/run sessions, LogService reflects logs across edit/server/client, so script-origin peer is not reliable and entries omit peer. In StudioTestService multiplayer sessions only, peer attribution is reliable and entries also include peer. target=all (default) merges buffers and dedups same-message-and-level entries captured within 2s across different buffers.",
|
|
12364
|
+
description: "Read the in-memory log buffers captured by Studio plugin peers. Each buffer captures ~64 KB of recent LogService output; runtime peers seed from LogService:GetLogHistory() at plugin load so early startup logs emitted before the plugin finishes loading can still be returned, then continue capturing LogService.MessageOut entries. Live structured LogService entries include their context dictionary as optional data; Roblox GetLogHistory does not expose context for entries seeded at plugin load. Oldest entries drop when over budget. Entries include capturedBy for the plugin buffer that observed the log. In ordinary Studio play/run sessions, LogService reflects logs across edit/server/client, so script-origin peer is not reliable and entries omit peer. In StudioTestService multiplayer sessions only, peer attribution is reliable and entries also include peer. target=all (default) merges buffers and dedups same-message-and-level entries captured within 2s across different buffers.",
|
|
12247
12365
|
inputSchema: {
|
|
12248
12366
|
type: "object",
|
|
12249
12367
|
properties: {
|
package/package.json
CHANGED
|
@@ -1439,9 +1439,9 @@ local function computeBridgeStamp()
|
|
|
1439
1439
|
for i = 1, #combined do
|
|
1440
1440
|
h = (h * 33 + (string.byte(combined, i))) % 2147483647
|
|
1441
1441
|
end
|
|
1442
|
-
-- "2.22.
|
|
1442
|
+
-- "2.22.3" is replaced with the package version at package time
|
|
1443
1443
|
-- (scripts/build-plugin.mjs injectVersion), so a release bump also restamps.
|
|
1444
|
-
return `{tostring(h)}-2.22.
|
|
1444
|
+
return `{tostring(h)}-2.22.3`
|
|
1445
1445
|
end
|
|
1446
1446
|
local BRIDGE_STAMP = computeBridgeStamp()
|
|
1447
1447
|
local function setSource(scriptInst, source)
|
|
@@ -10493,7 +10493,7 @@ local function dropOldestUntilFits(incomingBytes)
|
|
|
10493
10493
|
totalDropped += 1
|
|
10494
10494
|
end
|
|
10495
10495
|
end
|
|
10496
|
-
local function pushEntry(msg, t, ts)
|
|
10496
|
+
local function pushEntry(msg, t, ts, data)
|
|
10497
10497
|
if ts == nil then
|
|
10498
10498
|
ts = nowSec()
|
|
10499
10499
|
end
|
|
@@ -10505,6 +10505,7 @@ local function pushEntry(msg, t, ts)
|
|
|
10505
10505
|
ts = ts,
|
|
10506
10506
|
level = levelTag(t),
|
|
10507
10507
|
message = safeMessage,
|
|
10508
|
+
data = data,
|
|
10508
10509
|
}
|
|
10509
10510
|
table.insert(entries, _arg0)
|
|
10510
10511
|
nextSeq += 1
|
|
@@ -10549,8 +10550,8 @@ local function install()
|
|
|
10549
10550
|
-- Seed from per-DataModel LogHistory so get_runtime_logs can still see them;
|
|
10550
10551
|
-- edit history is bounded to the current Studio process above.
|
|
10551
10552
|
seedRuntimeHistory()
|
|
10552
|
-
LogService.MessageOut:Connect(function(msg, t)
|
|
10553
|
-
pushEntry(msg, t)
|
|
10553
|
+
LogService.MessageOut:Connect(function(msg, t, context)
|
|
10554
|
+
pushEntry(msg, t, nil, context)
|
|
10554
10555
|
end)
|
|
10555
10556
|
end
|
|
10556
10557
|
local function detectPeer()
|
|
@@ -10813,7 +10814,7 @@ return {
|
|
|
10813
10814
|
<Properties>
|
|
10814
10815
|
<string name="Name">State</string>
|
|
10815
10816
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
10816
|
-
local CURRENT_VERSION = "2.22.
|
|
10817
|
+
local CURRENT_VERSION = "2.22.3"
|
|
10817
10818
|
local PLUGIN_VARIANT = "inspector"
|
|
10818
10819
|
local BASE_PORT = 58741
|
|
10819
10820
|
local function createConnection(port)
|
|
@@ -1439,9 +1439,9 @@ local function computeBridgeStamp()
|
|
|
1439
1439
|
for i = 1, #combined do
|
|
1440
1440
|
h = (h * 33 + (string.byte(combined, i))) % 2147483647
|
|
1441
1441
|
end
|
|
1442
|
-
-- "2.22.
|
|
1442
|
+
-- "2.22.3" is replaced with the package version at package time
|
|
1443
1443
|
-- (scripts/build-plugin.mjs injectVersion), so a release bump also restamps.
|
|
1444
|
-
return `{tostring(h)}-2.22.
|
|
1444
|
+
return `{tostring(h)}-2.22.3`
|
|
1445
1445
|
end
|
|
1446
1446
|
local BRIDGE_STAMP = computeBridgeStamp()
|
|
1447
1447
|
local function setSource(scriptInst, source)
|
|
@@ -10493,7 +10493,7 @@ local function dropOldestUntilFits(incomingBytes)
|
|
|
10493
10493
|
totalDropped += 1
|
|
10494
10494
|
end
|
|
10495
10495
|
end
|
|
10496
|
-
local function pushEntry(msg, t, ts)
|
|
10496
|
+
local function pushEntry(msg, t, ts, data)
|
|
10497
10497
|
if ts == nil then
|
|
10498
10498
|
ts = nowSec()
|
|
10499
10499
|
end
|
|
@@ -10505,6 +10505,7 @@ local function pushEntry(msg, t, ts)
|
|
|
10505
10505
|
ts = ts,
|
|
10506
10506
|
level = levelTag(t),
|
|
10507
10507
|
message = safeMessage,
|
|
10508
|
+
data = data,
|
|
10508
10509
|
}
|
|
10509
10510
|
table.insert(entries, _arg0)
|
|
10510
10511
|
nextSeq += 1
|
|
@@ -10549,8 +10550,8 @@ local function install()
|
|
|
10549
10550
|
-- Seed from per-DataModel LogHistory so get_runtime_logs can still see them;
|
|
10550
10551
|
-- edit history is bounded to the current Studio process above.
|
|
10551
10552
|
seedRuntimeHistory()
|
|
10552
|
-
LogService.MessageOut:Connect(function(msg, t)
|
|
10553
|
-
pushEntry(msg, t)
|
|
10553
|
+
LogService.MessageOut:Connect(function(msg, t, context)
|
|
10554
|
+
pushEntry(msg, t, nil, context)
|
|
10554
10555
|
end)
|
|
10555
10556
|
end
|
|
10556
10557
|
local function detectPeer()
|
|
@@ -10813,7 +10814,7 @@ return {
|
|
|
10813
10814
|
<Properties>
|
|
10814
10815
|
<string name="Name">State</string>
|
|
10815
10816
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
10816
|
-
local CURRENT_VERSION = "2.22.
|
|
10817
|
+
local CURRENT_VERSION = "2.22.3"
|
|
10817
10818
|
local PLUGIN_VARIANT = "main"
|
|
10818
10819
|
local BASE_PORT = 58741
|
|
10819
10820
|
local function createConnection(port)
|
|
@@ -24,6 +24,7 @@ interface RuntimeLogEntry {
|
|
|
24
24
|
ts: number; // wall-clock seconds via DateTime, coherent across peers
|
|
25
25
|
level: LogLevel;
|
|
26
26
|
message: string;
|
|
27
|
+
data?: Record<string, unknown>;
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
const MAX_BYTES = 64 * 1024;
|
|
@@ -88,7 +89,12 @@ function dropOldestUntilFits(incomingBytes: number): void {
|
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
91
|
|
|
91
|
-
function pushEntry(
|
|
92
|
+
function pushEntry(
|
|
93
|
+
msg: string,
|
|
94
|
+
t: Enum.MessageType,
|
|
95
|
+
ts = nowSec(),
|
|
96
|
+
data?: Record<string, unknown>,
|
|
97
|
+
): void {
|
|
92
98
|
const safeMessage = escapeInvalidUtf8(msg);
|
|
93
99
|
const bytes = safeMessage.size();
|
|
94
100
|
dropOldestUntilFits(bytes);
|
|
@@ -97,6 +103,7 @@ function pushEntry(msg: string, t: Enum.MessageType, ts = nowSec()): void {
|
|
|
97
103
|
ts,
|
|
98
104
|
level: levelTag(t),
|
|
99
105
|
message: safeMessage,
|
|
106
|
+
data,
|
|
100
107
|
});
|
|
101
108
|
nextSeq += 1;
|
|
102
109
|
totalBytes += bytes;
|
|
@@ -135,8 +142,8 @@ function install(): void {
|
|
|
135
142
|
// Seed from per-DataModel LogHistory so get_runtime_logs can still see them;
|
|
136
143
|
// edit history is bounded to the current Studio process above.
|
|
137
144
|
seedRuntimeHistory();
|
|
138
|
-
LogService.MessageOut.Connect((msg, t) => {
|
|
139
|
-
pushEntry(msg, t);
|
|
145
|
+
LogService.MessageOut.Connect((msg, t, context?: Record<string, unknown>) => {
|
|
146
|
+
pushEntry(msg, t, undefined, context);
|
|
140
147
|
});
|
|
141
148
|
}
|
|
142
149
|
|