@chrrxs/robloxstudio-mcp 2.23.1 → 3.0.1

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 (50) hide show
  1. package/dist/index.js +1688 -4547
  2. package/package.json +7 -5
  3. package/studio-plugin/MCPPlugin.rbxmx +489 -1965
  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 -171527
  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 -632
  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 -469
  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/BuildHandlers.ts +0 -481
  32. package/studio-plugin/src/modules/handlers/CaptureHandlers.ts +0 -170
  33. package/studio-plugin/src/modules/handlers/EvalRuntimeHandlers.ts +0 -149
  34. package/studio-plugin/src/modules/handlers/GenerateModelHandlers.ts +0 -168
  35. package/studio-plugin/src/modules/handlers/InputHandlers.ts +0 -163
  36. package/studio-plugin/src/modules/handlers/InstanceHandlers.ts +0 -380
  37. package/studio-plugin/src/modules/handlers/LogHandlers.ts +0 -14
  38. package/studio-plugin/src/modules/handlers/MemoryHandlers.ts +0 -44
  39. package/studio-plugin/src/modules/handlers/MetadataHandlers.ts +0 -354
  40. package/studio-plugin/src/modules/handlers/MicroProfilerHandlers.ts +0 -1263
  41. package/studio-plugin/src/modules/handlers/PropertyHandlers.ts +0 -191
  42. package/studio-plugin/src/modules/handlers/QueryHandlers.ts +0 -874
  43. package/studio-plugin/src/modules/handlers/SceneAnalysisHandlers.ts +0 -216
  44. package/studio-plugin/src/modules/handlers/ScriptHandlers.ts +0 -530
  45. package/studio-plugin/src/modules/handlers/ScriptProfilerHandlers.ts +0 -386
  46. package/studio-plugin/src/modules/handlers/SerializationHandlers.ts +0 -172
  47. package/studio-plugin/src/modules/handlers/TestHandlers.ts +0 -350
  48. package/studio-plugin/src/server/index.server.ts +0 -135
  49. package/studio-plugin/src/types/index.d.ts +0 -57
  50. package/studio-plugin/tsconfig.json +0 -20
