@chrrxs/robloxstudio-mcp 3.0.0 → 3.0.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.
Files changed (48) hide show
  1. package/dist/index.js +9401 -9047
  2. package/package.json +2 -2
  3. package/studio-plugin/MCPPlugin.rbxmx +400 -97
  4. package/studio-plugin/.Carbon.rbxm.lock +0 -0
  5. package/studio-plugin/Carbon.rbxm +0 -0
  6. package/studio-plugin/INSTALLATION.md +0 -170
  7. package/studio-plugin/MCPInspectorPlugin.rbxmx +0 -169759
  8. package/studio-plugin/default.project.json +0 -19
  9. package/studio-plugin/dev.project.json +0 -23
  10. package/studio-plugin/include/LibMP.lua +0 -156378
  11. package/studio-plugin/inspector-icon.png +0 -0
  12. package/studio-plugin/package-lock.json +0 -706
  13. package/studio-plugin/package.json +0 -19
  14. package/studio-plugin/plugin.json +0 -10
  15. package/studio-plugin/src/modules/AssetSanitizationPolicy.ts +0 -127
  16. package/studio-plugin/src/modules/ClientBroker.ts +0 -450
  17. package/studio-plugin/src/modules/Communication.ts +0 -601
  18. package/studio-plugin/src/modules/EvalBridges.ts +0 -255
  19. package/studio-plugin/src/modules/HttpDiagnostics.ts +0 -50
  20. package/studio-plugin/src/modules/LuauExec.ts +0 -403
  21. package/studio-plugin/src/modules/Recording.ts +0 -28
  22. package/studio-plugin/src/modules/RenderMonitor.ts +0 -60
  23. package/studio-plugin/src/modules/RuntimeLogBuffer.ts +0 -210
  24. package/studio-plugin/src/modules/ServerUrlSettings.ts +0 -117
  25. package/studio-plugin/src/modules/State.ts +0 -39
  26. package/studio-plugin/src/modules/StopPlayMonitor.ts +0 -267
  27. package/studio-plugin/src/modules/UI.ts +0 -597
  28. package/studio-plugin/src/modules/Utils.ts +0 -527
  29. package/studio-plugin/src/modules/handlers/AssetHandlers.ts +0 -391
  30. package/studio-plugin/src/modules/handlers/BreakpointHandlers.ts +0 -460
  31. package/studio-plugin/src/modules/handlers/CaptureHandlers.ts +0 -170
  32. package/studio-plugin/src/modules/handlers/EvalRuntimeHandlers.ts +0 -149
  33. package/studio-plugin/src/modules/handlers/GenerateModelHandlers.ts +0 -168
  34. package/studio-plugin/src/modules/handlers/InputHandlers.ts +0 -163
  35. package/studio-plugin/src/modules/handlers/LogHandlers.ts +0 -14
  36. package/studio-plugin/src/modules/handlers/MemoryHandlers.ts +0 -44
  37. package/studio-plugin/src/modules/handlers/MetadataHandlers.ts +0 -96
  38. package/studio-plugin/src/modules/handlers/MicroProfilerHandlers.ts +0 -1263
  39. package/studio-plugin/src/modules/handlers/PropertyHandlers.ts +0 -62
  40. package/studio-plugin/src/modules/handlers/QueryHandlers.ts +0 -716
  41. package/studio-plugin/src/modules/handlers/SceneAnalysisHandlers.ts +0 -216
  42. package/studio-plugin/src/modules/handlers/ScriptHandlers.ts +0 -531
  43. package/studio-plugin/src/modules/handlers/ScriptProfilerHandlers.ts +0 -386
  44. package/studio-plugin/src/modules/handlers/SerializationHandlers.ts +0 -172
  45. package/studio-plugin/src/modules/handlers/TestHandlers.ts +0 -350
  46. package/studio-plugin/src/server/index.server.ts +0 -135
  47. package/studio-plugin/src/types/index.d.ts +0 -57
  48. package/studio-plugin/tsconfig.json +0 -20
