@chrrxs/robloxstudio-mcp 2.15.2 → 2.16.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 +1232 -45
- package/package.json +1 -1
- package/studio-plugin/MCPInspectorPlugin.rbxmx +174 -133
- package/studio-plugin/MCPPlugin.rbxmx +174 -133
- package/studio-plugin/src/modules/Communication.ts +4 -12
- package/studio-plugin/src/modules/EvalBridges.ts +91 -64
- package/studio-plugin/src/modules/handlers/EvalRuntimeHandlers.ts +34 -6
- package/studio-plugin/src/modules/handlers/TestHandlers.ts +2 -33
- package/studio-plugin/src/server/index.server.ts +27 -8
|
@@ -21,30 +21,13 @@
|
|
|
21
21
|
// ServerScriptService.LoadStringEnabled, so eval_server_runtime works even
|
|
22
22
|
// when LoadStringEnabled=false (the default in fresh places).
|
|
23
23
|
//
|
|
24
|
-
// Lifecycle:
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
// tool — also gets the bridges cloned in. This is intentionally a little
|
|
32
|
-
// intrusive (two helper scripts visible in Explorer) in exchange for a
|
|
33
|
-
// zero-roundtrip eval_*_runtime experience for devs working 1:1 with an agent.
|
|
34
|
-
//
|
|
35
|
-
// Archivable handling: ExecutePlayModeAsync's deep-clone SKIPS instances
|
|
36
|
-
// with Archivable=false (verified empirically in v2.9.0 testing - bridges
|
|
37
|
-
// never reached the play DMs because we'd set them to false). We now keep
|
|
38
|
-
// Archivable=true so the clone works, and rely on cleanupBridges() to
|
|
39
|
-
// remove the scripts from the edit DM when the test ends. The only failure
|
|
40
|
-
// mode is the user saving DURING an active playtest, which would persist
|
|
41
|
-
// the bridges to the .rbxl - that's a no-op next session because
|
|
42
|
-
// installBridges() always calls cleanupBridges() first to clear stale
|
|
43
|
-
// instances. The RemoteFunction/BindableFunction that the bridge scripts
|
|
44
|
-
// CREATE at runtime stay Archivable=false (they're runtime-only and should
|
|
45
|
-
// never appear in a save).
|
|
46
|
-
|
|
47
|
-
import { ServerScriptService, StarterPlayer } from "@rbxts/services";
|
|
24
|
+
// Lifecycle: bridge scripts are created only in running play DataModels.
|
|
25
|
+
// The server plugin peer creates the Script in runtime ServerScriptService;
|
|
26
|
+
// each client plugin peer creates its LocalScript in that client's
|
|
27
|
+
// PlayerScripts. Nothing is installed into the edit DataModel anymore.
|
|
28
|
+
// Runtime-created scripts disappear naturally when the playtest stops.
|
|
29
|
+
|
|
30
|
+
import { Players, ReplicatedStorage, RunService, ServerScriptService, StarterPlayer } from "@rbxts/services";
|
|
48
31
|
|
|
49
32
|
const ScriptEditorService = game.GetService("ScriptEditorService");
|
|
50
33
|
|
|
@@ -122,12 +105,10 @@ end
|
|
|
122
105
|
`;
|
|
123
106
|
|
|
124
107
|
// Stamp written onto each installed bridge Script so we can tell whether the
|
|
125
|
-
// bridge currently in the DM was produced by THIS plugin build.
|
|
126
|
-
// hash of the actual bridge source plus the plugin version, so ANY
|
|
127
|
-
// the source (or a version bump) yields a new stamp
|
|
128
|
-
//
|
|
129
|
-
// keeping a stale bridge that happens to still be present (e.g. one saved into
|
|
130
|
-
// the .rbxl from an older build).
|
|
108
|
+
// runtime bridge currently in the play DM was produced by THIS plugin build.
|
|
109
|
+
// It's a djb2 hash of the actual bridge source plus the plugin version, so ANY
|
|
110
|
+
// change to the source (or a version bump) yields a new stamp and triggers a
|
|
111
|
+
// runtime refresh instead of keeping a stale bridge.
|
|
131
112
|
const STAMP_ATTR = "__MCPBridgeStamp";
|
|
132
113
|
|
|
133
114
|
function computeBridgeStamp(): string {
|
|
@@ -155,7 +136,12 @@ function setSource(scriptInst: Script | LocalScript, source: string): void {
|
|
|
155
136
|
}
|
|
156
137
|
}
|
|
157
138
|
|
|
158
|
-
|
|
139
|
+
interface InstallResult {
|
|
140
|
+
installed: boolean;
|
|
141
|
+
error?: string;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function findLegacyEditBridges(): { server?: Instance; client?: Instance } {
|
|
159
145
|
const sps = getStarterPlayerScripts();
|
|
160
146
|
return {
|
|
161
147
|
server: ServerScriptService.FindFirstChild(SERVER_SCRIPT_NAME),
|
|
@@ -163,8 +149,16 @@ function findBridges(): { server?: Instance; client?: Instance } {
|
|
|
163
149
|
};
|
|
164
150
|
}
|
|
165
151
|
|
|
166
|
-
|
|
167
|
-
const
|
|
152
|
+
function destroyIfPresent(parent: Instance, name: string): void {
|
|
153
|
+
const existing = parent.FindFirstChild(name);
|
|
154
|
+
if (existing) {
|
|
155
|
+
pcall(() => existing.Destroy());
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function cleanupLegacyEditBridges(): void {
|
|
160
|
+
if (RunService.IsRunning()) return;
|
|
161
|
+
const { server, client } = findLegacyEditBridges();
|
|
168
162
|
if (server) {
|
|
169
163
|
pcall(() => server.Destroy());
|
|
170
164
|
}
|
|
@@ -173,52 +167,75 @@ export function cleanupBridges(): void {
|
|
|
173
167
|
}
|
|
174
168
|
}
|
|
175
169
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}
|
|
170
|
+
function serverRuntimeBridgeReady(): boolean {
|
|
171
|
+
const scriptInst = ServerScriptService.FindFirstChild(SERVER_SCRIPT_NAME);
|
|
172
|
+
const bindable = ServerScriptService.FindFirstChild(BRIDGE_NAMES.serverLocal);
|
|
173
|
+
return scriptInst !== undefined &&
|
|
174
|
+
scriptInst.GetAttribute(STAMP_ATTR) === BRIDGE_STAMP &&
|
|
175
|
+
bindable !== undefined &&
|
|
176
|
+
bindable.IsA("BindableFunction");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function getPlayerScripts(): Instance | undefined {
|
|
180
|
+
const localPlayer = Players.LocalPlayer;
|
|
181
|
+
if (!localPlayer) return undefined;
|
|
182
|
+
let playerScripts = localPlayer.FindFirstChild("PlayerScripts");
|
|
183
|
+
if (!playerScripts) {
|
|
184
|
+
playerScripts = localPlayer.WaitForChild("PlayerScripts", 5);
|
|
192
185
|
}
|
|
193
|
-
return
|
|
186
|
+
return playerScripts;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function clientRuntimeBridgeReady(): boolean {
|
|
190
|
+
const playerScripts = getPlayerScripts();
|
|
191
|
+
if (!playerScripts) return false;
|
|
192
|
+
const scriptInst = playerScripts.FindFirstChild(CLIENT_SCRIPT_NAME);
|
|
193
|
+
const bindable = ReplicatedStorage.FindFirstChild(BRIDGE_NAMES.clientLocal);
|
|
194
|
+
return scriptInst !== undefined &&
|
|
195
|
+
scriptInst.GetAttribute(STAMP_ATTR) === BRIDGE_STAMP &&
|
|
196
|
+
bindable !== undefined &&
|
|
197
|
+
bindable.IsA("BindableFunction");
|
|
194
198
|
}
|
|
195
199
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
// inserting fresh. The injected script also self-cleans its
|
|
199
|
-
// ReplicatedStorage/ServerScriptService children at startup, but the
|
|
200
|
-
// containing Script/LocalScript objects themselves we must clear here.
|
|
201
|
-
cleanupBridges();
|
|
200
|
+
function installServerRuntimeBridge(): InstallResult {
|
|
201
|
+
if (serverRuntimeBridgeReady()) return { installed: true };
|
|
202
202
|
|
|
203
203
|
const [ok, err] = pcall(() => {
|
|
204
|
+
destroyIfPresent(ServerScriptService, SERVER_SCRIPT_NAME);
|
|
205
|
+
destroyIfPresent(ServerScriptService, BRIDGE_NAMES.serverLocal);
|
|
206
|
+
|
|
204
207
|
const serverScript = new Instance("Script");
|
|
205
208
|
serverScript.Name = SERVER_SCRIPT_NAME;
|
|
206
|
-
|
|
207
|
-
// script. cleanupBridges() removes it from the edit DM when the
|
|
208
|
-
// playtest ends.
|
|
209
|
+
serverScript.Archivable = false;
|
|
209
210
|
setSource(serverScript, SERVER_BRIDGE_SOURCE);
|
|
210
211
|
serverScript.SetAttribute(STAMP_ATTR, BRIDGE_STAMP);
|
|
211
212
|
serverScript.Parent = ServerScriptService;
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
if (!ok) {
|
|
216
|
+
return { installed: false, error: tostring(err) };
|
|
217
|
+
}
|
|
218
|
+
return { installed: true };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function installClientRuntimeBridge(): InstallResult {
|
|
222
|
+
if (clientRuntimeBridgeReady()) return { installed: true };
|
|
223
|
+
|
|
224
|
+
const playerScripts = getPlayerScripts();
|
|
225
|
+
if (!playerScripts) {
|
|
226
|
+
return { installed: false, error: "Players.LocalPlayer.PlayerScripts not found - cannot install client eval bridge" };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const [ok, err] = pcall(() => {
|
|
230
|
+
destroyIfPresent(playerScripts, CLIENT_SCRIPT_NAME);
|
|
231
|
+
destroyIfPresent(ReplicatedStorage, BRIDGE_NAMES.clientLocal);
|
|
212
232
|
|
|
213
|
-
const sps = getStarterPlayerScripts();
|
|
214
|
-
if (!sps) {
|
|
215
|
-
error("StarterPlayer.StarterPlayerScripts not found - cannot install client eval bridge");
|
|
216
|
-
}
|
|
217
233
|
const clientScript = new Instance("LocalScript");
|
|
218
234
|
clientScript.Name = CLIENT_SCRIPT_NAME;
|
|
235
|
+
clientScript.Archivable = false;
|
|
219
236
|
setSource(clientScript, CLIENT_BRIDGE_SOURCE);
|
|
220
237
|
clientScript.SetAttribute(STAMP_ATTR, BRIDGE_STAMP);
|
|
221
|
-
clientScript.Parent =
|
|
238
|
+
clientScript.Parent = playerScripts;
|
|
222
239
|
});
|
|
223
240
|
|
|
224
241
|
if (!ok) {
|
|
@@ -226,3 +243,13 @@ export function installBridges(): { installed: boolean; error?: string } {
|
|
|
226
243
|
}
|
|
227
244
|
return { installed: true };
|
|
228
245
|
}
|
|
246
|
+
|
|
247
|
+
export function ensureRuntimeBridgeInstalled(): InstallResult {
|
|
248
|
+
if (!RunService.IsRunning()) {
|
|
249
|
+
return { installed: false, error: "Eval bridges are installed only in running play DataModels" };
|
|
250
|
+
}
|
|
251
|
+
if (RunService.IsServer()) {
|
|
252
|
+
return installServerRuntimeBridge();
|
|
253
|
+
}
|
|
254
|
+
return installClientRuntimeBridge();
|
|
255
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LogService, ReplicatedStorage, RunService, ServerScriptService } from "@rbxts/services";
|
|
2
|
-
import { BRIDGE_NAMES } from "../EvalBridges";
|
|
2
|
+
import { BRIDGE_NAMES, ensureRuntimeBridgeInstalled } from "../EvalBridges";
|
|
3
3
|
import LuauExec from "../LuauExec";
|
|
4
4
|
|
|
5
5
|
const PAYLOAD_INSTANCE_NAME = "__MCPEvalPayload";
|
|
@@ -15,6 +15,21 @@ interface WrapperResult {
|
|
|
15
15
|
output?: unknown;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
function findBridge(config: { service: Instance; bridgeName: string }): BindableFunction | undefined {
|
|
19
|
+
const bridge = config.service.FindFirstChild(config.bridgeName);
|
|
20
|
+
return bridge && bridge.IsA("BindableFunction") ? bridge : undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function waitForBridge(config: { service: Instance; bridgeName: string }, timeoutSec = 2): BindableFunction | undefined {
|
|
24
|
+
const deadline = tick() + timeoutSec;
|
|
25
|
+
let bridge = findBridge(config);
|
|
26
|
+
while (!bridge && tick() < deadline) {
|
|
27
|
+
task.wait(0.05);
|
|
28
|
+
bridge = findBridge(config);
|
|
29
|
+
}
|
|
30
|
+
return bridge;
|
|
31
|
+
}
|
|
32
|
+
|
|
18
33
|
function getBridgeConfig() {
|
|
19
34
|
if (!RunService.IsRunning()) {
|
|
20
35
|
return {
|
|
@@ -25,13 +40,13 @@ function getBridgeConfig() {
|
|
|
25
40
|
return {
|
|
26
41
|
service: ServerScriptService,
|
|
27
42
|
bridgeName: BRIDGE_NAMES.serverLocal,
|
|
28
|
-
missingError: "ServerEvalBridge not found. The bridge runs inside the play DM, so a playtest must be running. The bridge installs automatically
|
|
43
|
+
missingError: "ServerEvalBridge not found. The bridge runs inside the play DM, so a playtest must be running. The bridge installs automatically in the runtime server peer, including for manually-started playtests.",
|
|
29
44
|
};
|
|
30
45
|
}
|
|
31
46
|
return {
|
|
32
47
|
service: ReplicatedStorage,
|
|
33
48
|
bridgeName: BRIDGE_NAMES.clientLocal,
|
|
34
|
-
missingError: "ClientEvalBridge not found. The bridge runs inside the play DM, so a playtest must be running. The bridge installs automatically
|
|
49
|
+
missingError: "ClientEvalBridge not found. The bridge runs inside the play DM, so a playtest must be running. The bridge installs automatically in the runtime client peer, including for manually-started playtests.",
|
|
35
50
|
};
|
|
36
51
|
}
|
|
37
52
|
|
|
@@ -44,9 +59,22 @@ function evalRuntime(requestData: Record<string, unknown>) {
|
|
|
44
59
|
return { bridge: "missing", error: config.error };
|
|
45
60
|
}
|
|
46
61
|
|
|
47
|
-
|
|
48
|
-
if (!bridge
|
|
49
|
-
|
|
62
|
+
let bridge = findBridge(config);
|
|
63
|
+
if (!bridge) {
|
|
64
|
+
const install = ensureRuntimeBridgeInstalled();
|
|
65
|
+
if (!install.installed) {
|
|
66
|
+
return {
|
|
67
|
+
bridge: "missing",
|
|
68
|
+
error: `${config.missingError} Runtime bridge install failed: ${install.error}`,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
bridge = waitForBridge(config);
|
|
72
|
+
}
|
|
73
|
+
if (!bridge) {
|
|
74
|
+
return {
|
|
75
|
+
bridge: "missing",
|
|
76
|
+
error: `${config.missingError} Runtime bridge was installed but did not become ready.`,
|
|
77
|
+
};
|
|
50
78
|
}
|
|
51
79
|
|
|
52
80
|
const m = new Instance("ModuleScript");
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { HttpService, LogService, Players, RunService } from "@rbxts/services";
|
|
2
|
-
import { installBridges, ensureBridgesInstalled } from "../EvalBridges";
|
|
3
2
|
import StopPlayMonitor from "../StopPlayMonitor";
|
|
4
3
|
|
|
5
4
|
interface StudioTestServiceMultiplayer extends StudioTestService {
|
|
@@ -200,9 +199,8 @@ function startPlaytest(requestData: Record<string, unknown>) {
|
|
|
200
199
|
logConnection = undefined;
|
|
201
200
|
}
|
|
202
201
|
cleanupStopListener();
|
|
203
|
-
//
|
|
204
|
-
//
|
|
205
|
-
// EvalBridges.ts lifecycle comment.
|
|
202
|
+
// Runtime eval bridges are created by the play server/client plugin
|
|
203
|
+
// peers and disappear with the play DataModels.
|
|
206
204
|
}
|
|
207
205
|
|
|
208
206
|
if (testRunning) {
|
|
@@ -236,15 +234,6 @@ function startPlaytest(requestData: Record<string, unknown>) {
|
|
|
236
234
|
warn(`[MCP] Failed to inject stop listener: ${injErr}`);
|
|
237
235
|
}
|
|
238
236
|
|
|
239
|
-
// Force-refresh the game-VM eval bridges (ServerEvalBridge + ClientEvalBridge)
|
|
240
|
-
// right before cloning so the play DMs get the current source. They also
|
|
241
|
-
// live permanently in the edit DM (installed on connect) so manually-started
|
|
242
|
-
// playtests get them too; here we just ensure they're fresh.
|
|
243
|
-
const bridgeInstall = installBridges();
|
|
244
|
-
if (!bridgeInstall.installed) {
|
|
245
|
-
warn(`[MCP] Eval bridge install failed: ${bridgeInstall.error}`);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
237
|
task.spawn(() => {
|
|
249
238
|
const [ok, result] = pcall(() => {
|
|
250
239
|
if (mode === "play") {
|
|
@@ -266,22 +255,12 @@ function startPlaytest(requestData: Record<string, unknown>) {
|
|
|
266
255
|
testRunning = false;
|
|
267
256
|
|
|
268
257
|
cleanupStopListener();
|
|
269
|
-
// Eval bridges persist in the edit DM (see EvalBridges.ts) — do not
|
|
270
|
-
// clean up here, so the next manual playtest still gets them.
|
|
271
|
-
ensureBridgesInstalled();
|
|
272
258
|
});
|
|
273
259
|
|
|
274
260
|
const response: Record<string, unknown> = {
|
|
275
261
|
success: true,
|
|
276
262
|
message: `Playtest started in ${mode} mode.`,
|
|
277
263
|
};
|
|
278
|
-
// Only mention eval bridges when they failed — when they're fine, the
|
|
279
|
-
// detail is noise. eval_server_runtime / eval_client_runtime will surface
|
|
280
|
-
// their own clear errors if the caller tries to use them after a failed
|
|
281
|
-
// install.
|
|
282
|
-
if (!bridgeInstall.installed) {
|
|
283
|
-
response.evalBridgesError = bridgeInstall.error;
|
|
284
|
-
}
|
|
285
264
|
|
|
286
265
|
return response;
|
|
287
266
|
}
|
|
@@ -370,11 +349,6 @@ function multiplayerTestStart(requestData: Record<string, unknown>) {
|
|
|
370
349
|
const testArgs = requestData.testArgs !== undefined ? requestData.testArgs : {};
|
|
371
350
|
const testId = HttpService.GenerateGUID(false);
|
|
372
351
|
|
|
373
|
-
const bridgeInstall = installBridges();
|
|
374
|
-
if (!bridgeInstall.installed) {
|
|
375
|
-
warn(`[MCP] Eval bridge install failed: ${bridgeInstall.error}`);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
352
|
multiplayerState = {
|
|
379
353
|
phase: "starting",
|
|
380
354
|
testId,
|
|
@@ -400,8 +374,6 @@ function multiplayerTestStart(requestData: Record<string, unknown>) {
|
|
|
400
374
|
multiplayerState.result = undefined;
|
|
401
375
|
multiplayerState.error = tostring(result);
|
|
402
376
|
}
|
|
403
|
-
|
|
404
|
-
ensureBridgesInstalled();
|
|
405
377
|
});
|
|
406
378
|
|
|
407
379
|
const response: Record<string, unknown> = {
|
|
@@ -412,9 +384,6 @@ function multiplayerTestStart(requestData: Record<string, unknown>) {
|
|
|
412
384
|
numPlayers,
|
|
413
385
|
testArgs,
|
|
414
386
|
};
|
|
415
|
-
if (!bridgeInstall.installed) {
|
|
416
|
-
response.evalBridgesError = bridgeInstall.error;
|
|
417
|
-
}
|
|
418
387
|
return response;
|
|
419
388
|
}
|
|
420
389
|
|
|
@@ -2,6 +2,7 @@ import State from "../modules/State";
|
|
|
2
2
|
import UI from "../modules/UI";
|
|
3
3
|
import Communication from "../modules/Communication";
|
|
4
4
|
import ClientBroker from "../modules/ClientBroker";
|
|
5
|
+
import { cleanupLegacyEditBridges, ensureRuntimeBridgeInstalled } from "../modules/EvalBridges";
|
|
5
6
|
import RuntimeLogBuffer from "../modules/RuntimeLogBuffer";
|
|
6
7
|
import StopPlayMonitor from "../modules/StopPlayMonitor";
|
|
7
8
|
import * as RenderMonitor from "../modules/RenderMonitor";
|
|
@@ -28,10 +29,24 @@ const elements = UI.getElements();
|
|
|
28
29
|
const ICON_DISCONNECTED = "rbxassetid://__BUTTON_ICON_DISCONNECTED__";
|
|
29
30
|
const ICON_CONNECTING = "rbxassetid://__BUTTON_ICON_CONNECTING__";
|
|
30
31
|
const ICON_CONNECTED = "rbxassetid://__BUTTON_ICON_CONNECTED__";
|
|
32
|
+
const TOOLBAR_REGISTRATION_DELAY_SECONDS = 1;
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
let toolbarButtonRegistered = false;
|
|
35
|
+
|
|
36
|
+
function registerToolbarButton() {
|
|
37
|
+
if (toolbarButtonRegistered) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
toolbarButtonRegistered = true;
|
|
41
|
+
|
|
42
|
+
const toolbar = plugin.CreateToolbar("__TOOLBAR_NAME__");
|
|
43
|
+
const button = toolbar.CreateButton("__BUTTON_TITLE__", "__BUTTON_TOOLTIP__", ICON_DISCONNECTED);
|
|
44
|
+
UI.setToolbarButton(button, { disconnected: ICON_DISCONNECTED, connecting: ICON_CONNECTING, connected: ICON_CONNECTED });
|
|
45
|
+
|
|
46
|
+
button.Click.Connect(() => {
|
|
47
|
+
elements.screenGui.Enabled = !elements.screenGui.Enabled;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
35
50
|
|
|
36
51
|
|
|
37
52
|
elements.connectButton.Activated.Connect(() => {
|
|
@@ -44,11 +59,6 @@ elements.connectButton.Activated.Connect(() => {
|
|
|
44
59
|
});
|
|
45
60
|
|
|
46
61
|
|
|
47
|
-
button.Click.Connect(() => {
|
|
48
|
-
elements.screenGui.Enabled = !elements.screenGui.Enabled;
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
|
|
52
62
|
plugin.Unloading.Connect(() => {
|
|
53
63
|
Communication.deactivateAll();
|
|
54
64
|
});
|
|
@@ -56,6 +66,7 @@ plugin.Unloading.Connect(() => {
|
|
|
56
66
|
|
|
57
67
|
UI.updateUIState();
|
|
58
68
|
Communication.checkForUpdates();
|
|
69
|
+
task.delay(TOOLBAR_REGISTRATION_DELAY_SECONDS, registerToolbarButton);
|
|
59
70
|
|
|
60
71
|
// Auto-activate per peer. The boshyxd plugin only registers with MCP when the
|
|
61
72
|
// user clicks Connect in its UI, but that UI is invisible in play DMs - so
|
|
@@ -63,6 +74,14 @@ Communication.checkForUpdates();
|
|
|
63
74
|
// short delay so the UI/State have a chance to initialize first.
|
|
64
75
|
task.delay(2, () => {
|
|
65
76
|
const role = ClientBroker.forkRole();
|
|
77
|
+
if (role === "edit") {
|
|
78
|
+
cleanupLegacyEditBridges();
|
|
79
|
+
} else {
|
|
80
|
+
const result = ensureRuntimeBridgeInstalled();
|
|
81
|
+
if (!result.installed) {
|
|
82
|
+
warn(`[MCPPlugin] Runtime eval bridge install failed: ${result.error}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
66
85
|
if (role === "edit" || role === "server") {
|
|
67
86
|
pcall(() => {
|
|
68
87
|
const idx = State.getActiveTabIndex();
|