@@ -271,6 +271,7 @@ local MemoryHandlers = TS.import(script, script.Parent, "handlers", "MemoryHandl
271
271
  local SceneAnalysisHandlers = TS.import(script, script.Parent, "handlers", "SceneAnalysisHandlers")
272
272
  local CaptureHandlers = TS.import(script, script.Parent, "handlers", "CaptureHandlers")
273
273
  local InputHandlers = TS.import(script, script.Parent, "handlers", "InputHandlers")
274
+ local MetadataHandlers = TS.import(script, script.Parent, "handlers", "MetadataHandlers")
274
275
  local EvalRuntimeHandlers = TS.import(script, script.Parent, "handlers", "EvalRuntimeHandlers")
275
276
  local BreakpointHandlers = TS.import(script, script.Parent, "handlers", "BreakpointHandlers")
276
277
  local ScriptProfilerHandlers = TS.import(script, script.Parent, "handlers", "ScriptProfilerHandlers")
@@ -351,6 +352,7 @@ local CLIENT_BROKER_ALLOWED_ENDPOINTS = {
351
352
  ["/api/capture-begin"] = true,
352
353
  ["/api/simulate-mouse-input"] = true,
353
354
  ["/api/simulate-keyboard-input"] = true,
355
+ ["/api/focus-viewport"] = true,
354
356
  }
355
357
  -- Throttle re-ready calls per proxyId so a brief window of unknownInstance
356
358
  -- polls doesn't cause a re-register stampede.
@@ -559,6 +561,9 @@ local function setupClientBroker()
559
561
  if payload and payload.endpoint == "/api/simulate-keyboard-input" then
560
562
  return InputHandlers.simulateKeyboardInput(payload.data or {})
561
563
  end
564
+ if payload and payload.endpoint == "/api/focus-viewport" then
565
+ return MetadataHandlers.focusViewport(payload.data or {})
566
+ end
562
567
  if payload and payload.endpoint == "/api/execute-luau" then
563
568
  return handleExecuteLuau(payload.data)
564
569
  end
@@ -778,11 +783,9 @@ local UI = TS.import(script, script.Parent, "UI")
778
783
  local cleanupLegacyEditBridges = TS.import(script, script.Parent, "EvalBridges").cleanupLegacyEditBridges
779
784
  local QueryHandlers = TS.import(script, script.Parent, "handlers", "QueryHandlers")
780
785
  local PropertyHandlers = TS.import(script, script.Parent, "handlers", "PropertyHandlers")
781
- local InstanceHandlers = TS.import(script, script.Parent, "handlers", "InstanceHandlers")
782
786
  local ScriptHandlers = TS.import(script, script.Parent, "handlers", "ScriptHandlers")
783
787
  local MetadataHandlers = TS.import(script, script.Parent, "handlers", "MetadataHandlers")
784
788
  local TestHandlers = TS.import(script, script.Parent, "handlers", "TestHandlers")
785
- local BuildHandlers = TS.import(script, script.Parent, "handlers", "BuildHandlers")
786
789
  local AssetHandlers = TS.import(script, script.Parent, "handlers", "AssetHandlers")
787
790
  local CaptureHandlers = TS.import(script, script.Parent, "handlers", "CaptureHandlers")
788
791
  local InputHandlers = TS.import(script, script.Parent, "handlers", "InputHandlers")
@@ -873,45 +876,24 @@ local routeMap = {
873
876
  ["/api/file-tree"] = QueryHandlers.getFileTree,
874
877
  ["/api/search-files"] = QueryHandlers.searchFiles,
875
878
  ["/api/place-info"] = QueryHandlers.getPlaceInfo,
876
- ["/api/services"] = QueryHandlers.getServices,
877
879
  ["/api/search-objects"] = QueryHandlers.searchObjects,
878
880
  ["/api/instance-properties"] = QueryHandlers.getInstanceProperties,
879
- ["/api/instance-children"] = QueryHandlers.getInstanceChildren,
880
881
  ["/api/search-by-property"] = QueryHandlers.searchByProperty,
881
882
  ["/api/class-info"] = QueryHandlers.getClassInfo,
882
883
  ["/api/project-structure"] = QueryHandlers.getProjectStructure,
883
884
  ["/api/grep-scripts"] = QueryHandlers.grepScripts,
884
- ["/api/get-descendants"] = QueryHandlers.getDescendants,
885
- ["/api/compare-instances"] = QueryHandlers.compareInstances,
886
- ["/api/set-property"] = PropertyHandlers.setProperty,
887
885
  ["/api/set-properties"] = PropertyHandlers.setProperties,
888
- ["/api/mass-set-property"] = PropertyHandlers.massSetProperty,
889
- ["/api/mass-get-property"] = PropertyHandlers.massGetProperty,
890
- ["/api/create-object"] = InstanceHandlers.createObject,
891
- ["/api/mass-create-objects"] = InstanceHandlers.massCreateObjects,
892
- ["/api/mass-create-objects-with-properties"] = InstanceHandlers.massCreateObjects,
893
- ["/api/delete-object"] = InstanceHandlers.deleteObject,
894
- ["/api/smart-duplicate"] = InstanceHandlers.smartDuplicate,
895
- ["/api/mass-duplicate"] = InstanceHandlers.massDuplicate,
896
- ["/api/clone-object"] = InstanceHandlers.cloneObject,
897
886
  ["/api/get-script-source"] = ScriptHandlers.getScriptSource,
898
887
  ["/api/set-script-source"] = ScriptHandlers.setScriptSource,
899
888
  ["/api/edit-script-lines"] = ScriptHandlers.editScriptLines,
900
889
  ["/api/insert-script-lines"] = ScriptHandlers.insertScriptLines,
901
890
  ["/api/delete-script-lines"] = ScriptHandlers.deleteScriptLines,
902
- ["/api/set-attribute"] = MetadataHandlers.setAttribute,
903
891
  ["/api/get-attributes"] = MetadataHandlers.getAttributes,
904
- ["/api/delete-attribute"] = MetadataHandlers.deleteAttribute,
905
- ["/api/get-tags"] = MetadataHandlers.getTags,
906
- ["/api/add-tag"] = MetadataHandlers.addTag,
907
- ["/api/remove-tag"] = MetadataHandlers.removeTag,
908
- ["/api/get-tagged"] = MetadataHandlers.getTagged,
909
892
  ["/api/get-selection"] = MetadataHandlers.getSelection,
893
+ ["/api/set-selection"] = MetadataHandlers.setSelection,
894
+ ["/api/focus-viewport"] = MetadataHandlers.focusViewport,
910
895
  ["/api/execute-luau"] = MetadataHandlers.executeLuau,
911
896
  ["/api/eval-runtime"] = EvalRuntimeHandlers.evalRuntime,
912
- ["/api/undo"] = MetadataHandlers.undo,
913
- ["/api/redo"] = MetadataHandlers.redo,
914
- ["/api/bulk-set-attributes"] = MetadataHandlers.bulkSetAttributes,
915
897
  ["/api/start-playtest"] = TestHandlers.startPlaytest,
916
898
  ["/api/stop-playtest"] = TestHandlers.stopPlaytest,
917
899
  ["/api/multiplayer-test-start"] = TestHandlers.multiplayerTestStart,
@@ -919,10 +901,6 @@ local routeMap = {
919
901
  ["/api/multiplayer-test-add-players"] = TestHandlers.multiplayerTestAddPlayers,
920
902
  ["/api/multiplayer-test-leave-client"] = TestHandlers.multiplayerTestLeaveClient,
921
903
  ["/api/multiplayer-test-end"] = TestHandlers.multiplayerTestEnd,
922
- ["/api/export-build"] = BuildHandlers.exportBuild,
923
- ["/api/import-build"] = BuildHandlers.importBuild,
924
- ["/api/import-scene"] = BuildHandlers.importScene,
925
- ["/api/search-materials"] = BuildHandlers.searchMaterials,
926
904
  ["/api/insert-asset"] = AssetHandlers.insertAsset,
927
905
  ["/api/preview-asset"] = AssetHandlers.previewAsset,
928
906
  ["/api/capture-screenshot"] = CaptureHandlers.captureScreenshot,
@@ -1550,9 +1528,9 @@ local function computeBridgeStamp()
1550
1528
  for i = 1, #combined do
1551
1529
  h = (h * 33 + (string.byte(combined, i))) % 2147483647
1552
1530
  end
1553
- -- "2.23.1" is replaced with the package version at package time
1531
+ -- "3.0.1" is replaced with the package version at package time
1554
1532
  -- (scripts/build-plugin.mjs injectVersion), so a release bump also restamps.
1555
- return `{tostring(h)}-2.23.1`
1533
+ return `{tostring(h)}-3.0.1`
1556
1534
  end
1557
1535
  local BRIDGE_STAMP = computeBridgeStamp()
1558
1536
  local function setSource(scriptInst, source)
@@ -2669,571 +2647,6 @@ return {
2669
2647
  </Properties>
2670
2648
  </Item>
2671
2649
  <Item class="ModuleScript" referent="9">
2672
- <Properties>
2673
- <string name="Name">BuildHandlers</string>
2674
- <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
2675
- local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
2676
- local Utils = TS.import(script, script.Parent.Parent, "Utils")
2677
- local Recording = TS.import(script, script.Parent.Parent, "Recording")
2678
- local MaterialService = game:GetService("MaterialService")
2679
- local _binding = Utils
2680
- local getInstancePath = _binding.getInstancePath
2681
- local getInstanceByPath = _binding.getInstanceByPath
2682
- local _binding_1 = Recording
2683
- local beginRecording = _binding_1.beginRecording
2684
- local finishRecording = _binding_1.finishRecording
2685
- local MATERIAL_BY_NAME = {}
2686
- for _, enumItem in Enum.Material:GetEnumItems() do
2687
- local _name = enumItem.Name
2688
- MATERIAL_BY_NAME[_name] = enumItem
2689
- end
2690
- -- Shape class mapping
2691
- local SHAPE_CLASSES = {
2692
- Block = "Part",
2693
- Wedge = "WedgePart",
2694
- Cylinder = "Part",
2695
- Ball = "Part",
2696
- CornerWedge = "CornerWedgePart",
2697
- }
2698
- local PALETTE_KEYS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
2699
- local function roundTo(n, decimals)
2700
- local mult = 10 ^ decimals
2701
- return math.round(n * mult) / mult
2702
- end
2703
- local function encodePaletteKey(index)
2704
- local base = #PALETTE_KEYS
2705
- local value = math.floor(index) + 1
2706
- local encoded = ""
2707
- while value > 0 do
2708
- value -= 1
2709
- local digit = value % base
2710
- local _arg0 = digit + 1
2711
- local _arg1 = digit + 1
2712
- encoded = string.sub(PALETTE_KEYS, _arg0, _arg1) .. encoded
2713
- value = math.floor(value / base)
2714
- end
2715
- return encoded
2716
- end
2717
- local function getVariantName(part)
2718
- local variantName = part.MaterialVariant
2719
- if variantName == "" then
2720
- local ok, variantAttr = pcall(function()
2721
- return part:GetAttribute("MaterialVariant")
2722
- end)
2723
- if ok and type(variantAttr) == "string" then
2724
- variantName = variantAttr
2725
- end
2726
- end
2727
- return variantName
2728
- end
2729
- local function exportBuild(requestData)
2730
- local instancePath = requestData.instancePath
2731
- local outputId = requestData.outputId
2732
- local _condition = (requestData.style)
2733
- if _condition == nil then
2734
- _condition = "misc"
2735
- end
2736
- local style = _condition
2737
- if not (instancePath ~= "" and instancePath) then
2738
- return {
2739
- error = "Instance path is required",
2740
- }
2741
- end
2742
- local instance = getInstanceByPath(instancePath)
2743
- if not instance then
2744
- return {
2745
- error = `Instance not found: {instancePath}`,
2746
- }
2747
- end
2748
- if not instance:IsA("Model") and not instance:IsA("Folder") then
2749
- return {
2750
- error = "Instance must be a Model or Folder",
2751
- }
2752
- end
2753
- local success, result = pcall(function()
2754
- local descendants = instance:GetDescendants()
2755
- local baseParts = {}
2756
- for _, desc in descendants do
2757
- if desc:IsA("BasePart") then
2758
- table.insert(baseParts, desc)
2759
- end
2760
- end
2761
- if #baseParts == 0 then
2762
- return {
2763
- error = "No BaseParts found in instance",
2764
- }
2765
- end
2766
- -- Compute bounding box center
2767
- local minX = math.huge
2768
- local minY = math.huge
2769
- local minZ = math.huge
2770
- local maxX = -math.huge
2771
- local maxY = -math.huge
2772
- local maxZ = -math.huge
2773
- for _, part in baseParts do
2774
- local pos = part.Position
2775
- local sz = part.Size
2776
- local halfX = sz.X / 2
2777
- local halfY = sz.Y / 2
2778
- local halfZ = sz.Z / 2
2779
- minX = math.min(minX, pos.X - halfX)
2780
- minY = math.min(minY, pos.Y - halfY)
2781
- minZ = math.min(minZ, pos.Z - halfZ)
2782
- maxX = math.max(maxX, pos.X + halfX)
2783
- maxY = math.max(maxY, pos.Y + halfY)
2784
- maxZ = math.max(maxZ, pos.Z + halfZ)
2785
- end
2786
- local centerX = (minX + maxX) / 2
2787
- local centerY = minY
2788
- local centerZ = (minZ + maxZ) / 2
2789
- local boundsX = roundTo(maxX - minX, 1)
2790
- local boundsY = roundTo(maxY - minY, 1)
2791
- local boundsZ = roundTo(maxZ - minZ, 1)
2792
- -- Build palette from unique (BrickColor, Material, MaterialVariant?) combos
2793
- local paletteMap = {}
2794
- local palette = {}
2795
- local keyIndex = 0
2796
- for _, part in baseParts do
2797
- local colorName = part.BrickColor.Name
2798
- local materialName = part.Material.Name
2799
- local variantName = getVariantName(part)
2800
- local combo = if variantName ~= "" then `{colorName}|{materialName}|{variantName}` else `{colorName}|{materialName}`
2801
- if not (paletteMap[combo] ~= nil) then
2802
- local key = encodePaletteKey(keyIndex)
2803
- keyIndex += 1
2804
- paletteMap[combo] = key
2805
- if variantName ~= "" then
2806
- palette[key] = { colorName, materialName, variantName }
2807
- else
2808
- palette[key] = { colorName, materialName }
2809
- end
2810
- end
2811
- end
2812
- -- Build compact part arrays
2813
- local parts = {}
2814
- for _, part in baseParts do
2815
- local pos = part.Position
2816
- local orient = part.Orientation
2817
- local sz = part.Size
2818
- local colorName = part.BrickColor.Name
2819
- local materialName = part.Material.Name
2820
- local variantName = getVariantName(part)
2821
- local combo = if variantName ~= "" then `{colorName}|{materialName}|{variantName}` else `{colorName}|{materialName}`
2822
- local _condition_1 = paletteMap[combo]
2823
- if _condition_1 == nil then
2824
- _condition_1 = "a"
2825
- end
2826
- local paletteKey = _condition_1
2827
- -- Relative position to center
2828
- local relX = roundTo(pos.X - centerX, 1)
2829
- local relY = roundTo(pos.Y - centerY, 1)
2830
- local relZ = roundTo(pos.Z - centerZ, 1)
2831
- local sizeX = roundTo(sz.X, 1)
2832
- local sizeY = roundTo(sz.Y, 1)
2833
- local sizeZ = roundTo(sz.Z, 1)
2834
- local rotX = roundTo(orient.X, 1)
2835
- local rotY = roundTo(orient.Y, 1)
2836
- local rotZ = roundTo(orient.Z, 1)
2837
- -- Determine shape
2838
- local shape = "Block"
2839
- if part:IsA("WedgePart") then
2840
- shape = "Wedge"
2841
- elseif part:IsA("CornerWedgePart") then
2842
- shape = "CornerWedge"
2843
- elseif part:IsA("Part") then
2844
- local p = part
2845
- if p.Shape == Enum.PartType.Cylinder then
2846
- shape = "Cylinder"
2847
- elseif p.Shape == Enum.PartType.Ball then
2848
- shape = "Ball"
2849
- end
2850
- end
2851
- -- Build part array with optional trailing fields
2852
- local hasTransparency = part.Transparency > 0
2853
- local hasShape = shape ~= "Block"
2854
- local partArr
2855
- if hasTransparency then
2856
- partArr = { relX, relY, relZ, sizeX, sizeY, sizeZ, rotX, rotY, rotZ, paletteKey, if hasShape then shape else "Block", roundTo(part.Transparency, 2) }
2857
- elseif hasShape then
2858
- partArr = { relX, relY, relZ, sizeX, sizeY, sizeZ, rotX, rotY, rotZ, paletteKey, shape }
2859
- else
2860
- partArr = { relX, relY, relZ, sizeX, sizeY, sizeZ, rotX, rotY, rotZ, paletteKey }
2861
- end
2862
- local _partArr = partArr
2863
- table.insert(parts, _partArr)
2864
- end
2865
- local _condition_1 = outputId
2866
- if _condition_1 == nil then
2867
- _condition_1 = `{style}/{(string.gsub(string.lower(instance.Name), " ", "_"))}`
2868
- end
2869
- local buildId = _condition_1
2870
- return {
2871
- success = true,
2872
- buildData = {
2873
- id = buildId,
2874
- style = style,
2875
- bounds = { boundsX, boundsY, boundsZ },
2876
- palette = palette,
2877
- parts = parts,
2878
- },
2879
- }
2880
- end)
2881
- if success and result then
2882
- return result
2883
- else
2884
- return {
2885
- error = `Failed to export build: {result}`,
2886
- }
2887
- end
2888
- end
2889
- local function importBuild(requestData)
2890
- local buildData = requestData.buildData
2891
- local targetPath = requestData.targetPath
2892
- local positionOffset = (requestData.position) or { 0, 0, 0 }
2893
- if not buildData or not (targetPath ~= "" and targetPath) then
2894
- return {
2895
- error = "buildData and targetPath are required",
2896
- }
2897
- end
2898
- local parentInstance = getInstanceByPath(targetPath)
2899
- if not parentInstance then
2900
- return {
2901
- error = `Target not found: {targetPath}`,
2902
- }
2903
- end
2904
- local recordingId = beginRecording("Import build")
2905
- local success, result = pcall(function()
2906
- local palette = buildData.palette
2907
- local parts = buildData.parts
2908
- local _condition = (buildData.id)
2909
- if _condition == nil then
2910
- _condition = "imported_build"
2911
- end
2912
- local buildId = _condition
2913
- -- Create model container
2914
- local model = Instance.new("Model")
2915
- local _condition_1 = (string.match(buildId, "[^/]+$"))
2916
- if _condition_1 == nil then
2917
- _condition_1 = buildId
2918
- end
2919
- model.Name = _condition_1
2920
- local partCount = 0
2921
- for _, partArr in parts do
2922
- local _exp = (partArr[1])
2923
- local _condition_2 = positionOffset[1]
2924
- if _condition_2 == nil then
2925
- _condition_2 = 0
2926
- end
2927
- local posX = _exp + _condition_2
2928
- local _exp_1 = (partArr[2])
2929
- local _condition_3 = positionOffset[2]
2930
- if _condition_3 == nil then
2931
- _condition_3 = 0
2932
- end
2933
- local posY = _exp_1 + _condition_3
2934
- local _exp_2 = (partArr[3])
2935
- local _condition_4 = positionOffset[3]
2936
- if _condition_4 == nil then
2937
- _condition_4 = 0
2938
- end
2939
- local posZ = _exp_2 + _condition_4
2940
- local sizeX = partArr[4]
2941
- local sizeY = partArr[5]
2942
- local sizeZ = partArr[6]
2943
- local rotX = partArr[7]
2944
- local rotY = partArr[8]
2945
- local rotZ = partArr[9]
2946
- local paletteKey = partArr[10]
2947
- local _condition_5 = (partArr[11])
2948
- if _condition_5 == nil then
2949
- _condition_5 = "Block"
2950
- end
2951
- local shape = _condition_5
2952
- local _condition_6 = (partArr[12])
2953
- if _condition_6 == nil then
2954
- _condition_6 = 0
2955
- end
2956
- local transparency = _condition_6
2957
- -- Determine class from shape
2958
- local _condition_7 = SHAPE_CLASSES[shape]
2959
- if _condition_7 == nil then
2960
- _condition_7 = "Part"
2961
- end
2962
- local className = _condition_7
2963
- local part = Instance.new(className)
2964
- -- Set shape for Part instances with non-Block shapes
2965
- if className == "Part" and shape ~= "Block" then
2966
- if shape == "Cylinder" then
2967
- part.Shape = Enum.PartType.Cylinder
2968
- elseif shape == "Ball" then
2969
- part.Shape = Enum.PartType.Ball
2970
- end
2971
- end
2972
- part.Size = Vector3.new(sizeX, sizeY, sizeZ)
2973
- part.Position = Vector3.new(posX, posY, posZ)
2974
- part.Orientation = Vector3.new(rotX, rotY, rotZ)
2975
- part.Anchored = true
2976
- if transparency > 0 then
2977
- part.Transparency = transparency
2978
- end
2979
- -- Apply palette
2980
- local paletteEntry = palette[paletteKey]
2981
- if paletteEntry then
2982
- local _binding_2 = paletteEntry
2983
- local colorName = _binding_2[1]
2984
- local materialName = _binding_2[2]
2985
- local variantName = _binding_2[3]
2986
- pcall(function()
2987
- part.BrickColor = BrickColor.new(colorName)
2988
- end)
2989
- pcall(function()
2990
- local mat = MATERIAL_BY_NAME[materialName]
2991
- if mat ~= nil then
2992
- part.Material = mat
2993
- end
2994
- end)
2995
- -- Apply MaterialVariant if specified
2996
- if variantName ~= nil and variantName ~= "" then
2997
- pcall(function()
2998
- part.MaterialVariant = variantName
2999
- end)
3000
- end
3001
- end
3002
- part.Parent = model
3003
- partCount += 1
3004
- end
3005
- model.Parent = parentInstance
3006
- return {
3007
- success = true,
3008
- partCount = partCount,
3009
- modelPath = getInstancePath(model),
3010
- }
3011
- end)
3012
- if success and result then
3013
- finishRecording(recordingId, true)
3014
- return result
3015
- else
3016
- finishRecording(recordingId, false)
3017
- return {
3018
- error = `Failed to import build: {result}`,
3019
- }
3020
- end
3021
- end
3022
- local function importScene(requestData)
3023
- local expandedBuilds = requestData.expandedBuilds
3024
- local _condition = (requestData.targetPath)
3025
- if _condition == nil then
3026
- _condition = "game.Workspace"
3027
- end
3028
- local targetPath = _condition
3029
- if not expandedBuilds or not (type(expandedBuilds) == "table") or #expandedBuilds == 0 then
3030
- return {
3031
- error = "expandedBuilds array is required",
3032
- }
3033
- end
3034
- local parentInstance = getInstanceByPath(targetPath)
3035
- if not parentInstance then
3036
- return {
3037
- error = `Target not found: {targetPath}`,
3038
- }
3039
- end
3040
- local recordingId = beginRecording("Import scene")
3041
- local success, result = pcall(function()
3042
- local modelCount = 0
3043
- local totalParts = 0
3044
- local models = {}
3045
- for _, entry in expandedBuilds do
3046
- local buildData = entry.buildData
3047
- local position = (entry.position) or { 0, 0, 0 }
3048
- local rotation = (entry.rotation) or { 0, 0, 0 }
3049
- local _condition_1 = (entry.name)
3050
- if _condition_1 == nil then
3051
- _condition_1 = "SceneModel"
3052
- end
3053
- local name = _condition_1
3054
- local palette = buildData.palette
3055
- local parts = buildData.parts
3056
- local model = Instance.new("Model")
3057
- model.Name = name
3058
- local _condition_2 = rotation[1]
3059
- if _condition_2 == nil then
3060
- _condition_2 = 0
3061
- end
3062
- local _exp = math.rad(_condition_2)
3063
- local _condition_3 = rotation[2]
3064
- if _condition_3 == nil then
3065
- _condition_3 = 0
3066
- end
3067
- local _exp_1 = math.rad(_condition_3)
3068
- local _condition_4 = rotation[3]
3069
- if _condition_4 == nil then
3070
- _condition_4 = 0
3071
- end
3072
- local rotCF = CFrame.Angles(_exp, _exp_1, math.rad(_condition_4))
3073
- local _condition_5 = position[1]
3074
- if _condition_5 == nil then
3075
- _condition_5 = 0
3076
- end
3077
- local _condition_6 = position[2]
3078
- if _condition_6 == nil then
3079
- _condition_6 = 0
3080
- end
3081
- local _condition_7 = position[3]
3082
- if _condition_7 == nil then
3083
- _condition_7 = 0
3084
- end
3085
- local originCF = CFrame.new(_condition_5, _condition_6, _condition_7) * rotCF
3086
- local partCount = 0
3087
- for _1, partArr in parts do
3088
- local localX = partArr[1]
3089
- local localY = partArr[2]
3090
- local localZ = partArr[3]
3091
- local sizeX = partArr[4]
3092
- local sizeY = partArr[5]
3093
- local sizeZ = partArr[6]
3094
- local rotX = partArr[7]
3095
- local rotY = partArr[8]
3096
- local rotZ = partArr[9]
3097
- local paletteKey = partArr[10]
3098
- local _condition_8 = (partArr[11])
3099
- if _condition_8 == nil then
3100
- _condition_8 = "Block"
3101
- end
3102
- local shape = _condition_8
3103
- local _condition_9 = (partArr[12])
3104
- if _condition_9 == nil then
3105
- _condition_9 = 0
3106
- end
3107
- local transparency = _condition_9
3108
- local _condition_10 = SHAPE_CLASSES[shape]
3109
- if _condition_10 == nil then
3110
- _condition_10 = "Part"
3111
- end
3112
- local className = _condition_10
3113
- local part = Instance.new(className)
3114
- if className == "Part" and shape ~= "Block" then
3115
- if shape == "Cylinder" then
3116
- part.Shape = Enum.PartType.Cylinder
3117
- elseif shape == "Ball" then
3118
- part.Shape = Enum.PartType.Ball
3119
- end
3120
- end
3121
- part.Size = Vector3.new(sizeX, sizeY, sizeZ)
3122
- -- Apply local rotation then world transform
3123
- local localRotCF = CFrame.Angles(math.rad(rotX), math.rad(rotY), math.rad(rotZ))
3124
- local localPosCF = CFrame.new(localX, localY, localZ) * localRotCF
3125
- local worldCF = originCF * localPosCF
3126
- part.CFrame = worldCF
3127
- part.Anchored = true
3128
- if transparency > 0 then
3129
- part.Transparency = transparency
3130
- end
3131
- local paletteEntry = palette[paletteKey]
3132
- if paletteEntry then
3133
- local _binding_2 = paletteEntry
3134
- local colorName = _binding_2[1]
3135
- local materialName = _binding_2[2]
3136
- local variantName = _binding_2[3]
3137
- pcall(function()
3138
- part.BrickColor = BrickColor.new(colorName)
3139
- end)
3140
- pcall(function()
3141
- local mat = MATERIAL_BY_NAME[materialName]
3142
- if mat ~= nil then
3143
- part.Material = mat
3144
- end
3145
- end)
3146
- if variantName ~= nil and variantName ~= "" then
3147
- pcall(function()
3148
- part.MaterialVariant = variantName
3149
- end)
3150
- end
3151
- end
3152
- part.Parent = model
3153
- partCount += 1
3154
- end
3155
- model.Parent = parentInstance
3156
- modelCount += 1
3157
- totalParts += partCount
3158
- local _arg0 = {
3159
- name = name,
3160
- partCount = partCount,
3161
- modelPath = getInstancePath(model),
3162
- }
3163
- table.insert(models, _arg0)
3164
- end
3165
- return {
3166
- success = true,
3167
- modelCount = modelCount,
3168
- totalParts = totalParts,
3169
- models = models,
3170
- }
3171
- end)
3172
- if success and result then
3173
- finishRecording(recordingId, true)
3174
- return result
3175
- else
3176
- finishRecording(recordingId, false)
3177
- return {
3178
- error = `Failed to import scene: {result}`,
3179
- }
3180
- end
3181
- end
3182
- local function searchMaterials(requestData)
3183
- local _condition = (requestData.query)
3184
- if _condition == nil then
3185
- _condition = ""
3186
- end
3187
- local query = string.lower(_condition)
3188
- local _condition_1 = (requestData.maxResults)
3189
- if _condition_1 == nil then
3190
- _condition_1 = 50
3191
- end
3192
- local maxResults = _condition_1
3193
- local success, result = pcall(function()
3194
- local children = MaterialService:GetChildren()
3195
- local materials = {}
3196
- for _, child in children do
3197
- if not child:IsA("MaterialVariant") then
3198
- continue
3199
- end
3200
- local nameMatch = query == "" or (string.find(string.lower(child.Name), query)) ~= nil
3201
- if not nameMatch then
3202
- continue
3203
- end
3204
- local _arg0 = {
3205
- name = child.Name,
3206
- baseMaterial = child.BaseMaterial.Name,
3207
- }
3208
- table.insert(materials, _arg0)
3209
- if #materials >= maxResults then
3210
- break
3211
- end
3212
- end
3213
- return {
3214
- success = true,
3215
- materials = materials,
3216
- total = #materials,
3217
- }
3218
- end)
3219
- if success and result then
3220
- return result
3221
- else
3222
- return {
3223
- error = `Failed to search materials: {result}`,
3224
- }
3225
- end
3226
- end
3227
- return {
3228
- exportBuild = exportBuild,
3229
- importBuild = importBuild,
3230
- importScene = importScene,
3231
- searchMaterials = searchMaterials,
3232
- }
3233
- ]]></string>
3234
- </Properties>
3235
- </Item>
3236
- <Item class="ModuleScript" referent="10">
3237
2650
  <Properties>
3238
2651
  <string name="Name">CaptureHandlers</string>
3239
2652
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -3448,7 +2861,7 @@ return {
3448
2861
  ]]></string>
3449
2862
  </Properties>
3450
2863
  </Item>
3451
- <Item class="ModuleScript" referent="11">
2864
+ <Item class="ModuleScript" referent="10">
3452
2865
  <Properties>
3453
2866
  <string name="Name">EvalRuntimeHandlers</string>
3454
2867
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -3602,7 +3015,7 @@ return {
3602
3015
  ]]></string>
3603
3016
  </Properties>
3604
3017
  </Item>
3605
- <Item class="ModuleScript" referent="12">
3018
+ <Item class="ModuleScript" referent="11">
3606
3019
  <Properties>
3607
3020
  <string name="Name">GenerateModelHandlers</string>
3608
3021
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -3807,7 +3220,7 @@ return {
3807
3220
  ]]></string>
3808
3221
  </Properties>
3809
3222
  </Item>
3810
- <Item class="ModuleScript" referent="13">
3223
+ <Item class="ModuleScript" referent="12">
3811
3224
  <Properties>
3812
3225
  <string name="Name">InputHandlers</string>
3813
3226
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -4007,514 +3420,7 @@ return {
4007
3420
  ]]></string>
4008
3421
  </Properties>
4009
3422
  </Item>
