@chrrxs/robloxstudio-mcp-inspector 2.21.0 → 2.22.0
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 +1614 -204
- package/package.json +1 -1
- package/studio-plugin/MCPInspectorPlugin.rbxmx +3 -3
- package/studio-plugin/MCPPlugin.rbxmx +3 -3
package/dist/index.js
CHANGED
|
@@ -51,7 +51,26 @@ var init_bridge_service = __esm({
|
|
|
51
51
|
// Keyed by pluginSessionId (the per-plugin GUID).
|
|
52
52
|
instances = /* @__PURE__ */ new Map();
|
|
53
53
|
instanceAliases = /* @__PURE__ */ new Map();
|
|
54
|
+
instanceRegisteredListeners = /* @__PURE__ */ new Set();
|
|
54
55
|
requestTimeout = 3e4;
|
|
56
|
+
onInstanceRegistered(listener) {
|
|
57
|
+
this.instanceRegisteredListeners.add(listener);
|
|
58
|
+
for (const instance of this.getPublicInstances()) {
|
|
59
|
+
try {
|
|
60
|
+
listener(instance);
|
|
61
|
+
} catch {
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return () => this.instanceRegisteredListeners.delete(listener);
|
|
65
|
+
}
|
|
66
|
+
notifyInstanceRegistered(instance) {
|
|
67
|
+
for (const listener of this.instanceRegisteredListeners) {
|
|
68
|
+
try {
|
|
69
|
+
listener(instance);
|
|
70
|
+
} catch {
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
55
74
|
canonicalInstanceId(instanceId, placeId) {
|
|
56
75
|
return publishedInstanceId(placeId) ?? instanceId;
|
|
57
76
|
}
|
|
@@ -157,7 +176,7 @@ var init_bridge_service = __esm({
|
|
|
157
176
|
}
|
|
158
177
|
};
|
|
159
178
|
}
|
|
160
|
-
|
|
179
|
+
const registered = {
|
|
161
180
|
pluginSessionId,
|
|
162
181
|
instanceId,
|
|
163
182
|
role: assignedRole,
|
|
@@ -171,7 +190,9 @@ var init_bridge_service = __esm({
|
|
|
171
190
|
versionMismatch,
|
|
172
191
|
lastActivity: Date.now(),
|
|
173
192
|
connectedAt: prior?.connectedAt ?? Date.now()
|
|
174
|
-
}
|
|
193
|
+
};
|
|
194
|
+
this.instances.set(pluginSessionId, registered);
|
|
195
|
+
this.notifyInstanceRegistered(toPublic(registered));
|
|
175
196
|
return { ok: true, assignedRole, instanceId };
|
|
176
197
|
}
|
|
177
198
|
unregisterInstance(pluginSessionId) {
|
|
@@ -369,7 +390,7 @@ var init_bridge_service = __esm({
|
|
|
369
390
|
async sendRequest(endpoint, data, targetInstanceId, targetRole, timeoutMs = this.requestTimeout) {
|
|
370
391
|
const requestId = uuidv4();
|
|
371
392
|
const effectiveTimeoutMs = Math.max(1, timeoutMs);
|
|
372
|
-
return new Promise((
|
|
393
|
+
return new Promise((resolve5, reject) => {
|
|
373
394
|
const timeoutId = setTimeout(() => {
|
|
374
395
|
if (this.pendingRequests.has(requestId)) {
|
|
375
396
|
this.pendingRequests.delete(requestId);
|
|
@@ -384,7 +405,7 @@ var init_bridge_service = __esm({
|
|
|
384
405
|
targetRole,
|
|
385
406
|
timestamp: Date.now(),
|
|
386
407
|
inFlight: false,
|
|
387
|
-
resolve:
|
|
408
|
+
resolve: resolve5,
|
|
388
409
|
reject,
|
|
389
410
|
timeoutId,
|
|
390
411
|
timeoutMs: effectiveTimeoutMs
|
|
@@ -686,8 +707,8 @@ function resolveAuthToken() {
|
|
|
686
707
|
} catch {
|
|
687
708
|
}
|
|
688
709
|
return { token: fresh, source: "file", filePath };
|
|
689
|
-
} catch (
|
|
690
|
-
console.error(`[auth] Could not read/create ${filePath} (${
|
|
710
|
+
} catch (err2) {
|
|
711
|
+
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
712
|
return { token: randomBytes(32).toString("hex"), source: "file" };
|
|
692
713
|
}
|
|
693
714
|
}
|
|
@@ -811,7 +832,7 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
811
832
|
next();
|
|
812
833
|
});
|
|
813
834
|
const authToken = security?.authToken;
|
|
814
|
-
const authRequired = (
|
|
835
|
+
const authRequired = (path6) => path6 === "/mcp" || path6.startsWith("/mcp/") || path6 === "/proxy" || path6 === "/instances" || path6 === "/unregister-instance-id";
|
|
815
836
|
app.use((req, res, next) => {
|
|
816
837
|
if (!authToken || !authRequired(req.path)) {
|
|
817
838
|
next();
|
|
@@ -891,11 +912,11 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
891
912
|
pluginVariant: typeof pluginVariant === "string" ? pluginVariant : "unknown",
|
|
892
913
|
serverVersion: serverConfig?.version ?? ""
|
|
893
914
|
});
|
|
894
|
-
} catch (
|
|
915
|
+
} catch (err2) {
|
|
895
916
|
res.status(500).json({
|
|
896
917
|
success: false,
|
|
897
918
|
error: "ready_registration_exception",
|
|
898
|
-
message:
|
|
919
|
+
message: err2 instanceof Error ? err2.message : String(err2),
|
|
899
920
|
request: requestContext
|
|
900
921
|
});
|
|
901
922
|
return;
|
|
@@ -1046,8 +1067,8 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
1046
1067
|
try {
|
|
1047
1068
|
const response = await bridge.sendRequest(endpoint, data, targetInstanceId, targetRole);
|
|
1048
1069
|
res.json({ response });
|
|
1049
|
-
} catch (
|
|
1050
|
-
res.status(500).json({ error:
|
|
1070
|
+
} catch (err2) {
|
|
1071
|
+
res.status(500).json({ error: err2.message || "Proxy request failed" });
|
|
1051
1072
|
}
|
|
1052
1073
|
});
|
|
1053
1074
|
if (serverConfig) {
|
|
@@ -1159,19 +1180,19 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig, security) {
|
|
|
1159
1180
|
return app;
|
|
1160
1181
|
}
|
|
1161
1182
|
function listenWithRetry(app, host, startPort, maxAttempts = 5) {
|
|
1162
|
-
return new Promise(async (
|
|
1183
|
+
return new Promise(async (resolve5, reject) => {
|
|
1163
1184
|
for (let i = 0; i < maxAttempts; i++) {
|
|
1164
1185
|
const port = startPort + i;
|
|
1165
1186
|
try {
|
|
1166
1187
|
const server = await bindPort(app, host, port);
|
|
1167
|
-
|
|
1188
|
+
resolve5({ server, port });
|
|
1168
1189
|
return;
|
|
1169
|
-
} catch (
|
|
1170
|
-
if (
|
|
1190
|
+
} catch (err2) {
|
|
1191
|
+
if (err2.code === "EADDRINUSE") {
|
|
1171
1192
|
console.error(`Port ${port} in use, trying next...`);
|
|
1172
1193
|
continue;
|
|
1173
1194
|
}
|
|
1174
|
-
reject(
|
|
1195
|
+
reject(err2);
|
|
1175
1196
|
return;
|
|
1176
1197
|
}
|
|
1177
1198
|
}
|
|
@@ -1179,16 +1200,16 @@ function listenWithRetry(app, host, startPort, maxAttempts = 5) {
|
|
|
1179
1200
|
});
|
|
1180
1201
|
}
|
|
1181
1202
|
function bindPort(app, host, port) {
|
|
1182
|
-
return new Promise((
|
|
1203
|
+
return new Promise((resolve5, reject) => {
|
|
1183
1204
|
const server = http.createServer(app);
|
|
1184
|
-
const onError = (
|
|
1205
|
+
const onError = (err2) => {
|
|
1185
1206
|
server.removeListener("error", onError);
|
|
1186
|
-
reject(
|
|
1207
|
+
reject(err2);
|
|
1187
1208
|
};
|
|
1188
1209
|
server.once("error", onError);
|
|
1189
1210
|
server.listen(port, host, () => {
|
|
1190
1211
|
server.removeListener("error", onError);
|
|
1191
|
-
|
|
1212
|
+
resolve5(server);
|
|
1192
1213
|
});
|
|
1193
1214
|
});
|
|
1194
1215
|
}
|
|
@@ -1200,6 +1221,7 @@ var init_http_server = __esm({
|
|
|
1200
1221
|
init_mcp_compat();
|
|
1201
1222
|
init_auth();
|
|
1202
1223
|
TOOL_HANDLERS = {
|
|
1224
|
+
get_roblox_skills: (tools, body) => tools.getRobloxSkills(body.action, body.name),
|
|
1203
1225
|
get_roblox_docs: (tools, body) => tools.getRobloxDocs(body.name, body.doc_type, body.section),
|
|
1204
1226
|
get_file_tree: (tools, body) => tools.getFileTree(body.path, body.instance_id),
|
|
1205
1227
|
search_files: (tools, body) => tools.searchFiles(body.query, body.searchType, body.instance_id),
|
|
@@ -1382,8 +1404,8 @@ function runRestrictedScript(code, globals, options) {
|
|
|
1382
1404
|
let ast;
|
|
1383
1405
|
try {
|
|
1384
1406
|
ast = acorn.parse(code, { ecmaVersion: 2022, sourceType: "script" });
|
|
1385
|
-
} catch (
|
|
1386
|
-
throw new Error(`Syntax error: ${
|
|
1407
|
+
} catch (err2) {
|
|
1408
|
+
throw new Error(`Syntax error: ${err2.message}`);
|
|
1387
1409
|
}
|
|
1388
1410
|
const globalScope = new Scope(void 0, true);
|
|
1389
1411
|
for (const [name, value] of Object.entries(globals)) {
|
|
@@ -1694,15 +1716,15 @@ function runRestrictedScript(code, globals, options) {
|
|
|
1694
1716
|
case "TryStatement": {
|
|
1695
1717
|
try {
|
|
1696
1718
|
execute(node.block, scope);
|
|
1697
|
-
} catch (
|
|
1698
|
-
if (
|
|
1699
|
-
throw
|
|
1700
|
-
if (
|
|
1701
|
-
throw
|
|
1719
|
+
} catch (err2) {
|
|
1720
|
+
if (err2 instanceof BreakSignal || err2 instanceof ContinueSignal || err2 instanceof ReturnSignal)
|
|
1721
|
+
throw err2;
|
|
1722
|
+
if (err2 instanceof ScriptTimeoutError)
|
|
1723
|
+
throw err2;
|
|
1702
1724
|
if (node.handler) {
|
|
1703
1725
|
const catchScope = new Scope(scope);
|
|
1704
1726
|
if (node.handler.param) {
|
|
1705
|
-
const message =
|
|
1727
|
+
const message = err2 instanceof Error ? { message: err2.message } : err2;
|
|
1706
1728
|
bindPattern(node.handler.param, message, catchScope, "let");
|
|
1707
1729
|
}
|
|
1708
1730
|
execute(node.handler.body, catchScope);
|
|
@@ -2580,11 +2602,11 @@ function runBuildExecutor(code, palette, seed, options) {
|
|
|
2580
2602
|
};
|
|
2581
2603
|
try {
|
|
2582
2604
|
runRestrictedScript(code, sandbox, { timeoutMs: timeout });
|
|
2583
|
-
} catch (
|
|
2584
|
-
if (
|
|
2605
|
+
} catch (err2) {
|
|
2606
|
+
if (err2 instanceof ScriptTimeoutError) {
|
|
2585
2607
|
throw new Error(`Build code execution timed out after ${timeout}ms`);
|
|
2586
2608
|
}
|
|
2587
|
-
throw new Error(`Build code execution error: ${
|
|
2609
|
+
throw new Error(`Build code execution error: ${err2?.message ?? String(err2)}`);
|
|
2588
2610
|
}
|
|
2589
2611
|
if (parts.length === 0) {
|
|
2590
2612
|
throw new Error("Build code produced no parts. Make sure to call part(), wall(), floor(), etc.");
|
|
@@ -2843,7 +2865,7 @@ var init_opencloud_client = __esm({
|
|
|
2843
2865
|
if (result.error) {
|
|
2844
2866
|
throw new Error(`Asset upload failed: ${result.error.message}`);
|
|
2845
2867
|
}
|
|
2846
|
-
await new Promise((
|
|
2868
|
+
await new Promise((resolve5) => setTimeout(resolve5, intervalMs));
|
|
2847
2869
|
}
|
|
2848
2870
|
throw new Error(`Asset upload timed out after ${maxAttempts * intervalMs / 1e3}s. Operation ID: ${operationId}`);
|
|
2849
2871
|
}
|
|
@@ -2964,7 +2986,7 @@ function isRecord(value) {
|
|
|
2964
2986
|
const record = value;
|
|
2965
2987
|
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
2988
|
}
|
|
2967
|
-
var REGISTRY_VERSION, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, EVENT_RETENTION_DAYS, ManagedInstanceRegistry;
|
|
2989
|
+
var REGISTRY_VERSION, LOCK_STALE_MS, LOCK_RETRY_MS, LOCK_TIMEOUT_MS, EVENT_RETENTION_DAYS, TERMINAL_RECORD_RETENTION_MS, ManagedInstanceRegistry;
|
|
2968
2990
|
var init_managed_instance_registry = __esm({
|
|
2969
2991
|
"../core/dist/managed-instance-registry.js"() {
|
|
2970
2992
|
"use strict";
|
|
@@ -2973,6 +2995,7 @@ var init_managed_instance_registry = __esm({
|
|
|
2973
2995
|
LOCK_RETRY_MS = 25;
|
|
2974
2996
|
LOCK_TIMEOUT_MS = 5e3;
|
|
2975
2997
|
EVENT_RETENTION_DAYS = 2;
|
|
2998
|
+
TERMINAL_RECORD_RETENTION_MS = 24 * 60 * 60 * 1e3;
|
|
2976
2999
|
ManagedInstanceRegistry = class {
|
|
2977
3000
|
dir;
|
|
2978
3001
|
constructor(dir = defaultManagedInstanceRegistryDir()) {
|
|
@@ -3000,12 +3023,21 @@ var init_managed_instance_registry = __esm({
|
|
|
3000
3023
|
findAnyByInstanceId(instanceId) {
|
|
3001
3024
|
return this.withLock(() => this.readRecordsUnlocked().find((record) => record.instanceId === instanceId));
|
|
3002
3025
|
}
|
|
3026
|
+
findAnyByRecordId(recordId, options) {
|
|
3027
|
+
return this.withLock(() => {
|
|
3028
|
+
this.sweepUnlocked(options);
|
|
3029
|
+
return this.readRecordsUnlocked().find((record) => record.recordId === recordId);
|
|
3030
|
+
});
|
|
3031
|
+
}
|
|
3003
3032
|
listOpen(options) {
|
|
3004
3033
|
return this.withLock(() => {
|
|
3005
3034
|
this.sweepUnlocked(options);
|
|
3006
3035
|
return this.readOpenRecordsUnlocked();
|
|
3007
3036
|
});
|
|
3008
3037
|
}
|
|
3038
|
+
listOpenUnchecked() {
|
|
3039
|
+
return this.withLock(() => this.readOpenRecordsUnlocked());
|
|
3040
|
+
}
|
|
3009
3041
|
markClosed(recordId, closedAt = Date.now()) {
|
|
3010
3042
|
this.withLock(() => {
|
|
3011
3043
|
const record = this.readRecordUnlocked(recordId);
|
|
@@ -3177,41 +3209,52 @@ var init_managed_instance_registry = __esm({
|
|
|
3177
3209
|
}, now);
|
|
3178
3210
|
continue;
|
|
3179
3211
|
}
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3212
|
+
const terminalAt = parsed.closedAt ?? parsed.exitedAt;
|
|
3213
|
+
if (terminalAt !== void 0) {
|
|
3214
|
+
if (now - terminalAt > TERMINAL_RECORD_RETENTION_MS) {
|
|
3215
|
+
fs.rmSync(file, { force: true });
|
|
3216
|
+
this.appendEventUnlocked({
|
|
3217
|
+
event: "registry_pruned_terminal_record",
|
|
3218
|
+
recordId: parsed.recordId,
|
|
3219
|
+
instanceId: parsed.instanceId,
|
|
3220
|
+
source: parsed.source,
|
|
3221
|
+
reason: "terminal_retention_expired",
|
|
3222
|
+
action: "deleted_record"
|
|
3223
|
+
}, now);
|
|
3224
|
+
}
|
|
3190
3225
|
continue;
|
|
3191
3226
|
}
|
|
3192
3227
|
if (parsed.bootId !== options.currentBootId) {
|
|
3193
3228
|
this.cleanupRecord(options, parsed);
|
|
3194
|
-
|
|
3229
|
+
parsed.state = parsed.state === "failed" ? "failed" : "exited";
|
|
3230
|
+
parsed.exitedAt = now;
|
|
3231
|
+
parsed.closedAt = now;
|
|
3232
|
+
parsed.failureReason ??= "Studio process belongs to a previous host boot.";
|
|
3233
|
+
this.writeRecordUnlocked(parsed);
|
|
3195
3234
|
this.appendEventUnlocked({
|
|
3196
|
-
event: "
|
|
3235
|
+
event: "registry_marked_previous_boot_exited",
|
|
3197
3236
|
recordId: parsed.recordId,
|
|
3198
3237
|
instanceId: parsed.instanceId,
|
|
3199
3238
|
source: parsed.source,
|
|
3200
3239
|
reason: "boot_id_changed",
|
|
3201
|
-
action: "
|
|
3240
|
+
action: "marked_exited_and_cleaned_baseplate"
|
|
3202
3241
|
}, now);
|
|
3203
3242
|
continue;
|
|
3204
3243
|
}
|
|
3205
3244
|
if (options.isProcessRunning && (parsed.nativeProcessId || parsed.spawnPid) && !options.isProcessRunning(parsed)) {
|
|
3206
3245
|
this.cleanupRecord(options, parsed);
|
|
3207
|
-
|
|
3246
|
+
parsed.state = parsed.state === "failed" ? "failed" : "exited";
|
|
3247
|
+
parsed.exitedAt = now;
|
|
3248
|
+
parsed.closedAt = now;
|
|
3249
|
+
parsed.failureReason ??= parsed.instanceId ? "Studio process exited." : "Studio process exited before the MCP plugin connected.";
|
|
3250
|
+
this.writeRecordUnlocked(parsed);
|
|
3208
3251
|
this.appendEventUnlocked({
|
|
3209
|
-
event: "
|
|
3252
|
+
event: "registry_marked_process_exited",
|
|
3210
3253
|
recordId: parsed.recordId,
|
|
3211
3254
|
instanceId: parsed.instanceId,
|
|
3212
3255
|
source: parsed.source,
|
|
3213
3256
|
reason: "pid_not_running",
|
|
3214
|
-
action: "
|
|
3257
|
+
action: "marked_exited_and_cleaned_baseplate"
|
|
3215
3258
|
}, now);
|
|
3216
3259
|
}
|
|
3217
3260
|
}
|
|
@@ -3270,6 +3313,63 @@ function toStudioLaunchArg(arg) {
|
|
|
3270
3313
|
return arg;
|
|
3271
3314
|
return run("wslpath", ["-w", arg]);
|
|
3272
3315
|
}
|
|
3316
|
+
function powershellStringLiteral(value) {
|
|
3317
|
+
return `'${value.replace(/'/g, "''")}'`;
|
|
3318
|
+
}
|
|
3319
|
+
function quoteWindowsCommandLineArg(value) {
|
|
3320
|
+
if (value.length > 0 && !/[\s"]/u.test(value))
|
|
3321
|
+
return value;
|
|
3322
|
+
let quoted = '"';
|
|
3323
|
+
let backslashes = 0;
|
|
3324
|
+
for (const char of value) {
|
|
3325
|
+
if (char === "\\") {
|
|
3326
|
+
backslashes += 1;
|
|
3327
|
+
continue;
|
|
3328
|
+
}
|
|
3329
|
+
if (char === '"') {
|
|
3330
|
+
quoted += "\\".repeat(backslashes * 2 + 1) + '"';
|
|
3331
|
+
backslashes = 0;
|
|
3332
|
+
continue;
|
|
3333
|
+
}
|
|
3334
|
+
quoted += "\\".repeat(backslashes) + char;
|
|
3335
|
+
backslashes = 0;
|
|
3336
|
+
}
|
|
3337
|
+
return `${quoted}${"\\".repeat(backslashes * 2)}"`;
|
|
3338
|
+
}
|
|
3339
|
+
function buildWindowsStudioStartScript(exe, args) {
|
|
3340
|
+
const windowsExe = toStudioLaunchArg(exe);
|
|
3341
|
+
const commandLine = args.map(quoteWindowsCommandLineArg).join(" ");
|
|
3342
|
+
return [
|
|
3343
|
+
"$psi = New-Object System.Diagnostics.ProcessStartInfo",
|
|
3344
|
+
`$psi.FileName = ${powershellStringLiteral(windowsExe)}`,
|
|
3345
|
+
`$psi.Arguments = ${powershellStringLiteral(commandLine)}`,
|
|
3346
|
+
// With UseShellExecute=false, Studio inherits the synchronous
|
|
3347
|
+
// powershell.exe invocation's stdout/stderr pipe handles under WSL. Those
|
|
3348
|
+
// handles keep execFileSync waiting until Studio exits even though
|
|
3349
|
+
// PowerShell already printed the PID. Shell execution prevents that
|
|
3350
|
+
// inheritance while Process.Start still returns the native Studio PID.
|
|
3351
|
+
"$psi.UseShellExecute = $true",
|
|
3352
|
+
"$studio = [System.Diagnostics.Process]::Start($psi)",
|
|
3353
|
+
'if ($null -eq $studio) { throw "Roblox Studio process did not start." }'
|
|
3354
|
+
].join("; ");
|
|
3355
|
+
}
|
|
3356
|
+
function spawnWindowsStudioFromWsl(exe, args) {
|
|
3357
|
+
const script = buildWindowsStudioStartScript(exe, args);
|
|
3358
|
+
const output = powershell(`${script}; [PSCustomObject]@{ pid = $studio.Id; started = $studio.StartTime.ToUniversalTime().ToFileTimeUtc().ToString() } | ConvertTo-Json -Compress`);
|
|
3359
|
+
const parsed = JSON.parse(output);
|
|
3360
|
+
const nativePid = Number(parsed.pid);
|
|
3361
|
+
const nativeStartedAt = typeof parsed.started === "string" && /^\d+$/u.test(parsed.started) ? parsed.started : void 0;
|
|
3362
|
+
if (!Number.isSafeInteger(nativePid) || nativePid <= 0) {
|
|
3363
|
+
throw new Error(`Could not determine the Windows Studio process id from: ${nativePid}`);
|
|
3364
|
+
}
|
|
3365
|
+
return {
|
|
3366
|
+
pid: nativePid,
|
|
3367
|
+
nativePid,
|
|
3368
|
+
nativeStartedAt,
|
|
3369
|
+
unref: () => {
|
|
3370
|
+
}
|
|
3371
|
+
};
|
|
3372
|
+
}
|
|
3273
3373
|
function resolveEntrypointDir() {
|
|
3274
3374
|
const entrypoint = process.argv[1];
|
|
3275
3375
|
if (!entrypoint)
|
|
@@ -3301,8 +3401,8 @@ function isProcessAlive(pid) {
|
|
|
3301
3401
|
try {
|
|
3302
3402
|
process.kill(pid, 0);
|
|
3303
3403
|
return true;
|
|
3304
|
-
} catch (
|
|
3305
|
-
return
|
|
3404
|
+
} catch (err2) {
|
|
3405
|
+
return err2.code === "EPERM";
|
|
3306
3406
|
}
|
|
3307
3407
|
}
|
|
3308
3408
|
function sweepStaleBaseplateFiles() {
|
|
@@ -3395,7 +3495,7 @@ function listStudioProcesses() {
|
|
|
3395
3495
|
return [];
|
|
3396
3496
|
let out = "";
|
|
3397
3497
|
try {
|
|
3398
|
-
out = powershell("Get-Process RobloxStudioBeta -ErrorAction SilentlyContinue |
|
|
3498
|
+
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
3499
|
} catch {
|
|
3400
3500
|
return [];
|
|
3401
3501
|
}
|
|
@@ -3459,7 +3559,7 @@ function buildStudioLaunchArgs(options) {
|
|
|
3459
3559
|
}
|
|
3460
3560
|
}
|
|
3461
3561
|
function delay(ms) {
|
|
3462
|
-
return new Promise((
|
|
3562
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
3463
3563
|
}
|
|
3464
3564
|
function basenameAny(filePath) {
|
|
3465
3565
|
return path2.basename(filePath.replace(/\\/g, "/"));
|
|
@@ -3477,6 +3577,8 @@ var init_studio_instance_manager = __esm({
|
|
|
3477
3577
|
StudioInstanceManager = class {
|
|
3478
3578
|
managedByInstanceId = /* @__PURE__ */ new Map();
|
|
3479
3579
|
pending = /* @__PURE__ */ new Set();
|
|
3580
|
+
monitors = /* @__PURE__ */ new Map();
|
|
3581
|
+
connectionTimers = /* @__PURE__ */ new Map();
|
|
3480
3582
|
registry;
|
|
3481
3583
|
processAdapter;
|
|
3482
3584
|
constructor(options = {}) {
|
|
@@ -3485,6 +3587,9 @@ var init_studio_instance_manager = __esm({
|
|
|
3485
3587
|
}
|
|
3486
3588
|
list() {
|
|
3487
3589
|
this.sweepRegistry();
|
|
3590
|
+
for (const record of [...this.managedByInstanceId.values(), ...this.pending]) {
|
|
3591
|
+
this.refresh(record);
|
|
3592
|
+
}
|
|
3488
3593
|
const records = [...this.managedByInstanceId.values(), ...this.pending];
|
|
3489
3594
|
for (const registryRecord of this.registry.listOpen(this.registrySweepOptions())) {
|
|
3490
3595
|
const record = this.fromRegistryRecord(registryRecord);
|
|
@@ -3493,27 +3598,82 @@ var init_studio_instance_manager = __esm({
|
|
|
3493
3598
|
}
|
|
3494
3599
|
records.push(record);
|
|
3495
3600
|
}
|
|
3496
|
-
return records.filter((instance, index, all) => all.indexOf(instance) === index);
|
|
3601
|
+
return records.filter((record) => record.closedAt === void 0).filter((instance, index, all) => all.indexOf(instance) === index);
|
|
3497
3602
|
}
|
|
3498
3603
|
get(instanceId) {
|
|
3499
3604
|
this.sweepRegistry();
|
|
3500
3605
|
const memoryRecord = this.managedByInstanceId.get(instanceId);
|
|
3501
3606
|
if (memoryRecord)
|
|
3502
|
-
return memoryRecord;
|
|
3503
|
-
const registryRecord = this.registry.
|
|
3504
|
-
return registryRecord ? this.fromRegistryRecord(registryRecord) : void 0;
|
|
3607
|
+
return this.refresh(memoryRecord);
|
|
3608
|
+
const registryRecord = this.registry.findAnyByInstanceId(instanceId);
|
|
3609
|
+
return registryRecord ? this.refresh(this.fromRegistryRecord(registryRecord)) : void 0;
|
|
3610
|
+
}
|
|
3611
|
+
getByLaunchId(launchId) {
|
|
3612
|
+
this.sweepRegistry();
|
|
3613
|
+
const memoryRecord = [...this.managedByInstanceId.values(), ...this.pending].find((record) => record.recordId === launchId);
|
|
3614
|
+
if (memoryRecord)
|
|
3615
|
+
return this.refresh(memoryRecord);
|
|
3616
|
+
const registryRecord = this.registry.findAnyByRecordId(launchId, this.registrySweepOptions());
|
|
3617
|
+
return registryRecord ? this.refresh(this.fromRegistryRecord(registryRecord)) : void 0;
|
|
3618
|
+
}
|
|
3619
|
+
pendingLaunches() {
|
|
3620
|
+
const now = Date.now();
|
|
3621
|
+
const records = [...this.pending];
|
|
3622
|
+
for (const registryRecord of this.registry.listOpenUnchecked()) {
|
|
3623
|
+
if (registryRecord.bootId !== this.getCurrentBootId())
|
|
3624
|
+
continue;
|
|
3625
|
+
if (records.some((record) => record.recordId === registryRecord.recordId))
|
|
3626
|
+
continue;
|
|
3627
|
+
records.push(this.fromRegistryRecord(registryRecord));
|
|
3628
|
+
}
|
|
3629
|
+
return records.filter((record) => record.instanceId === void 0).filter((record) => record.state === "launching").filter((record) => record.connectionDeadlineAt === void 0 || record.connectionDeadlineAt > now);
|
|
3505
3630
|
}
|
|
3506
3631
|
attachInstanceId(record, instanceId) {
|
|
3632
|
+
this.refresh(record);
|
|
3633
|
+
if (record.closedAt !== void 0 || record.state === "failed" || record.state === "exited")
|
|
3634
|
+
return;
|
|
3635
|
+
if (record.instanceId && record.instanceId !== instanceId)
|
|
3636
|
+
return;
|
|
3507
3637
|
record.instanceId = instanceId;
|
|
3638
|
+
record.state = "connected";
|
|
3639
|
+
record.connectedAt = record.connectedAt ?? Date.now();
|
|
3640
|
+
this.clearConnectionTimer(record);
|
|
3508
3641
|
this.pending.delete(record);
|
|
3509
3642
|
this.managedByInstanceId.set(instanceId, record);
|
|
3510
|
-
|
|
3511
|
-
|
|
3643
|
+
this.persist(record);
|
|
3644
|
+
}
|
|
3645
|
+
markFailed(record, reason) {
|
|
3646
|
+
this.refresh(record);
|
|
3647
|
+
if (record.closedAt !== void 0 || record.state !== "launching")
|
|
3648
|
+
return record;
|
|
3649
|
+
record.state = "failed";
|
|
3650
|
+
record.failedAt = Date.now();
|
|
3651
|
+
record.failureReason = reason;
|
|
3652
|
+
this.clearConnectionTimer(record);
|
|
3653
|
+
this.persist(record);
|
|
3654
|
+
return record;
|
|
3655
|
+
}
|
|
3656
|
+
refresh(record) {
|
|
3657
|
+
if (record.closedAt !== void 0)
|
|
3658
|
+
return record;
|
|
3659
|
+
const processId = record.nativeProcessId ?? record.spawnPid;
|
|
3660
|
+
const studioProcess = processId ? this.findProcessById(processId) : void 0;
|
|
3661
|
+
if (processId && (!studioProcess || !this.verifyProcessForRecord(record, studioProcess))) {
|
|
3662
|
+
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
3663
|
}
|
|
3664
|
+
if (record.state === "launching" && record.connectionDeadlineAt !== void 0 && Date.now() >= record.connectionDeadlineAt) {
|
|
3665
|
+
record.state = "failed";
|
|
3666
|
+
record.failedAt = Date.now();
|
|
3667
|
+
record.failureReason = "Studio launched, but the MCP plugin did not connect before timeout.";
|
|
3668
|
+
this.clearConnectionTimer(record);
|
|
3669
|
+
this.persist(record);
|
|
3670
|
+
}
|
|
3671
|
+
return record;
|
|
3513
3672
|
}
|
|
3514
3673
|
async launch(options) {
|
|
3515
3674
|
this.sweepRegistry();
|
|
3516
3675
|
const preparedOptions = prepareStudioLaunchOptions(options);
|
|
3676
|
+
const bootId = this.getCurrentBootId();
|
|
3517
3677
|
const before = new Set(this.listStudioProcesses().map((proc2) => proc2.Id));
|
|
3518
3678
|
const exe = this.processAdapter.resolveStudioExe?.() ?? resolveStudioExe();
|
|
3519
3679
|
const args = buildStudioLaunchArgs(preparedOptions).map(toStudioLaunchArg);
|
|
@@ -3522,11 +3682,35 @@ var init_studio_instance_manager = __esm({
|
|
|
3522
3682
|
detached: true,
|
|
3523
3683
|
stdio: "ignore"
|
|
3524
3684
|
};
|
|
3525
|
-
|
|
3526
|
-
|
|
3685
|
+
let proc;
|
|
3686
|
+
try {
|
|
3687
|
+
if (this.processAdapter.spawnStudio) {
|
|
3688
|
+
proc = this.processAdapter.spawnStudio(exe, args, spawnOptions);
|
|
3689
|
+
} else if (isWsl()) {
|
|
3690
|
+
proc = spawnWindowsStudioFromWsl(exe, args);
|
|
3691
|
+
} else {
|
|
3692
|
+
const child = spawn(exe, args, spawnOptions);
|
|
3693
|
+
proc = {
|
|
3694
|
+
pid: child.pid,
|
|
3695
|
+
nativePid: child.pid,
|
|
3696
|
+
unref: () => child.unref(),
|
|
3697
|
+
onExit: (listener) => {
|
|
3698
|
+
child.once("exit", listener);
|
|
3699
|
+
},
|
|
3700
|
+
onError: (listener) => {
|
|
3701
|
+
child.once("error", listener);
|
|
3702
|
+
}
|
|
3703
|
+
};
|
|
3704
|
+
}
|
|
3705
|
+
} catch (error) {
|
|
3706
|
+
cleanupManagedBaseplateFiles({ source: preparedOptions.source, localPlaceFile: preparedOptions.localPlaceFile });
|
|
3707
|
+
throw error;
|
|
3708
|
+
}
|
|
3527
3709
|
const record = {
|
|
3528
3710
|
recordId: randomUUID(),
|
|
3529
3711
|
source: options.source,
|
|
3712
|
+
nativeProcessId: proc.nativePid,
|
|
3713
|
+
nativeProcessStartedAt: proc.nativeStartedAt,
|
|
3530
3714
|
spawnPid: proc.pid,
|
|
3531
3715
|
exe,
|
|
3532
3716
|
args,
|
|
@@ -3535,29 +3719,74 @@ var init_studio_instance_manager = __esm({
|
|
|
3535
3719
|
placeVersion: preparedOptions.placeVersion,
|
|
3536
3720
|
localPlaceFile: preparedOptions.localPlaceFile,
|
|
3537
3721
|
launchedAt: Date.now(),
|
|
3722
|
+
connectionDeadlineAt: Date.now() + (options.connectionTimeoutMs ?? 12e4),
|
|
3723
|
+
state: "launching",
|
|
3538
3724
|
ownerPid: process.pid,
|
|
3539
|
-
bootId
|
|
3725
|
+
bootId,
|
|
3540
3726
|
deleteLocalPlaceFileOnClose: options.source === "baseplate"
|
|
3541
3727
|
};
|
|
3542
3728
|
this.pending.add(record);
|
|
3543
|
-
|
|
3729
|
+
try {
|
|
3730
|
+
this.persist(record);
|
|
3731
|
+
} catch (error) {
|
|
3732
|
+
this.pending.delete(record);
|
|
3733
|
+
const processId = record.nativeProcessId ?? record.spawnPid;
|
|
3734
|
+
let stopError;
|
|
3735
|
+
if (processId) {
|
|
3736
|
+
try {
|
|
3737
|
+
this.closeProcess(processId);
|
|
3738
|
+
} catch (caught) {
|
|
3739
|
+
stopError = caught;
|
|
3740
|
+
}
|
|
3741
|
+
}
|
|
3742
|
+
cleanupManagedBaseplateFiles(record);
|
|
3743
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
3744
|
+
const cleanupDetail = stopError ? ` The newly launched process could not be stopped: ${stopError instanceof Error ? stopError.message : String(stopError)}` : "";
|
|
3745
|
+
throw new Error(`Studio launched, but its managed-instance record could not be persisted: ${detail}.${cleanupDetail}`);
|
|
3746
|
+
}
|
|
3747
|
+
proc.unref();
|
|
3748
|
+
proc.onExit?.((code, signal) => {
|
|
3749
|
+
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.");
|
|
3750
|
+
});
|
|
3751
|
+
proc.onError?.((error) => {
|
|
3752
|
+
this.markFailed(record, `Studio process failed to start: ${error.message}`);
|
|
3753
|
+
});
|
|
3544
3754
|
const deadline = Date.now() + 5e3;
|
|
3545
3755
|
while (Date.now() < deadline && record.nativeProcessId === void 0) {
|
|
3546
3756
|
const created = this.listStudioProcesses().find((candidate) => !before.has(candidate.Id));
|
|
3547
3757
|
if (created) {
|
|
3548
3758
|
record.nativeProcessId = created.Id;
|
|
3549
|
-
|
|
3759
|
+
record.nativeProcessStartedAt = created.StartTimeUtcFileTime;
|
|
3760
|
+
this.persist(record);
|
|
3550
3761
|
break;
|
|
3551
3762
|
}
|
|
3552
3763
|
await delay(250);
|
|
3553
3764
|
}
|
|
3554
3765
|
if (record.nativeProcessId === void 0 && process.platform !== "win32" && !isWsl()) {
|
|
3555
3766
|
record.nativeProcessId = proc.pid;
|
|
3556
|
-
this.
|
|
3767
|
+
this.persist(record);
|
|
3557
3768
|
}
|
|
3769
|
+
if (record.nativeProcessId !== void 0 && record.nativeProcessStartedAt === void 0) {
|
|
3770
|
+
const nativeProcess = this.findProcessById(record.nativeProcessId);
|
|
3771
|
+
if (nativeProcess?.StartTimeUtcFileTime !== void 0) {
|
|
3772
|
+
record.nativeProcessStartedAt = nativeProcess.StartTimeUtcFileTime;
|
|
3773
|
+
this.persist(record);
|
|
3774
|
+
}
|
|
3775
|
+
}
|
|
3776
|
+
this.startMonitor(record);
|
|
3558
3777
|
return record;
|
|
3559
3778
|
}
|
|
3779
|
+
closeByLaunchId(launchId) {
|
|
3780
|
+
const record = this.getByLaunchId(launchId);
|
|
3781
|
+
if (!record)
|
|
3782
|
+
return { status: "not_found", launchId };
|
|
3783
|
+
if (record.closedAt !== void 0) {
|
|
3784
|
+
return { status: "already_closed", launchId, instanceId: record.instanceId };
|
|
3785
|
+
}
|
|
3786
|
+
return this.close(record);
|
|
3787
|
+
}
|
|
3560
3788
|
closeByInstanceId(instanceId) {
|
|
3789
|
+
this.sweepRegistry();
|
|
3561
3790
|
const memoryRecord = this.managedByInstanceId.get(instanceId);
|
|
3562
3791
|
if (memoryRecord)
|
|
3563
3792
|
return this.close(memoryRecord);
|
|
@@ -3568,33 +3797,24 @@ var init_studio_instance_manager = __esm({
|
|
|
3568
3797
|
}
|
|
3569
3798
|
if (registryRecord.closedAt !== void 0) {
|
|
3570
3799
|
this.cleanupManagedRecord(registryRecord);
|
|
3571
|
-
this.registry.delete(registryRecord.recordId);
|
|
3572
3800
|
this.registry.logEvent({
|
|
3573
3801
|
event: "registry_close_already_stopped",
|
|
3574
3802
|
recordId: registryRecord.recordId,
|
|
3575
3803
|
instanceId: registryRecord.instanceId,
|
|
3576
3804
|
source: registryRecord.source,
|
|
3577
3805
|
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"
|
|
3806
|
+
action: "retained_terminal_record_and_cleaned_baseplate"
|
|
3592
3807
|
});
|
|
3593
|
-
return { status: "already_closed", instanceId };
|
|
3808
|
+
return { status: "already_closed", launchId: registryRecord.recordId, instanceId };
|
|
3594
3809
|
}
|
|
3595
3810
|
return this.close(this.fromRegistryRecord(registryRecord));
|
|
3596
3811
|
}
|
|
3597
3812
|
close(record) {
|
|
3813
|
+
this.stopMonitor(record);
|
|
3814
|
+
this.refresh(record);
|
|
3815
|
+
if (record.closedAt !== void 0) {
|
|
3816
|
+
return { status: "already_closed", launchId: record.recordId, instanceId: record.instanceId };
|
|
3817
|
+
}
|
|
3598
3818
|
const processId = record.nativeProcessId ?? record.spawnPid;
|
|
3599
3819
|
if (!processId) {
|
|
3600
3820
|
throw new Error(`Cannot close ${record.instanceId ?? "Studio launch"} because its process id was not detected.`);
|
|
@@ -3602,8 +3822,7 @@ var init_studio_instance_manager = __esm({
|
|
|
3602
3822
|
const studioProcess = this.findProcessById(processId);
|
|
3603
3823
|
if (!studioProcess) {
|
|
3604
3824
|
this.cleanupManagedRecord(record);
|
|
3605
|
-
this.
|
|
3606
|
-
this.markClosedInRegistry(record);
|
|
3825
|
+
this.markProcessExited(record, void 0, record.failureReason);
|
|
3607
3826
|
this.registry.logEvent({
|
|
3608
3827
|
event: "registry_close_already_stopped",
|
|
3609
3828
|
recordId: record.recordId,
|
|
@@ -3612,7 +3831,7 @@ var init_studio_instance_manager = __esm({
|
|
|
3612
3831
|
reason: "pid_not_running",
|
|
3613
3832
|
action: "marked_closed_and_cleaned_baseplate"
|
|
3614
3833
|
});
|
|
3615
|
-
return { status: "already_closed", instanceId: record.instanceId };
|
|
3834
|
+
return { status: "already_closed", launchId: record.recordId, instanceId: record.instanceId };
|
|
3616
3835
|
}
|
|
3617
3836
|
if (!this.verifyProcessForRecord(record, studioProcess)) {
|
|
3618
3837
|
this.registry.logEvent({
|
|
@@ -3638,15 +3857,18 @@ var init_studio_instance_manager = __esm({
|
|
|
3638
3857
|
action: "marked_closed_and_cleaned_baseplate"
|
|
3639
3858
|
});
|
|
3640
3859
|
this.cleanupManagedRecord(record);
|
|
3641
|
-
this.
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
record.closedAt =
|
|
3860
|
+
this.markProcessExited(record, void 0, record.failureReason);
|
|
3861
|
+
return { status: "already_closed", launchId: record.recordId, instanceId: record.instanceId };
|
|
3862
|
+
}
|
|
3863
|
+
const closedAt = Date.now();
|
|
3864
|
+
record.closedAt = closedAt;
|
|
3865
|
+
record.exitedAt = record.exitedAt ?? closedAt;
|
|
3866
|
+
if (record.state !== "failed")
|
|
3867
|
+
record.state = "exited";
|
|
3646
3868
|
this.cleanupManagedRecord(record);
|
|
3647
3869
|
this.markClosedInMemory(record);
|
|
3648
|
-
this.
|
|
3649
|
-
return { status: "closed", instanceId: record.instanceId };
|
|
3870
|
+
this.persist(record);
|
|
3871
|
+
return { status: "closed", launchId: record.recordId, instanceId: record.instanceId };
|
|
3650
3872
|
}
|
|
3651
3873
|
closeConnectedInstance(instance) {
|
|
3652
3874
|
const process2 = this.findProcessForConnectedInstance(instance);
|
|
@@ -3721,6 +3943,9 @@ var init_studio_instance_manager = __esm({
|
|
|
3721
3943
|
const processName = `${studioProcess.Name ?? ""} ${studioProcess.Path ?? ""}`.toLowerCase();
|
|
3722
3944
|
if (!processName.includes("robloxstudio"))
|
|
3723
3945
|
return false;
|
|
3946
|
+
if (record.nativeProcessStartedAt !== void 0 && studioProcess.StartTimeUtcFileTime !== record.nativeProcessStartedAt) {
|
|
3947
|
+
return false;
|
|
3948
|
+
}
|
|
3724
3949
|
const processId = record.nativeProcessId ?? record.spawnPid;
|
|
3725
3950
|
if (record.spawnPid && record.spawnPid === processId && studioProcess.Id === processId)
|
|
3726
3951
|
return true;
|
|
@@ -3746,10 +3971,64 @@ var init_studio_instance_manager = __esm({
|
|
|
3746
3971
|
if (record.instanceId)
|
|
3747
3972
|
this.managedByInstanceId.delete(record.instanceId);
|
|
3748
3973
|
this.pending.delete(record);
|
|
3974
|
+
this.stopMonitor(record);
|
|
3975
|
+
}
|
|
3976
|
+
markProcessExited(record, exitCode, reason) {
|
|
3977
|
+
if (record.closedAt !== void 0)
|
|
3978
|
+
return record;
|
|
3979
|
+
const exitedAt = Date.now();
|
|
3980
|
+
record.exitedAt = exitedAt;
|
|
3981
|
+
record.closedAt = exitedAt;
|
|
3982
|
+
if (record.state !== "failed")
|
|
3983
|
+
record.state = "exited";
|
|
3984
|
+
if (exitCode !== void 0)
|
|
3985
|
+
record.exitCode = exitCode;
|
|
3986
|
+
if (reason)
|
|
3987
|
+
record.failureReason = reason;
|
|
3988
|
+
this.cleanupManagedRecord(record);
|
|
3989
|
+
this.markClosedInMemory(record);
|
|
3990
|
+
this.persist(record);
|
|
3991
|
+
return record;
|
|
3749
3992
|
}
|
|
3750
|
-
|
|
3751
|
-
if (record.recordId)
|
|
3752
|
-
|
|
3993
|
+
startMonitor(record) {
|
|
3994
|
+
if (!record.recordId || record.closedAt !== void 0 || this.monitors.has(record.recordId))
|
|
3995
|
+
return;
|
|
3996
|
+
if (record.state === "launching" && record.connectionDeadlineAt !== void 0) {
|
|
3997
|
+
const timeout = setTimeout(() => {
|
|
3998
|
+
this.markFailed(record, "Studio launched, but the MCP plugin did not connect before timeout.");
|
|
3999
|
+
}, Math.max(0, record.connectionDeadlineAt - Date.now()));
|
|
4000
|
+
if (typeof timeout === "object" && "unref" in timeout)
|
|
4001
|
+
timeout.unref();
|
|
4002
|
+
this.connectionTimers.set(record.recordId, timeout);
|
|
4003
|
+
}
|
|
4004
|
+
const timer = setInterval(() => {
|
|
4005
|
+
this.refresh(record);
|
|
4006
|
+
if (record.closedAt !== void 0)
|
|
4007
|
+
this.stopMonitor(record);
|
|
4008
|
+
}, 5e3);
|
|
4009
|
+
if (typeof timer === "object" && "unref" in timer)
|
|
4010
|
+
timer.unref();
|
|
4011
|
+
this.monitors.set(record.recordId, timer);
|
|
4012
|
+
}
|
|
4013
|
+
stopMonitor(record) {
|
|
4014
|
+
if (!record.recordId)
|
|
4015
|
+
return;
|
|
4016
|
+
const timer = this.monitors.get(record.recordId);
|
|
4017
|
+
if (timer)
|
|
4018
|
+
clearInterval(timer);
|
|
4019
|
+
this.monitors.delete(record.recordId);
|
|
4020
|
+
this.clearConnectionTimer(record);
|
|
4021
|
+
}
|
|
4022
|
+
clearConnectionTimer(record) {
|
|
4023
|
+
if (!record.recordId)
|
|
4024
|
+
return;
|
|
4025
|
+
const timer = this.connectionTimers.get(record.recordId);
|
|
4026
|
+
if (timer)
|
|
4027
|
+
clearTimeout(timer);
|
|
4028
|
+
this.connectionTimers.delete(record.recordId);
|
|
4029
|
+
}
|
|
4030
|
+
persist(record) {
|
|
4031
|
+
this.registry.upsert(this.toRegistryRecord(record));
|
|
3753
4032
|
}
|
|
3754
4033
|
toRegistryRecord(record) {
|
|
3755
4034
|
if (!record.recordId)
|
|
@@ -3762,6 +4041,7 @@ var init_studio_instance_manager = __esm({
|
|
|
3762
4041
|
instanceId: record.instanceId,
|
|
3763
4042
|
source: record.source,
|
|
3764
4043
|
nativeProcessId: record.nativeProcessId,
|
|
4044
|
+
nativeProcessStartedAt: record.nativeProcessStartedAt,
|
|
3765
4045
|
spawnPid: record.spawnPid,
|
|
3766
4046
|
exe: record.exe,
|
|
3767
4047
|
args: record.args,
|
|
@@ -3771,18 +4051,26 @@ var init_studio_instance_manager = __esm({
|
|
|
3771
4051
|
localPlaceFile: record.localPlaceFile,
|
|
3772
4052
|
deleteLocalPlaceFileOnClose: record.deleteLocalPlaceFileOnClose,
|
|
3773
4053
|
launchedAt: record.launchedAt,
|
|
3774
|
-
attachedAt: record.
|
|
4054
|
+
attachedAt: record.connectedAt,
|
|
4055
|
+
connectionDeadlineAt: record.connectionDeadlineAt,
|
|
4056
|
+
state: record.state,
|
|
4057
|
+
failedAt: record.failedAt,
|
|
4058
|
+
exitedAt: record.exitedAt,
|
|
4059
|
+
exitCode: record.exitCode,
|
|
4060
|
+
failureReason: record.failureReason,
|
|
3775
4061
|
closedAt: record.closedAt,
|
|
3776
4062
|
ownerPid: record.ownerPid,
|
|
3777
4063
|
bootId: record.bootId
|
|
3778
4064
|
};
|
|
3779
4065
|
}
|
|
3780
4066
|
fromRegistryRecord(record) {
|
|
4067
|
+
const state = record.state ?? (record.closedAt !== void 0 ? "exited" : record.instanceId ? "connected" : "launching");
|
|
3781
4068
|
return {
|
|
3782
4069
|
recordId: record.recordId,
|
|
3783
4070
|
source: record.source,
|
|
3784
4071
|
instanceId: record.instanceId,
|
|
3785
4072
|
nativeProcessId: record.nativeProcessId,
|
|
4073
|
+
nativeProcessStartedAt: record.nativeProcessStartedAt,
|
|
3786
4074
|
spawnPid: record.spawnPid,
|
|
3787
4075
|
exe: record.exe,
|
|
3788
4076
|
args: record.args,
|
|
@@ -3791,6 +4079,13 @@ var init_studio_instance_manager = __esm({
|
|
|
3791
4079
|
placeVersion: record.placeVersion,
|
|
3792
4080
|
localPlaceFile: record.localPlaceFile,
|
|
3793
4081
|
launchedAt: record.launchedAt,
|
|
4082
|
+
connectionDeadlineAt: record.connectionDeadlineAt ?? (state === "launching" ? record.launchedAt + 12e4 : void 0),
|
|
4083
|
+
state,
|
|
4084
|
+
connectedAt: record.attachedAt,
|
|
4085
|
+
failedAt: record.failedAt,
|
|
4086
|
+
exitedAt: record.exitedAt,
|
|
4087
|
+
exitCode: record.exitCode,
|
|
4088
|
+
failureReason: record.failureReason,
|
|
3794
4089
|
closedAt: record.closedAt,
|
|
3795
4090
|
ownerPid: record.ownerPid,
|
|
3796
4091
|
bootId: record.bootId,
|
|
@@ -3976,6 +4271,982 @@ var init_image_decode = __esm({
|
|
|
3976
4271
|
}
|
|
3977
4272
|
});
|
|
3978
4273
|
|
|
4274
|
+
// ../../node_modules/fzstd/esm/index.mjs
|
|
4275
|
+
function decompress(dat, buf) {
|
|
4276
|
+
var bufs = [], nb = +!buf;
|
|
4277
|
+
var bt = 0, ol = 0;
|
|
4278
|
+
for (; dat.length; ) {
|
|
4279
|
+
var st = rzfh(dat, nb || buf);
|
|
4280
|
+
if (typeof st == "object") {
|
|
4281
|
+
if (nb) {
|
|
4282
|
+
buf = null;
|
|
4283
|
+
if (st.w.length == st.u) {
|
|
4284
|
+
bufs.push(buf = st.w);
|
|
4285
|
+
ol += st.u;
|
|
4286
|
+
}
|
|
4287
|
+
} else {
|
|
4288
|
+
bufs.push(buf);
|
|
4289
|
+
st.e = 0;
|
|
4290
|
+
}
|
|
4291
|
+
for (; !st.l; ) {
|
|
4292
|
+
var blk = rzb(dat, st, buf);
|
|
4293
|
+
if (!blk)
|
|
4294
|
+
err(5);
|
|
4295
|
+
if (buf)
|
|
4296
|
+
st.e = st.y;
|
|
4297
|
+
else {
|
|
4298
|
+
bufs.push(blk);
|
|
4299
|
+
ol += blk.length;
|
|
4300
|
+
cpw(st.w, 0, blk.length);
|
|
4301
|
+
st.w.set(blk, st.w.length - blk.length);
|
|
4302
|
+
}
|
|
4303
|
+
}
|
|
4304
|
+
bt = st.b + st.c * 4;
|
|
4305
|
+
} else
|
|
4306
|
+
bt = st;
|
|
4307
|
+
dat = dat.subarray(bt);
|
|
4308
|
+
}
|
|
4309
|
+
return cct(bufs, ol);
|
|
4310
|
+
}
|
|
4311
|
+
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;
|
|
4312
|
+
var init_esm = __esm({
|
|
4313
|
+
"../../node_modules/fzstd/esm/index.mjs"() {
|
|
4314
|
+
"use strict";
|
|
4315
|
+
ab = ArrayBuffer;
|
|
4316
|
+
u8 = Uint8Array;
|
|
4317
|
+
u16 = Uint16Array;
|
|
4318
|
+
i16 = Int16Array;
|
|
4319
|
+
i32 = Int32Array;
|
|
4320
|
+
slc = function(v, s, e) {
|
|
4321
|
+
if (u8.prototype.slice)
|
|
4322
|
+
return u8.prototype.slice.call(v, s, e);
|
|
4323
|
+
if (s == null || s < 0)
|
|
4324
|
+
s = 0;
|
|
4325
|
+
if (e == null || e > v.length)
|
|
4326
|
+
e = v.length;
|
|
4327
|
+
var n = new u8(e - s);
|
|
4328
|
+
n.set(v.subarray(s, e));
|
|
4329
|
+
return n;
|
|
4330
|
+
};
|
|
4331
|
+
fill = function(v, n, s, e) {
|
|
4332
|
+
if (u8.prototype.fill)
|
|
4333
|
+
return u8.prototype.fill.call(v, n, s, e);
|
|
4334
|
+
if (s == null || s < 0)
|
|
4335
|
+
s = 0;
|
|
4336
|
+
if (e == null || e > v.length)
|
|
4337
|
+
e = v.length;
|
|
4338
|
+
for (; s < e; ++s)
|
|
4339
|
+
v[s] = n;
|
|
4340
|
+
return v;
|
|
4341
|
+
};
|
|
4342
|
+
cpw = function(v, t, s, e) {
|
|
4343
|
+
if (u8.prototype.copyWithin)
|
|
4344
|
+
return u8.prototype.copyWithin.call(v, t, s, e);
|
|
4345
|
+
if (s == null || s < 0)
|
|
4346
|
+
s = 0;
|
|
4347
|
+
if (e == null || e > v.length)
|
|
4348
|
+
e = v.length;
|
|
4349
|
+
while (s < e) {
|
|
4350
|
+
v[t++] = v[s++];
|
|
4351
|
+
}
|
|
4352
|
+
};
|
|
4353
|
+
ec = [
|
|
4354
|
+
"invalid zstd data",
|
|
4355
|
+
"window size too large (>2046MB)",
|
|
4356
|
+
"invalid block type",
|
|
4357
|
+
"FSE accuracy too high",
|
|
4358
|
+
"match distance too far back",
|
|
4359
|
+
"unexpected EOF"
|
|
4360
|
+
];
|
|
4361
|
+
err = function(ind, msg, nt) {
|
|
4362
|
+
var e = new Error(msg || ec[ind]);
|
|
4363
|
+
e.code = ind;
|
|
4364
|
+
if (Error.captureStackTrace)
|
|
4365
|
+
Error.captureStackTrace(e, err);
|
|
4366
|
+
if (!nt)
|
|
4367
|
+
throw e;
|
|
4368
|
+
return e;
|
|
4369
|
+
};
|
|
4370
|
+
rb = function(d, b, n) {
|
|
4371
|
+
var i = 0, o = 0;
|
|
4372
|
+
for (; i < n; ++i)
|
|
4373
|
+
o |= d[b++] << (i << 3);
|
|
4374
|
+
return o;
|
|
4375
|
+
};
|
|
4376
|
+
b4 = function(d, b) {
|
|
4377
|
+
return (d[b] | d[b + 1] << 8 | d[b + 2] << 16 | d[b + 3] << 24) >>> 0;
|
|
4378
|
+
};
|
|
4379
|
+
rzfh = function(dat, w) {
|
|
4380
|
+
var n3 = dat[0] | dat[1] << 8 | dat[2] << 16;
|
|
4381
|
+
if (n3 == 3126568 && dat[3] == 253) {
|
|
4382
|
+
var flg = dat[4];
|
|
4383
|
+
var ss = flg >> 5 & 1, cc = flg >> 2 & 1, df = flg & 3, fcf = flg >> 6;
|
|
4384
|
+
if (flg & 8)
|
|
4385
|
+
err(0);
|
|
4386
|
+
var bt = 6 - ss;
|
|
4387
|
+
var db = df == 3 ? 4 : df;
|
|
4388
|
+
var di = rb(dat, bt, db);
|
|
4389
|
+
bt += db;
|
|
4390
|
+
var fsb = fcf ? 1 << fcf : ss;
|
|
4391
|
+
var fss = rb(dat, bt, fsb) + (fcf == 1 && 256);
|
|
4392
|
+
var ws = fss;
|
|
4393
|
+
if (!ss) {
|
|
4394
|
+
var wb = 1 << 10 + (dat[5] >> 3);
|
|
4395
|
+
ws = wb + (wb >> 3) * (dat[5] & 7);
|
|
4396
|
+
}
|
|
4397
|
+
if (ws > 2145386496)
|
|
4398
|
+
err(1);
|
|
4399
|
+
var buf = new u8((w == 1 ? fss || ws : w ? 0 : ws) + 12);
|
|
4400
|
+
buf[0] = 1, buf[4] = 4, buf[8] = 8;
|
|
4401
|
+
return {
|
|
4402
|
+
b: bt + fsb,
|
|
4403
|
+
y: 0,
|
|
4404
|
+
l: 0,
|
|
4405
|
+
d: di,
|
|
4406
|
+
w: w && w != 1 ? w : buf.subarray(12),
|
|
4407
|
+
e: ws,
|
|
4408
|
+
o: new i32(buf.buffer, 0, 3),
|
|
4409
|
+
u: fss,
|
|
4410
|
+
c: cc,
|
|
4411
|
+
m: Math.min(131072, ws)
|
|
4412
|
+
};
|
|
4413
|
+
} else if ((n3 >> 4 | dat[3] << 20) == 25481893) {
|
|
4414
|
+
return b4(dat, 4) + 8;
|
|
4415
|
+
}
|
|
4416
|
+
err(0);
|
|
4417
|
+
};
|
|
4418
|
+
msb = function(val) {
|
|
4419
|
+
var bits = 0;
|
|
4420
|
+
for (; 1 << bits <= val; ++bits)
|
|
4421
|
+
;
|
|
4422
|
+
return bits - 1;
|
|
4423
|
+
};
|
|
4424
|
+
rfse = function(dat, bt, mal) {
|
|
4425
|
+
var tpos = (bt << 3) + 4;
|
|
4426
|
+
var al = (dat[bt] & 15) + 5;
|
|
4427
|
+
if (al > mal)
|
|
4428
|
+
err(3);
|
|
4429
|
+
var sz = 1 << al;
|
|
4430
|
+
var probs = sz, sym = -1, re = -1, i = -1, ht = sz;
|
|
4431
|
+
var buf = new ab(512 + (sz << 2));
|
|
4432
|
+
var freq = new i16(buf, 0, 256);
|
|
4433
|
+
var dstate = new u16(buf, 0, 256);
|
|
4434
|
+
var nstate = new u16(buf, 512, sz);
|
|
4435
|
+
var bb1 = 512 + (sz << 1);
|
|
4436
|
+
var syms = new u8(buf, bb1, sz);
|
|
4437
|
+
var nbits = new u8(buf, bb1 + sz);
|
|
4438
|
+
while (sym < 255 && probs > 0) {
|
|
4439
|
+
var bits = msb(probs + 1);
|
|
4440
|
+
var cbt = tpos >> 3;
|
|
4441
|
+
var msk = (1 << bits + 1) - 1;
|
|
4442
|
+
var val = (dat[cbt] | dat[cbt + 1] << 8 | dat[cbt + 2] << 16) >> (tpos & 7) & msk;
|
|
4443
|
+
var msk1fb = (1 << bits) - 1;
|
|
4444
|
+
var msv = msk - probs - 1;
|
|
4445
|
+
var sval = val & msk1fb;
|
|
4446
|
+
if (sval < msv)
|
|
4447
|
+
tpos += bits, val = sval;
|
|
4448
|
+
else {
|
|
4449
|
+
tpos += bits + 1;
|
|
4450
|
+
if (val > msk1fb)
|
|
4451
|
+
val -= msv;
|
|
4452
|
+
}
|
|
4453
|
+
freq[++sym] = --val;
|
|
4454
|
+
if (val == -1) {
|
|
4455
|
+
probs += val;
|
|
4456
|
+
syms[--ht] = sym;
|
|
4457
|
+
} else
|
|
4458
|
+
probs -= val;
|
|
4459
|
+
if (!val) {
|
|
4460
|
+
do {
|
|
4461
|
+
var rbt = tpos >> 3;
|
|
4462
|
+
re = (dat[rbt] | dat[rbt + 1] << 8) >> (tpos & 7) & 3;
|
|
4463
|
+
tpos += 2;
|
|
4464
|
+
sym += re;
|
|
4465
|
+
} while (re == 3);
|
|
4466
|
+
}
|
|
4467
|
+
}
|
|
4468
|
+
if (sym > 255 || probs)
|
|
4469
|
+
err(0);
|
|
4470
|
+
var sympos = 0;
|
|
4471
|
+
var sstep = (sz >> 1) + (sz >> 3) + 3;
|
|
4472
|
+
var smask = sz - 1;
|
|
4473
|
+
for (var s = 0; s <= sym; ++s) {
|
|
4474
|
+
var sf = freq[s];
|
|
4475
|
+
if (sf < 1) {
|
|
4476
|
+
dstate[s] = -sf;
|
|
4477
|
+
continue;
|
|
4478
|
+
}
|
|
4479
|
+
for (i = 0; i < sf; ++i) {
|
|
4480
|
+
syms[sympos] = s;
|
|
4481
|
+
do {
|
|
4482
|
+
sympos = sympos + sstep & smask;
|
|
4483
|
+
} while (sympos >= ht);
|
|
4484
|
+
}
|
|
4485
|
+
}
|
|
4486
|
+
if (sympos)
|
|
4487
|
+
err(0);
|
|
4488
|
+
for (i = 0; i < sz; ++i) {
|
|
4489
|
+
var ns = dstate[syms[i]]++;
|
|
4490
|
+
var nb = nbits[i] = al - msb(ns);
|
|
4491
|
+
nstate[i] = (ns << nb) - sz;
|
|
4492
|
+
}
|
|
4493
|
+
return [tpos + 7 >> 3, {
|
|
4494
|
+
b: al,
|
|
4495
|
+
s: syms,
|
|
4496
|
+
n: nbits,
|
|
4497
|
+
t: nstate
|
|
4498
|
+
}];
|
|
4499
|
+
};
|
|
4500
|
+
rhu = function(dat, bt) {
|
|
4501
|
+
var i = 0, wc = -1;
|
|
4502
|
+
var buf = new u8(292), hb = dat[bt];
|
|
4503
|
+
var hw = buf.subarray(0, 256);
|
|
4504
|
+
var rc = buf.subarray(256, 268);
|
|
4505
|
+
var ri = new u16(buf.buffer, 268);
|
|
4506
|
+
if (hb < 128) {
|
|
4507
|
+
var _a = rfse(dat, bt + 1, 6), ebt = _a[0], fdt = _a[1];
|
|
4508
|
+
bt += hb;
|
|
4509
|
+
var epos = ebt << 3;
|
|
4510
|
+
var lb = dat[bt];
|
|
4511
|
+
if (!lb)
|
|
4512
|
+
err(0);
|
|
4513
|
+
var st1 = 0, st2 = 0, btr1 = fdt.b, btr2 = btr1;
|
|
4514
|
+
var fpos = (++bt << 3) - 8 + msb(lb);
|
|
4515
|
+
for (; ; ) {
|
|
4516
|
+
fpos -= btr1;
|
|
4517
|
+
if (fpos < epos)
|
|
4518
|
+
break;
|
|
4519
|
+
var cbt = fpos >> 3;
|
|
4520
|
+
st1 += (dat[cbt] | dat[cbt + 1] << 8) >> (fpos & 7) & (1 << btr1) - 1;
|
|
4521
|
+
hw[++wc] = fdt.s[st1];
|
|
4522
|
+
fpos -= btr2;
|
|
4523
|
+
if (fpos < epos)
|
|
4524
|
+
break;
|
|
4525
|
+
cbt = fpos >> 3;
|
|
4526
|
+
st2 += (dat[cbt] | dat[cbt + 1] << 8) >> (fpos & 7) & (1 << btr2) - 1;
|
|
4527
|
+
hw[++wc] = fdt.s[st2];
|
|
4528
|
+
btr1 = fdt.n[st1];
|
|
4529
|
+
st1 = fdt.t[st1];
|
|
4530
|
+
btr2 = fdt.n[st2];
|
|
4531
|
+
st2 = fdt.t[st2];
|
|
4532
|
+
}
|
|
4533
|
+
if (++wc > 255)
|
|
4534
|
+
err(0);
|
|
4535
|
+
} else {
|
|
4536
|
+
wc = hb - 127;
|
|
4537
|
+
for (; i < wc; i += 2) {
|
|
4538
|
+
var byte = dat[++bt];
|
|
4539
|
+
hw[i] = byte >> 4;
|
|
4540
|
+
hw[i + 1] = byte & 15;
|
|
4541
|
+
}
|
|
4542
|
+
++bt;
|
|
4543
|
+
}
|
|
4544
|
+
var wes = 0;
|
|
4545
|
+
for (i = 0; i < wc; ++i) {
|
|
4546
|
+
var wt = hw[i];
|
|
4547
|
+
if (wt > 11)
|
|
4548
|
+
err(0);
|
|
4549
|
+
wes += wt && 1 << wt - 1;
|
|
4550
|
+
}
|
|
4551
|
+
var mb = msb(wes) + 1;
|
|
4552
|
+
var ts = 1 << mb;
|
|
4553
|
+
var rem = ts - wes;
|
|
4554
|
+
if (rem & rem - 1)
|
|
4555
|
+
err(0);
|
|
4556
|
+
hw[wc++] = msb(rem) + 1;
|
|
4557
|
+
for (i = 0; i < wc; ++i) {
|
|
4558
|
+
var wt = hw[i];
|
|
4559
|
+
++rc[hw[i] = wt && mb + 1 - wt];
|
|
4560
|
+
}
|
|
4561
|
+
var hbuf = new u8(ts << 1);
|
|
4562
|
+
var syms = hbuf.subarray(0, ts), nb = hbuf.subarray(ts);
|
|
4563
|
+
ri[mb] = 0;
|
|
4564
|
+
for (i = mb; i > 0; --i) {
|
|
4565
|
+
var pv = ri[i];
|
|
4566
|
+
fill(nb, i, pv, ri[i - 1] = pv + rc[i] * (1 << mb - i));
|
|
4567
|
+
}
|
|
4568
|
+
if (ri[0] != ts)
|
|
4569
|
+
err(0);
|
|
4570
|
+
for (i = 0; i < wc; ++i) {
|
|
4571
|
+
var bits = hw[i];
|
|
4572
|
+
if (bits) {
|
|
4573
|
+
var code = ri[bits];
|
|
4574
|
+
fill(syms, i, code, ri[bits] = code + (1 << mb - bits));
|
|
4575
|
+
}
|
|
4576
|
+
}
|
|
4577
|
+
return [bt, {
|
|
4578
|
+
n: nb,
|
|
4579
|
+
b: mb,
|
|
4580
|
+
s: syms
|
|
4581
|
+
}];
|
|
4582
|
+
};
|
|
4583
|
+
dllt = rfse(/* @__PURE__ */ new u8([
|
|
4584
|
+
81,
|
|
4585
|
+
16,
|
|
4586
|
+
99,
|
|
4587
|
+
140,
|
|
4588
|
+
49,
|
|
4589
|
+
198,
|
|
4590
|
+
24,
|
|
4591
|
+
99,
|
|
4592
|
+
12,
|
|
4593
|
+
33,
|
|
4594
|
+
196,
|
|
4595
|
+
24,
|
|
4596
|
+
99,
|
|
4597
|
+
102,
|
|
4598
|
+
102,
|
|
4599
|
+
134,
|
|
4600
|
+
70,
|
|
4601
|
+
146,
|
|
4602
|
+
4
|
|
4603
|
+
]), 0, 6)[1];
|
|
4604
|
+
dmlt = rfse(/* @__PURE__ */ new u8([
|
|
4605
|
+
33,
|
|
4606
|
+
20,
|
|
4607
|
+
196,
|
|
4608
|
+
24,
|
|
4609
|
+
99,
|
|
4610
|
+
140,
|
|
4611
|
+
33,
|
|
4612
|
+
132,
|
|
4613
|
+
16,
|
|
4614
|
+
66,
|
|
4615
|
+
8,
|
|
4616
|
+
33,
|
|
4617
|
+
132,
|
|
4618
|
+
16,
|
|
4619
|
+
66,
|
|
4620
|
+
8,
|
|
4621
|
+
33,
|
|
4622
|
+
68,
|
|
4623
|
+
68,
|
|
4624
|
+
68,
|
|
4625
|
+
68,
|
|
4626
|
+
68,
|
|
4627
|
+
68,
|
|
4628
|
+
68,
|
|
4629
|
+
68,
|
|
4630
|
+
36,
|
|
4631
|
+
9
|
|
4632
|
+
]), 0, 6)[1];
|
|
4633
|
+
doct = rfse(/* @__PURE__ */ new u8([
|
|
4634
|
+
32,
|
|
4635
|
+
132,
|
|
4636
|
+
16,
|
|
4637
|
+
66,
|
|
4638
|
+
102,
|
|
4639
|
+
70,
|
|
4640
|
+
68,
|
|
4641
|
+
68,
|
|
4642
|
+
68,
|
|
4643
|
+
68,
|
|
4644
|
+
36,
|
|
4645
|
+
73,
|
|
4646
|
+
2
|
|
4647
|
+
]), 0, 5)[1];
|
|
4648
|
+
b2bl = function(b, s) {
|
|
4649
|
+
var len = b.length, bl = new i32(len);
|
|
4650
|
+
for (var i = 0; i < len; ++i) {
|
|
4651
|
+
bl[i] = s;
|
|
4652
|
+
s += 1 << b[i];
|
|
4653
|
+
}
|
|
4654
|
+
return bl;
|
|
4655
|
+
};
|
|
4656
|
+
llb = /* @__PURE__ */ new u8((/* @__PURE__ */ new i32([
|
|
4657
|
+
0,
|
|
4658
|
+
0,
|
|
4659
|
+
0,
|
|
4660
|
+
0,
|
|
4661
|
+
16843009,
|
|
4662
|
+
50528770,
|
|
4663
|
+
134678020,
|
|
4664
|
+
202050057,
|
|
4665
|
+
269422093
|
|
4666
|
+
])).buffer, 0, 36);
|
|
4667
|
+
llbl = /* @__PURE__ */ b2bl(llb, 0);
|
|
4668
|
+
mlb = /* @__PURE__ */ new u8((/* @__PURE__ */ new i32([
|
|
4669
|
+
0,
|
|
4670
|
+
0,
|
|
4671
|
+
0,
|
|
4672
|
+
0,
|
|
4673
|
+
0,
|
|
4674
|
+
0,
|
|
4675
|
+
0,
|
|
4676
|
+
0,
|
|
4677
|
+
16843009,
|
|
4678
|
+
50528770,
|
|
4679
|
+
117769220,
|
|
4680
|
+
185207048,
|
|
4681
|
+
252579084,
|
|
4682
|
+
16
|
|
4683
|
+
])).buffer, 0, 53);
|
|
4684
|
+
mlbl = /* @__PURE__ */ b2bl(mlb, 3);
|
|
4685
|
+
dhu = function(dat, out, hu) {
|
|
4686
|
+
var len = dat.length, ss = out.length, lb = dat[len - 1], msk = (1 << hu.b) - 1, eb = -hu.b;
|
|
4687
|
+
if (!lb)
|
|
4688
|
+
err(0);
|
|
4689
|
+
var st = 0, btr = hu.b, pos = (len << 3) - 8 + msb(lb) - btr, i = -1;
|
|
4690
|
+
for (; pos > eb && i < ss; ) {
|
|
4691
|
+
var cbt = pos >> 3;
|
|
4692
|
+
var val = (dat[cbt] | dat[cbt + 1] << 8 | dat[cbt + 2] << 16) >> (pos & 7);
|
|
4693
|
+
st = (st << btr | val) & msk;
|
|
4694
|
+
out[++i] = hu.s[st];
|
|
4695
|
+
pos -= btr = hu.n[st];
|
|
4696
|
+
}
|
|
4697
|
+
if (pos != eb || i + 1 != ss)
|
|
4698
|
+
err(0);
|
|
4699
|
+
};
|
|
4700
|
+
dhu4 = function(dat, out, hu) {
|
|
4701
|
+
var bt = 6;
|
|
4702
|
+
var ss = out.length, sz1 = ss + 3 >> 2, sz2 = sz1 << 1, sz3 = sz1 + sz2;
|
|
4703
|
+
dhu(dat.subarray(bt, bt += dat[0] | dat[1] << 8), out.subarray(0, sz1), hu);
|
|
4704
|
+
dhu(dat.subarray(bt, bt += dat[2] | dat[3] << 8), out.subarray(sz1, sz2), hu);
|
|
4705
|
+
dhu(dat.subarray(bt, bt += dat[4] | dat[5] << 8), out.subarray(sz2, sz3), hu);
|
|
4706
|
+
dhu(dat.subarray(bt), out.subarray(sz3), hu);
|
|
4707
|
+
};
|
|
4708
|
+
rzb = function(dat, st, out) {
|
|
4709
|
+
var _a;
|
|
4710
|
+
var bt = st.b;
|
|
4711
|
+
var b0 = dat[bt], btype = b0 >> 1 & 3;
|
|
4712
|
+
st.l = b0 & 1;
|
|
4713
|
+
var sz = b0 >> 3 | dat[bt + 1] << 5 | dat[bt + 2] << 13;
|
|
4714
|
+
var ebt = (bt += 3) + sz;
|
|
4715
|
+
if (btype == 1) {
|
|
4716
|
+
if (bt >= dat.length)
|
|
4717
|
+
return;
|
|
4718
|
+
st.b = bt + 1;
|
|
4719
|
+
if (out) {
|
|
4720
|
+
fill(out, dat[bt], st.y, st.y += sz);
|
|
4721
|
+
return out;
|
|
4722
|
+
}
|
|
4723
|
+
return fill(new u8(sz), dat[bt]);
|
|
4724
|
+
}
|
|
4725
|
+
if (ebt > dat.length)
|
|
4726
|
+
return;
|
|
4727
|
+
if (btype == 0) {
|
|
4728
|
+
st.b = ebt;
|
|
4729
|
+
if (out) {
|
|
4730
|
+
out.set(dat.subarray(bt, ebt), st.y);
|
|
4731
|
+
st.y += sz;
|
|
4732
|
+
return out;
|
|
4733
|
+
}
|
|
4734
|
+
return slc(dat, bt, ebt);
|
|
4735
|
+
}
|
|
4736
|
+
if (btype == 2) {
|
|
4737
|
+
var b3 = dat[bt], lbt = b3 & 3, sf = b3 >> 2 & 3;
|
|
4738
|
+
var lss = b3 >> 4, lcs = 0, s4 = 0;
|
|
4739
|
+
if (lbt < 2) {
|
|
4740
|
+
if (sf & 1)
|
|
4741
|
+
lss |= dat[++bt] << 4 | (sf & 2 && dat[++bt] << 12);
|
|
4742
|
+
else
|
|
4743
|
+
lss = b3 >> 3;
|
|
4744
|
+
} else {
|
|
4745
|
+
s4 = sf;
|
|
4746
|
+
if (sf < 2)
|
|
4747
|
+
lss |= (dat[++bt] & 63) << 4, lcs = dat[bt] >> 6 | dat[++bt] << 2;
|
|
4748
|
+
else if (sf == 2)
|
|
4749
|
+
lss |= dat[++bt] << 4 | (dat[++bt] & 3) << 12, lcs = dat[bt] >> 2 | dat[++bt] << 6;
|
|
4750
|
+
else
|
|
4751
|
+
lss |= dat[++bt] << 4 | (dat[++bt] & 63) << 12, lcs = dat[bt] >> 6 | dat[++bt] << 2 | dat[++bt] << 10;
|
|
4752
|
+
}
|
|
4753
|
+
++bt;
|
|
4754
|
+
var buf = out ? out.subarray(st.y, st.y + st.m) : new u8(st.m);
|
|
4755
|
+
var spl = buf.length - lss;
|
|
4756
|
+
if (lbt == 0)
|
|
4757
|
+
buf.set(dat.subarray(bt, bt += lss), spl);
|
|
4758
|
+
else if (lbt == 1)
|
|
4759
|
+
fill(buf, dat[bt++], spl);
|
|
4760
|
+
else {
|
|
4761
|
+
var hu = st.h;
|
|
4762
|
+
if (lbt == 2) {
|
|
4763
|
+
var hud = rhu(dat, bt);
|
|
4764
|
+
lcs += bt - (bt = hud[0]);
|
|
4765
|
+
st.h = hu = hud[1];
|
|
4766
|
+
} else if (!hu)
|
|
4767
|
+
err(0);
|
|
4768
|
+
(s4 ? dhu4 : dhu)(dat.subarray(bt, bt += lcs), buf.subarray(spl), hu);
|
|
4769
|
+
}
|
|
4770
|
+
var ns = dat[bt++];
|
|
4771
|
+
if (ns) {
|
|
4772
|
+
if (ns == 255)
|
|
4773
|
+
ns = (dat[bt++] | dat[bt++] << 8) + 32512;
|
|
4774
|
+
else if (ns > 127)
|
|
4775
|
+
ns = ns - 128 << 8 | dat[bt++];
|
|
4776
|
+
var scm = dat[bt++];
|
|
4777
|
+
if (scm & 3)
|
|
4778
|
+
err(0);
|
|
4779
|
+
var dts = [dmlt, doct, dllt];
|
|
4780
|
+
for (var i = 2; i > -1; --i) {
|
|
4781
|
+
var md = scm >> (i << 1) + 2 & 3;
|
|
4782
|
+
if (md == 1) {
|
|
4783
|
+
var rbuf = new u8([0, 0, dat[bt++]]);
|
|
4784
|
+
dts[i] = {
|
|
4785
|
+
s: rbuf.subarray(2, 3),
|
|
4786
|
+
n: rbuf.subarray(0, 1),
|
|
4787
|
+
t: new u16(rbuf.buffer, 0, 1),
|
|
4788
|
+
b: 0
|
|
4789
|
+
};
|
|
4790
|
+
} else if (md == 2) {
|
|
4791
|
+
_a = rfse(dat, bt, 9 - (i & 1)), bt = _a[0], dts[i] = _a[1];
|
|
4792
|
+
} else if (md == 3) {
|
|
4793
|
+
if (!st.t)
|
|
4794
|
+
err(0);
|
|
4795
|
+
dts[i] = st.t[i];
|
|
4796
|
+
}
|
|
4797
|
+
}
|
|
4798
|
+
var _b = st.t = dts, mlt = _b[0], oct = _b[1], llt = _b[2];
|
|
4799
|
+
var lb = dat[ebt - 1];
|
|
4800
|
+
if (!lb)
|
|
4801
|
+
err(0);
|
|
4802
|
+
var spos = (ebt << 3) - 8 + msb(lb) - llt.b, cbt = spos >> 3, oubt = 0;
|
|
4803
|
+
var lst = (dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7) & (1 << llt.b) - 1;
|
|
4804
|
+
cbt = (spos -= oct.b) >> 3;
|
|
4805
|
+
var ost = (dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7) & (1 << oct.b) - 1;
|
|
4806
|
+
cbt = (spos -= mlt.b) >> 3;
|
|
4807
|
+
var mst = (dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7) & (1 << mlt.b) - 1;
|
|
4808
|
+
for (++ns; --ns; ) {
|
|
4809
|
+
var llc = llt.s[lst];
|
|
4810
|
+
var lbtr = llt.n[lst];
|
|
4811
|
+
var mlc = mlt.s[mst];
|
|
4812
|
+
var mbtr = mlt.n[mst];
|
|
4813
|
+
var ofc = oct.s[ost];
|
|
4814
|
+
var obtr = oct.n[ost];
|
|
4815
|
+
cbt = (spos -= ofc) >> 3;
|
|
4816
|
+
var ofp = 1 << ofc;
|
|
4817
|
+
var off = ofp + ((dat[cbt] | dat[cbt + 1] << 8 | dat[cbt + 2] << 16 | dat[cbt + 3] << 24) >>> (spos & 7) & ofp - 1);
|
|
4818
|
+
cbt = (spos -= mlb[mlc]) >> 3;
|
|
4819
|
+
var ml = mlbl[mlc] + ((dat[cbt] | dat[cbt + 1] << 8 | dat[cbt + 2] << 16) >> (spos & 7) & (1 << mlb[mlc]) - 1);
|
|
4820
|
+
cbt = (spos -= llb[llc]) >> 3;
|
|
4821
|
+
var ll = llbl[llc] + ((dat[cbt] | dat[cbt + 1] << 8 | dat[cbt + 2] << 16) >> (spos & 7) & (1 << llb[llc]) - 1);
|
|
4822
|
+
cbt = (spos -= lbtr) >> 3;
|
|
4823
|
+
lst = llt.t[lst] + ((dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7) & (1 << lbtr) - 1);
|
|
4824
|
+
cbt = (spos -= mbtr) >> 3;
|
|
4825
|
+
mst = mlt.t[mst] + ((dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7) & (1 << mbtr) - 1);
|
|
4826
|
+
cbt = (spos -= obtr) >> 3;
|
|
4827
|
+
ost = oct.t[ost] + ((dat[cbt] | dat[cbt + 1] << 8) >> (spos & 7) & (1 << obtr) - 1);
|
|
4828
|
+
if (off > 3) {
|
|
4829
|
+
st.o[2] = st.o[1];
|
|
4830
|
+
st.o[1] = st.o[0];
|
|
4831
|
+
st.o[0] = off -= 3;
|
|
4832
|
+
} else {
|
|
4833
|
+
var idx = off - (ll != 0);
|
|
4834
|
+
if (idx) {
|
|
4835
|
+
off = idx == 3 ? st.o[0] - 1 : st.o[idx];
|
|
4836
|
+
if (idx > 1)
|
|
4837
|
+
st.o[2] = st.o[1];
|
|
4838
|
+
st.o[1] = st.o[0];
|
|
4839
|
+
st.o[0] = off;
|
|
4840
|
+
} else
|
|
4841
|
+
off = st.o[0];
|
|
4842
|
+
}
|
|
4843
|
+
for (var i = 0; i < ll; ++i) {
|
|
4844
|
+
buf[oubt + i] = buf[spl + i];
|
|
4845
|
+
}
|
|
4846
|
+
oubt += ll, spl += ll;
|
|
4847
|
+
var stin = oubt - off;
|
|
4848
|
+
if (stin < 0) {
|
|
4849
|
+
var len = -stin;
|
|
4850
|
+
var bs = st.e + stin;
|
|
4851
|
+
if (len > ml)
|
|
4852
|
+
len = ml;
|
|
4853
|
+
for (var i = 0; i < len; ++i) {
|
|
4854
|
+
buf[oubt + i] = st.w[bs + i];
|
|
4855
|
+
}
|
|
4856
|
+
oubt += len, ml -= len, stin = 0;
|
|
4857
|
+
}
|
|
4858
|
+
for (var i = 0; i < ml; ++i) {
|
|
4859
|
+
buf[oubt + i] = buf[stin + i];
|
|
4860
|
+
}
|
|
4861
|
+
oubt += ml;
|
|
4862
|
+
}
|
|
4863
|
+
if (oubt != spl) {
|
|
4864
|
+
while (spl < buf.length) {
|
|
4865
|
+
buf[oubt++] = buf[spl++];
|
|
4866
|
+
}
|
|
4867
|
+
} else
|
|
4868
|
+
oubt = buf.length;
|
|
4869
|
+
if (out)
|
|
4870
|
+
st.y += oubt;
|
|
4871
|
+
else
|
|
4872
|
+
buf = slc(buf, 0, oubt);
|
|
4873
|
+
} else if (out) {
|
|
4874
|
+
st.y += lss;
|
|
4875
|
+
if (spl) {
|
|
4876
|
+
for (var i = 0; i < lss; ++i) {
|
|
4877
|
+
buf[i] = buf[spl + i];
|
|
4878
|
+
}
|
|
4879
|
+
}
|
|
4880
|
+
} else if (spl)
|
|
4881
|
+
buf = slc(buf, spl);
|
|
4882
|
+
st.b = ebt;
|
|
4883
|
+
return buf;
|
|
4884
|
+
}
|
|
4885
|
+
err(2);
|
|
4886
|
+
};
|
|
4887
|
+
cct = function(bufs, ol) {
|
|
4888
|
+
if (bufs.length == 1)
|
|
4889
|
+
return bufs[0];
|
|
4890
|
+
var buf = new u8(ol);
|
|
4891
|
+
for (var i = 0, b = 0; i < bufs.length; ++i) {
|
|
4892
|
+
var chk = bufs[i];
|
|
4893
|
+
buf.set(chk, b);
|
|
4894
|
+
b += chk.length;
|
|
4895
|
+
}
|
|
4896
|
+
return buf;
|
|
4897
|
+
};
|
|
4898
|
+
}
|
|
4899
|
+
});
|
|
4900
|
+
|
|
4901
|
+
// ../core/dist/studio-skills.js
|
|
4902
|
+
import { createHash as createHash2 } from "crypto";
|
|
4903
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
|
|
4904
|
+
import * as path4 from "path";
|
|
4905
|
+
function decompressLz4Block(input, outputLength) {
|
|
4906
|
+
const output = Buffer.allocUnsafe(outputLength);
|
|
4907
|
+
let inputOffset = 0;
|
|
4908
|
+
let outputOffset = 0;
|
|
4909
|
+
const readExtendedLength = (initial) => {
|
|
4910
|
+
let length = initial;
|
|
4911
|
+
if (initial !== 15)
|
|
4912
|
+
return length;
|
|
4913
|
+
let next = 255;
|
|
4914
|
+
while (next === 255) {
|
|
4915
|
+
if (inputOffset >= input.length) {
|
|
4916
|
+
throw new Error("Invalid LZ4 chunk: truncated extended length");
|
|
4917
|
+
}
|
|
4918
|
+
next = input[inputOffset++];
|
|
4919
|
+
length += next;
|
|
4920
|
+
}
|
|
4921
|
+
return length;
|
|
4922
|
+
};
|
|
4923
|
+
while (inputOffset < input.length) {
|
|
4924
|
+
const token = input[inputOffset++];
|
|
4925
|
+
const literalLength = readExtendedLength(token >>> 4);
|
|
4926
|
+
if (inputOffset + literalLength > input.length || outputOffset + literalLength > output.length) {
|
|
4927
|
+
throw new Error("Invalid LZ4 chunk: literal exceeds chunk boundary");
|
|
4928
|
+
}
|
|
4929
|
+
input.copy(output, outputOffset, inputOffset, inputOffset + literalLength);
|
|
4930
|
+
inputOffset += literalLength;
|
|
4931
|
+
outputOffset += literalLength;
|
|
4932
|
+
if (inputOffset === input.length)
|
|
4933
|
+
break;
|
|
4934
|
+
if (inputOffset + 2 > input.length) {
|
|
4935
|
+
throw new Error("Invalid LZ4 chunk: missing match offset");
|
|
4936
|
+
}
|
|
4937
|
+
const matchOffset = input.readUInt16LE(inputOffset);
|
|
4938
|
+
inputOffset += 2;
|
|
4939
|
+
if (matchOffset === 0 || matchOffset > outputOffset) {
|
|
4940
|
+
throw new Error("Invalid LZ4 chunk: invalid match offset");
|
|
4941
|
+
}
|
|
4942
|
+
const matchLength = readExtendedLength(token & 15) + 4;
|
|
4943
|
+
if (outputOffset + matchLength > output.length) {
|
|
4944
|
+
throw new Error("Invalid LZ4 chunk: match exceeds output boundary");
|
|
4945
|
+
}
|
|
4946
|
+
for (let index = 0; index < matchLength; index += 1) {
|
|
4947
|
+
output[outputOffset] = output[outputOffset - matchOffset];
|
|
4948
|
+
outputOffset += 1;
|
|
4949
|
+
}
|
|
4950
|
+
}
|
|
4951
|
+
if (outputOffset !== output.length) {
|
|
4952
|
+
throw new Error(`Invalid LZ4 chunk: produced ${outputOffset} bytes, expected ${output.length}`);
|
|
4953
|
+
}
|
|
4954
|
+
return output;
|
|
4955
|
+
}
|
|
4956
|
+
function decompressChunk(compressed, outputLength) {
|
|
4957
|
+
if (compressed.subarray(0, ZSTD_MAGIC.length).equals(ZSTD_MAGIC)) {
|
|
4958
|
+
const decompressed = Buffer.from(decompress(compressed));
|
|
4959
|
+
if (decompressed.length !== outputLength) {
|
|
4960
|
+
throw new Error(`Invalid Zstandard chunk: produced ${decompressed.length} bytes, expected ${outputLength}`);
|
|
4961
|
+
}
|
|
4962
|
+
return decompressed;
|
|
4963
|
+
}
|
|
4964
|
+
return decompressLz4Block(compressed, outputLength);
|
|
4965
|
+
}
|
|
4966
|
+
function readChunk(reader) {
|
|
4967
|
+
if (reader.remaining < RBXM_CHUNK_HEADER_BYTES) {
|
|
4968
|
+
throw new Error("Invalid Assistant.rbxm: truncated chunk header");
|
|
4969
|
+
}
|
|
4970
|
+
const type = reader.readBytes(4).toString("latin1");
|
|
4971
|
+
const compressedLength = reader.readUint32();
|
|
4972
|
+
const uncompressedLength = reader.readUint32();
|
|
4973
|
+
reader.skip(4);
|
|
4974
|
+
if (type === "END\0")
|
|
4975
|
+
return { type };
|
|
4976
|
+
if (compressedLength === 0) {
|
|
4977
|
+
return { type, content: reader.readBytes(uncompressedLength) };
|
|
4978
|
+
}
|
|
4979
|
+
return {
|
|
4980
|
+
type,
|
|
4981
|
+
content: decompressChunk(reader.readBytes(compressedLength), uncompressedLength)
|
|
4982
|
+
};
|
|
4983
|
+
}
|
|
4984
|
+
function parseFrontmatter(markdown) {
|
|
4985
|
+
const match = markdown.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
|
|
4986
|
+
if (!match)
|
|
4987
|
+
return void 0;
|
|
4988
|
+
const fields = /* @__PURE__ */ new Map();
|
|
4989
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
4990
|
+
const separator = line.indexOf(":");
|
|
4991
|
+
if (separator <= 0)
|
|
4992
|
+
continue;
|
|
4993
|
+
const key = line.slice(0, separator).trim().toLowerCase();
|
|
4994
|
+
let value = line.slice(separator + 1).trim();
|
|
4995
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
4996
|
+
try {
|
|
4997
|
+
value = JSON.parse(value);
|
|
4998
|
+
} catch {
|
|
4999
|
+
}
|
|
5000
|
+
} else if (value.startsWith("'") && value.endsWith("'")) {
|
|
5001
|
+
value = value.slice(1, -1).replace(/''/g, "'");
|
|
5002
|
+
}
|
|
5003
|
+
fields.set(key, value);
|
|
5004
|
+
}
|
|
5005
|
+
const name = fields.get("name")?.trim();
|
|
5006
|
+
if (!name)
|
|
5007
|
+
return void 0;
|
|
5008
|
+
return { name, description: fields.get("description")?.trim() ?? "" };
|
|
5009
|
+
}
|
|
5010
|
+
function canonicalBuiltInSkillName(sourceName) {
|
|
5011
|
+
return sourceName.toLowerCase().startsWith("rbx-") ? sourceName : `rbx-${sourceName}`;
|
|
5012
|
+
}
|
|
5013
|
+
function parseBuiltInStudioSkills(buffer) {
|
|
5014
|
+
if (buffer.length < RBXM_HEADER_BYTES || !buffer.subarray(0, RBXM_MAGIC.length).equals(RBXM_MAGIC)) {
|
|
5015
|
+
throw new Error("Invalid Assistant.rbxm: Roblox binary model header not found");
|
|
5016
|
+
}
|
|
5017
|
+
const reader = new ByteReader(buffer, "Assistant.rbxm");
|
|
5018
|
+
reader.skip(RBXM_MAGIC.length);
|
|
5019
|
+
reader.readUint16();
|
|
5020
|
+
reader.readInt32();
|
|
5021
|
+
reader.readInt32();
|
|
5022
|
+
reader.skip(8);
|
|
5023
|
+
const stringValueClasses = /* @__PURE__ */ new Map();
|
|
5024
|
+
let foundEnd = false;
|
|
5025
|
+
while (reader.remaining > 0) {
|
|
5026
|
+
const chunk = readChunk(reader);
|
|
5027
|
+
if (chunk.type === "END\0") {
|
|
5028
|
+
foundEnd = true;
|
|
5029
|
+
break;
|
|
5030
|
+
}
|
|
5031
|
+
if (!chunk.content)
|
|
5032
|
+
continue;
|
|
5033
|
+
const chunkReader = new ByteReader(chunk.content, `${chunk.type} chunk`);
|
|
5034
|
+
if (chunk.type === "INST") {
|
|
5035
|
+
const classId2 = chunkReader.readUint32();
|
|
5036
|
+
const className = chunkReader.readString();
|
|
5037
|
+
chunkReader.readUint8();
|
|
5038
|
+
const count = chunkReader.readUint32();
|
|
5039
|
+
chunkReader.skip(count * 4);
|
|
5040
|
+
if (className === "StringValue")
|
|
5041
|
+
stringValueClasses.set(classId2, { count });
|
|
5042
|
+
continue;
|
|
5043
|
+
}
|
|
5044
|
+
if (chunk.type !== "PROP")
|
|
5045
|
+
continue;
|
|
5046
|
+
const classId = chunkReader.readUint32();
|
|
5047
|
+
const propertyName = chunkReader.readString();
|
|
5048
|
+
const propertyType = chunkReader.readUint8();
|
|
5049
|
+
const classInfo = stringValueClasses.get(classId);
|
|
5050
|
+
if (!classInfo || propertyType !== STRING_PROPERTY_TYPE)
|
|
5051
|
+
continue;
|
|
5052
|
+
if (propertyName !== "Name" && propertyName !== "Value")
|
|
5053
|
+
continue;
|
|
5054
|
+
const values = [];
|
|
5055
|
+
for (let index = 0; index < classInfo.count; index += 1) {
|
|
5056
|
+
values.push(chunkReader.readString());
|
|
5057
|
+
}
|
|
5058
|
+
if (propertyName === "Name")
|
|
5059
|
+
classInfo.names = values;
|
|
5060
|
+
else
|
|
5061
|
+
classInfo.values = values;
|
|
5062
|
+
}
|
|
5063
|
+
if (!foundEnd)
|
|
5064
|
+
throw new Error("Invalid Assistant.rbxm: END chunk not found");
|
|
5065
|
+
const documents = [];
|
|
5066
|
+
for (const classInfo of stringValueClasses.values()) {
|
|
5067
|
+
if (!classInfo.names || !classInfo.values)
|
|
5068
|
+
continue;
|
|
5069
|
+
for (let index = 0; index < classInfo.count; index += 1) {
|
|
5070
|
+
const document = classInfo.names[index];
|
|
5071
|
+
if (document !== "SKILL" && document !== "SKILL-combined")
|
|
5072
|
+
continue;
|
|
5073
|
+
const content = classInfo.values[index];
|
|
5074
|
+
const frontmatter = parseFrontmatter(content);
|
|
5075
|
+
if (!frontmatter)
|
|
5076
|
+
continue;
|
|
5077
|
+
documents.push({
|
|
5078
|
+
document,
|
|
5079
|
+
sourceName: frontmatter.name,
|
|
5080
|
+
description: frontmatter.description,
|
|
5081
|
+
content
|
|
5082
|
+
});
|
|
5083
|
+
}
|
|
5084
|
+
}
|
|
5085
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
5086
|
+
for (const document of documents) {
|
|
5087
|
+
const key = document.sourceName.toLowerCase();
|
|
5088
|
+
const group = grouped.get(key) ?? {};
|
|
5089
|
+
if (document.document === "SKILL-combined")
|
|
5090
|
+
group.combined = document;
|
|
5091
|
+
else
|
|
5092
|
+
group.base = document;
|
|
5093
|
+
grouped.set(key, group);
|
|
5094
|
+
}
|
|
5095
|
+
return [...grouped.values()].map((group) => {
|
|
5096
|
+
const selected = group.combined ?? group.base;
|
|
5097
|
+
return {
|
|
5098
|
+
name: canonicalBuiltInSkillName(selected.sourceName),
|
|
5099
|
+
sourceName: selected.sourceName,
|
|
5100
|
+
description: selected.description,
|
|
5101
|
+
document: selected.document,
|
|
5102
|
+
hasCombinedDocument: group.combined !== void 0,
|
|
5103
|
+
content: selected.content,
|
|
5104
|
+
contentLength: Buffer.byteLength(selected.content, "utf8"),
|
|
5105
|
+
contentSha256: createHash2("sha256").update(selected.content).digest("hex")
|
|
5106
|
+
};
|
|
5107
|
+
}).sort((left, right) => left.name.localeCompare(right.name));
|
|
5108
|
+
}
|
|
5109
|
+
function discoverNamedFile(root, fileName, depth = 0) {
|
|
5110
|
+
if (depth > MAX_DISCOVERY_DEPTH || !existsSync4(root))
|
|
5111
|
+
return [];
|
|
5112
|
+
const matches = [];
|
|
5113
|
+
let entries;
|
|
5114
|
+
try {
|
|
5115
|
+
entries = readdirSync3(root, { withFileTypes: true });
|
|
5116
|
+
} catch {
|
|
5117
|
+
return matches;
|
|
5118
|
+
}
|
|
5119
|
+
for (const entry of entries) {
|
|
5120
|
+
const entryPath = path4.join(root, entry.name);
|
|
5121
|
+
if (entry.isFile() && entry.name.toLowerCase() === fileName.toLowerCase()) {
|
|
5122
|
+
matches.push(entryPath);
|
|
5123
|
+
} else if (entry.isDirectory()) {
|
|
5124
|
+
matches.push(...discoverNamedFile(entryPath, fileName, depth + 1));
|
|
5125
|
+
}
|
|
5126
|
+
}
|
|
5127
|
+
return matches;
|
|
5128
|
+
}
|
|
5129
|
+
function resolveAssistantBundlePath(studioExe = resolveStudioExe()) {
|
|
5130
|
+
const override = process.env.ROBLOX_STUDIO_ASSISTANT_BUNDLE;
|
|
5131
|
+
if (override) {
|
|
5132
|
+
if (!existsSync4(override)) {
|
|
5133
|
+
throw new Error(`ROBLOX_STUDIO_ASSISTANT_BUNDLE does not exist: ${override}`);
|
|
5134
|
+
}
|
|
5135
|
+
return override;
|
|
5136
|
+
}
|
|
5137
|
+
const exeDirectory = path4.dirname(studioExe);
|
|
5138
|
+
const roots = [
|
|
5139
|
+
path4.join(exeDirectory, "BuiltInStandalonePlugins"),
|
|
5140
|
+
path4.resolve(exeDirectory, "..", "Resources", "BuiltInStandalonePlugins"),
|
|
5141
|
+
path4.resolve(exeDirectory, "..", "..", "OTAPlugins")
|
|
5142
|
+
];
|
|
5143
|
+
const direct = path4.join(exeDirectory, "BuiltInStandalonePlugins", "Optimized_Embedded_Signature", "Assistant.rbxm");
|
|
5144
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
5145
|
+
if (existsSync4(direct))
|
|
5146
|
+
candidates.add(direct);
|
|
5147
|
+
for (const root of roots) {
|
|
5148
|
+
for (const candidate of discoverNamedFile(root, "Assistant.rbxm"))
|
|
5149
|
+
candidates.add(candidate);
|
|
5150
|
+
}
|
|
5151
|
+
const newest = [...candidates].map((candidate) => ({ candidate, modifiedAt: statSync3(candidate).mtimeMs })).sort((left, right) => right.modifiedAt - left.modifiedAt)[0]?.candidate;
|
|
5152
|
+
if (!newest) {
|
|
5153
|
+
throw new Error(`Studio Assistant bundle not found for ${studioExe}. Set ROBLOX_STUDIO_ASSISTANT_BUNDLE to the installed Assistant.rbxm path.`);
|
|
5154
|
+
}
|
|
5155
|
+
return newest;
|
|
5156
|
+
}
|
|
5157
|
+
function loadBuiltInStudioSkills(bundlePath = resolveAssistantBundlePath()) {
|
|
5158
|
+
const stats = statSync3(bundlePath);
|
|
5159
|
+
if (cachedBundle?.path === bundlePath && cachedBundle.modifiedAt === stats.mtimeMs && cachedBundle.size === stats.size) {
|
|
5160
|
+
return cachedBundle.value;
|
|
5161
|
+
}
|
|
5162
|
+
const buffer = readFileSync5(bundlePath);
|
|
5163
|
+
const skills = parseBuiltInStudioSkills(buffer);
|
|
5164
|
+
if (skills.length === 0) {
|
|
5165
|
+
throw new Error(`No built-in skill documents found in ${bundlePath}`);
|
|
5166
|
+
}
|
|
5167
|
+
const studioVersion = bundlePath.split(path4.sep).find((segment) => /^version-[a-z0-9]+$/i.test(segment));
|
|
5168
|
+
const value = {
|
|
5169
|
+
bundlePath,
|
|
5170
|
+
bundleModifiedAt: stats.mtime.toISOString(),
|
|
5171
|
+
bundleSha256: createHash2("sha256").update(buffer).digest("hex"),
|
|
5172
|
+
studioVersion,
|
|
5173
|
+
skills
|
|
5174
|
+
};
|
|
5175
|
+
cachedBundle = { path: bundlePath, modifiedAt: stats.mtimeMs, size: stats.size, value };
|
|
5176
|
+
return value;
|
|
5177
|
+
}
|
|
5178
|
+
function findBuiltInStudioSkill(bundle, requestedName) {
|
|
5179
|
+
const wanted = requestedName.trim().toLowerCase();
|
|
5180
|
+
return bundle.skills.find((skill) => skill.name.toLowerCase() === wanted || skill.sourceName.toLowerCase() === wanted);
|
|
5181
|
+
}
|
|
5182
|
+
var RBXM_MAGIC, RBXM_HEADER_BYTES, RBXM_CHUNK_HEADER_BYTES, ZSTD_MAGIC, STRING_PROPERTY_TYPE, MAX_DISCOVERY_DEPTH, ByteReader, cachedBundle;
|
|
5183
|
+
var init_studio_skills = __esm({
|
|
5184
|
+
"../core/dist/studio-skills.js"() {
|
|
5185
|
+
"use strict";
|
|
5186
|
+
init_esm();
|
|
5187
|
+
init_studio_instance_manager();
|
|
5188
|
+
RBXM_MAGIC = Buffer.from("<roblox!\x89\xFF\r\n\n", "latin1");
|
|
5189
|
+
RBXM_HEADER_BYTES = 32;
|
|
5190
|
+
RBXM_CHUNK_HEADER_BYTES = 16;
|
|
5191
|
+
ZSTD_MAGIC = Buffer.from([40, 181, 47, 253]);
|
|
5192
|
+
STRING_PROPERTY_TYPE = 1;
|
|
5193
|
+
MAX_DISCOVERY_DEPTH = 6;
|
|
5194
|
+
ByteReader = class {
|
|
5195
|
+
bytes;
|
|
5196
|
+
label;
|
|
5197
|
+
offset = 0;
|
|
5198
|
+
constructor(bytes, label) {
|
|
5199
|
+
this.bytes = bytes;
|
|
5200
|
+
this.label = label;
|
|
5201
|
+
}
|
|
5202
|
+
get remaining() {
|
|
5203
|
+
return this.bytes.length - this.offset;
|
|
5204
|
+
}
|
|
5205
|
+
requireBytes(length) {
|
|
5206
|
+
if (!Number.isSafeInteger(length) || length < 0 || this.remaining < length) {
|
|
5207
|
+
throw new Error(`Invalid ${this.label}: requested ${length} bytes with ${this.remaining} remaining`);
|
|
5208
|
+
}
|
|
5209
|
+
}
|
|
5210
|
+
readUint8() {
|
|
5211
|
+
this.requireBytes(1);
|
|
5212
|
+
return this.bytes[this.offset++];
|
|
5213
|
+
}
|
|
5214
|
+
readUint16() {
|
|
5215
|
+
this.requireBytes(2);
|
|
5216
|
+
const value = this.bytes.readUInt16LE(this.offset);
|
|
5217
|
+
this.offset += 2;
|
|
5218
|
+
return value;
|
|
5219
|
+
}
|
|
5220
|
+
readUint32() {
|
|
5221
|
+
this.requireBytes(4);
|
|
5222
|
+
const value = this.bytes.readUInt32LE(this.offset);
|
|
5223
|
+
this.offset += 4;
|
|
5224
|
+
return value;
|
|
5225
|
+
}
|
|
5226
|
+
readInt32() {
|
|
5227
|
+
this.requireBytes(4);
|
|
5228
|
+
const value = this.bytes.readInt32LE(this.offset);
|
|
5229
|
+
this.offset += 4;
|
|
5230
|
+
return value;
|
|
5231
|
+
}
|
|
5232
|
+
readBytes(length) {
|
|
5233
|
+
this.requireBytes(length);
|
|
5234
|
+
const value = this.bytes.subarray(this.offset, this.offset + length);
|
|
5235
|
+
this.offset += length;
|
|
5236
|
+
return value;
|
|
5237
|
+
}
|
|
5238
|
+
skip(length) {
|
|
5239
|
+
this.requireBytes(length);
|
|
5240
|
+
this.offset += length;
|
|
5241
|
+
}
|
|
5242
|
+
readString() {
|
|
5243
|
+
const length = this.readUint32();
|
|
5244
|
+
return this.readBytes(length).toString("utf8");
|
|
5245
|
+
}
|
|
5246
|
+
};
|
|
5247
|
+
}
|
|
5248
|
+
});
|
|
5249
|
+
|
|
3979
5250
|
// ../core/dist/jpeg-encoder.js
|
|
3980
5251
|
function rgbaToJpeg(rgba, width, height, quality = 80) {
|
|
3981
5252
|
if (width <= 0 || height <= 0)
|
|
@@ -4995,7 +6266,7 @@ var init_png_encoder = __esm({
|
|
|
4995
6266
|
// ../core/dist/tools/index.js
|
|
4996
6267
|
import * as fs3 from "fs";
|
|
4997
6268
|
import * as os3 from "os";
|
|
4998
|
-
import * as
|
|
6269
|
+
import * as path5 from "path";
|
|
4999
6270
|
function multiplayerStopDisabledBody() {
|
|
5000
6271
|
return {
|
|
5001
6272
|
success: false,
|
|
@@ -5020,7 +6291,7 @@ function encodeImageFromRgbaResponse(response, format, quality) {
|
|
|
5020
6291
|
};
|
|
5021
6292
|
}
|
|
5022
6293
|
function sleep(ms) {
|
|
5023
|
-
return new Promise((
|
|
6294
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
5024
6295
|
}
|
|
5025
6296
|
function errorMessage(error) {
|
|
5026
6297
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -5082,7 +6353,7 @@ function loadMicroProfilerBaseline(source, sourcePath) {
|
|
|
5082
6353
|
if (typeof sourcePath !== "string" || sourcePath === "") {
|
|
5083
6354
|
throw new Error("baseline_path must be a non-empty string when provided");
|
|
5084
6355
|
}
|
|
5085
|
-
const resolved =
|
|
6356
|
+
const resolved = path5.resolve(sourcePath);
|
|
5086
6357
|
const parsed = JSON.parse(fs3.readFileSync(resolved, "utf8"));
|
|
5087
6358
|
const record = asRecord(parsed);
|
|
5088
6359
|
if (!record)
|
|
@@ -5543,6 +6814,7 @@ var init_tools = __esm({
|
|
|
5543
6814
|
init_studio_instance_manager();
|
|
5544
6815
|
init_image_decode();
|
|
5545
6816
|
init_roblox_docs();
|
|
6817
|
+
init_studio_skills();
|
|
5546
6818
|
init_jpeg_encoder();
|
|
5547
6819
|
init_png_encoder();
|
|
5548
6820
|
MAX_INLINE_IMAGE_BYTES = 6e6;
|
|
@@ -5610,10 +6882,52 @@ var init_tools = __esm({
|
|
|
5610
6882
|
this.openCloudClient = new OpenCloudClient();
|
|
5611
6883
|
this.cookieClient = new RobloxCookieClient();
|
|
5612
6884
|
this.instanceManager = new StudioInstanceManager();
|
|
6885
|
+
this.bridge.onInstanceRegistered((instance) => this._associateManagedEditConnection(instance));
|
|
5613
6886
|
}
|
|
5614
6887
|
_textResult(body) {
|
|
5615
6888
|
return { content: [{ type: "text", text: JSON.stringify(body) }] };
|
|
5616
6889
|
}
|
|
6890
|
+
async getRobloxSkills(action, name) {
|
|
6891
|
+
if (action !== "list" && action !== "get") {
|
|
6892
|
+
throw new Error('get_roblox_skills action must be "list" or "get"');
|
|
6893
|
+
}
|
|
6894
|
+
const bundle = loadBuiltInStudioSkills();
|
|
6895
|
+
const bundleMetadata = {
|
|
6896
|
+
source: "installed-studio-assistant",
|
|
6897
|
+
studioVersion: bundle.studioVersion,
|
|
6898
|
+
bundlePath: bundle.bundlePath,
|
|
6899
|
+
bundleModifiedAt: bundle.bundleModifiedAt,
|
|
6900
|
+
bundleSha256: bundle.bundleSha256
|
|
6901
|
+
};
|
|
6902
|
+
if (action === "list") {
|
|
6903
|
+
return this._textResult({
|
|
6904
|
+
action,
|
|
6905
|
+
...bundleMetadata,
|
|
6906
|
+
count: bundle.skills.length,
|
|
6907
|
+
skills: bundle.skills.map((skill2) => ({
|
|
6908
|
+
name: skill2.name,
|
|
6909
|
+
sourceName: skill2.sourceName,
|
|
6910
|
+
description: skill2.description,
|
|
6911
|
+
document: skill2.document,
|
|
6912
|
+
hasCombinedDocument: skill2.hasCombinedDocument,
|
|
6913
|
+
contentLength: skill2.contentLength,
|
|
6914
|
+
contentSha256: skill2.contentSha256
|
|
6915
|
+
}))
|
|
6916
|
+
});
|
|
6917
|
+
}
|
|
6918
|
+
if (!name || typeof name !== "string" || !name.trim()) {
|
|
6919
|
+
throw new Error('get_roblox_skills action="get" requires a skill name from action="list"');
|
|
6920
|
+
}
|
|
6921
|
+
const skill = findBuiltInStudioSkill(bundle, name);
|
|
6922
|
+
if (!skill) {
|
|
6923
|
+
throw new Error(`Built-in Studio skill "${name}" was not found. Available skills: ` + bundle.skills.map((candidate) => candidate.name).join(", "));
|
|
6924
|
+
}
|
|
6925
|
+
return this._textResult({
|
|
6926
|
+
action,
|
|
6927
|
+
...bundleMetadata,
|
|
6928
|
+
skill
|
|
6929
|
+
});
|
|
6930
|
+
}
|
|
5617
6931
|
async getRobloxDocs(name, docType, section) {
|
|
5618
6932
|
if (!name || typeof name !== "string") {
|
|
5619
6933
|
throw new Error('get_roblox_docs requires a name (e.g. "ProximityPrompt")');
|
|
@@ -5966,8 +7280,8 @@ var init_tools = __esm({
|
|
|
5966
7280
|
timedOut: true
|
|
5967
7281
|
};
|
|
5968
7282
|
}
|
|
5969
|
-
async getFileTree(
|
|
5970
|
-
const response = await this._callSingle("/api/file-tree", { path:
|
|
7283
|
+
async getFileTree(path6 = "", instance_id) {
|
|
7284
|
+
const response = await this._callSingle("/api/file-tree", { path: path6 }, void 0, instance_id);
|
|
5971
7285
|
return {
|
|
5972
7286
|
content: [
|
|
5973
7287
|
{
|
|
@@ -6084,9 +7398,9 @@ var init_tools = __esm({
|
|
|
6084
7398
|
]
|
|
6085
7399
|
};
|
|
6086
7400
|
}
|
|
6087
|
-
async getProjectStructure(
|
|
7401
|
+
async getProjectStructure(path6, maxDepth, scriptsOnly, instance_id) {
|
|
6088
7402
|
const response = await this._callSingle("/api/project-structure", {
|
|
6089
|
-
path:
|
|
7403
|
+
path: path6,
|
|
6090
7404
|
maxDepth,
|
|
6091
7405
|
scriptsOnly
|
|
6092
7406
|
}, void 0, instance_id);
|
|
@@ -7010,8 +8324,8 @@ ${code}`
|
|
|
7010
8324
|
const rawJson = mutable.raw_json;
|
|
7011
8325
|
if (typeof rawJson === "string") {
|
|
7012
8326
|
if (typeof outputPath === "string" && outputPath !== "") {
|
|
7013
|
-
const resolvedOutputPath =
|
|
7014
|
-
fs3.mkdirSync(
|
|
8327
|
+
const resolvedOutputPath = path5.resolve(outputPath);
|
|
8328
|
+
fs3.mkdirSync(path5.dirname(resolvedOutputPath), { recursive: true });
|
|
7015
8329
|
fs3.writeFileSync(resolvedOutputPath, rawJson, "utf8");
|
|
7016
8330
|
mutable.output_path = resolvedOutputPath;
|
|
7017
8331
|
}
|
|
@@ -7073,8 +8387,8 @@ ${code}`
|
|
|
7073
8387
|
const rawSnapshotBase64 = mutable.raw_snapshot_base64;
|
|
7074
8388
|
if (typeof rawSnapshotBase64 === "string") {
|
|
7075
8389
|
if (typeof outputPath === "string" && outputPath !== "") {
|
|
7076
|
-
const resolvedOutputPath =
|
|
7077
|
-
fs3.mkdirSync(
|
|
8390
|
+
const resolvedOutputPath = path5.resolve(outputPath);
|
|
8391
|
+
fs3.mkdirSync(path5.dirname(resolvedOutputPath), { recursive: true });
|
|
7078
8392
|
fs3.writeFileSync(resolvedOutputPath, Buffer.from(rawSnapshotBase64, "base64"));
|
|
7079
8393
|
mutable.output_path = resolvedOutputPath;
|
|
7080
8394
|
}
|
|
@@ -7089,8 +8403,8 @@ ${code}`
|
|
|
7089
8403
|
});
|
|
7090
8404
|
}
|
|
7091
8405
|
if (typeof summaryOutputPath === "string" && summaryOutputPath !== "") {
|
|
7092
|
-
const resolvedSummaryPath =
|
|
7093
|
-
fs3.mkdirSync(
|
|
8406
|
+
const resolvedSummaryPath = path5.resolve(summaryOutputPath);
|
|
8407
|
+
fs3.mkdirSync(path5.dirname(resolvedSummaryPath), { recursive: true });
|
|
7094
8408
|
fs3.writeFileSync(resolvedSummaryPath, JSON.stringify(mutable, null, 2), "utf8");
|
|
7095
8409
|
mutable.summary_output_path = resolvedSummaryPath;
|
|
7096
8410
|
}
|
|
@@ -7158,11 +8472,18 @@ ${code}`
|
|
|
7158
8472
|
return record.placeId !== void 0 && instance.placeId === record.placeId;
|
|
7159
8473
|
}
|
|
7160
8474
|
if ((record.source === "baseplate" || record.source === "local_file") && record.localPlaceFile) {
|
|
7161
|
-
const expectedName =
|
|
8475
|
+
const expectedName = path5.basename(record.localPlaceFile);
|
|
7162
8476
|
return instance.placeName === expectedName || instance.dataModelName === expectedName;
|
|
7163
8477
|
}
|
|
7164
8478
|
return true;
|
|
7165
8479
|
}
|
|
8480
|
+
_associateManagedEditConnection(instance) {
|
|
8481
|
+
if (instance.role !== "edit")
|
|
8482
|
+
return;
|
|
8483
|
+
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];
|
|
8484
|
+
if (candidate)
|
|
8485
|
+
this.instanceManager.attachInstanceId(candidate, instance.instanceId);
|
|
8486
|
+
}
|
|
7166
8487
|
async _deriveUniverseId(placeId) {
|
|
7167
8488
|
const response = await fetch(`https://apis.roblox.com/universes/v1/places/${placeId}/universe`);
|
|
7168
8489
|
if (!response.ok) {
|
|
@@ -7178,6 +8499,10 @@ ${code}`
|
|
|
7178
8499
|
async _waitForManagedEditConnection(record, beforeKeys, timeoutMs) {
|
|
7179
8500
|
const deadline = Date.now() + timeoutMs;
|
|
7180
8501
|
while (Date.now() < deadline) {
|
|
8502
|
+
this.instanceManager.refresh(record);
|
|
8503
|
+
if (record.state === "failed" || record.state === "exited" || record.closedAt !== void 0) {
|
|
8504
|
+
return void 0;
|
|
8505
|
+
}
|
|
7181
8506
|
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
8507
|
if (candidates[0])
|
|
7183
8508
|
return candidates[0];
|
|
@@ -7186,12 +8511,25 @@ ${code}`
|
|
|
7186
8511
|
return void 0;
|
|
7187
8512
|
}
|
|
7188
8513
|
_managedStatus(record) {
|
|
8514
|
+
this.instanceManager.refresh(record);
|
|
7189
8515
|
const connected = record.instanceId ? this.bridge.getPublicInstances().filter((instance) => instance.instanceId === record.instanceId) : [];
|
|
7190
8516
|
return {
|
|
8517
|
+
launch_id: record.recordId,
|
|
7191
8518
|
instance_id: record.instanceId,
|
|
8519
|
+
managed: true,
|
|
8520
|
+
state: record.state,
|
|
8521
|
+
pid: record.nativeProcessId ?? record.spawnPid,
|
|
8522
|
+
process_running: record.closedAt === void 0 && record.exitedAt === void 0,
|
|
7192
8523
|
source: record.source,
|
|
8524
|
+
local_place_file: record.localPlaceFile,
|
|
7193
8525
|
place_id: record.placeId,
|
|
7194
8526
|
place_version: record.placeVersion,
|
|
8527
|
+
launched_at: new Date(record.launchedAt).toISOString(),
|
|
8528
|
+
connected_at: record.connectedAt ? new Date(record.connectedAt).toISOString() : void 0,
|
|
8529
|
+
failed_at: record.failedAt ? new Date(record.failedAt).toISOString() : void 0,
|
|
8530
|
+
exited_at: record.exitedAt ? new Date(record.exitedAt).toISOString() : void 0,
|
|
8531
|
+
exit_code: record.exitCode,
|
|
8532
|
+
failure_reason: record.failureReason,
|
|
7195
8533
|
connected: connected.length > 0,
|
|
7196
8534
|
roles: connected.map((instance) => instance.role).sort()
|
|
7197
8535
|
};
|
|
@@ -7203,6 +8541,10 @@ ${code}`
|
|
|
7203
8541
|
async manageInstance(request) {
|
|
7204
8542
|
const action = request.action;
|
|
7205
8543
|
const instance_id = typeof request.instance_id === "string" ? request.instance_id : void 0;
|
|
8544
|
+
const launch_id = typeof request.launch_id === "string" ? request.launch_id : void 0;
|
|
8545
|
+
if (instance_id && launch_id) {
|
|
8546
|
+
throw new Error("manage_instance accepts only one of instance_id or launch_id.");
|
|
8547
|
+
}
|
|
7206
8548
|
if (action !== "launch" && action !== "close" && action !== "status" && action !== "list_place_versions") {
|
|
7207
8549
|
throw new Error("manage_instance requires action=launch|close|status|list_place_versions");
|
|
7208
8550
|
}
|
|
@@ -7230,18 +8572,26 @@ ${code}`
|
|
|
7230
8572
|
return this._textResult(body);
|
|
7231
8573
|
}
|
|
7232
8574
|
if (action === "status") {
|
|
8575
|
+
if (launch_id) {
|
|
8576
|
+
const record2 = this.instanceManager.getByLaunchId(launch_id);
|
|
8577
|
+
if (!record2)
|
|
8578
|
+
return this._textResult({ error: "Launch is not managed.", launch_id });
|
|
8579
|
+
return this._textResult(this._managedStatus(record2));
|
|
8580
|
+
}
|
|
7233
8581
|
if (instance_id) {
|
|
7234
8582
|
const record2 = this.instanceManager.get(instance_id);
|
|
7235
8583
|
const connected2 = this.bridge.getPublicInstances().filter((instance) => instance.instanceId === instance_id);
|
|
7236
8584
|
if (!record2 && connected2.length === 0) {
|
|
7237
8585
|
return this._textResult({ error: "Instance is not connected or managed.", instance_id });
|
|
7238
8586
|
}
|
|
8587
|
+
if (record2)
|
|
8588
|
+
return this._textResult(this._managedStatus(record2));
|
|
7239
8589
|
return this._textResult({
|
|
7240
8590
|
instance_id,
|
|
7241
|
-
managed:
|
|
7242
|
-
|
|
7243
|
-
place_id:
|
|
7244
|
-
|
|
8591
|
+
managed: false,
|
|
8592
|
+
state: "connected",
|
|
8593
|
+
place_id: connected2[0]?.placeId,
|
|
8594
|
+
connected: true,
|
|
7245
8595
|
roles: connected2.map((instance) => instance.role).sort()
|
|
7246
8596
|
});
|
|
7247
8597
|
}
|
|
@@ -7257,14 +8607,32 @@ ${code}`
|
|
|
7257
8607
|
}
|
|
7258
8608
|
if (action === "close") {
|
|
7259
8609
|
let record2;
|
|
8610
|
+
if (launch_id) {
|
|
8611
|
+
record2 = this.instanceManager.getByLaunchId(launch_id);
|
|
8612
|
+
if (!record2)
|
|
8613
|
+
return this._textResult({ error: "Launch is not managed.", launch_id });
|
|
8614
|
+
const connectedInstanceId = record2.instanceId;
|
|
8615
|
+
const closeResult2 = record2.closedAt === void 0 ? this.instanceManager.close(record2) : { status: "already_closed" };
|
|
8616
|
+
if (connectedInstanceId) {
|
|
8617
|
+
await this.bridge.unregisterInstanceIdEverywhere(connectedInstanceId);
|
|
8618
|
+
await sleep(500);
|
|
8619
|
+
await this.bridge.unregisterInstanceIdEverywhere(connectedInstanceId);
|
|
8620
|
+
}
|
|
8621
|
+
return this._textResult({
|
|
8622
|
+
...this._managedStatus(record2),
|
|
8623
|
+
message: closeResult2.status === "already_closed" ? "Studio instance was already closed." : "Studio instance closed."
|
|
8624
|
+
});
|
|
8625
|
+
}
|
|
7260
8626
|
if (instance_id) {
|
|
8627
|
+
const recordBeforeClose = this.instanceManager.get(instance_id);
|
|
7261
8628
|
const managedClose = this.instanceManager.closeByInstanceId(instance_id);
|
|
7262
8629
|
if (managedClose.status !== "not_found") {
|
|
7263
8630
|
await this.bridge.unregisterInstanceIdEverywhere(instance_id);
|
|
7264
8631
|
await sleep(500);
|
|
7265
8632
|
await this.bridge.unregisterInstanceIdEverywhere(instance_id);
|
|
8633
|
+
const closedRecord = recordBeforeClose ?? (managedClose.launchId ? this.instanceManager.getByLaunchId(managedClose.launchId) : void 0);
|
|
7266
8634
|
return this._textResult({
|
|
7267
|
-
instance_id,
|
|
8635
|
+
...closedRecord ? this._managedStatus(closedRecord) : { instance_id },
|
|
7268
8636
|
message: managedClose.status === "already_closed" ? "Studio instance was already closed." : "Studio instance closed."
|
|
7269
8637
|
});
|
|
7270
8638
|
}
|
|
@@ -7311,7 +8679,7 @@ ${code}`
|
|
|
7311
8679
|
await this.bridge.unregisterInstanceIdEverywhere(record2.instanceId);
|
|
7312
8680
|
}
|
|
7313
8681
|
return this._textResult({
|
|
7314
|
-
|
|
8682
|
+
...this._managedStatus(record2),
|
|
7315
8683
|
message: closeResult.status === "already_closed" ? "Studio instance was already closed." : "Studio instance closed."
|
|
7316
8684
|
});
|
|
7317
8685
|
}
|
|
@@ -7338,24 +8706,34 @@ ${code}`
|
|
|
7338
8706
|
localPlaceFile,
|
|
7339
8707
|
placeId,
|
|
7340
8708
|
universeId,
|
|
7341
|
-
placeVersion
|
|
8709
|
+
placeVersion,
|
|
8710
|
+
connectionTimeoutMs: timeoutMs
|
|
7342
8711
|
});
|
|
7343
8712
|
if (!waitForConnection) {
|
|
7344
|
-
return this._textResult({
|
|
8713
|
+
return this._textResult({
|
|
8714
|
+
...this._managedStatus(record),
|
|
8715
|
+
message: "Studio launch requested."
|
|
8716
|
+
});
|
|
7345
8717
|
}
|
|
7346
8718
|
const connected = await this._waitForManagedEditConnection(record, beforeKeys, timeoutMs);
|
|
7347
8719
|
if (!connected) {
|
|
7348
|
-
|
|
7349
|
-
this.instanceManager.
|
|
7350
|
-
}
|
|
8720
|
+
if (record.state === "launching") {
|
|
8721
|
+
this.instanceManager.markFailed(record, "Studio launched, but the MCP plugin did not connect before timeout.");
|
|
8722
|
+
}
|
|
8723
|
+
if (record.closedAt === void 0) {
|
|
8724
|
+
try {
|
|
8725
|
+
this.instanceManager.close(record);
|
|
8726
|
+
} catch {
|
|
8727
|
+
}
|
|
7351
8728
|
}
|
|
7352
8729
|
return this._textResult({
|
|
7353
|
-
|
|
8730
|
+
...this._managedStatus(record),
|
|
8731
|
+
error: record.failureReason ?? "Studio launched, but the MCP plugin did not connect before timeout."
|
|
7354
8732
|
});
|
|
7355
8733
|
}
|
|
7356
8734
|
this.instanceManager.attachInstanceId(record, connected.instanceId);
|
|
7357
8735
|
return this._textResult({
|
|
7358
|
-
|
|
8736
|
+
...this._managedStatus(record),
|
|
7359
8737
|
message: launchSource === "place_revision" ? `Studio opened place revision ${placeVersion}.` : "Studio opened."
|
|
7360
8738
|
});
|
|
7361
8739
|
}
|
|
@@ -7538,16 +8916,16 @@ ${code}`
|
|
|
7538
8916
|
try {
|
|
7539
8917
|
editState = await this.client.request("/api/multiplayer-test-state", {}, instanceId, "edit");
|
|
7540
8918
|
body.edit = editState;
|
|
7541
|
-
} catch (
|
|
7542
|
-
body.edit = { error:
|
|
8919
|
+
} catch (err2) {
|
|
8920
|
+
body.edit = { error: err2 instanceof Error ? err2.message : String(err2) };
|
|
7543
8921
|
}
|
|
7544
8922
|
}
|
|
7545
8923
|
if (server) {
|
|
7546
8924
|
try {
|
|
7547
8925
|
serverState = await this.client.request("/api/multiplayer-test-state", {}, instanceId, "server");
|
|
7548
8926
|
body.server = serverState;
|
|
7549
|
-
} catch (
|
|
7550
|
-
body.server = { error:
|
|
8927
|
+
} catch (err2) {
|
|
8928
|
+
body.server = { error: err2 instanceof Error ? err2.message : String(err2) };
|
|
7551
8929
|
}
|
|
7552
8930
|
}
|
|
7553
8931
|
const session = editState?.session;
|
|
@@ -7873,14 +9251,14 @@ ${code}`
|
|
|
7873
9251
|
};
|
|
7874
9252
|
}
|
|
7875
9253
|
static findProjectRoot(startDir) {
|
|
7876
|
-
let dir =
|
|
9254
|
+
let dir = path5.resolve(startDir);
|
|
7877
9255
|
let previous = "";
|
|
7878
9256
|
while (dir !== previous) {
|
|
7879
|
-
if (fs3.existsSync(
|
|
9257
|
+
if (fs3.existsSync(path5.join(dir, ".git")) || fs3.existsSync(path5.join(dir, "package.json"))) {
|
|
7880
9258
|
return dir;
|
|
7881
9259
|
}
|
|
7882
9260
|
previous = dir;
|
|
7883
|
-
dir =
|
|
9261
|
+
dir = path5.dirname(dir);
|
|
7884
9262
|
}
|
|
7885
9263
|
return null;
|
|
7886
9264
|
}
|
|
@@ -7894,7 +9272,7 @@ ${code}`
|
|
|
7894
9272
|
}
|
|
7895
9273
|
}
|
|
7896
9274
|
static ensureWritableDirectory(candidate, label) {
|
|
7897
|
-
const resolved =
|
|
9275
|
+
const resolved = path5.resolve(candidate);
|
|
7898
9276
|
try {
|
|
7899
9277
|
fs3.mkdirSync(resolved, { recursive: true });
|
|
7900
9278
|
} catch (error) {
|
|
@@ -7915,11 +9293,11 @@ ${code}`
|
|
|
7915
9293
|
if (_RobloxStudioTools._cachedLibraryPath)
|
|
7916
9294
|
return _RobloxStudioTools._cachedLibraryPath;
|
|
7917
9295
|
const overridePath = process.env.ROBLOXSTUDIO_MCP_BUILD_LIBRARY || process.env.BUILD_LIBRARY_PATH;
|
|
7918
|
-
const cwd =
|
|
9296
|
+
const cwd = path5.resolve(process.cwd());
|
|
7919
9297
|
const projectRoot = _RobloxStudioTools.findProjectRoot(cwd);
|
|
7920
|
-
const homeLibraryPath =
|
|
7921
|
-
const projectLibraryPath = projectRoot ?
|
|
7922
|
-
const cwdLibraryPath =
|
|
9298
|
+
const homeLibraryPath = path5.join(os3.homedir(), ".robloxstudio-mcp", "build-library");
|
|
9299
|
+
const projectLibraryPath = projectRoot ? path5.join(projectRoot, "build-library") : null;
|
|
9300
|
+
const cwdLibraryPath = path5.join(cwd, "build-library");
|
|
7923
9301
|
let result;
|
|
7924
9302
|
if (overridePath) {
|
|
7925
9303
|
result = _RobloxStudioTools.ensureWritableDirectory(overridePath, "override");
|
|
@@ -7933,12 +9311,12 @@ ${code}`
|
|
|
7933
9311
|
}
|
|
7934
9312
|
})());
|
|
7935
9313
|
if (existing) {
|
|
7936
|
-
result =
|
|
9314
|
+
result = path5.resolve(existing);
|
|
7937
9315
|
} else if (projectLibraryPath) {
|
|
7938
9316
|
try {
|
|
7939
9317
|
result = _RobloxStudioTools.ensureWritableDirectory(projectLibraryPath, "project-root");
|
|
7940
|
-
} catch (
|
|
7941
|
-
console.error(`Warning: could not create build-library at project root (${projectLibraryPath}): ${
|
|
9318
|
+
} catch (err2) {
|
|
9319
|
+
console.error(`Warning: could not create build-library at project root (${projectLibraryPath}): ${err2.message}. Falling back to home directory.`);
|
|
7942
9320
|
result = _RobloxStudioTools.ensureWritableDirectory(homeLibraryPath, "home");
|
|
7943
9321
|
}
|
|
7944
9322
|
} else {
|
|
@@ -7960,8 +9338,8 @@ ${code}`
|
|
|
7960
9338
|
if (response && response.success && response.buildData) {
|
|
7961
9339
|
const buildData = response.buildData;
|
|
7962
9340
|
const buildId = buildData.id || `${style}/exported`;
|
|
7963
|
-
const filePath =
|
|
7964
|
-
const dirPath =
|
|
9341
|
+
const filePath = path5.join(_RobloxStudioTools.findLibraryPath(), `${buildId}.json`);
|
|
9342
|
+
const dirPath = path5.dirname(filePath);
|
|
7965
9343
|
if (!fs3.existsSync(dirPath)) {
|
|
7966
9344
|
fs3.mkdirSync(dirPath, { recursive: true });
|
|
7967
9345
|
}
|
|
@@ -8062,8 +9440,8 @@ ${code}`
|
|
|
8062
9440
|
const normalizedParts = this.normalizeBuildParts(parts, new Set(Object.keys(normalizedPalette)));
|
|
8063
9441
|
const computedBounds = bounds || this.computeBounds(normalizedParts);
|
|
8064
9442
|
const buildData = { id, style, bounds: computedBounds, palette: normalizedPalette, parts: normalizedParts };
|
|
8065
|
-
const filePath =
|
|
8066
|
-
const dirPath =
|
|
9443
|
+
const filePath = path5.join(_RobloxStudioTools.findLibraryPath(), `${id}.json`);
|
|
9444
|
+
const dirPath = path5.dirname(filePath);
|
|
8067
9445
|
if (!fs3.existsSync(dirPath)) {
|
|
8068
9446
|
fs3.mkdirSync(dirPath, { recursive: true });
|
|
8069
9447
|
}
|
|
@@ -8121,8 +9499,8 @@ ${code}`
|
|
|
8121
9499
|
};
|
|
8122
9500
|
if (seed !== void 0)
|
|
8123
9501
|
buildData.generatorSeed = seed;
|
|
8124
|
-
const filePath =
|
|
8125
|
-
const dirPath =
|
|
9502
|
+
const filePath = path5.join(_RobloxStudioTools.findLibraryPath(), `${id}.json`);
|
|
9503
|
+
const dirPath = path5.dirname(filePath);
|
|
8126
9504
|
if (!fs3.existsSync(dirPath)) {
|
|
8127
9505
|
fs3.mkdirSync(dirPath, { recursive: true });
|
|
8128
9506
|
}
|
|
@@ -8150,13 +9528,13 @@ ${code}`
|
|
|
8150
9528
|
}
|
|
8151
9529
|
let resolved;
|
|
8152
9530
|
if (typeof buildData === "string") {
|
|
8153
|
-
const filePath =
|
|
9531
|
+
const filePath = path5.join(_RobloxStudioTools.findLibraryPath(), `${buildData}.json`);
|
|
8154
9532
|
if (!fs3.existsSync(filePath)) {
|
|
8155
9533
|
throw new Error(`Build not found in library: ${buildData}`);
|
|
8156
9534
|
}
|
|
8157
9535
|
resolved = JSON.parse(fs3.readFileSync(filePath, "utf-8"));
|
|
8158
9536
|
} else if (buildData.id && !buildData.parts) {
|
|
8159
|
-
const filePath =
|
|
9537
|
+
const filePath = path5.join(_RobloxStudioTools.findLibraryPath(), `${buildData.id}.json`);
|
|
8160
9538
|
if (!fs3.existsSync(filePath)) {
|
|
8161
9539
|
throw new Error(`Build not found in library: ${buildData.id}`);
|
|
8162
9540
|
}
|
|
@@ -8183,13 +9561,13 @@ ${code}`
|
|
|
8183
9561
|
const styles = style ? [style] : ["medieval", "modern", "nature", "scifi", "misc"];
|
|
8184
9562
|
const builds = [];
|
|
8185
9563
|
for (const s of styles) {
|
|
8186
|
-
const dirPath =
|
|
9564
|
+
const dirPath = path5.join(libraryPath, s);
|
|
8187
9565
|
if (!fs3.existsSync(dirPath))
|
|
8188
9566
|
continue;
|
|
8189
9567
|
const files = fs3.readdirSync(dirPath).filter((f) => f.endsWith(".json"));
|
|
8190
9568
|
for (const file of files) {
|
|
8191
9569
|
try {
|
|
8192
|
-
const content = fs3.readFileSync(
|
|
9570
|
+
const content = fs3.readFileSync(path5.join(dirPath, file), "utf-8");
|
|
8193
9571
|
const data = JSON.parse(content);
|
|
8194
9572
|
builds.push({
|
|
8195
9573
|
id: data.id || `${s}/${file.replace(".json", "")}`,
|
|
@@ -8228,7 +9606,7 @@ ${code}`
|
|
|
8228
9606
|
if (!id) {
|
|
8229
9607
|
throw new Error("Build ID is required for get_build");
|
|
8230
9608
|
}
|
|
8231
|
-
const filePath =
|
|
9609
|
+
const filePath = path5.join(_RobloxStudioTools.findLibraryPath(), `${id}.json`);
|
|
8232
9610
|
if (!fs3.existsSync(filePath)) {
|
|
8233
9611
|
throw new Error(`Build not found in library: ${id}`);
|
|
8234
9612
|
}
|
|
@@ -8315,7 +9693,7 @@ ${code}`
|
|
|
8315
9693
|
if (!buildId) {
|
|
8316
9694
|
throw new Error(`Invalid ${validatedKeyPath}: model key "${modelKey}" is not defined in sceneData.models`);
|
|
8317
9695
|
}
|
|
8318
|
-
const filePath =
|
|
9696
|
+
const filePath = path5.join(libraryPath, `${buildId}.json`);
|
|
8319
9697
|
if (!fs3.existsSync(filePath)) {
|
|
8320
9698
|
throw new Error(`Build not found in library: ${buildId}`);
|
|
8321
9699
|
}
|
|
@@ -8663,7 +10041,7 @@ ${code}`
|
|
|
8663
10041
|
throw new Error(`File not found: ${filePath}`);
|
|
8664
10042
|
}
|
|
8665
10043
|
const fileContent = fs3.readFileSync(filePath);
|
|
8666
|
-
const fileName =
|
|
10044
|
+
const fileName = path5.basename(filePath);
|
|
8667
10045
|
if (assetType === "Decal" && this.cookieClient.hasCookie()) {
|
|
8668
10046
|
const result2 = await this.cookieClient.uploadDecal(fileContent, displayName, description || "");
|
|
8669
10047
|
return {
|
|
@@ -8888,12 +10266,12 @@ ${code}`
|
|
|
8888
10266
|
return { content: [{ type: "text", text: JSON.stringify({ error: "plugin returned no base64 payload" }) }] };
|
|
8889
10267
|
}
|
|
8890
10268
|
const bytes = Buffer.from(response.base64, "base64");
|
|
8891
|
-
const resolved =
|
|
10269
|
+
const resolved = path5.resolve(outputPath);
|
|
8892
10270
|
try {
|
|
8893
|
-
fs3.mkdirSync(
|
|
10271
|
+
fs3.mkdirSync(path5.dirname(resolved), { recursive: true });
|
|
8894
10272
|
fs3.writeFileSync(resolved, bytes);
|
|
8895
|
-
} catch (
|
|
8896
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: `failed to write ${resolved}: ${
|
|
10273
|
+
} catch (err2) {
|
|
10274
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: `failed to write ${resolved}: ${err2.message}` }) }] };
|
|
8897
10275
|
}
|
|
8898
10276
|
return {
|
|
8899
10277
|
content: [{
|
|
@@ -8924,11 +10302,11 @@ ${code}`
|
|
|
8924
10302
|
let bytes;
|
|
8925
10303
|
let sourceLabel;
|
|
8926
10304
|
if (source.path !== void 0) {
|
|
8927
|
-
const resolved =
|
|
10305
|
+
const resolved = path5.resolve(source.path);
|
|
8928
10306
|
try {
|
|
8929
10307
|
bytes = fs3.readFileSync(resolved);
|
|
8930
|
-
} catch (
|
|
8931
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: `failed to read ${resolved}: ${
|
|
10308
|
+
} catch (err2) {
|
|
10309
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: `failed to read ${resolved}: ${err2.message}` }) }] };
|
|
8932
10310
|
}
|
|
8933
10311
|
sourceLabel = resolved;
|
|
8934
10312
|
} else if (source.url !== void 0) {
|
|
@@ -8957,15 +10335,15 @@ ${code}`
|
|
|
8957
10335
|
return { content: [{ type: "text", text: JSON.stringify({ error: `fetch ${source.url}: downloaded ${arr.byteLength} bytes exceeds ${MAX_IMPORT_BYTES} byte cap` }) }] };
|
|
8958
10336
|
}
|
|
8959
10337
|
bytes = Buffer.from(arr);
|
|
8960
|
-
} catch (
|
|
8961
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: `fetch ${source.url} failed: ${
|
|
10338
|
+
} catch (err2) {
|
|
10339
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: `fetch ${source.url} failed: ${err2.message}` }) }] };
|
|
8962
10340
|
}
|
|
8963
10341
|
sourceLabel = source.url;
|
|
8964
10342
|
} else {
|
|
8965
10343
|
try {
|
|
8966
10344
|
bytes = Buffer.from(source.base64, "base64");
|
|
8967
|
-
} catch (
|
|
8968
|
-
return { content: [{ type: "text", text: JSON.stringify({ error: `base64 decode failed: ${
|
|
10345
|
+
} catch (err2) {
|
|
10346
|
+
return { content: [{ type: "text", text: JSON.stringify({ error: `base64 decode failed: ${err2.message}` }) }] };
|
|
8969
10347
|
}
|
|
8970
10348
|
sourceLabel = `base64(${bytes.length}B)`;
|
|
8971
10349
|
}
|
|
@@ -9098,7 +10476,13 @@ var init_proxy_bridge_service = __esm({
|
|
|
9098
10476
|
return;
|
|
9099
10477
|
const body = await res.json();
|
|
9100
10478
|
if (Array.isArray(body.instances)) {
|
|
10479
|
+
const previousKeys = new Set(this.cachedInstances.map((instance) => `${instance.pluginSessionId}\0${instance.instanceId}\0${instance.role}\0${instance.connectedAt}`));
|
|
9101
10480
|
this.cachedInstances = body.instances;
|
|
10481
|
+
for (const instance of body.instances) {
|
|
10482
|
+
const key = `${instance.pluginSessionId}\0${instance.instanceId}\0${instance.role}\0${instance.connectedAt}`;
|
|
10483
|
+
if (!previousKeys.has(key))
|
|
10484
|
+
this.notifyInstanceRegistered(toPublic(instance));
|
|
10485
|
+
}
|
|
9102
10486
|
}
|
|
9103
10487
|
} catch {
|
|
9104
10488
|
}
|
|
@@ -9158,12 +10542,12 @@ var init_proxy_bridge_service = __esm({
|
|
|
9158
10542
|
throw new Error(result.error);
|
|
9159
10543
|
}
|
|
9160
10544
|
return result.response;
|
|
9161
|
-
} catch (
|
|
10545
|
+
} catch (err2) {
|
|
9162
10546
|
clearTimeout(timeoutId);
|
|
9163
|
-
if (
|
|
10547
|
+
if (err2.name === "AbortError") {
|
|
9164
10548
|
throw new Error("Proxy request timeout");
|
|
9165
10549
|
}
|
|
9166
|
-
throw
|
|
10550
|
+
throw err2;
|
|
9167
10551
|
}
|
|
9168
10552
|
}
|
|
9169
10553
|
cleanupOldRequests() {
|
|
@@ -10346,7 +11730,7 @@ var init_definitions = __esm({
|
|
|
10346
11730
|
{
|
|
10347
11731
|
name: "manage_instance",
|
|
10348
11732
|
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="
|
|
11733
|
+
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
11734
|
inputSchema: {
|
|
10351
11735
|
type: "object",
|
|
10352
11736
|
properties: {
|
|
@@ -10374,11 +11758,11 @@ var init_definitions = __esm({
|
|
|
10374
11758
|
},
|
|
10375
11759
|
wait_for_connection: {
|
|
10376
11760
|
type: "boolean",
|
|
10377
|
-
description: 'For action="launch": wait until the MCP plugin connects and return instance_id (default true).'
|
|
11761
|
+
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
11762
|
},
|
|
10379
11763
|
timeout_ms: {
|
|
10380
11764
|
type: "number",
|
|
10381
|
-
description: 'For action="launch": max milliseconds
|
|
11765
|
+
description: 'For action="launch": max milliseconds for plugin connection (default 120000). The deadline also applies asynchronously when wait_for_connection=false.'
|
|
10382
11766
|
},
|
|
10383
11767
|
max_page_size: {
|
|
10384
11768
|
type: "number",
|
|
@@ -10390,7 +11774,11 @@ var init_definitions = __esm({
|
|
|
10390
11774
|
},
|
|
10391
11775
|
instance_id: {
|
|
10392
11776
|
type: "string",
|
|
10393
|
-
description: 'For action="close" or action="status": Studio instance to inspect or close.
|
|
11777
|
+
description: 'For action="close" or action="status": connected Studio instance to inspect or close. Mutually exclusive with launch_id.'
|
|
11778
|
+
},
|
|
11779
|
+
launch_id: {
|
|
11780
|
+
type: "string",
|
|
11781
|
+
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
11782
|
}
|
|
10395
11783
|
},
|
|
10396
11784
|
required: ["action"]
|
|
@@ -11968,6 +13356,27 @@ part(0,2,0,2,1,1,"b")`,
|
|
|
11968
13356
|
required: ["pattern", "replacement"]
|
|
11969
13357
|
}
|
|
11970
13358
|
},
|
|
13359
|
+
// === Installed Studio Skills ===
|
|
13360
|
+
{
|
|
13361
|
+
name: "get_roblox_skills",
|
|
13362
|
+
category: "read",
|
|
13363
|
+
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.`,
|
|
13364
|
+
inputSchema: {
|
|
13365
|
+
type: "object",
|
|
13366
|
+
properties: {
|
|
13367
|
+
action: {
|
|
13368
|
+
type: "string",
|
|
13369
|
+
enum: ["list", "get"],
|
|
13370
|
+
description: "List installed built-in skills or get one skill document."
|
|
13371
|
+
},
|
|
13372
|
+
name: {
|
|
13373
|
+
type: "string",
|
|
13374
|
+
description: 'Skill name returned by action="list". Required for action="get"; both canonical rbx-* and embedded source names are accepted.'
|
|
13375
|
+
}
|
|
13376
|
+
},
|
|
13377
|
+
required: ["action"]
|
|
13378
|
+
}
|
|
13379
|
+
},
|
|
11971
13380
|
// === Documentation ===
|
|
11972
13381
|
{
|
|
11973
13382
|
name: "get_roblox_docs",
|
|
@@ -12151,15 +13560,15 @@ part(0,2,0,2,1,1,"b")`,
|
|
|
12151
13560
|
});
|
|
12152
13561
|
|
|
12153
13562
|
// ../core/dist/install-plugin-helpers.js
|
|
12154
|
-
import { existsSync as
|
|
13563
|
+
import { existsSync as existsSync6, readFileSync as readFileSync7, unlinkSync } from "fs";
|
|
12155
13564
|
import { execSync } from "child_process";
|
|
12156
|
-
import { join as
|
|
13565
|
+
import { join as join6 } from "path";
|
|
12157
13566
|
import { homedir as homedir5 } from "os";
|
|
12158
13567
|
function isWSL() {
|
|
12159
13568
|
if (process.platform !== "linux")
|
|
12160
13569
|
return false;
|
|
12161
13570
|
try {
|
|
12162
|
-
const v =
|
|
13571
|
+
const v = readFileSync7("/proc/version", "utf8");
|
|
12163
13572
|
return /microsoft|wsl/i.test(v);
|
|
12164
13573
|
} catch {
|
|
12165
13574
|
return false;
|
|
@@ -12177,7 +13586,7 @@ function getWindowsUserPluginsDir() {
|
|
|
12177
13586
|
}).toString().trim();
|
|
12178
13587
|
if (!linuxPath)
|
|
12179
13588
|
return null;
|
|
12180
|
-
return
|
|
13589
|
+
return join6(linuxPath, "Roblox", "Plugins");
|
|
12181
13590
|
} catch {
|
|
12182
13591
|
return null;
|
|
12183
13592
|
}
|
|
@@ -12186,7 +13595,7 @@ function getPluginsFolder() {
|
|
|
12186
13595
|
if (process.env.MCP_PLUGINS_DIR)
|
|
12187
13596
|
return process.env.MCP_PLUGINS_DIR;
|
|
12188
13597
|
if (process.platform === "win32") {
|
|
12189
|
-
return
|
|
13598
|
+
return join6(process.env.LOCALAPPDATA || join6(homedir5(), "AppData", "Local"), "Roblox", "Plugins");
|
|
12190
13599
|
}
|
|
12191
13600
|
if (isWSL()) {
|
|
12192
13601
|
const win = getWindowsUserPluginsDir();
|
|
@@ -12194,18 +13603,18 @@ function getPluginsFolder() {
|
|
|
12194
13603
|
return win;
|
|
12195
13604
|
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
13605
|
}
|
|
12197
|
-
return
|
|
13606
|
+
return join6(homedir5(), "Documents", "Roblox", "Plugins");
|
|
12198
13607
|
}
|
|
12199
13608
|
function handleVariantConflict({ pluginsFolder, otherAssetName, replace, log = console.log, warn = console.warn }) {
|
|
12200
|
-
const otherDest =
|
|
12201
|
-
if (!
|
|
13609
|
+
const otherDest = join6(pluginsFolder, otherAssetName);
|
|
13610
|
+
if (!existsSync6(otherDest))
|
|
12202
13611
|
return;
|
|
12203
13612
|
if (replace) {
|
|
12204
13613
|
try {
|
|
12205
13614
|
unlinkSync(otherDest);
|
|
12206
13615
|
log(`Removed conflicting ${otherAssetName}.`);
|
|
12207
|
-
} catch (
|
|
12208
|
-
warn(`[install-plugin] Could not remove ${otherDest}: ${
|
|
13616
|
+
} catch (err2) {
|
|
13617
|
+
warn(`[install-plugin] Could not remove ${otherDest}: ${err2}. Continuing.`);
|
|
12209
13618
|
}
|
|
12210
13619
|
return;
|
|
12211
13620
|
}
|
|
@@ -12234,6 +13643,7 @@ var init_dist = __esm({
|
|
|
12234
13643
|
init_opencloud_client();
|
|
12235
13644
|
init_install_plugin_helpers();
|
|
12236
13645
|
init_roblox_cookie_client();
|
|
13646
|
+
init_studio_skills();
|
|
12237
13647
|
}
|
|
12238
13648
|
});
|
|
12239
13649
|
|
|
@@ -12243,13 +13653,13 @@ __export(install_plugin_exports, {
|
|
|
12243
13653
|
installBundledPlugin: () => installBundledPlugin,
|
|
12244
13654
|
installPlugin: () => installPlugin
|
|
12245
13655
|
});
|
|
12246
|
-
import { copyFileSync as copyFileSync2, createWriteStream, existsSync as
|
|
12247
|
-
import { dirname as
|
|
13656
|
+
import { copyFileSync as copyFileSync2, createWriteStream, existsSync as existsSync7, mkdirSync as mkdirSync5, readFileSync as readFileSync8, unlinkSync as unlinkSync2 } from "fs";
|
|
13657
|
+
import { dirname as dirname5, join as join7 } from "path";
|
|
12248
13658
|
import { fileURLToPath } from "url";
|
|
12249
13659
|
import { get } from "https";
|
|
12250
13660
|
function httpsGet(url) {
|
|
12251
|
-
return new Promise((
|
|
12252
|
-
const req = get(url, { headers: { "User-Agent": "robloxstudio-mcp-inspector" } },
|
|
13661
|
+
return new Promise((resolve5, reject) => {
|
|
13662
|
+
const req = get(url, { headers: { "User-Agent": "robloxstudio-mcp-inspector" } }, resolve5);
|
|
12253
13663
|
req.on("error", reject);
|
|
12254
13664
|
req.setTimeout(TIMEOUT_MS, () => {
|
|
12255
13665
|
req.destroy(new Error(`Request timed out after ${TIMEOUT_MS}ms`));
|
|
@@ -12267,21 +13677,21 @@ async function download(url, dest, redirects = 0) {
|
|
|
12267
13677
|
if (res.statusCode !== 200) {
|
|
12268
13678
|
throw new Error(`Download failed: HTTP ${res.statusCode}`);
|
|
12269
13679
|
}
|
|
12270
|
-
return new Promise((
|
|
13680
|
+
return new Promise((resolve5, reject) => {
|
|
12271
13681
|
const file = createWriteStream(dest);
|
|
12272
|
-
const cleanup = (
|
|
13682
|
+
const cleanup = (err2) => {
|
|
12273
13683
|
file.close(() => {
|
|
12274
13684
|
try {
|
|
12275
13685
|
unlinkSync2(dest);
|
|
12276
13686
|
} catch {
|
|
12277
13687
|
}
|
|
12278
|
-
reject(
|
|
13688
|
+
reject(err2);
|
|
12279
13689
|
});
|
|
12280
13690
|
};
|
|
12281
13691
|
res.pipe(file);
|
|
12282
13692
|
file.on("finish", () => {
|
|
12283
13693
|
file.close();
|
|
12284
|
-
|
|
13694
|
+
resolve5();
|
|
12285
13695
|
});
|
|
12286
13696
|
file.on("error", cleanup);
|
|
12287
13697
|
res.on("error", cleanup);
|
|
@@ -12304,7 +13714,7 @@ function prepareInstall({
|
|
|
12304
13714
|
warn
|
|
12305
13715
|
}) {
|
|
12306
13716
|
const pluginsFolder = getPluginsFolder();
|
|
12307
|
-
if (!
|
|
13717
|
+
if (!existsSync7(pluginsFolder)) {
|
|
12308
13718
|
mkdirSync5(pluginsFolder, { recursive: true });
|
|
12309
13719
|
}
|
|
12310
13720
|
handleVariantConflict({
|
|
@@ -12317,23 +13727,23 @@ function prepareInstall({
|
|
|
12317
13727
|
return pluginsFolder;
|
|
12318
13728
|
}
|
|
12319
13729
|
function bundledAssetPath() {
|
|
12320
|
-
const currentDir =
|
|
13730
|
+
const currentDir = dirname5(fileURLToPath(import.meta.url));
|
|
12321
13731
|
const candidates = [
|
|
12322
|
-
|
|
12323
|
-
|
|
13732
|
+
join7(currentDir, "..", "studio-plugin", ASSET_NAME),
|
|
13733
|
+
join7(currentDir, "..", "..", "..", "studio-plugin", ASSET_NAME)
|
|
12324
13734
|
];
|
|
12325
|
-
return candidates.find((candidate) =>
|
|
13735
|
+
return candidates.find((candidate) => existsSync7(candidate)) ?? null;
|
|
12326
13736
|
}
|
|
12327
13737
|
function packageVersion() {
|
|
12328
|
-
const currentDir =
|
|
12329
|
-
const pkg = JSON.parse(
|
|
13738
|
+
const currentDir = dirname5(fileURLToPath(import.meta.url));
|
|
13739
|
+
const pkg = JSON.parse(readFileSync8(join7(currentDir, "..", "package.json"), "utf8"));
|
|
12330
13740
|
if (!pkg.version) {
|
|
12331
13741
|
throw new Error("Package version not found");
|
|
12332
13742
|
}
|
|
12333
13743
|
return pkg.version;
|
|
12334
13744
|
}
|
|
12335
13745
|
function bundledPluginVersion(source) {
|
|
12336
|
-
const match =
|
|
13746
|
+
const match = readFileSync8(source, "utf8").match(/local CURRENT_VERSION = "([^"]+)"/);
|
|
12337
13747
|
return match ? match[1] : null;
|
|
12338
13748
|
}
|
|
12339
13749
|
function assertBundledPluginVersion(source) {
|
|
@@ -12346,9 +13756,9 @@ function assertBundledPluginVersion(source) {
|
|
|
12346
13756
|
}
|
|
12347
13757
|
}
|
|
12348
13758
|
function filesMatch(a, b) {
|
|
12349
|
-
if (!
|
|
12350
|
-
const aBytes =
|
|
12351
|
-
const bBytes =
|
|
13759
|
+
if (!existsSync7(b)) return false;
|
|
13760
|
+
const aBytes = readFileSync8(a);
|
|
13761
|
+
const bBytes = readFileSync8(b);
|
|
12352
13762
|
return aBytes.length === bBytes.length && aBytes.equals(bBytes);
|
|
12353
13763
|
}
|
|
12354
13764
|
async function installBundledPlugin(options = {}) {
|
|
@@ -12361,7 +13771,7 @@ async function installBundledPlugin(options = {}) {
|
|
|
12361
13771
|
}
|
|
12362
13772
|
assertBundledPluginVersion(source);
|
|
12363
13773
|
const pluginsFolder = prepareInstall({ replaceVariant, log, warn });
|
|
12364
|
-
const dest =
|
|
13774
|
+
const dest = join7(pluginsFolder, ASSET_NAME);
|
|
12365
13775
|
if (filesMatch(source, dest)) return;
|
|
12366
13776
|
copyFileSync2(source, dest);
|
|
12367
13777
|
log(`Installed ${ASSET_NAME} to ${dest}`);
|
|
@@ -12374,7 +13784,7 @@ async function installPlugin(options = {}) {
|
|
|
12374
13784
|
const bundled = bundledAssetPath();
|
|
12375
13785
|
if (bundled) {
|
|
12376
13786
|
assertBundledPluginVersion(bundled);
|
|
12377
|
-
const dest2 =
|
|
13787
|
+
const dest2 = join7(pluginsFolder, ASSET_NAME);
|
|
12378
13788
|
if (filesMatch(bundled, dest2)) {
|
|
12379
13789
|
log(`${ASSET_NAME} already installed.`);
|
|
12380
13790
|
return;
|
|
@@ -12389,7 +13799,7 @@ async function installPlugin(options = {}) {
|
|
|
12389
13799
|
if (!asset) {
|
|
12390
13800
|
throw new Error(`${ASSET_NAME} not found in release ${release.tag_name}`);
|
|
12391
13801
|
}
|
|
12392
|
-
const dest =
|
|
13802
|
+
const dest = join7(pluginsFolder, ASSET_NAME);
|
|
12393
13803
|
log(`Downloading ${ASSET_NAME} from ${release.tag_name}...`);
|
|
12394
13804
|
await download(asset.browser_download_url, dest);
|
|
12395
13805
|
log(`Installed to ${dest}`);
|
|
@@ -12412,8 +13822,8 @@ init_dist();
|
|
|
12412
13822
|
import { createRequire } from "module";
|
|
12413
13823
|
if (process.argv.includes("--install-plugin")) {
|
|
12414
13824
|
const { installPlugin: installPlugin2 } = await Promise.resolve().then(() => (init_install_plugin(), install_plugin_exports));
|
|
12415
|
-
await installPlugin2().catch((
|
|
12416
|
-
console.error(
|
|
13825
|
+
await installPlugin2().catch((err2) => {
|
|
13826
|
+
console.error(err2 instanceof Error ? err2.message : String(err2));
|
|
12417
13827
|
process.exitCode = 1;
|
|
12418
13828
|
});
|
|
12419
13829
|
} else {
|
|
@@ -12422,9 +13832,9 @@ if (process.argv.includes("--install-plugin")) {
|
|
|
12422
13832
|
await installBundledPlugin2({
|
|
12423
13833
|
log: (message) => console.error(`[install-plugin] ${message}`),
|
|
12424
13834
|
warn: (message) => console.error(message)
|
|
12425
|
-
}).catch((
|
|
13835
|
+
}).catch((err2) => {
|
|
12426
13836
|
console.error(
|
|
12427
|
-
`[install-plugin] Auto-install skipped: ${
|
|
13837
|
+
`[install-plugin] Auto-install skipped: ${err2 instanceof Error ? err2.message : String(err2)}`
|
|
12428
13838
|
);
|
|
12429
13839
|
});
|
|
12430
13840
|
}
|