@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 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
- this.instances.set(pluginSessionId, {
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((resolve4, reject) => {
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: resolve4,
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 (err) {
690
- console.error(`[auth] Could not read/create ${filePath} (${err instanceof Error ? err.message : err}). Using an in-memory token for this process; set ROBLOX_STUDIO_AUTH_TOKEN to share one across sessions.`);
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 = (path5) => path5 === "/mcp" || path5.startsWith("/mcp/") || path5 === "/proxy" || path5 === "/instances" || path5 === "/unregister-instance-id";
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 (err) {
915
+ } catch (err2) {
895
916
  res.status(500).json({
896
917
  success: false,
897
918
  error: "ready_registration_exception",
898
- message: err instanceof Error ? err.message : String(err),
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 (err) {
1050
- res.status(500).json({ error: err.message || "Proxy request failed" });
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 (resolve4, reject) => {
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
- resolve4({ server, port });
1188
+ resolve5({ server, port });
1168
1189
  return;
1169
- } catch (err) {
1170
- if (err.code === "EADDRINUSE") {
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(err);
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((resolve4, reject) => {
1203
+ return new Promise((resolve5, reject) => {
1183
1204
  const server = http.createServer(app);
1184
- const onError = (err) => {
1205
+ const onError = (err2) => {
1185
1206
  server.removeListener("error", onError);
1186
- reject(err);
1207
+ reject(err2);
1187
1208
  };
1188
1209
  server.once("error", onError);
1189
1210
  server.listen(port, host, () => {
1190
1211
  server.removeListener("error", onError);
1191
- resolve4(server);
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 (err) {
1386
- throw new Error(`Syntax error: ${err.message}`);
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 (err) {
1698
- if (err instanceof BreakSignal || err instanceof ContinueSignal || err instanceof ReturnSignal)
1699
- throw err;
1700
- if (err instanceof ScriptTimeoutError)
1701
- throw err;
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 = err instanceof Error ? { message: err.message } : err;
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 (err) {
2584
- if (err instanceof ScriptTimeoutError) {
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: ${err?.message ?? String(err)}`);
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((resolve4) => setTimeout(resolve4, intervalMs));
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
- if (parsed.closedAt !== void 0) {
3181
- fs.rmSync(file, { force: true });
3182
- this.appendEventUnlocked({
3183
- event: "registry_pruned_closed_record",
3184
- recordId: parsed.recordId,
3185
- instanceId: parsed.instanceId,
3186
- source: parsed.source,
3187
- reason: "closed_at_present",
3188
- action: "deleted_record"
3189
- }, now);
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
- fs.rmSync(file, { force: true });
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: "registry_pruned_previous_boot",
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: "deleted_record_and_cleaned_baseplate"
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
- fs.rmSync(file, { force: true });
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: "registry_pruned_stale_process",
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: "deleted_record_and_cleaned_baseplate"
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 (err) {
3305
- return err.code === "EPERM";
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 | Select-Object Id,Name,Path,MainWindowTitle | ConvertTo-Json -Compress");
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((resolve4) => setTimeout(resolve4, ms));
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.findOpenByInstanceId(instanceId, this.registrySweepOptions());
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
- if (record.recordId) {
3511
- this.registry.attachInstanceId(record.recordId, instanceId);
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
- const proc = this.processAdapter.spawnStudio ? this.processAdapter.spawnStudio(exe, args, spawnOptions) : spawn(exe, args, spawnOptions);
3526
- proc.unref();
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: this.getCurrentBootId(),
3725
+ bootId,
3540
3726
  deleteLocalPlaceFileOnClose: options.source === "baseplate"
3541
3727
  };
3542
3728
  this.pending.add(record);
3543
- this.registry.upsert(this.toRegistryRecord(record));
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
- this.registry.upsert(this.toRegistryRecord(record));
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.registry.upsert(this.toRegistryRecord(record));
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: "deleted_record_and_cleaned_baseplate"
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.markClosedInMemory(record);
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.markClosedInMemory(record);
3642
- this.markClosedInRegistry(record);
3643
- return { status: "already_closed", instanceId: record.instanceId };
3644
- }
3645
- record.closedAt = Date.now();
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.markClosedInRegistry(record);
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
- markClosedInRegistry(record) {
3751
- if (record.recordId)
3752
- this.registry.markClosed(record.recordId, record.closedAt ?? Date.now());
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.instanceId ? Date.now() : void 0,
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 path4 from "path";
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((resolve4) => setTimeout(resolve4, ms));
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 = path4.resolve(sourcePath);
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(path5 = "", instance_id) {
5970
- const response = await this._callSingle("/api/file-tree", { path: path5 }, void 0, instance_id);
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(path5, maxDepth, scriptsOnly, instance_id) {
7401
+ async getProjectStructure(path6, maxDepth, scriptsOnly, instance_id) {
6088
7402
  const response = await this._callSingle("/api/project-structure", {
6089
- path: path5,
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 = path4.resolve(outputPath);
7014
- fs3.mkdirSync(path4.dirname(resolvedOutputPath), { recursive: true });
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 = path4.resolve(outputPath);
7077
- fs3.mkdirSync(path4.dirname(resolvedOutputPath), { recursive: true });
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 = path4.resolve(summaryOutputPath);
7093
- fs3.mkdirSync(path4.dirname(resolvedSummaryPath), { recursive: true });
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 = path4.basename(record.localPlaceFile);
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: !!record2,
7242
- source: record2?.source,
7243
- place_id: record2?.placeId ?? connected2[0]?.placeId,
7244
- place_version: record2?.placeVersion,
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
- instance_id: record2.instanceId,
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({ message: "Studio launch requested." });
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
- try {
7349
- this.instanceManager.close(record);
7350
- } catch {
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
- error: "Studio launched, but the MCP plugin did not connect before timeout."
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
- instance_id: connected.instanceId,
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 (err) {
7542
- body.edit = { error: err instanceof Error ? err.message : String(err) };
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 (err) {
7550
- body.server = { error: err instanceof Error ? err.message : String(err) };
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 = path4.resolve(startDir);
9254
+ let dir = path5.resolve(startDir);
7877
9255
  let previous = "";
7878
9256
  while (dir !== previous) {
7879
- if (fs3.existsSync(path4.join(dir, ".git")) || fs3.existsSync(path4.join(dir, "package.json"))) {
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 = path4.dirname(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 = path4.resolve(candidate);
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 = path4.resolve(process.cwd());
9296
+ const cwd = path5.resolve(process.cwd());
7919
9297
  const projectRoot = _RobloxStudioTools.findProjectRoot(cwd);
7920
- const homeLibraryPath = path4.join(os3.homedir(), ".robloxstudio-mcp", "build-library");
7921
- const projectLibraryPath = projectRoot ? path4.join(projectRoot, "build-library") : null;
7922
- const cwdLibraryPath = path4.join(cwd, "build-library");
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 = path4.resolve(existing);
9314
+ result = path5.resolve(existing);
7937
9315
  } else if (projectLibraryPath) {
7938
9316
  try {
7939
9317
  result = _RobloxStudioTools.ensureWritableDirectory(projectLibraryPath, "project-root");
7940
- } catch (err) {
7941
- console.error(`Warning: could not create build-library at project root (${projectLibraryPath}): ${err.message}. Falling back to home directory.`);
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 = path4.join(_RobloxStudioTools.findLibraryPath(), `${buildId}.json`);
7964
- const dirPath = path4.dirname(filePath);
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 = path4.join(_RobloxStudioTools.findLibraryPath(), `${id}.json`);
8066
- const dirPath = path4.dirname(filePath);
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 = path4.join(_RobloxStudioTools.findLibraryPath(), `${id}.json`);
8125
- const dirPath = path4.dirname(filePath);
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 = path4.join(_RobloxStudioTools.findLibraryPath(), `${buildData}.json`);
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 = path4.join(_RobloxStudioTools.findLibraryPath(), `${buildData.id}.json`);
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 = path4.join(libraryPath, s);
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(path4.join(dirPath, file), "utf-8");
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 = path4.join(_RobloxStudioTools.findLibraryPath(), `${id}.json`);
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 = path4.join(libraryPath, `${buildId}.json`);
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 = path4.basename(filePath);
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 = path4.resolve(outputPath);
10269
+ const resolved = path5.resolve(outputPath);
8892
10270
  try {
8893
- fs3.mkdirSync(path4.dirname(resolved), { recursive: true });
10271
+ fs3.mkdirSync(path5.dirname(resolved), { recursive: true });
8894
10272
  fs3.writeFileSync(resolved, bytes);
8895
- } catch (err) {
8896
- return { content: [{ type: "text", text: JSON.stringify({ error: `failed to write ${resolved}: ${err.message}` }) }] };
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 = path4.resolve(source.path);
10305
+ const resolved = path5.resolve(source.path);
8928
10306
  try {
8929
10307
  bytes = fs3.readFileSync(resolved);
8930
- } catch (err) {
8931
- return { content: [{ type: "text", text: JSON.stringify({ error: `failed to read ${resolved}: ${err.message}` }) }] };
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 (err) {
8961
- return { content: [{ type: "text", text: JSON.stringify({ error: `fetch ${source.url} failed: ${err.message}` }) }] };
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 (err) {
8968
- return { content: [{ type: "text", text: JSON.stringify({ error: `base64 decode failed: ${err.message}` }) }] };
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 (err) {
10545
+ } catch (err2) {
9162
10546
  clearTimeout(timeoutId);
9163
- if (err.name === "AbortError") {
10547
+ if (err2.name === "AbortError") {
9164
10548
  throw new Error("Proxy request timeout");
9165
10549
  }
9166
- throw err;
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="close" can close an MCP-managed instance or an explicitly connected edit instance by instance_id. 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.',
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 to wait for plugin connection (default 120000).'
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. close accepts MCP-managed instances and explicitly connected edit instances.'
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 existsSync5, readFileSync as readFileSync6, unlinkSync } from "fs";
13563
+ import { existsSync as existsSync6, readFileSync as readFileSync7, unlinkSync } from "fs";
12155
13564
  import { execSync } from "child_process";
12156
- import { join as join5 } from "path";
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 = readFileSync6("/proc/version", "utf8");
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 join5(linuxPath, "Roblox", "Plugins");
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 join5(process.env.LOCALAPPDATA || join5(homedir5(), "AppData", "Local"), "Roblox", "Plugins");
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 join5(homedir5(), "Documents", "Roblox", "Plugins");
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 = join5(pluginsFolder, otherAssetName);
12201
- if (!existsSync5(otherDest))
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 (err) {
12208
- warn(`[install-plugin] Could not remove ${otherDest}: ${err}. Continuing.`);
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 existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync7, unlinkSync as unlinkSync2 } from "fs";
12247
- import { dirname as dirname4, join as join6 } from "path";
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((resolve4, reject) => {
12252
- const req = get(url, { headers: { "User-Agent": "robloxstudio-mcp-inspector" } }, resolve4);
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((resolve4, reject) => {
13680
+ return new Promise((resolve5, reject) => {
12271
13681
  const file = createWriteStream(dest);
12272
- const cleanup = (err) => {
13682
+ const cleanup = (err2) => {
12273
13683
  file.close(() => {
12274
13684
  try {
12275
13685
  unlinkSync2(dest);
12276
13686
  } catch {
12277
13687
  }
12278
- reject(err);
13688
+ reject(err2);
12279
13689
  });
12280
13690
  };
12281
13691
  res.pipe(file);
12282
13692
  file.on("finish", () => {
12283
13693
  file.close();
12284
- resolve4();
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 (!existsSync6(pluginsFolder)) {
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 = dirname4(fileURLToPath(import.meta.url));
13730
+ const currentDir = dirname5(fileURLToPath(import.meta.url));
12321
13731
  const candidates = [
12322
- join6(currentDir, "..", "studio-plugin", ASSET_NAME),
12323
- join6(currentDir, "..", "..", "..", "studio-plugin", ASSET_NAME)
13732
+ join7(currentDir, "..", "studio-plugin", ASSET_NAME),
13733
+ join7(currentDir, "..", "..", "..", "studio-plugin", ASSET_NAME)
12324
13734
  ];
12325
- return candidates.find((candidate) => existsSync6(candidate)) ?? null;
13735
+ return candidates.find((candidate) => existsSync7(candidate)) ?? null;
12326
13736
  }
12327
13737
  function packageVersion() {
12328
- const currentDir = dirname4(fileURLToPath(import.meta.url));
12329
- const pkg = JSON.parse(readFileSync7(join6(currentDir, "..", "package.json"), "utf8"));
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 = readFileSync7(source, "utf8").match(/local CURRENT_VERSION = "([^"]+)"/);
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 (!existsSync6(b)) return false;
12350
- const aBytes = readFileSync7(a);
12351
- const bBytes = readFileSync7(b);
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 = join6(pluginsFolder, ASSET_NAME);
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 = join6(pluginsFolder, ASSET_NAME);
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 = join6(pluginsFolder, ASSET_NAME);
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((err) => {
12416
- console.error(err instanceof Error ? err.message : String(err));
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((err) => {
13835
+ }).catch((err2) => {
12426
13836
  console.error(
12427
- `[install-plugin] Auto-install skipped: ${err instanceof Error ? err.message : String(err)}`
13837
+ `[install-plugin] Auto-install skipped: ${err2 instanceof Error ? err2.message : String(err2)}`
12428
13838
  );
12429
13839
  });
12430
13840
  }