@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/dist/index.js
CHANGED
|
@@ -32,7 +32,7 @@ function publishedInstanceId(placeId) {
|
|
|
32
32
|
return void 0;
|
|
33
33
|
return `place:${Math.trunc(placeId)}`;
|
|
34
34
|
}
|
|
35
|
-
var RoutingFailure, STALE_INSTANCE_MS, INSTANCE_ALIAS_TTL_MS, BridgeService;
|
|
35
|
+
var RoutingFailure, STALE_INSTANCE_MS, DUPLICATE_TAKEOVER_MS, INSTANCE_ALIAS_TTL_MS, BridgeService;
|
|
36
36
|
var init_bridge_service = __esm({
|
|
37
37
|
"../core/dist/bridge-service.js"() {
|
|
38
38
|
"use strict";
|
|
@@ -45,13 +45,33 @@ var init_bridge_service = __esm({
|
|
|
45
45
|
}
|
|
46
46
|
};
|
|
47
47
|
STALE_INSTANCE_MS = 3e4;
|
|
48
|
+
DUPLICATE_TAKEOVER_MS = 3e3;
|
|
48
49
|
INSTANCE_ALIAS_TTL_MS = 5 * 60 * 1e3;
|
|
49
50
|
BridgeService = class {
|
|
50
51
|
pendingRequests = /* @__PURE__ */ new Map();
|
|
51
52
|
// Keyed by pluginSessionId (the per-plugin GUID).
|
|
52
53
|
instances = /* @__PURE__ */ new Map();
|
|
53
54
|
instanceAliases = /* @__PURE__ */ new Map();
|
|
55
|
+
instanceRegisteredListeners = /* @__PURE__ */ new Set();
|
|
54
56
|
requestTimeout = 3e4;
|
|
57
|
+
onInstanceRegistered(listener) {
|
|
58
|
+
this.instanceRegisteredListeners.add(listener);
|
|
59
|
+
for (const instance of this.getPublicInstances()) {
|
|
60
|
+
try {
|
|
61
|
+
listener(instance);
|
|
62
|
+
} catch {
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return () => this.instanceRegisteredListeners.delete(listener);
|
|
66
|
+
}
|
|
67
|
+
notifyInstanceRegistered(instance) {
|
|
68
|
+
for (const listener of this.instanceRegisteredListeners) {
|
|
69
|
+
try {
|
|
70
|
+
listener(instance);
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
55
75
|
canonicalInstanceId(instanceId, placeId) {
|
|
56
76
|
return publishedInstanceId(placeId) ?? instanceId;
|
|
57
77
|
}
|
|
@@ -148,16 +168,20 @@ var init_bridge_service = __esm({
|
|
|
148
168
|
}
|
|
149
169
|
const existing = Array.from(this.instances.values()).find((i) => i.instanceId === instanceId && i.role === assignedRole && i.pluginSessionId !== pluginSessionId);
|
|
150
170
|
if (existing) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
171
|
+
if (Date.now() - existing.lastActivity > DUPLICATE_TAKEOVER_MS) {
|
|
172
|
+
this.unregisterInstance(existing.pluginSessionId);
|
|
173
|
+
} else {
|
|
174
|
+
return {
|
|
175
|
+
ok: false,
|
|
176
|
+
error: {
|
|
177
|
+
code: "duplicate_instance_role",
|
|
178
|
+
message: `Another plugin is already registered as (${instanceId}, ${assignedRole}).`,
|
|
179
|
+
existing: toPublic(existing)
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
159
183
|
}
|
|
160
|
-
|
|
184
|
+
const registered = {
|
|
161
185
|
pluginSessionId,
|
|
162
186
|
instanceId,
|
|
163
187
|
role: assignedRole,
|
|
@@ -171,7 +195,9 @@ var init_bridge_service = __esm({
|
|
|
171
195
|
versionMismatch,
|
|
172
196
|
lastActivity: Date.now(),
|
|
173
197
|
connectedAt: prior?.connectedAt ?? Date.now()
|
|
174
|
-
}
|
|
198
|
+
};
|
|
199
|
+
this.instances.set(pluginSessionId, registered);
|
|
200
|
+
this.notifyInstanceRegistered(toPublic(registered));
|
|
175
201
|
return { ok: true, assignedRole, instanceId };
|
|
176
202
|
}
|
|
177
203
|
unregisterInstance(pluginSessionId) {
|
|
@@ -369,7 +395,7 @@ var init_bridge_service = __esm({
|
|
|
369
395
|
async sendRequest(endpoint, data, targetInstanceId, targetRole, timeoutMs = this.requestTimeout) {
|
|
370
396
|
const requestId = uuidv4();
|
|
371
397
|
const effectiveTimeoutMs = Math.max(1, timeoutMs);
|
|
372
|
-
return new Promise((
|
|
398
|
+
return new Promise((resolve5, reject) => {
|
|
373
399
|
const timeoutId = setTimeout(() => {
|
|
374
400
|
if (this.pendingRequests.has(requestId)) {
|
|
375
401
|
this.pendingRequests.delete(requestId);
|
|
@@ -384,7 +410,7 @@ var init_bridge_service = __esm({
|
|
|
384
410
|
targetRole,
|
|
385
411
|
timestamp: Date.now(),
|
|
386
412
|
inFlight: false,
|
|
387
|
-
resolve:
|
|
413
|
+
resolve: resolve5,
|
|
388
414
|
reject,
|
|
389
415
|
timeoutId,
|
|
390
416
|
timeoutMs: effectiveTimeoutMs
|
|
@@ -480,6 +506,93 @@ function cacheSet(key, entry) {
|
|
|
480
506
|
function docUrl(category, name) {
|
|
481
507
|
return `${DOCS_BASE_URL}/${category}/${encodeURIComponent(name)}.md`;
|
|
482
508
|
}
|
|
509
|
+
function normalizeDocName(value) {
|
|
510
|
+
return value.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
511
|
+
}
|
|
512
|
+
function queryNameVariants(value) {
|
|
513
|
+
const parts = value.split(/[./:#\s]+/).filter(Boolean);
|
|
514
|
+
const variants = /* @__PURE__ */ new Set([normalizeDocName(value)]);
|
|
515
|
+
for (const part of parts) {
|
|
516
|
+
const normalized = normalizeDocName(part);
|
|
517
|
+
if (normalized && normalized !== "enum")
|
|
518
|
+
variants.add(normalized);
|
|
519
|
+
}
|
|
520
|
+
return Array.from(variants).filter(Boolean);
|
|
521
|
+
}
|
|
522
|
+
function editDistance(left, right) {
|
|
523
|
+
const previous = Array.from({ length: right.length + 1 }, (_, index) => index);
|
|
524
|
+
for (let i = 1; i <= left.length; i++) {
|
|
525
|
+
let diagonal = previous[0];
|
|
526
|
+
previous[0] = i;
|
|
527
|
+
for (let j = 1; j <= right.length; j++) {
|
|
528
|
+
const above = previous[j];
|
|
529
|
+
previous[j] = Math.min(previous[j] + 1, previous[j - 1] + 1, diagonal + (left[i - 1] === right[j - 1] ? 0 : 1));
|
|
530
|
+
diagonal = above;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return previous[right.length];
|
|
534
|
+
}
|
|
535
|
+
async function fetchDocCatalog() {
|
|
536
|
+
if (catalogCache && Date.now() - catalogCache.fetchedAt <= CACHE_TTL_MS) {
|
|
537
|
+
return catalogCache.pages;
|
|
538
|
+
}
|
|
539
|
+
const controller = new AbortController();
|
|
540
|
+
const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
541
|
+
let response;
|
|
542
|
+
try {
|
|
543
|
+
response = await fetch(DOCS_INDEX_URL, {
|
|
544
|
+
signal: controller.signal,
|
|
545
|
+
headers: { Accept: "text/html" }
|
|
546
|
+
});
|
|
547
|
+
} finally {
|
|
548
|
+
clearTimeout(timer);
|
|
549
|
+
}
|
|
550
|
+
if (!response.ok) {
|
|
551
|
+
throw new Error(`Failed to fetch Roblox docs index: HTTP ${response.status}`);
|
|
552
|
+
}
|
|
553
|
+
const html = await response.text();
|
|
554
|
+
const pages = /* @__PURE__ */ new Map();
|
|
555
|
+
const routePattern = /reference\/engine\/(classes|enums|datatypes|libraries|globals)\/([A-Za-z0-9_]+)/g;
|
|
556
|
+
for (const match of html.matchAll(routePattern)) {
|
|
557
|
+
const category = match[1];
|
|
558
|
+
const name = match[2];
|
|
559
|
+
const key = `${category}/${name}`;
|
|
560
|
+
pages.set(key, { category, name, url: docUrl(category, name) });
|
|
561
|
+
}
|
|
562
|
+
const result = Array.from(pages.values());
|
|
563
|
+
if (result.length === 0) {
|
|
564
|
+
throw new Error("Roblox docs index did not contain any engine reference pages");
|
|
565
|
+
}
|
|
566
|
+
catalogCache = { fetchedAt: Date.now(), pages: result };
|
|
567
|
+
return result;
|
|
568
|
+
}
|
|
569
|
+
async function recommendRobloxDocs(category, name, limit = 5) {
|
|
570
|
+
const variants = queryNameVariants(name);
|
|
571
|
+
const catalog = await fetchDocCatalog();
|
|
572
|
+
return catalog.map((page) => {
|
|
573
|
+
const candidate = normalizeDocName(page.name);
|
|
574
|
+
const similarity = Math.max(...variants.map((variant) => {
|
|
575
|
+
if (variant === candidate)
|
|
576
|
+
return 2;
|
|
577
|
+
const distance = editDistance(variant, candidate);
|
|
578
|
+
const ratio = 1 - distance / Math.max(variant.length, candidate.length, 1);
|
|
579
|
+
const prefixBonus = variant.startsWith(candidate) || candidate.startsWith(variant) ? 0.15 : 0;
|
|
580
|
+
const containsBonus = variant.includes(candidate) || candidate.includes(variant) ? 0.05 : 0;
|
|
581
|
+
return ratio + prefixBonus + containsBonus;
|
|
582
|
+
}));
|
|
583
|
+
return { page, score: similarity + (page.category === category ? 0.08 : 0) };
|
|
584
|
+
}).sort((a, b) => b.score - a.score || a.page.name.localeCompare(b.page.name)).slice(0, Math.max(1, limit)).map(({ page }) => page);
|
|
585
|
+
}
|
|
586
|
+
function recommendationsMarkdown(requestedCategory, requestedName, recommendations) {
|
|
587
|
+
const lines = recommendations.map((page) => `- [${page.category}/${page.name}](${page.url}) \u2014 retry with \`name="${page.name}", doc_type="${page.category}"\``);
|
|
588
|
+
return [
|
|
589
|
+
`# No exact Roblox documentation page found`,
|
|
590
|
+
"",
|
|
591
|
+
`The lookup \`${requestedCategory}/${requestedName}\` did not resolve. Recommended pages:`,
|
|
592
|
+
"",
|
|
593
|
+
...lines
|
|
594
|
+
].join("\n");
|
|
595
|
+
}
|
|
483
596
|
async function fetchRobloxDoc(category, name) {
|
|
484
597
|
const key = `${category}/${name}`;
|
|
485
598
|
const cached = cacheGet(key);
|
|
@@ -544,7 +657,24 @@ function extractSection(markdown, section) {
|
|
|
544
657
|
return lines.slice(start, end).join("\n").trimEnd();
|
|
545
658
|
}
|
|
546
659
|
async function getRobloxDoc(category, name, section) {
|
|
547
|
-
|
|
660
|
+
let markdown;
|
|
661
|
+
try {
|
|
662
|
+
markdown = await fetchRobloxDoc(category, name);
|
|
663
|
+
} catch (error) {
|
|
664
|
+
if (!(error instanceof DocNotFoundError))
|
|
665
|
+
throw error;
|
|
666
|
+
try {
|
|
667
|
+
const recommendations = await recommendRobloxDocs(category, name);
|
|
668
|
+
return {
|
|
669
|
+
content: recommendationsMarkdown(category, name, recommendations),
|
|
670
|
+
truncated: false,
|
|
671
|
+
sections: [],
|
|
672
|
+
recommendations
|
|
673
|
+
};
|
|
674
|
+
} catch {
|
|
675
|
+
throw error;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
548
678
|
const sections = listSections(markdown);
|
|
549
679
|
if (section) {
|
|
550
680
|
const extracted = extractSection(markdown, section);
|
|
@@ -563,12 +693,13 @@ async function getRobloxDoc(category, name, section) {
|
|
|
563
693
|
}
|
|
564
694
|
return { content: markdown, truncated: false, sections };
|
|
565
695
|
}
|
|
566
|
-
var DOC_CATEGORIES, DOCS_BASE_URL, FETCH_TIMEOUT_MS, CACHE_TTL_MS, NEGATIVE_CACHE_TTL_MS, MAX_CACHE_ENTRIES, MAX_DOC_CHARS, cache, DocNotFoundError;
|
|
696
|
+
var DOC_CATEGORIES, DOCS_BASE_URL, DOCS_INDEX_URL, FETCH_TIMEOUT_MS, CACHE_TTL_MS, NEGATIVE_CACHE_TTL_MS, MAX_CACHE_ENTRIES, MAX_DOC_CHARS, cache, catalogCache, DocNotFoundError;
|
|
567
697
|
var init_roblox_docs = __esm({
|
|
568
698
|
"../core/dist/roblox-docs.js"() {
|
|
569
699
|
"use strict";
|
|
570
700
|
DOC_CATEGORIES = ["classes", "enums", "datatypes", "libraries", "globals"];
|
|
571
701
|
DOCS_BASE_URL = "https://create.roblox.com/docs/reference/engine";
|
|
702
|
+
DOCS_INDEX_URL = DOCS_BASE_URL;
|
|
572
703
|
FETCH_TIMEOUT_MS = 15e3;
|
|
573
704
|
CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
574
705
|
NEGATIVE_CACHE_TTL_MS = 5 * 60 * 1e3;
|
|
@@ -686,8 +817,8 @@ function resolveAuthToken() {
|
|
|
686
817
|
} catch {
|
|
687
818
|
}
|
|
688
819
|
return { token: fresh, source: "file", filePath };
|
|
689
|
-
} catch (
|
|
690
|
-
console.error(`[auth] Could not read/create ${filePath} (${
|
|
820
|
+
} catch (err2) {
|
|
821
|
+
console.error(`[auth] Could not read/create ${filePath} (${err2 instanceof Error ? err2.message : err2}). Using an in-memory token for this process; set ROBLOX_STUDIO_AUTH_TOKEN to share one across sessions.`);
|
|
691
822
|
return { token: randomBytes(32).toString("hex"), source: "file" };
|
|
692
823
|
}
|
|
693
824
|
}
|
|
@@ -811,7 +942,7 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
811
942
|
next();
|
|
812
943
|
});
|
|
813
944
|
const authToken = security?.authToken;
|
|
814
|
-
const authRequired = (
|
|
945
|
+
const authRequired = (path6) => path6 === "/mcp" || path6.startsWith("/mcp/") || path6 === "/proxy" || path6 === "/instances" || path6 === "/unregister-instance-id";
|
|
815
946
|
app.use((req, res, next) => {
|
|
816
947
|
if (!authToken || !authRequired(req.path)) {
|
|
817
948
|
next();
|
|
@@ -891,11 +1022,11 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
891
1022
|
pluginVariant: typeof pluginVariant === "string" ? pluginVariant : "unknown",
|
|
892
1023
|
serverVersion: serverConfig?.version ?? ""
|
|
893
1024
|
});
|
|
894
|
-
} catch (
|
|
1025
|
+
} catch (err2) {
|
|
895
1026
|
res.status(500).json({
|
|
896
1027
|
success: false,
|
|
897
1028
|
error: "ready_registration_exception",
|
|
898
|
-
message:
|
|
1029
|
+
message: err2 instanceof Error ? err2.message : String(err2),
|
|
899
1030
|
request: requestContext
|
|
900
1031
|
});
|
|
901
1032
|
return;
|
|
@@ -1046,8 +1177,8 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
1046
1177
|
try {
|
|
1047
1178
|
const response = await bridge.sendRequest(endpoint, data, targetInstanceId, targetRole);
|
|
1048
1179
|
res.json({ response });
|
|
1049
|
-
} catch (
|
|
1050
|
-
res.status(500).json({ error:
|
|
1180
|
+
} catch (err2) {
|
|
1181
|
+
res.status(500).json({ error: err2.message || "Proxy request failed" });
|
|
1051
1182
|
}
|
|
1052
1183
|
});
|
|
1053
1184
|
if (serverConfig) {
|
|
@@ -1159,19 +1290,19 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
1159
1290
|
return app;
|
|
1160
1291
|
}
|
|
1161
1292
|
function listenWithRetry(app, host, startPort, maxAttempts = 5) {
|
|
1162
|
-
return new Promise(async (
|
|
1293
|
+
return new Promise(async (resolve5, reject) => {
|
|
1163
1294
|
for (let i = 0; i < maxAttempts; i++) {
|
|
1164
1295
|
const port = startPort + i;
|
|
1165
1296
|
try {
|
|
1166
1297
|
const server = await bindPort(app, host, port);
|
|
1167
|
-
|
|
1298
|
+
resolve5({ server, port });
|
|
1168
1299
|
return;
|
|
1169
|
-
} catch (
|
|
1170
|
-
if (
|
|
1300
|
+
} catch (err2) {
|
|
1301
|
+
if (err2.code === "EADDRINUSE") {
|
|
1171
1302
|
console.error(`Port ${port} in use, trying next...`);
|
|
1172
1303
|
continue;
|
|
1173
1304
|
}
|
|
1174
|
-
reject(
|
|
1305
|
+
reject(err2);
|
|
1175
1306
|
return;
|
|
1176
1307
|
}
|
|
1177
1308
|
}
|
|
@@ -1179,16 +1310,16 @@ function listenWithRetry(app, host, startPort, maxAttempts = 5) {
|
|
|
1179
1310
|
});
|
|
1180
1311
|
}
|
|
1181
1312
|
function bindPort(app, host, port) {
|
|
1182
|
-
return new Promise((
|
|
1313
|
+
return new Promise((resolve5, reject) => {
|
|
1183
1314
|
const server = http.createServer(app);
|
|
1184
|
-
const onError = (
|
|
1315
|
+
const onError = (err2) => {
|
|
1185
1316
|
server.removeListener("error", onError);
|
|
1186
|
-
reject(
|
|
1317
|
+
reject(err2);
|
|
1187
1318
|
};
|
|
1188
1319
|
server.once("error", onError);
|
|
1189
1320
|
server.listen(port, host, () => {
|
|
1190
1321
|
server.removeListener("error", onError);
|
|
1191
|
-
|
|
1322
|
+
resolve5(server);
|
|
1192
1323
|
});
|
|
1193
1324
|
});
|
|
1194
1325
|
}
|
|
@@ -1200,6 +1331,7 @@ var init_http_server = __esm({
|
|
|
1200
1331
|
init_mcp_compat();
|
|
1201
1332
|
init_auth();
|
|
1202
1333
|
TOOL_HANDLERS = {
|
|
1334
|
+
get_roblox_skills: (tools, body) => tools.getRobloxSkills(body.action, body.name),
|
|
1203
1335
|
get_roblox_docs: (tools, body) => tools.getRobloxDocs(body.name, body.doc_type, body.section),
|
|
1204
1336
|
get_file_tree: (tools, body) => tools.getFileTree(body.path, body.instance_id),
|
|
1205
1337
|
search_files: (tools, body) => tools.searchFiles(body.query, body.searchType, body.instance_id),
|
|
@@ -1382,8 +1514,8 @@ function runRestrictedScript(code, globals, options) {
|
|
|
1382
1514
|
let ast;
|
|
1383
1515
|
try {
|
|
1384
1516
|
ast = acorn.parse(code, { ecmaVersion: 2022, sourceType: "script" });
|
|
1385
|
-
} catch (
|
|
1386
|
-
throw new Error(`Syntax error: ${
|
|
1517
|
+
} catch (err2) {
|
|
1518
|
+
throw new Error(`Syntax error: ${err2.message}`);
|
|
1387
1519
|
}
|
|
1388
1520
|
const globalScope = new Scope(void 0, true);
|
|
1389
1521
|
for (const [name, value] of Object.entries(globals)) {
|
|
@@ -1694,15 +1826,15 @@ function runRestrictedScript(code, globals, options) {
|
|
|
1694
1826
|
case "TryStatement": {
|
|
1695
1827
|
try {
|
|
1696
1828
|
execute(node.block, scope);
|
|
1697
|
-
} catch (
|
|
1698
|
-
if (
|
|
1699
|
-
throw
|
|
1700
|
-
if (
|
|
1701
|
-
throw
|
|
1829
|
+
} catch (err2) {
|
|
1830
|
+
if (err2 instanceof BreakSignal || err2 instanceof ContinueSignal || err2 instanceof ReturnSignal)
|
|
1831
|
+
throw err2;
|
|
1832
|
+
if (err2 instanceof ScriptTimeoutError)
|
|
1833
|
+
throw err2;
|
|
1702
1834
|
if (node.handler) {
|
|
1703
1835
|
const catchScope = new Scope(scope);
|
|
1704
1836
|
if (node.handler.param) {
|
|
1705
|
-
const message =
|
|
1837
|
+
const message = err2 instanceof Error ? { message: err2.message } : err2;
|
|
1706
1838
|
bindPattern(node.handler.param, message, catchScope, "let");
|
|
1707
1839
|
}
|
|
1708
1840
|
execute(node.handler.body, catchScope);
|
|
@@ -2580,11 +2712,11 @@ function runBuildExecutor(code, palette, seed, options) {
|
|
|
2580
2712
|
};
|
|
2581
2713
|
try {
|
|
2582
2714
|
runRestrictedScript(code, sandbox, { timeoutMs: timeout });
|
|
2583
|
-
} catch (
|
|
2584
|
-
if (
|
|
2715
|
+
} catch (err2) {
|
|
2716
|
+
if (err2 instanceof ScriptTimeoutError) {
|
|
2585
2717
|
throw new Error(`Build code execution timed out after ${timeout}ms`);
|
|
2586
2718
|
}
|
|
2587
|
-
throw new Error(`Build code execution error: ${
|
|
2719
|
+
throw new Error(`Build code execution error: ${err2?.message ?? String(err2)}`);
|
|
2588
2720
|
}
|
|
2589
2721
|
if (parts.length === 0) {
|
|
2590
2722
|
throw new Error("Build code produced no parts. Make sure to call part(), wall(), floor(), etc.");
|
|
@@ -2843,7 +2975,7 @@ var init_opencloud_client = __esm({
|
|
|
2843
2975
|
if (result.error) {
|
|
2844
2976
|
throw new Error(`Asset upload failed: ${result.error.message}`);
|
|
2845
2977
|
}
|
|
2846
|
-
await new Promise((
|
|
2978
|
+
await new Promise((resolve5) => setTimeout(resolve5, intervalMs));
|
|
2847
2979
|
}
|
|
2848
2980
|
throw new Error(`Asset upload timed out after ${maxAttempts * intervalMs / 1e3}s. Operation ID: ${operationId}`);
|
|
2849
2981
|
}
|
|
@@ -2964,7 +3096,7 @@ function isRecord(value) {
|
|
|
2964
3096
|
const record = value;
|
|
2965
3097
|
return record.version === REGISTRY_VERSION && typeof record.recordId === "string" && typeof record.source === "string" && typeof record.exe === "string" && Array.isArray(record.args) && typeof record.launchedAt === "number" && typeof record.bootId === "string";
|
|
2966
3098
|
}
|
|
2967
|
-
var REGISTRY_VERSION, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, EVENT_RETENTION_DAYS, ManagedInstanceRegistry;
|
|
3099
|
+
var REGISTRY_VERSION, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, EVENT_RETENTION_DAYS, TERMINAL_RECORD_RETENTION_MS, ManagedInstanceRegistry;
|
|
2968
3100
|
var init_managed_instance_registry = __esm({
|
|
2969
3101
|
"../core/dist/managed-instance-registry.js"() {
|
|
2970
3102
|
"use strict";
|
|
@@ -2973,6 +3105,7 @@ var init_managed_instance_registry = __esm({
|
|
|
2973
3105
|
LOCK_RETRY_MS = 25;
|
|
2974
3106
|
LOCK_TIMEOUT_MS = 5e3;
|
|
2975
3107
|
EVENT_RETENTION_DAYS = 2;
|
|
3108
|
+
TERMINAL_RECORD_RETENTION_MS = 24 * 60 * 60 * 1e3;
|
|
2976
3109
|
ManagedInstanceRegistry = class {
|
|
2977
3110
|
dir;
|
|
2978
3111
|
constructor(dir = defaultManagedInstanceRegistryDir()) {
|
|
@@ -3000,12 +3133,21 @@ var init_managed_instance_registry = __esm({
|
|
|
3000
3133
|
findAnyByInstanceId(instanceId) {
|
|
3001
3134
|
return this.withLock(() => this.readRecordsUnlocked().find((record) => record.instanceId === instanceId));
|
|
3002
3135
|
}
|
|
3136
|
+
findAnyByRecordId(recordId, options) {
|
|
3137
|
+
return this.withLock(() => {
|
|
3138
|
+
this.sweepUnlocked(options);
|
|
3139
|
+
return this.readRecordsUnlocked().find((record) => record.recordId === recordId);
|
|
3140
|
+
});
|
|
3141
|
+
}
|
|
3003
3142
|
listOpen(options) {
|
|
3004
3143
|
return this.withLock(() => {
|
|
3005
3144
|
this.sweepUnlocked(options);
|
|
3006
3145
|
return this.readOpenRecordsUnlocked();
|
|
3007
3146
|
});
|
|
3008
3147
|
}
|
|
3148
|
+
listOpenUnchecked() {
|
|
3149
|
+
return this.withLock(() => this.readOpenRecordsUnlocked());
|
|
3150
|
+
}
|
|
3009
3151
|
markClosed(recordId, closedAt = Date.now()) {
|
|
3010
3152
|
this.withLock(() => {
|
|
3011
3153
|
const record = this.readRecordUnlocked(recordId);
|
|
@@ -3177,41 +3319,52 @@ var init_managed_instance_registry = __esm({
|
|
|
3177
3319
|
}, now);
|
|
3178
3320
|
continue;
|
|
3179
3321
|
}
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3322
|
+
const terminalAt = parsed.closedAt ?? parsed.exitedAt;
|
|
3323
|
+
if (terminalAt !== void 0) {
|
|
3324
|
+
if (now - terminalAt > TERMINAL_RECORD_RETENTION_MS) {
|
|
3325
|
+
fs.rmSync(file, { force: true });
|
|
3326
|
+
this.appendEventUnlocked({
|
|
3327
|
+
event: "registry_pruned_terminal_record",
|
|
3328
|
+
recordId: parsed.recordId,
|
|
3329
|
+
instanceId: parsed.instanceId,
|
|
3330
|
+
source: parsed.source,
|
|
3331
|
+
reason: "terminal_retention_expired",
|
|
3332
|
+
action: "deleted_record"
|
|
3333
|
+
}, now);
|
|
3334
|
+
}
|
|
3190
3335
|
continue;
|
|
3191
3336
|
}
|
|
3192
3337
|
if (parsed.bootId !== options.currentBootId) {
|
|
3193
3338
|
this.cleanupRecord(options, parsed);
|
|
3194
|
-
|
|
3339
|
+
parsed.state = parsed.state === "failed" ? "failed" : "exited";
|
|
3340
|
+
parsed.exitedAt = now;
|
|
3341
|
+
parsed.closedAt = now;
|
|
3342
|
+
parsed.failureReason ??= "Studio process belongs to a previous host boot.";
|
|
3343
|
+
this.writeRecordUnlocked(parsed);
|
|
3195
3344
|
this.appendEventUnlocked({
|
|
3196
|
-
event: "
|
|
3345
|
+
event: "registry_marked_previous_boot_exited",
|
|
3197
3346
|
recordId: parsed.recordId,
|
|
3198
3347
|
instanceId: parsed.instanceId,
|
|
3199
3348
|
source: parsed.source,
|
|
3200
3349
|
reason: "boot_id_changed",
|
|
3201
|
-
action: "
|
|
3350
|
+
action: "marked_exited_and_cleaned_baseplate"
|
|
3202
3351
|
}, now);
|
|
3203
3352
|
continue;
|
|
3204
3353
|
}
|
|
3205
3354
|
if (options.isProcessRunning && (parsed.nativeProcessId || parsed.spawnPid) && !options.isProcessRunning(parsed)) {
|
|
3206
3355
|
this.cleanupRecord(options, parsed);
|
|
3207
|
-
|
|
3356
|
+
parsed.state = parsed.state === "failed" ? "failed" : "exited";
|
|
3357
|
+
parsed.exitedAt = now;
|
|
3358
|
+
parsed.closedAt = now;
|
|
3359
|
+
parsed.failureReason ??= parsed.instanceId ? "Studio process exited." : "Studio process exited before the MCP plugin connected.";
|
|
3360
|
+
this.writeRecordUnlocked(parsed);
|
|
3208
3361
|
this.appendEventUnlocked({
|
|
3209
|
-
event: "
|
|
3362
|
+
event: "registry_marked_process_exited",
|
|
3210
3363
|
recordId: parsed.recordId,
|
|
3211
3364
|
instanceId: parsed.instanceId,
|
|
3212
3365
|
source: parsed.source,
|
|
3213
3366
|
reason: "pid_not_running",
|
|
3214
|
-
action: "
|
|
3367
|
+
action: "marked_exited_and_cleaned_baseplate"
|
|
3215
3368
|
}, now);
|
|
3216
3369
|
}
|
|
3217
3370
|
}
|
|
@@ -3270,6 +3423,63 @@ function toStudioLaunchArg(arg) {
|
|
|
3270
3423
|
return arg;
|
|
3271
3424
|
return run("wslpath", ["-w", arg]);
|
|
3272
3425
|
}
|
|
3426
|
+
function powershellStringLiteral(value) {
|
|
3427
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
3428
|
+
}
|
|
3429
|
+
function quoteWindowsCommandLineArg(value) {
|
|
3430
|
+
if (value.length > 0 && !/[\s"]/u.test(value))
|
|
3431
|
+
return value;
|
|
3432
|
+
let quoted = '"';
|
|
3433
|
+
let backslashes = 0;
|
|
3434
|
+
for (const char of value) {
|
|
3435
|
+
if (char === "\\") {
|
|
3436
|
+
backslashes += 1;
|
|
3437
|
+
continue;
|
|
3438
|
+
}
|
|
3439
|
+
if (char === '"') {
|
|
3440
|
+
quoted += "\\".repeat(backslashes * 2 + 1) + '"';
|
|
3441
|
+
backslashes = 0;
|
|
3442
|
+
continue;
|
|
3443
|
+
}
|
|
3444
|
+
quoted += "\\".repeat(backslashes) + char;
|
|
3445
|
+
backslashes = 0;
|
|
3446
|
+
}
|
|
3447
|
+
return `${quoted}${"\\".repeat(backslashes * 2)}"`;
|
|
3448
|
+
}
|
|
3449
|
+
function buildWindowsStudioStartScript(exe, args) {
|
|
3450
|
+
const windowsExe = toStudioLaunchArg(exe);
|
|
3451
|
+
const commandLine = args.map(quoteWindowsCommandLineArg).join(" ");
|
|
3452
|
+
return [
|
|
3453
|
+
"$psi = New-Object System.Diagnostics.ProcessStartInfo",
|
|
3454
|
+
`$psi.FileName = ${powershellStringLiteral(windowsExe)}`,
|
|
3455
|
+
`$psi.Arguments = ${powershellStringLiteral(commandLine)}`,
|
|
3456
|
+
// With UseShellExecute=false, Studio inherits the synchronous
|
|
3457
|
+
// powershell.exe invocation's stdout/stderr pipe handles under WSL. Those
|
|
3458
|
+
// handles keep execFileSync waiting until Studio exits even though
|
|
3459
|
+
// PowerShell already printed the PID. Shell execution prevents that
|
|
3460
|
+
// inheritance while Process.Start still returns the native Studio PID.
|
|
3461
|
+
"$psi.UseShellExecute = $true",
|
|
3462
|
+
"$studio = [System.Diagnostics.Process]::Start($psi)",
|
|
3463
|
+
'if ($null -eq $studio) { throw "Roblox Studio process did not start." }'
|
|
3464
|
+
].join("; ");
|
|
3465
|
+
}
|
|
3466
|
+
function spawnWindowsStudioFromWsl(exe, args) {
|
|
3467
|
+
const script = buildWindowsStudioStartScript(exe, args);
|
|
3468
|
+
const output = powershell(`${script}; [PSCustomObject]@{ pid = $studio.Id; started = $studio.StartTime.ToUniversalTime().ToFileTimeUtc().ToString() } | ConvertTo-Json -Compress`);
|
|
3469
|
+
const parsed = JSON.parse(output);
|
|
3470
|
+
const nativePid = Number(parsed.pid);
|
|
3471
|
+
const nativeStartedAt = typeof parsed.started === "string" && /^\d+$/u.test(parsed.started) ? parsed.started : void 0;
|
|
3472
|
+
if (!Number.isSafeInteger(nativePid) || nativePid <= 0) {
|
|
3473
|
+
throw new Error(`Could not determine the Windows Studio process id from: ${nativePid}`);
|
|
3474
|
+
}
|
|
3475
|
+
return {
|
|
3476
|
+
pid: nativePid,
|
|
3477
|
+
nativePid,
|
|
3478
|
+
nativeStartedAt,
|
|
3479
|
+
unref: () => {
|
|
3480
|
+
}
|
|
3481
|
+
};
|
|
3482
|
+
}
|
|
3273
3483
|
function resolveEntrypointDir() {
|
|
3274
3484
|
const entrypoint = process.argv[1];
|
|
3275
3485
|
if (!entrypoint)
|
|
@@ -3301,8 +3511,8 @@ function isProcessAlive(pid) {
|
|
|
3301
3511
|
try {
|
|
3302
3512
|
process.kill(pid, 0);
|
|
3303
3513
|
return true;
|
|
3304
|
-
} catch (
|
|
3305
|
-
return
|
|
3514
|
+
} catch (err2) {
|
|
3515
|
+
return err2.code === "EPERM";
|
|
3306
3516
|
}
|
|
3307
3517
|
}
|
|
3308
3518
|
function sweepStaleBaseplateFiles() {
|
|
@@ -3395,7 +3605,7 @@ function listStudioProcesses() {
|
|
|
3395
3605
|
return [];
|
|
3396
3606
|
let out = "";
|
|
3397
3607
|
try {
|
|
3398
|
-
out = powershell("Get-Process RobloxStudioBeta -ErrorAction SilentlyContinue |
|
|
3608
|
+
out = powershell("Get-Process RobloxStudioBeta -ErrorAction SilentlyContinue | ForEach-Object { [PSCustomObject]@{ Id = $_.Id; Name = $_.Name; Path = $_.Path; MainWindowTitle = $_.MainWindowTitle; StartTimeUtcFileTime = $_.StartTime.ToUniversalTime().ToFileTimeUtc().ToString() } } | ConvertTo-Json -Compress");
|
|
3399
3609
|
} catch {
|
|
3400
3610
|
return [];
|
|
3401
3611
|
}
|
|
@@ -3459,7 +3669,7 @@ function buildStudioLaunchArgs(options) {
|
|
|
3459
3669
|
}
|
|
3460
3670
|
}
|
|
3461
3671
|
function delay(ms) {
|
|
3462
|
-
return new Promise((
|
|
3672
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
3463
3673
|
}
|
|
3464
3674
|
function basenameAny(filePath) {
|
|
3465
3675
|
return path2.basename(filePath.replace(/\\/g, "/"));
|
|
@@ -3477,6 +3687,8 @@ var init_studio_instance_manager = __esm({
|
|
|
3477
3687
|
StudioInstanceManager = class {
|
|
3478
3688
|
managedByInstanceId = /* @__PURE__ */ new Map();
|
|
3479
3689
|
pending = /* @__PURE__ */ new Set();
|
|
3690
|
+
monitors = /* @__PURE__ */ new Map();
|
|
3691
|
+
connectionTimers = /* @__PURE__ */ new Map();
|
|
3480
3692
|
registry;
|
|
3481
3693
|
processAdapter;
|
|
3482
3694
|
constructor(options = {}) {
|
|
@@ -3485,6 +3697,9 @@ var init_studio_instance_manager = __esm({
|
|
|
3485
3697
|
}
|
|
3486
3698
|
list() {
|
|
3487
3699
|
this.sweepRegistry();
|
|
3700
|
+
for (const record of [...this.managedByInstanceId.values(), ...this.pending]) {
|
|
3701
|
+
this.refresh(record);
|
|
3702
|
+
}
|
|
3488
3703
|
const records = [...this.managedByInstanceId.values(), ...this.pending];
|
|
3489
3704
|
for (const registryRecord of this.registry.listOpen(this.registrySweepOptions())) {
|
|
3490
3705
|
const record = this.fromRegistryRecord(registryRecord);
|
|
@@ -3493,27 +3708,82 @@ var init_studio_instance_manager = __esm({
|
|
|
3493
3708
|
}
|
|
3494
3709
|
records.push(record);
|
|
3495
3710
|
}
|
|
3496
|
-
return records.filter((instance, index, all) => all.indexOf(instance) === index);
|
|
3711
|
+
return records.filter((record) => record.closedAt === void 0).filter((instance, index, all) => all.indexOf(instance) === index);
|
|
3497
3712
|
}
|
|
3498
3713
|
get(instanceId) {
|
|
3499
3714
|
this.sweepRegistry();
|
|
3500
3715
|
const memoryRecord = this.managedByInstanceId.get(instanceId);
|
|
3501
3716
|
if (memoryRecord)
|
|
3502
|
-
return memoryRecord;
|
|
3503
|
-
const registryRecord = this.registry.
|
|
3504
|
-
return registryRecord ? this.fromRegistryRecord(registryRecord) : void 0;
|
|
3717
|
+
return this.refresh(memoryRecord);
|
|
3718
|
+
const registryRecord = this.registry.findAnyByInstanceId(instanceId);
|
|
3719
|
+
return registryRecord ? this.refresh(this.fromRegistryRecord(registryRecord)) : void 0;
|
|
3720
|
+
}
|
|
3721
|
+
getByLaunchId(launchId) {
|
|
3722
|
+
this.sweepRegistry();
|
|
3723
|
+
const memoryRecord = [...this.managedByInstanceId.values(), ...this.pending].find((record) => record.recordId === launchId);
|
|
3724
|
+
if (memoryRecord)
|
|
3725
|
+
return this.refresh(memoryRecord);
|
|
3726
|
+
const registryRecord = this.registry.findAnyByRecordId(launchId, this.registrySweepOptions());
|
|
3727
|
+
return registryRecord ? this.refresh(this.fromRegistryRecord(registryRecord)) : void 0;
|
|
3728
|
+
}
|
|
3729
|
+
pendingLaunches() {
|
|
3730
|
+
const now = Date.now();
|
|
3731
|
+
const records = [...this.pending];
|
|
3732
|
+
for (const registryRecord of this.registry.listOpenUnchecked()) {
|
|
3733
|
+
if (registryRecord.bootId !== this.getCurrentBootId())
|
|
3734
|
+
continue;
|
|
3735
|
+
if (records.some((record) => record.recordId === registryRecord.recordId))
|
|
3736
|
+
continue;
|
|
3737
|
+
records.push(this.fromRegistryRecord(registryRecord));
|
|
3738
|
+
}
|
|
3739
|
+
return records.filter((record) => record.instanceId === void 0).filter((record) => record.state === "launching").filter((record) => record.connectionDeadlineAt === void 0 || record.connectionDeadlineAt > now);
|
|
3505
3740
|
}
|
|
3506
3741
|
attachInstanceId(record, instanceId) {
|
|
3742
|
+
this.refresh(record);
|
|
3743
|
+
if (record.closedAt !== void 0 || record.state === "failed" || record.state === "exited")
|
|
3744
|
+
return;
|
|
3745
|
+
if (record.instanceId && record.instanceId !== instanceId)
|
|
3746
|
+
return;
|
|
3507
3747
|
record.instanceId = instanceId;
|
|
3748
|
+
record.state = "connected";
|
|
3749
|
+
record.connectedAt = record.connectedAt ?? Date.now();
|
|
3750
|
+
this.clearConnectionTimer(record);
|
|
3508
3751
|
this.pending.delete(record);
|
|
3509
3752
|
this.managedByInstanceId.set(instanceId, record);
|
|
3510
|
-
|
|
3511
|
-
|
|
3753
|
+
this.persist(record);
|
|
3754
|
+
}
|
|
3755
|
+
markFailed(record, reason) {
|
|
3756
|
+
this.refresh(record);
|
|
3757
|
+
if (record.closedAt !== void 0 || record.state !== "launching")
|
|
3758
|
+
return record;
|
|
3759
|
+
record.state = "failed";
|
|
3760
|
+
record.failedAt = Date.now();
|
|
3761
|
+
record.failureReason = reason;
|
|
3762
|
+
this.clearConnectionTimer(record);
|
|
3763
|
+
this.persist(record);
|
|
3764
|
+
return record;
|
|
3765
|
+
}
|
|
3766
|
+
refresh(record) {
|
|
3767
|
+
if (record.closedAt !== void 0)
|
|
3768
|
+
return record;
|
|
3769
|
+
const processId = record.nativeProcessId ?? record.spawnPid;
|
|
3770
|
+
const studioProcess = processId ? this.findProcessById(processId) : void 0;
|
|
3771
|
+
if (processId && (!studioProcess || !this.verifyProcessForRecord(record, studioProcess))) {
|
|
3772
|
+
return this.markProcessExited(record, void 0, studioProcess ? "Studio process identity changed; the retained PID was not reused." : record.instanceId ? "Studio process exited." : "Studio process exited before the MCP plugin connected.");
|
|
3512
3773
|
}
|
|
3774
|
+
if (record.state === "launching" && record.connectionDeadlineAt !== void 0 && Date.now() >= record.connectionDeadlineAt) {
|
|
3775
|
+
record.state = "failed";
|
|
3776
|
+
record.failedAt = Date.now();
|
|
3777
|
+
record.failureReason = "Studio launched, but the MCP plugin did not connect before timeout.";
|
|
3778
|
+
this.clearConnectionTimer(record);
|
|
3779
|
+
this.persist(record);
|
|
3780
|
+
}
|
|
3781
|
+
return record;
|
|
3513
3782
|
}
|
|
3514
3783
|
async launch(options) {
|
|
3515
3784
|
this.sweepRegistry();
|
|
3516
3785
|
const preparedOptions = prepareStudioLaunchOptions(options);
|
|
3786
|
+
const bootId = this.getCurrentBootId();
|
|
3517
3787
|
const before = new Set(this.listStudioProcesses().map((proc2) => proc2.Id));
|
|
3518
3788
|
const exe = this.processAdapter.resolveStudioExe?.() ?? resolveStudioExe();
|
|
3519
3789
|
const args = buildStudioLaunchArgs(preparedOptions).map(toStudioLaunchArg);
|
|
@@ -3522,11 +3792,35 @@ var init_studio_instance_manager = __esm({
|
|
|
3522
3792
|
detached: true,
|
|
3523
3793
|
stdio: "ignore"
|
|
3524
3794
|
};
|
|
3525
|
-
|
|
3526
|
-
|
|
3795
|
+
let proc;
|
|
3796
|
+
try {
|
|
3797
|
+
if (this.processAdapter.spawnStudio) {
|
|
3798
|
+
proc = this.processAdapter.spawnStudio(exe, args, spawnOptions);
|
|
3799
|
+
} else if (isWsl()) {
|
|
3800
|
+
proc = spawnWindowsStudioFromWsl(exe, args);
|
|
3801
|
+
} else {
|
|
3802
|
+
const child = spawn(exe, args, spawnOptions);
|
|
3803
|
+
proc = {
|
|
3804
|
+
pid: child.pid,
|
|
3805
|
+
nativePid: child.pid,
|
|
3806
|
+
unref: () => child.unref(),
|
|
3807
|
+
onExit: (listener) => {
|
|
3808
|
+
child.once("exit", listener);
|
|
3809
|
+
},
|
|
3810
|
+
onError: (listener) => {
|
|
3811
|
+
child.once("error", listener);
|
|
3812
|
+
}
|
|
3813
|
+
};
|
|
3814
|
+
}
|
|
3815
|
+
} catch (error) {
|
|
3816
|
+
cleanupManagedBaseplateFiles({ source: preparedOptions.source, localPlaceFile: preparedOptions.localPlaceFile });
|
|
3817
|
+
throw error;
|
|
3818
|
+
}
|
|
3527
3819
|
const record = {
|
|
3528
3820
|
recordId: randomUUID(),
|
|
3529
3821
|
source: options.source,
|
|
3822
|
+
nativeProcessId: proc.nativePid,
|
|
3823
|
+
nativeProcessStartedAt: proc.nativeStartedAt,
|
|
3530
3824
|
spawnPid: proc.pid,
|
|
3531
3825
|
exe,
|
|
3532
3826
|
args,
|
|
@@ -3535,29 +3829,74 @@ var init_studio_instance_manager = __esm({
|
|
|
3535
3829
|
placeVersion: preparedOptions.placeVersion,
|
|
3536
3830
|
localPlaceFile: preparedOptions.localPlaceFile,
|
|
3537
3831
|
launchedAt: Date.now(),
|
|
3832
|
+
connectionDeadlineAt: Date.now() + (options.connectionTimeoutMs ?? 12e4),
|
|
3833
|
+
state: "launching",
|
|
3538
3834
|
ownerPid: process.pid,
|
|
3539
|
-
bootId
|
|
3835
|
+
bootId,
|
|
3540
3836
|
deleteLocalPlaceFileOnClose: options.source === "baseplate"
|
|
3541
3837
|
};
|
|
3542
3838
|
this.pending.add(record);
|
|
3543
|
-
|
|
3839
|
+
try {
|
|
3840
|
+
this.persist(record);
|
|
3841
|
+
} catch (error) {
|
|
3842
|
+
this.pending.delete(record);
|
|
3843
|
+
const processId = record.nativeProcessId ?? record.spawnPid;
|
|
3844
|
+
let stopError;
|
|
3845
|
+
if (processId) {
|
|
3846
|
+
try {
|
|
3847
|
+
this.closeProcess(processId);
|
|
3848
|
+
} catch (caught) {
|
|
3849
|
+
stopError = caught;
|
|
3850
|
+
}
|
|
3851
|
+
}
|
|
3852
|
+
cleanupManagedBaseplateFiles(record);
|
|
3853
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
3854
|
+
const cleanupDetail = stopError ? ` The newly launched process could not be stopped: ${stopError instanceof Error ? stopError.message : String(stopError)}` : "";
|
|
3855
|
+
throw new Error(`Studio launched, but its managed-instance record could not be persisted: ${detail}.${cleanupDetail}`);
|
|
3856
|
+
}
|
|
3857
|
+
proc.unref();
|
|
3858
|
+
proc.onExit?.((code, signal) => {
|
|
3859
|
+
this.markProcessExited(record, code ?? void 0, signal ? `Studio process exited from signal ${signal}.` : record.instanceId ? "Studio process exited." : "Studio process exited before the MCP plugin connected.");
|
|
3860
|
+
});
|
|
3861
|
+
proc.onError?.((error) => {
|
|
3862
|
+
this.markFailed(record, `Studio process failed to start: ${error.message}`);
|
|
3863
|
+
});
|
|
3544
3864
|
const deadline = Date.now() + 5e3;
|
|
3545
3865
|
while (Date.now() < deadline && record.nativeProcessId === void 0) {
|
|
3546
3866
|
const created = this.listStudioProcesses().find((candidate) => !before.has(candidate.Id));
|
|
3547
3867
|
if (created) {
|
|
3548
3868
|
record.nativeProcessId = created.Id;
|
|
3549
|
-
|
|
3869
|
+
record.nativeProcessStartedAt = created.StartTimeUtcFileTime;
|
|
3870
|
+
this.persist(record);
|
|
3550
3871
|
break;
|
|
3551
3872
|
}
|
|
3552
3873
|
await delay(250);
|
|
3553
3874
|
}
|
|
3554
3875
|
if (record.nativeProcessId === void 0 && process.platform !== "win32" && !isWsl()) {
|
|
3555
3876
|
record.nativeProcessId = proc.pid;
|
|
3556
|
-
this.
|
|
3877
|
+
this.persist(record);
|
|
3557
3878
|
}
|
|
3879
|
+
if (record.nativeProcessId !== void 0 && record.nativeProcessStartedAt === void 0) {
|
|
3880
|
+
const nativeProcess = this.findProcessById(record.nativeProcessId);
|
|
3881
|
+
if (nativeProcess?.StartTimeUtcFileTime !== void 0) {
|
|
3882
|
+
record.nativeProcessStartedAt = nativeProcess.StartTimeUtcFileTime;
|
|
3883
|
+
this.persist(record);
|
|
3884
|
+
}
|
|
3885
|
+
}
|
|
3886
|
+
this.startMonitor(record);
|
|
3558
3887
|
return record;
|
|
3559
3888
|
}
|
|
3889
|
+
closeByLaunchId(launchId) {
|
|
3890
|
+
const record = this.getByLaunchId(launchId);
|
|
3891
|
+
if (!record)
|
|
3892
|
+
return { status: "not_found", launchId };
|
|
3893
|
+
if (record.closedAt !== void 0) {
|
|
3894
|
+
return { status: "already_closed", launchId, instanceId: record.instanceId };
|
|
3895
|
+
}
|
|
3896
|
+
return this.close(record);
|
|
3897
|
+
}
|
|
3560
3898
|
closeByInstanceId(instanceId) {
|
|
3899
|
+
this.sweepRegistry();
|
|
3561
3900
|
const memoryRecord = this.managedByInstanceId.get(instanceId);
|
|
3562
3901
|
if (memoryRecord)
|
|
3563
3902
|
return this.close(memoryRecord);
|
|
@@ -3568,33 +3907,24 @@ var init_studio_instance_manager = __esm({
|
|
|
3568
3907
|
}
|
|
3569
3908
|
if (registryRecord.closedAt !== void 0) {
|
|
3570
3909
|
this.cleanupManagedRecord(registryRecord);
|
|
3571
|
-
this.registry.delete(registryRecord.recordId);
|
|
3572
3910
|
this.registry.logEvent({
|
|
3573
3911
|
event: "registry_close_already_stopped",
|
|
3574
3912
|
recordId: registryRecord.recordId,
|
|
3575
3913
|
instanceId: registryRecord.instanceId,
|
|
3576
3914
|
source: registryRecord.source,
|
|
3577
3915
|
reason: "closed_at_present",
|
|
3578
|
-
action: "
|
|
3579
|
-
});
|
|
3580
|
-
return { status: "already_closed", instanceId };
|
|
3581
|
-
}
|
|
3582
|
-
if (registryRecord.bootId !== this.getCurrentBootId()) {
|
|
3583
|
-
this.cleanupManagedRecord(registryRecord);
|
|
3584
|
-
this.registry.delete(registryRecord.recordId);
|
|
3585
|
-
this.registry.logEvent({
|
|
3586
|
-
event: "registry_pruned_previous_boot",
|
|
3587
|
-
recordId: registryRecord.recordId,
|
|
3588
|
-
instanceId: registryRecord.instanceId,
|
|
3589
|
-
source: registryRecord.source,
|
|
3590
|
-
reason: "boot_id_changed",
|
|
3591
|
-
action: "deleted_record_and_cleaned_baseplate"
|
|
3916
|
+
action: "retained_terminal_record_and_cleaned_baseplate"
|
|
3592
3917
|
});
|
|
3593
|
-
return { status: "already_closed", instanceId };
|
|
3918
|
+
return { status: "already_closed", launchId: registryRecord.recordId, instanceId };
|
|
3594
3919
|
}
|
|
3595
3920
|
return this.close(this.fromRegistryRecord(registryRecord));
|
|
3596
3921
|
}
|
|
3597
3922
|
close(record) {
|
|
3923
|
+
this.stopMonitor(record);
|
|
3924
|
+
this.refresh(record);
|
|
3925
|
+
if (record.closedAt !== void 0) {
|
|
3926
|
+
return { status: "already_closed", launchId: record.recordId, instanceId: record.instanceId };
|
|
3927
|
+
}
|
|
3598
3928
|
const processId = record.nativeProcessId ?? record.spawnPid;
|
|
3599
3929
|
if (!processId) {
|
|
3600
3930
|
throw new Error(`Cannot close ${record.instanceId ?? "Studio launch"} because its process id was not detected.`);
|
|
@@ -3602,8 +3932,7 @@ var init_studio_instance_manager = __esm({
|
|
|
3602
3932
|
const studioProcess = this.findProcessById(processId);
|
|
3603
3933
|
if (!studioProcess) {
|
|
3604
3934
|
this.cleanupManagedRecord(record);
|
|
3605
|
-
this.
|
|
3606
|
-
this.markClosedInRegistry(record);
|
|
3935
|
+
this.markProcessExited(record, void 0, record.failureReason);
|
|
3607
3936
|
this.registry.logEvent({
|
|
3608
3937
|
event: "registry_close_already_stopped",
|
|
3609
3938
|
recordId: record.recordId,
|
|
@@ -3612,7 +3941,7 @@ var init_studio_instance_manager = __esm({
|
|
|
3612
3941
|
reason: "pid_not_running",
|
|
3613
3942
|
action: "marked_closed_and_cleaned_baseplate"
|
|
3614
3943
|
});
|
|
3615
|
-
return { status: "already_closed", instanceId: record.instanceId };
|
|
3944
|
+
return { status: "already_closed", launchId: record.recordId, instanceId: record.instanceId };
|
|
3616
3945
|
}
|
|
3617
3946
|
if (!this.verifyProcessForRecord(record, studioProcess)) {
|
|
3618
3947
|
this.registry.logEvent({
|
|
@@ -3638,15 +3967,18 @@ var init_studio_instance_manager = __esm({
|
|
|
3638
3967
|
action: "marked_closed_and_cleaned_baseplate"
|
|
3639
3968
|
});
|
|
3640
3969
|
this.cleanupManagedRecord(record);
|
|
3641
|
-
this.
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
record.closedAt =
|
|
3970
|
+
this.markProcessExited(record, void 0, record.failureReason);
|
|
3971
|
+
return { status: "already_closed", launchId: record.recordId, instanceId: record.instanceId };
|
|
3972
|
+
}
|
|
3973
|
+
const closedAt = Date.now();
|
|
3974
|
+
record.closedAt = closedAt;
|
|
3975
|
+
record.exitedAt = record.exitedAt ?? closedAt;
|
|
3976
|
+
if (record.state !== "failed")
|
|
3977
|
+
record.state = "exited";
|
|
3646
3978
|
this.cleanupManagedRecord(record);
|
|
3647
3979
|
this.markClosedInMemory(record);
|
|
3648
|
-
this.
|
|
3649
|
-
return { status: "closed", instanceId: record.instanceId };
|
|
3980
|
+
this.persist(record);
|
|
3981
|
+
return { status: "closed", launchId: record.recordId, instanceId: record.instanceId };
|
|
3650
3982
|
}
|
|
3651
3983
|
closeConnectedInstance(instance) {
|
|
3652
3984
|
const process2 = this.findProcessForConnectedInstance(instance);
|
|
@@ -3721,6 +4053,9 @@ var init_studio_instance_manager = __esm({
|
|
|
3721
4053
|
const processName = `${studioProcess.Name ?? ""} ${studioProcess.Path ?? ""}`.toLowerCase();
|
|
3722
4054
|
if (!processName.includes("robloxstudio"))
|
|
3723
4055
|
return false;
|
|
4056
|
+
if (record.nativeProcessStartedAt !== void 0 && studioProcess.StartTimeUtcFileTime !== record.nativeProcessStartedAt) {
|
|
4057
|
+
return false;
|
|
4058
|
+
}
|
|
3724
4059
|
const processId = record.nativeProcessId ?? record.spawnPid;
|
|
3725
4060
|
if (record.spawnPid && record.spawnPid === processId && studioProcess.Id === processId)
|
|
3726
4061
|
return true;
|
|
@@ -3746,10 +4081,64 @@ var init_studio_instance_manager = __esm({
|
|
|
3746
4081
|
if (record.instanceId)
|
|
3747
4082
|
this.managedByInstanceId.delete(record.instanceId);
|
|
3748
4083
|
this.pending.delete(record);
|
|
4084
|
+
this.stopMonitor(record);
|
|
4085
|
+
}
|
|
4086
|
+
markProcessExited(record, exitCode, reason) {
|
|
4087
|
+
if (record.closedAt !== void 0)
|
|
4088
|
+
return record;
|
|
4089
|
+
const exitedAt = Date.now();
|
|
4090
|
+
record.exitedAt = exitedAt;
|
|
4091
|
+
record.closedAt = exitedAt;
|
|
4092
|
+
if (record.state !== "failed")
|
|
4093
|
+
record.state = "exited";
|
|
4094
|
+
if (exitCode !== void 0)
|
|
4095
|
+
record.exitCode = exitCode;
|
|
4096
|
+
if (reason)
|
|
4097
|
+
record.failureReason = reason;
|
|
4098
|
+
this.cleanupManagedRecord(record);
|
|
4099
|
+
this.markClosedInMemory(record);
|
|
4100
|
+
this.persist(record);
|
|
4101
|
+
return record;
|
|
3749
4102
|
}
|
|
3750
|
-
|
|
3751
|
-
if (record.recordId)
|
|
3752
|
-
|
|
4103
|
+
startMonitor(record) {
|
|
4104
|
+
if (!record.recordId || record.closedAt !== void 0 || this.monitors.has(record.recordId))
|
|
4105
|
+
return;
|
|
4106
|
+
if (record.state === "launching" && record.connectionDeadlineAt !== void 0) {
|
|
4107
|
+
const timeout = setTimeout(() => {
|
|
4108
|
+
this.markFailed(record, "Studio launched, but the MCP plugin did not connect before timeout.");
|
|
4109
|
+
}, Math.max(0, record.connectionDeadlineAt - Date.now()));
|
|
4110
|
+
if (typeof timeout === "object" && "unref" in timeout)
|
|
4111
|
+
timeout.unref();
|
|
4112
|
+
this.connectionTimers.set(record.recordId, timeout);
|
|
4113
|
+
}
|
|
4114
|
+
const timer = setInterval(() => {
|
|
4115
|
+
this.refresh(record);
|
|
4116
|
+
if (record.closedAt !== void 0)
|
|
4117
|
+
this.stopMonitor(record);
|
|
4118
|
+
}, 5e3);
|
|
4119
|
+
if (typeof timer === "object" && "unref" in timer)
|
|
4120
|
+
timer.unref();
|
|
4121
|
+
this.monitors.set(record.recordId, timer);
|
|
4122
|
+
}
|
|
4123
|
+
stopMonitor(record) {
|
|
4124
|
+
if (!record.recordId)
|
|
4125
|
+
return;
|
|
4126
|
+
const timer = this.monitors.get(record.recordId);
|
|
4127
|
+
if (timer)
|
|
4128
|
+
clearInterval(timer);
|
|
4129
|
+
this.monitors.delete(record.recordId);
|
|
4130
|
+
this.clearConnectionTimer(record);
|
|
4131
|
+
}
|
|
4132
|
+
clearConnectionTimer(record) {
|
|
4133
|
+
if (!record.recordId)
|
|
4134
|
+
return;
|
|
4135
|
+
const timer = this.connectionTimers.get(record.recordId);
|
|
4136
|
+
if (timer)
|
|
4137
|
+
clearTimeout(timer);
|
|
4138
|
+
this.connectionTimers.delete(record.recordId);
|
|
4139
|
+
}
|
|
4140
|
+
persist(record) {
|
|
4141
|
+
this.registry.upsert(this.toRegistryRecord(record));
|
|
3753
4142
|
}
|
|
3754
4143
|
toRegistryRecord(record) {
|
|
3755
4144
|
if (!record.recordId)
|
|
@@ -3762,6 +4151,7 @@ var init_studio_instance_manager = __esm({
|
|
|
3762
4151
|
instanceId: record.instanceId,
|
|
3763
4152
|
source: record.source,
|
|
3764
4153
|
nativeProcessId: record.nativeProcessId,
|
|
4154
|
+
nativeProcessStartedAt: record.nativeProcessStartedAt,
|
|
3765
4155
|
spawnPid: record.spawnPid,
|
|
3766
4156
|
exe: record.exe,
|
|
3767
4157
|
args: record.args,
|
|
@@ -3771,18 +4161,26 @@ var init_studio_instance_manager = __esm({
|
|
|
3771
4161
|
localPlaceFile: record.localPlaceFile,
|
|
3772
4162
|
deleteLocalPlaceFileOnClose: record.deleteLocalPlaceFileOnClose,
|
|
3773
4163
|
launchedAt: record.launchedAt,
|
|
3774
|
-
attachedAt: record.
|
|
4164
|
+
attachedAt: record.connectedAt,
|
|
4165
|
+
connectionDeadlineAt: record.connectionDeadlineAt,
|
|
4166
|
+
state: record.state,
|
|
4167
|
+
failedAt: record.failedAt,
|
|
4168
|
+
exitedAt: record.exitedAt,
|
|
4169
|
+
exitCode: record.exitCode,
|
|
4170
|
+
failureReason: record.failureReason,
|
|
3775
4171
|
closedAt: record.closedAt,
|
|
3776
4172
|
ownerPid: record.ownerPid,
|
|
3777
4173
|
bootId: record.bootId
|
|
3778
4174
|
};
|
|
3779
4175
|
}
|
|
3780
4176
|
fromRegistryRecord(record) {
|
|
4177
|
+
const state = record.state ?? (record.closedAt !== void 0 ? "exited" : record.instanceId ? "connected" : "launching");
|
|
3781
4178
|
return {
|
|
3782
4179
|
recordId: record.recordId,
|
|
3783
4180
|
source: record.source,
|
|
3784
4181
|
instanceId: record.instanceId,
|
|
3785
4182
|
nativeProcessId: record.nativeProcessId,
|
|
4183
|
+
nativeProcessStartedAt: record.nativeProcessStartedAt,
|
|
3786
4184
|
spawnPid: record.spawnPid,
|
|
3787
4185
|
exe: record.exe,
|
|
3788
4186
|
args: record.args,
|
|
@@ -3791,6 +4189,13 @@ var init_studio_instance_manager = __esm({
|
|
|
3791
4189
|
placeVersion: record.placeVersion,
|
|
3792
4190
|
localPlaceFile: record.localPlaceFile,
|
|
3793
4191
|
launchedAt: record.launchedAt,
|
|
4192
|
+
connectionDeadlineAt: record.connectionDeadlineAt ?? (state === "launching" ? record.launchedAt + 12e4 : void 0),
|
|
4193
|
+
state,
|
|
4194
|
+
connectedAt: record.attachedAt,
|
|
4195
|
+
failedAt: record.failedAt,
|
|
4196
|
+
exitedAt: record.exitedAt,
|
|
4197
|
+
exitCode: record.exitCode,
|
|
4198
|
+
failureReason: record.failureReason,
|
|
3794
4199
|
closedAt: record.closedAt,
|
|
3795
4200
|
ownerPid: record.ownerPid,
|
|
3796
4201
|
bootId: record.bootId,
|
|
@@ -3976,6 +4381,982 @@ var init_image_decode = __esm({
|
|
|
3976
4381
|
}
|
|
3977
4382
|
});
|
|
3978
4383
|
|
|
4384
|
+
// ../../node_modules/fzstd/esm/index.mjs
|
|
4385
|
+
function decompress(dat, buf) {
|
|
4386
|
+
var bufs = [], nb = +!buf;
|
|
4387
|
+
var bt = 0, ol = 0;
|
|
4388
|
+
for (; dat.length; ) {
|
|
4389
|
+
var st = rzfh(dat, nb || buf);
|
|
4390
|
+
if (typeof st == "object") {
|
|
4391
|
+
if (nb) {
|
|
4392
|
+
buf = null;
|
|
4393
|
+
if (st.w.length == st.u) {
|
|
4394
|
+
bufs.push(buf = st.w);
|
|
4395
|
+
ol += st.u;
|
|
4396
|
+
}
|
|
4397
|
+
} else {
|
|
4398
|
+
bufs.push(buf);
|
|
4399
|
+
st.e = 0;
|
|
4400
|
+
}
|
|
4401
|
+
for (; !st.l; ) {
|
|
4402
|
+
var blk = rzb(dat, st, buf);
|
|
4403
|
+
if (!blk)
|
|
4404
|
+
err(5);
|
|
4405
|
+
if (buf)
|
|
4406
|
+
st.e = st.y;
|
|
4407
|
+
else {
|
|
4408
|
+
bufs.push(blk);
|
|
4409
|
+
ol += blk.length;
|
|
4410
|
+
cpw(st.w, 0, blk.length);
|
|
4411
|
+
st.w.set(blk, st.w.length - blk.length);
|
|
4412
|
+
}
|
|
4413
|
+
}
|
|
4414
|
+
bt = st.b + st.c * 4;
|
|
4415
|
+
} else
|
|
4416
|
+
bt = st;
|
|
4417
|
+
dat = dat.subarray(bt);
|
|
4418
|
+
}
|
|
4419
|
+
return cct(bufs, ol);
|
|
4420
|
+
}
|
|
4421
|
+
var ab, u8, u16, i16, i32, slc, fill, cpw, ec, err, rb, b4, rzfh, msb, rfse, rhu, dllt, dmlt, doct, b2bl, llb, llbl, mlb, mlbl, dhu, dhu4, rzb, cct;
|
|
4422
|
+
var init_esm = __esm({
|
|
4423
|
+
"../../node_modules/fzstd/esm/index.mjs"() {
|
|
4424
|
+
"use strict";
|
|
4425
|
+
ab = ArrayBuffer;
|
|
4426
|
+
u8 = Uint8Array;
|
|
4427
|
+
u16 = Uint16Array;
|
|
4428
|
+
i16 = Int16Array;
|
|
4429
|
+
i32 = Int32Array;
|
|
4430
|
+
slc = function(v, s, e) {
|
|
4431
|
+
if (u8.prototype.slice)
|
|
4432
|
+
return u8.prototype.slice.call(v, s, e);
|
|
4433
|
+
if (s == null || s < 0)
|
|
4434
|
+
s = 0;
|
|
4435
|
+
if (e == null || e > v.length)
|
|
4436
|
+
e = v.length;
|
|
4437
|
+
var n = new u8(e - s);
|
|
4438
|
+
n.set(v.subarray(s, e));
|
|
4439
|
+
return n;
|
|
4440
|
+
};
|
|
4441
|
+
fill = function(v, n, s, e) {
|
|
4442
|
+
if (u8.prototype.fill)
|
|
4443
|
+
return u8.prototype.fill.call(v, n, s, e);
|
|
4444
|
+
if (s == null || s < 0)
|
|
4445
|
+
s = 0;
|
|
4446
|
+
if (e == null || e > v.length)
|
|
4447
|
+
e = v.length;
|
|
4448
|
+
for (; s < e; ++s)
|
|
4449
|
+
v[s] = n;
|
|
4450
|
+
return v;
|
|
4451
|
+
};
|
|
4452
|
+
cpw = function(v, t, s, e) {
|
|
4453
|
+
if (u8.prototype.copyWithin)
|
|
4454
|
+
return u8.prototype.copyWithin.call(v, t, s, e);
|
|
4455
|
+
if (s == null || s < 0)
|
|
4456
|
+
s = 0;
|
|
4457
|
+
if (e == null || e > v.length)
|
|
4458
|
+
e = v.length;
|
|
4459
|
+
while (s < e) {
|
|
4460
|
+
v[t++] = v[s++];
|
|
4461
|
+
}
|
|
4462
|
+
};
|
|
4463
|
+
ec = [
|
|
4464
|
+
"invalid zstd data",
|
|
4465
|
+
"window size too large (>2046MB)",
|
|
4466
|
+
"invalid block type",
|
|
4467
|
+
"FSE accuracy too high",
|
|
4468
|
+
"match distance too far back",
|
|
4469
|
+
"unexpected EOF"
|
|
4470
|
+
];
|
|
4471
|
+
err = function(ind, msg, nt) {
|
|
4472
|
+
var e = new Error(msg || ec[ind]);
|
|
4473
|
+
e.code = ind;
|
|
4474
|
+
if (Error.captureStackTrace)
|
|
4475
|
+
Error.captureStackTrace(e, err);
|
|
4476
|
+
if (!nt)
|
|
4477
|
+
throw e;
|
|
4478
|
+
return e;
|
|
4479
|
+
};
|
|
4480
|
+
rb = function(d, b, n) {
|
|
4481
|
+
var i = 0, o = 0;
|
|
4482
|
+
for (; i < n; ++i)
|
|
4483
|
+
o |= d[b++] << (i << 3);
|
|
4484
|
+
return o;
|
|
4485
|
+
};
|
|
4486
|
+
b4 = function(d, b) {
|
|
4487
|
+
return (d[b] | d[b + 1] << 8 | d[b + 2] << 16 | d[b + 3] << 24) >>> 0;
|
|
4488
|
+
};
|
|
4489
|
+
rzfh = function(dat, w) {
|
|
4490
|
+
var n3 = dat[0] | dat[1] << 8 | dat[2] << 16;
|
|
4491
|
+
if (n3 == 3126568 && dat[3] == 253) {
|
|
4492
|
+
var flg = dat[4];
|
|
4493
|
+
var ss = flg >> 5 & 1, cc = flg >> 2 & 1, df = flg & 3, fcf = flg >> 6;
|
|
4494
|
+
if (flg & 8)
|
|
4495
|
+
err(0);
|
|
4496
|
+
var bt = 6 - ss;
|
|
4497
|
+
var db = df == 3 ? 4 : df;
|
|
4498
|
+
var di = rb(dat, bt, db);
|
|
4499
|
+
bt += db;
|
|
4500
|
+
var fsb = fcf ? 1 << fcf : ss;
|
|
4501
|
+
var fss = rb(dat, bt, fsb) + (fcf == 1 && 256);
|
|
4502
|
+
var ws = fss;
|
|
4503
|
+
if (!ss) {
|
|
4504
|
+
var wb = 1 << 10 + (dat[5] >> 3);
|
|
4505
|
+
ws = wb + (wb >> 3) * (dat[5] & 7);
|
|
4506
|
+
}
|
|
4507
|
+
if (ws > 2145386496)
|
|
4508
|
+
err(1);
|
|
4509
|
+
var buf = new u8((w == 1 ? fss || ws : w ? 0 : ws) + 12);
|
|
4510
|
+
buf[0] = 1, buf[4] = 4, buf[8] = 8;
|
|
4511
|
+
return {
|
|
4512
|
+
b: bt + fsb,
|
|
4513
|
+
y: 0,
|
|
4514
|
+
l: 0,
|
|
4515
|
+
d: di,
|
|
4516
|
+
w: w && w != 1 ? w : buf.subarray(12),
|
|
4517
|
+
e: ws,
|
|
4518
|
+
o: new i32(buf.buffer, 0, 3),
|
|
4519
|
+
u: fss,
|
|
4520
|
+
c: cc,
|
|
4521
|
+
m: Math.min(131072, ws)
|
|
4522
|
+
};
|
|
4523
|
+
} else if ((n3 >> 4 | dat[3] << 20) == 25481893) {
|
|
4524
|
+
return b4(dat, 4) + 8;
|
|
4525
|
+
}
|
|
4526
|
+
err(0);
|
|
4527
|
+
};
|
|
4528
|
+
msb = function(val) {
|
|
4529
|
+
var bits = 0;
|
|
4530
|
+
for (; 1 << bits <= val; ++bits)
|
|
4531
|
+
;
|
|
4532
|
+
return bits - 1;
|
|
4533
|
+
};
|
|
4534
|
+
rfse = function(dat, bt, mal) {
|
|
4535
|
+
var tpos = (bt << 3) + 4;
|
|
4536
|
+
var al = (dat[bt] & 15) + 5;
|
|
4537
|
+
if (al > mal)
|
|
4538
|
+
err(3);
|
|
4539
|
+
var sz = 1 << al;
|
|
4540
|
+
var probs = sz, sym = -1, re = -1, i = -1, ht = sz;
|
|
4541
|
+
var buf = new ab(512 + (sz << 2));
|
|
4542
|
+
var freq = new i16(buf, 0, 256);
|
|
4543
|
+
var dstate = new u16(buf, 0, 256);
|
|
4544
|
+
var nstate = new u16(buf, 512, sz);
|
|
4545
|
+
var bb1 = 512 + (sz << 1);
|
|
4546
|
+
var syms = new u8(buf, bb1, sz);
|
|
4547
|
+
var nbits = new u8(buf, bb1 + sz);
|
|
4548
|
+
while (sym < 255 && probs > 0) {
|
|
4549
|
+
var bits = msb(probs + 1);
|
|
4550
|
+
var cbt = tpos >> 3;
|
|
4551
|
+
var msk = (1 << bits + 1) - 1;
|
|
4552
|
+
var val = (dat[cbt] | dat[cbt + 1] << 8 | dat[cbt + 2] << 16) >> (tpos & 7) & msk;
|
|
4553
|
+
var msk1fb = (1 << bits) - 1;
|
|
4554
|
+
var msv = msk - probs - 1;
|
|
4555
|
+
var sval = val & msk1fb;
|
|
4556
|
+
if (sval < msv)
|
|
4557
|
+
tpos += bits, val = sval;
|
|
4558
|
+
else {
|
|
4559
|
+
tpos += bits + 1;
|
|
4560
|
+
if (val > msk1fb)
|
|
4561
|
+
val -= msv;
|
|
4562
|
+
}
|
|
4563
|
+
freq[++sym] = --val;
|
|
4564
|
+
if (val == -1) {
|
|
4565
|
+
probs += val;
|
|
4566
|
+
syms[--ht] = sym;
|
|
4567
|
+
} else
|
|
4568
|
+
probs -= val;
|
|
4569
|
+
if (!val) {
|
|
4570
|
+
do {
|
|
4571
|
+
var rbt = tpos >> 3;
|
|
4572
|
+
re = (dat[rbt] | dat[rbt + 1] << 8) >> (tpos & 7) & 3;
|
|
4573
|
+
tpos += 2;
|
|
4574
|
+
sym += re;
|
|
4575
|
+
} while (re == 3);
|
|
4576
|
+
}
|
|
4577
|
+
}
|
|
4578
|
+
if (sym > 255 || probs)
|
|
4579
|
+
err(0);
|
|
4580
|
+
var sympos = 0;
|
|
4581
|
+
var sstep = (sz >> 1) + (sz >> 3) + 3;
|
|
4582
|
+
var smask = sz - 1;
|
|
4583
|
+
for (var s = 0; s <= sym; ++s) {
|
|
4584
|
+
var sf = freq[s];
|
|
4585
|
+
if (sf < 1) {
|
|
4586
|
+
dstate[s] = -sf;
|
|
4587
|
+
continue;
|
|
4588
|
+
}
|
|
4589
|
+
for (i = 0; i < sf; ++i) {
|
|
4590
|
+
syms[sympos] = s;
|
|
4591
|
+
do {
|
|
4592
|
+
sympos = sympos + sstep & smask;
|
|
4593
|
+
} while (sympos >= ht);
|
|
4594
|
+
}
|
|
4595
|
+
}
|
|
4596
|
+
if (sympos)
|
|
4597
|
+
err(0);
|
|
4598
|
+
for (i = 0; i < sz; ++i) {
|
|
4599
|
+
var ns = dstate[syms[i]]++;
|
|
4600
|
+
var nb = nbits[i] = al - msb(ns);
|
|
4601
|
+
nstate[i] = (ns << nb) - sz;
|
|
4602
|
+
}
|
|
4603
|
+
return [tpos + 7 >> 3, {
|
|
4604
|
+
b: al,
|
|
4605
|
+
s: syms,
|
|
4606
|
+
n: nbits,
|
|
4607
|
+
t: nstate
|
|
4608
|
+
}];
|
|
4609
|
+
};
|
|
4610
|
+
rhu = function(dat, bt) {
|
|
4611
|
+
var i = 0, wc = -1;
|
|
4612
|
+
var buf = new u8(292), hb = dat[bt];
|
|
4613
|
+
var hw = buf.subarray(0, 256);
|
|
4614
|
+
var rc = buf.subarray(256, 268);
|
|
4615
|
+
var ri = new u16(buf.buffer, 268);
|
|
4616
|
+
if (hb < 128) {
|
|
4617
|
+
var _a = rfse(dat, bt + 1, 6), ebt = _a[0], fdt = _a[1];
|
|
4618
|
+
bt += hb;
|
|
4619
|
+
var epos = ebt << 3;
|
|
4620
|
+
var lb = dat[bt];
|
|
4621
|
+
if (!lb)
|
|
4622
|
+
err(0);
|
|
4623
|
+
var st1 = 0, st2 = 0, btr1 = fdt.b, btr2 = btr1;
|
|
4624
|
+
var fpos = (++bt << 3) - 8 + msb(lb);
|
|
4625
|
+
for (; ; ) {
|
|
4626
|
+
fpos -= btr1;
|
|
4627
|
+
if (fpos < epos)
|
|
4628
|
+
break;
|
|
4629
|
+
var cbt = fpos >> 3;
|
|
4630
|
+
st1 += (dat[cbt] | dat[cbt + 1] << 8) >> (fpos & 7) & (1 << btr1) - 1;
|
|
4631
|
+
hw[++wc] = fdt.s[st1];
|
|
4632
|
+
fpos -= btr2;
|
|
4633
|
+
if (fpos < epos)
|
|
4634
|
+
break;
|
|
4635
|
+
cbt = fpos >> 3;
|
|
4636
|
+
st2 += (dat[cbt] | dat[cbt + 1] << 8) >> (fpos & 7) & (1 << btr2) - 1;
|
|
4637
|
+
hw[++wc] = fdt.s[st2];
|
|
4638
|
+
btr1 = fdt.n[st1];
|
|
4639
|
+
st1 = fdt.t[st1];
|
|
4640
|
+
btr2 = fdt.n[st2];
|
|
4641
|
+
st2 = fdt.t[st2];
|
|
4642
|
+
}
|
|
4643
|
+
if (++wc > 255)
|
|
4644
|
+
err(0);
|
|
4645
|
+
} else {
|
|
4646
|
+
wc = hb - 127;
|
|
4647
|
+
for (; i < wc; i += 2) {
|
|
4648
|
+
var byte = dat[++bt];
|
|
4649
|
+
hw[i] = byte >> 4;
|
|
4650
|
+
hw[i + 1] = byte & 15;
|
|
4651
|
+
}
|
|
4652
|
+
++bt;
|
|
4653
|
+
}
|
|
4654
|
+
var wes = 0;
|
|
4655
|
+
for (i = 0; i < wc; ++i) {
|
|
4656
|
+
var wt = hw[i];
|
|
4657
|
+
if (wt > 11)
|
|
4658
|
+
err(0);
|
|
4659
|
+
wes += wt && 1 << wt - 1;
|
|
4660
|
+
}
|
|
4661
|
+
var mb = msb(wes) + 1;
|
|
4662
|
+
var ts = 1 << mb;
|
|
4663
|
+
var rem = ts - wes;
|
|
4664
|
+
if (rem & rem - 1)
|
|
4665
|
+
err(0);
|
|
4666
|
+
hw[wc++] = msb(rem) + 1;
|
|
4667
|
+
for (i = 0; i < wc; ++i) {
|
|
4668
|
+
var wt = hw[i];
|
|
4669
|
+
++rc[hw[i] = wt && mb + 1 - wt];
|
|
4670
|
+
}
|
|
4671
|
+
var hbuf = new u8(ts << 1);
|
|
4672
|
+
var syms = hbuf.subarray(0, ts), nb = hbuf.subarray(ts);
|
|
4673
|
+
ri[mb] = 0;
|
|
4674
|
+
for (i = mb; i > 0; --i) {
|
|
4675
|
+
var pv = ri[i];
|
|
4676
|
+
fill(nb, i, pv, ri[i - 1] = pv + rc[i] * (1 << mb - i));
|
|
4677
|
+
}
|
|
4678
|
+
if (ri[0] != ts)
|
|
4679
|
+
err(0);
|
|
4680
|
+
for (i = 0; i < wc; ++i) {
|
|
4681
|
+
var bits = hw[i];
|
|
4682
|
+
if (bits) {
|
|
4683
|
+
var code = ri[bits];
|
|
4684
|
+
fill(syms, i, code, ri[bits] = code + (1 << mb - bits));
|
|
4685
|
+
}
|
|
4686
|
+
}
|
|
4687
|
+
return [bt, {
|
|
4688
|
+
n: nb,
|
|
4689
|
+
b: mb,
|
|
4690
|
+
s: syms
|
|
4691
|
+
}];
|
|
4692
|
+
};
|
|
4693
|
+
dllt = rfse(/* @__PURE__ */ new u8([
|
|
4694
|
+
81,
|
|
4695
|
+
16,
|
|
4696
|
+
99,
|
|
4697
|
+
140,
|
|
4698
|
+
49,
|
|
4699
|
+
198,
|
|
4700
|
+
24,
|
|
4701
|
+
99,
|
|
4702
|
+
12,
|
|
4703
|
+
33,
|
|
4704
|
+
196,
|
|
4705
|
+
24,
|
|
4706
|
+
99,
|
|
4707
|
+
102,
|
|
4708
|
+
102,
|
|
4709
|
+
134,
|
|
4710
|
+
70,
|
|
4711
|
+
146,
|
|
4712
|
+
4
|
|
4713
|
+
]), 0, 6)[1];
|
|
4714
|
+
dmlt = rfse(/* @__PURE__ */ new u8([
|
|
4715
|
+
33,
|
|
4716
|
+
20,
|
|
4717
|
+
196,
|
|
4718
|
+
24,
|
|
4719
|
+
99,
|
|
4720
|
+
140,
|
|
4721
|
+
33,
|
|
4722
|
+
132,
|
|
4723
|
+
16,
|
|
4724
|
+
66,
|
|
4725
|
+
8,
|
|
4726
|
+
33,
|
|
4727
|
+
132,
|
|
4728
|
+
16,
|
|
4729
|
+
66,
|
|
4730
|
+
8,
|
|
4731
|
+
33,
|
|
4732
|
+
68,
|
|
4733
|
+
68,
|
|
4734
|
+
68,
|
|
4735
|
+
68,
|
|
4736
|
+
68,
|
|
4737
|
+
68,
|
|
4738
|
+
68,
|
|
4739
|
+
68,
|
|
4740
|
+
36,
|
|
4741
|
+
9
|
|
4742
|
+
]), 0, 6)[1];
|
|
4743
|
+
doct = rfse(/* @__PURE__ */ new u8([
|
|
4744
|
+
32,
|
|
4745
|
+
132,
|
|
4746
|
+
16,
|
|
4747
|
+
66,
|
|
4748
|
+
102,
|
|
4749
|
+
70,
|
|
4750
|
+
68,
|
|
4751
|
+
68,
|
|
4752
|
+
68,
|
|
4753
|
+
68,
|
|
4754
|
+
36,
|
|
4755
|
+
73,
|
|
4756
|
+
2
|
|
4757
|
+
]), 0, 5)[1];
|
|
4758
|
+
b2bl = function(b, s) {
|
|
4759
|
+
var len = b.length, bl = new i32(len);
|
|
4760
|
+
for (var i = 0; i < len; ++i) {
|
|
4761
|
+
bl[i] = s;
|
|
4762
|
+
s += 1 << b[i];
|
|
4763
|
+
}
|
|
4764
|
+
return bl;
|
|
4765
|
+
};
|
|
4766
|
+
llb = /* @__PURE__ */ new u8((/* @__PURE__ */ new i32([
|
|
4767
|
+
0,
|
|
4768
|
+
0,
|
|
4769
|
+
0,
|
|
4770
|
+
0,
|
|
4771
|
+
16843009,
|
|
4772
|
+
50528770,
|
|
4773
|
+
134678020,
|
|
4774
|
+
202050057,
|
|
4775
|
+
269422093
|
|
4776
|
+
])).buffer, 0, 36);
|
|
4777
|
+
llbl = /* @__PURE__ */ b2bl(llb, 0);
|
|
4778
|
+
mlb = /* @__PURE__ */ new u8((/* @__PURE__ */ new i32([
|
|
4779
|
+
0,
|
|
4780
|
+
0,
|
|
4781
|
+
0,
|
|
4782
|
+
0,
|
|
4783
|
+
0,
|
|
4784
|
+
0,
|
|
4785
|
+
0,
|
|
4786
|
+
0,
|
|
4787
|
+
16843009,
|
|
4788
|
+
50528770,
|
|
4789
|
+
117769220,
|
|
4790
|
+
185207048,
|
|
4791
|
+
252579084,
|
|
4792
|
+
16
|
|
4793
|
+
])).buffer, 0, 53);
|
|
4794
|
+
mlbl = /* @__PURE__ */ b2bl(mlb, 3);
|
|
4795
|
+
dhu = function(dat, out, hu) {
|
|
4796
|
+
var len = dat.length, ss = out.length, lb = dat[len - 1], msk = (1 << hu.b) - 1, eb = -hu.b;
|
|
4797
|
+
if (!lb)
|
|
4798
|
+
err(0);
|
|
4799
|
+
var st = 0, btr = hu.b, pos = (len << 3) - 8 + msb(lb) - btr, i = -1;
|
|
4800
|
+
for (; pos > eb && i < ss; ) {
|
|
4801
|
+
var cbt = pos >> 3;
|
|
4802
|
+
var val = (dat[cbt] | dat[cbt + 1] << 8 | dat[cbt + 2] << 16) >> (pos & 7);
|
|
4803
|
+
st = (st << btr | val) & msk;
|
|
4804
|
+
out[++i] = hu.s[st];
|
|
4805
|
+
pos -= btr = hu.n[st];
|
|
4806
|
+
}
|
|
4807
|
+
if (pos != eb || i + 1 != ss)
|
|
4808
|
+
err(0);
|
|
4809
|
+
};
|
|
4810
|
+
dhu4 = function(dat, out, hu) {
|
|
4811
|
+
var bt = 6;
|
|
4812
|
+
var ss = out.length, sz1 = ss + 3 >> 2, sz2 = sz1 << 1, sz3 = sz1 + sz2;
|
|
4813
|
+
dhu(dat.subarray(bt, bt += dat[0] | dat[1] << 8), out.subarray(0, sz1), hu);
|
|
4814
|
+
dhu(dat.subarray(bt, bt += dat[2] | dat[3] << 8), out.subarray(sz1, sz2), hu);
|
|
4815
|
+
dhu(dat.subarray(bt, bt += dat[4] | dat[5] << 8), out.subarray(sz2, sz3), hu);
|
|
4816
|
+
dhu(dat.subarray(bt), out.subarray(sz3), hu);
|
|
4817
|
+
};
|
|
4818
|
+
rzb = function(dat, st, out) {
|
|
4819
|
+
var _a;
|
|
4820
|
+
var bt = st.b;
|
|
4821
|
+
var b0 = dat[bt], btype = b0 >> 1 & 3;
|
|
4822
|
+
st.l = b0 & 1;
|
|
4823
|
+
var sz = b0 >> 3 | dat[bt + 1] << 5 | dat[bt + 2] << 13;
|
|
4824
|
+
var ebt = (bt += 3) + sz;
|
|
4825
|
+
if (btype == 1) {
|
|
4826
|
+
if (bt >= dat.length)
|
|
4827
|
+
return;
|
|
4828
|
+
st.b = bt + 1;
|
|
4829
|
+
if (out) {
|
|
4830
|
+
fill(out, dat[bt], st.y, st.y += sz);
|
|
4831
|
+
return out;
|
|
4832
|
+
}
|
|
4833
|
+
return fill(new u8(sz), dat[bt]);
|
|
4834
|
+
}
|
|
4835
|
+
if (ebt > dat.length)
|
|
4836
|
+
return;
|
|
4837
|
+
if (btype == 0) {
|
|
4838
|
+
st.b = ebt;
|
|
4839
|
+
if (out) {
|
|
4840
|
+
out.set(dat.subarray(bt, ebt), st.y);
|
|
4841
|
+
st.y += sz;
|
|
4842
|
+
return out;
|
|
4843
|
+
}
|
|
4844
|
+
return slc(dat, bt, ebt);
|
|
4845
|
+
}
|
|
4846
|
+
if (btype == 2) {
|
|
4847
|
+
var b3 = dat[bt], lbt = b3 & 3, sf = b3 >> 2 & 3;
|
|
4848
|
+
var lss = b3 >> 4, lcs = 0, s4 = 0;
|
|
4849
|
+
if (lbt < 2) {
|
|
4850
|
+
if (sf & 1)
|
|
4851
|
+
lss |= dat[++bt] << 4 | (sf & 2 && dat[++bt] << 12);
|
|
4852
|
+
else
|
|
4853
|
+
lss = b3 >> 3;
|
|
4854
|
+
} else {
|
|
4855
|
+
s4 = sf;
|
|
4856
|
+
if (sf < 2)
|
|
4857
|
+
lss |= (dat[++bt] & 63) << 4, lcs = dat[bt] >> 6 | dat[++bt] << 2;
|
|
4858
|
+
else if (sf == 2)
|
|
4859
|
+
lss |= dat[++bt] << 4 | (dat[++bt] & 3) << 12, lcs = dat[bt] >> 2 | dat[++bt] << 6;
|
|
4860
|
+
else
|
|
4861
|
+
lss |= dat[++bt] << 4 | (dat[++bt] & 63) << 12, lcs = dat[bt] >> 6 | dat[++bt] << 2 | dat[++bt] << 10;
|
|
4862
|
+
}
|
|
4863
|
+
++bt;
|
|
4864
|
+
var buf = out ? out.subarray(st.y, st.y + st.m) : new u8(st.m);
|
|
4865
|
+
var spl = buf.length - lss;
|
|
4866
|
+
if (lbt == 0)
|
|
4867
|
+
buf.set(dat.subarray(bt, bt += lss), spl);
|
|
4868
|
+
else if (lbt == 1)
|
|
4869
|
+
fill(buf, dat[bt++], spl);
|
|
4870
|
+
else {
|
|
4871
|
+
var hu = st.h;
|
|
4872
|
+
if (lbt == 2) {
|
|
4873
|
+
var hud = rhu(dat, bt);
|
|
4874
|
+
lcs += bt - (bt = hud[0]);
|
|
4875
|
+
st.h = hu = hud[1];
|
|
4876
|
+
} else if (!hu)
|
|
4877
|
+
err(0);
|
|
4878
|
+
(s4 ? dhu4 : dhu)(dat.subarray(bt, bt += lcs), buf.subarray(spl), hu);
|
|
4879
|
+
}
|
|
4880
|
+
var ns = dat[bt++];
|
|
4881
|
+
if (ns) {
|
|
4882
|
+
if (ns == 255)
|
|
4883
|
+
ns = (dat[bt++] | dat[bt++] << 8) + 32512;
|
|
4884
|
+
else if (ns > 127)
|
|
4885
|
+
ns = ns - 128 << 8 | dat[bt++];
|
|
4886
|
+
var scm = dat[bt++];
|
|
4887
|
+
if (scm & 3)
|
|
4888
|
+
err(0);
|
|
4889
|
+
var dts = [dmlt, doct, dllt];
|
|
4890
|
+
for (var i = 2; i > -1; --i) {
|
|
4891
|
+
var md = scm >> (i << 1) + 2 & 3;
|
|
4892
|
+
if (md == 1) {
|
|
4893
|
+
var rbuf = new u8([0, 0, dat[bt++]]);
|
|
4894
|
+
dts[i] = {
|
|
4895
|
+
s: rbuf.subarray(2, 3),
|
|
4896
|
+
n: rbuf.subarray(0, 1),
|
|
4897
|
+
t: new u16(rbuf.buffer, 0, 1),
|
|
4898
|
+
b: 0
|
|
4899
|
+
};
|
|
4900
|
+
} else if (md == 2) {
|
|
4901
|
+
_a = rfse(dat, bt, 9 - (i & 1)), bt = _a[0], dts[i] = _a[1];
|
|
4902
|
+
} else if (md == 3) {
|
|
4903
|
+
if (!st.t)
|
|
4904
|
+
err(0);
|
|
4905
|
+
dts[i] = st.t[i];
|
|
4906
|
+
}
|
|
4907
|
+
}
|
|
4908
|
+
var _b = st.t = dts, mlt = _b[0], oct = _b[1], llt = _b[2];
|
|
4909
|
+
var lb = dat[ebt - 1];
|
|
4910
|
+
if (!lb)
|
|
4911
|
+
err(0);
|
|
4912
|
+
var spos = (ebt << 3) - 8 + msb(lb) - llt.b, cbt = spos >> 3, oubt = 0;
|
|
4913
|
+
var lst = (dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7) & (1 << llt.b) - 1;
|
|
4914
|
+
cbt = (spos -= oct.b) >> 3;
|
|
4915
|
+
var ost = (dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7) & (1 << oct.b) - 1;
|
|
4916
|
+
cbt = (spos -= mlt.b) >> 3;
|
|
4917
|
+
var mst = (dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7) & (1 << mlt.b) - 1;
|
|
4918
|
+
for (++ns; --ns; ) {
|
|
4919
|
+
var llc = llt.s[lst];
|
|
4920
|
+
var lbtr = llt.n[lst];
|
|
4921
|
+
var mlc = mlt.s[mst];
|
|
4922
|
+
var mbtr = mlt.n[mst];
|
|
4923
|
+
var ofc = oct.s[ost];
|
|
4924
|
+
var obtr = oct.n[ost];
|
|
4925
|
+
cbt = (spos -= ofc) >> 3;
|
|
4926
|
+
var ofp = 1 << ofc;
|
|
4927
|
+
var off = ofp + ((dat[cbt] | dat[cbt + 1] << 8 | dat[cbt + 2] << 16 | dat[cbt + 3] << 24) >>> (spos & 7) & ofp - 1);
|
|
4928
|
+
cbt = (spos -= mlb[mlc]) >> 3;
|
|
4929
|
+
var ml = mlbl[mlc] + ((dat[cbt] | dat[cbt + 1] << 8 | dat[cbt + 2] << 16) >> (spos & 7) & (1 << mlb[mlc]) - 1);
|
|
4930
|
+
cbt = (spos -= llb[llc]) >> 3;
|
|
4931
|
+
var ll = llbl[llc] + ((dat[cbt] | dat[cbt + 1] << 8 | dat[cbt + 2] << 16) >> (spos & 7) & (1 << llb[llc]) - 1);
|
|
4932
|
+
cbt = (spos -= lbtr) >> 3;
|
|
4933
|
+
lst = llt.t[lst] + ((dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7) & (1 << lbtr) - 1);
|
|
4934
|
+
cbt = (spos -= mbtr) >> 3;
|
|
4935
|
+
mst = mlt.t[mst] + ((dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7) & (1 << mbtr) - 1);
|
|
4936
|
+
cbt = (spos -= obtr) >> 3;
|
|
4937
|
+
ost = oct.t[ost] + ((dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7) & (1 << obtr) - 1);
|
|
4938
|
+
if (off > 3) {
|
|
4939
|
+
st.o[2] = st.o[1];
|
|
4940
|
+
st.o[1] = st.o[0];
|
|
4941
|
+
st.o[0] = off -= 3;
|
|
4942
|
+
} else {
|
|
4943
|
+
var idx = off - (ll != 0);
|
|
4944
|
+
if (idx) {
|
|
4945
|
+
off = idx == 3 ? st.o[0] - 1 : st.o[idx];
|
|
4946
|
+
if (idx > 1)
|
|
4947
|
+
st.o[2] = st.o[1];
|
|
4948
|
+
st.o[1] = st.o[0];
|
|
4949
|
+
st.o[0] = off;
|
|
4950
|
+
} else
|
|
4951
|
+
off = st.o[0];
|
|
4952
|
+
}
|
|
4953
|
+
for (var i = 0; i < ll; ++i) {
|
|
4954
|
+
buf[oubt + i] = buf[spl + i];
|
|
4955
|
+
}
|
|
4956
|
+
oubt += ll, spl += ll;
|
|
4957
|
+
var stin = oubt - off;
|
|
4958
|
+
if (stin < 0) {
|
|
4959
|
+
var len = -stin;
|
|
4960
|
+
var bs = st.e + stin;
|
|
4961
|
+
if (len > ml)
|
|
4962
|
+
len = ml;
|
|
4963
|
+
for (var i = 0; i < len; ++i) {
|
|
4964
|
+
buf[oubt + i] = st.w[bs + i];
|
|
4965
|
+
}
|
|
4966
|
+
oubt += len, ml -= len, stin = 0;
|
|
4967
|
+
}
|
|
4968
|
+
for (var i = 0; i < ml; ++i) {
|
|
4969
|
+
buf[oubt + i] = buf[stin + i];
|
|
4970
|
+
}
|
|
4971
|
+
oubt += ml;
|
|
4972
|
+
}
|
|
4973
|
+
if (oubt != spl) {
|
|
4974
|
+
while (spl < buf.length) {
|
|
4975
|
+
buf[oubt++] = buf[spl++];
|
|
4976
|
+
}
|
|
4977
|
+
} else
|
|
4978
|
+
oubt = buf.length;
|
|
4979
|
+
if (out)
|
|
4980
|
+
st.y += oubt;
|
|
4981
|
+
else
|
|
4982
|
+
buf = slc(buf, 0, oubt);
|
|
4983
|
+
} else if (out) {
|
|
4984
|
+
st.y += lss;
|
|
4985
|
+
if (spl) {
|
|
4986
|
+
for (var i = 0; i < lss; ++i) {
|
|
4987
|
+
buf[i] = buf[spl + i];
|
|
4988
|
+
}
|
|
4989
|
+
}
|
|
4990
|
+
} else if (spl)
|
|
4991
|
+
buf = slc(buf, spl);
|
|
4992
|
+
st.b = ebt;
|
|
4993
|
+
return buf;
|
|
4994
|
+
}
|
|
4995
|
+
err(2);
|
|
4996
|
+
};
|
|
4997
|
+
cct = function(bufs, ol) {
|
|
4998
|
+
if (bufs.length == 1)
|
|
4999
|
+
return bufs[0];
|
|
5000
|
+
var buf = new u8(ol);
|
|
5001
|
+
for (var i = 0, b = 0; i < bufs.length; ++i) {
|
|
5002
|
+
var chk = bufs[i];
|
|
5003
|
+
buf.set(chk, b);
|
|
5004
|
+
b += chk.length;
|
|
5005
|
+
}
|
|
5006
|
+
return buf;
|
|
5007
|
+
};
|
|
5008
|
+
}
|
|
5009
|
+
});
|
|
5010
|
+
|
|
5011
|
+
// ../core/dist/studio-skills.js
|
|
5012
|
+
import { createHash as createHash2 } from "crypto";
|
|
5013
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
|
|
5014
|
+
import * as path4 from "path";
|
|
5015
|
+
function decompressLz4Block(input, outputLength) {
|
|
5016
|
+
const output = Buffer.allocUnsafe(outputLength);
|
|
5017
|
+
let inputOffset = 0;
|
|
5018
|
+
let outputOffset = 0;
|
|
5019
|
+
const readExtendedLength = (initial) => {
|
|
5020
|
+
let length = initial;
|
|
5021
|
+
if (initial !== 15)
|
|
5022
|
+
return length;
|
|
5023
|
+
let next = 255;
|
|
5024
|
+
while (next === 255) {
|
|
5025
|
+
if (inputOffset >= input.length) {
|
|
5026
|
+
throw new Error("Invalid LZ4 chunk: truncated extended length");
|
|
5027
|
+
}
|
|
5028
|
+
next = input[inputOffset++];
|
|
5029
|
+
length += next;
|
|
5030
|
+
}
|
|
5031
|
+
return length;
|
|
5032
|
+
};
|
|
5033
|
+
while (inputOffset < input.length) {
|
|
5034
|
+
const token = input[inputOffset++];
|
|
5035
|
+
const literalLength = readExtendedLength(token >>> 4);
|
|
5036
|
+
if (inputOffset + literalLength > input.length || outputOffset + literalLength > output.length) {
|
|
5037
|
+
throw new Error("Invalid LZ4 chunk: literal exceeds chunk boundary");
|
|
5038
|
+
}
|
|
5039
|
+
input.copy(output, outputOffset, inputOffset, inputOffset + literalLength);
|
|
5040
|
+
inputOffset += literalLength;
|
|
5041
|
+
outputOffset += literalLength;
|
|
5042
|
+
if (inputOffset === input.length)
|
|
5043
|
+
break;
|
|
5044
|
+
if (inputOffset + 2 > input.length) {
|
|
5045
|
+
throw new Error("Invalid LZ4 chunk: missing match offset");
|
|
5046
|
+
}
|
|
5047
|
+
const matchOffset = input.readUInt16LE(inputOffset);
|
|
5048
|
+
inputOffset += 2;
|
|
5049
|
+
if (matchOffset === 0 || matchOffset > outputOffset) {
|
|
5050
|
+
throw new Error("Invalid LZ4 chunk: invalid match offset");
|
|
5051
|
+
}
|
|
5052
|
+
const matchLength = readExtendedLength(token & 15) + 4;
|
|
5053
|
+
if (outputOffset + matchLength > output.length) {
|
|
5054
|
+
throw new Error("Invalid LZ4 chunk: match exceeds output boundary");
|
|
5055
|
+
}
|
|
5056
|
+
for (let index = 0; index < matchLength; index += 1) {
|
|
5057
|
+
output[outputOffset] = output[outputOffset - matchOffset];
|
|
5058
|
+
outputOffset += 1;
|
|
5059
|
+
}
|
|
5060
|
+
}
|
|
5061
|
+
if (outputOffset !== output.length) {
|
|
5062
|
+
throw new Error(`Invalid LZ4 chunk: produced ${outputOffset} bytes, expected ${output.length}`);
|
|
5063
|
+
}
|
|
5064
|
+
return output;
|
|
5065
|
+
}
|
|
5066
|
+
function decompressChunk(compressed, outputLength) {
|
|
5067
|
+
if (compressed.subarray(0, ZSTD_MAGIC.length).equals(ZSTD_MAGIC)) {
|
|
5068
|
+
const decompressed = Buffer.from(decompress(compressed));
|
|
5069
|
+
if (decompressed.length !== outputLength) {
|
|
5070
|
+
throw new Error(`Invalid Zstandard chunk: produced ${decompressed.length} bytes, expected ${outputLength}`);
|
|
5071
|
+
}
|
|
5072
|
+
return decompressed;
|
|
5073
|
+
}
|
|
5074
|
+
return decompressLz4Block(compressed, outputLength);
|
|
5075
|
+
}
|
|
5076
|
+
function readChunk(reader) {
|
|
5077
|
+
if (reader.remaining < RBXM_CHUNK_HEADER_BYTES) {
|
|
5078
|
+
throw new Error("Invalid Assistant.rbxm: truncated chunk header");
|
|
5079
|
+
}
|
|
5080
|
+
const type = reader.readBytes(4).toString("latin1");
|
|
5081
|
+
const compressedLength = reader.readUint32();
|
|
5082
|
+
const uncompressedLength = reader.readUint32();
|
|
5083
|
+
reader.skip(4);
|
|
5084
|
+
if (type === "END\0")
|
|
5085
|
+
return { type };
|
|
5086
|
+
if (compressedLength === 0) {
|
|
5087
|
+
return { type, content: reader.readBytes(uncompressedLength) };
|
|
5088
|
+
}
|
|
5089
|
+
return {
|
|
5090
|
+
type,
|
|
5091
|
+
content: decompressChunk(reader.readBytes(compressedLength), uncompressedLength)
|
|
5092
|
+
};
|
|
5093
|
+
}
|
|
5094
|
+
function parseFrontmatter(markdown) {
|
|
5095
|
+
const match = markdown.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
|
|
5096
|
+
if (!match)
|
|
5097
|
+
return void 0;
|
|
5098
|
+
const fields = /* @__PURE__ */ new Map();
|
|
5099
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
5100
|
+
const separator = line.indexOf(":");
|
|
5101
|
+
if (separator <= 0)
|
|
5102
|
+
continue;
|
|
5103
|
+
const key = line.slice(0, separator).trim().toLowerCase();
|
|
5104
|
+
let value = line.slice(separator + 1).trim();
|
|
5105
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
5106
|
+
try {
|
|
5107
|
+
value = JSON.parse(value);
|
|
5108
|
+
} catch {
|
|
5109
|
+
}
|
|
5110
|
+
} else if (value.startsWith("'") && value.endsWith("'")) {
|
|
5111
|
+
value = value.slice(1, -1).replace(/''/g, "'");
|
|
5112
|
+
}
|
|
5113
|
+
fields.set(key, value);
|
|
5114
|
+
}
|
|
5115
|
+
const name = fields.get("name")?.trim();
|
|
5116
|
+
if (!name)
|
|
5117
|
+
return void 0;
|
|
5118
|
+
return { name, description: fields.get("description")?.trim() ?? "" };
|
|
5119
|
+
}
|
|
5120
|
+
function canonicalBuiltInSkillName(sourceName) {
|
|
5121
|
+
return sourceName.toLowerCase().startsWith("rbx-") ? sourceName : `rbx-${sourceName}`;
|
|
5122
|
+
}
|
|
5123
|
+
function parseBuiltInStudioSkills(buffer) {
|
|
5124
|
+
if (buffer.length < RBXM_HEADER_BYTES || !buffer.subarray(0, RBXM_MAGIC.length).equals(RBXM_MAGIC)) {
|
|
5125
|
+
throw new Error("Invalid Assistant.rbxm: Roblox binary model header not found");
|
|
5126
|
+
}
|
|
5127
|
+
const reader = new ByteReader(buffer, "Assistant.rbxm");
|
|
5128
|
+
reader.skip(RBXM_MAGIC.length);
|
|
5129
|
+
reader.readUint16();
|
|
5130
|
+
reader.readInt32();
|
|
5131
|
+
reader.readInt32();
|
|
5132
|
+
reader.skip(8);
|
|
5133
|
+
const stringValueClasses = /* @__PURE__ */ new Map();
|
|
5134
|
+
let foundEnd = false;
|
|
5135
|
+
while (reader.remaining > 0) {
|
|
5136
|
+
const chunk = readChunk(reader);
|
|
5137
|
+
if (chunk.type === "END\0") {
|
|
5138
|
+
foundEnd = true;
|
|
5139
|
+
break;
|
|
5140
|
+
}
|
|
5141
|
+
if (!chunk.content)
|
|
5142
|
+
continue;
|
|
5143
|
+
const chunkReader = new ByteReader(chunk.content, `${chunk.type} chunk`);
|
|
5144
|
+
if (chunk.type === "INST") {
|
|
5145
|
+
const classId2 = chunkReader.readUint32();
|
|
5146
|
+
const className = chunkReader.readString();
|
|
5147
|
+
chunkReader.readUint8();
|
|
5148
|
+
const count = chunkReader.readUint32();
|
|
5149
|
+
chunkReader.skip(count * 4);
|
|
5150
|
+
if (className === "StringValue")
|
|
5151
|
+
stringValueClasses.set(classId2, { count });
|
|
5152
|
+
continue;
|
|
5153
|
+
}
|
|
5154
|
+
if (chunk.type !== "PROP")
|
|
5155
|
+
continue;
|
|
5156
|
+
const classId = chunkReader.readUint32();
|
|
5157
|
+
const propertyName = chunkReader.readString();
|
|
5158
|
+
const propertyType = chunkReader.readUint8();
|
|
5159
|
+
const classInfo = stringValueClasses.get(classId);
|
|
5160
|
+
if (!classInfo || propertyType !== STRING_PROPERTY_TYPE)
|
|
5161
|
+
continue;
|
|
5162
|
+
if (propertyName !== "Name" && propertyName !== "Value")
|
|
5163
|
+
continue;
|
|
5164
|
+
const values = [];
|
|
5165
|
+
for (let index = 0; index < classInfo.count; index += 1) {
|
|
5166
|
+
values.push(chunkReader.readString());
|
|
5167
|
+
}
|
|
5168
|
+
if (propertyName === "Name")
|
|
5169
|
+
classInfo.names = values;
|
|
5170
|
+
else
|
|
5171
|
+
classInfo.values = values;
|
|
5172
|
+
}
|
|
5173
|
+
if (!foundEnd)
|
|
5174
|
+
throw new Error("Invalid Assistant.rbxm: END chunk not found");
|
|
5175
|
+
const documents = [];
|
|
5176
|
+
for (const classInfo of stringValueClasses.values()) {
|
|
5177
|
+
if (!classInfo.names || !classInfo.values)
|
|
5178
|
+
continue;
|
|
5179
|
+
for (let index = 0; index < classInfo.count; index += 1) {
|
|
5180
|
+
const document = classInfo.names[index];
|
|
5181
|
+
if (document !== "SKILL" && document !== "SKILL-combined")
|
|
5182
|
+
continue;
|
|
5183
|
+
const content = classInfo.values[index];
|
|
5184
|
+
const frontmatter = parseFrontmatter(content);
|
|
5185
|
+
if (!frontmatter)
|
|
5186
|
+
continue;
|
|
5187
|
+
documents.push({
|
|
5188
|
+
document,
|
|
5189
|
+
sourceName: frontmatter.name,
|
|
5190
|
+
description: frontmatter.description,
|
|
5191
|
+
content
|
|
5192
|
+
});
|
|
5193
|
+
}
|
|
5194
|
+
}
|
|
5195
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
5196
|
+
for (const document of documents) {
|
|
5197
|
+
const key = document.sourceName.toLowerCase();
|
|
5198
|
+
const group = grouped.get(key) ?? {};
|
|
5199
|
+
if (document.document === "SKILL-combined")
|
|
5200
|
+
group.combined = document;
|
|
5201
|
+
else
|
|
5202
|
+
group.base = document;
|
|
5203
|
+
grouped.set(key, group);
|
|
5204
|
+
}
|
|
5205
|
+
return [...grouped.values()].map((group) => {
|
|
5206
|
+
const selected = group.combined ?? group.base;
|
|
5207
|
+
return {
|
|
5208
|
+
name: canonicalBuiltInSkillName(selected.sourceName),
|
|
5209
|
+
sourceName: selected.sourceName,
|
|
5210
|
+
description: selected.description,
|
|
5211
|
+
document: selected.document,
|
|
5212
|
+
hasCombinedDocument: group.combined !== void 0,
|
|
5213
|
+
content: selected.content,
|
|
5214
|
+
contentLength: Buffer.byteLength(selected.content, "utf8"),
|
|
5215
|
+
contentSha256: createHash2("sha256").update(selected.content).digest("hex")
|
|
5216
|
+
};
|
|
5217
|
+
}).sort((left, right) => left.name.localeCompare(right.name));
|
|
5218
|
+
}
|
|
5219
|
+
function discoverNamedFile(root, fileName, depth = 0) {
|
|
5220
|
+
if (depth > MAX_DISCOVERY_DEPTH || !existsSync4(root))
|
|
5221
|
+
return [];
|
|
5222
|
+
const matches = [];
|
|
5223
|
+
let entries;
|
|
5224
|
+
try {
|
|
5225
|
+
entries = readdirSync3(root, { withFileTypes: true });
|
|
5226
|
+
} catch {
|
|
5227
|
+
return matches;
|
|
5228
|
+
}
|
|
5229
|
+
for (const entry of entries) {
|
|
5230
|
+
const entryPath = path4.join(root, entry.name);
|
|
5231
|
+
if (entry.isFile() && entry.name.toLowerCase() === fileName.toLowerCase()) {
|
|
5232
|
+
matches.push(entryPath);
|
|
5233
|
+
} else if (entry.isDirectory()) {
|
|
5234
|
+
matches.push(...discoverNamedFile(entryPath, fileName, depth + 1));
|
|
5235
|
+
}
|
|
5236
|
+
}
|
|
5237
|
+
return matches;
|
|
5238
|
+
}
|
|
5239
|
+
function resolveAssistantBundlePath(studioExe = resolveStudioExe()) {
|
|
5240
|
+
const override = process.env.ROBLOX_STUDIO_ASSISTANT_BUNDLE;
|
|
5241
|
+
if (override) {
|
|
5242
|
+
if (!existsSync4(override)) {
|
|
5243
|
+
throw new Error(`ROBLOX_STUDIO_ASSISTANT_BUNDLE does not exist: ${override}`);
|
|
5244
|
+
}
|
|
5245
|
+
return override;
|
|
5246
|
+
}
|
|
5247
|
+
const exeDirectory = path4.dirname(studioExe);
|
|
5248
|
+
const roots = [
|
|
5249
|
+
path4.join(exeDirectory, "BuiltInStandalonePlugins"),
|
|
5250
|
+
path4.resolve(exeDirectory, "..", "Resources", "BuiltInStandalonePlugins"),
|
|
5251
|
+
path4.resolve(exeDirectory, "..", "..", "OTAPlugins")
|
|
5252
|
+
];
|
|
5253
|
+
const direct = path4.join(exeDirectory, "BuiltInStandalonePlugins", "Optimized_Embedded_Signature", "Assistant.rbxm");
|
|
5254
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
5255
|
+
if (existsSync4(direct))
|
|
5256
|
+
candidates.add(direct);
|
|
5257
|
+
for (const root of roots) {
|
|
5258
|
+
for (const candidate of discoverNamedFile(root, "Assistant.rbxm"))
|
|
5259
|
+
candidates.add(candidate);
|
|
5260
|
+
}
|
|
5261
|
+
const newest = [...candidates].map((candidate) => ({ candidate, modifiedAt: statSync3(candidate).mtimeMs })).sort((left, right) => right.modifiedAt - left.modifiedAt)[0]?.candidate;
|
|
5262
|
+
if (!newest) {
|
|
5263
|
+
throw new Error(`Studio Assistant bundle not found for ${studioExe}. Set ROBLOX_STUDIO_ASSISTANT_BUNDLE to the installed Assistant.rbxm path.`);
|
|
5264
|
+
}
|
|
5265
|
+
return newest;
|
|
5266
|
+
}
|
|
5267
|
+
function loadBuiltInStudioSkills(bundlePath = resolveAssistantBundlePath()) {
|
|
5268
|
+
const stats = statSync3(bundlePath);
|
|
5269
|
+
if (cachedBundle?.path === bundlePath && cachedBundle.modifiedAt === stats.mtimeMs && cachedBundle.size === stats.size) {
|
|
5270
|
+
return cachedBundle.value;
|
|
5271
|
+
}
|
|
5272
|
+
const buffer = readFileSync5(bundlePath);
|
|
5273
|
+
const skills = parseBuiltInStudioSkills(buffer);
|
|
5274
|
+
if (skills.length === 0) {
|
|
5275
|
+
throw new Error(`No built-in skill documents found in ${bundlePath}`);
|
|
5276
|
+
}
|
|
5277
|
+
const studioVersion = bundlePath.split(path4.sep).find((segment) => /^version-[a-z0-9]+$/i.test(segment));
|
|
5278
|
+
const value = {
|
|
5279
|
+
bundlePath,
|
|
5280
|
+
bundleModifiedAt: stats.mtime.toISOString(),
|
|
5281
|
+
bundleSha256: createHash2("sha256").update(buffer).digest("hex"),
|
|
5282
|
+
studioVersion,
|
|
5283
|
+
skills
|
|
5284
|
+
};
|
|
5285
|
+
cachedBundle = { path: bundlePath, modifiedAt: stats.mtimeMs, size: stats.size, value };
|
|
5286
|
+
return value;
|
|
5287
|
+
}
|
|
5288
|
+
function findBuiltInStudioSkill(bundle, requestedName) {
|
|
5289
|
+
const wanted = requestedName.trim().toLowerCase();
|
|
5290
|
+
return bundle.skills.find((skill) => skill.name.toLowerCase() === wanted || skill.sourceName.toLowerCase() === wanted);
|
|
5291
|
+
}
|
|
5292
|
+
var RBXM_MAGIC, RBXM_HEADER_BYTES, RBXM_CHUNK_HEADER_BYTES, ZSTD_MAGIC, STRING_PROPERTY_TYPE, MAX_DISCOVERY_DEPTH, ByteReader, cachedBundle;
|
|
5293
|
+
var init_studio_skills = __esm({
|
|
5294
|
+
"../core/dist/studio-skills.js"() {
|
|
5295
|
+
"use strict";
|
|
5296
|
+
init_esm();
|
|
5297
|
+
init_studio_instance_manager();
|
|
5298
|
+
RBXM_MAGIC = Buffer.from("<roblox!\x89\xFF\r\n\n", "latin1");
|
|
5299
|
+
RBXM_HEADER_BYTES = 32;
|
|
5300
|
+
RBXM_CHUNK_HEADER_BYTES = 16;
|
|
5301
|
+
ZSTD_MAGIC = Buffer.from([40, 181, 47, 253]);
|
|
5302
|
+
STRING_PROPERTY_TYPE = 1;
|
|
5303
|
+
MAX_DISCOVERY_DEPTH = 6;
|
|
5304
|
+
ByteReader = class {
|
|
5305
|
+
bytes;
|
|
5306
|
+
label;
|
|
5307
|
+
offset = 0;
|
|
5308
|
+
constructor(bytes, label) {
|
|
5309
|
+
this.bytes = bytes;
|
|
5310
|
+
this.label = label;
|
|
5311
|
+
}
|
|
5312
|
+
get remaining() {
|
|
5313
|
+
return this.bytes.length - this.offset;
|
|
5314
|
+
}
|
|
5315
|
+
requireBytes(length) {
|
|
5316
|
+
if (!Number.isSafeInteger(length) || length < 0 || this.remaining < length) {
|
|
5317
|
+
throw new Error(`Invalid ${this.label}: requested ${length} bytes with ${this.remaining} remaining`);
|
|
5318
|
+
}
|
|
5319
|
+
}
|
|
5320
|
+
readUint8() {
|
|
5321
|
+
this.requireBytes(1);
|
|
5322
|
+
return this.bytes[this.offset++];
|
|
5323
|
+
}
|
|
5324
|
+
readUint16() {
|
|
5325
|
+
this.requireBytes(2);
|
|
5326
|
+
const value = this.bytes.readUInt16LE(this.offset);
|
|
5327
|
+
this.offset += 2;
|
|
5328
|
+
return value;
|
|
5329
|
+
}
|
|
5330
|
+
readUint32() {
|
|
5331
|
+
this.requireBytes(4);
|
|
5332
|
+
const value = this.bytes.readUInt32LE(this.offset);
|
|
5333
|
+
this.offset += 4;
|
|
5334
|
+
return value;
|
|
5335
|
+
}
|
|
5336
|
+
readInt32() {
|
|
5337
|
+
this.requireBytes(4);
|
|
5338
|
+
const value = this.bytes.readInt32LE(this.offset);
|
|
5339
|
+
this.offset += 4;
|
|
5340
|
+
return value;
|
|
5341
|
+
}
|
|
5342
|
+
readBytes(length) {
|
|
5343
|
+
this.requireBytes(length);
|
|
5344
|
+
const value = this.bytes.subarray(this.offset, this.offset + length);
|
|
5345
|
+
this.offset += length;
|
|
5346
|
+
return value;
|
|
5347
|
+
}
|
|
5348
|
+
skip(length) {
|
|
5349
|
+
this.requireBytes(length);
|
|
5350
|
+
this.offset += length;
|
|
5351
|
+
}
|
|
5352
|
+
readString() {
|
|
5353
|
+
const length = this.readUint32();
|
|
5354
|
+
return this.readBytes(length).toString("utf8");
|
|
5355
|
+
}
|
|
5356
|
+
};
|
|
5357
|
+
}
|
|
5358
|
+
});
|
|
5359
|
+
|
|
3979
5360
|
// ../core/dist/jpeg-encoder.js
|
|
3980
5361
|
function rgbaToJpeg(rgba, width, height, quality = 80) {
|
|
3981
5362
|
if (width <= 0 || height <= 0)
|
|
@@ -4995,7 +6376,7 @@ var init_png_encoder = __esm({
|
|
|
4995
6376
|
// ../core/dist/tools/index.js
|
|
4996
6377
|
import * as fs3 from "fs";
|
|
4997
6378
|
import * as os3 from "os";
|
|
4998
|
-
import * as
|
|
6379
|
+
import * as path5 from "path";
|
|
4999
6380
|
function multiplayerStopDisabledBody() {
|
|
5000
6381
|
return {
|
|
5001
6382
|
success: false,
|
|
@@ -5020,7 +6401,7 @@ function encodeImageFromRgbaResponse(response, format, quality) {
|
|
|
5020
6401
|
};
|
|
5021
6402
|
}
|
|
5022
6403
|
function sleep(ms) {
|
|
5023
|
-
return new Promise((
|
|
6404
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
5024
6405
|
}
|
|
5025
6406
|
function errorMessage(error) {
|
|
5026
6407
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -5082,7 +6463,7 @@ function loadMicroProfilerBaseline(source, sourcePath) {
|
|
|
5082
6463
|
if (typeof sourcePath !== "string" || sourcePath === "") {
|
|
5083
6464
|
throw new Error("baseline_path must be a non-empty string when provided");
|
|
5084
6465
|
}
|
|
5085
|
-
const resolved =
|
|
6466
|
+
const resolved = path5.resolve(sourcePath);
|
|
5086
6467
|
const parsed = JSON.parse(fs3.readFileSync(resolved, "utf8"));
|
|
5087
6468
|
const record = asRecord(parsed);
|
|
5088
6469
|
if (!record)
|
|
@@ -5543,6 +6924,7 @@ var init_tools = __esm({
|
|
|
5543
6924
|
init_studio_instance_manager();
|
|
5544
6925
|
init_image_decode();
|
|
5545
6926
|
init_roblox_docs();
|
|
6927
|
+
init_studio_skills();
|
|
5546
6928
|
init_jpeg_encoder();
|
|
5547
6929
|
init_png_encoder();
|
|
5548
6930
|
MAX_INLINE_IMAGE_BYTES = 6e6;
|
|
@@ -5610,10 +6992,52 @@ var init_tools = __esm({
|
|
|
5610
6992
|
this.openCloudClient = new OpenCloudClient();
|
|
5611
6993
|
this.cookieClient = new RobloxCookieClient();
|
|
5612
6994
|
this.instanceManager = new StudioInstanceManager();
|
|
6995
|
+
this.bridge.onInstanceRegistered((instance) => this._associateManagedEditConnection(instance));
|
|
5613
6996
|
}
|
|
5614
6997
|
_textResult(body) {
|
|
5615
6998
|
return { content: [{ type: "text", text: JSON.stringify(body) }] };
|
|
5616
6999
|
}
|
|
7000
|
+
async getRobloxSkills(action, name) {
|
|
7001
|
+
if (action !== "list" && action !== "get") {
|
|
7002
|
+
throw new Error('get_roblox_skills action must be "list" or "get"');
|
|
7003
|
+
}
|
|
7004
|
+
const bundle = loadBuiltInStudioSkills();
|
|
7005
|
+
const bundleMetadata = {
|
|
7006
|
+
source: "installed-studio-assistant",
|
|
7007
|
+
studioVersion: bundle.studioVersion,
|
|
7008
|
+
bundlePath: bundle.bundlePath,
|
|
7009
|
+
bundleModifiedAt: bundle.bundleModifiedAt,
|
|
7010
|
+
bundleSha256: bundle.bundleSha256
|
|
7011
|
+
};
|
|
7012
|
+
if (action === "list") {
|
|
7013
|
+
return this._textResult({
|
|
7014
|
+
action,
|
|
7015
|
+
...bundleMetadata,
|
|
7016
|
+
count: bundle.skills.length,
|
|
7017
|
+
skills: bundle.skills.map((skill2) => ({
|
|
7018
|
+
name: skill2.name,
|
|
7019
|
+
sourceName: skill2.sourceName,
|
|
7020
|
+
description: skill2.description,
|
|
7021
|
+
document: skill2.document,
|
|
7022
|
+
hasCombinedDocument: skill2.hasCombinedDocument,
|
|
7023
|
+
contentLength: skill2.contentLength,
|
|
7024
|
+
contentSha256: skill2.contentSha256
|
|
7025
|
+
}))
|
|
7026
|
+
});
|
|
7027
|
+
}
|
|
7028
|
+
if (!name || typeof name !== "string" || !name.trim()) {
|
|
7029
|
+
throw new Error('get_roblox_skills action="get" requires a skill name from action="list"');
|
|
7030
|
+
}
|
|
7031
|
+
const skill = findBuiltInStudioSkill(bundle, name);
|
|
7032
|
+
if (!skill) {
|
|
7033
|
+
throw new Error(`Built-in Studio skill "${name}" was not found. Available skills: ` + bundle.skills.map((candidate) => candidate.name).join(", "));
|
|
7034
|
+
}
|
|
7035
|
+
return this._textResult({
|
|
7036
|
+
action,
|
|
7037
|
+
...bundleMetadata,
|
|
7038
|
+
skill
|
|
7039
|
+
});
|
|
7040
|
+
}
|
|
5617
7041
|
async getRobloxDocs(name, docType, section) {
|
|
5618
7042
|
if (!name || typeof name !== "string") {
|
|
5619
7043
|
throw new Error('get_roblox_docs requires a name (e.g. "ProximityPrompt")');
|
|
@@ -5966,8 +7390,8 @@ var init_tools = __esm({
|
|
|
5966
7390
|
timedOut: true
|
|
5967
7391
|
};
|
|
5968
7392
|
}
|
|
5969
|
-
async getFileTree(
|
|
5970
|
-
const response = await this._callSingle("/api/file-tree", { path:
|
|
7393
|
+
async getFileTree(path6 = "", instance_id) {
|
|
7394
|
+
const response = await this._callSingle("/api/file-tree", { path: path6 }, void 0, instance_id);
|
|
5971
7395
|
return {
|
|
5972
7396
|
content: [
|
|
5973
7397
|
{
|
|
@@ -6084,9 +7508,9 @@ var init_tools = __esm({
|
|
|
6084
7508
|
]
|
|
6085
7509
|
};
|
|
6086
7510
|
}
|
|
6087
|
-
async getProjectStructure(
|
|
7511
|
+
async getProjectStructure(path6, maxDepth, scriptsOnly, instance_id) {
|
|
6088
7512
|
const response = await this._callSingle("/api/project-structure", {
|
|
6089
|
-
path:
|
|
7513
|
+
path: path6,
|
|
6090
7514
|
maxDepth,
|
|
6091
7515
|
scriptsOnly
|
|
6092
7516
|
}, void 0, instance_id);
|
|
@@ -7010,8 +8434,8 @@ ${code}`
|
|
|
7010
8434
|
const rawJson = mutable.raw_json;
|
|
7011
8435
|
if (typeof rawJson === "string") {
|
|
7012
8436
|
if (typeof outputPath === "string" && outputPath !== "") {
|
|
7013
|
-
const resolvedOutputPath =
|
|
7014
|
-
fs3.mkdirSync(
|
|
8437
|
+
const resolvedOutputPath = path5.resolve(outputPath);
|
|
8438
|
+
fs3.mkdirSync(path5.dirname(resolvedOutputPath), { recursive: true });
|
|
7015
8439
|
fs3.writeFileSync(resolvedOutputPath, rawJson, "utf8");
|
|
7016
8440
|
mutable.output_path = resolvedOutputPath;
|
|
7017
8441
|
}
|
|
@@ -7073,8 +8497,8 @@ ${code}`
|
|
|
7073
8497
|
const rawSnapshotBase64 = mutable.raw_snapshot_base64;
|
|
7074
8498
|
if (typeof rawSnapshotBase64 === "string") {
|
|
7075
8499
|
if (typeof outputPath === "string" && outputPath !== "") {
|
|
7076
|
-
const resolvedOutputPath =
|
|
7077
|
-
fs3.mkdirSync(
|
|
8500
|
+
const resolvedOutputPath = path5.resolve(outputPath);
|
|
8501
|
+
fs3.mkdirSync(path5.dirname(resolvedOutputPath), { recursive: true });
|
|
7078
8502
|
fs3.writeFileSync(resolvedOutputPath, Buffer.from(rawSnapshotBase64, "base64"));
|
|
7079
8503
|
mutable.output_path = resolvedOutputPath;
|
|
7080
8504
|
}
|
|
@@ -7089,8 +8513,8 @@ ${code}`
|
|
|
7089
8513
|
});
|
|
7090
8514
|
}
|
|
7091
8515
|
if (typeof summaryOutputPath === "string" && summaryOutputPath !== "") {
|
|
7092
|
-
const resolvedSummaryPath =
|
|
7093
|
-
fs3.mkdirSync(
|
|
8516
|
+
const resolvedSummaryPath = path5.resolve(summaryOutputPath);
|
|
8517
|
+
fs3.mkdirSync(path5.dirname(resolvedSummaryPath), { recursive: true });
|
|
7094
8518
|
fs3.writeFileSync(resolvedSummaryPath, JSON.stringify(mutable, null, 2), "utf8");
|
|
7095
8519
|
mutable.summary_output_path = resolvedSummaryPath;
|
|
7096
8520
|
}
|
|
@@ -7158,11 +8582,18 @@ ${code}`
|
|
|
7158
8582
|
return record.placeId !== void 0 && instance.placeId === record.placeId;
|
|
7159
8583
|
}
|
|
7160
8584
|
if ((record.source === "baseplate" || record.source === "local_file") && record.localPlaceFile) {
|
|
7161
|
-
const expectedName =
|
|
8585
|
+
const expectedName = path5.basename(record.localPlaceFile);
|
|
7162
8586
|
return instance.placeName === expectedName || instance.dataModelName === expectedName;
|
|
7163
8587
|
}
|
|
7164
8588
|
return true;
|
|
7165
8589
|
}
|
|
8590
|
+
_associateManagedEditConnection(instance) {
|
|
8591
|
+
if (instance.role !== "edit")
|
|
8592
|
+
return;
|
|
8593
|
+
const candidate = this.instanceManager.pendingLaunches().filter((record) => instance.connectedAt >= record.launchedAt - 1e3).filter((record) => this._matchesManagedLaunch(record, instance)).sort((a, b) => a.launchedAt - b.launchedAt)[0];
|
|
8594
|
+
if (candidate)
|
|
8595
|
+
this.instanceManager.attachInstanceId(candidate, instance.instanceId);
|
|
8596
|
+
}
|
|
7166
8597
|
async _deriveUniverseId(placeId) {
|
|
7167
8598
|
const response = await fetch(`https://apis.roblox.com/universes/v1/places/${placeId}/universe`);
|
|
7168
8599
|
if (!response.ok) {
|
|
@@ -7178,6 +8609,10 @@ ${code}`
|
|
|
7178
8609
|
async _waitForManagedEditConnection(record, beforeKeys, timeoutMs) {
|
|
7179
8610
|
const deadline = Date.now() + timeoutMs;
|
|
7180
8611
|
while (Date.now() < deadline) {
|
|
8612
|
+
this.instanceManager.refresh(record);
|
|
8613
|
+
if (record.state === "failed" || record.state === "exited" || record.closedAt !== void 0) {
|
|
8614
|
+
return void 0;
|
|
8615
|
+
}
|
|
7181
8616
|
const candidates = this.bridge.getPublicInstances().filter((instance) => instance.role === "edit").filter((instance) => !beforeKeys.has(this._publicInstanceKey(instance))).filter((instance) => instance.connectedAt >= record.launchedAt - 1e3).filter((instance) => this._matchesManagedLaunch(record, instance)).sort((a, b) => b.connectedAt - a.connectedAt);
|
|
7182
8617
|
if (candidates[0])
|
|
7183
8618
|
return candidates[0];
|
|
@@ -7186,12 +8621,25 @@ ${code}`
|
|
|
7186
8621
|
return void 0;
|
|
7187
8622
|
}
|
|
7188
8623
|
_managedStatus(record) {
|
|
8624
|
+
this.instanceManager.refresh(record);
|
|
7189
8625
|
const connected = record.instanceId ? this.bridge.getPublicInstances().filter((instance) => instance.instanceId === record.instanceId) : [];
|
|
7190
8626
|
return {
|
|
8627
|
+
launch_id: record.recordId,
|
|
7191
8628
|
instance_id: record.instanceId,
|
|
8629
|
+
managed: true,
|
|
8630
|
+
state: record.state,
|
|
8631
|
+
pid: record.nativeProcessId ?? record.spawnPid,
|
|
8632
|
+
process_running: record.closedAt === void 0 && record.exitedAt === void 0,
|
|
7192
8633
|
source: record.source,
|
|
8634
|
+
local_place_file: record.localPlaceFile,
|
|
7193
8635
|
place_id: record.placeId,
|
|
7194
8636
|
place_version: record.placeVersion,
|
|
8637
|
+
launched_at: new Date(record.launchedAt).toISOString(),
|
|
8638
|
+
connected_at: record.connectedAt ? new Date(record.connectedAt).toISOString() : void 0,
|
|
8639
|
+
failed_at: record.failedAt ? new Date(record.failedAt).toISOString() : void 0,
|
|
8640
|
+
exited_at: record.exitedAt ? new Date(record.exitedAt).toISOString() : void 0,
|
|
8641
|
+
exit_code: record.exitCode,
|
|
8642
|
+
failure_reason: record.failureReason,
|
|
7195
8643
|
connected: connected.length > 0,
|
|
7196
8644
|
roles: connected.map((instance) => instance.role).sort()
|
|
7197
8645
|
};
|
|
@@ -7203,6 +8651,10 @@ ${code}`
|
|
|
7203
8651
|
async manageInstance(request) {
|
|
7204
8652
|
const action = request.action;
|
|
7205
8653
|
const instance_id = typeof request.instance_id === "string" ? request.instance_id : void 0;
|
|
8654
|
+
const launch_id = typeof request.launch_id === "string" ? request.launch_id : void 0;
|
|
8655
|
+
if (instance_id && launch_id) {
|
|
8656
|
+
throw new Error("manage_instance accepts only one of instance_id or launch_id.");
|
|
8657
|
+
}
|
|
7206
8658
|
if (action !== "launch" && action !== "close" && action !== "status" && action !== "list_place_versions") {
|
|
7207
8659
|
throw new Error("manage_instance requires action=launch|close|status|list_place_versions");
|
|
7208
8660
|
}
|
|
@@ -7230,18 +8682,26 @@ ${code}`
|
|
|
7230
8682
|
return this._textResult(body);
|
|
7231
8683
|
}
|
|
7232
8684
|
if (action === "status") {
|
|
8685
|
+
if (launch_id) {
|
|
8686
|
+
const record2 = this.instanceManager.getByLaunchId(launch_id);
|
|
8687
|
+
if (!record2)
|
|
8688
|
+
return this._textResult({ error: "Launch is not managed.", launch_id });
|
|
8689
|
+
return this._textResult(this._managedStatus(record2));
|
|
8690
|
+
}
|
|
7233
8691
|
if (instance_id) {
|
|
7234
8692
|
const record2 = this.instanceManager.get(instance_id);
|
|
7235
8693
|
const connected2 = this.bridge.getPublicInstances().filter((instance) => instance.instanceId === instance_id);
|
|
7236
8694
|
if (!record2 && connected2.length === 0) {
|
|
7237
8695
|
return this._textResult({ error: "Instance is not connected or managed.", instance_id });
|
|
7238
8696
|
}
|
|
8697
|
+
if (record2)
|
|
8698
|
+
return this._textResult(this._managedStatus(record2));
|
|
7239
8699
|
return this._textResult({
|
|
7240
8700
|
instance_id,
|
|
7241
|
-
managed:
|
|
7242
|
-
|
|
7243
|
-
place_id:
|
|
7244
|
-
|
|
8701
|
+
managed: false,
|
|
8702
|
+
state: "connected",
|
|
8703
|
+
place_id: connected2[0]?.placeId,
|
|
8704
|
+
connected: true,
|
|
7245
8705
|
roles: connected2.map((instance) => instance.role).sort()
|
|
7246
8706
|
});
|
|
7247
8707
|
}
|
|
@@ -7257,14 +8717,32 @@ ${code}`
|
|
|
7257
8717
|
}
|
|
7258
8718
|
if (action === "close") {
|
|
7259
8719
|
let record2;
|
|
8720
|
+
if (launch_id) {
|
|
8721
|
+
record2 = this.instanceManager.getByLaunchId(launch_id);
|
|
8722
|
+
if (!record2)
|
|
8723
|
+
return this._textResult({ error: "Launch is not managed.", launch_id });
|
|
8724
|
+
const connectedInstanceId = record2.instanceId;
|
|
8725
|
+
const closeResult2 = record2.closedAt === void 0 ? this.instanceManager.close(record2) : { status: "already_closed" };
|
|
8726
|
+
if (connectedInstanceId) {
|
|
8727
|
+
await this.bridge.unregisterInstanceIdEverywhere(connectedInstanceId);
|
|
8728
|
+
await sleep(500);
|
|
8729
|
+
await this.bridge.unregisterInstanceIdEverywhere(connectedInstanceId);
|
|
8730
|
+
}
|
|
8731
|
+
return this._textResult({
|
|
8732
|
+
...this._managedStatus(record2),
|
|
8733
|
+
message: closeResult2.status === "already_closed" ? "Studio instance was already closed." : "Studio instance closed."
|
|
8734
|
+
});
|
|
8735
|
+
}
|
|
7260
8736
|
if (instance_id) {
|
|
8737
|
+
const recordBeforeClose = this.instanceManager.get(instance_id);
|
|
7261
8738
|
const managedClose = this.instanceManager.closeByInstanceId(instance_id);
|
|
7262
8739
|
if (managedClose.status !== "not_found") {
|
|
7263
8740
|
await this.bridge.unregisterInstanceIdEverywhere(instance_id);
|
|
7264
8741
|
await sleep(500);
|
|
7265
8742
|
await this.bridge.unregisterInstanceIdEverywhere(instance_id);
|
|
8743
|
+
const closedRecord = recordBeforeClose ?? (managedClose.launchId ? this.instanceManager.getByLaunchId(managedClose.launchId) : void 0);
|
|
7266
8744
|
return this._textResult({
|
|
7267
|
-
instance_id,
|
|
8745
|
+
...closedRecord ? this._managedStatus(closedRecord) : { instance_id },
|
|
7268
8746
|
message: managedClose.status === "already_closed" ? "Studio instance was already closed." : "Studio instance closed."
|
|
7269
8747
|
});
|
|
7270
8748
|
}
|
|
@@ -7311,7 +8789,7 @@ ${code}`
|
|
|
7311
8789
|
await this.bridge.unregisterInstanceIdEverywhere(record2.instanceId);
|
|
7312
8790
|
}
|
|
7313
8791
|
return this._textResult({
|
|
7314
|
-
|
|
8792
|
+
...this._managedStatus(record2),
|
|
7315
8793
|
message: closeResult.status === "already_closed" ? "Studio instance was already closed." : "Studio instance closed."
|
|
7316
8794
|
});
|
|
7317
8795
|
}
|
|
@@ -7338,24 +8816,34 @@ ${code}`
|
|
|
7338
8816
|
localPlaceFile,
|
|
7339
8817
|
placeId,
|
|
7340
8818
|
universeId,
|
|
7341
|
-
placeVersion
|
|
8819
|
+
placeVersion,
|
|
8820
|
+
connectionTimeoutMs: timeoutMs
|
|
7342
8821
|
});
|
|
7343
8822
|
if (!waitForConnection) {
|
|
7344
|
-
return this._textResult({
|
|
8823
|
+
return this._textResult({
|
|
8824
|
+
...this._managedStatus(record),
|
|
8825
|
+
message: "Studio launch requested."
|
|
8826
|
+
});
|
|
7345
8827
|
}
|
|
7346
8828
|
const connected = await this._waitForManagedEditConnection(record, beforeKeys, timeoutMs);
|
|
7347
8829
|
if (!connected) {
|
|
7348
|
-
|
|
7349
|
-
this.instanceManager.
|
|
7350
|
-
}
|
|
8830
|
+
if (record.state === "launching") {
|
|
8831
|
+
this.instanceManager.markFailed(record, "Studio launched, but the MCP plugin did not connect before timeout.");
|
|
8832
|
+
}
|
|
8833
|
+
if (record.closedAt === void 0) {
|
|
8834
|
+
try {
|
|
8835
|
+
this.instanceManager.close(record);
|
|
8836
|
+
} catch {
|
|
8837
|
+
}
|
|
7351
8838
|
}
|
|
7352
8839
|
return this._textResult({
|
|
7353
|
-
|
|
8840
|
+
...this._managedStatus(record),
|
|
8841
|
+
error: record.failureReason ?? "Studio launched, but the MCP plugin did not connect before timeout."
|
|
7354
8842
|
});
|
|
7355
8843
|
}
|
|
7356
8844
|
this.instanceManager.attachInstanceId(record, connected.instanceId);
|
|
7357
8845
|
return this._textResult({
|
|
7358
|
-
|
|
8846
|
+
...this._managedStatus(record),
|
|
7359
8847
|
message: launchSource === "place_revision" ? `Studio opened place revision ${placeVersion}.` : "Studio opened."
|
|
7360
8848
|
});
|
|
7361
8849
|
}
|
|
@@ -7538,16 +9026,16 @@ ${code}`
|
|
|
7538
9026
|
try {
|
|
7539
9027
|
editState = await this.client.request("/api/multiplayer-test-state", {}, instanceId, "edit");
|
|
7540
9028
|
body.edit = editState;
|
|
7541
|
-
} catch (
|
|
7542
|
-
body.edit = { error:
|
|
9029
|
+
} catch (err2) {
|
|
9030
|
+
body.edit = { error: err2 instanceof Error ? err2.message : String(err2) };
|
|
7543
9031
|
}
|
|
7544
9032
|
}
|
|
7545
9033
|
if (server) {
|
|
7546
9034
|
try {
|
|
7547
9035
|
serverState = await this.client.request("/api/multiplayer-test-state", {}, instanceId, "server");
|
|
7548
9036
|
body.server = serverState;
|
|
7549
|
-
} catch (
|
|
7550
|
-
body.server = { error:
|
|
9037
|
+
} catch (err2) {
|
|
9038
|
+
body.server = { error: err2 instanceof Error ? err2.message : String(err2) };
|
|
7551
9039
|
}
|
|
7552
9040
|
}
|
|
7553
9041
|
const session = editState?.session;
|
|
@@ -7873,14 +9361,14 @@ ${code}`
|
|
|
7873
9361
|
};
|
|
7874
9362
|
}
|
|
7875
9363
|
static findProjectRoot(startDir) {
|
|
7876
|
-
let dir =
|
|
9364
|
+
let dir = path5.resolve(startDir);
|
|
7877
9365
|
let previous = "";
|
|
7878
9366
|
while (dir !== previous) {
|
|
7879
|
-
if (fs3.existsSync(
|
|
9367
|
+
if (fs3.existsSync(path5.join(dir, ".git")) || fs3.existsSync(path5.join(dir, "package.json"))) {
|
|
7880
9368
|
return dir;
|
|
7881
9369
|
}
|
|
7882
9370
|
previous = dir;
|
|
7883
|
-
dir =
|
|
9371
|
+
dir = path5.dirname(dir);
|
|
7884
9372
|
}
|
|
7885
9373
|
return null;
|
|
7886
9374
|
}
|
|
@@ -7894,7 +9382,7 @@ ${code}`
|
|
|
7894
9382
|
}
|
|
7895
9383
|
}
|
|
7896
9384
|
static ensureWritableDirectory(candidate, label) {
|
|
7897
|
-
const resolved =
|
|
9385
|
+
const resolved = path5.resolve(candidate);
|
|
7898
9386
|
try {
|
|
7899
9387
|
fs3.mkdirSync(resolved, { recursive: true });
|
|
7900
9388
|
} catch (error) {
|
|
@@ -7915,11 +9403,11 @@ ${code}`
|
|
|
7915
9403
|
if (_RobloxStudioTools._cachedLibraryPath)
|
|
7916
9404
|
return _RobloxStudioTools._cachedLibraryPath;
|
|
7917
9405
|
const overridePath = process.env.ROBLOXSTUDIO_MCP_BUILD_LIBRARY || process.env.BUILD_LIBRARY_PATH;
|
|
7918
|
-
const cwd =
|
|
9406
|
+
const cwd = path5.resolve(process.cwd());
|
|
7919
9407
|
const projectRoot = _RobloxStudioTools.findProjectRoot(cwd);
|
|
7920
|
-
const homeLibraryPath =
|
|
7921
|
-
const projectLibraryPath = projectRoot ?
|
|
7922
|
-
const cwdLibraryPath =
|
|
9408
|
+
const homeLibraryPath = path5.join(os3.homedir(), ".robloxstudio-mcp", "build-library");
|
|
9409
|
+
const projectLibraryPath = projectRoot ? path5.join(projectRoot, "build-library") : null;
|
|
9410
|
+
const cwdLibraryPath = path5.join(cwd, "build-library");
|
|
7923
9411
|
let result;
|
|
7924
9412
|
if (overridePath) {
|
|
7925
9413
|
result = _RobloxStudioTools.ensureWritableDirectory(overridePath, "override");
|
|
@@ -7933,12 +9421,12 @@ ${code}`
|
|
|
7933
9421
|
}
|
|
7934
9422
|
})());
|
|
7935
9423
|
if (existing) {
|
|
7936
|
-
result =
|
|
9424
|
+
result = path5.resolve(existing);
|
|
7937
9425
|
} else if (projectLibraryPath) {
|
|
7938
9426
|
try {
|
|
7939
9427
|
result = _RobloxStudioTools.ensureWritableDirectory(projectLibraryPath, "project-root");
|
|
7940
|
-
} catch (
|
|
7941
|
-
console.error(`Warning: could not create build-library at project root (${projectLibraryPath}): ${
|
|
9428
|
+
} catch (err2) {
|
|
9429
|
+
console.error(`Warning: could not create build-library at project root (${projectLibraryPath}): ${err2.message}. Falling back to home directory.`);
|
|
7942
9430
|
result = _RobloxStudioTools.ensureWritableDirectory(homeLibraryPath, "home");
|
|
7943
9431
|
}
|
|
7944
9432
|
} else {
|
|
@@ -7960,8 +9448,8 @@ ${code}`
|
|
|
7960
9448
|
if (response && response.success && response.buildData) {
|
|
7961
9449
|
const buildData = response.buildData;
|
|
7962
9450
|
const buildId = buildData.id || `${style}/exported`;
|
|
7963
|
-
const filePath =
|
|
7964
|
-
const dirPath =
|
|
9451
|
+
const filePath = path5.join(_RobloxStudioTools.findLibraryPath(), `${buildId}.json`);
|
|
9452
|
+
const dirPath = path5.dirname(filePath);
|
|
7965
9453
|
if (!fs3.existsSync(dirPath)) {
|
|
7966
9454
|
fs3.mkdirSync(dirPath, { recursive: true });
|
|
7967
9455
|
}
|
|
@@ -8062,8 +9550,8 @@ ${code}`
|
|
|
8062
9550
|
const normalizedParts = this.normalizeBuildParts(parts, new Set(Object.keys(normalizedPalette)));
|
|
8063
9551
|
const computedBounds = bounds || this.computeBounds(normalizedParts);
|
|
8064
9552
|
const buildData = { id, style, bounds: computedBounds, palette: normalizedPalette, parts: normalizedParts };
|
|
8065
|
-
const filePath =
|
|
8066
|
-
const dirPath =
|
|
9553
|
+
const filePath = path5.join(_RobloxStudioTools.findLibraryPath(), `${id}.json`);
|
|
9554
|
+
const dirPath = path5.dirname(filePath);
|
|
8067
9555
|
if (!fs3.existsSync(dirPath)) {
|
|
8068
9556
|
fs3.mkdirSync(dirPath, { recursive: true });
|
|
8069
9557
|
}
|
|
@@ -8121,8 +9609,8 @@ ${code}`
|
|
|
8121
9609
|
};
|
|
8122
9610
|
if (seed !== void 0)
|
|
8123
9611
|
buildData.generatorSeed = seed;
|
|
8124
|
-
const filePath =
|
|
8125
|
-
const dirPath =
|
|
9612
|
+
const filePath = path5.join(_RobloxStudioTools.findLibraryPath(), `${id}.json`);
|
|
9613
|
+
const dirPath = path5.dirname(filePath);
|
|
8126
9614
|
if (!fs3.existsSync(dirPath)) {
|
|
8127
9615
|
fs3.mkdirSync(dirPath, { recursive: true });
|
|
8128
9616
|
}
|
|
@@ -8150,13 +9638,13 @@ ${code}`
|
|
|
8150
9638
|
}
|
|
8151
9639
|
let resolved;
|
|
8152
9640
|
if (typeof buildData === "string") {
|
|
8153
|
-
const filePath =
|
|
9641
|
+
const filePath = path5.join(_RobloxStudioTools.findLibraryPath(), `${buildData}.json`);
|
|
8154
9642
|
if (!fs3.existsSync(filePath)) {
|
|
8155
9643
|
throw new Error(`Build not found in library: ${buildData}`);
|
|
8156
9644
|
}
|
|
8157
9645
|
resolved = JSON.parse(fs3.readFileSync(filePath, "utf-8"));
|
|
8158
9646
|
} else if (buildData.id && !buildData.parts) {
|
|
8159
|
-
const filePath =
|
|
9647
|
+
const filePath = path5.join(_RobloxStudioTools.findLibraryPath(), `${buildData.id}.json`);
|
|
8160
9648
|
if (!fs3.existsSync(filePath)) {
|
|
8161
9649
|
throw new Error(`Build not found in library: ${buildData.id}`);
|
|
8162
9650
|
}
|
|
@@ -8183,13 +9671,13 @@ ${code}`
|
|
|
8183
9671
|
const styles = style ? [style] : ["medieval", "modern", "nature", "scifi", "misc"];
|
|
8184
9672
|
const builds = [];
|
|
8185
9673
|
for (const s of styles) {
|
|
8186
|
-
const dirPath =
|
|
9674
|
+
const dirPath = path5.join(libraryPath, s);
|
|
8187
9675
|
if (!fs3.existsSync(dirPath))
|
|
8188
9676
|
continue;
|
|
8189
9677
|
const files = fs3.readdirSync(dirPath).filter((f) => f.endsWith(".json"));
|
|
8190
9678
|
for (const file of files) {
|
|
8191
9679
|
try {
|
|
8192
|
-
const content = fs3.readFileSync(
|
|
9680
|
+
const content = fs3.readFileSync(path5.join(dirPath, file), "utf-8");
|
|
8193
9681
|
const data = JSON.parse(content);
|
|
8194
9682
|
builds.push({
|
|
8195
9683
|
id: data.id || `${s}/${file.replace(".json", "")}`,
|
|
@@ -8228,7 +9716,7 @@ ${code}`
|
|
|
8228
9716
|
if (!id) {
|
|
8229
9717
|
throw new Error("Build ID is required for get_build");
|
|
8230
9718
|
}
|
|
8231
|
-
const filePath =
|
|
9719
|
+
const filePath = path5.join(_RobloxStudioTools.findLibraryPath(), `${id}.json`);
|
|
8232
9720
|
if (!fs3.existsSync(filePath)) {
|
|
8233
9721
|
throw new Error(`Build not found in library: ${id}`);
|
|
8234
9722
|
}
|
|
@@ -8315,7 +9803,7 @@ ${code}`
|
|
|
8315
9803
|
if (!buildId) {
|
|
8316
9804
|
throw new Error(`Invalid ${validatedKeyPath}: model key "${modelKey}" is not defined in sceneData.models`);
|
|
8317
9805
|
}
|
|
8318
|
-
const filePath =
|
|
9806
|
+
const filePath = path5.join(libraryPath, `${buildId}.json`);
|
|
8319
9807
|
if (!fs3.existsSync(filePath)) {
|
|
8320
9808
|
throw new Error(`Build not found in library: ${buildId}`);
|
|
8321
9809
|
}
|
|
@@ -8663,7 +10151,7 @@ ${code}`
|
|
|
8663
10151
|
throw new Error(`File not found: ${filePath}`);
|
|
8664
10152
|
}
|
|
8665
10153
|
const fileContent = fs3.readFileSync(filePath);
|
|
8666
|
-
const fileName =
|
|
10154
|
+
const fileName = path5.basename(filePath);
|
|
8667
10155
|
if (assetType === "Decal" && this.cookieClient.hasCookie()) {
|
|
8668
10156
|
const result2 = await this.cookieClient.uploadDecal(fileContent, displayName, description || "");
|
|
8669
10157
|
return {
|
|
@@ -8888,12 +10376,12 @@ ${code}`
|
|
|
8888
10376
|
return { content: [{ type: "text", text: JSON.stringify({ error: "plugin returned no base64 payload" }) }] };
|
|
8889
10377
|
}
|
|
8890
10378
|
const bytes = Buffer.from(response.base64, "base64");
|
|
8891
|
-
const resolved =
|
|
10379
|
+
const resolved = path5.resolve(outputPath);
|
|
8892
10380
|
try {
|
|
8893
|
-
fs3.mkdirSync(
|
|
10381
|
+
fs3.mkdirSync(path5.dirname(resolved), { recursive: true });
|
|
8894
10382
|
fs3.writeFileSync(resolved, bytes);
|
|
8895
|
-
} catch (
|
|
8896
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: `failed to write ${resolved}: ${
|
|
10383
|
+
} catch (err2) {
|
|
10384
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: `failed to write ${resolved}: ${err2.message}` }) }] };
|
|
8897
10385
|
}
|
|
8898
10386
|
return {
|
|
8899
10387
|
content: [{
|
|
@@ -8924,11 +10412,11 @@ ${code}`
|
|
|
8924
10412
|
let bytes;
|
|
8925
10413
|
let sourceLabel;
|
|
8926
10414
|
if (source.path !== void 0) {
|
|
8927
|
-
const resolved =
|
|
10415
|
+
const resolved = path5.resolve(source.path);
|
|
8928
10416
|
try {
|
|
8929
10417
|
bytes = fs3.readFileSync(resolved);
|
|
8930
|
-
} catch (
|
|
8931
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: `failed to read ${resolved}: ${
|
|
10418
|
+
} catch (err2) {
|
|
10419
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: `failed to read ${resolved}: ${err2.message}` }) }] };
|
|
8932
10420
|
}
|
|
8933
10421
|
sourceLabel = resolved;
|
|
8934
10422
|
} else if (source.url !== void 0) {
|
|
@@ -8957,15 +10445,15 @@ ${code}`
|
|
|
8957
10445
|
return { content: [{ type: "text", text: JSON.stringify({ error: `fetch ${source.url}: downloaded ${arr.byteLength} bytes exceeds ${MAX_IMPORT_BYTES} byte cap` }) }] };
|
|
8958
10446
|
}
|
|
8959
10447
|
bytes = Buffer.from(arr);
|
|
8960
|
-
} catch (
|
|
8961
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: `fetch ${source.url} failed: ${
|
|
10448
|
+
} catch (err2) {
|
|
10449
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: `fetch ${source.url} failed: ${err2.message}` }) }] };
|
|
8962
10450
|
}
|
|
8963
10451
|
sourceLabel = source.url;
|
|
8964
10452
|
} else {
|
|
8965
10453
|
try {
|
|
8966
10454
|
bytes = Buffer.from(source.base64, "base64");
|
|
8967
|
-
} catch (
|
|
8968
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: `base64 decode failed: ${
|
|
10455
|
+
} catch (err2) {
|
|
10456
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: `base64 decode failed: ${err2.message}` }) }] };
|
|
8969
10457
|
}
|
|
8970
10458
|
sourceLabel = `base64(${bytes.length}B)`;
|
|
8971
10459
|
}
|
|
@@ -9098,7 +10586,13 @@ var init_proxy_bridge_service = __esm({
|
|
|
9098
10586
|
return;
|
|
9099
10587
|
const body = await res.json();
|
|
9100
10588
|
if (Array.isArray(body.instances)) {
|
|
10589
|
+
const previousKeys = new Set(this.cachedInstances.map((instance) => `${instance.pluginSessionId}\0${instance.instanceId}\0${instance.role}\0${instance.connectedAt}`));
|
|
9101
10590
|
this.cachedInstances = body.instances;
|
|
10591
|
+
for (const instance of body.instances) {
|
|
10592
|
+
const key = `${instance.pluginSessionId}\0${instance.instanceId}\0${instance.role}\0${instance.connectedAt}`;
|
|
10593
|
+
if (!previousKeys.has(key))
|
|
10594
|
+
this.notifyInstanceRegistered(toPublic(instance));
|
|
10595
|
+
}
|
|
9102
10596
|
}
|
|
9103
10597
|
} catch {
|
|
9104
10598
|
}
|
|
@@ -9158,12 +10652,12 @@ var init_proxy_bridge_service = __esm({
|
|
|
9158
10652
|
throw new Error(result.error);
|
|
9159
10653
|
}
|
|
9160
10654
|
return result.response;
|
|
9161
|
-
} catch (
|
|
10655
|
+
} catch (err2) {
|
|
9162
10656
|
clearTimeout(timeoutId);
|
|
9163
|
-
if (
|
|
10657
|
+
if (err2.name === "AbortError") {
|
|
9164
10658
|
throw new Error("Proxy request timeout");
|
|
9165
10659
|
}
|
|
9166
|
-
throw
|
|
10660
|
+
throw err2;
|
|
9167
10661
|
}
|
|
9168
10662
|
}
|
|
9169
10663
|
cleanupOldRequests() {
|
|
@@ -10346,7 +11840,7 @@ var init_definitions = __esm({
|
|
|
10346
11840
|
{
|
|
10347
11841
|
name: "manage_instance",
|
|
10348
11842
|
category: "write",
|
|
10349
|
-
description: 'Launch, close, inspect, and find revisions for Studio instances. Use action="launch" with source="baseplate" for a blank place, or source="local_file" with local_place_file for a local place; neither uses place_id. Use action="list_place_versions" with place_id to retrieve version numbers through Open Cloud asset versions, then action="launch" with source="place_revision", place_id, and place_version to open an older revision. action="
|
|
11843
|
+
description: 'Launch, close, inspect, and find revisions for Studio instances. Every launch returns launch_id, native pid, source, and lifecycle state; status and close accept launch_id before the plugin connects and instance_id after association. Use action="launch" with source="baseplate" for a blank place, or source="local_file" with local_place_file for a local place; neither uses place_id. Use action="list_place_versions" with place_id to retrieve version numbers through Open Cloud asset versions, then action="launch" with source="place_revision", place_id, and place_version to open an older revision. action="launch" source="published_place" opens the latest published place and is blocked if that place_id is already connected; source="place_revision" is allowed because Studio opens explicit past revisions as anonymous local copies. Requires ROBLOX_OPEN_CLOUD_API_KEY with asset:read for list_place_versions.',
|
|
10350
11844
|
inputSchema: {
|
|
10351
11845
|
type: "object",
|
|
10352
11846
|
properties: {
|
|
@@ -10374,11 +11868,11 @@ var init_definitions = __esm({
|
|
|
10374
11868
|
},
|
|
10375
11869
|
wait_for_connection: {
|
|
10376
11870
|
type: "boolean",
|
|
10377
|
-
description: 'For action="launch": wait until the MCP plugin connects and return instance_id (default true).'
|
|
11871
|
+
description: 'For action="launch": wait until the MCP plugin connects and return instance_id (default true). false returns launch_id immediately and continues association/failure tracking asynchronously.'
|
|
10378
11872
|
},
|
|
10379
11873
|
timeout_ms: {
|
|
10380
11874
|
type: "number",
|
|
10381
|
-
description: 'For action="launch": max milliseconds
|
|
11875
|
+
description: 'For action="launch": max milliseconds for plugin connection (default 120000). The deadline also applies asynchronously when wait_for_connection=false.'
|
|
10382
11876
|
},
|
|
10383
11877
|
max_page_size: {
|
|
10384
11878
|
type: "number",
|
|
@@ -10390,7 +11884,11 @@ var init_definitions = __esm({
|
|
|
10390
11884
|
},
|
|
10391
11885
|
instance_id: {
|
|
10392
11886
|
type: "string",
|
|
10393
|
-
description: 'For action="close" or action="status": Studio instance to inspect or close.
|
|
11887
|
+
description: 'For action="close" or action="status": connected Studio instance to inspect or close. Mutually exclusive with launch_id.'
|
|
11888
|
+
},
|
|
11889
|
+
launch_id: {
|
|
11890
|
+
type: "string",
|
|
11891
|
+
description: 'For action="close" or action="status": opaque identifier returned by launch. Works before plugin connection and for retained terminal launch status. Mutually exclusive with instance_id.'
|
|
10394
11892
|
}
|
|
10395
11893
|
},
|
|
10396
11894
|
required: ["action"]
|
|
@@ -11968,11 +13466,32 @@ part(0,2,0,2,1,1,"b")`,
|
|
|
11968
13466
|
required: ["pattern", "replacement"]
|
|
11969
13467
|
}
|
|
11970
13468
|
},
|
|
13469
|
+
// === Installed Studio Skills ===
|
|
13470
|
+
{
|
|
13471
|
+
name: "get_roblox_skills",
|
|
13472
|
+
category: "read",
|
|
13473
|
+
description: `List or retrieve Roblox-authored skills embedded in the locally installed Studio Assistant bundle. This reads the installed Assistant.rbxm directly, so it does not require a connected Studio place or Roblox's built-in MCP. Use action="list" to discover available names, then action="get" to retrieve the exact Markdown for one skill.`,
|
|
13474
|
+
inputSchema: {
|
|
13475
|
+
type: "object",
|
|
13476
|
+
properties: {
|
|
13477
|
+
action: {
|
|
13478
|
+
type: "string",
|
|
13479
|
+
enum: ["list", "get"],
|
|
13480
|
+
description: "List installed built-in skills or get one skill document."
|
|
13481
|
+
},
|
|
13482
|
+
name: {
|
|
13483
|
+
type: "string",
|
|
13484
|
+
description: 'Skill name returned by action="list". Required for action="get"; both canonical rbx-* and embedded source names are accepted.'
|
|
13485
|
+
}
|
|
13486
|
+
},
|
|
13487
|
+
required: ["action"]
|
|
13488
|
+
}
|
|
13489
|
+
},
|
|
11971
13490
|
// === Documentation ===
|
|
11972
13491
|
{
|
|
11973
13492
|
name: "get_roblox_docs",
|
|
11974
13493
|
category: "read",
|
|
11975
|
-
description: 'Fetch official Roblox engine API documentation as markdown from create.roblox.com. Call this BEFORE writing or editing code that uses an engine class, enum, datatype, or Luau library you are not fully certain about (e.g. ProximityPrompt, Enum.KeyCode, CFrame, TweenService) \u2014 the page includes the description, properties, methods, events, and code samples. Results are cached, so repeat lookups are cheap. Very large pages are truncated with a section index; pass section (e.g. "Properties", "Methods", "Events") to read one section in full.',
|
|
13494
|
+
description: 'Fetch official Roblox engine API documentation as markdown from create.roblox.com. Call this BEFORE writing or editing code that uses an engine class, enum, datatype, or Luau library you are not fully certain about (e.g. ProximityPrompt, Enum.KeyCode, CFrame, TweenService) \u2014 the page includes the description, properties, methods, events, and code samples. Unresolved names return ranked recommendations from the official engine index, including pages in other doc categories. Results are cached, so repeat lookups are cheap. Very large pages are truncated with a section index; pass section (e.g. "Properties", "Methods", "Events") to read one section in full.',
|
|
11976
13495
|
inputSchema: {
|
|
11977
13496
|
type: "object",
|
|
11978
13497
|
properties: {
|
|
@@ -12151,15 +13670,15 @@ part(0,2,0,2,1,1,"b")`,
|
|
|
12151
13670
|
});
|
|
12152
13671
|
|
|
12153
13672
|
// ../core/dist/install-plugin-helpers.js
|
|
12154
|
-
import { existsSync as
|
|
13673
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7, unlinkSync } from "fs";
|
|
12155
13674
|
import { execSync } from "child_process";
|
|
12156
|
-
import { join as
|
|
13675
|
+
import { join as join6 } from "path";
|
|
12157
13676
|
import { homedir as homedir5 } from "os";
|
|
12158
13677
|
function isWSL() {
|
|
12159
13678
|
if (process.platform !== "linux")
|
|
12160
13679
|
return false;
|
|
12161
13680
|
try {
|
|
12162
|
-
const v =
|
|
13681
|
+
const v = readFileSync7("/proc/version", "utf8");
|
|
12163
13682
|
return /microsoft|wsl/i.test(v);
|
|
12164
13683
|
} catch {
|
|
12165
13684
|
return false;
|
|
@@ -12177,7 +13696,7 @@ function getWindowsUserPluginsDir() {
|
|
|
12177
13696
|
}).toString().trim();
|
|
12178
13697
|
if (!linuxPath)
|
|
12179
13698
|
return null;
|
|
12180
|
-
return
|
|
13699
|
+
return join6(linuxPath, "Roblox", "Plugins");
|
|
12181
13700
|
} catch {
|
|
12182
13701
|
return null;
|
|
12183
13702
|
}
|
|
@@ -12186,7 +13705,7 @@ function getPluginsFolder() {
|
|
|
12186
13705
|
if (process.env.MCP_PLUGINS_DIR)
|
|
12187
13706
|
return process.env.MCP_PLUGINS_DIR;
|
|
12188
13707
|
if (process.platform === "win32") {
|
|
12189
|
-
return
|
|
13708
|
+
return join6(process.env.LOCALAPPDATA || join6(homedir5(), "AppData", "Local"), "Roblox", "Plugins");
|
|
12190
13709
|
}
|
|
12191
13710
|
if (isWSL()) {
|
|
12192
13711
|
const win = getWindowsUserPluginsDir();
|
|
@@ -12194,18 +13713,18 @@ function getPluginsFolder() {
|
|
|
12194
13713
|
return win;
|
|
12195
13714
|
console.warn("[install-plugin] WSL detected but could not resolve Windows %LOCALAPPDATA%. Falling back to ~/Documents/Roblox/Plugins/ - you will likely need to copy the rbxmx to /mnt/c/Users/<you>/AppData/Local/Roblox/Plugins/ manually. Set MCP_PLUGINS_DIR to skip detection.");
|
|
12196
13715
|
}
|
|
12197
|
-
return
|
|
13716
|
+
return join6(homedir5(), "Documents", "Roblox", "Plugins");
|
|
12198
13717
|
}
|
|
12199
13718
|
function handleVariantConflict({ pluginsFolder, otherAssetName, replace, log = console.log, warn = console.warn }) {
|
|
12200
|
-
const otherDest =
|
|
12201
|
-
if (!
|
|
13719
|
+
const otherDest = join6(pluginsFolder, otherAssetName);
|
|
13720
|
+
if (!existsSync6(otherDest))
|
|
12202
13721
|
return;
|
|
12203
13722
|
if (replace) {
|
|
12204
13723
|
try {
|
|
12205
13724
|
unlinkSync(otherDest);
|
|
12206
13725
|
log(`Removed conflicting ${otherAssetName}.`);
|
|
12207
|
-
} catch (
|
|
12208
|
-
warn(`[install-plugin] Could not remove ${otherDest}: ${
|
|
13726
|
+
} catch (err2) {
|
|
13727
|
+
warn(`[install-plugin] Could not remove ${otherDest}: ${err2}. Continuing.`);
|
|
12209
13728
|
}
|
|
12210
13729
|
return;
|
|
12211
13730
|
}
|
|
@@ -12234,6 +13753,7 @@ var init_dist = __esm({
|
|
|
12234
13753
|
init_opencloud_client();
|
|
12235
13754
|
init_install_plugin_helpers();
|
|
12236
13755
|
init_roblox_cookie_client();
|
|
13756
|
+
init_studio_skills();
|
|
12237
13757
|
}
|
|
12238
13758
|
});
|
|
12239
13759
|
|
|
@@ -12243,13 +13763,13 @@ __export(install_plugin_exports, {
|
|
|
12243
13763
|
installBundledPlugin: () => installBundledPlugin,
|
|
12244
13764
|
installPlugin: () => installPlugin
|
|
12245
13765
|
});
|
|
12246
|
-
import { copyFileSync as copyFileSync2, createWriteStream, existsSync as
|
|
12247
|
-
import { dirname as
|
|
13766
|
+
import { copyFileSync as copyFileSync2, createWriteStream, existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync8, unlinkSync as unlinkSync2 } from "fs";
|
|
13767
|
+
import { dirname as dirname5, join as join7 } from "path";
|
|
12248
13768
|
import { fileURLToPath } from "url";
|
|
12249
13769
|
import { get } from "https";
|
|
12250
13770
|
function httpsGet(url) {
|
|
12251
|
-
return new Promise((
|
|
12252
|
-
const req = get(url, { headers: { "User-Agent": "robloxstudio-mcp-inspector" } },
|
|
13771
|
+
return new Promise((resolve5, reject) => {
|
|
13772
|
+
const req = get(url, { headers: { "User-Agent": "robloxstudio-mcp-inspector" } }, resolve5);
|
|
12253
13773
|
req.on("error", reject);
|
|
12254
13774
|
req.setTimeout(TIMEOUT_MS, () => {
|
|
12255
13775
|
req.destroy(new Error(`Request timed out after ${TIMEOUT_MS}ms`));
|
|
@@ -12267,21 +13787,21 @@ async function download(url, dest, redirects = 0) {
|
|
|
12267
13787
|
if (res.statusCode !== 200) {
|
|
12268
13788
|
throw new Error(`Download failed: HTTP ${res.statusCode}`);
|
|
12269
13789
|
}
|
|
12270
|
-
return new Promise((
|
|
13790
|
+
return new Promise((resolve5, reject) => {
|
|
12271
13791
|
const file = createWriteStream(dest);
|
|
12272
|
-
const cleanup = (
|
|
13792
|
+
const cleanup = (err2) => {
|
|
12273
13793
|
file.close(() => {
|
|
12274
13794
|
try {
|
|
12275
13795
|
unlinkSync2(dest);
|
|
12276
13796
|
} catch {
|
|
12277
13797
|
}
|
|
12278
|
-
reject(
|
|
13798
|
+
reject(err2);
|
|
12279
13799
|
});
|
|
12280
13800
|
};
|
|
12281
13801
|
res.pipe(file);
|
|
12282
13802
|
file.on("finish", () => {
|
|
12283
13803
|
file.close();
|
|
12284
|
-
|
|
13804
|
+
resolve5();
|
|
12285
13805
|
});
|
|
12286
13806
|
file.on("error", cleanup);
|
|
12287
13807
|
res.on("error", cleanup);
|
|
@@ -12304,7 +13824,7 @@ function prepareInstall({
|
|
|
12304
13824
|
warn
|
|
12305
13825
|
}) {
|
|
12306
13826
|
const pluginsFolder = getPluginsFolder();
|
|
12307
|
-
if (!
|
|
13827
|
+
if (!existsSync7(pluginsFolder)) {
|
|
12308
13828
|
mkdirSync5(pluginsFolder, { recursive: true });
|
|
12309
13829
|
}
|
|
12310
13830
|
handleVariantConflict({
|
|
@@ -12317,23 +13837,23 @@ function prepareInstall({
|
|
|
12317
13837
|
return pluginsFolder;
|
|
12318
13838
|
}
|
|
12319
13839
|
function bundledAssetPath() {
|
|
12320
|
-
const currentDir =
|
|
13840
|
+
const currentDir = dirname5(fileURLToPath(import.meta.url));
|
|
12321
13841
|
const candidates = [
|
|
12322
|
-
|
|
12323
|
-
|
|
13842
|
+
join7(currentDir, "..", "studio-plugin", ASSET_NAME),
|
|
13843
|
+
join7(currentDir, "..", "..", "..", "studio-plugin", ASSET_NAME)
|
|
12324
13844
|
];
|
|
12325
|
-
return candidates.find((candidate) =>
|
|
13845
|
+
return candidates.find((candidate) => existsSync7(candidate)) ?? null;
|
|
12326
13846
|
}
|
|
12327
13847
|
function packageVersion() {
|
|
12328
|
-
const currentDir =
|
|
12329
|
-
const pkg = JSON.parse(
|
|
13848
|
+
const currentDir = dirname5(fileURLToPath(import.meta.url));
|
|
13849
|
+
const pkg = JSON.parse(readFileSync8(join7(currentDir, "..", "package.json"), "utf8"));
|
|
12330
13850
|
if (!pkg.version) {
|
|
12331
13851
|
throw new Error("Package version not found");
|
|
12332
13852
|
}
|
|
12333
13853
|
return pkg.version;
|
|
12334
13854
|
}
|
|
12335
13855
|
function bundledPluginVersion(source) {
|
|
12336
|
-
const match =
|
|
13856
|
+
const match = readFileSync8(source, "utf8").match(/local CURRENT_VERSION = "([^"]+)"/);
|
|
12337
13857
|
return match ? match[1] : null;
|
|
12338
13858
|
}
|
|
12339
13859
|
function assertBundledPluginVersion(source) {
|
|
@@ -12346,9 +13866,9 @@ function assertBundledPluginVersion(source) {
|
|
|
12346
13866
|
}
|
|
12347
13867
|
}
|
|
12348
13868
|
function filesMatch(a, b) {
|
|
12349
|
-
if (!
|
|
12350
|
-
const aBytes =
|
|
12351
|
-
const bBytes =
|
|
13869
|
+
if (!existsSync7(b)) return false;
|
|
13870
|
+
const aBytes = readFileSync8(a);
|
|
13871
|
+
const bBytes = readFileSync8(b);
|
|
12352
13872
|
return aBytes.length === bBytes.length && aBytes.equals(bBytes);
|
|
12353
13873
|
}
|
|
12354
13874
|
async function installBundledPlugin(options = {}) {
|
|
@@ -12361,7 +13881,7 @@ async function installBundledPlugin(options = {}) {
|
|
|
12361
13881
|
}
|
|
12362
13882
|
assertBundledPluginVersion(source);
|
|
12363
13883
|
const pluginsFolder = prepareInstall({ replaceVariant, log, warn });
|
|
12364
|
-
const dest =
|
|
13884
|
+
const dest = join7(pluginsFolder, ASSET_NAME);
|
|
12365
13885
|
if (filesMatch(source, dest)) return;
|
|
12366
13886
|
copyFileSync2(source, dest);
|
|
12367
13887
|
log(`Installed ${ASSET_NAME} to ${dest}`);
|
|
@@ -12374,7 +13894,7 @@ async function installPlugin(options = {}) {
|
|
|
12374
13894
|
const bundled = bundledAssetPath();
|
|
12375
13895
|
if (bundled) {
|
|
12376
13896
|
assertBundledPluginVersion(bundled);
|
|
12377
|
-
const dest2 =
|
|
13897
|
+
const dest2 = join7(pluginsFolder, ASSET_NAME);
|
|
12378
13898
|
if (filesMatch(bundled, dest2)) {
|
|
12379
13899
|
log(`${ASSET_NAME} already installed.`);
|
|
12380
13900
|
return;
|
|
@@ -12389,7 +13909,7 @@ async function installPlugin(options = {}) {
|
|
|
12389
13909
|
if (!asset) {
|
|
12390
13910
|
throw new Error(`${ASSET_NAME} not found in release ${release.tag_name}`);
|
|
12391
13911
|
}
|
|
12392
|
-
const dest =
|
|
13912
|
+
const dest = join7(pluginsFolder, ASSET_NAME);
|
|
12393
13913
|
log(`Downloading ${ASSET_NAME} from ${release.tag_name}...`);
|
|
12394
13914
|
await download(asset.browser_download_url, dest);
|
|
12395
13915
|
log(`Installed to ${dest}`);
|
|
@@ -12412,8 +13932,8 @@ init_dist();
|
|
|
12412
13932
|
import { createRequire } from "module";
|
|
12413
13933
|
if (process.argv.includes("--install-plugin")) {
|
|
12414
13934
|
const { installPlugin: installPlugin2 } = await Promise.resolve().then(() => (init_install_plugin(), install_plugin_exports));
|
|
12415
|
-
await installPlugin2().catch((
|
|
12416
|
-
console.error(
|
|
13935
|
+
await installPlugin2().catch((err2) => {
|
|
13936
|
+
console.error(err2 instanceof Error ? err2.message : String(err2));
|
|
12417
13937
|
process.exitCode = 1;
|
|
12418
13938
|
});
|
|
12419
13939
|
} else {
|
|
@@ -12422,9 +13942,9 @@ if (process.argv.includes("--install-plugin")) {
|
|
|
12422
13942
|
await installBundledPlugin2({
|
|
12423
13943
|
log: (message) => console.error(`[install-plugin] ${message}`),
|
|
12424
13944
|
warn: (message) => console.error(message)
|
|
12425
|
-
}).catch((
|
|
13945
|
+
}).catch((err2) => {
|
|
12426
13946
|
console.error(
|
|
12427
|
-
`[install-plugin] Auto-install skipped: ${
|
|
13947
|
+
`[install-plugin] Auto-install skipped: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
12428
13948
|
);
|
|
12429
13949
|
});
|
|
12430
13950
|
}
|