dominus-cli 2.2.1 → 2.3.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 +30 -0
- package/README.md +16 -2
- package/dist/bridge-daemon.js.map +1 -1
- package/dist/install-plugin.js.map +1 -1
- package/dist/mcp.js +299 -16
- package/dist/mcp.js.map +1 -1
- package/package.json +5 -5
- package/plugin/Dominus.rbxmx +500 -12
- package/plugin/src/Building.lua +427 -0
- package/plugin/src/CommandRouter.lua +25 -0
- package/plugin/src/Mutation.lua +30 -0
package/dist/mcp.js
CHANGED
|
@@ -109,6 +109,14 @@ 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",
|
|
112
120
|
V2_DELETE: "studio:v2:delete",
|
|
113
121
|
V2_BUILD_UI: "studio:v2:build_ui",
|
|
114
122
|
V2_SNAPSHOT_UI: "studio:v2:snapshot_ui",
|
|
@@ -561,11 +569,18 @@ Required workflow for Studio changes:
|
|
|
561
569
|
|
|
562
570
|
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
571
|
|
|
572
|
+
Building workflow:
|
|
573
|
+
- Use studio_create_parts for multi-part structures instead of many generic create operations.
|
|
574
|
+
- Use studio_clone_instances when an existing instance or model is the intended template. Clone rather than manually recreating descendants and properties.
|
|
575
|
+
- Use studio_transform_instances for Models and BaseParts so model layouts move through PVInstance pivots.
|
|
576
|
+
- 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.
|
|
577
|
+
- Use studio_set_selection when surfacing completed objects to the Studio user.
|
|
578
|
+
|
|
564
579
|
Safety rules:
|
|
565
580
|
- Never invent instance IDs or Roblox API members.
|
|
566
581
|
- Do not retry a failed write unchanged. Inspect the error and current state first.
|
|
567
582
|
- 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.
|
|
583
|
+
- studio_apply is transactional and supports setProperties, create, clone, and move operations. Keep related edits together, but stay within its documented limits.
|
|
569
584
|
- UI fidelity work must snapshot and inspect the result after building; verify fonts, UDim2 values, colors, strokes, and hierarchy.
|
|
570
585
|
- Dominus intentionally has no arbitrary Luau execution tool.`;
|
|
571
586
|
|
|
@@ -632,6 +647,8 @@ function failure(error, data) {
|
|
|
632
647
|
data
|
|
633
648
|
});
|
|
634
649
|
}
|
|
650
|
+
|
|
651
|
+
// src/mcp/tools/shared.ts
|
|
635
652
|
var instanceRefSchema = z.object({
|
|
636
653
|
instanceId: z.string().uuid().optional(),
|
|
637
654
|
pathSegments: z.array(z.string().min(1).max(256)).min(1).max(100).optional()
|
|
@@ -664,7 +681,245 @@ function ref(value) {
|
|
|
664
681
|
return value;
|
|
665
682
|
}
|
|
666
683
|
|
|
667
|
-
// src/mcp/tools/
|
|
684
|
+
// src/mcp/tools/building.ts
|
|
685
|
+
var finiteNumber = z.number().finite();
|
|
686
|
+
var vector3Schema = z.object({ x: finiteNumber, y: finiteNumber, z: finiteNumber }).strict();
|
|
687
|
+
var propertiesSchema = z.record(z.unknown()).refine(
|
|
688
|
+
(properties) => Object.keys(properties).length <= 100 && !Object.keys(properties).some((name) => ["Parent", "ClassName", "Source"].includes(name)),
|
|
689
|
+
"At most 100 properties are allowed; Parent, ClassName, and Source require dedicated operations"
|
|
690
|
+
);
|
|
691
|
+
var partSchema = z.object({
|
|
692
|
+
name: z.string().min(1).max(100),
|
|
693
|
+
className: z.enum([
|
|
694
|
+
"Part",
|
|
695
|
+
"WedgePart",
|
|
696
|
+
"CornerWedgePart",
|
|
697
|
+
"TrussPart",
|
|
698
|
+
"MeshPart",
|
|
699
|
+
"SpawnLocation",
|
|
700
|
+
"Seat",
|
|
701
|
+
"VehicleSeat"
|
|
702
|
+
]).optional().default("Part"),
|
|
703
|
+
shape: z.enum(["Block", "Ball", "Cylinder", "Wedge", "CornerWedge"]).optional(),
|
|
704
|
+
position: vector3Schema.optional(),
|
|
705
|
+
orientation: vector3Schema.optional(),
|
|
706
|
+
size: vector3Schema.optional(),
|
|
707
|
+
color: z.string().min(1).max(100).optional(),
|
|
708
|
+
material: z.string().min(1).max(100).optional(),
|
|
709
|
+
transparency: z.number().min(0).max(1).optional(),
|
|
710
|
+
anchored: z.boolean().optional().default(true),
|
|
711
|
+
canCollide: z.boolean().optional(),
|
|
712
|
+
properties: propertiesSchema.optional()
|
|
713
|
+
});
|
|
714
|
+
var cloneRequestSchema = z.object({
|
|
715
|
+
target: instanceRefSchema,
|
|
716
|
+
parent: instanceRefSchema.optional(),
|
|
717
|
+
name: z.string().min(1).max(100).optional(),
|
|
718
|
+
count: z.number().int().min(1).max(50).optional().default(1),
|
|
719
|
+
offset: vector3Schema.optional(),
|
|
720
|
+
stepOffset: vector3Schema.optional(),
|
|
721
|
+
properties: propertiesSchema.optional()
|
|
722
|
+
});
|
|
723
|
+
var cframeSchema = z.array(finiteNumber).refine((value) => value.length === 3 || value.length === 12, "CFrame requires 3 or 12 numbers");
|
|
724
|
+
var transformOperationSchema = z.object({
|
|
725
|
+
target: instanceRefSchema,
|
|
726
|
+
mode: z.enum(["absolute", "relative"]).optional().default("absolute"),
|
|
727
|
+
cframe: cframeSchema.optional(),
|
|
728
|
+
position: vector3Schema.optional(),
|
|
729
|
+
orientation: vector3Schema.optional(),
|
|
730
|
+
offset: vector3Schema.optional(),
|
|
731
|
+
rotationOffset: vector3Schema.optional(),
|
|
732
|
+
space: z.enum(["world", "local"]).optional().default("world")
|
|
733
|
+
}).refine(
|
|
734
|
+
(value) => value.mode === "relative" ? Boolean(value.offset || value.rotationOffset) : Boolean(value.cframe || value.position || value.orientation),
|
|
735
|
+
"Absolute transforms require cframe, position, or orientation; relative transforms require offset or rotationOffset"
|
|
736
|
+
);
|
|
737
|
+
function registerBuildingTools(server, bridge) {
|
|
738
|
+
server.registerTool(
|
|
739
|
+
"studio_create_parts",
|
|
740
|
+
{
|
|
741
|
+
title: "Build Roblox Parts",
|
|
742
|
+
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.",
|
|
743
|
+
inputSchema: {
|
|
744
|
+
parent: instanceRefSchema.optional(),
|
|
745
|
+
groupName: z.string().min(1).max(100).optional(),
|
|
746
|
+
groupClass: z.enum(["Model", "Folder"]).optional().default("Model"),
|
|
747
|
+
parts: z.array(partSchema).min(1).max(200)
|
|
748
|
+
},
|
|
749
|
+
outputSchema: toolOutputSchema,
|
|
750
|
+
annotations: {
|
|
751
|
+
readOnlyHint: false,
|
|
752
|
+
destructiveHint: false,
|
|
753
|
+
idempotentHint: false,
|
|
754
|
+
openWorldHint: false
|
|
755
|
+
}
|
|
756
|
+
},
|
|
757
|
+
({ parent, groupName, groupClass, parts }) => callStudio(bridge, CliMsg.V2_CREATE_PARTS, { parent, groupName, groupClass, parts }, 12e4)
|
|
758
|
+
);
|
|
759
|
+
server.registerTool(
|
|
760
|
+
"studio_clone_instances",
|
|
761
|
+
{
|
|
762
|
+
title: "Clone Roblox Instances",
|
|
763
|
+
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.",
|
|
764
|
+
inputSchema: { requests: z.array(cloneRequestSchema).min(1).max(100) },
|
|
765
|
+
outputSchema: toolOutputSchema,
|
|
766
|
+
annotations: {
|
|
767
|
+
readOnlyHint: false,
|
|
768
|
+
destructiveHint: false,
|
|
769
|
+
idempotentHint: false,
|
|
770
|
+
openWorldHint: false
|
|
771
|
+
}
|
|
772
|
+
},
|
|
773
|
+
({ requests }) => callStudio(bridge, CliMsg.V2_CLONE_INSTANCES, { requests }, 12e4)
|
|
774
|
+
);
|
|
775
|
+
server.registerTool(
|
|
776
|
+
"studio_group_instances",
|
|
777
|
+
{
|
|
778
|
+
title: "Group Roblox Instances",
|
|
779
|
+
description: "Create a Model or Folder and move up to 100 independent instances into it in one undoable operation.",
|
|
780
|
+
inputSchema: {
|
|
781
|
+
targets: z.array(instanceRefSchema).min(1).max(100),
|
|
782
|
+
name: z.string().min(1).max(100),
|
|
783
|
+
className: z.enum(["Model", "Folder"]).optional().default("Model"),
|
|
784
|
+
parent: instanceRefSchema.optional()
|
|
785
|
+
},
|
|
786
|
+
outputSchema: toolOutputSchema,
|
|
787
|
+
annotations: {
|
|
788
|
+
readOnlyHint: false,
|
|
789
|
+
destructiveHint: false,
|
|
790
|
+
idempotentHint: false,
|
|
791
|
+
openWorldHint: false
|
|
792
|
+
}
|
|
793
|
+
},
|
|
794
|
+
({ targets, name, className, parent }) => callStudio(bridge, CliMsg.V2_GROUP_INSTANCES, { targets, name, className, parent }, 6e4)
|
|
795
|
+
);
|
|
796
|
+
server.registerTool(
|
|
797
|
+
"studio_ungroup_instances",
|
|
798
|
+
{
|
|
799
|
+
title: "Ungroup Roblox Instances",
|
|
800
|
+
description: "Move every child out of up to 20 Models or Folders, then remove the empty containers in one undoable operation.",
|
|
801
|
+
inputSchema: {
|
|
802
|
+
targets: z.array(instanceRefSchema).min(1).max(20),
|
|
803
|
+
parent: instanceRefSchema.optional(),
|
|
804
|
+
confirm: z.literal(true)
|
|
805
|
+
},
|
|
806
|
+
outputSchema: toolOutputSchema,
|
|
807
|
+
annotations: {
|
|
808
|
+
readOnlyHint: false,
|
|
809
|
+
destructiveHint: true,
|
|
810
|
+
idempotentHint: false,
|
|
811
|
+
openWorldHint: false
|
|
812
|
+
}
|
|
813
|
+
},
|
|
814
|
+
({ targets, parent, confirm }) => callStudio(bridge, CliMsg.V2_UNGROUP_INSTANCES, { targets, parent, confirm }, 6e4)
|
|
815
|
+
);
|
|
816
|
+
server.registerTool(
|
|
817
|
+
"studio_transform_instances",
|
|
818
|
+
{
|
|
819
|
+
title: "Transform Roblox Models and Parts",
|
|
820
|
+
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.",
|
|
821
|
+
inputSchema: { operations: z.array(transformOperationSchema).min(1).max(100) },
|
|
822
|
+
outputSchema: toolOutputSchema,
|
|
823
|
+
annotations: {
|
|
824
|
+
readOnlyHint: false,
|
|
825
|
+
destructiveHint: false,
|
|
826
|
+
idempotentHint: false,
|
|
827
|
+
openWorldHint: false
|
|
828
|
+
}
|
|
829
|
+
},
|
|
830
|
+
({ operations }) => callStudio(bridge, CliMsg.V2_TRANSFORM_INSTANCES, { operations }, 6e4)
|
|
831
|
+
);
|
|
832
|
+
server.registerTool(
|
|
833
|
+
"studio_create_welds",
|
|
834
|
+
{
|
|
835
|
+
title: "Create Roblox Welds",
|
|
836
|
+
description: "Create up to 200 WeldConstraints between explicit BasePart refs in one undoable operation.",
|
|
837
|
+
inputSchema: {
|
|
838
|
+
welds: z.array(
|
|
839
|
+
z.object({
|
|
840
|
+
part0: instanceRefSchema,
|
|
841
|
+
part1: instanceRefSchema,
|
|
842
|
+
name: z.string().min(1).max(100).optional(),
|
|
843
|
+
parent: instanceRefSchema.optional()
|
|
844
|
+
})
|
|
845
|
+
).min(1).max(200)
|
|
846
|
+
},
|
|
847
|
+
outputSchema: toolOutputSchema,
|
|
848
|
+
annotations: {
|
|
849
|
+
readOnlyHint: false,
|
|
850
|
+
destructiveHint: false,
|
|
851
|
+
idempotentHint: false,
|
|
852
|
+
openWorldHint: false
|
|
853
|
+
}
|
|
854
|
+
},
|
|
855
|
+
({ welds }) => callStudio(bridge, CliMsg.V2_CREATE_WELDS, { welds }, 6e4)
|
|
856
|
+
);
|
|
857
|
+
server.registerTool(
|
|
858
|
+
"studio_query_parts",
|
|
859
|
+
{
|
|
860
|
+
title: "Query Parts in 3D Space",
|
|
861
|
+
description: "Find BaseParts intersecting an oriented box or radius using WorldRoot spatial queries. Returns stable refs, positions, and sizes for inspection or later mutations.",
|
|
862
|
+
inputSchema: {
|
|
863
|
+
root: instanceRefSchema.optional(),
|
|
864
|
+
mode: z.enum(["box", "radius"]).optional().default("box"),
|
|
865
|
+
position: vector3Schema,
|
|
866
|
+
orientation: vector3Schema.optional(),
|
|
867
|
+
size: vector3Schema.optional(),
|
|
868
|
+
radius: z.number().finite().positive().optional(),
|
|
869
|
+
maxParts: z.number().int().min(1).max(500).optional().default(200),
|
|
870
|
+
filter: z.array(instanceRefSchema).max(100).optional(),
|
|
871
|
+
filterType: z.enum(["Include", "Exclude"]).optional().default("Exclude"),
|
|
872
|
+
respectCanCollide: z.boolean().optional().default(false)
|
|
873
|
+
},
|
|
874
|
+
outputSchema: toolOutputSchema,
|
|
875
|
+
annotations: {
|
|
876
|
+
readOnlyHint: true,
|
|
877
|
+
destructiveHint: false,
|
|
878
|
+
idempotentHint: true,
|
|
879
|
+
openWorldHint: false
|
|
880
|
+
}
|
|
881
|
+
},
|
|
882
|
+
({
|
|
883
|
+
root,
|
|
884
|
+
mode,
|
|
885
|
+
position,
|
|
886
|
+
orientation,
|
|
887
|
+
size,
|
|
888
|
+
radius,
|
|
889
|
+
maxParts,
|
|
890
|
+
filter,
|
|
891
|
+
filterType,
|
|
892
|
+
respectCanCollide
|
|
893
|
+
}) => callStudio(bridge, CliMsg.V2_QUERY_PARTS, {
|
|
894
|
+
root,
|
|
895
|
+
mode,
|
|
896
|
+
position,
|
|
897
|
+
orientation,
|
|
898
|
+
size,
|
|
899
|
+
radius,
|
|
900
|
+
maxParts,
|
|
901
|
+
filter,
|
|
902
|
+
filterType,
|
|
903
|
+
respectCanCollide
|
|
904
|
+
})
|
|
905
|
+
);
|
|
906
|
+
server.registerTool(
|
|
907
|
+
"studio_set_selection",
|
|
908
|
+
{
|
|
909
|
+
title: "Set Roblox Studio Selection",
|
|
910
|
+
description: "Replace the current Studio selection with up to 200 stable instance refs. Pass an empty array to clear selection.",
|
|
911
|
+
inputSchema: { targets: z.array(instanceRefSchema).max(200) },
|
|
912
|
+
outputSchema: toolOutputSchema,
|
|
913
|
+
annotations: {
|
|
914
|
+
readOnlyHint: false,
|
|
915
|
+
destructiveHint: false,
|
|
916
|
+
idempotentHint: true,
|
|
917
|
+
openWorldHint: false
|
|
918
|
+
}
|
|
919
|
+
},
|
|
920
|
+
({ targets }) => callStudio(bridge, CliMsg.V2_SET_SELECTION, { targets })
|
|
921
|
+
);
|
|
922
|
+
}
|
|
668
923
|
function registerConnectionTools(server, bridge) {
|
|
669
924
|
server.registerTool(
|
|
670
925
|
"dominus_status",
|
|
@@ -1192,7 +1447,7 @@ function registerDocsTools(server) {
|
|
|
1192
1447
|
}
|
|
1193
1448
|
);
|
|
1194
1449
|
}
|
|
1195
|
-
var
|
|
1450
|
+
var propertiesSchema2 = z.record(z.unknown()).refine(
|
|
1196
1451
|
(properties) => Object.keys(properties).length <= 100 && !Object.keys(properties).some((name) => ["Parent", "ClassName", "Source"].includes(name)),
|
|
1197
1452
|
"At most 100 properties are allowed; Parent, ClassName, and Source require dedicated operations"
|
|
1198
1453
|
);
|
|
@@ -1200,19 +1455,27 @@ var mutationOperationSchema = z.discriminatedUnion("op", [
|
|
|
1200
1455
|
z.object({
|
|
1201
1456
|
op: z.literal("setProperties"),
|
|
1202
1457
|
target: instanceRefSchema,
|
|
1203
|
-
properties:
|
|
1458
|
+
properties: propertiesSchema2
|
|
1204
1459
|
}),
|
|
1205
1460
|
z.object({
|
|
1206
1461
|
op: z.literal("create"),
|
|
1207
1462
|
parent: instanceRefSchema,
|
|
1208
1463
|
className: z.string().min(1).max(100),
|
|
1209
1464
|
name: z.string().min(1).max(256),
|
|
1210
|
-
properties:
|
|
1465
|
+
properties: propertiesSchema2.optional()
|
|
1211
1466
|
}),
|
|
1212
1467
|
z.object({
|
|
1213
1468
|
op: z.literal("move"),
|
|
1214
1469
|
target: instanceRefSchema,
|
|
1215
1470
|
parent: instanceRefSchema
|
|
1471
|
+
}),
|
|
1472
|
+
z.object({
|
|
1473
|
+
op: z.literal("clone"),
|
|
1474
|
+
target: instanceRefSchema,
|
|
1475
|
+
parent: instanceRefSchema,
|
|
1476
|
+
name: z.string().min(1).max(100),
|
|
1477
|
+
offset: z.object({ x: z.number().finite(), y: z.number().finite(), z: z.number().finite() }).optional(),
|
|
1478
|
+
properties: propertiesSchema2.optional()
|
|
1216
1479
|
})
|
|
1217
1480
|
]);
|
|
1218
1481
|
var workerProposalSchema = z.object({
|
|
@@ -1441,7 +1704,7 @@ async function runWorker(server, bridge, goal, assignment, constraints, taskType
|
|
|
1441
1704
|
systemPrompt: [
|
|
1442
1705
|
"You are a read-only Dominus worker. Inspect only the Studio evidence in the request.",
|
|
1443
1706
|
"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.",
|
|
1707
|
+
"Only propose setProperties, create, clone, or move operations using exact instance refs copied from evidence.",
|
|
1445
1708
|
"Never invent instanceId values. Never propose delete, script edits, or arbitrary execution."
|
|
1446
1709
|
].join(" "),
|
|
1447
1710
|
messages: [
|
|
@@ -1472,6 +1735,13 @@ async function runWorker(server, bridge, goal, assignment, constraints, taskType
|
|
|
1472
1735
|
op: "move",
|
|
1473
1736
|
target: { instanceId: "uuid", pathSegments: ["..."] },
|
|
1474
1737
|
parent: { instanceId: "uuid", pathSegments: ["..."] }
|
|
1738
|
+
},
|
|
1739
|
+
{
|
|
1740
|
+
op: "clone",
|
|
1741
|
+
target: { instanceId: "uuid", pathSegments: ["..."] },
|
|
1742
|
+
parent: { instanceId: "uuid", pathSegments: ["..."] },
|
|
1743
|
+
name: "CloneName",
|
|
1744
|
+
offset: { x: 0, y: 0, z: 0 }
|
|
1475
1745
|
}
|
|
1476
1746
|
],
|
|
1477
1747
|
risks: ["string"],
|
|
@@ -1556,7 +1826,7 @@ function validateWorkerResults(results) {
|
|
|
1556
1826
|
function validateOperations(assignment, operations) {
|
|
1557
1827
|
const errors = [];
|
|
1558
1828
|
for (const [index, operation] of operations.entries()) {
|
|
1559
|
-
const refs = operation.op === "create" ? [operation.parent] : operation.op === "move" ? [operation.target, operation.parent] : [operation.target];
|
|
1829
|
+
const refs = operation.op === "create" ? [operation.parent] : operation.op === "move" || operation.op === "clone" ? [operation.target, operation.parent] : [operation.target];
|
|
1560
1830
|
for (const ref2 of refs) {
|
|
1561
1831
|
if (!isKnownParallelRef(ref2, assignment.knownRefKeys)) {
|
|
1562
1832
|
errors.push(`Operation ${index} references an instance absent from worker evidence`);
|
|
@@ -1702,12 +1972,14 @@ async function verifyPlan(bridge, rootRef, originalRoot, operations) {
|
|
|
1702
1972
|
);
|
|
1703
1973
|
const knownPaths = collectInstancePaths(originalRoot);
|
|
1704
1974
|
const expectedCreates = operations.flatMap((operation) => {
|
|
1705
|
-
if (operation.op !== "create") return [];
|
|
1975
|
+
if (operation.op !== "create" && operation.op !== "clone") return [];
|
|
1706
1976
|
const parentPath = resolveRefPath(operation.parent, knownPaths);
|
|
1707
1977
|
return parentPath ? [pathKey([...parentPath, operation.name])] : [];
|
|
1708
1978
|
});
|
|
1709
1979
|
const missingCreates = expectedCreates.filter((path5) => !paths.has(path5));
|
|
1710
|
-
const inspectOperations = operations.filter(
|
|
1980
|
+
const inspectOperations = operations.filter(
|
|
1981
|
+
(operation) => operation.op !== "create" && operation.op !== "clone"
|
|
1982
|
+
);
|
|
1711
1983
|
const inspectFailures = [];
|
|
1712
1984
|
const propertyMismatches = [];
|
|
1713
1985
|
const moveMismatches = [];
|
|
@@ -1777,6 +2049,7 @@ function findRepairOperations(operations, verification, originalRoot) {
|
|
|
1777
2049
|
const parentPath = resolveRefPath(operation.parent, knownPaths);
|
|
1778
2050
|
return Boolean(parentPath && missingCreates.has(pathKey([...parentPath, operation.name])));
|
|
1779
2051
|
}
|
|
2052
|
+
if (operation.op === "clone") return false;
|
|
1780
2053
|
if (operation.op === "setProperties") return mismatchedTargets.has(refLabel(operation.target));
|
|
1781
2054
|
return mismatchedMoves.has(refLabel(operation.target));
|
|
1782
2055
|
});
|
|
@@ -1846,7 +2119,7 @@ function isParallelRefWithinScopes(ref2, scopes) {
|
|
|
1846
2119
|
return scopes.some((scope) => Boolean(ref2.instanceId && scope.instanceId === ref2.instanceId));
|
|
1847
2120
|
}
|
|
1848
2121
|
function operationTarget(operation) {
|
|
1849
|
-
if (operation.op === "create") {
|
|
2122
|
+
if (operation.op === "create" || operation.op === "clone") {
|
|
1850
2123
|
const parentPath = operation.parent.pathSegments;
|
|
1851
2124
|
return {
|
|
1852
2125
|
key: `${refKey(operation.parent)}/create:${operation.name}`,
|
|
@@ -1951,37 +2224,46 @@ function emit(server, type, data) {
|
|
|
1951
2224
|
function toErrorMessage(error) {
|
|
1952
2225
|
return error instanceof Error ? error.message : String(error);
|
|
1953
2226
|
}
|
|
1954
|
-
var
|
|
2227
|
+
var propertiesSchema3 = z.record(z.unknown()).refine(
|
|
1955
2228
|
(properties) => Object.keys(properties).length <= 100 && !Object.keys(properties).some((name) => ["Parent", "ClassName", "Source"].includes(name)),
|
|
1956
2229
|
"At most 100 properties are allowed; Parent, ClassName, and Source require dedicated operations"
|
|
1957
2230
|
);
|
|
1958
2231
|
var setPropertiesOperation = z.object({
|
|
1959
2232
|
op: z.literal("setProperties"),
|
|
1960
2233
|
target: instanceRefSchema,
|
|
1961
|
-
properties:
|
|
2234
|
+
properties: propertiesSchema3
|
|
1962
2235
|
});
|
|
1963
2236
|
var createOperation = z.object({
|
|
1964
2237
|
op: z.literal("create"),
|
|
1965
2238
|
parent: instanceRefSchema,
|
|
1966
2239
|
className: z.string().min(1).max(100),
|
|
1967
2240
|
name: z.string().min(1).max(256),
|
|
1968
|
-
properties:
|
|
2241
|
+
properties: propertiesSchema3.optional()
|
|
1969
2242
|
});
|
|
1970
2243
|
var moveOperation = z.object({
|
|
1971
2244
|
op: z.literal("move"),
|
|
1972
2245
|
target: instanceRefSchema,
|
|
1973
2246
|
parent: instanceRefSchema
|
|
1974
2247
|
});
|
|
2248
|
+
var cloneOperation = z.object({
|
|
2249
|
+
op: z.literal("clone"),
|
|
2250
|
+
target: instanceRefSchema,
|
|
2251
|
+
parent: instanceRefSchema.optional(),
|
|
2252
|
+
name: z.string().min(1).max(100).optional(),
|
|
2253
|
+
offset: z.object({ x: z.number().finite(), y: z.number().finite(), z: z.number().finite() }).optional(),
|
|
2254
|
+
properties: propertiesSchema3.optional()
|
|
2255
|
+
});
|
|
1975
2256
|
var mutationOperation = z.discriminatedUnion("op", [
|
|
1976
2257
|
setPropertiesOperation,
|
|
1977
2258
|
createOperation,
|
|
2259
|
+
cloneOperation,
|
|
1978
2260
|
moveOperation
|
|
1979
2261
|
]);
|
|
1980
2262
|
var uiNodeSchema = z.lazy(
|
|
1981
2263
|
() => z.object({
|
|
1982
2264
|
ClassName: z.string().min(1).max(100),
|
|
1983
2265
|
Name: z.string().min(1).max(256).optional(),
|
|
1984
|
-
properties:
|
|
2266
|
+
properties: propertiesSchema3.optional(),
|
|
1985
2267
|
Children: z.array(uiNodeSchema).max(1e3).optional()
|
|
1986
2268
|
})
|
|
1987
2269
|
);
|
|
@@ -2085,7 +2367,7 @@ function registerStudioTools(server, bridge) {
|
|
|
2085
2367
|
"studio_apply",
|
|
2086
2368
|
{
|
|
2087
2369
|
title: "Apply Atomic Studio Mutations",
|
|
2088
|
-
description: "Atomically set properties, create instances, and move instances. Any failed operation cancels and undoes the entire batch.",
|
|
2370
|
+
description: "Atomically set properties, create instances, deep-clone instances, and move instances. Any failed operation cancels and undoes the entire batch.",
|
|
2089
2371
|
inputSchema: { operations: z.array(mutationOperation).min(1).max(100) },
|
|
2090
2372
|
outputSchema: toolOutputSchema,
|
|
2091
2373
|
annotations: {
|
|
@@ -2226,7 +2508,7 @@ function registerStudioTools(server, bridge) {
|
|
|
2226
2508
|
}
|
|
2227
2509
|
|
|
2228
2510
|
// src/mcp/server.ts
|
|
2229
|
-
var VERSION = "2.
|
|
2511
|
+
var VERSION = "2.3.0";
|
|
2230
2512
|
function createDominusMcpServer(bridge) {
|
|
2231
2513
|
const server = new McpServer(
|
|
2232
2514
|
{
|
|
@@ -2239,6 +2521,7 @@ function createDominusMcpServer(bridge) {
|
|
|
2239
2521
|
);
|
|
2240
2522
|
registerConnectionTools(server, bridge);
|
|
2241
2523
|
registerStudioTools(server, bridge);
|
|
2524
|
+
registerBuildingTools(server, bridge);
|
|
2242
2525
|
registerParallelTaskTool(server, bridge);
|
|
2243
2526
|
registerDocsTools(server);
|
|
2244
2527
|
registerDominusResources(server, bridge);
|