@chrrxs/robloxstudio-mcp 2.16.1 → 2.16.2

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.
@@ -1,6 +1,9 @@
1
1
  // Cross-DM stop_playtest signaling via plugin:SetSetting, scoped by
2
2
  // per-instance setting key so the same Studio process can host playtests
3
3
  // for multiple places without one place's stop_playtest yanking another's.
4
+ // During publish-after-connect, both "anon:<uuid>" and "place:<PlaceId>"
5
+ // can refer to the same Studio place, so stop requests are mirrored across
6
+ // both keys while the monitor waits for a matching result on either key.
4
7
  //
5
8
  // `plugin:SetSetting` / `plugin:GetSetting` is a per-plugin persistent store
6
9
  // shared across every DataModel the plugin runs in (edit DMs, play-server
@@ -71,23 +74,36 @@ function init(p: Plugin): void {
71
74
  // agree on the place identifier (published places: placeId; unpublished:
72
75
  // UUID on ServerStorage's __MCPPlaceId attribute, travels with the .rbxl
73
76
  // into the play DM).
74
- function computeInstanceId(): string {
77
+ function addUnique(values: string[], value: string): void {
78
+ if (!values.includes(value)) {
79
+ values.push(value);
80
+ }
81
+ }
82
+
83
+ function computeInstanceIds(): string[] {
84
+ const ids: string[] = [];
75
85
  if (game.PlaceId !== 0) {
76
- return `place:${tostring(game.PlaceId)}`;
86
+ addUnique(ids, `place:${tostring(game.PlaceId)}`);
77
87
  }
78
88
  const existing = ServerStorage.GetAttribute("__MCPPlaceId");
79
89
  if (typeIs(existing, "string") && existing !== "") {
80
- return `anon:${existing as string}`;
90
+ addUnique(ids, `anon:${existing as string}`);
91
+ } else if (game.PlaceId === 0) {
92
+ const fresh = HttpService.GenerateGUID(false);
93
+ pcall(() => ServerStorage.SetAttribute("__MCPPlaceId", fresh));
94
+ addUnique(ids, `anon:${fresh}`);
81
95
  }
82
- const fresh = HttpService.GenerateGUID(false);
83
- pcall(() => ServerStorage.SetAttribute("__MCPPlaceId", fresh));
84
- return `anon:${fresh}`;
96
+ return ids;
85
97
  }
86
98
 
87
99
  function settingKey(instanceId: string): string {
88
100
  return SETTING_KEY_PREFIX + instanceId;
89
101
  }
90
102
 
103
+ function settingKeys(): string[] {
104
+ return computeInstanceIds().map((instanceId) => settingKey(instanceId));
105
+ }
106
+
91
107
  function readSetting(key: string): unknown {
92
108
  if (!pluginRef) return undefined;
93
109
  const [ok, value] = pcall(() => pluginRef!.GetSetting(key));
@@ -168,18 +184,19 @@ function startMonitor(): void {
168
184
  warn("[robloxstudio-mcp] StopPlayMonitor.startMonitor called before init; skipping");
169
185
  return;
170
186
  }
171
- const myKey = settingKey(computeInstanceId());
172
187
  task.spawn(() => {
173
188
  while (true) {
174
- const value = readSetting(myKey);
175
- if (value === true) {
176
- // Legacy boolean requests are ambiguous and may be stale from
177
- // a prior crashed session. New stop requests use token payloads.
178
- writeSetting(myKey, false);
179
- } else {
180
- const payload = decodePayload(value);
181
- if (payload) {
182
- handleStopRequest(myKey, payload);
189
+ for (const myKey of settingKeys()) {
190
+ const value = readSetting(myKey);
191
+ if (value === true) {
192
+ // Legacy boolean requests are ambiguous and may be stale from
193
+ // a prior crashed session. New stop requests use token payloads.
194
+ writeSetting(myKey, false);
195
+ } else {
196
+ const payload = decodePayload(value);
197
+ if (payload) {
198
+ handleStopRequest(myKey, payload);
199
+ }
183
200
  }
184
201
  }
185
202
  task.wait(POLL_INTERVAL_SEC);
@@ -189,28 +206,32 @@ function startMonitor(): void {
189
206
 
190
207
  function requestStop(): StopRequestResult {
191
208
  if (!pluginRef) return { ok: false };
192
- const myKey = settingKey(computeInstanceId());
193
209
  const requestId = HttpService.GenerateGUID(false);
194
- const ok = writePayload(myKey, {
210
+ const payload: StopPayload = {
195
211
  kind: "request",
196
212
  id: requestId,
197
213
  requestedAt: tick(),
198
- });
214
+ };
215
+ let ok = false;
216
+ for (const myKey of settingKeys()) {
217
+ ok = writePayload(myKey, payload) || ok;
218
+ }
199
219
  return { ok, requestId: ok ? requestId : undefined };
200
220
  }
201
221
 
202
222
  function waitForConsumption(requestId: string): StopConsumptionResult {
203
223
  if (!pluginRef) return { ok: false, consumed: false, error: "Plugin reference is not initialized." };
204
- const myKey = settingKey(computeInstanceId());
205
224
  const start = tick();
206
225
  while (tick() - start < WAIT_FOR_CONSUMPTION_TIMEOUT_SEC) {
207
- const payload = decodePayload(readSetting(myKey));
208
- if (payload && payload.kind === "result" && payload.id === requestId) {
209
- return {
210
- ok: payload.ok === true,
211
- consumed: true,
212
- error: payload.error,
213
- };
226
+ for (const myKey of settingKeys()) {
227
+ const payload = decodePayload(readSetting(myKey));
228
+ if (payload && payload.kind === "result" && payload.id === requestId) {
229
+ return {
230
+ ok: payload.ok === true,
231
+ consumed: true,
232
+ error: payload.error,
233
+ };
234
+ }
214
235
  }
215
236
  task.wait(WAIT_POLL_SEC);
216
237
  }
@@ -223,12 +244,13 @@ function waitForConsumption(requestId: string): StopConsumptionResult {
223
244
 
224
245
  function clearPending(requestId?: string): void {
225
246
  if (!pluginRef) return;
226
- const myKey = settingKey(computeInstanceId());
227
- if (requestId !== undefined) {
228
- const payload = decodePayload(readSetting(myKey));
229
- if (payload && payload.id !== requestId) return;
247
+ for (const myKey of settingKeys()) {
248
+ if (requestId !== undefined) {
249
+ const payload = decodePayload(readSetting(myKey));
250
+ if (payload && payload.id !== requestId) continue;
251
+ }
252
+ writeSetting(myKey, false);
230
253
  }
231
- writeSetting(myKey, false);
232
254
  }
233
255
 
234
256
  export = {