4010
- <Item class="ModuleScript" referent="14">
4011
- <Properties>
4012
- <string name="Name">InstanceHandlers</string>
4013
- <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
4014
- local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
4015
- local Utils = TS.import(script, script.Parent.Parent, "Utils")
4016
- local Recording = TS.import(script, script.Parent.Parent, "Recording")
4017
- local _binding = Utils
4018
- local getInstancePath = _binding.getInstancePath
4019
- local getInstanceByPath = _binding.getInstanceByPath
4020
- local convertPropertyValue = _binding.convertPropertyValue
4021
- local _binding_1 = Recording
4022
- local beginRecording = _binding_1.beginRecording
4023
- local finishRecording = _binding_1.finishRecording
4024
- local function processObjectEntries(objects, createFn)
4025
- local results = {}
4026
- local successCount = 0
4027
- local failureCount = 0
4028
- local loopSuccess, loopError = pcall(function()
4029
- for _, entry in objects do
4030
- if not (type(entry) == "table") then
4031
- failureCount += 1
4032
- table.insert(results, {
4033
- success = false,
4034
- error = "Each object entry must be a table",
4035
- })
4036
- continue
4037
- end
4038
- local objData = entry
4039
- local className = objData.className
4040
- local parentPath = objData.parent
4041
- if not (className ~= "" and className) or not (parentPath ~= "" and parentPath) then
4042
- failureCount += 1
4043
- table.insert(results, {
4044
- success = false,
4045
- error = "Class name and parent are required",
4046
- })
4047
- continue
4048
- end
4049
- local entrySuccess, entryResult = pcall(function()
4050
- return createFn(objData)
4051
- end)
4052
- if not entrySuccess then
4053
- failureCount += 1
4054
- local _arg0 = {
4055
- success = false,
4056
- className = className,
4057
- parent = parentPath,
4058
- error = tostring(entryResult),
4059
- }
4060
- table.insert(results, _arg0)
4061
- continue
4062
- end
4063
- if entryResult.instance ~= nil then
4064
- successCount += 1
4065
- local _arg0 = {
4066
- success = true,
4067
- className = entryResult.className,
4068
- parent = entryResult.parentPath,
4069
- instancePath = getInstancePath(entryResult.instance),
4070
- name = entryResult.instance.Name,
4071
- }
4072
- table.insert(results, _arg0)
4073
- else
4074
- failureCount += 1
4075
- local _object = {
4076
- success = false,
4077
- }
4078
- local _left = "className"
4079
- local _condition = entryResult.className
4080
- if _condition == nil then
4081
- _condition = className
4082
- end
4083
- _object[_left] = _condition
4084
- local _left_1 = "parent"
4085
- local _condition_1 = entryResult.parentPath
4086
- if _condition_1 == nil then
4087
- _condition_1 = parentPath
4088
- end
4089
- _object[_left_1] = _condition_1
4090
- _object.error = entryResult.error
4091
- table.insert(results, _object)
4092
- end
4093
- end
4094
- end)
4095
- if not loopSuccess then
4096
- failureCount += 1
4097
- local _arg0 = {
4098
- success = false,
4099
- error = `Unexpected mass create failure: {tostring(loopError)}`,
4100
- }
4101
- table.insert(results, _arg0)
4102
- end
4103
- return {
4104
- results = results,
4105
- successCount = successCount,
4106
- failureCount = failureCount,
4107
- }
4108
- end
4109
- local function createObject(requestData)
4110
- local className = requestData.className
4111
- local parentPath = requestData.parent
4112
- local name = requestData.name
4113
- local properties = (requestData.properties) or {}
4114
- if not (className ~= "" and className) or not (parentPath ~= "" and parentPath) then
4115
- return {
4116
- error = "Class name and parent are required",
4117
- }
4118
- end
4119
- local parentInstance = getInstanceByPath(parentPath)
4120
- if not parentInstance then
4121
- return {
4122
- error = `Parent instance not found: {parentPath}`,
4123
- }
4124
- end
4125
- local recordingId = beginRecording(`Create {className}`)
4126
- local success, newInstance = pcall(function()
4127
- local instance = Instance.new(className)
4128
- if name ~= "" and name then
4129
- instance.Name = name
4130
- end
4131
- for propertyName, propertyValue in pairs(properties) do
4132
- pcall(function()
4133
- local converted = convertPropertyValue(instance, propertyName, propertyValue)
4134
- instance[propertyName] = if converted ~= nil then converted else propertyValue
4135
- end)
4136
- end
4137
- instance.Parent = parentInstance
4138
- return instance
4139
- end)
4140
- if success and newInstance then
4141
- finishRecording(recordingId, true)
4142
- return {
4143
- success = true,
4144
- className = className,
4145
- parent = parentPath,
4146
- instancePath = getInstancePath(newInstance),
4147
- name = newInstance.Name,
4148
- message = "Object created successfully",
4149
- }
4150
- else
4151
- finishRecording(recordingId, false)
4152
- return {
4153
- error = `Failed to create object: {newInstance}`,
4154
- className = className,
4155
- parent = parentPath,
4156
- }
4157
- end
4158
- end
4159
- local function deleteObject(requestData)
4160
- local instancePath = requestData.instancePath
4161
- if not (instancePath ~= "" and instancePath) then
4162
- return {
4163
- error = "Instance path is required",
4164
- }
4165
- end
4166
- local instance = getInstanceByPath(instancePath)
4167
- if not instance then
4168
- return {
4169
- error = `Instance not found: {instancePath}`,
4170
- }
4171
- end
4172
- if instance == game then
4173
- return {
4174
- error = "Cannot delete the game instance",
4175
- }
4176
- end
4177
- local recordingId = beginRecording(`Delete {instance.ClassName} ({instance.Name})`)
4178
- local success, result = pcall(function()
4179
- instance:Destroy()
4180
- return true
4181
- end)
4182
- if success then
4183
- finishRecording(recordingId, true)
4184
- return {
4185
- success = true,
4186
- instancePath = instancePath,
4187
- message = "Object deleted successfully",
4188
- }
4189
- else
4190
- finishRecording(recordingId, false)
4191
- return {
4192
- error = `Failed to delete object: {result}`,
4193
- instancePath = instancePath,
4194
- }
4195
- end
4196
- end
4197
- local function massCreateObjects(requestData)
4198
- local objects = requestData.objects
4199
- if not objects or not (type(objects) == "table") or #objects == 0 then
4200
- return {
4201
- error = "Objects array is required",
4202
- }
4203
- end
4204
- local recordingId = beginRecording("Mass create objects")
4205
- local _binding_2 = processObjectEntries(objects, function(objData)
4206
- local className = objData.className
4207
- local parentPath = objData.parent
4208
- local name = objData.name
4209
- local properties = (objData.properties) or {}
4210
- local parentInstance = getInstanceByPath(parentPath)
4211
- if not parentInstance then
4212
- return {
4213
- error = "Parent instance not found",
4214
- className = className,
4215
- parentPath = parentPath,
4216
- }
4217
- end
4218
- local success, newInstance = pcall(function()
4219
- local instance = Instance.new(className)
4220
- if name ~= "" and name then
4221
- instance.Name = name
4222
- end
4223
- for propertyName, propertyValue in pairs(properties) do
4224
- pcall(function()
4225
- local converted = convertPropertyValue(instance, propertyName, propertyValue)
4226
- instance[propertyName] = if converted ~= nil then converted else propertyValue
4227
- end)
4228
- end
4229
- instance.Parent = parentInstance
4230
- return instance
4231
- end)
4232
- if not success or not newInstance then
4233
- return {
4234
- error = tostring(newInstance),
4235
- className = className,
4236
- parentPath = parentPath,
4237
- }
4238
- end
4239
- return {
4240
- instance = newInstance,
4241
- className = className,
4242
- parentPath = parentPath,
4243
- }
4244
- end)
4245
- local results = _binding_2.results
4246
- local successCount = _binding_2.successCount
4247
- local failureCount = _binding_2.failureCount
4248
- finishRecording(recordingId, successCount > 0)
4249
- return {
4250
- results = results,
4251
- summary = {
4252
- total = #objects,
4253
- succeeded = successCount,
4254
- failed = failureCount,
4255
- },
4256
- }
4257
- end
4258
- local function performSmartDuplicate(requestData, useRecording)
4259
- if useRecording == nil then
4260
- useRecording = true
4261
- end
4262
- local instancePath = requestData.instancePath
4263
- local count = requestData.count
4264
- local options = (requestData.options) or {}
4265
- if not (instancePath ~= "" and instancePath) or not (count ~= 0 and count == count and count) or count < 1 then
4266
- return {
4267
- error = "Instance path and count > 0 are required",
4268
- }
4269
- end
4270
- local instance = getInstanceByPath(instancePath)
4271
- if not instance then
4272
- return {
4273
- error = `Instance not found: {instancePath}`,
4274
- }
4275
- end
4276
- local recordingId = if useRecording then beginRecording(`Smart duplicate {instance.Name}`) else nil
4277
- local results = {}
4278
- local successCount = 0
4279
- local failureCount = 0
4280
- do
4281
- local _i = 1
4282
- local _shouldIncrement = false
4283
- while true do
4284
- local i = _i
4285
- if _shouldIncrement then
4286
- i += 1
4287
- else
4288
- _shouldIncrement = true
4289
- end
4290
- if not (i <= count) then
4291
- break
4292
- end
4293
- local success, newInstance = pcall(function()
4294
- local clone = instance:Clone()
4295
- local _value = options.namePattern
4296
- if _value ~= 0 and _value == _value and _value ~= "" and _value then
4297
- local _exp = (options.namePattern)
4298
- local _arg1 = tostring(i)
4299
- clone.Name = (string.gsub(_exp, "{n}", _arg1))
4300
- else
4301
- clone.Name = instance.Name .. tostring(i)
4302
- end
4303
- local _condition = options.positionOffset
4304
- if _condition ~= 0 and _condition == _condition and _condition ~= "" and _condition then
4305
- _condition = clone:IsA("BasePart")
4306
- end
4307
- if _condition ~= 0 and _condition == _condition and _condition ~= "" and _condition then
4308
- local offset = options.positionOffset
4309
- local currentPos = clone.Position
4310
- local _exp = currentPos.X
4311
- local _condition_1 = offset[1]
4312
- if _condition_1 == nil then
4313
- _condition_1 = 0
4314
- end
4315
- local _exp_1 = _exp + _condition_1 * i
4316
- local _exp_2 = currentPos.Y
4317
- local _condition_2 = offset[2]
4318
- if _condition_2 == nil then
4319
- _condition_2 = 0
4320
- end
4321
- local _exp_3 = _exp_2 + _condition_2 * i
4322
- local _exp_4 = currentPos.Z
4323
- local _condition_3 = offset[3]
4324
- if _condition_3 == nil then
4325
- _condition_3 = 0
4326
- end
4327
- clone.Position = Vector3.new(_exp_1, _exp_3, _exp_4 + _condition_3 * i)
4328
- end
4329
- local _condition_1 = options.rotationOffset
4330
- if _condition_1 ~= 0 and _condition_1 == _condition_1 and _condition_1 ~= "" and _condition_1 then
4331
- _condition_1 = clone:IsA("BasePart")
4332
- end
4333
- if _condition_1 ~= 0 and _condition_1 == _condition_1 and _condition_1 ~= "" and _condition_1 then
4334
- local offset = options.rotationOffset
4335
- local _cFrame = clone.CFrame
4336
- local _condition_2 = offset[1]
4337
- if _condition_2 == nil then
4338
- _condition_2 = 0
4339
- end
4340
- local _exp = math.rad(_condition_2 * i)
4341
- local _condition_3 = offset[2]
4342
- if _condition_3 == nil then
4343
- _condition_3 = 0
4344
- end
4345
- local _exp_1 = math.rad(_condition_3 * i)
4346
- local _condition_4 = offset[3]
4347
- if _condition_4 == nil then
4348
- _condition_4 = 0
4349
- end
4350
- local _arg0 = CFrame.Angles(_exp, _exp_1, math.rad(_condition_4 * i))
4351
- clone.CFrame = _cFrame * _arg0
4352
- end
4353
- local _condition_2 = options.scaleOffset
4354
- if _condition_2 ~= 0 and _condition_2 == _condition_2 and _condition_2 ~= "" and _condition_2 then
4355
- _condition_2 = clone:IsA("BasePart")
4356
- end
4357
- if _condition_2 ~= 0 and _condition_2 == _condition_2 and _condition_2 ~= "" and _condition_2 then
4358
- local offset = options.scaleOffset
4359
- local currentSize = clone.Size
4360
- local _exp = currentSize.X
4361
- local _condition_3 = offset[1]
4362
- if _condition_3 == nil then
4363
- _condition_3 = 1
4364
- end
4365
- local _exp_1 = _exp * (_condition_3 ^ i)
4366
- local _exp_2 = currentSize.Y
4367
- local _condition_4 = offset[2]
4368
- if _condition_4 == nil then
4369
- _condition_4 = 1
4370
- end
4371
- local _exp_3 = _exp_2 * (_condition_4 ^ i)
4372
- local _exp_4 = currentSize.Z
4373
- local _condition_5 = offset[3]
4374
- if _condition_5 == nil then
4375
- _condition_5 = 1
4376
- end
4377
- clone.Size = Vector3.new(_exp_1, _exp_3, _exp_4 * (_condition_5 ^ i))
4378
- end
4379
- local _value_1 = options.propertyVariations
4380
- if _value_1 ~= 0 and _value_1 == _value_1 and _value_1 ~= "" and _value_1 then
4381
- for propName, values in pairs(options.propertyVariations) do
4382
- if values and #values > 0 then
4383
- local valueIndex = ((i - 1) % #values)
4384
- pcall(function()
4385
- clone[propName] = values[valueIndex + 1]
4386
- end)
4387
- end
4388
- end
4389
- end
4390
- local targetParents = options.targetParents
4391
- local _value_2 = targetParents and targetParents[i]
4392
- if _value_2 ~= "" and _value_2 then
4393
- local targetParent = getInstanceByPath(targetParents[i])
4394
- clone.Parent = targetParent or instance.Parent
4395
- else
4396
- clone.Parent = instance.Parent
4397
- end
4398
- return clone
4399
- end)
4400
- if success and newInstance then
4401
- successCount += 1
4402
- local _arg0 = {
4403
- success = true,
4404
- instancePath = getInstancePath(newInstance),
4405
- name = newInstance.Name,
4406
- index = i,
4407
- }
4408
- table.insert(results, _arg0)
4409
- else
4410
- failureCount += 1
4411
- local _arg0 = {
4412
- success = false,
4413
- index = i,
4414
- error = tostring(newInstance),
4415
- }
4416
- table.insert(results, _arg0)
4417
- end
4418
- _i = i
4419
- end
4420
- end
4421
- finishRecording(recordingId, successCount > 0)
4422
- return {
4423
- results = results,
4424
- summary = {
4425
- total = count,
4426
- succeeded = successCount,
4427
- failed = failureCount,
4428
- },
4429
- sourceInstance = instancePath,
4430
- }
4431
- end
4432
- local function smartDuplicate(requestData)
4433
- return performSmartDuplicate(requestData, true)
4434
- end
4435
- local function massDuplicate(requestData)
4436
- local duplications = requestData.duplications
4437
- if not duplications or not (type(duplications) == "table") or #duplications == 0 then
4438
- return {
4439
- error = "Duplications array is required",
4440
- }
4441
- end
4442
- local allResults = {}
4443
- local totalSuccess = 0
4444
- local totalFailures = 0
4445
- local recordingId = beginRecording("Mass duplicate operations")
4446
- for _, duplication in duplications do
4447
- local result = performSmartDuplicate(duplication, false)
4448
- table.insert(allResults, result)
4449
- if result.summary then
4450
- totalSuccess += result.summary.succeeded
4451
- totalFailures += result.summary.failed
4452
- end
4453
- end
4454
- finishRecording(recordingId, totalSuccess > 0)
4455
- return {
4456
- results = allResults,
4457
- summary = {
4458
- total = totalSuccess + totalFailures,
4459
- succeeded = totalSuccess,
4460
- failed = totalFailures,
4461
- },
4462
- }
4463
- end
4464
- local function cloneObject(requestData)
4465
- local instancePath = requestData.instancePath
4466
- local targetParentPath = requestData.targetParentPath
4467
- if not (instancePath ~= "" and instancePath) or not (targetParentPath ~= "" and targetParentPath) then
4468
- return {
4469
- error = "Instance path and target parent path are required",
4470
- }
4471
- end
4472
- local instance = getInstanceByPath(instancePath)
4473
- if not instance then
4474
- return {
4475
- error = `Instance not found: {instancePath}`,
4476
- }
4477
- end
4478
- local targetParent = getInstanceByPath(targetParentPath)
4479
- if not targetParent then
4480
- return {
4481
- error = `Target parent not found: {targetParentPath}`,
4482
- }
4483
- end
4484
- local recordingId = beginRecording(`Clone {instance.Name}`)
4485
- local success, clone = pcall(function()
4486
- local cloned = instance:Clone()
4487
- cloned.Parent = targetParent
4488
- return cloned
4489
- end)
4490
- if success and clone then
4491
- finishRecording(recordingId, true)
4492
- return {
4493
- success = true,
4494
- instancePath = getInstancePath(clone),
4495
- name = clone.Name,
4496
- className = clone.ClassName,
4497
- parent = targetParentPath,
4498
- message = "Object cloned successfully",
4499
- }
4500
- end
4501
- finishRecording(recordingId, false)
4502
- return {
4503
- error = `Failed to clone object: {clone}`,
4504
- }
4505
- end
4506
- return {
4507
- createObject = createObject,
4508
- deleteObject = deleteObject,
4509
- massCreateObjects = massCreateObjects,
4510
- smartDuplicate = smartDuplicate,
4511
- massDuplicate = massDuplicate,
4512
- cloneObject = cloneObject,
4513
- }
4514
- ]]></string>
4515
- </Properties>
4516
- </Item>
4517
- <Item class="ModuleScript" referent="15">
3423
+ <Item class="ModuleScript" referent="13">
4518
3424
  <Properties>
