@princeofscale/bloxforge-inspector 2.20.2 → 3.0.0-rc.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/dist/index.js +63 -26
- package/package.json +5 -2
- package/studio-plugin/MCPInspectorPlugin.rbxmx +83 -20
- package/studio-plugin/MCPPlugin.rbxmx +83 -20
- package/studio-plugin/package-lock.json +9 -9
- package/studio-plugin/src/modules/Communication.ts +26 -6
- package/studio-plugin/src/modules/LuauExec.ts +6 -3
package/dist/index.js
CHANGED
|
@@ -1222,6 +1222,7 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, registry) {
|
|
|
1222
1222
|
let mcpServerStartTime = 0;
|
|
1223
1223
|
const proxyInstances = /* @__PURE__ */ new Set();
|
|
1224
1224
|
const warnedVersionMismatches = /* @__PURE__ */ new Set();
|
|
1225
|
+
const warnedProtocolMismatches = /* @__PURE__ */ new Set();
|
|
1225
1226
|
const setMCPServerActive = (active) => {
|
|
1226
1227
|
mcpServerActive = active;
|
|
1227
1228
|
if (active) {
|
|
@@ -1346,6 +1347,10 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, registry) {
|
|
|
1346
1347
|
warnedVersionMismatches.add(pluginSessionId);
|
|
1347
1348
|
console.error(`[version-mismatch] Studio plugin v${registered.pluginVersion} (${registered.pluginVariant}) does not match MCP server v${registered.serverVersion} for ${registered.instanceId}/${registered.role}`);
|
|
1348
1349
|
}
|
|
1350
|
+
if (registered?.protocolMismatch && !warnedProtocolMismatches.has(pluginSessionId)) {
|
|
1351
|
+
warnedProtocolMismatches.add(pluginSessionId);
|
|
1352
|
+
console.error(`[protocol-mismatch] Studio plugin protocol v${registered.pluginProtocolVersion} does not match MCP server protocol v${registered.serverProtocolVersion} for ${registered.instanceId}/${registered.role}. Run --auto-install-plugin to update the plugin.`);
|
|
1353
|
+
}
|
|
1349
1354
|
res.json({
|
|
1350
1355
|
success: true,
|
|
1351
1356
|
assignedRole: result.assignedRole,
|
|
@@ -1353,13 +1358,16 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, registry) {
|
|
|
1353
1358
|
serverVersion: serverConfig?.version,
|
|
1354
1359
|
serverProtocolVersion: MCP_PROTOCOL_VERSION,
|
|
1355
1360
|
versionMismatch: registered?.versionMismatch ?? false,
|
|
1356
|
-
protocolMismatch: registered?.protocolMismatch ?? false
|
|
1361
|
+
protocolMismatch: registered?.protocolMismatch ?? false,
|
|
1362
|
+
capabilities: ["core", "scene", "mutation", "scripts", "runtime", "assets", "ui", "environment", "terrain", "build", "media", "sync", "safety"]
|
|
1357
1363
|
});
|
|
1358
1364
|
});
|
|
1359
1365
|
app.post("/disconnect", (req, res) => {
|
|
1360
1366
|
const { pluginSessionId } = req.body;
|
|
1361
1367
|
if (pluginSessionId) {
|
|
1362
1368
|
bridge.unregisterInstance(pluginSessionId, "plugin_request");
|
|
1369
|
+
warnedVersionMismatches.delete(pluginSessionId);
|
|
1370
|
+
warnedProtocolMismatches.delete(pluginSessionId);
|
|
1363
1371
|
}
|
|
1364
1372
|
res.json({ success: true });
|
|
1365
1373
|
});
|
|
@@ -3828,7 +3836,7 @@ var init_scripting = __esm({
|
|
|
3828
3836
|
},
|
|
3829
3837
|
usePattern: {
|
|
3830
3838
|
type: "boolean",
|
|
3831
|
-
description: "Use Lua pattern matching instead of literal (default: false)"
|
|
3839
|
+
description: "Use Lua pattern matching instead of literal (default: false). Lua patterns do not support regex alternation; | is literal, so run separate searches for alternatives."
|
|
3832
3840
|
},
|
|
3833
3841
|
contextLines: {
|
|
3834
3842
|
type: "number",
|
|
@@ -10627,10 +10635,12 @@ var init_asset_tools = __esm({
|
|
|
10627
10635
|
static findLibraryPath() {
|
|
10628
10636
|
if (_AssetTools._cachedLibraryPath)
|
|
10629
10637
|
return _AssetTools._cachedLibraryPath;
|
|
10630
|
-
const overridePath = process.env.ROBLOXSTUDIO_MCP_BUILD_LIBRARY || process.env.BUILD_LIBRARY_PATH;
|
|
10638
|
+
const overridePath = process.env.BLOXFORGE_BUILD_LIBRARY || process.env.ROBLOXSTUDIO_MCP_BUILD_LIBRARY || process.env.BUILD_LIBRARY_PATH;
|
|
10631
10639
|
const cwd = path.resolve(process.cwd());
|
|
10632
10640
|
const projectRoot = _AssetTools.findProjectRoot(cwd);
|
|
10633
|
-
const
|
|
10641
|
+
const newHomeLibraryPath = path.join(os.homedir(), ".bloxforge", "build-library");
|
|
10642
|
+
const legacyHomeLibraryPath = path.join(os.homedir(), ".robloxstudio-mcp", "build-library");
|
|
10643
|
+
const homeLibraryPath = fs.existsSync(newHomeLibraryPath) ? newHomeLibraryPath : fs.existsSync(legacyHomeLibraryPath) ? legacyHomeLibraryPath : newHomeLibraryPath;
|
|
10634
10644
|
const projectLibraryPath = projectRoot ? path.join(projectRoot, "build-library") : null;
|
|
10635
10645
|
const cwdLibraryPath = path.join(cwd, "build-library");
|
|
10636
10646
|
let result;
|
|
@@ -15139,16 +15149,30 @@ import * as fs4 from "fs";
|
|
|
15139
15149
|
import * as os2 from "os";
|
|
15140
15150
|
import * as path4 from "path";
|
|
15141
15151
|
function defaultManagedInstanceRegistryDir() {
|
|
15152
|
+
if (process.env.BLOXFORGE_MANAGED_INSTANCE_REGISTRY_DIR) {
|
|
15153
|
+
return process.env.BLOXFORGE_MANAGED_INSTANCE_REGISTRY_DIR;
|
|
15154
|
+
}
|
|
15142
15155
|
if (process.env.ROBLOXSTUDIO_MCP_MANAGED_INSTANCE_REGISTRY_DIR) {
|
|
15143
15156
|
return process.env.ROBLOXSTUDIO_MCP_MANAGED_INSTANCE_REGISTRY_DIR;
|
|
15144
15157
|
}
|
|
15158
|
+
const suffix = path4.join("managed-instances", "v1");
|
|
15159
|
+
let newDir;
|
|
15160
|
+
let legacyDir;
|
|
15145
15161
|
if (process.platform === "win32" && process.env.LOCALAPPDATA) {
|
|
15146
|
-
|
|
15147
|
-
|
|
15148
|
-
if (process.platform === "darwin") {
|
|
15149
|
-
|
|
15162
|
+
newDir = path4.join(process.env.LOCALAPPDATA, "bloxforge", suffix);
|
|
15163
|
+
legacyDir = path4.join(process.env.LOCALAPPDATA, "robloxstudio-mcp", suffix);
|
|
15164
|
+
} else if (process.platform === "darwin") {
|
|
15165
|
+
newDir = path4.join(os2.homedir(), "Library", "Application Support", "bloxforge", suffix);
|
|
15166
|
+
legacyDir = path4.join(os2.homedir(), "Library", "Application Support", "robloxstudio-mcp", suffix);
|
|
15167
|
+
} else {
|
|
15168
|
+
newDir = path4.join(os2.homedir(), ".local", "state", "bloxforge", suffix);
|
|
15169
|
+
legacyDir = path4.join(os2.homedir(), ".local", "state", "robloxstudio-mcp", suffix);
|
|
15150
15170
|
}
|
|
15151
|
-
|
|
15171
|
+
if (fs4.existsSync(newDir))
|
|
15172
|
+
return newDir;
|
|
15173
|
+
if (fs4.existsSync(legacyDir))
|
|
15174
|
+
return legacyDir;
|
|
15175
|
+
return newDir;
|
|
15152
15176
|
}
|
|
15153
15177
|
function sleepSync(ms) {
|
|
15154
15178
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
@@ -15424,7 +15448,7 @@ var init_managed_instance_registry = __esm({
|
|
|
15424
15448
|
|
|
15425
15449
|
// ../core/dist/studio-instance-manager.js
|
|
15426
15450
|
import { execFileSync, spawn } from "child_process";
|
|
15427
|
-
import { copyFileSync, existsSync as
|
|
15451
|
+
import { copyFileSync, existsSync as existsSync3, mkdirSync as mkdirSync5, readdirSync as readdirSync4, readFileSync as readFileSync4, realpathSync, rmSync as rmSync2, statSync as statSync3 } from "fs";
|
|
15428
15452
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
15429
15453
|
import * as os3 from "os";
|
|
15430
15454
|
import * as path5 from "path";
|
|
@@ -15446,7 +15470,7 @@ function isWsl() {
|
|
|
15446
15470
|
}
|
|
15447
15471
|
function powershell(script) {
|
|
15448
15472
|
return run("powershell.exe", ["-NoProfile", "-Command", script], {
|
|
15449
|
-
cwd: isWsl() &&
|
|
15473
|
+
cwd: isWsl() && existsSync3("/mnt/c/Windows") ? "/mnt/c/Windows" : process.cwd()
|
|
15450
15474
|
});
|
|
15451
15475
|
}
|
|
15452
15476
|
function windowsLocalAppData() {
|
|
@@ -15456,7 +15480,7 @@ function windowsLocalAppData() {
|
|
|
15456
15480
|
return void 0;
|
|
15457
15481
|
try {
|
|
15458
15482
|
return run("cmd.exe", ["/c", "echo %LOCALAPPDATA%"], {
|
|
15459
|
-
cwd:
|
|
15483
|
+
cwd: existsSync3("/mnt/c/Windows") ? "/mnt/c/Windows" : process.cwd()
|
|
15460
15484
|
});
|
|
15461
15485
|
} catch {
|
|
15462
15486
|
return void 0;
|
|
@@ -15468,7 +15492,7 @@ function toWslPath(windowsPath) {
|
|
|
15468
15492
|
return run("wslpath", ["-u", windowsPath]);
|
|
15469
15493
|
}
|
|
15470
15494
|
function toStudioLaunchArg(arg) {
|
|
15471
|
-
if (!isWsl() || !path5.isAbsolute(arg) || !
|
|
15495
|
+
if (!isWsl() || !path5.isAbsolute(arg) || !existsSync3(arg))
|
|
15472
15496
|
return arg;
|
|
15473
15497
|
return run("wslpath", ["-w", arg]);
|
|
15474
15498
|
}
|
|
@@ -15494,7 +15518,7 @@ function resolveBaseplateTemplatePath() {
|
|
|
15494
15518
|
path5.join(process.cwd(), "assets", BASEPLATE_TEMPLATE_NAME)
|
|
15495
15519
|
];
|
|
15496
15520
|
for (const candidate of candidates) {
|
|
15497
|
-
if (
|
|
15521
|
+
if (existsSync3(candidate))
|
|
15498
15522
|
return candidate;
|
|
15499
15523
|
}
|
|
15500
15524
|
throw new Error(`Baseplate template not found. Expected ${BASEPLATE_TEMPLATE_NAME} in one of: ${candidates.join(", ")}`);
|
|
@@ -15571,10 +15595,10 @@ function resolveStudioExe() {
|
|
|
15571
15595
|
}
|
|
15572
15596
|
const localAppData = windowsLocalAppData();
|
|
15573
15597
|
const root = localAppData ? path5.join(toWslPath(localAppData), "Roblox", "Versions") : path5.join(os3.homedir(), "AppData", "Local", "Roblox", "Versions");
|
|
15574
|
-
if (!
|
|
15598
|
+
if (!existsSync3(root)) {
|
|
15575
15599
|
throw new Error(`Roblox Studio Versions folder not found: ${root}. Set ROBLOX_STUDIO_EXE.`);
|
|
15576
15600
|
}
|
|
15577
|
-
const candidates = readdirSync4(root).filter((name) => name.startsWith("version-")).map((name) => path5.join(root, name, "RobloxStudioBeta.exe")).filter((candidate) =>
|
|
15601
|
+
const candidates = readdirSync4(root).filter((name) => name.startsWith("version-")).map((name) => path5.join(root, name, "RobloxStudioBeta.exe")).filter((candidate) => existsSync3(candidate)).sort((a, b) => statSync3(b).mtimeMs - statSync3(a).mtimeMs);
|
|
15578
15602
|
if (candidates.length === 0) {
|
|
15579
15603
|
throw new Error(`RobloxStudioBeta.exe not found under ${root}. Set ROBLOX_STUDIO_EXE.`);
|
|
15580
15604
|
}
|
|
@@ -15720,7 +15744,7 @@ var init_studio_instance_manager = __esm({
|
|
|
15720
15744
|
const exe = this.processAdapter.resolveStudioExe?.() ?? resolveStudioExe();
|
|
15721
15745
|
const args = buildStudioLaunchArgs(preparedOptions).map(toStudioLaunchArg);
|
|
15722
15746
|
const spawnOptions = {
|
|
15723
|
-
cwd: isWsl() &&
|
|
15747
|
+
cwd: isWsl() && existsSync3("/mnt/c/Windows") ? "/mnt/c/Windows" : process.cwd(),
|
|
15724
15748
|
detached: true,
|
|
15725
15749
|
stdio: "ignore"
|
|
15726
15750
|
};
|
|
@@ -16865,10 +16889,12 @@ var init_tools = __esm({
|
|
|
16865
16889
|
static findLibraryPath() {
|
|
16866
16890
|
if (_RobloxStudioTools._cachedLibraryPath)
|
|
16867
16891
|
return _RobloxStudioTools._cachedLibraryPath;
|
|
16868
|
-
const overridePath = process.env.ROBLOXSTUDIO_MCP_BUILD_LIBRARY || process.env.BUILD_LIBRARY_PATH;
|
|
16892
|
+
const overridePath = process.env.BLOXFORGE_BUILD_LIBRARY || process.env.ROBLOXSTUDIO_MCP_BUILD_LIBRARY || process.env.BUILD_LIBRARY_PATH;
|
|
16869
16893
|
const cwd = path6.resolve(process.cwd());
|
|
16870
16894
|
const projectRoot = _RobloxStudioTools.findProjectRoot(cwd);
|
|
16871
|
-
const
|
|
16895
|
+
const newHomeLibraryPath = path6.join(os4.homedir(), ".bloxforge", "build-library");
|
|
16896
|
+
const legacyHomeLibraryPath = path6.join(os4.homedir(), ".robloxstudio-mcp", "build-library");
|
|
16897
|
+
const homeLibraryPath = fs5.existsSync(newHomeLibraryPath) ? newHomeLibraryPath : fs5.existsSync(legacyHomeLibraryPath) ? legacyHomeLibraryPath : newHomeLibraryPath;
|
|
16872
16898
|
const projectLibraryPath = projectRoot ? path6.join(projectRoot, "build-library") : null;
|
|
16873
16899
|
const cwdLibraryPath = path6.join(cwd, "build-library");
|
|
16874
16900
|
let result;
|
|
@@ -18979,7 +19005,7 @@ var init_server = __esm({
|
|
|
18979
19005
|
});
|
|
18980
19006
|
|
|
18981
19007
|
// ../core/dist/install-plugin-helpers.js
|
|
18982
|
-
import { existsSync as
|
|
19008
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6, unlinkSync as unlinkSync2 } from "fs";
|
|
18983
19009
|
import { execSync } from "child_process";
|
|
18984
19010
|
import { join as join6 } from "path";
|
|
18985
19011
|
import { homedir as homedir5 } from "os";
|
|
@@ -19026,7 +19052,7 @@ function getPluginsFolder() {
|
|
|
19026
19052
|
}
|
|
19027
19053
|
function handleVariantConflict({ pluginsFolder, otherAssetName, replace, log = console.log, warn = console.warn }) {
|
|
19028
19054
|
const otherDest = join6(pluginsFolder, otherAssetName);
|
|
19029
|
-
if (!
|
|
19055
|
+
if (!existsSync5(otherDest))
|
|
19030
19056
|
return;
|
|
19031
19057
|
if (replace) {
|
|
19032
19058
|
try {
|
|
@@ -19054,6 +19080,7 @@ var init_doctor = __esm({
|
|
|
19054
19080
|
"../core/dist/doctor.js"() {
|
|
19055
19081
|
"use strict";
|
|
19056
19082
|
init_install_plugin_helpers();
|
|
19083
|
+
init_bridge_service();
|
|
19057
19084
|
}
|
|
19058
19085
|
});
|
|
19059
19086
|
|
|
@@ -19062,6 +19089,7 @@ var init_dist = __esm({
|
|
|
19062
19089
|
"../core/dist/index.js"() {
|
|
19063
19090
|
"use strict";
|
|
19064
19091
|
init_server();
|
|
19092
|
+
init_server();
|
|
19065
19093
|
init_http_server();
|
|
19066
19094
|
init_bridge_service();
|
|
19067
19095
|
init_tools();
|
|
@@ -19081,7 +19109,7 @@ __export(install_plugin_exports, {
|
|
|
19081
19109
|
installBundledPlugin: () => installBundledPlugin,
|
|
19082
19110
|
installPlugin: () => installPlugin
|
|
19083
19111
|
});
|
|
19084
|
-
import { copyFileSync as copyFileSync2, createWriteStream, existsSync as
|
|
19112
|
+
import { copyFileSync as copyFileSync2, createWriteStream, existsSync as existsSync6, mkdirSync as mkdirSync7, readFileSync as readFileSync7, unlinkSync as unlinkSync3 } from "fs";
|
|
19085
19113
|
import { dirname as dirname6, join as join7 } from "path";
|
|
19086
19114
|
import { fileURLToPath } from "url";
|
|
19087
19115
|
import { get } from "https";
|
|
@@ -19142,7 +19170,7 @@ function prepareInstall({
|
|
|
19142
19170
|
warn
|
|
19143
19171
|
}) {
|
|
19144
19172
|
const pluginsFolder = getPluginsFolder();
|
|
19145
|
-
if (!
|
|
19173
|
+
if (!existsSync6(pluginsFolder)) {
|
|
19146
19174
|
mkdirSync7(pluginsFolder, { recursive: true });
|
|
19147
19175
|
}
|
|
19148
19176
|
handleVariantConflict({
|
|
@@ -19160,7 +19188,7 @@ function bundledAssetPath() {
|
|
|
19160
19188
|
join7(currentDir, "..", "studio-plugin", ASSET_NAME),
|
|
19161
19189
|
join7(currentDir, "..", "..", "..", "studio-plugin", ASSET_NAME)
|
|
19162
19190
|
];
|
|
19163
|
-
return candidates.find((candidate) =>
|
|
19191
|
+
return candidates.find((candidate) => existsSync6(candidate)) ?? null;
|
|
19164
19192
|
}
|
|
19165
19193
|
function packageVersion() {
|
|
19166
19194
|
const currentDir = dirname6(fileURLToPath(import.meta.url));
|
|
@@ -19184,7 +19212,7 @@ function assertBundledPluginVersion(source) {
|
|
|
19184
19212
|
}
|
|
19185
19213
|
}
|
|
19186
19214
|
function filesMatch(a, b) {
|
|
19187
|
-
if (!
|
|
19215
|
+
if (!existsSync6(b)) return false;
|
|
19188
19216
|
const aBytes = readFileSync7(a);
|
|
19189
19217
|
const bBytes = readFileSync7(b);
|
|
19190
19218
|
return aBytes.length === bBytes.length && aBytes.equals(bBytes);
|
|
@@ -19248,7 +19276,16 @@ var init_install_plugin = __esm({
|
|
|
19248
19276
|
// src/index.ts
|
|
19249
19277
|
init_dist();
|
|
19250
19278
|
import { createRequire } from "module";
|
|
19251
|
-
if (process.argv.includes("--
|
|
19279
|
+
if (process.argv.includes("--help") || process.argv.includes("-h")) {
|
|
19280
|
+
console.log(`BloxForge Inspector MCP Server
|
|
19281
|
+
|
|
19282
|
+
Usage: npx @princeofscale/bloxforge-inspector [options]
|
|
19283
|
+
|
|
19284
|
+
Options:
|
|
19285
|
+
--install-plugin Install the read-only Studio plugin
|
|
19286
|
+
--auto-install-plugin Install the plugin without prompting
|
|
19287
|
+
--help, -h Show this help message`);
|
|
19288
|
+
} else if (process.argv.includes("--install-plugin")) {
|
|
19252
19289
|
const { installPlugin: installPlugin2 } = await Promise.resolve().then(() => (init_install_plugin(), install_plugin_exports));
|
|
19253
19290
|
await installPlugin2().catch((err) => {
|
|
19254
19291
|
console.error(err instanceof Error ? err.message : String(err));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@princeofscale/bloxforge-inspector",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0-rc.1",
|
|
4
4
|
"description": "Read-only MCP server for AI agents to inspect and debug Roblox Studio — safe browsing with zero write risk.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -36,7 +36,10 @@
|
|
|
36
36
|
"read-only",
|
|
37
37
|
"open-source"
|
|
38
38
|
],
|
|
39
|
-
"author":
|
|
39
|
+
"author": {
|
|
40
|
+
"name": "princeofscale",
|
|
41
|
+
"url": "https://github.com/princeofscale"
|
|
42
|
+
},
|
|
40
43
|
"license": "MIT",
|
|
41
44
|
"publishConfig": {
|
|
42
45
|
"access": "public"
|
|
@@ -708,8 +708,10 @@ local function computeInstanceId()
|
|
|
708
708
|
end
|
|
709
709
|
local assignedRole
|
|
710
710
|
local duplicateInstanceRole = false
|
|
711
|
-
local
|
|
711
|
+
local versionMismatchConnections = {}
|
|
712
712
|
local lastVersionMismatchWarningKey
|
|
713
|
+
local protocolMismatchConnections = {}
|
|
714
|
+
local lastProtocolMismatchWarningKey
|
|
713
715
|
local lastReadyInstanceId
|
|
714
716
|
local readyFailureLogKeys = {}
|
|
715
717
|
-- Cache the published place name from MarketplaceService:GetProductInfo so
|
|
@@ -1118,16 +1120,49 @@ local function pollForRequests(connIndex)
|
|
|
1118
1120
|
end
|
|
1119
1121
|
local serverVersion = _condition
|
|
1120
1122
|
if data.versionMismatch == true then
|
|
1121
|
-
|
|
1123
|
+
versionMismatchConnections[conn] = true
|
|
1122
1124
|
local warningKey = `{State.CURRENT_VERSION}:{serverVersion}`
|
|
1123
1125
|
if lastVersionMismatchWarningKey ~= warningKey then
|
|
1124
1126
|
lastVersionMismatchWarningKey = warningKey
|
|
1125
1127
|
warn(`[BloxForge] Version mismatch: Studio plugin v{State.CURRENT_VERSION} / MCP v{serverVersion}. Run npx -y @princeofscale/bloxforge@latest --auto-install-plugin and restart Studio.`)
|
|
1126
1128
|
end
|
|
1127
1129
|
UI.showBanner("version-mismatch", `Plugin v{State.CURRENT_VERSION} / MCP v{serverVersion} mismatch`)
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1130
|
+
else
|
|
1131
|
+
versionMismatchConnections[conn] = nil
|
|
1132
|
+
-- ▼ ReadonlySet.size ▼
|
|
1133
|
+
local _size = 0
|
|
1134
|
+
for _ in versionMismatchConnections do
|
|
1135
|
+
_size += 1
|
|
1136
|
+
end
|
|
1137
|
+
-- ▲ ReadonlySet.size ▲
|
|
1138
|
+
if _size == 0 then
|
|
1139
|
+
UI.hideBanner("version-mismatch")
|
|
1140
|
+
end
|
|
1141
|
+
end
|
|
1142
|
+
local _condition_1 = data.serverProtocolVersion
|
|
1143
|
+
if _condition_1 == nil then
|
|
1144
|
+
_condition_1 = 0
|
|
1145
|
+
end
|
|
1146
|
+
local serverProtocol = _condition_1
|
|
1147
|
+
if data.protocolMismatch == true then
|
|
1148
|
+
protocolMismatchConnections[conn] = true
|
|
1149
|
+
local warningKey = `{State.PROTOCOL_VERSION}:{serverProtocol}`
|
|
1150
|
+
if lastProtocolMismatchWarningKey ~= warningKey then
|
|
1151
|
+
lastProtocolMismatchWarningKey = warningKey
|
|
1152
|
+
warn(`[BloxForge] Protocol mismatch: Studio plugin protocol v{State.PROTOCOL_VERSION} / MCP protocol v{serverProtocol}. Run npx -y @princeofscale/bloxforge@latest --auto-install-plugin and restart Studio.`)
|
|
1153
|
+
end
|
|
1154
|
+
UI.showBanner("protocol-mismatch", `Protocol mismatch: Plugin protocol v{State.PROTOCOL_VERSION} / MCP protocol v{serverProtocol}`)
|
|
1155
|
+
else
|
|
1156
|
+
protocolMismatchConnections[conn] = nil
|
|
1157
|
+
-- ▼ ReadonlySet.size ▼
|
|
1158
|
+
local _size = 0
|
|
1159
|
+
for _ in protocolMismatchConnections do
|
|
1160
|
+
_size += 1
|
|
1161
|
+
end
|
|
1162
|
+
-- ▲ ReadonlySet.size ▲
|
|
1163
|
+
if _size == 0 then
|
|
1164
|
+
UI.hideBanner("protocol-mismatch")
|
|
1165
|
+
end
|
|
1131
1166
|
end
|
|
1132
1167
|
-- Server tells us when its in-memory instances map doesn't have us
|
|
1133
1168
|
-- (e.g. after an MCP process restart, or after it reaped us during a
|
|
@@ -1146,12 +1181,12 @@ local function pollForRequests(connIndex)
|
|
|
1146
1181
|
local el = ui
|
|
1147
1182
|
el.step1Dot.BackgroundColor3 = Color3.fromRGB(34, 197, 94)
|
|
1148
1183
|
el.step1Label.Text = "HTTP server (OK)"
|
|
1149
|
-
local
|
|
1150
|
-
if
|
|
1184
|
+
local _condition_2 = mcpConnected
|
|
1185
|
+
if _condition_2 then
|
|
1151
1186
|
local _value = (string.find(el.statusLabel.Text, "Connected"))
|
|
1152
|
-
|
|
1187
|
+
_condition_2 = not (_value ~= 0 and _value == _value and _value)
|
|
1153
1188
|
end
|
|
1154
|
-
if
|
|
1189
|
+
if _condition_2 then
|
|
1155
1190
|
el.statusLabel.Text = "Connected"
|
|
1156
1191
|
el.statusLabel.TextColor3 = Color3.fromRGB(34, 197, 94)
|
|
1157
1192
|
el.statusIndicator.BackgroundColor3 = Color3.fromRGB(34, 197, 94)
|
|
@@ -1182,11 +1217,11 @@ local function pollForRequests(connIndex)
|
|
|
1182
1217
|
conn.mcpWaitStartTime = tick()
|
|
1183
1218
|
end
|
|
1184
1219
|
local _exp = tick()
|
|
1185
|
-
local
|
|
1186
|
-
if
|
|
1187
|
-
|
|
1220
|
+
local _condition_3 = conn.mcpWaitStartTime
|
|
1221
|
+
if _condition_3 == nil then
|
|
1222
|
+
_condition_3 = tick()
|
|
1188
1223
|
end
|
|
1189
|
-
local elapsed = _exp -
|
|
1224
|
+
local elapsed = _exp - _condition_3
|
|
1190
1225
|
el.troubleshootLabel.Visible = elapsed > 8
|
|
1191
1226
|
UI.startPulseAnimation()
|
|
1192
1227
|
end
|
|
@@ -1356,6 +1391,26 @@ function deactivatePlugin(connIndex)
|
|
|
1356
1391
|
end
|
|
1357
1392
|
conn.isActive = false
|
|
1358
1393
|
conn.lastMcpOk = false
|
|
1394
|
+
versionMismatchConnections[conn] = nil
|
|
1395
|
+
protocolMismatchConnections[conn] = nil
|
|
1396
|
+
-- ▼ ReadonlySet.size ▼
|
|
1397
|
+
local _size = 0
|
|
1398
|
+
for _ in versionMismatchConnections do
|
|
1399
|
+
_size += 1
|
|
1400
|
+
end
|
|
1401
|
+
-- ▲ ReadonlySet.size ▲
|
|
1402
|
+
if _size == 0 then
|
|
1403
|
+
UI.hideBanner("version-mismatch")
|
|
1404
|
+
end
|
|
1405
|
+
-- ▼ ReadonlySet.size ▼
|
|
1406
|
+
local _size_1 = 0
|
|
1407
|
+
for _ in protocolMismatchConnections do
|
|
1408
|
+
_size_1 += 1
|
|
1409
|
+
end
|
|
1410
|
+
-- ▲ ReadonlySet.size ▲
|
|
1411
|
+
if _size_1 == 0 then
|
|
1412
|
+
UI.hideBanner("protocol-mismatch")
|
|
1413
|
+
end
|
|
1359
1414
|
if idx == State.getActiveTabIndex() then
|
|
1360
1415
|
UI.updateUIState()
|
|
1361
1416
|
end
|
|
@@ -1418,7 +1473,13 @@ local function checkForUpdates()
|
|
|
1418
1473
|
if _condition ~= "" and _condition then
|
|
1419
1474
|
local latestVersion = data.version
|
|
1420
1475
|
if Utils.compareVersions(State.CURRENT_VERSION, latestVersion) < 0 then
|
|
1421
|
-
|
|
1476
|
+
-- ▼ ReadonlySet.size ▼
|
|
1477
|
+
local _size = 0
|
|
1478
|
+
for _ in versionMismatchConnections do
|
|
1479
|
+
_size += 1
|
|
1480
|
+
end
|
|
1481
|
+
-- ▲ ReadonlySet.size ▲
|
|
1482
|
+
if _size == 0 then
|
|
1422
1483
|
UI.showBanner("update", `v{latestVersion} available - github.com/princeofscale/bloxforge`)
|
|
1423
1484
|
end
|
|
1424
1485
|
end
|
|
@@ -1556,9 +1617,9 @@ local function computeBridgeStamp()
|
|
|
1556
1617
|
for i = 1, #combined do
|
|
1557
1618
|
h = (h * 33 + (string.byte(combined, i))) % 2147483647
|
|
1558
1619
|
end
|
|
1559
|
-
-- "
|
|
1620
|
+
-- "3.0.0-rc.1" is replaced with the package version at package time
|
|
1560
1621
|
-- (scripts/build-plugin.mjs injectVersion), so a release bump also restamps.
|
|
1561
|
-
return `{tostring(h)}-
|
|
1622
|
+
return `{tostring(h)}-3.0.0-rc.1`
|
|
1562
1623
|
end
|
|
1563
1624
|
local BRIDGE_STAMP = computeBridgeStamp()
|
|
1564
1625
|
local function setSource(scriptInst, source)
|
|
@@ -10353,7 +10414,6 @@ local function computeWrapperLineOffset()
|
|
|
10353
10414
|
local before = string.sub(probe, 1, start - 1)
|
|
10354
10415
|
return countLines(before) - 1
|
|
10355
10416
|
end
|
|
10356
|
-
local WRAPPER_LINE_OFFSET = computeWrapperLineOffset()
|
|
10357
10417
|
-- Count source lines so the wrapper can filter traceback frames that fall
|
|
10358
10418
|
-- outside the user code range (the wrapper's own preamble/postamble lines).
|
|
10359
10419
|
function countLines(s)
|
|
@@ -10388,7 +10448,7 @@ end
|
|
|
10388
10448
|
-- instead of hand-maintained, so reordering preamble lines can never desync
|
|
10389
10449
|
-- __mcp_LINE_OFFSET / remapPayloadLines from the real line count.
|
|
10390
10450
|
function renderWrapper(lineOffset, userLines, code, payloadPattern)
|
|
10391
|
-
return `return (
|
|
10451
|
+
return `return (function()\
|
|
10392
10452
|
\tlocal __mcp_traceback\
|
|
10393
10453
|
\tlocal __mcp_remap\
|
|
10394
10454
|
\tlocal __mcp_LINE_OFFSET = {lineOffset}\
|
|
@@ -10505,7 +10565,7 @@ function renderWrapper(lineOffset, userLines, code, payloadPattern)
|
|
|
10505
10565
|
\t__mcp_remap = function(s)\
|
|
10506
10566
|
\t\t-- Two chunk-name formats can reference our payload:\
|
|
10507
10567
|
\t\t-- * "Workspace.__MCPExecLuauPayload:N" — ModuleScript:require fallback path\
|
|
10508
|
-
\t\t-- * "[string \\"return (
|
|
10568
|
+
\t\t-- * "[string \\"return (function()...\\"]:N" — loadstring() (default in plugin)\
|
|
10509
10569
|
\t\t-- Subtract LINE_OFFSET to get the user-relative number, then clamp.\
|
|
10510
10570
|
\t\t-- Clamping matters for unclosed constructs ("local x = (") where the\
|
|
10511
10571
|
\t\t-- parser keeps reading into wrapper postamble and reports a payload\
|
|
@@ -10568,6 +10628,9 @@ function renderWrapper(lineOffset, userLines, code, payloadPattern)
|
|
|
10568
10628
|
\treturn \{ ok = ok, value = errOrValue, output = __mcp_output \}\
|
|
10569
10629
|
end)()`
|
|
10570
10630
|
end
|
|
10631
|
+
-- roblox-ts emits function declarations as ordered local assignments, so this
|
|
10632
|
+
-- must run after every helper used by computeWrapperLineOffset is initialized.
|
|
10633
|
+
local WRAPPER_LINE_OFFSET = computeWrapperLineOffset()
|
|
10571
10634
|
local function buildWrapper(code, payloadInstanceName)
|
|
10572
10635
|
if payloadInstanceName == nil then
|
|
10573
10636
|
payloadInstanceName = PAYLOAD_INSTANCE_NAME
|
|
@@ -11236,7 +11299,7 @@ return {
|
|
|
11236
11299
|
<Properties>
|
|
11237
11300
|
<string name="Name">State</string>
|
|
11238
11301
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
11239
|
-
local CURRENT_VERSION = "
|
|
11302
|
+
local CURRENT_VERSION = "3.0.0-rc.1"
|
|
11240
11303
|
local PLUGIN_VARIANT = "inspector"
|
|
11241
11304
|
local PROTOCOL_VERSION = 1
|
|
11242
11305
|
local MAX_CONNECTIONS = 5
|
|
@@ -708,8 +708,10 @@ local function computeInstanceId()
|
|
|
708
708
|
end
|
|
709
709
|
local assignedRole
|
|
710
710
|
local duplicateInstanceRole = false
|
|
711
|
-
local
|
|
711
|
+
local versionMismatchConnections = {}
|
|
712
712
|
local lastVersionMismatchWarningKey
|
|
713
|
+
local protocolMismatchConnections = {}
|
|
714
|
+
local lastProtocolMismatchWarningKey
|
|
713
715
|
local lastReadyInstanceId
|
|
714
716
|
local readyFailureLogKeys = {}
|
|
715
717
|
-- Cache the published place name from MarketplaceService:GetProductInfo so
|
|
@@ -1118,16 +1120,49 @@ local function pollForRequests(connIndex)
|
|
|
1118
1120
|
end
|
|
1119
1121
|
local serverVersion = _condition
|
|
1120
1122
|
if data.versionMismatch == true then
|
|
1121
|
-
|
|
1123
|
+
versionMismatchConnections[conn] = true
|
|
1122
1124
|
local warningKey = `{State.CURRENT_VERSION}:{serverVersion}`
|
|
1123
1125
|
if lastVersionMismatchWarningKey ~= warningKey then
|
|
1124
1126
|
lastVersionMismatchWarningKey = warningKey
|
|
1125
1127
|
warn(`[BloxForge] Version mismatch: Studio plugin v{State.CURRENT_VERSION} / MCP v{serverVersion}. Run npx -y @princeofscale/bloxforge@latest --auto-install-plugin and restart Studio.`)
|
|
1126
1128
|
end
|
|
1127
1129
|
UI.showBanner("version-mismatch", `Plugin v{State.CURRENT_VERSION} / MCP v{serverVersion} mismatch`)
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1130
|
+
else
|
|
1131
|
+
versionMismatchConnections[conn] = nil
|
|
1132
|
+
-- ▼ ReadonlySet.size ▼
|
|
1133
|
+
local _size = 0
|
|
1134
|
+
for _ in versionMismatchConnections do
|
|
1135
|
+
_size += 1
|
|
1136
|
+
end
|
|
1137
|
+
-- ▲ ReadonlySet.size ▲
|
|
1138
|
+
if _size == 0 then
|
|
1139
|
+
UI.hideBanner("version-mismatch")
|
|
1140
|
+
end
|
|
1141
|
+
end
|
|
1142
|
+
local _condition_1 = data.serverProtocolVersion
|
|
1143
|
+
if _condition_1 == nil then
|
|
1144
|
+
_condition_1 = 0
|
|
1145
|
+
end
|
|
1146
|
+
local serverProtocol = _condition_1
|
|
1147
|
+
if data.protocolMismatch == true then
|
|
1148
|
+
protocolMismatchConnections[conn] = true
|
|
1149
|
+
local warningKey = `{State.PROTOCOL_VERSION}:{serverProtocol}`
|
|
1150
|
+
if lastProtocolMismatchWarningKey ~= warningKey then
|
|
1151
|
+
lastProtocolMismatchWarningKey = warningKey
|
|
1152
|
+
warn(`[BloxForge] Protocol mismatch: Studio plugin protocol v{State.PROTOCOL_VERSION} / MCP protocol v{serverProtocol}. Run npx -y @princeofscale/bloxforge@latest --auto-install-plugin and restart Studio.`)
|
|
1153
|
+
end
|
|
1154
|
+
UI.showBanner("protocol-mismatch", `Protocol mismatch: Plugin protocol v{State.PROTOCOL_VERSION} / MCP protocol v{serverProtocol}`)
|
|
1155
|
+
else
|
|
1156
|
+
protocolMismatchConnections[conn] = nil
|
|
1157
|
+
-- ▼ ReadonlySet.size ▼
|
|
1158
|
+
local _size = 0
|
|
1159
|
+
for _ in protocolMismatchConnections do
|
|
1160
|
+
_size += 1
|
|
1161
|
+
end
|
|
1162
|
+
-- ▲ ReadonlySet.size ▲
|
|
1163
|
+
if _size == 0 then
|
|
1164
|
+
UI.hideBanner("protocol-mismatch")
|
|
1165
|
+
end
|
|
1131
1166
|
end
|
|
1132
1167
|
-- Server tells us when its in-memory instances map doesn't have us
|
|
1133
1168
|
-- (e.g. after an MCP process restart, or after it reaped us during a
|
|
@@ -1146,12 +1181,12 @@ local function pollForRequests(connIndex)
|
|
|
1146
1181
|
local el = ui
|
|
1147
1182
|
el.step1Dot.BackgroundColor3 = Color3.fromRGB(34, 197, 94)
|
|
1148
1183
|
el.step1Label.Text = "HTTP server (OK)"
|
|
1149
|
-
local
|
|
1150
|
-
if
|
|
1184
|
+
local _condition_2 = mcpConnected
|
|
1185
|
+
if _condition_2 then
|
|
1151
1186
|
local _value = (string.find(el.statusLabel.Text, "Connected"))
|
|
1152
|
-
|
|
1187
|
+
_condition_2 = not (_value ~= 0 and _value == _value and _value)
|
|
1153
1188
|
end
|
|
1154
|
-
if
|
|
1189
|
+
if _condition_2 then
|
|
1155
1190
|
el.statusLabel.Text = "Connected"
|
|
1156
1191
|
el.statusLabel.TextColor3 = Color3.fromRGB(34, 197, 94)
|
|
1157
1192
|
el.statusIndicator.BackgroundColor3 = Color3.fromRGB(34, 197, 94)
|
|
@@ -1182,11 +1217,11 @@ local function pollForRequests(connIndex)
|
|
|
1182
1217
|
conn.mcpWaitStartTime = tick()
|
|
1183
1218
|
end
|
|
1184
1219
|
local _exp = tick()
|
|
1185
|
-
local
|
|
1186
|
-
if
|
|
1187
|
-
|
|
1220
|
+
local _condition_3 = conn.mcpWaitStartTime
|
|
1221
|
+
if _condition_3 == nil then
|
|
1222
|
+
_condition_3 = tick()
|
|
1188
1223
|
end
|
|
1189
|
-
local elapsed = _exp -
|
|
1224
|
+
local elapsed = _exp - _condition_3
|
|
1190
1225
|
el.troubleshootLabel.Visible = elapsed > 8
|
|
1191
1226
|
UI.startPulseAnimation()
|
|
1192
1227
|
end
|
|
@@ -1356,6 +1391,26 @@ function deactivatePlugin(connIndex)
|
|
|
1356
1391
|
end
|
|
1357
1392
|
conn.isActive = false
|
|
1358
1393
|
conn.lastMcpOk = false
|
|
1394
|
+
versionMismatchConnections[conn] = nil
|
|
1395
|
+
protocolMismatchConnections[conn] = nil
|
|
1396
|
+
-- ▼ ReadonlySet.size ▼
|
|
1397
|
+
local _size = 0
|
|
1398
|
+
for _ in versionMismatchConnections do
|
|
1399
|
+
_size += 1
|
|
1400
|
+
end
|
|
1401
|
+
-- ▲ ReadonlySet.size ▲
|
|
1402
|
+
if _size == 0 then
|
|
1403
|
+
UI.hideBanner("version-mismatch")
|
|
1404
|
+
end
|
|
1405
|
+
-- ▼ ReadonlySet.size ▼
|
|
1406
|
+
local _size_1 = 0
|
|
1407
|
+
for _ in protocolMismatchConnections do
|
|
1408
|
+
_size_1 += 1
|
|
1409
|
+
end
|
|
1410
|
+
-- ▲ ReadonlySet.size ▲
|
|
1411
|
+
if _size_1 == 0 then
|
|
1412
|
+
UI.hideBanner("protocol-mismatch")
|
|
1413
|
+
end
|
|
1359
1414
|
if idx == State.getActiveTabIndex() then
|
|
1360
1415
|
UI.updateUIState()
|
|
1361
1416
|
end
|
|
@@ -1418,7 +1473,13 @@ local function checkForUpdates()
|
|
|
1418
1473
|
if _condition ~= "" and _condition then
|
|
1419
1474
|
local latestVersion = data.version
|
|
1420
1475
|
if Utils.compareVersions(State.CURRENT_VERSION, latestVersion) < 0 then
|
|
1421
|
-
|
|
1476
|
+
-- ▼ ReadonlySet.size ▼
|
|
1477
|
+
local _size = 0
|
|
1478
|
+
for _ in versionMismatchConnections do
|
|
1479
|
+
_size += 1
|
|
1480
|
+
end
|
|
1481
|
+
-- ▲ ReadonlySet.size ▲
|
|
1482
|
+
if _size == 0 then
|
|
1422
1483
|
UI.showBanner("update", `v{latestVersion} available - github.com/princeofscale/bloxforge`)
|
|
1423
1484
|
end
|
|
1424
1485
|
end
|
|
@@ -1556,9 +1617,9 @@ local function computeBridgeStamp()
|
|
|
1556
1617
|
for i = 1, #combined do
|
|
1557
1618
|
h = (h * 33 + (string.byte(combined, i))) % 2147483647
|
|
1558
1619
|
end
|
|
1559
|
-
-- "
|
|
1620
|
+
-- "3.0.0-rc.1" is replaced with the package version at package time
|
|
1560
1621
|
-- (scripts/build-plugin.mjs injectVersion), so a release bump also restamps.
|
|
1561
|
-
return `{tostring(h)}-
|
|
1622
|
+
return `{tostring(h)}-3.0.0-rc.1`
|
|
1562
1623
|
end
|
|
1563
1624
|
local BRIDGE_STAMP = computeBridgeStamp()
|
|
1564
1625
|
local function setSource(scriptInst, source)
|
|
@@ -10353,7 +10414,6 @@ local function computeWrapperLineOffset()
|
|
|
10353
10414
|
local before = string.sub(probe, 1, start - 1)
|
|
10354
10415
|
return countLines(before) - 1
|
|
10355
10416
|
end
|
|
10356
|
-
local WRAPPER_LINE_OFFSET = computeWrapperLineOffset()
|
|
10357
10417
|
-- Count source lines so the wrapper can filter traceback frames that fall
|
|
10358
10418
|
-- outside the user code range (the wrapper's own preamble/postamble lines).
|
|
10359
10419
|
function countLines(s)
|
|
@@ -10388,7 +10448,7 @@ end
|
|
|
10388
10448
|
-- instead of hand-maintained, so reordering preamble lines can never desync
|
|
10389
10449
|
-- __mcp_LINE_OFFSET / remapPayloadLines from the real line count.
|
|
10390
10450
|
function renderWrapper(lineOffset, userLines, code, payloadPattern)
|
|
10391
|
-
return `return (
|
|
10451
|
+
return `return (function()\
|
|
10392
10452
|
\tlocal __mcp_traceback\
|
|
10393
10453
|
\tlocal __mcp_remap\
|
|
10394
10454
|
\tlocal __mcp_LINE_OFFSET = {lineOffset}\
|
|
@@ -10505,7 +10565,7 @@ function renderWrapper(lineOffset, userLines, code, payloadPattern)
|
|
|
10505
10565
|
\t__mcp_remap = function(s)\
|
|
10506
10566
|
\t\t-- Two chunk-name formats can reference our payload:\
|
|
10507
10567
|
\t\t-- * "Workspace.__MCPExecLuauPayload:N" — ModuleScript:require fallback path\
|
|
10508
|
-
\t\t-- * "[string \\"return (
|
|
10568
|
+
\t\t-- * "[string \\"return (function()...\\"]:N" — loadstring() (default in plugin)\
|
|
10509
10569
|
\t\t-- Subtract LINE_OFFSET to get the user-relative number, then clamp.\
|
|
10510
10570
|
\t\t-- Clamping matters for unclosed constructs ("local x = (") where the\
|
|
10511
10571
|
\t\t-- parser keeps reading into wrapper postamble and reports a payload\
|
|
@@ -10568,6 +10628,9 @@ function renderWrapper(lineOffset, userLines, code, payloadPattern)
|
|
|
10568
10628
|
\treturn \{ ok = ok, value = errOrValue, output = __mcp_output \}\
|
|
10569
10629
|
end)()`
|
|
10570
10630
|
end
|
|
10631
|
+
-- roblox-ts emits function declarations as ordered local assignments, so this
|
|
10632
|
+
-- must run after every helper used by computeWrapperLineOffset is initialized.
|
|
10633
|
+
local WRAPPER_LINE_OFFSET = computeWrapperLineOffset()
|
|
10571
10634
|
local function buildWrapper(code, payloadInstanceName)
|
|
10572
10635
|
if payloadInstanceName == nil then
|
|
10573
10636
|
payloadInstanceName = PAYLOAD_INSTANCE_NAME
|
|
@@ -11236,7 +11299,7 @@ return {
|
|
|
11236
11299
|
<Properties>
|
|
11237
11300
|
<string name="Name">State</string>
|
|
11238
11301
|
<string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
|
|
11239
|
-
local CURRENT_VERSION = "
|
|
11302
|
+
local CURRENT_VERSION = "3.0.0-rc.1"
|
|
11240
11303
|
local PLUGIN_VARIANT = "main"
|
|
11241
11304
|
local PROTOCOL_VERSION = 1
|
|
11242
11305
|
local MAX_CONNECTIONS = 5
|
|
@@ -67,9 +67,9 @@
|
|
|
67
67
|
}
|
|
68
68
|
},
|
|
69
69
|
"node_modules/ajv": {
|
|
70
|
-
"version": "8.
|
|
71
|
-
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.
|
|
72
|
-
"integrity": "sha512-
|
|
70
|
+
"version": "8.20.0",
|
|
71
|
+
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
|
|
72
|
+
"integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
|
|
73
73
|
"dev": true,
|
|
74
74
|
"license": "MIT",
|
|
75
75
|
"dependencies": {
|
|
@@ -234,9 +234,9 @@
|
|
|
234
234
|
"license": "MIT"
|
|
235
235
|
},
|
|
236
236
|
"node_modules/fast-uri": {
|
|
237
|
-
"version": "3.1.
|
|
238
|
-
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.
|
|
239
|
-
"integrity": "sha512-
|
|
237
|
+
"version": "3.1.3",
|
|
238
|
+
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz",
|
|
239
|
+
"integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==",
|
|
240
240
|
"dev": true,
|
|
241
241
|
"funding": [
|
|
242
242
|
{
|
|
@@ -466,9 +466,9 @@
|
|
|
466
466
|
"license": "MIT"
|
|
467
467
|
},
|
|
468
468
|
"node_modules/picomatch": {
|
|
469
|
-
"version": "2.3.
|
|
470
|
-
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.
|
|
471
|
-
"integrity": "sha512-
|
|
469
|
+
"version": "2.3.2",
|
|
470
|
+
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
|
471
|
+
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
|
472
472
|
"dev": true,
|
|
473
473
|
"license": "MIT",
|
|
474
474
|
"engines": {
|
|
@@ -54,8 +54,10 @@ function computeInstanceId(): string {
|
|
|
54
54
|
|
|
55
55
|
let assignedRole: string | undefined;
|
|
56
56
|
let duplicateInstanceRole = false;
|
|
57
|
-
|
|
57
|
+
const versionMismatchConnections = new Set<Connection>();
|
|
58
58
|
let lastVersionMismatchWarningKey: string | undefined;
|
|
59
|
+
const protocolMismatchConnections = new Set<Connection>();
|
|
60
|
+
let lastProtocolMismatchWarningKey: string | undefined;
|
|
59
61
|
let lastReadyInstanceId: string | undefined;
|
|
60
62
|
const readyFailureLogKeys = new Set<string>();
|
|
61
63
|
|
|
@@ -408,16 +410,30 @@ function pollForRequests(connIndex: number) {
|
|
|
408
410
|
conn.lastMcpOk = mcpConnected;
|
|
409
411
|
const serverVersion = data.serverVersion ?? "unknown";
|
|
410
412
|
if (data.versionMismatch === true) {
|
|
411
|
-
|
|
413
|
+
versionMismatchConnections.add(conn);
|
|
412
414
|
const warningKey = `${State.CURRENT_VERSION}:${serverVersion}`;
|
|
413
415
|
if (lastVersionMismatchWarningKey !== warningKey) {
|
|
414
416
|
lastVersionMismatchWarningKey = warningKey;
|
|
415
417
|
warn(`[BloxForge] Version mismatch: Studio plugin v${State.CURRENT_VERSION} / MCP v${serverVersion}. Run npx -y @princeofscale/bloxforge@latest --auto-install-plugin and restart Studio.`);
|
|
416
418
|
}
|
|
417
419
|
UI.showBanner("version-mismatch", `Plugin v${State.CURRENT_VERSION} / MCP v${serverVersion} mismatch`);
|
|
418
|
-
} else
|
|
419
|
-
|
|
420
|
-
UI.hideBanner("version-mismatch");
|
|
420
|
+
} else {
|
|
421
|
+
versionMismatchConnections.delete(conn);
|
|
422
|
+
if (versionMismatchConnections.size() === 0) UI.hideBanner("version-mismatch");
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const serverProtocol = data.serverProtocolVersion ?? 0;
|
|
426
|
+
if (data.protocolMismatch === true) {
|
|
427
|
+
protocolMismatchConnections.add(conn);
|
|
428
|
+
const warningKey = `${State.PROTOCOL_VERSION}:${serverProtocol}`;
|
|
429
|
+
if (lastProtocolMismatchWarningKey !== warningKey) {
|
|
430
|
+
lastProtocolMismatchWarningKey = warningKey;
|
|
431
|
+
warn(`[BloxForge] Protocol mismatch: Studio plugin protocol v${State.PROTOCOL_VERSION} / MCP protocol v${serverProtocol}. Run npx -y @princeofscale/bloxforge@latest --auto-install-plugin and restart Studio.`);
|
|
432
|
+
}
|
|
433
|
+
UI.showBanner("protocol-mismatch", `Protocol mismatch: Plugin protocol v${State.PROTOCOL_VERSION} / MCP protocol v${serverProtocol}`);
|
|
434
|
+
} else {
|
|
435
|
+
protocolMismatchConnections.delete(conn);
|
|
436
|
+
if (protocolMismatchConnections.size() === 0) UI.hideBanner("protocol-mismatch");
|
|
421
437
|
}
|
|
422
438
|
|
|
423
439
|
// Server tells us when its in-memory instances map doesn't have us
|
|
@@ -629,6 +645,10 @@ function deactivatePlugin(connIndex?: number) {
|
|
|
629
645
|
|
|
630
646
|
conn.isActive = false;
|
|
631
647
|
conn.lastMcpOk = false;
|
|
648
|
+
versionMismatchConnections.delete(conn);
|
|
649
|
+
protocolMismatchConnections.delete(conn);
|
|
650
|
+
if (versionMismatchConnections.size() === 0) UI.hideBanner("version-mismatch");
|
|
651
|
+
if (protocolMismatchConnections.size() === 0) UI.hideBanner("protocol-mismatch");
|
|
632
652
|
|
|
633
653
|
if (idx === State.getActiveTabIndex()) UI.updateUIState();
|
|
634
654
|
UI.updateTabDot(idx);
|
|
@@ -679,7 +699,7 @@ function checkForUpdates() {
|
|
|
679
699
|
if (ok && data?.version) {
|
|
680
700
|
const latestVersion = data.version;
|
|
681
701
|
if (Utils.compareVersions(State.CURRENT_VERSION, latestVersion) < 0) {
|
|
682
|
-
if (
|
|
702
|
+
if (versionMismatchConnections.size() === 0) {
|
|
683
703
|
UI.showBanner("update", `v${latestVersion} available - github.com/princeofscale/bloxforge`);
|
|
684
704
|
}
|
|
685
705
|
}
|
|
@@ -68,7 +68,6 @@ function computeWrapperLineOffset(): number {
|
|
|
68
68
|
const before = string.sub(probe, 1, (start as number) - 1);
|
|
69
69
|
return countLines(before) - 1;
|
|
70
70
|
}
|
|
71
|
-
const WRAPPER_LINE_OFFSET = computeWrapperLineOffset();
|
|
72
71
|
|
|
73
72
|
// Count source lines so the wrapper can filter traceback frames that fall
|
|
74
73
|
// outside the user code range (the wrapper's own preamble/postamble lines).
|
|
@@ -97,7 +96,7 @@ function renderWrapper(
|
|
|
97
96
|
code: string,
|
|
98
97
|
payloadPattern: string,
|
|
99
98
|
): string {
|
|
100
|
-
return `return (
|
|
99
|
+
return `return (function()
|
|
101
100
|
\tlocal __mcp_traceback
|
|
102
101
|
\tlocal __mcp_remap
|
|
103
102
|
\tlocal __mcp_LINE_OFFSET = ${lineOffset}
|
|
@@ -214,7 +213,7 @@ ${code}
|
|
|
214
213
|
\t__mcp_remap = function(s)
|
|
215
214
|
\t\t-- Two chunk-name formats can reference our payload:
|
|
216
215
|
\t\t-- * "Workspace.__MCPExecLuauPayload:N" — ModuleScript:require fallback path
|
|
217
|
-
\t\t-- * "[string \\"return (
|
|
216
|
+
\t\t-- * "[string \\"return (function()...\\"]:N" — loadstring() (default in plugin)
|
|
218
217
|
\t\t-- Subtract LINE_OFFSET to get the user-relative number, then clamp.
|
|
219
218
|
\t\t-- Clamping matters for unclosed constructs ("local x = (") where the
|
|
220
219
|
\t\t-- parser keeps reading into wrapper postamble and reports a payload
|
|
@@ -278,6 +277,10 @@ ${code}
|
|
|
278
277
|
end)()`;
|
|
279
278
|
}
|
|
280
279
|
|
|
280
|
+
// roblox-ts emits function declarations as ordered local assignments, so this
|
|
281
|
+
// must run after every helper used by computeWrapperLineOffset is initialized.
|
|
282
|
+
const WRAPPER_LINE_OFFSET = computeWrapperLineOffset();
|
|
283
|
+
|
|
281
284
|
function buildWrapper(code: string, payloadInstanceName = PAYLOAD_INSTANCE_NAME): string {
|
|
282
285
|
// The line offset is DERIVED from the rendered template (see
|
|
283
286
|
// computeWrapperLineOffset) instead of hand-maintained, so reordering the
|