@@ -1,149 +0,0 @@
1
- import { LogService, ReplicatedStorage, RunService, ServerScriptService } from "@rbxts/services";
2
- import { BRIDGE_NAMES, ensureRuntimeBridgeInstalled } from "../EvalBridges";
3
- import LuauExec from "../LuauExec";
4
-
5
- const PAYLOAD_INSTANCE_NAME = "__MCPEvalPayload";
6
-
7
- interface BridgeInvokeResult {
8
- ok?: boolean;
9
- value?: unknown;
10
- }
11
-
12
- interface WrapperResult {
13
- ok?: boolean;
14
- value?: unknown;
15
- output?: unknown;
16
- }
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
-
33
- function getBridgeConfig() {
34
- if (!RunService.IsRunning()) {
35
- return {
36
- error: "eval_*_runtime requires a running playtest.",
37
- };
38
- }
39
- if (RunService.IsServer()) {
40
- return {
41
- service: ServerScriptService,
42
- bridgeName: BRIDGE_NAMES.serverLocal,
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.",
44
- };
45
- }
46
- return {
47
- service: ReplicatedStorage,
48
- bridgeName: BRIDGE_NAMES.clientLocal,
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.",
50
- };
51
- }
52
-
53
- function evalRuntime(requestData: Record<string, unknown>) {
54
- const code = requestData.code as string;
55
- if (!code || code === "") return { error: "Code is required" };
56
-
57
- const config = getBridgeConfig();
58
- if (config.error !== undefined) {
59
- return { bridge: "missing", error: config.error };
60
- }
61
-
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
- };
78
- }
79
-
80
- const m = new Instance("ModuleScript");
81
- m.Name = PAYLOAD_INSTANCE_NAME;
82
- const userLines = LuauExec.countLines(code);
83
- const wrapped = LuauExec.buildWrapper(code, PAYLOAD_INSTANCE_NAME);
84
-
85
- const [okSet, setErr] = pcall(() => {
86
- (m as unknown as { Source: string }).Source = wrapped;
87
- });
88
- if (!okSet) {
89
- m.Destroy();
90
- return {
91
- bridge: "ok",
92
- ok: false,
93
- error: `ModuleScript Source set failed: ${tostring(setErr)}`,
94
- };
95
- }
96
-
97
- m.Parent = game.GetService("Workspace");
98
- const historyStart = LogService.GetLogHistory().size();
99
- const [invokeOk, invokeResult] = pcall(() => bridge.Invoke(m) as BridgeInvokeResult);
100
- m.Destroy();
101
-
102
- if (!invokeOk) {
103
- return {
104
- bridge: "ok",
105
- ok: false,
106
- error: tostring(invokeResult),
107
- };
108
- }
109
-
110
- if (!typeIs(invokeResult, "table")) {
111
- return {
112
- bridge: "ok",
113
- ok: false,
114
- error: `Eval bridge returned invalid result: ${tostring(invokeResult)}`,
115
- };
116
- }
117
-
118
- const bridgeResult = invokeResult as BridgeInvokeResult;
119
- if (bridgeResult.ok !== true) {
120
- return {
121
- bridge: "ok",
122
- ok: false,
123
- error: LuauExec.recoverPayloadRequireError(bridgeResult.value, userLines, PAYLOAD_INSTANCE_NAME, historyStart),
124
- };
125
- }
126
-
127
- const inner = bridgeResult.value;
128
- if (!typeIs(inner, "table")) {
129
- return {
130
- bridge: "ok",
131
- ok: true,
132
- result: inner === undefined ? undefined : LuauExec.formatReturnValue(inner),
133
- };
134
- }
135
-
136
- const r = inner as WrapperResult;
137
- const ok = r.ok === true;
138
- return {
139
- bridge: "ok",
140
- ok,
141
- result: ok && r.value !== undefined ? LuauExec.formatReturnValue(r.value) : undefined,
142
- error: !ok ? tostring(r.value) : undefined,
143
- output: r.output ?? [],
144
- };
145
- }
146
-
147
- export = {
148
- evalRuntime,
149
- };
@@ -1,168 +0,0 @@
1
- import Utils from "../Utils";
2
- import Recording from "../Recording";
3
-
4
- const GenerationService = game.GetService("GenerationService");
5
- const HttpService = game.GetService("HttpService");
6
- const ServerStorage = game.GetService("ServerStorage");
7
- const Selection = game.GetService("Selection");
8
-
9
- const { getInstancePath } = Utils;
10
- const { beginRecording, finishRecording } = Recording;
11
-
12
- const OUTPUT_FOLDER_NAME = "__MCPGeneratedModels";
13
- const GENERATE_MODEL_MODERATION_RETRIES = 2;
14
-
15
- type AssetImageInput = {
16
- kind: "asset";
17
- asset_id: number;
18
- };
19
-
20
- function fail(message: string) {
21
- return { success: false, error: message };
22
- }
23
-
24
- function contentFromAssetId(assetId: number): Content {
25
- return Content.fromUri(`rbxassetid://${assetId}`);
26
- }
27
-
28
- function imageToContent(image: unknown): Content {
29
- const payload = image as Partial<AssetImageInput>;
30
- if (payload.kind !== "asset" || !typeIs(payload.asset_id, "number") || payload.asset_id <= 0) {
31
- error("generate_model image input must be an asset-backed Roblox image ID", 0);
32
- }
33
- return contentFromAssetId(math.floor(payload.asset_id));
34
- }
35
-
36
- function outputFolder(): Folder | string {
37
- const existing = ServerStorage.FindFirstChild(OUTPUT_FOLDER_NAME);
38
- if (existing !== undefined) {
39
- if (existing.IsA("Folder")) return existing;
40
- return `game.ServerStorage.${OUTPUT_FOLDER_NAME} already exists and is not a Folder`;
41
- }
42
-
43
- const folder = new Instance("Folder");
44
- folder.Name = OUTPUT_FOLDER_NAME;
45
- folder.Parent = ServerStorage;
46
- return folder;
47
- }
48
-
49
- function sanitizeName(value: unknown): string {
50
- const raw = typeIs(value, "string") && value !== "" ? value : "GeneratedModel";
51
- let name = raw.gsub("[%c]", " ")[0];
52
- name = name.gsub("^%s+", "")[0].gsub("%s+$", "")[0];
53
- if (name === "") name = "GeneratedModel";
54
- if (name.size() > 80) name = name.sub(1, 80);
55
- return name;
56
- }
57
-
58
- function uniqueName(parent: Instance, baseName: string): string {
59
- if (parent.FindFirstChild(baseName) === undefined) return baseName;
60
- for (let i = 2; i <= 999; i++) {
61
- const candidate = `${baseName}_${i}`;
62
- if (parent.FindFirstChild(candidate) === undefined) return candidate;
63
- }
64
- return `${baseName}_${HttpService.GenerateGUID(false)}`;
65
- }
66
-
67
- function buildInputs(requestData: Record<string, unknown>): Record<string, unknown> {
68
- const inputs: Record<string, unknown> = {};
69
- const prompt = requestData.prompt;
70
- if (typeIs(prompt, "string") && prompt !== "") {
71
- inputs.TextPrompt = prompt;
72
- }
73
-
74
- const image = requestData.image;
75
- if (image !== undefined) {
76
- inputs.Image = imageToContent(image);
77
- }
78
-
79
- const size = requestData.size as { x?: number; y?: number; z?: number } | undefined;
80
- if (size !== undefined) {
81
- inputs.Size = new Vector3(size.x ?? 1, size.y ?? 1, size.z ?? 1);
82
- }
83
-
84
- const maxTriangles = requestData.max_triangles;
85
- if (typeIs(maxTriangles, "number")) {
86
- inputs.MaxTriangles = math.floor(maxTriangles);
87
- }
88
-
89
- const generateTextures = requestData.generate_textures;
90
- if (typeIs(generateTextures, "boolean")) {
91
- inputs.GenerateTextures = generateTextures;
92
- }
93
-
94
- return inputs;
95
- }
96
-
97
- function buildSchema(requestData: Record<string, unknown>): Record<string, unknown> {
98
- const schemaGroups = requestData.schema_groups as string[] | undefined;
99
- if (schemaGroups !== undefined) {
100
- return { SchemaDefinition: { Groups: schemaGroups } };
101
- }
102
- const schema = typeIs(requestData.schema, "string") && requestData.schema !== ""
103
- ? requestData.schema
104
- : "Body1";
105
- return { PredefinedSchema: schema };
106
- }
107
-
108
- function isModerationFailure(value: unknown): boolean {
109
- return tostring(value).find("Moderation failed", 1, true)[0] !== undefined;
110
- }
111
-
112
- function generateModel(requestData: Record<string, unknown>) {
113
- const [inputOk, inputsOrError] = pcall(() => buildInputs(requestData));
114
- if (!inputOk) return fail(`Failed to prepare model input: ${tostring(inputsOrError)}`);
115
-
116
- const schema = buildSchema(requestData);
117
- const recordingId = beginRecording("Generate model");
118
- let createdModel: Model | undefined;
119
- let generateOk = false;
120
- let generateResult: unknown;
121
-
122
- for (let attempt = 0; attempt <= GENERATE_MODEL_MODERATION_RETRIES; attempt++) {
123
- const [ok, result] = pcall(() => {
124
- return GenerationService.GenerateModelAsync(inputsOrError as Record<string, unknown>, schema, {});
125
- });
126
- generateOk = ok;
127
- generateResult = result;
128
- if (ok || !isModerationFailure(result) || attempt === GENERATE_MODEL_MODERATION_RETRIES) {
129
- break;
130
- }
131
- task.wait(0.25);
132
- }
133
-
134
- if (!generateOk) {
135
- finishRecording(recordingId, false);
136
- if (isModerationFailure(generateResult)) {
137
- return fail("Moderation failed after 3 attempts.");
138
- }
139
- return fail(tostring(generateResult));
140
- }
141
-
142
- if (!typeIs(generateResult, "Instance") || !generateResult.IsA("Model")) {
143
- finishRecording(recordingId, false);
144
- return fail("GenerationService did not return a Model.");
145
- }
146
-
147
- createdModel = generateResult as Model;
148
- const folder = outputFolder();
149
- if (typeIs(folder, "string")) {
150
- createdModel.Destroy();
151
- finishRecording(recordingId, false);
152
- return fail(folder);
153
- }
154
-
155
- createdModel.Name = uniqueName(folder, sanitizeName(requestData.name));
156
- createdModel.Parent = folder;
157
- pcall(() => Selection.Set([createdModel as Instance]));
158
- finishRecording(recordingId, true);
159
-
160
- return {
161
- success: true,
162
- modelPath: getInstancePath(createdModel),
163
- };
164
- }
165
-
166
- export = {
167
- generateModel,
168
- };
@@ -1,163 +0,0 @@
1
- // Virtual input via UserInputService:CreateVirtualInput().
2
- //
3
- // We deliberately do NOT use VirtualInputManager:Send*Event — those methods
4
- // are gated behind RobloxScriptSecurity ("lacking capability RobloxScript")
5
- // in every context a plugin can reach (edit DM, play server/client DMs), so
6
- // they silently never worked. CreateVirtualInput() is callable without that
7
- // capability and drives the REAL input pipeline: SendKey feeds
8
- // UserInputService.InputBegan/Ended and the control modules (so WASD walks the
9
- // character at full WalkSpeed with controls intact, no Humanoid hijack),
10
- // SendMouseButton feeds UIS and activates GUI buttons (and hit-tests against
11
- // CoreGui), and SendTextInput types into the focused TextBox.
12
- //
13
- // Method set on the VirtualInput object (verified live):
14
- // SendKey(isDown: boolean, keyCode: Enum.KeyCode)
15
- // SendMouseButton(position: Vector2, inputType: Enum.UserInputType, isDown: boolean)
16
- // SendTextInput(text: string)
17
- // There is NO SendMouseMove / SendMouseWheel / SendKeyEvent — so "move" and
18
- // "scroll" mouse actions are not supported.
19
- //
20
- // Coordinate space: SendMouseButton coordinates are viewport pixels matching
21
- // what capture_screenshot returns (window space, origin at the top-left of the
22
- // rendered viewport). Pass screenshot pixel coordinates straight through. Note
23
- // that UserInputService reports input positions in GUI space, which is offset
24
- // from this by GuiService:GetGuiInset() (~58px on the Y axis) — irrelevant for
25
- // callers who pick coordinates off a screenshot, which is why we do not
26
- // translate here.
27
-
28
- import * as RenderMonitor from "../RenderMonitor";
29
-
30
- const UserInputService = game.GetService("UserInputService");
31
-
32
- interface VirtualInput {
33
- SendKey(isDown: boolean, keyCode: Enum.KeyCode): void;
34
- SendMouseButton(position: Vector2, inputType: Enum.UserInputType, isDown: boolean): void;
35
- SendTextInput(text: string): void;
36
- }
37
-
38
- // One VirtualInput per plugin VM, reused across calls so that a key held down
39
- // in one call (action="press") and released in a later call (action="release")
40
- // share the same input source.
41
- let cachedVI: VirtualInput | undefined;
42
-
43
- function getVI(): VirtualInput | undefined {
44
- if (cachedVI) return cachedVI;
45
- const [ok, vi] = pcall(() => {
46
- return (UserInputService as unknown as { CreateVirtualInput(): unknown }).CreateVirtualInput();
47
- });
48
- if (ok && vi !== undefined) {
49
- cachedVI = vi as VirtualInput;
50
- return cachedVI;
51
- }
52
- return undefined;
53
- }
54
-
55
- const MOUSE_TYPE_MAP: Record<string, Enum.UserInputType> = {
56
- Left: Enum.UserInputType.MouseButton1,
57
- Right: Enum.UserInputType.MouseButton2,
58
- Middle: Enum.UserInputType.MouseButton3,
59
- };
60
-
61
- function simulateMouseInput(requestData: Record<string, unknown>) {
62
- const action = requestData.action as string;
63
- const x = requestData.x as number | undefined;
64
- const y = requestData.y as number | undefined;
65
- const button = (requestData.button as string) ?? "Left";
66
-
67
- if (!action) return { error: "action is required" };
68
- if (x === undefined || y === undefined) {
69
- return { error: "x and y are required" };
70
- }
71
-
72
- // Input is silently dropped by the engine when the window isn't rendering
73
- // (e.g. minimized). Surface that instead of returning a false success.
74
- const notRendering = RenderMonitor.notRenderingReason();
75
- if (notRendering !== undefined) return { error: notRendering };
76
-
77
- const vi = getVI();
78
- if (!vi) {
79
- return { error: "UserInputService:CreateVirtualInput() is not available in this context" };
80
- }
81
-
82
- const inputType = MOUSE_TYPE_MAP[button] ?? Enum.UserInputType.MouseButton1;
83
- const pos = new Vector2(x, y);
84
-
85
- const [success, err] = pcall(() => {
86
- if (action === "click") {
87
- vi.SendMouseButton(pos, inputType, true);
88
- task.wait(0.05);
89
- vi.SendMouseButton(pos, inputType, false);
90
- } else if (action === "mouseDown") {
91
- vi.SendMouseButton(pos, inputType, true);
92
- } else if (action === "mouseUp") {
93
- vi.SendMouseButton(pos, inputType, false);
94
- } else {
95
- error(
96
- `Unsupported action "${action}". CreateVirtualInput supports click, mouseDown, mouseUp ` +
97
- `(no move/scroll — those methods don't exist on VirtualInput).`,
98
- );
99
- }
100
- });
101
-
102
- if (success) {
103
- return { success: true, action, x, y, button };
104
- }
105
- return { error: `Failed to simulate mouse input: ${err}` };
106
- }
107
-
108
- function simulateKeyboardInput(requestData: Record<string, unknown>) {
109
- const notRendering = RenderMonitor.notRenderingReason();
110
- if (notRendering !== undefined) return { error: notRendering };
111
-
112
- const vi = getVI();
113
- if (!vi) {
114
- return { error: "UserInputService:CreateVirtualInput() is not available in this context" };
115
- }
116
-
117
- // Text mode: type a string into the focused TextBox.
118
- const text = requestData.text as string | undefined;
119
- if (text !== undefined) {
120
- const [ok, err] = pcall(() => vi.SendTextInput(text));
121
- if (ok) return { success: true, text };
122
- return { error: `Failed to send text input: ${err}` };
123
- }
124
-
125
- const keyCodeName = requestData.keyCode as string;
126
- if (!keyCodeName) return { error: "keyCode (or text) is required" };
127
-
128
- const action = (requestData.action as string) ?? "tap";
129
- const duration = (requestData.duration as number) ?? 0.1;
130
-
131
- const [enumOk, keyCode] = pcall(() => {
132
- return (Enum.KeyCode as unknown as Record<string, Enum.KeyCode>)[keyCodeName];
133
- });
134
- if (!enumOk || !keyCode) {
135
- return {
136
- error: `Unknown keyCode: ${keyCodeName}. Use Enum.KeyCode names like "W", "Space", "E", "LeftShift", etc.`,
137
- };
138
- }
139
-
140
- const [success, err] = pcall(() => {
141
- if (action === "press") {
142
- vi.SendKey(true, keyCode);
143
- } else if (action === "release") {
144
- vi.SendKey(false, keyCode);
145
- } else if (action === "tap") {
146
- vi.SendKey(true, keyCode);
147
- task.wait(duration);
148
- vi.SendKey(false, keyCode);
149
- } else {
150
- error(`Unknown action: ${action}`);
151
- }
152
- });
153
-
154
- if (success) {
155
- return { success: true, keyCode: keyCodeName, action };
156
- }
157
- return { error: `Failed to simulate keyboard input: ${err}` };
158
- }
159
-
160
- export = {
161
- simulateMouseInput,
162
- simulateKeyboardInput,
163
- };
@@ -1,14 +0,0 @@
1
- import RuntimeLogBuffer from "../RuntimeLogBuffer";
2
-
3
- function getRuntimeLogs(requestData: Record<string, unknown>): unknown {
4
- const since = requestData.since as number | undefined;
5
- const tail = requestData.tail as number | undefined;
6
- const filter = requestData.filter as string | undefined;
7
- // This is the buffer that captured the LogService event, not necessarily
8
- // the script-origin peer. Ordinary playtests share/reflect logs across
9
- // edit/server/client LogService buffers.
10
- const capturedBy = RuntimeLogBuffer.detectPeer();
11
- return RuntimeLogBuffer.query({ since, tail, filter }, capturedBy);
12
- }
13
-
14
- export = { getRuntimeLogs };
@@ -1,44 +0,0 @@
1
- const Stats = game.GetService("Stats");
2
-
3
- // GetMemoryUsageMbAllCategories is gated by capability "InternalTest" and not
4
- // callable from plugin context. GetMemoryUsageMbForTag is not - so we iterate
5
- // Enum.DeveloperMemoryTag and ask per-tag.
6
- function getMemoryBreakdown(requestData: Record<string, unknown>): unknown {
7
- if (!Stats.MemoryTrackingEnabled) {
8
- return { error: "MemoryTrackingEnabled is false on this peer" };
9
- }
10
-
11
- const requested = requestData.tags as string[] | undefined;
12
- const requestedSet = requested && requested.size() > 0 ? new Set(requested) : undefined;
13
-
14
- const categories: Record<string, number> = {};
15
- for (const item of Enum.DeveloperMemoryTag.GetEnumItems()) {
16
- const name = item.Name;
17
- if (requestedSet && !requestedSet.has(name)) continue;
18
- const [ok, mb] = pcall(() => Stats.GetMemoryUsageMbForTag(item));
19
- categories[name] = ok ? (mb as number) : 0;
20
- }
21
-
22
- const unknownTags: string[] = [];
23
- if (requestedSet) {
24
- const known = new Set<string>();
25
- for (const i of Enum.DeveloperMemoryTag.GetEnumItems()) known.add(i.Name);
26
- for (const t of requestedSet) {
27
- if (!known.has(t)) {
28
- unknownTags.push(t);
29
- categories[t] = 0;
30
- }
31
- }
32
- }
33
-
34
- const result: Record<string, unknown> = {
35
- total_mb: Stats.GetTotalMemoryUsageMb(),
36
- categories,
37
- memory_tracking_enabled: true,
38
- timestamp: DateTime.now().UnixTimestampMillis,
39
- };
40
- if (unknownTags.size() > 0) result.unknown_tags = unknownTags;
41
- return result;
42
- }
43
-
44
- export = { getMemoryBreakdown };
@@ -1,96 +0,0 @@
1
- import Utils from "../Utils";
2
- import LuauExec from "../LuauExec";
3
-
4
- const Selection = game.GetService("Selection");
5
-
6
- const { getInstancePath, getInstanceByPath } = Utils;
7
-
8
- function serializeValue(value: unknown): unknown {
9
- const vType = typeOf(value);
10
- if (vType === "Vector3") {
11
- const v = value as Vector3;
12
- return { X: v.X, Y: v.Y, Z: v.Z, _type: "Vector3" };
13
- } else if (vType === "Color3") {
14
- const v = value as Color3;
15
- return { R: v.R, G: v.G, B: v.B, _type: "Color3" };
16
- } else if (vType === "CFrame") {
17
- const v = value as CFrame;
18
- return { Position: { X: v.Position.X, Y: v.Position.Y, Z: v.Position.Z }, _type: "CFrame" };
19
- } else if (vType === "UDim2") {
20
- const v = value as UDim2;
21
- return {
22
- X: { Scale: v.X.Scale, Offset: v.X.Offset },
23
- Y: { Scale: v.Y.Scale, Offset: v.Y.Offset },
24
- _type: "UDim2",
25
- };
26
- } else if (vType === "BrickColor") {
27
- const v = value as BrickColor;
28
- return { Name: v.Name, _type: "BrickColor" };
29
- }
30
- return value;
31
- }
32
-
33
- function getAttributes(requestData: Record<string, unknown>) {
34
- const instancePath = requestData.instancePath as string;
35
- if (!instancePath) return { error: "Instance path is required" };
36
-
37
- const instance = getInstanceByPath(instancePath);
38
- if (!instance) return { error: `Instance not found: ${instancePath}` };
39
-
40
- const [success, result] = pcall(() => {
41
- const attributes = instance.GetAttributes();
42
- const serializedAttributes: Record<string, { value: unknown; type: string }> = {};
43
- let count = 0;
44
-
45
- for (const [name, value] of pairs(attributes)) {
46
- serializedAttributes[name as string] = {
47
- value: serializeValue(value),
48
- type: typeOf(value),
49
- };
50
- count++;
51
- }
52
-
53
- return { instancePath, attributes: serializedAttributes, count };
54
- });
55
-
56
- if (success) return result;
57
- return { error: `Failed to get attributes: ${result}` };
58
- }
59
-
60
- function getSelection(_requestData: Record<string, unknown>) {
61
- const selection = Selection.Get();
62
-
63
- if (selection.size() === 0) {
64
- return { success: true, selection: [], count: 0, message: "No objects selected" };
65
- }
66
-
67
- const selectedObjects = selection.map((instance: Instance) => ({
68
- name: instance.Name,
69
- className: instance.ClassName,
70
- path: getInstancePath(instance),
71
- parent: instance.Parent ? getInstancePath(instance.Parent) : undefined,
72
- }));
73
-
74
- return {
75
- success: true,
76
- selection: selectedObjects,
77
- count: selection.size(),
78
- message: `${selection.size()} object(s) selected`,
79
- };
80
- }
81
-
82
- function executeLuau(requestData: Record<string, unknown>) {
83
- const code = requestData.code as string;
84
- if (!code || code === "") return { error: "Code is required" };
85
- // All wrapping, print/warn capture, loadstring fallback, JSON-encoding
86
- // of table returns, and parse-error recovery live in LuauExec so the
87
- // edit/server (this handler) and the play-client (ClientBroker) take
88
- // the same code path and produce identical output shapes.
89
- return LuauExec.execute(code);
90
- }
91
-
92
- export = {
93
- getAttributes,
94
- getSelection,
95
- executeLuau,
96
- };