4519
3425
  <string name="Name">LogHandlers</string>
4520
3426
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -4540,7 +3446,7 @@ return {
4540
3446
  ]]></string>
4541
3447
  </Properties>
4542
3448
  </Item>
4543
- <Item class="ModuleScript" referent="16">
3449
+ <Item class="ModuleScript" referent="14">
4544
3450
  <Properties>
4545
3451
  <string name="Name">MemoryHandlers</string>
4546
3452
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -4608,23 +3514,17 @@ return {
4608
3514
  ]]></string>
4609
3515
  </Properties>
4610
3516
  </Item>
4611
- <Item class="ModuleScript" referent="17">
3517
+ <Item class="ModuleScript" referent="15">
4612
3518
  <Properties>
4613
3519
  <string name="Name">MetadataHandlers</string>
4614
3520
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
4615
3521
  local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
4616
- local CollectionService = TS.import(script, script.Parent.Parent.Parent, "node_modules", "@rbxts", "services").CollectionService
4617
3522
  local Utils = TS.import(script, script.Parent.Parent, "Utils")
4618
- local Recording = TS.import(script, script.Parent.Parent, "Recording")
4619
3523
  local LuauExec = TS.import(script, script.Parent.Parent, "LuauExec")
4620
- local ChangeHistoryService = game:GetService("ChangeHistoryService")
4621
3524
  local Selection = game:GetService("Selection")
4622
3525
  local _binding = Utils
4623
3526
  local getInstancePath = _binding.getInstancePath
4624
3527
  local getInstanceByPath = _binding.getInstanceByPath
4625
- local _binding_1 = Recording
4626
- local beginRecording = _binding_1.beginRecording
4627
- local finishRecording = _binding_1.finishRecording
4628
3528
  local function serializeValue(value)
4629
3529
  local _value = value
4630
3530
  local vType = typeof(_value)
@@ -4676,127 +3576,6 @@ local function serializeValue(value)
4676
3576
  end
4677
3577
  return value
4678
3578
  end
4679
- local function deserializeValue(attributeValue, valueType)
4680
- local _attributeValue = attributeValue
4681
- if not (type(_attributeValue) == "table") then
4682
- return attributeValue
4683
- end
4684
- local tbl = attributeValue
4685
- local _condition = (tbl._type)
4686
- if _condition == nil then
4687
- _condition = valueType
4688
- end
4689
- local t = _condition
4690
- if t == "Vector3" then
4691
- local _condition_1 = (tbl.X)
4692
- if _condition_1 == nil then
4693
- _condition_1 = 0
4694
- end
4695
- local _condition_2 = (tbl.Y)
4696
- if _condition_2 == nil then
4697
- _condition_2 = 0
4698
- end
4699
- local _condition_3 = (tbl.Z)
4700
- if _condition_3 == nil then
4701
- _condition_3 = 0
4702
- end
4703
- return Vector3.new(_condition_1, _condition_2, _condition_3)
4704
- elseif t == "Color3" then
4705
- local _condition_1 = (tbl.R)
4706
- if _condition_1 == nil then
4707
- _condition_1 = 0
4708
- end
4709
- local _condition_2 = (tbl.G)
4710
- if _condition_2 == nil then
4711
- _condition_2 = 0
4712
- end
4713
- local _condition_3 = (tbl.B)
4714
- if _condition_3 == nil then
4715
- _condition_3 = 0
4716
- end
4717
- return Color3.new(_condition_1, _condition_2, _condition_3)
4718
- elseif t == "UDim2" then
4719
- local x = tbl.X
4720
- local y = tbl.Y
4721
- local _result = x
4722
- if _result ~= nil then
4723
- _result = _result.Scale
4724
- end
4725
- local _condition_1 = _result
4726
- if _condition_1 == nil then
4727
- _condition_1 = 0
4728
- end
4729
- local _result_1 = x
4730
- if _result_1 ~= nil then
4731
- _result_1 = _result_1.Offset
4732
- end
4733
- local _condition_2 = _result_1
4734
- if _condition_2 == nil then
4735
- _condition_2 = 0
4736
- end
4737
- local _result_2 = y
4738
- if _result_2 ~= nil then
4739
- _result_2 = _result_2.Scale
4740
- end
4741
- local _condition_3 = _result_2
4742
- if _condition_3 == nil then
4743
- _condition_3 = 0
4744
- end
4745
- local _result_3 = y
4746
- if _result_3 ~= nil then
4747
- _result_3 = _result_3.Offset
4748
- end
4749
- local _condition_4 = _result_3
4750
- if _condition_4 == nil then
4751
- _condition_4 = 0
4752
- end
4753
- return UDim2.new(_condition_1, _condition_2, _condition_3, _condition_4)
4754
- elseif t == "BrickColor" then
4755
- local _condition_1 = (tbl.Name)
4756
- if _condition_1 == nil then
4757
- _condition_1 = "Medium stone grey"
4758
- end
4759
- return BrickColor.new(_condition_1)
4760
- end
4761
- return attributeValue
4762
- end
4763
- local function setAttribute(requestData)
4764
- local instancePath = requestData.instancePath
4765
- local attributeName = requestData.attributeName
4766
- local attributeValue = requestData.attributeValue
4767
- local valueType = requestData.valueType
4768
- if not (instancePath ~= "" and instancePath) or not (attributeName ~= "" and attributeName) then
4769
- return {
4770
- error = "Instance path and attribute name are required",
4771
- }
4772
- end
4773
- local instance = getInstanceByPath(instancePath)
4774
- if not instance then
4775
- return {
4776
- error = `Instance not found: {instancePath}`,
4777
- }
4778
- end
4779
- local recordingId = beginRecording(`Set attribute {attributeName} on {instance.Name}`)
4780
- local success, result = pcall(function()
4781
- local value = deserializeValue(attributeValue, valueType)
4782
- instance:SetAttribute(attributeName, value)
4783
- return {
4784
- success = true,
4785
- instancePath = instancePath,
4786
- attributeName = attributeName,
4787
- value = attributeValue,
4788
- message = "Attribute set successfully",
4789
- }
4790
- end)
4791
- if success then
4792
- finishRecording(recordingId, true)
4793
- return result
4794
- end
4795
- finishRecording(recordingId, false)
4796
- return {
4797
- error = `Failed to set attribute: {result}`,
4798
- }
4799
- end
4800
3579
  local function getAttributes(requestData)
4801
3580
  local instancePath = requestData.instancePath
4802
3581
  if not (instancePath ~= "" and instancePath) then
@@ -4834,110 +3613,117 @@ local function getAttributes(requestData)
4834
3613
  error = `Failed to get attributes: {result}`,
4835
3614
  }
4836
3615
  end
4837
- local function deleteAttribute(requestData)
4838
- local instancePath = requestData.instancePath
4839
- local attributeName = requestData.attributeName
4840
- if not (instancePath ~= "" and instancePath) or not (attributeName ~= "" and attributeName) then
3616
+ local function getSelection(_requestData)
3617
+ local selection = Selection:Get()
3618
+ if #selection == 0 then
4841
3619
  return {
4842
- error = "Instance path and attribute name are required",
3620
+ success = true,
3621
+ selection = {},
3622
+ count = 0,
3623
+ message = "No objects selected",
4843
3624
  }
4844
3625
  end
4845
- local instance = getInstanceByPath(instancePath)
4846
- if not instance then
3626
+ -- ReadonlyArray.map
3627
+ local _newValue = table.create(#selection)
3628
+ local _callback = function(instance)
4847
3629
  return {
4848
- error = `Instance not found: {instancePath}`,
3630
+ name = instance.Name,
3631
+ className = instance.ClassName,
3632
+ path = getInstancePath(instance),
3633
+ parent = if instance.Parent then getInstancePath(instance.Parent) else nil,
4849
3634
  }
4850
3635
  end
4851
- local recordingId = beginRecording(`Delete attribute {attributeName} from {instance.Name}`)
4852
- local success, result = pcall(function()
4853
- local existed = instance:GetAttribute(attributeName) ~= nil
4854
- instance:SetAttribute(attributeName, nil)
4855
- return {
4856
- success = true,
4857
- instancePath = instancePath,
4858
- attributeName = attributeName,
4859
- existed = existed,
4860
- message = if existed then "Attribute deleted successfully" else "Attribute did not exist",
4861
- }
4862
- end)
4863
- if success then
4864
- finishRecording(recordingId, true)
4865
- return result
3636
+ for _k, _v in selection do
3637
+ _newValue[_k] = _callback(_v, _k - 1, selection)
4866
3638
  end
4867
- finishRecording(recordingId, false)
3639
+ -- ▲ ReadonlyArray.map ▲
3640
+ local selectedObjects = _newValue
4868
3641
  return {
4869
- error = `Failed to delete attribute: {result}`,
3642
+ success = true,
3643
+ selection = selectedObjects,
3644
+ count = #selection,
3645
+ message = `{#selection} object(s) selected`,
4870
3646
  }
4871
3647
  end
4872
- local function getTags(requestData)
4873
- local instancePath = requestData.instancePath
4874
- if not (instancePath ~= "" and instancePath) then
3648
+ -- The counterpart to getSelection: this sets it. Selecting what the agent just
3649
+ -- built shows the user the result and puts the instance under Studio's own
3650
+ -- move/scale handles, which is why it is a tool and not left to execute_luau.
3651
+ local function setSelection(requestData)
3652
+ local rawPaths = requestData.paths
3653
+ if not (type(rawPaths) == "table") then
4875
3654
  return {
4876
- error = "Instance path is required",
3655
+ error = "paths is required (an empty array clears in set mode)",
4877
3656
  }
4878
3657
  end
4879
- local instance = getInstanceByPath(instancePath)
4880
- if not instance then
4881
- return {
4882
- error = `Instance not found: {instancePath}`,
4883
- }
3658
+ local _condition = (requestData.mode)
3659
+ if _condition == nil then
3660
+ _condition = "set"
4884
3661
  end
4885
- local success, result = pcall(function()
4886
- local tags = CollectionService:GetTags(instance)
3662
+ local mode = _condition
3663
+ if mode ~= "set" and mode ~= "add" and mode ~= "remove" then
4887
3664
  return {
4888
- instancePath = instancePath,
4889
- tags = tags,
4890
- count = #tags,
3665
+ error = `mode must be "set", "add" or "remove" (got: {mode})`,
4891
3666
  }
4892
- end)
4893
- if success then
4894
- return result
4895
3667
  end
4896
- return {
4897
- error = `Failed to get tags: {result}`,
4898
- }
4899
- end
4900
- local function addTag(requestData)
4901
- local instancePath = requestData.instancePath
4902
- local tagName = requestData.tagName
4903
- if not (instancePath ~= "" and instancePath) or not (tagName ~= "" and tagName) then
3668
+ local paths = rawPaths
3669
+ if #paths == 0 and mode ~= "set" then
4904
3670
  return {
4905
- error = "Instance path and tag name are required",
3671
+ error = `paths cannot be empty in {mode} mode`,
4906
3672
  }
4907
3673
  end
4908
- local instance = getInstanceByPath(instancePath)
4909
- if not instance then
3674
+ local targets = {}
3675
+ local missing = {}
3676
+ for _, entry in paths do
3677
+ if not (type(entry) == "string") or entry == "" then
3678
+ return {
3679
+ error = "paths must contain non-empty instance path strings",
3680
+ }
3681
+ end
3682
+ local instance = getInstanceByPath(entry)
3683
+ if instance then
3684
+ table.insert(targets, instance)
3685
+ else
3686
+ table.insert(missing, entry)
3687
+ end
3688
+ end
3689
+ local clearsSelection = mode == "set" and #paths == 0
3690
+ if #targets == 0 and not clearsSelection then
4910
3691
  return {
4911
- error = `Instance not found: {instancePath}`,
3692
+ error = "No matching instances found for any path",
3693
+ missingPaths = missing,
4912
3694
  }
4913
3695
  end
4914
- local recordingId = beginRecording(`Add tag {tagName} to {instance.Name}`)
4915
- local success, result = pcall(function()
4916
- local alreadyHad = CollectionService:HasTag(instance, tagName)
4917
- CollectionService:AddTag(instance, tagName)
3696
+ local ok, err = pcall(function()
3697
+ if mode == "add" then
3698
+ Selection:Add(targets)
3699
+ elseif mode == "remove" then
3700
+ Selection:Remove(targets)
3701
+ else
3702
+ Selection:Set(targets)
3703
+ end
3704
+ end)
3705
+ if not ok then
4918
3706
  return {
4919
- success = true,
4920
- instancePath = instancePath,
4921
- tagName = tagName,
4922
- alreadyHad = alreadyHad,
4923
- message = if alreadyHad then "Instance already had this tag" else "Tag added successfully",
3707
+ error = `Selection.{mode} failed: {tostring(err)}`,
4924
3708
  }
4925
- end)
4926
- if success then
4927
- finishRecording(recordingId, true)
4928
- return result
4929
3709
  end
4930
- finishRecording(recordingId, false)
4931
3710
  return {
4932
- error = `Failed to add tag: {result}`,
3711
+ success = true,
3712
+ mode = mode,
3713
+ selected = #targets,
3714
+ missingPaths = missing,
3715
+ message = if clearsSelection then "Selection cleared" elseif mode == "remove" then `Deselected {#targets} object(s)` else `{#targets} object(s) {if mode == "add" then "added to" else "in"} the selection`,
4933
3716
  }
4934
3717
  end
4935
- local function removeTag(requestData)
4936
- local instancePath = requestData.instancePath
4937
- local tagName = requestData.tagName
4938
- if not (instancePath ~= "" and instancePath) or not (tagName ~= "" and tagName) then
3718
+ -- Aims the Studio camera at an instance and frames it from a sensible angle.
3719
+ -- This completes the screenshot loop capture_screenshot documents: build,
3720
+ -- focus, screenshot. Framing distance comes from the bounding box and the
3721
+ -- camera's own field of view so any subject fills a similar share of frame.
3722
+ local function focusViewport(requestData)
3723
+ local instancePath = requestData.path
3724
+ if not (instancePath ~= "" and instancePath) then
4939
3725
  return {
4940
- error = "Instance path and tag name are required",
3726
+ error = "path is required (the instance to frame)",
4941
3727
  }
4942
3728
  end
4943
3729
  local instance = getInstanceByPath(instancePath)
@@ -4946,93 +3732,91 @@ local function removeTag(requestData)
4946
3732
  error = `Instance not found: {instancePath}`,
4947
3733
  }
4948
3734
  end
4949
- local recordingId = beginRecording(`Remove tag {tagName} from {instance.Name}`)
4950
- local success, result = pcall(function()
4951
- local hadTag = CollectionService:HasTag(instance, tagName)
4952
- CollectionService:RemoveTag(instance, tagName)
3735
+ local workspace = game:GetService("Workspace")
3736
+ local camera = workspace.CurrentCamera
3737
+ if not camera then
4953
3738
  return {
4954
- success = true,
4955
- instancePath = instancePath,
4956
- tagName = tagName,
4957
- hadTag = hadTag,
4958
- message = if hadTag then "Tag removed successfully" else "Instance did not have this tag",
3739
+ error = "Workspace.CurrentCamera is unavailable right now",
4959
3740
  }
4960
- end)
4961
- if success then
4962
- finishRecording(recordingId, true)
4963
- return result
4964
3741
  end
4965
- finishRecording(recordingId, false)
4966
- return {
4967
- error = `Failed to remove tag: {result}`,
4968
- }
4969
- end
4970
- local function getTagged(requestData)
4971
- local tagName = requestData.tagName
4972
- if not (tagName ~= "" and tagName) then
3742
+ local _condition = tonumber(requestData.padding)
3743
+ if _condition == nil then
3744
+ _condition = 1
3745
+ end
3746
+ local padding = _condition
3747
+ if padding <= 0 or padding > 10 then
4973
3748
  return {
4974
- error = "Tag name is required",
3749
+ error = "padding must be greater than 0 and at most 10",
4975
3750
  }
4976
3751
  end
