dominus-cli 2.2.1 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -0
- package/README.md +32 -3
- package/dist/bridge-daemon.js.map +1 -1
- package/dist/install-plugin.js.map +1 -1
- package/dist/mcp.js +658 -20
- package/dist/mcp.js.map +1 -1
- package/package.json +5 -5
- package/plugin/Dominus.rbxmx +1064 -12
- package/plugin/src/Building.lua +427 -0
- package/plugin/src/CommandRouter.lua +41 -0
- package/plugin/src/Metadata.lua +411 -0
- package/plugin/src/Mutation.lua +45 -0
- package/plugin/src/ValueCodec.lua +116 -0
package/dist/mcp.js
CHANGED
|
@@ -109,6 +109,19 @@ var CliMsg = {
|
|
|
109
109
|
V2_READ_SCRIPT: "studio:v2:read_script",
|
|
110
110
|
V2_UPDATE_SCRIPT: "studio:v2:update_script",
|
|
111
111
|
V2_APPLY: "studio:v2:apply",
|
|
112
|
+
V2_CREATE_PARTS: "studio:v2:create_parts",
|
|
113
|
+
V2_CLONE_INSTANCES: "studio:v2:clone_instances",
|
|
114
|
+
V2_GROUP_INSTANCES: "studio:v2:group_instances",
|
|
115
|
+
V2_UNGROUP_INSTANCES: "studio:v2:ungroup_instances",
|
|
116
|
+
V2_TRANSFORM_INSTANCES: "studio:v2:transform_instances",
|
|
117
|
+
V2_CREATE_WELDS: "studio:v2:create_welds",
|
|
118
|
+
V2_QUERY_PARTS: "studio:v2:query_parts",
|
|
119
|
+
V2_SET_SELECTION: "studio:v2:set_selection",
|
|
120
|
+
V2_GET_METADATA: "studio:v2:get_metadata",
|
|
121
|
+
V2_SET_ATTRIBUTES: "studio:v2:set_attributes",
|
|
122
|
+
V2_UPDATE_TAGS: "studio:v2:update_tags",
|
|
123
|
+
V2_LIST_CALLABLE_METHODS: "studio:v2:list_callable_methods",
|
|
124
|
+
V2_INVOKE_METHODS: "studio:v2:invoke_methods",
|
|
112
125
|
V2_DELETE: "studio:v2:delete",
|
|
113
126
|
V2_BUILD_UI: "studio:v2:build_ui",
|
|
114
127
|
V2_SNAPSHOT_UI: "studio:v2:snapshot_ui",
|
|
@@ -561,11 +574,20 @@ Required workflow for Studio changes:
|
|
|
561
574
|
|
|
562
575
|
For broad tasks with independent Studio branches, prefer run_parallel_task when the MCP client supports Sampling. Dominus partitions immediate child scopes, runs proposal-only workers concurrently, rejects unknown refs and overlapping writes, applies only the coordinator-approved atomic plan, and reads the result back. Use the normal workflow when Sampling is unavailable or the task is a simple edit.
|
|
563
576
|
|
|
577
|
+
Building workflow:
|
|
578
|
+
- Use studio_create_parts for multi-part structures instead of many generic create operations.
|
|
579
|
+
- Use studio_clone_instances when an existing instance or model is the intended template. Clone rather than manually recreating descendants and properties.
|
|
580
|
+
- Use studio_get_metadata, studio_set_attributes, and studio_update_tags for saved Instance metadata. Do not create runtime bootstrap scripts or attribute mirrors when direct tags or attributes satisfy the task.
|
|
581
|
+
- Use studio_list_callable_methods before studio_invoke_methods when a task needs an Instance method not covered by a dedicated tool. Dedicated tools remain preferred for common writes.
|
|
582
|
+
- Use studio_transform_instances for Models and BaseParts so model layouts move through PVInstance pivots.
|
|
583
|
+
- Use studio_group_instances and studio_ungroup_instances to organize hierarchy, studio_create_welds for rigid assemblies, and studio_query_parts to inspect crowded 3D regions before editing.
|
|
584
|
+
- Use studio_set_selection when surfacing completed objects to the Studio user.
|
|
585
|
+
|
|
564
586
|
Safety rules:
|
|
565
587
|
- Never invent instance IDs or Roblox API members.
|
|
566
588
|
- Do not retry a failed write unchanged. Inspect the error and current state first.
|
|
567
589
|
- Deletion is a separate destructive tool and requires explicit user intent.
|
|
568
|
-
- studio_apply is transactional. Keep related edits together, but stay within its documented limits.
|
|
590
|
+
- studio_apply is transactional and supports setProperties, setAttributes, updateTags, create, clone, and move operations. Keep related edits together, but stay within its documented limits.
|
|
569
591
|
- UI fidelity work must snapshot and inspect the result after building; verify fonts, UDim2 values, colors, strokes, and hierarchy.
|
|
570
592
|
- Dominus intentionally has no arbitrary Luau execution tool.`;
|
|
571
593
|
|
|
@@ -632,6 +654,8 @@ function failure(error, data) {
|
|
|
632
654
|
data
|
|
633
655
|
});
|
|
634
656
|
}
|
|
657
|
+
|
|
658
|
+
// src/mcp/tools/shared.ts
|
|
635
659
|
var instanceRefSchema = z.object({
|
|
636
660
|
instanceId: z.string().uuid().optional(),
|
|
637
661
|
pathSegments: z.array(z.string().min(1).max(256)).min(1).max(100).optional()
|
|
@@ -664,7 +688,245 @@ function ref(value) {
|
|
|
664
688
|
return value;
|
|
665
689
|
}
|
|
666
690
|
|
|
667
|
-
// src/mcp/tools/
|
|
691
|
+
// src/mcp/tools/building.ts
|
|
692
|
+
var finiteNumber = z.number().finite();
|
|
693
|
+
var vector3Schema = z.object({ x: finiteNumber, y: finiteNumber, z: finiteNumber }).strict();
|
|
694
|
+
var propertiesSchema = z.record(z.unknown()).refine(
|
|
695
|
+
(properties) => Object.keys(properties).length <= 100 && !Object.keys(properties).some((name) => ["Parent", "ClassName", "Source"].includes(name)),
|
|
696
|
+
"At most 100 properties are allowed; Parent, ClassName, and Source require dedicated operations"
|
|
697
|
+
);
|
|
698
|
+
var partSchema = z.object({
|
|
699
|
+
name: z.string().min(1).max(100),
|
|
700
|
+
className: z.enum([
|
|
701
|
+
"Part",
|
|
702
|
+
"WedgePart",
|
|
703
|
+
"CornerWedgePart",
|
|
704
|
+
"TrussPart",
|
|
705
|
+
"MeshPart",
|
|
706
|
+
"SpawnLocation",
|
|
707
|
+
"Seat",
|
|
708
|
+
"VehicleSeat"
|
|
709
|
+
]).optional().default("Part"),
|
|
710
|
+
shape: z.enum(["Block", "Ball", "Cylinder", "Wedge", "CornerWedge"]).optional(),
|
|
711
|
+
position: vector3Schema.optional(),
|
|
712
|
+
orientation: vector3Schema.optional(),
|
|
713
|
+
size: vector3Schema.optional(),
|
|
714
|
+
color: z.string().min(1).max(100).optional(),
|
|
715
|
+
material: z.string().min(1).max(100).optional(),
|
|
716
|
+
transparency: z.number().min(0).max(1).optional(),
|
|
717
|
+
anchored: z.boolean().optional().default(true),
|
|
718
|
+
canCollide: z.boolean().optional(),
|
|
719
|
+
properties: propertiesSchema.optional()
|
|
720
|
+
});
|
|
721
|
+
var cloneRequestSchema = z.object({
|
|
722
|
+
target: instanceRefSchema,
|
|
723
|
+
parent: instanceRefSchema.optional(),
|
|
724
|
+
name: z.string().min(1).max(100).optional(),
|
|
725
|
+
count: z.number().int().min(1).max(50).optional().default(1),
|
|
726
|
+
offset: vector3Schema.optional(),
|
|
727
|
+
stepOffset: vector3Schema.optional(),
|
|
728
|
+
properties: propertiesSchema.optional()
|
|
729
|
+
});
|
|
730
|
+
var cframeSchema = z.array(finiteNumber).refine((value) => value.length === 3 || value.length === 12, "CFrame requires 3 or 12 numbers");
|
|
731
|
+
var transformOperationSchema = z.object({
|
|
732
|
+
target: instanceRefSchema,
|
|
733
|
+
mode: z.enum(["absolute", "relative"]).optional().default("absolute"),
|
|
734
|
+
cframe: cframeSchema.optional(),
|
|
735
|
+
position: vector3Schema.optional(),
|
|
736
|
+
orientation: vector3Schema.optional(),
|
|
737
|
+
offset: vector3Schema.optional(),
|
|
738
|
+
rotationOffset: vector3Schema.optional(),
|
|
739
|
+
space: z.enum(["world", "local"]).optional().default("world")
|
|
740
|
+
}).refine(
|
|
741
|
+
(value) => value.mode === "relative" ? Boolean(value.offset || value.rotationOffset) : Boolean(value.cframe || value.position || value.orientation),
|
|
742
|
+
"Absolute transforms require cframe, position, or orientation; relative transforms require offset or rotationOffset"
|
|
743
|
+
);
|
|
744
|
+
function registerBuildingTools(server, bridge) {
|
|
745
|
+
server.registerTool(
|
|
746
|
+
"studio_create_parts",
|
|
747
|
+
{
|
|
748
|
+
title: "Build Roblox Parts",
|
|
749
|
+
description: "Create up to 200 typed BaseParts in one undoable batch, optionally grouped under a new Model or Folder. Supports positions, orientations, sizes, colors, materials, shapes, and additional writable properties.",
|
|
750
|
+
inputSchema: {
|
|
751
|
+
parent: instanceRefSchema.optional(),
|
|
752
|
+
groupName: z.string().min(1).max(100).optional(),
|
|
753
|
+
groupClass: z.enum(["Model", "Folder"]).optional().default("Model"),
|
|
754
|
+
parts: z.array(partSchema).min(1).max(200)
|
|
755
|
+
},
|
|
756
|
+
outputSchema: toolOutputSchema,
|
|
757
|
+
annotations: {
|
|
758
|
+
readOnlyHint: false,
|
|
759
|
+
destructiveHint: false,
|
|
760
|
+
idempotentHint: false,
|
|
761
|
+
openWorldHint: false
|
|
762
|
+
}
|
|
763
|
+
},
|
|
764
|
+
({ parent, groupName, groupClass, parts }) => callStudio(bridge, CliMsg.V2_CREATE_PARTS, { parent, groupName, groupClass, parts }, 12e4)
|
|
765
|
+
);
|
|
766
|
+
server.registerTool(
|
|
767
|
+
"studio_clone_instances",
|
|
768
|
+
{
|
|
769
|
+
title: "Clone Roblox Instances",
|
|
770
|
+
description: "Deep-clone instances and descendants with Instance:Clone(), optionally rename, reparent, override root properties, and create repeated spatial copies using offsets. The entire request is undoable and rolls back on failure.",
|
|
771
|
+
inputSchema: { requests: z.array(cloneRequestSchema).min(1).max(100) },
|
|
772
|
+
outputSchema: toolOutputSchema,
|
|
773
|
+
annotations: {
|
|
774
|
+
readOnlyHint: false,
|
|
775
|
+
destructiveHint: false,
|
|
776
|
+
idempotentHint: false,
|
|
777
|
+
openWorldHint: false
|
|
778
|
+
}
|
|
779
|
+
},
|
|
780
|
+
({ requests }) => callStudio(bridge, CliMsg.V2_CLONE_INSTANCES, { requests }, 12e4)
|
|
781
|
+
);
|
|
782
|
+
server.registerTool(
|
|
783
|
+
"studio_group_instances",
|
|
784
|
+
{
|
|
785
|
+
title: "Group Roblox Instances",
|
|
786
|
+
description: "Create a Model or Folder and move up to 100 independent instances into it in one undoable operation.",
|
|
787
|
+
inputSchema: {
|
|
788
|
+
targets: z.array(instanceRefSchema).min(1).max(100),
|
|
789
|
+
name: z.string().min(1).max(100),
|
|
790
|
+
className: z.enum(["Model", "Folder"]).optional().default("Model"),
|
|
791
|
+
parent: instanceRefSchema.optional()
|
|
792
|
+
},
|
|
793
|
+
outputSchema: toolOutputSchema,
|
|
794
|
+
annotations: {
|
|
795
|
+
readOnlyHint: false,
|
|
796
|
+
destructiveHint: false,
|
|
797
|
+
idempotentHint: false,
|
|
798
|
+
openWorldHint: false
|
|
799
|
+
}
|
|
800
|
+
},
|
|
801
|
+
({ targets, name, className, parent }) => callStudio(bridge, CliMsg.V2_GROUP_INSTANCES, { targets, name, className, parent }, 6e4)
|
|
802
|
+
);
|
|
803
|
+
server.registerTool(
|
|
804
|
+
"studio_ungroup_instances",
|
|
805
|
+
{
|
|
806
|
+
title: "Ungroup Roblox Instances",
|
|
807
|
+
description: "Move every child out of up to 20 Models or Folders, then remove the empty containers in one undoable operation.",
|
|
808
|
+
inputSchema: {
|
|
809
|
+
targets: z.array(instanceRefSchema).min(1).max(20),
|
|
810
|
+
parent: instanceRefSchema.optional(),
|
|
811
|
+
confirm: z.literal(true)
|
|
812
|
+
},
|
|
813
|
+
outputSchema: toolOutputSchema,
|
|
814
|
+
annotations: {
|
|
815
|
+
readOnlyHint: false,
|
|
816
|
+
destructiveHint: true,
|
|
817
|
+
idempotentHint: false,
|
|
818
|
+
openWorldHint: false
|
|
819
|
+
}
|
|
820
|
+
},
|
|
821
|
+
({ targets, parent, confirm }) => callStudio(bridge, CliMsg.V2_UNGROUP_INSTANCES, { targets, parent, confirm }, 6e4)
|
|
822
|
+
);
|
|
823
|
+
server.registerTool(
|
|
824
|
+
"studio_transform_instances",
|
|
825
|
+
{
|
|
826
|
+
title: "Transform Roblox Models and Parts",
|
|
827
|
+
description: "Move or rotate up to 100 Models and BaseParts through PVInstance:PivotTo. Supports exact absolute CFrames and world/local relative offsets without breaking model layouts.",
|
|
828
|
+
inputSchema: { operations: z.array(transformOperationSchema).min(1).max(100) },
|
|
829
|
+
outputSchema: toolOutputSchema,
|
|
830
|
+
annotations: {
|
|
831
|
+
readOnlyHint: false,
|
|
832
|
+
destructiveHint: false,
|
|
833
|
+
idempotentHint: false,
|
|
834
|
+
openWorldHint: false
|
|
835
|
+
}
|
|
836
|
+
},
|
|
837
|
+
({ operations }) => callStudio(bridge, CliMsg.V2_TRANSFORM_INSTANCES, { operations }, 6e4)
|
|
838
|
+
);
|
|
839
|
+
server.registerTool(
|
|
840
|
+
"studio_create_welds",
|
|
841
|
+
{
|
|
842
|
+
title: "Create Roblox Welds",
|
|
843
|
+
description: "Create up to 200 WeldConstraints between explicit BasePart refs in one undoable operation.",
|
|
844
|
+
inputSchema: {
|
|
845
|
+
welds: z.array(
|
|
846
|
+
z.object({
|
|
847
|
+
part0: instanceRefSchema,
|
|
848
|
+
part1: instanceRefSchema,
|
|
849
|
+
name: z.string().min(1).max(100).optional(),
|
|
850
|
+
parent: instanceRefSchema.optional()
|
|
851
|
+
})
|
|
852
|
+
).min(1).max(200)
|
|
853
|
+
},
|
|
854
|
+
outputSchema: toolOutputSchema,
|
|
855
|
+
annotations: {
|
|
856
|
+
readOnlyHint: false,
|
|
857
|
+
destructiveHint: false,
|
|
858
|
+
idempotentHint: false,
|
|
859
|
+
openWorldHint: false
|
|
860
|
+
}
|
|
861
|
+
},
|
|
862
|
+
({ welds }) => callStudio(bridge, CliMsg.V2_CREATE_WELDS, { welds }, 6e4)
|
|
863
|
+
);
|
|
864
|
+
server.registerTool(
|
|
865
|
+
"studio_query_parts",
|
|
866
|
+
{
|
|
867
|
+
title: "Query Parts in 3D Space",
|
|
868
|
+
description: "Find BaseParts intersecting an oriented box or radius using WorldRoot spatial queries. Returns stable refs, positions, and sizes for inspection or later mutations.",
|
|
869
|
+
inputSchema: {
|
|
870
|
+
root: instanceRefSchema.optional(),
|
|
871
|
+
mode: z.enum(["box", "radius"]).optional().default("box"),
|
|
872
|
+
position: vector3Schema,
|
|
873
|
+
orientation: vector3Schema.optional(),
|
|
874
|
+
size: vector3Schema.optional(),
|
|
875
|
+
radius: z.number().finite().positive().optional(),
|
|
876
|
+
maxParts: z.number().int().min(1).max(500).optional().default(200),
|
|
877
|
+
filter: z.array(instanceRefSchema).max(100).optional(),
|
|
878
|
+
filterType: z.enum(["Include", "Exclude"]).optional().default("Exclude"),
|
|
879
|
+
respectCanCollide: z.boolean().optional().default(false)
|
|
880
|
+
},
|
|
881
|
+
outputSchema: toolOutputSchema,
|
|
882
|
+
annotations: {
|
|
883
|
+
readOnlyHint: true,
|
|
884
|
+
destructiveHint: false,
|
|
885
|
+
idempotentHint: true,
|
|
886
|
+
openWorldHint: false
|
|
887
|
+
}
|
|
888
|
+
},
|
|
889
|
+
({
|
|
890
|
+
root,
|
|
891
|
+
mode,
|
|
892
|
+
position,
|
|
893
|
+
orientation,
|
|
894
|
+
size,
|
|
895
|
+
radius,
|
|
896
|
+
maxParts,
|
|
897
|
+
filter,
|
|
898
|
+
filterType,
|
|
899
|
+
respectCanCollide
|
|
900
|
+
}) => callStudio(bridge, CliMsg.V2_QUERY_PARTS, {
|
|
901
|
+
root,
|
|
902
|
+
mode,
|
|
903
|
+
position,
|
|
904
|
+
orientation,
|
|
905
|
+
size,
|
|
906
|
+
radius,
|
|
907
|
+
maxParts,
|
|
908
|
+
filter,
|
|
909
|
+
filterType,
|
|
910
|
+
respectCanCollide
|
|
911
|
+
})
|
|
912
|
+
);
|
|
913
|
+
server.registerTool(
|
|
914
|
+
"studio_set_selection",
|
|
915
|
+
{
|
|
916
|
+
title: "Set Roblox Studio Selection",
|
|
917
|
+
description: "Replace the current Studio selection with up to 200 stable instance refs. Pass an empty array to clear selection.",
|
|
918
|
+
inputSchema: { targets: z.array(instanceRefSchema).max(200) },
|
|
919
|
+
outputSchema: toolOutputSchema,
|
|
920
|
+
annotations: {
|
|
921
|
+
readOnlyHint: false,
|
|
922
|
+
destructiveHint: false,
|
|
923
|
+
idempotentHint: true,
|
|
924
|
+
openWorldHint: false
|
|
925
|
+
}
|
|
926
|
+
},
|
|
927
|
+
({ targets }) => callStudio(bridge, CliMsg.V2_SET_SELECTION, { targets })
|
|
928
|
+
);
|
|
929
|
+
}
|
|
668
930
|
function registerConnectionTools(server, bridge) {
|
|
669
931
|
server.registerTool(
|
|
670
932
|
"dominus_status",
|
|
@@ -1192,7 +1454,261 @@ function registerDocsTools(server) {
|
|
|
1192
1454
|
}
|
|
1193
1455
|
);
|
|
1194
1456
|
}
|
|
1195
|
-
var
|
|
1457
|
+
var finiteNumber2 = z.number().finite();
|
|
1458
|
+
var vector2Value = z.object({ x: finiteNumber2, y: finiteNumber2 }).strict();
|
|
1459
|
+
var vector3Value = z.object({ x: finiteNumber2, y: finiteNumber2, z: finiteNumber2 }).strict();
|
|
1460
|
+
var color3Object = z.object({ r: finiteNumber2, g: finiteNumber2, b: finiteNumber2 }).strict();
|
|
1461
|
+
var color3Value = z.union([
|
|
1462
|
+
z.string().min(1).max(100),
|
|
1463
|
+
color3Object,
|
|
1464
|
+
z.tuple([finiteNumber2, finiteNumber2, finiteNumber2])
|
|
1465
|
+
]);
|
|
1466
|
+
var cframeValue = z.array(finiteNumber2).refine((value) => value.length === 3 || value.length === 12, "CFrame requires 3 or 12 numbers");
|
|
1467
|
+
var numberSequenceKeypoint = z.union([
|
|
1468
|
+
z.tuple([finiteNumber2, finiteNumber2]),
|
|
1469
|
+
z.tuple([finiteNumber2, finiteNumber2, finiteNumber2])
|
|
1470
|
+
]);
|
|
1471
|
+
var colorSequenceKeypoint = z.tuple([finiteNumber2, color3Value]);
|
|
1472
|
+
var typedString = z.object({ type: z.literal("string"), value: z.string() }).strict();
|
|
1473
|
+
var typedBoolean = z.object({ type: z.literal("boolean"), value: z.boolean() }).strict();
|
|
1474
|
+
var typedNumber = z.object({ type: z.literal("number"), value: finiteNumber2 }).strict();
|
|
1475
|
+
var typedUDim = z.object({
|
|
1476
|
+
type: z.literal("UDim"),
|
|
1477
|
+
value: z.union([
|
|
1478
|
+
z.object({ scale: finiteNumber2, offset: finiteNumber2 }).strict(),
|
|
1479
|
+
z.tuple([finiteNumber2, finiteNumber2])
|
|
1480
|
+
])
|
|
1481
|
+
}).strict();
|
|
1482
|
+
var typedUDim2 = z.object({
|
|
1483
|
+
type: z.literal("UDim2"),
|
|
1484
|
+
value: z.union([
|
|
1485
|
+
z.object({
|
|
1486
|
+
xScale: finiteNumber2,
|
|
1487
|
+
xOffset: finiteNumber2,
|
|
1488
|
+
yScale: finiteNumber2,
|
|
1489
|
+
yOffset: finiteNumber2
|
|
1490
|
+
}).strict(),
|
|
1491
|
+
z.tuple([finiteNumber2, finiteNumber2, finiteNumber2, finiteNumber2])
|
|
1492
|
+
])
|
|
1493
|
+
}).strict();
|
|
1494
|
+
var typedBrickColor = z.object({ type: z.literal("BrickColor"), value: z.string().min(1).max(100) }).strict();
|
|
1495
|
+
var typedColor3 = z.object({ type: z.literal("Color3"), value: color3Value }).strict();
|
|
1496
|
+
var typedVector2 = z.object({
|
|
1497
|
+
type: z.literal("Vector2"),
|
|
1498
|
+
value: z.union([vector2Value, z.tuple([finiteNumber2, finiteNumber2])])
|
|
1499
|
+
}).strict();
|
|
1500
|
+
var typedVector3 = z.object({
|
|
1501
|
+
type: z.literal("Vector3"),
|
|
1502
|
+
value: z.union([vector3Value, z.tuple([finiteNumber2, finiteNumber2, finiteNumber2])])
|
|
1503
|
+
}).strict();
|
|
1504
|
+
var typedCFrame = z.object({ type: z.literal("CFrame"), value: cframeValue }).strict();
|
|
1505
|
+
var typedNumberSequence = z.object({
|
|
1506
|
+
type: z.literal("NumberSequence"),
|
|
1507
|
+
value: z.union([finiteNumber2, z.array(numberSequenceKeypoint).min(1).max(100)])
|
|
1508
|
+
}).strict();
|
|
1509
|
+
var typedColorSequence = z.object({
|
|
1510
|
+
type: z.literal("ColorSequence"),
|
|
1511
|
+
value: z.union([color3Value, z.array(colorSequenceKeypoint).min(1).max(100)])
|
|
1512
|
+
}).strict();
|
|
1513
|
+
var typedNumberRange = z.object({
|
|
1514
|
+
type: z.literal("NumberRange"),
|
|
1515
|
+
value: z.union([finiteNumber2, z.tuple([finiteNumber2, finiteNumber2])])
|
|
1516
|
+
}).strict();
|
|
1517
|
+
var typedRect = z.object({
|
|
1518
|
+
type: z.literal("Rect"),
|
|
1519
|
+
value: z.tuple([finiteNumber2, finiteNumber2, finiteNumber2, finiteNumber2])
|
|
1520
|
+
}).strict();
|
|
1521
|
+
var typedFont = z.object({
|
|
1522
|
+
type: z.literal("Font"),
|
|
1523
|
+
value: z.union([
|
|
1524
|
+
z.object({
|
|
1525
|
+
family: z.string().min(1).max(500),
|
|
1526
|
+
weight: z.string().min(1).max(100).optional(),
|
|
1527
|
+
style: z.string().min(1).max(100).optional()
|
|
1528
|
+
}).strict(),
|
|
1529
|
+
z.object({
|
|
1530
|
+
Family: z.string().min(1).max(500),
|
|
1531
|
+
Weight: z.string().min(1).max(100).optional(),
|
|
1532
|
+
Style: z.string().min(1).max(100).optional()
|
|
1533
|
+
}).strict()
|
|
1534
|
+
])
|
|
1535
|
+
}).strict();
|
|
1536
|
+
var attributeValueSchema = z.discriminatedUnion("type", [
|
|
1537
|
+
typedString,
|
|
1538
|
+
typedBoolean,
|
|
1539
|
+
typedNumber,
|
|
1540
|
+
typedUDim,
|
|
1541
|
+
typedUDim2,
|
|
1542
|
+
typedBrickColor,
|
|
1543
|
+
typedColor3,
|
|
1544
|
+
typedVector2,
|
|
1545
|
+
typedVector3,
|
|
1546
|
+
typedCFrame,
|
|
1547
|
+
typedNumberSequence,
|
|
1548
|
+
typedColorSequence,
|
|
1549
|
+
typedNumberRange,
|
|
1550
|
+
typedRect,
|
|
1551
|
+
typedFont
|
|
1552
|
+
]);
|
|
1553
|
+
var methodArgumentSchema = z.discriminatedUnion("type", [
|
|
1554
|
+
z.object({ type: z.literal("nil") }).strict(),
|
|
1555
|
+
typedString,
|
|
1556
|
+
typedBoolean,
|
|
1557
|
+
typedNumber,
|
|
1558
|
+
typedUDim,
|
|
1559
|
+
typedUDim2,
|
|
1560
|
+
typedBrickColor,
|
|
1561
|
+
typedColor3,
|
|
1562
|
+
typedVector2,
|
|
1563
|
+
typedVector3,
|
|
1564
|
+
typedCFrame,
|
|
1565
|
+
typedNumberSequence,
|
|
1566
|
+
typedColorSequence,
|
|
1567
|
+
typedNumberRange,
|
|
1568
|
+
typedRect,
|
|
1569
|
+
typedFont,
|
|
1570
|
+
z.object({ type: z.literal("Instance"), value: instanceRefSchema }).strict()
|
|
1571
|
+
]);
|
|
1572
|
+
var callableMethodSchema = z.enum([
|
|
1573
|
+
"GetAttribute",
|
|
1574
|
+
"GetAttributes",
|
|
1575
|
+
"GetTags",
|
|
1576
|
+
"HasTag",
|
|
1577
|
+
"GetFullName",
|
|
1578
|
+
"IsA",
|
|
1579
|
+
"IsAncestorOf",
|
|
1580
|
+
"IsDescendantOf",
|
|
1581
|
+
"FindFirstChild",
|
|
1582
|
+
"FindFirstChildOfClass",
|
|
1583
|
+
"FindFirstChildWhichIsA",
|
|
1584
|
+
"FindFirstAncestor",
|
|
1585
|
+
"FindFirstAncestorOfClass",
|
|
1586
|
+
"FindFirstAncestorWhichIsA",
|
|
1587
|
+
"GetChildren",
|
|
1588
|
+
"GetDescendants",
|
|
1589
|
+
"GetPivot",
|
|
1590
|
+
"GetScale",
|
|
1591
|
+
"GetBoundingBox",
|
|
1592
|
+
"AddTag",
|
|
1593
|
+
"RemoveTag",
|
|
1594
|
+
"SetAttribute",
|
|
1595
|
+
"PivotTo",
|
|
1596
|
+
"ScaleTo"
|
|
1597
|
+
]);
|
|
1598
|
+
var attributeUpdateSchema = z.object({
|
|
1599
|
+
target: instanceRefSchema,
|
|
1600
|
+
set: z.array(
|
|
1601
|
+
z.object({
|
|
1602
|
+
name: z.string().min(1).max(100),
|
|
1603
|
+
value: attributeValueSchema
|
|
1604
|
+
})
|
|
1605
|
+
).max(100).optional().default([]),
|
|
1606
|
+
remove: z.array(z.string().min(1).max(100)).max(100).optional().default([])
|
|
1607
|
+
});
|
|
1608
|
+
var tagUpdateSchema = z.object({
|
|
1609
|
+
target: instanceRefSchema,
|
|
1610
|
+
add: z.array(z.string().min(1).max(100)).max(100).optional().default([]),
|
|
1611
|
+
remove: z.array(z.string().min(1).max(100)).max(100).optional().default([])
|
|
1612
|
+
});
|
|
1613
|
+
function registerMetadataTools(server, bridge) {
|
|
1614
|
+
server.registerTool(
|
|
1615
|
+
"studio_get_metadata",
|
|
1616
|
+
{
|
|
1617
|
+
title: "Get Roblox Tags and Attributes",
|
|
1618
|
+
description: "Read persistent Instance tags and typed attributes for up to 100 instances. Use this instead of runtime bootstrap scripts or attribute mirrors when inspecting saved Studio metadata.",
|
|
1619
|
+
inputSchema: {
|
|
1620
|
+
targets: z.array(instanceRefSchema).min(1).max(100)
|
|
1621
|
+
},
|
|
1622
|
+
outputSchema: toolOutputSchema,
|
|
1623
|
+
annotations: {
|
|
1624
|
+
readOnlyHint: true,
|
|
1625
|
+
destructiveHint: false,
|
|
1626
|
+
idempotentHint: true,
|
|
1627
|
+
openWorldHint: false
|
|
1628
|
+
}
|
|
1629
|
+
},
|
|
1630
|
+
({ targets }) => callStudio(bridge, CliMsg.V2_GET_METADATA, { targets })
|
|
1631
|
+
);
|
|
1632
|
+
server.registerTool(
|
|
1633
|
+
"studio_set_attributes",
|
|
1634
|
+
{
|
|
1635
|
+
title: "Set Roblox Attributes",
|
|
1636
|
+
description: "Set or remove persistent typed Instance attributes in one undoable batch. Values are explicitly typed and saved with the place; no Play-mode bootstrap is required.",
|
|
1637
|
+
inputSchema: {
|
|
1638
|
+
operations: z.array(attributeUpdateSchema).min(1).max(100)
|
|
1639
|
+
},
|
|
1640
|
+
outputSchema: toolOutputSchema,
|
|
1641
|
+
annotations: {
|
|
1642
|
+
readOnlyHint: false,
|
|
1643
|
+
destructiveHint: false,
|
|
1644
|
+
idempotentHint: true,
|
|
1645
|
+
openWorldHint: false
|
|
1646
|
+
}
|
|
1647
|
+
},
|
|
1648
|
+
({ operations }) => callStudio(bridge, CliMsg.V2_SET_ATTRIBUTES, { operations }, 6e4)
|
|
1649
|
+
);
|
|
1650
|
+
server.registerTool(
|
|
1651
|
+
"studio_update_tags",
|
|
1652
|
+
{
|
|
1653
|
+
title: "Update Roblox Tags",
|
|
1654
|
+
description: "Add or remove persistent Instance tags in one undoable batch using Instance:AddTag and RemoveTag. Tags are saved directly in Studio and replicate normally.",
|
|
1655
|
+
inputSchema: {
|
|
1656
|
+
operations: z.array(tagUpdateSchema).min(1).max(100)
|
|
1657
|
+
},
|
|
1658
|
+
outputSchema: toolOutputSchema,
|
|
1659
|
+
annotations: {
|
|
1660
|
+
readOnlyHint: false,
|
|
1661
|
+
destructiveHint: false,
|
|
1662
|
+
idempotentHint: true,
|
|
1663
|
+
openWorldHint: false
|
|
1664
|
+
}
|
|
1665
|
+
},
|
|
1666
|
+
({ operations }) => callStudio(bridge, CliMsg.V2_UPDATE_TAGS, { operations }, 6e4)
|
|
1667
|
+
);
|
|
1668
|
+
server.registerTool(
|
|
1669
|
+
"studio_list_callable_methods",
|
|
1670
|
+
{
|
|
1671
|
+
title: "List Safe Instance Methods",
|
|
1672
|
+
description: "List the guarded Instance methods Dominus can invoke on a specific target, including required classes, argument types, and whether each method reads or writes Studio state.",
|
|
1673
|
+
inputSchema: {
|
|
1674
|
+
target: instanceRefSchema
|
|
1675
|
+
},
|
|
1676
|
+
outputSchema: toolOutputSchema,
|
|
1677
|
+
annotations: {
|
|
1678
|
+
readOnlyHint: true,
|
|
1679
|
+
destructiveHint: false,
|
|
1680
|
+
idempotentHint: true,
|
|
1681
|
+
openWorldHint: false
|
|
1682
|
+
}
|
|
1683
|
+
},
|
|
1684
|
+
({ target }) => callStudio(bridge, CliMsg.V2_LIST_CALLABLE_METHODS, { target })
|
|
1685
|
+
);
|
|
1686
|
+
server.registerTool(
|
|
1687
|
+
"studio_invoke_methods",
|
|
1688
|
+
{
|
|
1689
|
+
title: "Invoke Safe Instance Methods",
|
|
1690
|
+
description: "Invoke a bounded allowlist of typed Instance methods. Supports hierarchy queries, metadata methods, GetPivot/GetBoundingBox/GetScale, and undoable AddTag/RemoveTag/SetAttribute/PivotTo/ScaleTo writes. Arbitrary, destructive, yielding, network, and signal methods are blocked in the Studio plugin.",
|
|
1691
|
+
inputSchema: {
|
|
1692
|
+
calls: z.array(
|
|
1693
|
+
z.object({
|
|
1694
|
+
target: instanceRefSchema,
|
|
1695
|
+
method: callableMethodSchema,
|
|
1696
|
+
arguments: z.array(methodArgumentSchema).max(10).optional().default([])
|
|
1697
|
+
})
|
|
1698
|
+
).min(1).max(50)
|
|
1699
|
+
},
|
|
1700
|
+
outputSchema: toolOutputSchema,
|
|
1701
|
+
annotations: {
|
|
1702
|
+
readOnlyHint: false,
|
|
1703
|
+
destructiveHint: false,
|
|
1704
|
+
idempotentHint: false,
|
|
1705
|
+
openWorldHint: false
|
|
1706
|
+
}
|
|
1707
|
+
},
|
|
1708
|
+
({ calls }) => callStudio(bridge, CliMsg.V2_INVOKE_METHODS, { calls }, 6e4)
|
|
1709
|
+
);
|
|
1710
|
+
}
|
|
1711
|
+
var propertiesSchema2 = z.record(z.unknown()).refine(
|
|
1196
1712
|
(properties) => Object.keys(properties).length <= 100 && !Object.keys(properties).some((name) => ["Parent", "ClassName", "Source"].includes(name)),
|
|
1197
1713
|
"At most 100 properties are allowed; Parent, ClassName, and Source require dedicated operations"
|
|
1198
1714
|
);
|
|
@@ -1200,20 +1716,30 @@ var mutationOperationSchema = z.discriminatedUnion("op", [
|
|
|
1200
1716
|
z.object({
|
|
1201
1717
|
op: z.literal("setProperties"),
|
|
1202
1718
|
target: instanceRefSchema,
|
|
1203
|
-
properties:
|
|
1719
|
+
properties: propertiesSchema2
|
|
1204
1720
|
}),
|
|
1205
1721
|
z.object({
|
|
1206
1722
|
op: z.literal("create"),
|
|
1207
1723
|
parent: instanceRefSchema,
|
|
1208
1724
|
className: z.string().min(1).max(100),
|
|
1209
1725
|
name: z.string().min(1).max(256),
|
|
1210
|
-
properties:
|
|
1726
|
+
properties: propertiesSchema2.optional()
|
|
1211
1727
|
}),
|
|
1212
1728
|
z.object({
|
|
1213
1729
|
op: z.literal("move"),
|
|
1214
1730
|
target: instanceRefSchema,
|
|
1215
1731
|
parent: instanceRefSchema
|
|
1216
|
-
})
|
|
1732
|
+
}),
|
|
1733
|
+
z.object({
|
|
1734
|
+
op: z.literal("clone"),
|
|
1735
|
+
target: instanceRefSchema,
|
|
1736
|
+
parent: instanceRefSchema,
|
|
1737
|
+
name: z.string().min(1).max(100),
|
|
1738
|
+
offset: z.object({ x: z.number().finite(), y: z.number().finite(), z: z.number().finite() }).optional(),
|
|
1739
|
+
properties: propertiesSchema2.optional()
|
|
1740
|
+
}),
|
|
1741
|
+
attributeUpdateSchema.extend({ op: z.literal("setAttributes") }),
|
|
1742
|
+
tagUpdateSchema.extend({ op: z.literal("updateTags") })
|
|
1217
1743
|
]);
|
|
1218
1744
|
var workerProposalSchema = z.object({
|
|
1219
1745
|
workerId: z.string().min(1).max(64),
|
|
@@ -1441,7 +1967,7 @@ async function runWorker(server, bridge, goal, assignment, constraints, taskType
|
|
|
1441
1967
|
systemPrompt: [
|
|
1442
1968
|
"You are a read-only Dominus worker. Inspect only the Studio evidence in the request.",
|
|
1443
1969
|
"You cannot write to Studio. Return one strict JSON WorkerProposal and no Markdown.",
|
|
1444
|
-
"Only propose setProperties, create, or move operations using exact instance refs copied from evidence.",
|
|
1970
|
+
"Only propose setProperties, setAttributes, updateTags, create, clone, or move operations using exact instance refs copied from evidence.",
|
|
1445
1971
|
"Never invent instanceId values. Never propose delete, script edits, or arbitrary execution."
|
|
1446
1972
|
].join(" "),
|
|
1447
1973
|
messages: [
|
|
@@ -1472,6 +1998,25 @@ async function runWorker(server, bridge, goal, assignment, constraints, taskType
|
|
|
1472
1998
|
op: "move",
|
|
1473
1999
|
target: { instanceId: "uuid", pathSegments: ["..."] },
|
|
1474
2000
|
parent: { instanceId: "uuid", pathSegments: ["..."] }
|
|
2001
|
+
},
|
|
2002
|
+
{
|
|
2003
|
+
op: "clone",
|
|
2004
|
+
target: { instanceId: "uuid", pathSegments: ["..."] },
|
|
2005
|
+
parent: { instanceId: "uuid", pathSegments: ["..."] },
|
|
2006
|
+
name: "CloneName",
|
|
2007
|
+
offset: { x: 0, y: 0, z: 0 }
|
|
2008
|
+
},
|
|
2009
|
+
{
|
|
2010
|
+
op: "setAttributes",
|
|
2011
|
+
target: { instanceId: "uuid", pathSegments: ["..."] },
|
|
2012
|
+
set: [{ name: "PlotId", value: { type: "number", value: 1 } }],
|
|
2013
|
+
remove: []
|
|
2014
|
+
},
|
|
2015
|
+
{
|
|
2016
|
+
op: "updateTags",
|
|
2017
|
+
target: { instanceId: "uuid", pathSegments: ["..."] },
|
|
2018
|
+
add: ["Plot"],
|
|
2019
|
+
remove: []
|
|
1475
2020
|
}
|
|
1476
2021
|
],
|
|
1477
2022
|
risks: ["string"],
|
|
@@ -1556,7 +2101,7 @@ function validateWorkerResults(results) {
|
|
|
1556
2101
|
function validateOperations(assignment, operations) {
|
|
1557
2102
|
const errors = [];
|
|
1558
2103
|
for (const [index, operation] of operations.entries()) {
|
|
1559
|
-
const refs = operation.op === "create" ? [operation.parent] : operation.op === "move" ? [operation.target, operation.parent] : [operation.target];
|
|
2104
|
+
const refs = operation.op === "create" ? [operation.parent] : operation.op === "move" || operation.op === "clone" ? [operation.target, operation.parent] : [operation.target];
|
|
1560
2105
|
for (const ref2 of refs) {
|
|
1561
2106
|
if (!isKnownParallelRef(ref2, assignment.knownRefKeys)) {
|
|
1562
2107
|
errors.push(`Operation ${index} references an instance absent from worker evidence`);
|
|
@@ -1702,15 +2247,18 @@ async function verifyPlan(bridge, rootRef, originalRoot, operations) {
|
|
|
1702
2247
|
);
|
|
1703
2248
|
const knownPaths = collectInstancePaths(originalRoot);
|
|
1704
2249
|
const expectedCreates = operations.flatMap((operation) => {
|
|
1705
|
-
if (operation.op !== "create") return [];
|
|
2250
|
+
if (operation.op !== "create" && operation.op !== "clone") return [];
|
|
1706
2251
|
const parentPath = resolveRefPath(operation.parent, knownPaths);
|
|
1707
2252
|
return parentPath ? [pathKey([...parentPath, operation.name])] : [];
|
|
1708
2253
|
});
|
|
1709
2254
|
const missingCreates = expectedCreates.filter((path5) => !paths.has(path5));
|
|
1710
|
-
const inspectOperations = operations.filter(
|
|
2255
|
+
const inspectOperations = operations.filter(
|
|
2256
|
+
(operation) => operation.op === "setProperties" || operation.op === "move"
|
|
2257
|
+
);
|
|
1711
2258
|
const inspectFailures = [];
|
|
1712
2259
|
const propertyMismatches = [];
|
|
1713
2260
|
const moveMismatches = [];
|
|
2261
|
+
const metadataMismatches = [];
|
|
1714
2262
|
for (let offset = 0; offset < inspectOperations.length; offset += 20) {
|
|
1715
2263
|
const batch = inspectOperations.slice(offset, offset + 20);
|
|
1716
2264
|
const targets = batch.map((operation) => operation.target);
|
|
@@ -1755,8 +2303,77 @@ async function verifyPlan(bridge, rootRef, originalRoot, operations) {
|
|
|
1755
2303
|
}
|
|
1756
2304
|
});
|
|
1757
2305
|
}
|
|
2306
|
+
const metadataOperations = operations.filter(
|
|
2307
|
+
(operation) => operation.op === "setAttributes" || operation.op === "updateTags"
|
|
2308
|
+
);
|
|
2309
|
+
for (let offset = 0; offset < metadataOperations.length; offset += 100) {
|
|
2310
|
+
const batch = metadataOperations.slice(offset, offset + 100);
|
|
2311
|
+
const metadata = await requestStudio(bridge, CliMsg.V2_GET_METADATA, {
|
|
2312
|
+
targets: batch.map((operation) => operation.target)
|
|
2313
|
+
});
|
|
2314
|
+
const results = Array.isArray(metadata.results) ? metadata.results : [];
|
|
2315
|
+
batch.forEach((operation, index) => {
|
|
2316
|
+
const result = results[index];
|
|
2317
|
+
const targetLabel = refLabel(operation.target);
|
|
2318
|
+
if (!result) {
|
|
2319
|
+
inspectFailures.push(`${targetLabel}: missing metadata inspection result`);
|
|
2320
|
+
return;
|
|
2321
|
+
}
|
|
2322
|
+
if (operation.op === "setAttributes") {
|
|
2323
|
+
const attributes = Array.isArray(result.attributes) ? result.attributes : [];
|
|
2324
|
+
const actual = new Map(attributes.map((entry) => [String(entry.name), entry.value]));
|
|
2325
|
+
for (const item of operation.set) {
|
|
2326
|
+
const actualValue = actual.get(item.name);
|
|
2327
|
+
if (!actual.has(item.name) || !valuesEquivalent(item.value, actualValue)) {
|
|
2328
|
+
metadataMismatches.push({
|
|
2329
|
+
target: targetLabel,
|
|
2330
|
+
kind: "attribute",
|
|
2331
|
+
name: item.name,
|
|
2332
|
+
expected: item.value,
|
|
2333
|
+
actual: actualValue
|
|
2334
|
+
});
|
|
2335
|
+
}
|
|
2336
|
+
}
|
|
2337
|
+
for (const name of operation.remove) {
|
|
2338
|
+
if (actual.has(name)) {
|
|
2339
|
+
metadataMismatches.push({
|
|
2340
|
+
target: targetLabel,
|
|
2341
|
+
kind: "attribute",
|
|
2342
|
+
name,
|
|
2343
|
+
expected: "absent",
|
|
2344
|
+
actual: actual.get(name)
|
|
2345
|
+
});
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2348
|
+
} else {
|
|
2349
|
+
const tags = new Set(Array.isArray(result.tags) ? result.tags.map(String) : []);
|
|
2350
|
+
for (const name of operation.add) {
|
|
2351
|
+
if (!tags.has(name)) {
|
|
2352
|
+
metadataMismatches.push({
|
|
2353
|
+
target: targetLabel,
|
|
2354
|
+
kind: "tag",
|
|
2355
|
+
name,
|
|
2356
|
+
expected: true,
|
|
2357
|
+
actual: false
|
|
2358
|
+
});
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
for (const name of operation.remove) {
|
|
2362
|
+
if (tags.has(name)) {
|
|
2363
|
+
metadataMismatches.push({
|
|
2364
|
+
target: targetLabel,
|
|
2365
|
+
kind: "tag",
|
|
2366
|
+
name,
|
|
2367
|
+
expected: false,
|
|
2368
|
+
actual: true
|
|
2369
|
+
});
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
});
|
|
2374
|
+
}
|
|
1758
2375
|
return {
|
|
1759
|
-
passed: !tree.truncated && missingCreates.length === 0 && propertyMismatches.length === 0 && moveMismatches.length === 0 && inspectFailures.length === 0,
|
|
2376
|
+
passed: !tree.truncated && missingCreates.length === 0 && propertyMismatches.length === 0 && moveMismatches.length === 0 && metadataMismatches.length === 0 && inspectFailures.length === 0,
|
|
1760
2377
|
treeNodeCount: tree.nodeCount,
|
|
1761
2378
|
treeTruncated: tree.truncated,
|
|
1762
2379
|
expectedCreates: expectedCreates.length,
|
|
@@ -1764,6 +2381,7 @@ async function verifyPlan(bridge, rootRef, originalRoot, operations) {
|
|
|
1764
2381
|
missingCreates,
|
|
1765
2382
|
propertyMismatches,
|
|
1766
2383
|
moveMismatches,
|
|
2384
|
+
metadataMismatches,
|
|
1767
2385
|
inspectFailures
|
|
1768
2386
|
};
|
|
1769
2387
|
}
|
|
@@ -1772,12 +2390,17 @@ function findRepairOperations(operations, verification, originalRoot) {
|
|
|
1772
2390
|
const missingCreates = new Set(verification.missingCreates);
|
|
1773
2391
|
const mismatchedTargets = new Set(verification.propertyMismatches.map((item) => item.target));
|
|
1774
2392
|
const mismatchedMoves = new Set(verification.moveMismatches.map((item) => item.target));
|
|
2393
|
+
const mismatchedMetadata = new Set(verification.metadataMismatches.map((item) => item.target));
|
|
1775
2394
|
return operations.filter((operation) => {
|
|
1776
2395
|
if (operation.op === "create") {
|
|
1777
2396
|
const parentPath = resolveRefPath(operation.parent, knownPaths);
|
|
1778
2397
|
return Boolean(parentPath && missingCreates.has(pathKey([...parentPath, operation.name])));
|
|
1779
2398
|
}
|
|
2399
|
+
if (operation.op === "clone") return false;
|
|
1780
2400
|
if (operation.op === "setProperties") return mismatchedTargets.has(refLabel(operation.target));
|
|
2401
|
+
if (operation.op === "setAttributes" || operation.op === "updateTags") {
|
|
2402
|
+
return mismatchedMetadata.has(refLabel(operation.target));
|
|
2403
|
+
}
|
|
1781
2404
|
return mismatchedMoves.has(refLabel(operation.target));
|
|
1782
2405
|
});
|
|
1783
2406
|
}
|
|
@@ -1846,7 +2469,7 @@ function isParallelRefWithinScopes(ref2, scopes) {
|
|
|
1846
2469
|
return scopes.some((scope) => Boolean(ref2.instanceId && scope.instanceId === ref2.instanceId));
|
|
1847
2470
|
}
|
|
1848
2471
|
function operationTarget(operation) {
|
|
1849
|
-
if (operation.op === "create") {
|
|
2472
|
+
if (operation.op === "create" || operation.op === "clone") {
|
|
1850
2473
|
const parentPath = operation.parent.pathSegments;
|
|
1851
2474
|
return {
|
|
1852
2475
|
key: `${refKey(operation.parent)}/create:${operation.name}`,
|
|
@@ -1951,37 +2574,50 @@ function emit(server, type, data) {
|
|
|
1951
2574
|
function toErrorMessage(error) {
|
|
1952
2575
|
return error instanceof Error ? error.message : String(error);
|
|
1953
2576
|
}
|
|
1954
|
-
var
|
|
2577
|
+
var propertiesSchema3 = z.record(z.unknown()).refine(
|
|
1955
2578
|
(properties) => Object.keys(properties).length <= 100 && !Object.keys(properties).some((name) => ["Parent", "ClassName", "Source"].includes(name)),
|
|
1956
2579
|
"At most 100 properties are allowed; Parent, ClassName, and Source require dedicated operations"
|
|
1957
2580
|
);
|
|
1958
2581
|
var setPropertiesOperation = z.object({
|
|
1959
2582
|
op: z.literal("setProperties"),
|
|
1960
2583
|
target: instanceRefSchema,
|
|
1961
|
-
properties:
|
|
2584
|
+
properties: propertiesSchema3
|
|
1962
2585
|
});
|
|
1963
2586
|
var createOperation = z.object({
|
|
1964
2587
|
op: z.literal("create"),
|
|
1965
2588
|
parent: instanceRefSchema,
|
|
1966
2589
|
className: z.string().min(1).max(100),
|
|
1967
2590
|
name: z.string().min(1).max(256),
|
|
1968
|
-
properties:
|
|
2591
|
+
properties: propertiesSchema3.optional()
|
|
1969
2592
|
});
|
|
1970
2593
|
var moveOperation = z.object({
|
|
1971
2594
|
op: z.literal("move"),
|
|
1972
2595
|
target: instanceRefSchema,
|
|
1973
2596
|
parent: instanceRefSchema
|
|
1974
2597
|
});
|
|
2598
|
+
var cloneOperation = z.object({
|
|
2599
|
+
op: z.literal("clone"),
|
|
2600
|
+
target: instanceRefSchema,
|
|
2601
|
+
parent: instanceRefSchema.optional(),
|
|
2602
|
+
name: z.string().min(1).max(100).optional(),
|
|
2603
|
+
offset: z.object({ x: z.number().finite(), y: z.number().finite(), z: z.number().finite() }).optional(),
|
|
2604
|
+
properties: propertiesSchema3.optional()
|
|
2605
|
+
});
|
|
2606
|
+
var setAttributesOperation = attributeUpdateSchema.extend({ op: z.literal("setAttributes") });
|
|
2607
|
+
var updateTagsOperation = tagUpdateSchema.extend({ op: z.literal("updateTags") });
|
|
1975
2608
|
var mutationOperation = z.discriminatedUnion("op", [
|
|
1976
2609
|
setPropertiesOperation,
|
|
1977
2610
|
createOperation,
|
|
1978
|
-
|
|
2611
|
+
cloneOperation,
|
|
2612
|
+
moveOperation,
|
|
2613
|
+
setAttributesOperation,
|
|
2614
|
+
updateTagsOperation
|
|
1979
2615
|
]);
|
|
1980
2616
|
var uiNodeSchema = z.lazy(
|
|
1981
2617
|
() => z.object({
|
|
1982
2618
|
ClassName: z.string().min(1).max(100),
|
|
1983
2619
|
Name: z.string().min(1).max(256).optional(),
|
|
1984
|
-
properties:
|
|
2620
|
+
properties: propertiesSchema3.optional(),
|
|
1985
2621
|
Children: z.array(uiNodeSchema).max(1e3).optional()
|
|
1986
2622
|
})
|
|
1987
2623
|
);
|
|
@@ -2085,7 +2721,7 @@ function registerStudioTools(server, bridge) {
|
|
|
2085
2721
|
"studio_apply",
|
|
2086
2722
|
{
|
|
2087
2723
|
title: "Apply Atomic Studio Mutations",
|
|
2088
|
-
description: "Atomically set properties, create instances, and move instances. Any failed operation cancels and undoes the entire batch.",
|
|
2724
|
+
description: "Atomically set properties or typed attributes, update tags, create instances, deep-clone instances, and move instances. Any failed operation cancels and undoes the entire batch.",
|
|
2089
2725
|
inputSchema: { operations: z.array(mutationOperation).min(1).max(100) },
|
|
2090
2726
|
outputSchema: toolOutputSchema,
|
|
2091
2727
|
annotations: {
|
|
@@ -2211,7 +2847,7 @@ function registerStudioTools(server, bridge) {
|
|
|
2211
2847
|
"studio_get_reflection",
|
|
2212
2848
|
{
|
|
2213
2849
|
title: "Read Live Roblox Reflection",
|
|
2214
|
-
description: "Read current ReflectionService fields for a class in the connected Studio, including Type, Display category, and permission metadata.",
|
|
2850
|
+
description: "Read current ReflectionService fields for a class in the connected Studio, including Type, Display category, and permission metadata. Reflection discovers APIs but does not grant call permission; use studio_list_callable_methods for that.",
|
|
2215
2851
|
inputSchema: { className: z.string().min(1).max(100) },
|
|
2216
2852
|
outputSchema: toolOutputSchema,
|
|
2217
2853
|
annotations: {
|
|
@@ -2226,7 +2862,7 @@ function registerStudioTools(server, bridge) {
|
|
|
2226
2862
|
}
|
|
2227
2863
|
|
|
2228
2864
|
// src/mcp/server.ts
|
|
2229
|
-
var VERSION = "2.
|
|
2865
|
+
var VERSION = "2.4.0";
|
|
2230
2866
|
function createDominusMcpServer(bridge) {
|
|
2231
2867
|
const server = new McpServer(
|
|
2232
2868
|
{
|
|
@@ -2239,6 +2875,8 @@ function createDominusMcpServer(bridge) {
|
|
|
2239
2875
|
);
|
|
2240
2876
|
registerConnectionTools(server, bridge);
|
|
2241
2877
|
registerStudioTools(server, bridge);
|
|
2878
|
+
registerBuildingTools(server, bridge);
|
|
2879
|
+
registerMetadataTools(server, bridge);
|
|
2242
2880
|
registerParallelTaskTool(server, bridge);
|
|
2243
2881
|
registerDocsTools(server);
|
|
2244
2882
|
registerDominusResources(server, bridge);
|