@chrrxs/robloxstudio-mcp-inspector 2.16.0 → 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.
- package/dist/index.js +210 -37
- package/package.json +1 -1
- package/studio-plugin/MCPInspectorPlugin.rbxmx +556 -141
- package/studio-plugin/MCPPlugin.rbxmx +556 -141
- package/studio-plugin/src/modules/ClientBroker.ts +32 -6
- package/studio-plugin/src/modules/Communication.ts +81 -41
- package/studio-plugin/src/modules/HttpDiagnostics.ts +50 -0
- package/studio-plugin/src/modules/ServerUrlSettings.ts +61 -0
- package/studio-plugin/src/modules/StopPlayMonitor.ts +184 -45
- package/studio-plugin/src/modules/handlers/TestHandlers.ts +18 -13
- package/studio-plugin/src/server/index.server.ts +15 -4
package/dist/index.js
CHANGED
|
@@ -27,7 +27,12 @@ function toPublic(inst) {
|
|
|
27
27
|
connectedAt: inst.connectedAt
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
-
|
|
30
|
+
function publishedInstanceId(placeId) {
|
|
31
|
+
if (placeId === void 0 || !Number.isFinite(placeId) || placeId <= 0)
|
|
32
|
+
return void 0;
|
|
33
|
+
return `place:${Math.trunc(placeId)}`;
|
|
34
|
+
}
|
|
35
|
+
var RoutingFailure, STALE_INSTANCE_MS, INSTANCE_ALIAS_TTL_MS, BridgeService;
|
|
31
36
|
var init_bridge_service = __esm({
|
|
32
37
|
"../core/dist/bridge-service.js"() {
|
|
33
38
|
"use strict";
|
|
@@ -40,21 +45,91 @@ var init_bridge_service = __esm({
|
|
|
40
45
|
}
|
|
41
46
|
};
|
|
42
47
|
STALE_INSTANCE_MS = 3e4;
|
|
48
|
+
INSTANCE_ALIAS_TTL_MS = 5 * 60 * 1e3;
|
|
43
49
|
BridgeService = class {
|
|
44
50
|
pendingRequests = /* @__PURE__ */ new Map();
|
|
45
51
|
// Keyed by pluginSessionId (the per-plugin GUID).
|
|
46
52
|
instances = /* @__PURE__ */ new Map();
|
|
53
|
+
instanceAliases = /* @__PURE__ */ new Map();
|
|
47
54
|
requestTimeout = 3e4;
|
|
55
|
+
canonicalInstanceId(instanceId, placeId) {
|
|
56
|
+
return publishedInstanceId(placeId) ?? instanceId;
|
|
57
|
+
}
|
|
58
|
+
rememberInstanceAlias(aliasInstanceId, targetInstanceId) {
|
|
59
|
+
if (aliasInstanceId === targetInstanceId)
|
|
60
|
+
return;
|
|
61
|
+
this.instanceAliases.set(aliasInstanceId, {
|
|
62
|
+
targetInstanceId,
|
|
63
|
+
lastSeen: Date.now()
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
resolveInstanceAlias(instanceId) {
|
|
67
|
+
const alias = this.instanceAliases.get(instanceId);
|
|
68
|
+
if (!alias)
|
|
69
|
+
return instanceId;
|
|
70
|
+
alias.lastSeen = Date.now();
|
|
71
|
+
return alias.targetInstanceId;
|
|
72
|
+
}
|
|
73
|
+
migratePendingRequests(fromInstanceId, toInstanceId) {
|
|
74
|
+
if (fromInstanceId === toInstanceId)
|
|
75
|
+
return;
|
|
76
|
+
for (const request of this.pendingRequests.values()) {
|
|
77
|
+
if (request.targetInstanceId === fromInstanceId) {
|
|
78
|
+
request.targetInstanceId = toInstanceId;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
cleanupStaleAliases(now = Date.now()) {
|
|
83
|
+
for (const [alias, entry] of this.instanceAliases.entries()) {
|
|
84
|
+
const targetIsLive = this.getInstances().some((inst) => inst.instanceId === entry.targetInstanceId);
|
|
85
|
+
if (!targetIsLive && now - entry.lastSeen > INSTANCE_ALIAS_TTL_MS) {
|
|
86
|
+
this.instanceAliases.delete(alias);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
routingKeyForInstance(inst) {
|
|
91
|
+
return publishedInstanceId(inst.placeId) ?? this.resolveInstanceAlias(inst.instanceId);
|
|
92
|
+
}
|
|
93
|
+
matchingInstancesForInstanceId(instanceId) {
|
|
94
|
+
const resolvedInstanceId = this.resolveInstanceAlias(instanceId);
|
|
95
|
+
const ids = /* @__PURE__ */ new Set([instanceId, resolvedInstanceId]);
|
|
96
|
+
const placeIds = /* @__PURE__ */ new Set();
|
|
97
|
+
const addPlaceId = (placeId) => {
|
|
98
|
+
const published = publishedInstanceId(placeId);
|
|
99
|
+
if (!published || placeId === void 0)
|
|
100
|
+
return;
|
|
101
|
+
ids.add(published);
|
|
102
|
+
placeIds.add(Math.trunc(placeId));
|
|
103
|
+
};
|
|
104
|
+
const placeMatch = resolvedInstanceId.match(/^place:(\d+)$/) ?? instanceId.match(/^place:(\d+)$/);
|
|
105
|
+
if (placeMatch)
|
|
106
|
+
addPlaceId(Number(placeMatch[1]));
|
|
107
|
+
for (const inst of this.getInstances()) {
|
|
108
|
+
if (ids.has(inst.instanceId))
|
|
109
|
+
addPlaceId(inst.placeId);
|
|
110
|
+
}
|
|
111
|
+
return this.getInstances().filter((inst) => ids.has(inst.instanceId) || inst.placeId > 0 && placeIds.has(Math.trunc(inst.placeId)));
|
|
112
|
+
}
|
|
113
|
+
resolveInstanceId(instanceId) {
|
|
114
|
+
return this.resolveInstanceAlias(instanceId);
|
|
115
|
+
}
|
|
48
116
|
registerInstance(input) {
|
|
49
|
-
const { pluginSessionId,
|
|
117
|
+
const { pluginSessionId, role } = input;
|
|
118
|
+
const rawInstanceId = input.instanceId;
|
|
119
|
+
const instanceId = this.canonicalInstanceId(rawInstanceId, input.placeId);
|
|
50
120
|
const prior = this.instances.get(pluginSessionId);
|
|
51
121
|
let assignedRole = role;
|
|
52
122
|
const pluginVersion = input.pluginVersion ?? "";
|
|
53
123
|
const pluginVariant = input.pluginVariant ?? "unknown";
|
|
54
124
|
const serverVersion = input.serverVersion ?? "";
|
|
55
125
|
const versionMismatch = pluginVersion !== "" && serverVersion !== "" && pluginVersion !== serverVersion;
|
|
126
|
+
this.rememberInstanceAlias(rawInstanceId, instanceId);
|
|
127
|
+
if (prior && prior.instanceId !== instanceId) {
|
|
128
|
+
this.rememberInstanceAlias(prior.instanceId, instanceId);
|
|
129
|
+
this.migratePendingRequests(prior.instanceId, instanceId);
|
|
130
|
+
}
|
|
56
131
|
if (role === "client") {
|
|
57
|
-
if (prior && prior.
|
|
132
|
+
if (prior && prior.role.match(/^client-\d+$/)) {
|
|
58
133
|
assignedRole = prior.role;
|
|
59
134
|
} else {
|
|
60
135
|
const used = /* @__PURE__ */ new Set();
|
|
@@ -135,6 +210,7 @@ var init_bridge_service = __esm({
|
|
|
135
210
|
const inst = this.instances.get(pluginSessionId);
|
|
136
211
|
if (!inst)
|
|
137
212
|
return;
|
|
213
|
+
const priorInstanceId = inst.instanceId;
|
|
138
214
|
if (metadata.placeId !== void 0)
|
|
139
215
|
inst.placeId = metadata.placeId;
|
|
140
216
|
if (metadata.placeName !== void 0)
|
|
@@ -143,6 +219,15 @@ var init_bridge_service = __esm({
|
|
|
143
219
|
inst.dataModelName = metadata.dataModelName;
|
|
144
220
|
if (metadata.isRunning !== void 0)
|
|
145
221
|
inst.isRunning = metadata.isRunning;
|
|
222
|
+
const canonicalInstanceId = this.canonicalInstanceId(inst.instanceId, inst.placeId);
|
|
223
|
+
if (canonicalInstanceId !== inst.instanceId) {
|
|
224
|
+
const duplicate = Array.from(this.instances.values()).find((other) => other.pluginSessionId !== pluginSessionId && other.instanceId === canonicalInstanceId && other.role === inst.role);
|
|
225
|
+
if (!duplicate) {
|
|
226
|
+
this.rememberInstanceAlias(priorInstanceId, canonicalInstanceId);
|
|
227
|
+
this.migratePendingRequests(priorInstanceId, canonicalInstanceId);
|
|
228
|
+
inst.instanceId = canonicalInstanceId;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
146
231
|
}
|
|
147
232
|
cleanupStaleInstances() {
|
|
148
233
|
const now = Date.now();
|
|
@@ -151,6 +236,37 @@ var init_bridge_service = __esm({
|
|
|
151
236
|
this.unregisterInstance(id);
|
|
152
237
|
}
|
|
153
238
|
}
|
|
239
|
+
this.cleanupStaleAliases(now);
|
|
240
|
+
}
|
|
241
|
+
getEquivalentInstanceIds(instanceId) {
|
|
242
|
+
const resolvedInstanceId = this.resolveInstanceAlias(instanceId);
|
|
243
|
+
const ids = /* @__PURE__ */ new Set([instanceId, resolvedInstanceId]);
|
|
244
|
+
const placeIds = /* @__PURE__ */ new Set();
|
|
245
|
+
const addPlaceId = (placeId) => {
|
|
246
|
+
const published = publishedInstanceId(placeId);
|
|
247
|
+
if (!published || placeId === void 0)
|
|
248
|
+
return;
|
|
249
|
+
ids.add(published);
|
|
250
|
+
placeIds.add(Math.trunc(placeId));
|
|
251
|
+
};
|
|
252
|
+
const placeMatch = resolvedInstanceId.match(/^place:(\d+)$/) ?? instanceId.match(/^place:(\d+)$/);
|
|
253
|
+
if (placeMatch)
|
|
254
|
+
addPlaceId(Number(placeMatch[1]));
|
|
255
|
+
for (const inst of this.getInstances()) {
|
|
256
|
+
if (ids.has(inst.instanceId)) {
|
|
257
|
+
addPlaceId(inst.placeId);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
for (const inst of this.getInstances()) {
|
|
261
|
+
if (inst.placeId > 0 && placeIds.has(Math.trunc(inst.placeId))) {
|
|
262
|
+
ids.add(inst.instanceId);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
for (const [alias, entry] of this.instanceAliases.entries()) {
|
|
266
|
+
if (ids.has(entry.targetInstanceId))
|
|
267
|
+
ids.add(alias);
|
|
268
|
+
}
|
|
269
|
+
return Array.from(ids);
|
|
154
270
|
}
|
|
155
271
|
// Resolves (instance_id, target-role) MCP arguments to a concrete
|
|
156
272
|
// routing decision: either a single (instanceId, role) tuple or a fanout
|
|
@@ -164,7 +280,7 @@ var init_bridge_service = __esm({
|
|
|
164
280
|
const isFanout = target === "all";
|
|
165
281
|
const role = target && target !== "all" ? target : void 0;
|
|
166
282
|
if (instance_id !== void 0) {
|
|
167
|
-
const matchingInstances =
|
|
283
|
+
const matchingInstances = this.matchingInstancesForInstanceId(instance_id);
|
|
168
284
|
if (matchingInstances.length === 0) {
|
|
169
285
|
return {
|
|
170
286
|
ok: false,
|
|
@@ -197,19 +313,19 @@ var init_bridge_service = __esm({
|
|
|
197
313
|
}
|
|
198
314
|
};
|
|
199
315
|
}
|
|
200
|
-
return { ok: true, mode: "single", targetInstanceId:
|
|
316
|
+
return { ok: true, mode: "single", targetInstanceId: exact.instanceId, targetRole: role };
|
|
201
317
|
}
|
|
202
318
|
if (matchingInstances.length === 1) {
|
|
203
319
|
return {
|
|
204
320
|
ok: true,
|
|
205
321
|
mode: "single",
|
|
206
|
-
targetInstanceId:
|
|
322
|
+
targetInstanceId: matchingInstances[0].instanceId,
|
|
207
323
|
targetRole: matchingInstances[0].role
|
|
208
324
|
};
|
|
209
325
|
}
|
|
210
326
|
const edit = matchingInstances.find((i) => i.role === "edit");
|
|
211
327
|
if (edit) {
|
|
212
|
-
return { ok: true, mode: "single", targetInstanceId:
|
|
328
|
+
return { ok: true, mode: "single", targetInstanceId: edit.instanceId, targetRole: "edit" };
|
|
213
329
|
}
|
|
214
330
|
return {
|
|
215
331
|
ok: false,
|
|
@@ -220,7 +336,7 @@ var init_bridge_service = __esm({
|
|
|
220
336
|
}
|
|
221
337
|
};
|
|
222
338
|
}
|
|
223
|
-
const distinctInstanceIds = new Set(instances.map((i) => i
|
|
339
|
+
const distinctInstanceIds = new Set(instances.map((i) => this.routingKeyForInstance(i)));
|
|
224
340
|
if (distinctInstanceIds.size === 0) {
|
|
225
341
|
return {
|
|
226
342
|
ok: false,
|
|
@@ -236,7 +352,7 @@ var init_bridge_service = __esm({
|
|
|
236
352
|
const msg = role ? `target=${role} is ambiguous because multiple Studio places are connected. Pass instance_id to choose a place.` : "Multiple Studio places are connected. Pass instance_id to disambiguate.";
|
|
237
353
|
return { ok: false, error: { code: errorCode, message: msg, data: errorData } };
|
|
238
354
|
}
|
|
239
|
-
const onlyInstanceId =
|
|
355
|
+
const onlyInstanceId = distinctInstanceIds.values().next().value;
|
|
240
356
|
return this.resolveTarget({ instance_id: onlyInstanceId, target });
|
|
241
357
|
}
|
|
242
358
|
async sendRequest(endpoint, data, targetInstanceId, targetRole) {
|
|
@@ -386,30 +502,60 @@ function createHttpServer(tools, bridge, allowedTools, serverConfig) {
|
|
|
386
502
|
});
|
|
387
503
|
app.post("/ready", (req, res) => {
|
|
388
504
|
const { pluginSessionId, instanceId, role, placeId, placeName, dataModelName, isRunning, pluginVersion, pluginVariant } = req.body;
|
|
505
|
+
const requestContext = {
|
|
506
|
+
instanceId: typeof instanceId === "string" ? instanceId : void 0,
|
|
507
|
+
role: typeof role === "string" ? role : void 0,
|
|
508
|
+
placeId: typeof placeId === "number" ? placeId : void 0,
|
|
509
|
+
placeName: typeof placeName === "string" ? placeName : void 0,
|
|
510
|
+
dataModelName: typeof dataModelName === "string" ? dataModelName : void 0,
|
|
511
|
+
isRunning: typeof isRunning === "boolean" ? isRunning : void 0,
|
|
512
|
+
pluginVersion: typeof pluginVersion === "string" ? pluginVersion : void 0,
|
|
513
|
+
pluginVariant: typeof pluginVariant === "string" ? pluginVariant : void 0
|
|
514
|
+
};
|
|
389
515
|
if (!pluginSessionId || !instanceId || !role) {
|
|
516
|
+
const missingFields = [
|
|
517
|
+
!pluginSessionId ? "pluginSessionId" : void 0,
|
|
518
|
+
!instanceId ? "instanceId" : void 0,
|
|
519
|
+
!role ? "role" : void 0
|
|
520
|
+
].filter((field) => !!field);
|
|
390
521
|
res.status(400).json({
|
|
391
522
|
success: false,
|
|
392
|
-
error: "
|
|
523
|
+
error: "missing_ready_fields",
|
|
524
|
+
message: `/ready missing required field(s): ${missingFields.join(", ")}`,
|
|
525
|
+
missingFields,
|
|
526
|
+
request: requestContext
|
|
527
|
+
});
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
let result;
|
|
531
|
+
try {
|
|
532
|
+
result = bridge.registerInstance({
|
|
533
|
+
pluginSessionId,
|
|
534
|
+
instanceId,
|
|
535
|
+
role,
|
|
536
|
+
placeId: typeof placeId === "number" ? placeId : 0,
|
|
537
|
+
placeName: typeof placeName === "string" ? placeName : "",
|
|
538
|
+
dataModelName: typeof dataModelName === "string" ? dataModelName : "",
|
|
539
|
+
isRunning: !!isRunning,
|
|
540
|
+
pluginVersion: typeof pluginVersion === "string" ? pluginVersion : "",
|
|
541
|
+
pluginVariant: typeof pluginVariant === "string" ? pluginVariant : "unknown",
|
|
542
|
+
serverVersion: serverConfig?.version ?? ""
|
|
543
|
+
});
|
|
544
|
+
} catch (err) {
|
|
545
|
+
res.status(500).json({
|
|
546
|
+
success: false,
|
|
547
|
+
error: "ready_registration_exception",
|
|
548
|
+
message: err instanceof Error ? err.message : String(err),
|
|
549
|
+
request: requestContext
|
|
393
550
|
});
|
|
394
551
|
return;
|
|
395
552
|
}
|
|
396
|
-
const result = bridge.registerInstance({
|
|
397
|
-
pluginSessionId,
|
|
398
|
-
instanceId,
|
|
399
|
-
role,
|
|
400
|
-
placeId: typeof placeId === "number" ? placeId : 0,
|
|
401
|
-
placeName: typeof placeName === "string" ? placeName : "",
|
|
402
|
-
dataModelName: typeof dataModelName === "string" ? dataModelName : "",
|
|
403
|
-
isRunning: !!isRunning,
|
|
404
|
-
pluginVersion: typeof pluginVersion === "string" ? pluginVersion : "",
|
|
405
|
-
pluginVariant: typeof pluginVariant === "string" ? pluginVariant : "unknown",
|
|
406
|
-
serverVersion: serverConfig?.version ?? ""
|
|
407
|
-
});
|
|
408
553
|
if (!result.ok) {
|
|
409
554
|
res.status(409).json({
|
|
410
555
|
success: false,
|
|
411
556
|
error: result.error.code,
|
|
412
557
|
message: result.error.message,
|
|
558
|
+
request: requestContext,
|
|
413
559
|
existing: result.error.existing
|
|
414
560
|
});
|
|
415
561
|
return;
|
|
@@ -3012,23 +3158,25 @@ var init_tools = __esm({
|
|
|
3012
3158
|
if (!r.ok)
|
|
3013
3159
|
throw new RoutingFailure(r.error);
|
|
3014
3160
|
const resolvedId = r.targetInstanceId;
|
|
3015
|
-
const
|
|
3016
|
-
const
|
|
3017
|
-
|
|
3161
|
+
const equivalentIds = new Set(this.bridge.getEquivalentInstanceIds(resolvedId));
|
|
3162
|
+
const instances = this.bridge.getInstances().filter((i) => equivalentIds.has(i.instanceId));
|
|
3163
|
+
const client = instances.filter((inst) => inst.role.startsWith("client")).sort((a, b) => a.role.localeCompare(b.role))[0];
|
|
3164
|
+
return { instanceId: client?.instanceId ?? resolvedId, clientRole: client?.role };
|
|
3018
3165
|
}
|
|
3019
3166
|
_resolveInstanceIdOnly(instance_id) {
|
|
3020
3167
|
const instances = this.bridge.getInstances();
|
|
3021
3168
|
const publicList = this.bridge.getPublicInstances();
|
|
3022
3169
|
const errorData = { instances: publicList, count: publicList.length };
|
|
3023
3170
|
if (instance_id !== void 0) {
|
|
3024
|
-
|
|
3171
|
+
const resolvedInstanceId = this.bridge.resolveInstanceId(instance_id);
|
|
3172
|
+
if (!instances.some((i) => i.instanceId === resolvedInstanceId)) {
|
|
3025
3173
|
throw new RoutingFailure({
|
|
3026
3174
|
code: "unrecognized_instance_id",
|
|
3027
3175
|
message: `instance_id "${instance_id}" is not connected. Pass one from data.instances.`,
|
|
3028
3176
|
data: errorData
|
|
3029
3177
|
});
|
|
3030
3178
|
}
|
|
3031
|
-
return
|
|
3179
|
+
return resolvedInstanceId;
|
|
3032
3180
|
}
|
|
3033
3181
|
const distinct = Array.from(new Set(instances.map((i) => i.instanceId)));
|
|
3034
3182
|
if (distinct.length === 0) {
|
|
@@ -3066,6 +3214,10 @@ var init_tools = __esm({
|
|
|
3066
3214
|
_rolesForInstance(instanceId) {
|
|
3067
3215
|
return this.bridge.getInstances().filter((i) => i.instanceId === instanceId).map((i) => i.role);
|
|
3068
3216
|
}
|
|
3217
|
+
_rolesForEquivalentInstances(instanceId) {
|
|
3218
|
+
const instanceIds = new Set(this.bridge.getEquivalentInstanceIds(instanceId));
|
|
3219
|
+
return this.bridge.getInstances().filter((i) => instanceIds.has(i.instanceId)).map((i) => i.role);
|
|
3220
|
+
}
|
|
3069
3221
|
_clientRolesForInstance(instanceId) {
|
|
3070
3222
|
return this._rolesForInstance(instanceId).filter((role) => /^client-\d+$/.test(role)).sort((a, b) => Number(a.slice("client-".length)) - Number(b.slice("client-".length)));
|
|
3071
3223
|
}
|
|
@@ -3223,12 +3375,13 @@ var init_tools = __esm({
|
|
|
3223
3375
|
throw new Error(`capture_device_matrix cannot safely restore active custom device "${s.activeDeviceId}". Switch the simulator to default or a built-in preset first, or pass restoreAfter=false only if you intentionally accept changing the simulator state.`);
|
|
3224
3376
|
}
|
|
3225
3377
|
}
|
|
3226
|
-
async _waitForRuntimeRoles(instanceId, opts, timeoutSec = 30) {
|
|
3378
|
+
async _waitForRuntimeRoles(instanceId, opts, timeoutSec = 30, equivalentInstances = false) {
|
|
3227
3379
|
const deadline = Date.now() + timeoutSec * 1e3;
|
|
3228
3380
|
while (Date.now() < deadline) {
|
|
3229
|
-
const roles = this._rolesForInstance(instanceId);
|
|
3381
|
+
const roles = equivalentInstances ? this._rolesForEquivalentInstances(instanceId) : this._rolesForInstance(instanceId);
|
|
3382
|
+
const clientRoles = equivalentInstances ? roles.filter((role) => /^client-\d+$/.test(role)) : this._clientRolesForInstance(instanceId);
|
|
3230
3383
|
const hasServer = !opts.server || roles.includes("server");
|
|
3231
|
-
const hasClients = opts.clientCount === void 0 ||
|
|
3384
|
+
const hasClients = opts.clientCount === void 0 || clientRoles.length >= opts.clientCount;
|
|
3232
3385
|
const absent = opts.absentRole === void 0 || !roles.includes(opts.absentRole);
|
|
3233
3386
|
const runtimeAbsent = !opts.noRuntime || !roles.some((role) => role === "server" || /^client-\d+$/.test(role));
|
|
3234
3387
|
if (hasServer && hasClients && absent && runtimeAbsent) {
|
|
@@ -3236,7 +3389,11 @@ var init_tools = __esm({
|
|
|
3236
3389
|
}
|
|
3237
3390
|
await sleep(250);
|
|
3238
3391
|
}
|
|
3239
|
-
return {
|
|
3392
|
+
return {
|
|
3393
|
+
ok: false,
|
|
3394
|
+
roles: equivalentInstances ? this._rolesForEquivalentInstances(instanceId) : this._rolesForInstance(instanceId),
|
|
3395
|
+
timedOut: true
|
|
3396
|
+
};
|
|
3240
3397
|
}
|
|
3241
3398
|
async _waitForExactClientCount(instanceId, expectedClientCount, timeoutSec = 30, stableMs = 3e3) {
|
|
3242
3399
|
const deadline = Date.now() + timeoutSec * 1e3;
|
|
@@ -3261,10 +3418,11 @@ var init_tools = __esm({
|
|
|
3261
3418
|
const clientCount = this._clientRolesForInstance(instanceId).length;
|
|
3262
3419
|
return { ok: false, roles, timedOut: true, extraClients: clientCount > expectedClientCount, clientCount };
|
|
3263
3420
|
}
|
|
3264
|
-
async _waitForRuntimeRolesFresh(instanceId, connectedAfter, requiredRoles, timeoutSec = 60) {
|
|
3421
|
+
async _waitForRuntimeRolesFresh(instanceId, connectedAfter, requiredRoles, timeoutSec = 60, equivalentInstances = false) {
|
|
3265
3422
|
const deadline = Date.now() + timeoutSec * 1e3;
|
|
3266
3423
|
while (Date.now() < deadline) {
|
|
3267
|
-
const
|
|
3424
|
+
const instanceIds = equivalentInstances ? new Set(this.bridge.getEquivalentInstanceIds(instanceId)) : /* @__PURE__ */ new Set([instanceId]);
|
|
3425
|
+
const instances = this.bridge.getInstances().filter((i) => instanceIds.has(i.instanceId));
|
|
3268
3426
|
const roles = instances.map((i) => i.role);
|
|
3269
3427
|
const freshRoles = new Set(instances.filter((i) => i.connectedAt >= connectedAfter).map((i) => i.role));
|
|
3270
3428
|
if (requiredRoles.every((role) => freshRoles.has(role))) {
|
|
@@ -3272,7 +3430,11 @@ var init_tools = __esm({
|
|
|
3272
3430
|
}
|
|
3273
3431
|
await sleep(250);
|
|
3274
3432
|
}
|
|
3275
|
-
return {
|
|
3433
|
+
return {
|
|
3434
|
+
ok: false,
|
|
3435
|
+
roles: equivalentInstances ? this._rolesForEquivalentInstances(instanceId) : this._rolesForInstance(instanceId),
|
|
3436
|
+
timedOut: true
|
|
3437
|
+
};
|
|
3276
3438
|
}
|
|
3277
3439
|
async getFileTree(path2 = "", instance_id) {
|
|
3278
3440
|
const response = await this._callSingle("/api/file-tree", { path: path2 }, void 0, instance_id);
|
|
@@ -4306,7 +4468,7 @@ ${code}`
|
|
|
4306
4468
|
let wait;
|
|
4307
4469
|
if (response?.success === true) {
|
|
4308
4470
|
const requiredRoles = mode === "play" ? ["server", "client-1"] : ["server"];
|
|
4309
|
-
wait = await this._waitForRuntimeRolesFresh(resolved.targetInstanceId, startedAt, requiredRoles);
|
|
4471
|
+
wait = await this._waitForRuntimeRolesFresh(resolved.targetInstanceId, startedAt, requiredRoles, 60, true);
|
|
4310
4472
|
}
|
|
4311
4473
|
const body = wait ? {
|
|
4312
4474
|
...response,
|
|
@@ -4324,9 +4486,20 @@ ${code}`
|
|
|
4324
4486
|
};
|
|
4325
4487
|
}
|
|
4326
4488
|
async stopPlaytest(instance_id) {
|
|
4327
|
-
const
|
|
4489
|
+
const { instanceId } = this._resolveSingleTarget("edit", instance_id);
|
|
4490
|
+
const response = await this.client.request("/api/stop-playtest", {}, instanceId, "edit");
|
|
4491
|
+
let wait;
|
|
4492
|
+
if (response?.success === true) {
|
|
4493
|
+
wait = await this._waitForRuntimeRoles(instanceId, { noRuntime: true }, 15, true);
|
|
4494
|
+
}
|
|
4495
|
+
const body = wait ? {
|
|
4496
|
+
...response,
|
|
4497
|
+
runtimeStopped: wait.ok,
|
|
4498
|
+
timedOut: wait.timedOut,
|
|
4499
|
+
roles: wait.roles
|
|
4500
|
+
} : response;
|
|
4328
4501
|
return {
|
|
4329
|
-
content: [{ type: "text", text: JSON.stringify(
|
|
4502
|
+
content: [{ type: "text", text: JSON.stringify(body) }]
|
|
4330
4503
|
};
|
|
4331
4504
|
}
|
|
4332
4505
|
async getPlaytestOutput(target, instance_id) {
|
package/package.json
CHANGED