4977
- local success, result = pcall(function()
4978
- local taggedInstances = CollectionService:GetTagged(tagName)
4979
- -- ▼ ReadonlyArray.map ▼
4980
- local _newValue = table.create(#taggedInstances)
4981
- local _callback = function(instance)
4982
- return {
4983
- name = instance.Name,
4984
- className = instance.ClassName,
4985
- path = getInstancePath(instance),
4986
- }
4987
- end
4988
- for _k, _v in taggedInstances do
4989
- _newValue[_k] = _callback(_v, _k - 1, taggedInstances)
4990
- end
4991
- -- ▲ ReadonlyArray.map ▲
4992
- local instances = _newValue
3752
+ local compassOverride = if requestData.from == nil then nil else tonumber(requestData.from)
3753
+ if requestData.from ~= nil and compassOverride == nil then
4993
3754
  return {
4994
- tagName = tagName,
4995
- instances = instances,
4996
- count = #instances,
3755
+ error = "from must be a compass angle in degrees",
4997
3756
  }
4998
- end)
4999
- if success then
5000
- return result
5001
3757
  end
5002
- return {
5003
- error = `Failed to get tagged instances: {result}`,
5004
- }
5005
- end
5006
- local function getSelection(_requestData)
5007
- local selection = Selection:Get()
5008
- if #selection == 0 then
3758
+ local elevationOverride = if requestData.angleY == nil then nil else tonumber(requestData.angleY)
3759
+ if requestData.angleY ~= nil and (elevationOverride == nil or elevationOverride < -89 or elevationOverride > 89) then
5009
3760
  return {
5010
- success = true,
5011
- selection = {},
5012
- count = 0,
5013
- message = "No objects selected",
3761
+ error = "angleY must be between -89 and 89",
5014
3762
  }
5015
3763
  end
5016
- -- ReadonlyArray.map
5017
- local _newValue = table.create(#selection)
5018
- local _callback = function(instance)
3764
+ local ok, err = pcall(function()
3765
+ local boundsCF
3766
+ local boundsSize
3767
+ if instance:IsA("Model") then
3768
+ boundsCF, boundsSize = instance:GetBoundingBox()
3769
+ elseif instance:IsA("BasePart") then
3770
+ boundsCF = instance.CFrame
3771
+ boundsSize = instance.Size
3772
+ else
3773
+ return error(`Cannot frame a {instance.ClassName}: it has no 3D bounding box. Frame a part or model instead.`)
3774
+ end
3775
+ local center = boundsCF.Position
3776
+ local direction = camera.CFrame.LookVector * (-1)
3777
+ local currentHorizontal = Vector3.new(direction.X, 0, direction.Z)
3778
+ local horizontalDirection = if currentHorizontal.Magnitude < 0.001 then Vector3.new(1, 0, 0) else currentHorizontal.Unit
3779
+ if compassOverride ~= nil then
3780
+ local yaw = math.rad(compassOverride)
3781
+ horizontalDirection = Vector3.new(math.cos(yaw), 0, math.sin(yaw))
3782
+ end
3783
+ local vertical = direction.Y
3784
+ local horizontalMagnitude = math.sqrt(math.max(0, 1 - vertical * vertical))
3785
+ if elevationOverride ~= nil then
3786
+ local pitch = math.rad(elevationOverride)
3787
+ vertical = math.sin(pitch)
3788
+ horizontalMagnitude = math.cos(pitch)
3789
+ end
3790
+ local _horizontalDirection = horizontalDirection
3791
+ local _horizontalMagnitude = horizontalMagnitude
3792
+ local _vector3 = Vector3.new(0, vertical, 0)
3793
+ direction = (_horizontalDirection * _horizontalMagnitude + _vector3).Unit
3794
+ camera.CameraType = Enum.CameraType.Scriptable
3795
+ local _direction = direction
3796
+ local _arg0 = math.max(boundsSize.Magnitude, 1)
3797
+ camera.CFrame = CFrame.lookAt(center + (_direction * _arg0), center)
3798
+ camera.Focus = CFrame.new(center)
3799
+ camera:ZoomToExtents(boundsCF, boundsSize)
3800
+ if padding ~= 1 then
3801
+ local fittedOffset = camera.CFrame.Position - center
3802
+ local _arg0_1 = fittedOffset * padding
3803
+ camera.CFrame = CFrame.lookAt(center + _arg0_1, center)
3804
+ end
3805
+ camera.Focus = CFrame.new(center)
3806
+ end)
3807
+ if not ok then
5019
3808
  return {
5020
- name = instance.Name,
5021
- className = instance.ClassName,
5022
- path = getInstancePath(instance),
5023
- parent = if instance.Parent then getInstancePath(instance.Parent) else nil,
3809
+ error = `focus failed: {tostring(err)}`,
5024
3810
  }
5025
3811
  end
5026
- for _k, _v in selection do
5027
- _newValue[_k] = _callback(_v, _k - 1, selection)
5028
- end
5029
- -- ▲ ReadonlyArray.map ▲
5030
- local selectedObjects = _newValue
5031
3812
  return {
5032
3813
  success = true,
5033
- selection = selectedObjects,
5034
- count = #selection,
5035
- message = `{#selection} object(s) selected`,
3814
+ path = getInstancePath(instance),
3815
+ cameraPosition = {
3816
+ X = camera.CFrame.Position.X,
3817
+ Y = camera.CFrame.Position.Y,
3818
+ Z = camera.CFrame.Position.Z,
3819
+ },
5036
3820
  }
5037
3821
  end
5038
3822
  local function executeLuau(requestData)
@@ -5048,106 +3832,17 @@ local function executeLuau(requestData)
5048
3832
  -- the same code path and produce identical output shapes.
5049
3833
  return LuauExec.execute(code)
5050
3834
  end
5051
- local function undo(_requestData)
5052
- local success, result = pcall(function()
5053
- ChangeHistoryService:Undo()
5054
- return {
5055
- success = true,
5056
- message = "Undo executed successfully",
5057
- }
5058
- end)
5059
- if success then
5060
- return result
5061
- end
5062
- return {
5063
- error = `Failed to undo: {result}`,
5064
- }
5065
- end
5066
- local function redo(_requestData)
5067
- local success, result = pcall(function()
5068
- ChangeHistoryService:Redo()
5069
- return {
5070
- success = true,
5071
- message = "Redo executed successfully",
5072
- }
5073
- end)
5074
- if success then
5075
- return result
5076
- end
5077
- return {
5078
- error = `Failed to redo: {result}`,
5079
- }
5080
- end
5081
- local function bulkSetAttributes(requestData)
5082
- local instancePath = requestData.instancePath
5083
- local attributes = requestData.attributes
5084
- if not (instancePath ~= "" and instancePath) or not attributes then
5085
- return {
5086
- error = "Instance path and attributes are required",
5087
- }
5088
- end
5089
- local instance = getInstanceByPath(instancePath)
5090
- if not instance then
5091
- return {
5092
- error = `Instance not found: {instancePath}`,
5093
- }
5094
- end
5095
- local recordingId = beginRecording(`Bulk set attributes on {instance.Name}`)
5096
- local results = {}
5097
- local successCount = 0
5098
- local failureCount = 0
5099
- for name, rawValue in pairs(attributes) do
5100
- local attrName = name
5101
- local ok, err = pcall(function()
5102
- local value = deserializeValue(rawValue)
5103
- instance:SetAttribute(attrName, value)
5104
- end)
5105
- if ok then
5106
- successCount += 1
5107
- local _arg0 = {
5108
- attributeName = attrName,
5109
- success = true,
5110
- }
5111
- table.insert(results, _arg0)
5112
- else
5113
- failureCount += 1
5114
- local _arg0 = {
5115
- attributeName = attrName,
5116
- success = false,
5117
- error = tostring(err),
5118
- }
5119
- table.insert(results, _arg0)
5120
- end
5121
- end
5122
- finishRecording(recordingId, successCount > 0)
5123
- return {
5124
- instancePath = instancePath,
5125
- results = results,
5126
- summary = {
5127
- total = successCount + failureCount,
5128
- succeeded = successCount,
5129
- failed = failureCount,
5130
- },
5131
- }
5132
- end
5133
3835
  return {
5134
- setAttribute = setAttribute,
5135
3836
  getAttributes = getAttributes,
5136
- deleteAttribute = deleteAttribute,
5137
- getTags = getTags,
5138
- addTag = addTag,
5139
- removeTag = removeTag,
5140
- getTagged = getTagged,
5141
3837
  getSelection = getSelection,
3838
+ setSelection = setSelection,
3839
+ focusViewport = focusViewport,
5142
3840
  executeLuau = executeLuau,
5143
- undo = undo,
5144
- redo = redo,
5145
- bulkSetAttributes = bulkSetAttributes,
5146
3841
  }
5147
3842
  ]]></string>
5148
3843
  </Properties>
5149
3844
  </Item>
5150
- <Item class="ModuleScript" referent="18">
3845
+ <Item class="ModuleScript" referent="16">
5151
3846
  <Properties>
5152
3847
  <string name="Name">MicroProfilerHandlers</string>
5153
3848
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -5162,6 +3857,8 @@ local DEFAULT_MAX_TIMERS_PER_GROUP = 5
5162
3857
  local DEFAULT_MAX_RELATED_TIMERS = 3
5163
3858
  local DEFAULT_MAX_EVENTS = 250000
5164
3859
  local MAX_EVENTS = 1000000
3860
+ local COOPERATIVE_CLOCK_CHECK_INTERVAL = 128
3861
+ local MAX_COOPERATIVE_SLICE_SECONDS = 0.008
5165
3862
  local DEFAULT_FRAME_WINDOW = 240
5166
3863
  local BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
5167
3864
  local PAD_BYTE = (string.byte("="))
@@ -5178,6 +3875,87 @@ local FOCUS_GROUP_MASKS = {
5178
3875
  jobs = { "Jobs" },
5179
3876
  }
5180
3877
  local cachedLibMP
3878
+ -- Sampling the clock avoids a timing call for every event while still bounding
3879
+ -- each VM resumption. Sorting uses the same checkpoint so no full sort is atomic.
3880
+ local function createCooperativeCheckpoint()
3881
+ local operationsSinceClockCheck = 0
3882
+ local sliceStartedAt = os.clock()
3883
+ return function()
3884
+ operationsSinceClockCheck += 1
3885
+ if operationsSinceClockCheck < COOPERATIVE_CLOCK_CHECK_INTERVAL then
3886
+ return nil
3887
+ end
3888
+ operationsSinceClockCheck = 0
3889
+ if os.clock() - sliceStartedAt < MAX_COOPERATIVE_SLICE_SECONDS then
3890
+ return nil
3891
+ end
3892
+ task.wait()
3893
+ sliceStartedAt = os.clock()
3894
+ end
3895
+ end
3896
+ local function cooperativeSort(values, comesBefore, checkpoint)
3897
+ local count = #values
3898
+ if count < 2 then
3899
+ return nil
3900
+ end
3901
+ local function siftDown(start, lastIndex)
3902
+ local root = start
3903
+ while root * 2 + 1 <= lastIndex do
3904
+ checkpoint()
3905
+ local candidate = root
3906
+ local left = root * 2 + 1
3907
+ if comesBefore(values[candidate + 1], values[left + 1]) then
3908
+ candidate = left
3909
+ end
3910
+ local right = left + 1
3911
+ if right <= lastIndex and comesBefore(values[candidate + 1], values[right + 1]) then
3912
+ candidate = right
3913
+ end
3914
+ if candidate == root then
3915
+ return nil
3916
+ end
3917
+ local value = values[root + 1]
3918
+ values[root + 1] = values[candidate + 1]
3919
+ values[candidate + 1] = value
3920
+ root = candidate
3921
+ end
3922
+ end
3923
+ do
3924
+ local start = math.floor((count - 2) / 2)
3925
+ local _shouldIncrement = false
3926
+ while true do
3927
+ if _shouldIncrement then
3928
+ start -= 1
3929
+ else
3930
+ _shouldIncrement = true
3931
+ end
3932
+ if not (start >= 0) then
3933
+ break
3934
+ end
3935
+ siftDown(start, count - 1)
3936
+ checkpoint()
3937
+ end
3938
+ end
3939
+ do
3940
+ local lastIndex = count - 1
3941
+ local _shouldIncrement = false
3942
+ while true do
3943
+ if _shouldIncrement then
3944
+ lastIndex -= 1
3945
+ else
3946
+ _shouldIncrement = true
3947
+ end
3948
+ if not (lastIndex > 0) then
3949
+ break
3950
+ end
3951
+ local value = values[1]
3952
+ values[1] = values[lastIndex + 1]
3953
+ values[lastIndex + 1] = value
3954
+ checkpoint()
3955
+ siftDown(0, lastIndex - 1)
3956
+ end
3957
+ end
3958
+ end
5181
3959
  local function normalizeDurationMs(value)
5182
3960
  local _value = value
5183
3961
  if not (type(_value) == "number") then
@@ -5305,7 +4083,7 @@ local function addFrameRaw(map, frameId, raw)
5305
4083
  local _arg1 = _condition + raw
5306
4084
  _map[_exp] = _arg1
5307
4085
  end
5308
- local function summarizeFrameImpact(frameRawById, analyzedFrames)
4086
+ local function summarizeFrameImpact(frameRawById, analyzedFrames, checkpoint)
5309
4087
  local _condition = frameRawById == nil
5310
4088
  if not _condition then
5311
4089
  -- ▼ ReadonlyMap.size ▼
@@ -5330,6 +4108,7 @@ local function summarizeFrameImpact(frameRawById, analyzedFrames)
5330
4108
  local maxUs = 0
5331
4109
  local maxFrameId = 0
5332
4110
  for frameId, raw in frameRawById do
4111
+ checkpoint()
5333
4112
  local us = rawToUs(raw)
5334
4113
  table.insert(values, us)
5335
4114
  totalUs += us
@@ -5338,9 +4117,9 @@ local function summarizeFrameImpact(frameRawById, analyzedFrames)
5338
4117
  maxFrameId = frameId
5339
4118
  end
5340
4119
  end
5341
- table.sort(values, function(a, b)
4120
+ cooperativeSort(values, function(a, b)
5342
4121
  return a < b
5343
- end)
4122
+ end, checkpoint)
5344
4123
  return {
5345
4124
  active_frame_count = #values,
5346
4125
  active_frame_pct = percent(#values, analyzedFrames),
@@ -5351,7 +4130,7 @@ local function summarizeFrameImpact(frameRawById, analyzedFrames)
5351
4130
  max_frame_id = maxFrameId,
5352
4131
  }
5353
4132
  end
5354
- local function encodeBase64(buf)
4133
+ local function encodeBase64(buf, checkpoint)
5355
4134
  local len = buffer.len(buf)
5356
4135
  local fullTriples = math.floor(len / 3)
5357
4136
  local remaining = len - fullTriples * 3
@@ -5380,6 +4159,7 @@ local function encodeBase64(buf)
5380
4159
  buffer.writeu8(out, di + 3, B64[bit32.band(b2, 63) + 1])
5381
4160
  si += 3
5382
4161
  di += 4
4162
+ checkpoint()
5383
4163
  end
5384
4164
  end
5385
4165
  if remaining == 2 then
@@ -5490,7 +4270,7 @@ local function recommendedToolsForGroups(groups, targetRole)
5490
4270
  end
5491
4271
  return tools
5492
4272
  end
5493
- local function collectFrameSummary(session, startFrame, frameMax)
4273
+ local function collectFrameSummary(session, startFrame, frameMax, checkpoint)
5494
4274
  local frameRows = {}
5495
4275
  local durations = {}
5496
4276
  local incompleteFrames = 0
@@ -5508,6 +4288,7 @@ local function collectFrameSummary(session, startFrame, frameMax)
5508
4288
  if not (frameId <= frameMax) then
5509
4289
  break
5510
4290
  end
4291
+ checkpoint()
5511
4292
  local ok, descOrErr = safeCall(function()
5512
4293
  return session:GetFrameDesc(frameId)
5513
4294
  end)
@@ -5547,15 +4328,16 @@ local function collectFrameSummary(session, startFrame, frameMax)
5547
4328
  _frameId = frameId
5548
4329
  end
5549
4330
  end
5550
- table.sort(durations, function(a, b)
4331
+ cooperativeSort(durations, function(a, b)
5551
4332
  return a < b
5552
- end)
5553
- table.sort(frameRows, function(a, b)
4333
+ end, checkpoint)
4334
+ cooperativeSort(frameRows, function(a, b)
5554
4335
  return (a.duration_us) > (b.duration_us)
5555
- end)
4336
+ end, checkpoint)
5556
4337
  local totalUs = 0
5557
4338
  for _, duration in durations do
5558
4339
  totalUs += duration
4340
+ checkpoint()
5559
4341
  end
5560
4342
  local topFrames = {}
5561
4343
  do
@@ -5869,6 +4651,7 @@ local function captureMicroProfiler(requestData)
5869
4651
  backend = backend,
5870
4652
  }
5871
4653
  end
4654
+ local checkpoint = createCooperativeCheckpoint()
5872
4655
  local stacks = {}
5873
4656
  local aggregates = {}
5874
4657
  local timerThreadAggregates = {}
@@ -5884,7 +4667,11 @@ local function captureMicroProfiler(requestData)
5884
4667
  local sampledFrameMax
5885
4668
  local lastProcessedFrameId
5886
4669
  local lastProcessedTimestampRaw
5887
- while eventsSampled < maxEvents and iterator:Step() do
4670
+ while eventsSampled < maxEvents do
4671
+ checkpoint()
4672
+ if not iterator:Step() then
4673
+ break
4674
+ end
5888
4675
  eventsSampled += 1
5889
4676
  local state = iterator:GetState()
5890
4677
  if not state then
@@ -5925,6 +4712,7 @@ local function captureMicroProfiler(requestData)
5925
4712
  exitEvents += 1
5926
4713
  local entry
5927
4714
  while #stack > 0 do
4715
+ checkpoint()
5928
4716
  -- ▼ Array.pop ▼
5929
4717
  local _length = #stack
5930
4718
  local _result = stack[_length]
@@ -6002,7 +4790,7 @@ local function captureMicroProfiler(requestData)
6002
4790
  _condition_2 = frameMax
6003
4791
  end
6004
4792
  local analysisFrameMax = _condition_2
6005
- local frameSummary = collectFrameSummary(session, analysisFrameMin, analysisFrameMax)
4793
+ local frameSummary = collectFrameSummary(session, analysisFrameMin, analysisFrameMax, checkpoint)
6006
4794
  local _frames = frameSummary.frames
6007
4795
  local _condition_3 = type(_frames) == "number"
6008
4796
  if _condition_3 then
@@ -6051,6 +4839,7 @@ local function captureMicroProfiler(requestData)
6051
4839
  local omittedBelowThreshold = 0
6052
4840
  local omittedByFilter = 0
6053
4841
  for timerId, aggregate in aggregates do
4842
+ checkpoint()
6054
4843
  local info = getTimerInfo(timerId)
6055
4844
  local totalUs = rawToUs(aggregate.inclusive_raw)
6056
4845
  if not includeIdle and isIdleTimer(info) then
@@ -6115,7 +4904,7 @@ local function captureMicroProfiler(requestData)
6115
4904
  if maxToAvg ~= nil then
6116
4905
  row.max_to_avg = maxToAvg
6117
4906
  end
6118
- local timerFrameImpact = summarizeFrameImpact(timerFrameAggregates[timerId], analysisFrameCount)
4907
+ local timerFrameImpact = summarizeFrameImpact(timerFrameAggregates[timerId], analysisFrameCount, checkpoint)
6119
4908
  for key, value in pairs(timerFrameImpact) do
6120
4909
  row[key] = value
6121
4910
  end
@@ -6141,12 +4930,14 @@ local function captureMicroProfiler(requestData)
6141
4930
  end
6142
4931
  for frameId, raw in timerFrames do
6143
4932
  addFrameRaw(groupFrames, frameId, raw)
4933
+ checkpoint()
6144
4934
  end
6145
4935
  end
6146
4936
  local perThread = timerThreadAggregates[timerId]
6147
4937
  if perThread ~= nil then
6148
4938
  local timerThreadSummaryRows = {}
6149
4939
  for threadId, threadAggregate in perThread do
4940
+ checkpoint()
6150
4941
  local threadAggregateRow = threadAggregates[threadId]
6151
4942
  if threadAggregateRow == nil then
6152
4943
  threadAggregateRow = {
@@ -6205,9 +4996,9 @@ local function captureMicroProfiler(requestData)
6205
4996
  }
6206
4997
  table.insert(timerThreadSummaryRows, _arg0)
6207
4998
  end
6208
- table.sort(timerThreadSummaryRows, function(a, b)
4999
+ cooperativeSort(timerThreadSummaryRows, function(a, b)
6209
5000
  return (a.inclusive_us) > (b.inclusive_us)
6210
- end)
5001
+ end, checkpoint)
6211
5002
  if maxRelatedTimers > 0 and #timerThreadSummaryRows > 0 then
6212
5003
  local topTimerThreads = {}
6213
5004
  do
@@ -6230,13 +5021,14 @@ local function captureMicroProfiler(requestData)
6230
5021
  end
6231
5022
  end
6232
5023
  end
6233
- table.sort(rows, function(a, b)
5024
+ cooperativeSort(rows, function(a, b)
6234
5025
  return (a.inclusive_us) > (b.inclusive_us)
6235
- end)
5026
+ end, checkpoint)
6236
5027
  local edgeRows = {}
6237
5028
  local parentRelationsByChild = {}
6238
5029
  local childRelationsByParent = {}
6239
5030
  for _, edge in edgeAggregates do
5031
+ checkpoint()
6240
5032
  local _parent_timer_id = edge.parent_timer_id
6241
5033
  local parentRow = rowsByTimerId[_parent_timer_id]
6242
5034
  local _child_timer_id = edge.child_timer_id
@@ -6307,21 +5099,24 @@ local function captureMicroProfiler(requestData)
6307
5099
  }
6308
5100
  table.insert(_childRelations, _arg0_1)
6309
5101
  end
6310
- table.sort(edgeRows, function(a, b)
5102
+ cooperativeSort(edgeRows, function(a, b)
6311
5103
  return (a.inclusive_us) > (b.inclusive_us)
6312
- end)
5104
+ end, checkpoint)
6313
5105
  if maxRelatedTimers > 0 then
6314
5106
  for _, relationRows in parentRelationsByChild do
6315
- table.sort(relationRows, function(a, b)
5107
+ checkpoint()
5108
+ cooperativeSort(relationRows, function(a, b)
6316
5109
  return (a.inclusive_us) > (b.inclusive_us)
6317
- end)
5110
+ end, checkpoint)
6318
5111
  end
6319
5112
  for _, relationRows in childRelationsByParent do
6320
- table.sort(relationRows, function(a, b)
5113
+ checkpoint()
5114
+ cooperativeSort(relationRows, function(a, b)
6321
5115
  return (a.inclusive_us) > (b.inclusive_us)
6322
- end)
5116
+ end, checkpoint)
6323
5117
  end
6324
5118
  for _, row in rows do
5119
+ checkpoint()
6325
5120
  local timerId = row.timer_id
6326
5121
  local parents = parentRelationsByChild[timerId]
6327
5122
  local children = childRelationsByParent[timerId]
@@ -6388,10 +5183,11 @@ local function captureMicroProfiler(requestData)
6388
5183
  local rowsByExclusive = {}
6389
5184
  for _, row in rows do
6390
5185
  table.insert(rowsByExclusive, row)
5186
+ checkpoint()
6391
5187
  end
6392
- table.sort(rowsByExclusive, function(a, b)
5188
+ cooperativeSort(rowsByExclusive, function(a, b)
6393
5189
  return (a.exclusive_us) > (b.exclusive_us)
6394
- end)
5190
+ end, checkpoint)
6395
5191
  local topTimersByExclusive = {}
6396
5192
  do
6397
5193
  local i = 0
@@ -6430,6 +5226,7 @@ local function captureMicroProfiler(requestData)
6430
5226
  end
6431
5227
  local threadRows = {}
6432
5228
  for threadId, aggregate in threadAggregates do
5229
+ checkpoint()
6433
5230
  local info = getThreadInfo(threadId)
6434
5231
  local inclusiveUs = rawToUs(aggregate.inclusive_raw)
6435
5232
  local exclusiveUs = rawToUs(aggregate.exclusive_raw)
@@ -6447,9 +5244,9 @@ local function captureMicroProfiler(requestData)
6447
5244
  pct_of_analyzed_wall = percent(inclusiveUs, analysisDurationUs),
6448
5245
  }
6449
5246
  local timerRows = threadTimerRows[threadId] or {}
6450
- table.sort(timerRows, function(a, b)
5247
+ cooperativeSort(timerRows, function(a, b)
6451
5248
  return (a.inclusive_us) > (b.inclusive_us)
6452
- end)
5249
+ end, checkpoint)
6453
5250
  local topThreadTimers = {}
6454
5251
  do
6455
5252
  local i = 0
@@ -6472,9 +5269,9 @@ local function captureMicroProfiler(requestData)
6472
5269
  end
6473
5270
  table.insert(threadRows, row)
6474
5271
  end
6475
- table.sort(threadRows, function(a, b)
5272
+ cooperativeSort(threadRows, function(a, b)
6476
5273
  return (a.inclusive_us) > (b.inclusive_us)
6477
- end)
5274
+ end, checkpoint)
6478
5275
  local topThreads = {}
6479
5276
  do
6480
5277
  local i = 0
@@ -6495,15 +5292,16 @@ local function captureMicroProfiler(requestData)
6495
5292
  end
6496
5293
  local groupRows = {}
6497
5294
  for _, aggregate in groupAggregates do
5295
+ checkpoint()
6498
5296
  local _group = aggregate.group
6499
5297
  local _condition_5 = groupTimerRows[_group]
6500
5298
  if _condition_5 == nil then
6501
5299
  _condition_5 = {}
6502
5300
  end
6503
5301
  local timerRows = _condition_5
6504
- table.sort(timerRows, function(a, b)
5302
+ cooperativeSort(timerRows, function(a, b)
6505
5303
  return (a.inclusive_us) > (b.inclusive_us)
6506
- end)
5304
+ end, checkpoint)
6507
5305
  local topGroupTimers = {}
6508
5306
  do
6509
5307
  local i = 0
@@ -6546,7 +5344,7 @@ local function captureMicroProfiler(requestData)
6546
5344
  pct_of_analyzed_wall = percent(inclusiveUs, analysisDurationUs),
6547
5345
  }
6548
5346
  local _group_1 = aggregate.group
6549
- local groupFrameImpact = summarizeFrameImpact(groupFrameAggregates[_group_1], analysisFrameCount)
5347
+ local groupFrameImpact = summarizeFrameImpact(groupFrameAggregates[_group_1], analysisFrameCount, checkpoint)
6550
5348
  for key, value in pairs(groupFrameImpact) do
6551
5349
  groupRow[key] = value
6552
5350
  end
@@ -6555,9 +5353,9 @@ local function captureMicroProfiler(requestData)
6555
5353
  end
6556
5354
  table.insert(groupRows, groupRow)
6557
5355
  end
6558
- table.sort(groupRows, function(a, b)
5356
+ cooperativeSort(groupRows, function(a, b)
6559
5357
  return (a.inclusive_us) > (b.inclusive_us)
6560
- end)
5358
+ end, checkpoint)
6561
5359
  local topGroups = {}
6562
5360
  do
6563
5361
  local i = 0
@@ -6579,10 +5377,11 @@ local function captureMicroProfiler(requestData)
6579
5377
  local groupRowsByExclusive = {}
6580
5378
  for _, row in groupRows do
6581
5379
  table.insert(groupRowsByExclusive, row)
5380
+ checkpoint()
6582
5381
  end
6583
- table.sort(groupRowsByExclusive, function(a, b)
5382
+ cooperativeSort(groupRowsByExclusive, function(a, b)
6584
5383
  return (a.exclusive_us) > (b.exclusive_us)
6585
- end)
5384
+ end, checkpoint)
6586
5385
  local topGroupsByExclusive = {}
6587
5386
  do
6588
5387
  local i = 0
@@ -6614,6 +5413,7 @@ local function captureMicroProfiler(requestData)
6614
5413
  local openStackEntriesAtEnd = 0
6615
5414
  for _, stack in stacks do
6616
5415
  openStackEntriesAtEnd += #stack
5416
+ checkpoint()
6617
5417
  end
6618
5418
  local iteratorFinished = eventsSampled < maxEvents
6619
5419
  local partialReasons = {}
@@ -6636,21 +5436,25 @@ local function captureMicroProfiler(requestData)
6636
5436
  for _, row in rows do
6637
5437
  local _arg0 = pickFields(row, timerFields)
6638
5438
  table.insert(timerIndex, _arg0)
5439
+ checkpoint()
6639
5440
  end
6640
5441
  local groupIndex = {}
6641
5442
  for _, row in groupRows do
6642
5443
  local _arg0 = pickFields(row, groupFields)
6643
5444
  table.insert(groupIndex, _arg0)
5445
+ checkpoint()
6644
5446
  end
6645
5447
  local threadIndex = {}
6646
5448
  for _, row in threadRows do
6647
5449
  local _arg0 = pickFields(row, threadFields)
6648
5450
  table.insert(threadIndex, _arg0)
5451
+ checkpoint()
6649
5452
  end
6650
5453
  local edgeIndex = {}
6651
5454
  for _, row in edgeRows do
6652
5455
  local _arg0 = pickFields(row, edgeFields)
6653
5456
  table.insert(edgeIndex, _arg0)
5457
+ checkpoint()
6654
5458
  end
6655
5459
  comparisonIndex = {
6656
5460
  timers = timerIndex,
@@ -6779,7 +5583,7 @@ local function captureMicroProfiler(requestData)
6779
5583
  result.snapshot_object_size_bytes = session:GetObjSize()
6780
5584
  end
6781
5585
  if includeRawBuffer then
6782
- result.raw_snapshot_base64 = encodeBase64(snapshot)
5586
+ result.raw_snapshot_base64 = encodeBase64(snapshot, checkpoint)
6783
5587
  end
6784
5588
  iterator:Dispose()
6785
5589
  session:Dispose()
@@ -6791,7 +5595,7 @@ return {
6791
5595
  ]]></string>
6792
5596
  </Properties>
6793
5597
  </Item>
6794
- <Item class="ModuleScript" referent="19">
5598
+ <Item class="ModuleScript" referent="17">
6795
5599
  <Properties>
6796
5600
  <string name="Name">PropertyHandlers</string>
6797
5601
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -6804,170 +5608,6 @@ local convertPropertyValue = _binding.convertPropertyValue
6804
5608
  local _binding_1 = Recording
6805
5609
  local beginRecording = _binding_1.beginRecording
6806
5610
  local finishRecording = _binding_1.finishRecording
6807
- local function setProperty(requestData)
6808
- local instancePath = requestData.instancePath
6809
- local propertyName = requestData.propertyName
6810
- local propertyValue = requestData.propertyValue
6811
- if not (instancePath ~= "" and instancePath) or not (propertyName ~= "" and propertyName) then
6812
- return {
6813
- error = "Instance path and property name are required",
6814
- }
6815
- end
6816
- local instance = getInstanceByPath(instancePath)
6817
- if not instance then
6818
- return {
6819
- error = `Instance not found: {instancePath}`,
6820
- }
6821
- end
6822
- local recordingId = beginRecording(`Set {propertyName} property`)
6823
- local inst = instance
6824
- local success, result = pcall(function()
6825
- if propertyName == "Parent" or propertyName == "PrimaryPart" then
6826
- if type(propertyValue) == "string" then
6827
- local refInstance = getInstanceByPath(propertyValue)
6828
- if refInstance then
6829
- inst[propertyName] = refInstance
6830
- else
6831
- return {
6832
- error = `{propertyName} instance not found: {propertyValue}`,
6833
- }
6834
- end
6835
- end
6836
- elseif propertyName == "Name" then
6837
- instance.Name = tostring(propertyValue)
6838
- elseif propertyName == "Source" and instance:IsA("LuaSourceContainer") then
6839
- instance.Source = tostring(propertyValue)
6840
- else
6841
- local convertedValue = convertPropertyValue(instance, propertyName, propertyValue)
6842
- if convertedValue ~= nil then
6843
- inst[propertyName] = convertedValue
6844
- else
6845
- inst[propertyName] = propertyValue
6846
- end
6847
- end
6848
- return true
6849
- end)
6850
- if success then
6851
- finishRecording(recordingId, true)
6852
- return {
6853
- success = true,
6854
- instancePath = instancePath,
6855
- propertyName = propertyName,
6856
- propertyValue = propertyValue,
6857
- message = "Property set successfully",
6858
- }
6859
- else
6860
- finishRecording(recordingId, false)
6861
- return {
6862
- error = `Failed to set property: {result}`,
6863
- instancePath = instancePath,
6864
- propertyName = propertyName,
6865
- }
6866
- end
6867
- end
6868
- local function massSetProperty(requestData)
6869
- local paths = requestData.paths
6870
- local propertyName = requestData.propertyName
6871
- local propertyValue = requestData.propertyValue
6872
- if not paths or not (type(paths) == "table") or #paths == 0 or not (propertyName ~= "" and propertyName) then
6873
- return {
6874
- error = "Paths array and property name are required",
6875
- }
6876
- end
6877
- local results = {}
6878
- local successCount = 0
6879
- local failureCount = 0
6880
- local recordingId = beginRecording(`Mass set {propertyName} property`)
6881
- for _, path in paths do
6882
- local instance = getInstanceByPath(path)
6883
- if instance then
6884
- local success, err = pcall(function()
6885
- local converted = convertPropertyValue(instance, propertyName, propertyValue)
6886
- instance[propertyName] = if converted ~= nil then converted else propertyValue
6887
- end)
6888
- if success then
6889
- successCount += 1
6890
- local _arg0 = {
6891
- path = path,
6892
- success = true,
6893
- propertyName = propertyName,
6894
- propertyValue = propertyValue,
6895
- }
6896
- table.insert(results, _arg0)
6897
- else
6898
- failureCount += 1
6899
- local _arg0 = {
6900
- path = path,
6901
- success = false,
6902
- error = tostring(err),
6903
- }
6904
- table.insert(results, _arg0)
6905
- end
6906
- else
6907
- failureCount += 1
6908
- local _arg0 = {
6909
- path = path,
6910
- success = false,
6911
- error = "Instance not found",
6912
- }
6913
- table.insert(results, _arg0)
6914
- end
6915
- end
6916
- finishRecording(recordingId, successCount > 0)
6917
- return {
6918
- results = results,
6919
- summary = {
6920
- total = #paths,
6921
- succeeded = successCount,
6922
- failed = failureCount,
6923
- },
6924
- }
6925
- end
6926
- local function massGetProperty(requestData)
6927
- local paths = requestData.paths
6928
- local propertyName = requestData.propertyName
6929
- if not paths or not (type(paths) == "table") or #paths == 0 or not (propertyName ~= "" and propertyName) then
6930
- return {
6931
- error = "Paths array and property name are required",
6932
- }
6933
- end
6934
- local results = {}
6935
- for _, path in paths do
6936
- local instance = getInstanceByPath(path)
6937
- if instance then
6938
- local success, value = pcall(function()
6939
- return instance[propertyName]
6940
- end)
6941
- if success then
6942
- local _arg0 = {
6943
- path = path,
6944
- success = true,
6945
- propertyName = propertyName,
6946
- propertyValue = value,
6947
- }
6948
- table.insert(results, _arg0)
6949
- else
6950
- local _arg0 = {
6951
- path = path,
6952
- success = false,
6953
- error = tostring(value),
6954
- }
6955
- table.insert(results, _arg0)
6956
- end
6957
- else
6958
- local _arg0 = {
6959
- path = path,
6960
- success = false,
6961
- error = "Instance not found",
6962
- }
6963
- table.insert(results, _arg0)
6964
- end
6965
- end
6966
- return {
6967
- results = results,
6968
- propertyName = propertyName,
6969
- }
6970
- end
6971
5611
  local function setProperties(requestData)
6972
5612
  local instancePath = requestData.instancePath
6973
5613
  local properties = requestData.properties
@@ -7035,15 +5675,12 @@ local function setProperties(requestData)
7035
5675
  }
7036
5676
  end
7037
5677
  return {
7038
- setProperty = setProperty,
7039
- massSetProperty = massSetProperty,
7040
- massGetProperty = massGetProperty,
7041
5678
  setProperties = setProperties,
7042
5679
  }
7043
5680
  ]]></string>
7044
5681
  </Properties>
7045
5682
  </Item>
7046
- <Item class="ModuleScript" referent="20">
5683
+ <Item class="ModuleScript" referent="18">
7047
5684
  <Properties>
7048
5685
  <string name="Name">QueryHandlers</string>
7049
5686
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -7177,48 +5814,6 @@ local function getPlaceInfo(_requestData)
7177
5814
  },
7178
5815
  }
7179
5816
  end
7180
- local function getServices(requestData)
7181
- local serviceName = requestData.serviceName
7182
- if serviceName ~= "" and serviceName then
7183
- local ok, service = pcall(function()
7184
- return game:GetService(serviceName)
7185
- end)
7186
- if ok and service then
7187
- return {
7188
- service = {
7189
- name = service.Name,
7190
- className = service.ClassName,
7191
- path = getInstancePath(service),
7192
- childCount = #service:GetChildren(),
7193
- },
7194
- }
7195
- else
7196
- return {
7197
- error = `Service not found: {serviceName}`,
7198
- }
7199
- end
7200
- else
7201
- local services = {}
7202
- local commonServices = { "Workspace", "Players", "StarterGui", "StarterPack", "StarterPlayer", "ReplicatedStorage", "ServerStorage", "ServerScriptService", "HttpService", "TeleportService", "DataStoreService" }
7203
- for _, svcName in commonServices do
7204
- local ok, service = pcall(function()
7205
- return game:GetService(svcName)
7206
- end)
7207
- if ok and service then
7208
- local _arg0 = {
7209
- name = service.Name,
7210
- className = service.ClassName,
7211
- path = getInstancePath(service),
7212
- childCount = #service:GetChildren(),
7213
- }
7214
- table.insert(services, _arg0)
7215
- end
7216
- end
7217
- return {
7218
- services = services,
7219
- }
7220
- end
7221
- end
7222
5817
  local function searchObjects(requestData)
7223
5818
  local query = requestData.query
7224
5819
  local _condition = (requestData.searchType)
@@ -7397,39 +5992,6 @@ local function getInstanceProperties(requestData)
7397
5992
  }
7398
5993
  end
7399
5994
  end
7400
- local function getInstanceChildren(requestData)
7401
- local instancePath = requestData.instancePath
7402
- if not (instancePath ~= "" and instancePath) then
7403
- return {
7404
- error = "Instance path is required",
7405
- }
7406
- end
7407
- local instance = getInstanceByPath(instancePath)
7408
- if not instance then
7409
- return {
7410
- error = `Instance not found: {instancePath}`,
7411
- }
7412
- end
7413
- local children = {}
7414
- for _, child in instance:GetChildren() do
7415
- local entry = {
7416
- name = child.Name,
7417
- className = child.ClassName,
7418
- path = getInstancePath(child),
7419
- hasChildren = #child:GetChildren() > 0,
7420
- hasSource = child:IsA("LuaSourceContainer"),
7421
- }
7422
- if child:IsA("BaseScript") then
7423
- entry.enabled = child.Enabled
7424
- end
7425
- table.insert(children, entry)
7426
- end
7427
- return {
7428
- instancePath = instancePath,
7429
- children = children,
7430
- count = #children,
7431
- }
7432
- end
7433
5995
  local function searchByProperty(requestData)
7434
5996
  local propertyName = requestData.propertyName
7435
5997
  local propertyValue = requestData.propertyValue
@@ -7974,133 +6536,21 @@ local function grepScripts(requestData)
7974
6536
  },
7975
6537
  }
7976
6538
  end
7977
- local function getDescendants(requestData)
7978
- local instancePath = requestData.instancePath
7979
- if not (instancePath ~= "" and instancePath) then
7980
- return {
7981
- error = "Instance path is required",
7982
- }
7983
- end
7984
- local _condition = (requestData.maxDepth)
7985
- if _condition == nil then
7986
- _condition = 10
7987
- end
7988
- local maxDepth = _condition
7989
- local classFilter = requestData.classFilter
7990
- local instance = getInstanceByPath(instancePath)
7991
- if not instance then
7992
- return {
7993
- error = `Instance not found: {instancePath}`,
7994
- }
7995
- end
7996
- local descendants = {}
7997
- local function collect(inst, depth)
7998
- if depth > maxDepth then
7999
- return nil
8000
- end
8001
- for _, child in inst:GetChildren() do
8002
- local _condition_1 = classFilter
8003
- if _condition_1 ~= "" and _condition_1 then
8004
- _condition_1 = not child:IsA(classFilter)
8005
- end
8006
- if _condition_1 ~= "" and _condition_1 then
8007
- continue
8008
- end
8009
- local _arg0 = {
8010
- name = child.Name,
8011
- className = child.ClassName,
8012
- path = getInstancePath(child),
8013
- depth = depth,
8014
- }
8015
- table.insert(descendants, _arg0)
8016
- collect(child, depth + 1)
8017
- end
8018
- end
8019
- collect(instance, 1)
8020
- return {
8021
- instancePath = instancePath,
8022
- descendants = descendants,
8023
- count = #descendants,
8024
- maxDepth = maxDepth,
8025
- }
8026
- end
8027
- local function compareInstances(requestData)
8028
- local instancePathA = requestData.instancePathA
8029
- local instancePathB = requestData.instancePathB
8030
- if not (instancePathA ~= "" and instancePathA) or not (instancePathB ~= "" and instancePathB) then
8031
- return {
8032
- error = "Both instancePathA and instancePathB are required",
8033
- }
8034
- end
8035
- local instA = getInstanceByPath(instancePathA)
8036
- if not instA then
8037
- return {
8038
- error = `Instance not found: {instancePathA}`,
8039
- }
8040
- end
8041
- local instB = getInstanceByPath(instancePathB)
8042
- if not instB then
8043
- return {
8044
- error = `Instance not found: {instancePathB}`,
8045
- }
8046
- end
8047
- local commonProps = { "Name", "ClassName", "Size", "Position", "Rotation", "CFrame", "Anchored", "CanCollide", "Transparency", "BrickColor", "Material", "Color", "Text", "TextColor3", "BackgroundColor3", "Image", "ImageColor3", "Visible", "Active", "ZIndex", "BorderSizePixel", "BackgroundTransparency", "ImageTransparency", "TextTransparency", "Value", "Enabled", "Brightness", "Range", "Shadows" }
8048
- local matching = {}
8049
- local differing = {}
8050
- local onlyA = {}
8051
- local onlyB = {}
8052
- for _, prop in commonProps do
8053
- local okA, valA = pcall(function()
8054
- return tostring(instA[prop])
8055
- end)
8056
- local okB, valB = pcall(function()
8057
- return tostring(instB[prop])
8058
- end)
8059
- if okA and okB then
8060
- if valA == valB then
8061
- matching[prop] = valA
8062
- else
8063
- differing[prop] = {
8064
- a = valA,
8065
- b = valB,
8066
- }
8067
- end
8068
- elseif okA then
8069
- table.insert(onlyA, prop)
8070
- elseif okB then
8071
- table.insert(onlyB, prop)
8072
- end
8073
- end
8074
- return {
8075
- instancePathA = instancePathA,
8076
- instancePathB = instancePathB,
8077
- classNameA = instA.ClassName,
8078
- classNameB = instB.ClassName,
8079
- matching = matching,
8080
- differing = differing,
8081
- onlyA = onlyA,
8082
- onlyB = onlyB,
8083
- }
8084
- end
8085
6539
  return {
8086
6540
  getFileTree = getFileTree,
8087
6541
  searchFiles = searchFiles,
8088
6542
  getPlaceInfo = getPlaceInfo,
8089
- getServices = getServices,
8090
6543
  searchObjects = searchObjects,
8091
6544
  getInstanceProperties = getInstanceProperties,
8092
- getInstanceChildren = getInstanceChildren,
8093
6545
  searchByProperty = searchByProperty,
8094
6546
  getClassInfo = getClassInfo,
8095
6547
  getProjectStructure = getProjectStructure,
8096
6548
  grepScripts = grepScripts,
8097
- getDescendants = getDescendants,
8098
- compareInstances = compareInstances,
8099
6549
  }
8100
6550
  ]]></string>
8101
6551
  </Properties>
8102
6552
  </Item>
8103
- <Item class="ModuleScript" referent="21">
6553
+ <Item class="ModuleScript" referent="19">
8104
6554
  <Properties>
8105
6555
  <string name="Name">SceneAnalysisHandlers</string>
8106
6556
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -8349,18 +6799,18 @@ return {
8349
6799
  ]]></string>
8350
6800
  </Properties>
8351
6801
  </Item>
8352
- <Item class="ModuleScript" referent="22">
6802
+ <Item class="ModuleScript" referent="20">
8353
6803
  <Properties>
8354
6804
  <string name="Name">ScriptHandlers</string>
8355
6805
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
8356
6806
  local TS = require(script.Parent.Parent.Parent.include.RuntimeLib)
8357
6807
  local Utils = TS.import(script, script.Parent.Parent, "Utils")
8358
6808
  local Recording = TS.import(script, script.Parent.Parent, "Recording")
8359
- local ScriptEditorService = game:GetService("ScriptEditorService")
8360
6809
  local _binding = Utils
8361
6810
  local getInstancePath = _binding.getInstancePath
8362
6811
  local getInstanceByPath = _binding.getInstanceByPath
8363
6812
  local readScriptSource = _binding.readScriptSource
6813
+ local applyScriptSource = _binding.applyScriptSource
8364
6814
  local splitLines = _binding.splitLines
8365
6815
  local joinLines = _binding.joinLines
8366
6816
  local _binding_1 = Recording
@@ -8506,7 +6956,7 @@ end
8506
6956
  local function setScriptSource(requestData)
8507
6957
  local instancePath = requestData.instancePath
8508
6958
  local newSource = requestData.source
8509
- if not (instancePath ~= "" and instancePath) or not (newSource ~= "" and newSource) then
6959
+ if not (instancePath ~= "" and instancePath) or not (type(newSource) == "string") then
8510
6960
  return {
8511
6961
  error = "Instance path and source are required",
8512
6962
  }
@@ -8524,42 +6974,27 @@ local function setScriptSource(requestData)
8524
6974
  end
8525
6975
  local sourceToSet = normalizeEscapes(newSource)
8526
6976
  local recordingId = beginRecording(`Set script source: {instance.Name}`)
8527
- local updateSuccess, updateResult = pcall(function()
8528
- local oldSourceLength = #readScriptSource(instance)
8529
- ScriptEditorService:UpdateSourceAsync(instance, function()
8530
- return sourceToSet
8531
- end)
8532
- if readScriptSource(instance) ~= sourceToSet then
8533
- error("UpdateSourceAsync completed without updating the script source")
8534
- end
6977
+ local readSuccess, readResult = pcall(function()
6978
+ return #readScriptSource(instance)
6979
+ end)
6980
+ if not readSuccess then
6981
+ finishRecording(recordingId, false)
8535
6982
  return {
8536
- success = true,
8537
- instancePath = instancePath,
8538
- oldSourceLength = oldSourceLength,
8539
- newSourceLength = #sourceToSet,
8540
- method = "UpdateSourceAsync",
8541
- message = "Script source updated successfully (editor-safe)",
6983
+ error = `Failed to read script source before updating: {readResult}`,
8542
6984
  }
8543
- end)
8544
- if updateSuccess then
8545
- finishRecording(recordingId, true)
8546
- return updateResult
8547
6985
  end
8548
- local directSuccess, directResult = pcall(function()
8549
- local oldSource = instance.Source
8550
- instance.Source = sourceToSet
6986
+ local oldSourceLength = readResult
6987
+ local applyResult = applyScriptSource(instance, sourceToSet)
6988
+ if applyResult.success then
6989
+ finishRecording(recordingId, true)
8551
6990
  return {
8552
6991
  success = true,
8553
6992
  instancePath = instancePath,
8554
- oldSourceLength = #oldSource,
6993
+ oldSourceLength = oldSourceLength,
8555
6994
  newSourceLength = #sourceToSet,
8556
- method = "direct",
8557
- message = "Script source updated successfully (direct assignment)",
6995
+ method = applyResult.method,
6996
+ message = `Script source updated successfully ({if applyResult.method == "UpdateSourceAsync" then "editor-safe" else "direct assignment"})`,
8558
6997
  }
8559
- end)
8560
- if directSuccess then
8561
- finishRecording(recordingId, true)
8562
- return directResult
8563
6998
  end
8564
6999
  local replaceSuccess, replaceResult = pcall(function()
8565
7000
  local parent = instance.Parent
@@ -8569,9 +7004,15 @@ local function setScriptSource(requestData)
8569
7004
  local enabled = if wasBaseScript then instance.Enabled else nil
8570
7005
  local newScript = Instance.new(className)
8571
7006
  newScript.Name = name
8572
- newScript.Source = sourceToSet
7007
+ -- @rbxts/types does not expose PluginSecurity Source writes.
7008
+ local writableNewScript = newScript
7009
+ writableNewScript.Source = sourceToSet
7010
+ if readScriptSource(newScript) ~= sourceToSet then
7011
+ error("Replacement script source did not match the requested source")
7012
+ end
8573
7013
  if wasBaseScript and enabled ~= nil then
8574
- newScript.Enabled = enabled
7014
+ local newBaseScript = newScript
7015
+ newBaseScript.Enabled = enabled
8575
7016
  end
8576
7017
  newScript.Parent = parent
8577
7018
  instance:Destroy()
@@ -8588,7 +7029,7 @@ local function setScriptSource(requestData)
8588
7029
  end
8589
7030
  finishRecording(recordingId, false)
8590
7031
  return {
8591
- error = `Failed to set script source. UpdateSourceAsync failed: {updateResult}. Direct assignment failed: {directResult}. Replace method failed: {replaceResult}`,
7032
+ error = `Failed to set script source. {applyResult.error} Replace method failed: {replaceResult}`,
8592
7033
  }
8593
7034
  end
8594
7035
  local function editScriptLines(requestData)
@@ -8666,12 +7107,14 @@ local function editScriptLines(requestData)
8666
7107
  end
8667
7108
  -- Byte-slice replacement avoids Lua pattern escaping (safe for multi-byte chars like em dashes).
8668
7109
  local newSource = string.sub(source, 1, matchStart - 1) .. newString .. string.sub(source, matchStart + searchLen)
8669
- ScriptEditorService:UpdateSourceAsync(instance, function()
8670
- return newSource
8671
- end)
7110
+ local applyResult = applyScriptSource(instance, newSource, source)
7111
+ if not applyResult.success then
7112
+ error(applyResult.error)
7113
+ end
8672
7114
  return {
8673
7115
  success = true,
8674
7116
  instancePath = instancePath,
7117
+ method = applyResult.method,
8675
7118
  message = "Script edited successfully",
8676
7119
  }
8677
7120
  end)
@@ -8711,7 +7154,8 @@ local function insertScriptLines(requestData)
8711
7154
  end
8712
7155
  local recordingId = beginRecording(`Insert script lines after line {afterLine}: {instance.Name}`)
8713
7156
  local success, result = pcall(function()
8714
- local lines, hadTrailingNewline = splitLines(readScriptSource(instance))
7157
+ local source = readScriptSource(instance)
7158
+ local lines, hadTrailingNewline = splitLines(source)
8715
7159
  local totalLines = #lines
8716
7160
  if afterLine < 0 or afterLine > totalLines then
8717
7161
  error(`afterLine out of range (0-{totalLines})`)
@@ -8754,15 +7198,17 @@ local function insertScriptLines(requestData)
8754
7198
  end
8755
7199
  end
8756
7200
  local newSource = joinLines(resultLines, hadTrailingNewline)
8757
- ScriptEditorService:UpdateSourceAsync(instance, function()
8758
- return newSource
8759
- end)
7201
+ local applyResult = applyScriptSource(instance, newSource, source)
7202
+ if not applyResult.success then
7203
+ error(applyResult.error)
7204
+ end
8760
7205
  return {
8761
7206
  success = true,
8762
7207
  instancePath = instancePath,
8763
7208
  insertedAfterLine = afterLine,
8764
7209
  linesInserted = #newLines,
8765
7210
  newLineCount = #resultLines,
7211
+ method = applyResult.method,
8766
7212
  message = "Script lines inserted successfully",
8767
7213
  }
8768
7214
  end)
@@ -8797,7 +7243,8 @@ local function deleteScriptLines(requestData)
8797
7243
  end
8798
7244
  local recordingId = beginRecording(`Delete script lines {startLine}-{endLine}: {instance.Name}`)
8799
7245
  local success, result = pcall(function()
8800
- local lines, hadTrailingNewline = splitLines(readScriptSource(instance))
7246
+ local source = readScriptSource(instance)
7247
+ local lines, hadTrailingNewline = splitLines(source)
8801
7248
  local totalLines = #lines
8802
7249
  if startLine < 1 or startLine > totalLines then
8803
7250
  error(`startLine out of range (1-{totalLines})`)
@@ -8839,9 +7286,10 @@ local function deleteScriptLines(requestData)
8839
7286
  end
8840
7287
  end
8841
7288
  local newSource = joinLines(resultLines, hadTrailingNewline)
8842
- ScriptEditorService:UpdateSourceAsync(instance, function()
8843
- return newSource
8844
- end)
7289
+ local applyResult = applyScriptSource(instance, newSource, source)
7290
+ if not applyResult.success then
7291
+ error(applyResult.error)
7292
+ end
8845
7293
  return {
8846
7294
  success = true,
8847
7295
  instancePath = instancePath,
@@ -8851,6 +7299,7 @@ local function deleteScriptLines(requestData)
8851
7299
  },
8852
7300
  linesDeleted = endLine - startLine + 1,
8853
7301
  newLineCount = #resultLines,
7302
+ method = applyResult.method,
8854
7303
  message = "Script lines deleted successfully",
8855
7304
  }
8856
7305
  end)
@@ -8953,17 +7402,14 @@ local function findAndReplaceInScripts(requestData)
8953
7402
  if hitLimit then
8954
7403
  return nil
8955
7404
  end
8956
- if instance:IsA("LuaSourceContainer") then
8957
- local _condition_5 = classFilter
8958
- if _condition_5 ~= "" and _condition_5 then
8959
- local _exp = string.lower(instance.ClassName)
8960
- local _arg0 = string.lower(classFilter)
8961
- local _value = (string.find(_exp, _arg0))
8962
- _condition_5 = not (_value ~= 0 and _value == _value and _value)
8963
- end
8964
- if _condition_5 ~= "" and _condition_5 then
8965
- return nil
8966
- end
7405
+ local _condition_5 = classFilter == nil
7406
+ if not _condition_5 then
7407
+ local _exp = string.lower(instance.ClassName)
7408
+ local _arg0 = string.lower(classFilter)
7409
+ _condition_5 = (string.find(_exp, _arg0)) ~= nil
7410
+ end
7411
+ local matchesClass = _condition_5
7412
+ if instance:IsA("LuaSourceContainer") and matchesClass then
8967
7413
  scriptsSearched += 1
8968
7414
  local source = readScriptSource(instance)
8969
7415
  local newSource
@@ -8988,24 +7434,31 @@ local function findAndReplaceInScripts(requestData)
8988
7434
  hitLimit = true
8989
7435
  return nil
8990
7436
  end
8991
- totalReplacements += replCount
8992
- if not dryRun then
8993
- local ok = pcall(function()
8994
- ScriptEditorService:UpdateSourceAsync(instance, function()
8995
- return newSource
8996
- end)
8997
- end)
8998
- if not ok then
8999
- instance.Source = newSource
7437
+ local applyResult = if dryRun then nil else applyScriptSource(instance, newSource, source)
7438
+ if applyResult ~= nil and not applyResult.success then
7439
+ local _object = {
7440
+ instancePath = getInstancePath(instance),
7441
+ name = instance.Name,
7442
+ className = instance.ClassName,
7443
+ replacements = 0,
7444
+ }
7445
+ local _left = "error"
7446
+ local _condition_6 = applyResult.error
7447
+ if _condition_6 == nil then
7448
+ _condition_6 = "Script write failed verification"
9000
7449
  end
7450
+ _object[_left] = _condition_6
7451
+ table.insert(changes, _object)
7452
+ else
7453
+ totalReplacements += replCount
7454
+ local _arg0 = {
7455
+ instancePath = getInstancePath(instance),
7456
+ name = instance.Name,
7457
+ className = instance.ClassName,
7458
+ replacements = replCount,
7459
+ }
7460
+ table.insert(changes, _arg0)
9001
7461
  end
9002
- local _arg0 = {
9003
- instancePath = getInstancePath(instance),
9004
- name = instance.Name,
9005
- className = instance.ClassName,
9006
- replacements = replCount,
9007
- }
9008
- table.insert(changes, _arg0)
9009
7462
  end
9010
7463
  end
9011
7464
  for _, child in instance:GetChildren() do
@@ -9015,18 +7468,37 @@ local function findAndReplaceInScripts(requestData)
9015
7468
  processInstance(child)
9016
7469
  end
9017
7470
  end
9018
- processInstance(startInstance)
7471
+ local traversalSuccess, traversalResult = pcall(function()
7472
+ return processInstance(startInstance)
7473
+ end)
7474
+ -- ▼ ReadonlyArray.filter ▼
7475
+ local _newValue = {}
7476
+ local _callback = function(change)
7477
+ return change.error ~= nil
7478
+ end
7479
+ local _length = 0
7480
+ for _k, _v in changes do
7481
+ if _callback(_v, _k - 1, changes) == true then
7482
+ _length += 1
7483
+ _newValue[_length] = _v
7484
+ end
7485
+ end
7486
+ -- ▲ ReadonlyArray.filter ▲
7487
+ local failedScripts = #_newValue
7488
+ local scriptsModified = #changes - failedScripts
9019
7489
  if recordingId ~= nil then
9020
- finishRecording(recordingId, #changes > 0)
7490
+ finishRecording(recordingId, scriptsModified > 0)
9021
7491
  end
9022
7492
  return {
9023
- success = true,
7493
+ success = traversalSuccess and failedScripts == 0,
7494
+ error = if traversalSuccess then nil else `Script traversal failed: {traversalResult}`,
9024
7495
  dryRun = dryRun,
9025
7496
  pattern = searchPattern,
9026
7497
  replacement = replacement,
9027
7498
  totalReplacements = totalReplacements,
9028
7499
  scriptsSearched = scriptsSearched,
9029
- scriptsModified = #changes,
7500
+ scriptsModified = scriptsModified,
7501
+ scriptsFailed = failedScripts,
9030
7502
  changes = changes,
9031
7503
  truncated = hitLimit,
9032
7504
  }
@@ -9042,7 +7514,7 @@ return {
9042
7514
  ]]></string>
9043
7515
  </Properties>
9044
7516
  </Item>
9045
- <Item class="ModuleScript" referent="23">
7517
+ <Item class="ModuleScript" referent="21">
9046
7518
  <Properties>
9047
7519
  <string name="Name">ScriptProfilerHandlers</string>
9048
7520
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -9505,7 +7977,7 @@ return {
9505
7977
  ]]></string>
9506
7978
  </Properties>
9507
7979
  </Item>
9508
- <Item class="ModuleScript" referent="24">
7980
+ <Item class="ModuleScript" referent="22">
9509
7981
  <Properties>
9510
7982
  <string name="Name">SerializationHandlers</string>
9511
7983
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -9691,7 +8163,7 @@ return {
9691
8163
  ]]></string>
9692
8164
  </Properties>
9693
8165
  </Item>
9694
- <Item class="ModuleScript" referent="25">
8166
+ <Item class="ModuleScript" referent="23">
9695
8167
  <Properties>
9696
8168
  <string name="Name">TestHandlers</string>
9697
8169
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -10070,7 +8542,7 @@ return {
10070
8542
  </Properties>
10071
8543
  </Item>
10072
8544
  </Item>
10073
- <Item class="ModuleScript" referent="26">
8545
+ <Item class="ModuleScript" referent="24">
10074
8546
  <Properties>
10075
8547
  <string name="Name">HttpDiagnostics</string>
10076
8548
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -10151,7 +8623,7 @@ return {
10151
8623
  ]]></string>
10152
8624
  </Properties>
10153
8625
  </Item>
10154
- <Item class="ModuleScript" referent="27">
8626
+ <Item class="ModuleScript" referent="25">
10155
8627
  <Properties>
10156
8628
  <string name="Name">LuauExec</string>
10157
8629
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -10585,7 +9057,7 @@ return {
10585
9057
  ]]></string>
10586
9058
  </Properties>
10587
9059
  </Item>
10588
- <Item class="ModuleScript" referent="28">
9060
+ <Item class="ModuleScript" referent="26">
10589
9061
  <Properties>
10590
9062
  <string name="Name">Recording</string>
10591
9063
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -10615,7 +9087,7 @@ return {
10615
9087
  ]]></string>
10616
9088
  </Properties>
10617
9089
  </Item>
10618
- <Item class="ModuleScript" referent="29">
9090
+ <Item class="ModuleScript" referent="27">
10619
9091
  <Properties>
10620
9092
  <string name="Name">RenderMonitor</string>
10621
9093
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -10683,7 +9155,7 @@ return {
10683
9155
  ]]></string>
10684
9156
  </Properties>
10685
9157
  </Item>
10686
- <Item class="ModuleScript" referent="30">
9158
+ <Item class="ModuleScript" referent="28">
10687
9159
  <Properties>
10688
9160
  <string name="Name">RuntimeLogBuffer</string>
10689
9161
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -10938,7 +9410,7 @@ return {
10938
9410
  ]]></string>
10939
9411
  </Properties>
10940
9412
  </Item>
10941
- <Item class="ModuleScript" referent="31">
9413
+ <Item class="ModuleScript" referent="29">
10942
9414
  <Properties>
10943
9415
  <string name="Name">ServerUrlSettings</string>
10944
9416
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -11088,11 +9560,11 @@ return {
11088
9560
  ]]></string>
11089
9561
  </Properties>
11090
9562
  </Item>
11091
- <Item class="ModuleScript" referent="32">
9563
+ <Item class="ModuleScript" referent="30">
11092
9564
  <Properties>
11093
9565
  <string name="Name">State</string>
11094
9566
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
11095
- local CURRENT_VERSION = "2.23.1"
9567
+ local CURRENT_VERSION = "3.0.1"
11096
9568
  local PLUGIN_VARIANT = "main"
11097
9569
  local BASE_PORT = 58741
11098
9570
  local function createConnection(port)
@@ -11128,7 +9600,7 @@ return {
11128
9600
  ]]></string>
11129
9601
  </Properties>
11130
9602
  </Item>
11131
- <Item class="ModuleScript" referent="33">
9603
+ <Item class="ModuleScript" referent="31">
11132
9604
  <Properties>
11133
9605
  <string name="Name">StopPlayMonitor</string>
11134
9606
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -11426,7 +9898,7 @@ return {
11426
9898
  ]]></string>
11427
9899
  </Properties>
11428
9900
  </Item>
11429
- <Item class="ModuleScript" referent="34">
9901
+ <Item class="ModuleScript" referent="32">
11430
9902
  <Properties>
11431
9903
  <string name="Name">UI</string>
11432
9904
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -11978,7 +10450,7 @@ return {
11978
10450
  ]]></string>
11979
10451
  </Properties>
11980
10452
  </Item>
11981
- <Item class="ModuleScript" referent="35">
10453
+ <Item class="ModuleScript" referent="33">
11982
10454
  <Properties>
11983
10455
  <string name="Name">Utils</string>
11984
10456
  <string name="Source"><![CDATA[-- Compiled with roblox-ts v3.0.0
@@ -12298,11 +10770,62 @@ local function readScriptSource(instance)
12298
10770
  end
12299
10771
  return nil
12300
10772
  end)
12301
- local _value = ok and result
12302
- if _value ~= "" and _value then
10773
+ if ok and result ~= nil then
12303
10774
  return result
12304
10775
  end
12305
- return instance.Source
10776
+ -- @rbxts/types does not expose PluginSecurity Source reads.
10777
+ local readableScript = instance
10778
+ return readableScript.Source
10779
+ end
10780
+ --[[
10781
+ *
10782
+ * Writes newSource to instance and verifies the live editor text before
10783
+ * reporting success. expectedSource turns line-based edits into a
10784
+ * compare-and-set operation: if the document changes while
10785
+ * UpdateSourceAsync yields or before the direct fallback runs, the write is
10786
+ * rejected instead of overwriting the newer draft.
10787
+
10788
+ ]]
10789
+ local function applyScriptSource(instance, newSource, expectedSource)
10790
+ local updateSuccess, updateResult = pcall(function()
10791
+ ScriptEditorService:UpdateSourceAsync(instance, function(currentSource)
10792
+ if expectedSource ~= nil and currentSource ~= expectedSource then
10793
+ error("Script source changed while the edit was being applied; read the script again and retry")
10794
+ end
10795
+ return newSource
10796
+ end)
10797
+ if readScriptSource(instance) ~= newSource then
10798
+ error("UpdateSourceAsync completed without updating the live script source")
10799
+ end
10800
+ end)
10801
+ if updateSuccess then
10802
+ return {
10803
+ success = true,
10804
+ method = "UpdateSourceAsync",
10805
+ }
10806
+ end
10807
+ local directSuccess, directResult = pcall(function()
10808
+ if expectedSource ~= nil and readScriptSource(instance) ~= expectedSource then
10809
+ error("Script source changed before direct assignment; read the script again and retry")
10810
+ end
10811
+ -- @rbxts/types does not expose PluginSecurity Source writes.
10812
+ local writableScript = instance
10813
+ writableScript.Source = newSource
10814
+ if readScriptSource(instance) ~= newSource then
10815
+ error("Direct assignment completed without updating the live script source")
10816
+ end
10817
+ end)
10818
+ if directSuccess then
10819
+ return {
10820
+ success = true,
10821
+ method = "direct",
10822
+ }
10823
+ end
10824
+ return {
10825
+ success = false,
10826
+ method = "direct",
10827
+ error = `UpdateSourceAsync failed: {updateResult}. Direct assignment failed: {directResult}`,
10828
+ }
12306
10829
  end
12307
10830
  local function convertPropertyValue(instance, propertyName, propertyValue)
12308
10831
  if propertyValue == nil then
@@ -12763,6 +11286,7 @@ return {
12763
11286
  splitLines = splitLines,
12764
11287
  joinLines = joinLines,
12765
11288
  readScriptSource = readScriptSource,
11289
+ applyScriptSource = applyScriptSource,
12766
11290
  convertPropertyValue = convertPropertyValue,
12767
11291
  evaluateFormula = evaluateFormula,
12768
11292
  compareVersions = compareVersions,
@@ -12771,11 +11295,11 @@ return {
12771
11295
  </Properties>
12772
11296
  </Item>
12773
11297
  </Item>
12774
- <Item class="Folder" referent="40">
11298
+ <Item class="Folder" referent="38">
12775
11299
  <Properties>
12776
11300
  <string name="Name">include</string>
12777
11301
  </Properties>
12778
- <Item class="ModuleScript" referent="36">
11302
+ <Item class="ModuleScript" referent="34">
12779
11303
  <Properties>
12780
11304
  <string name="Name">LibMP</string>
12781
11305
  <string name="Source"><![CDATA[-- =============================================================================
@@ -169159,7 +167683,7 @@ return LibMP
169159
167683
  ]]></string>
169160
167684
  </Properties>
169161
167685
  </Item>
169162
- <Item class="ModuleScript" referent="37">
167686
+ <Item class="ModuleScript" referent="35">
169163
167687
  <Properties>
169164
167688
  <string name="Name">Promise</string>
169165
167689
  <string name="Source"><![CDATA[--[[
@@ -171233,7 +169757,7 @@ return Promise
171233
169757
  ]]></string>
171234
169758
  </Properties>
171235
169759
  </Item>
171236
- <Item class="ModuleScript" referent="38">
169760
+ <Item class="ModuleScript" referent="36">
171237
169761
  <Properties>
171238
169762
  <string name="Name">RuntimeLib</string>
171239
169763
  <string name="Source"><![CDATA[local Promise = require(script.Parent.Promise)
@@ -171500,15 +170024,15 @@ return TS
171500
170024
  </Properties>
171501
170025
  </Item>
171502
170026
  </Item>
171503
- <Item class="Folder" referent="41">
170027
+ <Item class="Folder" referent="39">
171504
170028
  <Properties>
171505
170029
  <string name="Name">node_modules</string>
171506
170030
  </Properties>
171507
- <Item class="Folder" referent="42">
170031
+ <Item class="Folder" referent="40">
171508
170032
  <Properties>
171509
170033
  <string name="Name">@rbxts</string>
171510
170034
  </Properties>
171511
- <Item class="ModuleScript" referent="39">
170035
+ <Item class="ModuleScript" referent="37">
171512
170036
  <Properties>
171513
170037
  <string name="Name">services</string>
171514
170038
  <string name="Source"><![CDATA[return setmetatable({}, {