dominus-cli 2.3.0 → 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 +29 -1
- package/README.md +18 -3
- package/dist/bridge-daemon.js.map +1 -1
- package/dist/install-plugin.js.map +1 -1
- package/dist/mcp.js +364 -9
- package/dist/mcp.js.map +1 -1
- package/package.json +1 -1
- package/plugin/Dominus.rbxmx +572 -8
- package/plugin/src/CommandRouter.lua +16 -0
- package/plugin/src/Metadata.lua +411 -0
- package/plugin/src/Mutation.lua +15 -0
- package/plugin/src/ValueCodec.lua +116 -0
package/dist/mcp.js
CHANGED
|
@@ -117,6 +117,11 @@ var CliMsg = {
|
|
|
117
117
|
V2_CREATE_WELDS: "studio:v2:create_welds",
|
|
118
118
|
V2_QUERY_PARTS: "studio:v2:query_parts",
|
|
119
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",
|
|
120
125
|
V2_DELETE: "studio:v2:delete",
|
|
121
126
|
V2_BUILD_UI: "studio:v2:build_ui",
|
|
122
127
|
V2_SNAPSHOT_UI: "studio:v2:snapshot_ui",
|
|
@@ -572,6 +577,8 @@ For broad tasks with independent Studio branches, prefer run_parallel_task when
|
|
|
572
577
|
Building workflow:
|
|
573
578
|
- Use studio_create_parts for multi-part structures instead of many generic create operations.
|
|
574
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.
|
|
575
582
|
- Use studio_transform_instances for Models and BaseParts so model layouts move through PVInstance pivots.
|
|
576
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.
|
|
577
584
|
- Use studio_set_selection when surfacing completed objects to the Studio user.
|
|
@@ -580,7 +587,7 @@ Safety rules:
|
|
|
580
587
|
- Never invent instance IDs or Roblox API members.
|
|
581
588
|
- Do not retry a failed write unchanged. Inspect the error and current state first.
|
|
582
589
|
- Deletion is a separate destructive tool and requires explicit user intent.
|
|
583
|
-
- studio_apply is transactional and supports setProperties, create, clone, and move operations. 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.
|
|
584
591
|
- UI fidelity work must snapshot and inspect the result after building; verify fonts, UDim2 values, colors, strokes, and hierarchy.
|
|
585
592
|
- Dominus intentionally has no arbitrary Luau execution tool.`;
|
|
586
593
|
|
|
@@ -1447,6 +1454,260 @@ function registerDocsTools(server) {
|
|
|
1447
1454
|
}
|
|
1448
1455
|
);
|
|
1449
1456
|
}
|
|
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
|
+
}
|
|
1450
1711
|
var propertiesSchema2 = z.record(z.unknown()).refine(
|
|
1451
1712
|
(properties) => Object.keys(properties).length <= 100 && !Object.keys(properties).some((name) => ["Parent", "ClassName", "Source"].includes(name)),
|
|
1452
1713
|
"At most 100 properties are allowed; Parent, ClassName, and Source require dedicated operations"
|
|
@@ -1476,7 +1737,9 @@ var mutationOperationSchema = z.discriminatedUnion("op", [
|
|
|
1476
1737
|
name: z.string().min(1).max(100),
|
|
1477
1738
|
offset: z.object({ x: z.number().finite(), y: z.number().finite(), z: z.number().finite() }).optional(),
|
|
1478
1739
|
properties: propertiesSchema2.optional()
|
|
1479
|
-
})
|
|
1740
|
+
}),
|
|
1741
|
+
attributeUpdateSchema.extend({ op: z.literal("setAttributes") }),
|
|
1742
|
+
tagUpdateSchema.extend({ op: z.literal("updateTags") })
|
|
1480
1743
|
]);
|
|
1481
1744
|
var workerProposalSchema = z.object({
|
|
1482
1745
|
workerId: z.string().min(1).max(64),
|
|
@@ -1704,7 +1967,7 @@ async function runWorker(server, bridge, goal, assignment, constraints, taskType
|
|
|
1704
1967
|
systemPrompt: [
|
|
1705
1968
|
"You are a read-only Dominus worker. Inspect only the Studio evidence in the request.",
|
|
1706
1969
|
"You cannot write to Studio. Return one strict JSON WorkerProposal and no Markdown.",
|
|
1707
|
-
"Only propose setProperties, create, clone, 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.",
|
|
1708
1971
|
"Never invent instanceId values. Never propose delete, script edits, or arbitrary execution."
|
|
1709
1972
|
].join(" "),
|
|
1710
1973
|
messages: [
|
|
@@ -1742,6 +2005,18 @@ async function runWorker(server, bridge, goal, assignment, constraints, taskType
|
|
|
1742
2005
|
parent: { instanceId: "uuid", pathSegments: ["..."] },
|
|
1743
2006
|
name: "CloneName",
|
|
1744
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: []
|
|
1745
2020
|
}
|
|
1746
2021
|
],
|
|
1747
2022
|
risks: ["string"],
|
|
@@ -1978,11 +2253,12 @@ async function verifyPlan(bridge, rootRef, originalRoot, operations) {
|
|
|
1978
2253
|
});
|
|
1979
2254
|
const missingCreates = expectedCreates.filter((path5) => !paths.has(path5));
|
|
1980
2255
|
const inspectOperations = operations.filter(
|
|
1981
|
-
(operation) => operation.op
|
|
2256
|
+
(operation) => operation.op === "setProperties" || operation.op === "move"
|
|
1982
2257
|
);
|
|
1983
2258
|
const inspectFailures = [];
|
|
1984
2259
|
const propertyMismatches = [];
|
|
1985
2260
|
const moveMismatches = [];
|
|
2261
|
+
const metadataMismatches = [];
|
|
1986
2262
|
for (let offset = 0; offset < inspectOperations.length; offset += 20) {
|
|
1987
2263
|
const batch = inspectOperations.slice(offset, offset + 20);
|
|
1988
2264
|
const targets = batch.map((operation) => operation.target);
|
|
@@ -2027,8 +2303,77 @@ async function verifyPlan(bridge, rootRef, originalRoot, operations) {
|
|
|
2027
2303
|
}
|
|
2028
2304
|
});
|
|
2029
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
|
+
}
|
|
2030
2375
|
return {
|
|
2031
|
-
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,
|
|
2032
2377
|
treeNodeCount: tree.nodeCount,
|
|
2033
2378
|
treeTruncated: tree.truncated,
|
|
2034
2379
|
expectedCreates: expectedCreates.length,
|
|
@@ -2036,6 +2381,7 @@ async function verifyPlan(bridge, rootRef, originalRoot, operations) {
|
|
|
2036
2381
|
missingCreates,
|
|
2037
2382
|
propertyMismatches,
|
|
2038
2383
|
moveMismatches,
|
|
2384
|
+
metadataMismatches,
|
|
2039
2385
|
inspectFailures
|
|
2040
2386
|
};
|
|
2041
2387
|
}
|
|
@@ -2044,6 +2390,7 @@ function findRepairOperations(operations, verification, originalRoot) {
|
|
|
2044
2390
|
const missingCreates = new Set(verification.missingCreates);
|
|
2045
2391
|
const mismatchedTargets = new Set(verification.propertyMismatches.map((item) => item.target));
|
|
2046
2392
|
const mismatchedMoves = new Set(verification.moveMismatches.map((item) => item.target));
|
|
2393
|
+
const mismatchedMetadata = new Set(verification.metadataMismatches.map((item) => item.target));
|
|
2047
2394
|
return operations.filter((operation) => {
|
|
2048
2395
|
if (operation.op === "create") {
|
|
2049
2396
|
const parentPath = resolveRefPath(operation.parent, knownPaths);
|
|
@@ -2051,6 +2398,9 @@ function findRepairOperations(operations, verification, originalRoot) {
|
|
|
2051
2398
|
}
|
|
2052
2399
|
if (operation.op === "clone") return false;
|
|
2053
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
|
+
}
|
|
2054
2404
|
return mismatchedMoves.has(refLabel(operation.target));
|
|
2055
2405
|
});
|
|
2056
2406
|
}
|
|
@@ -2253,11 +2603,15 @@ var cloneOperation = z.object({
|
|
|
2253
2603
|
offset: z.object({ x: z.number().finite(), y: z.number().finite(), z: z.number().finite() }).optional(),
|
|
2254
2604
|
properties: propertiesSchema3.optional()
|
|
2255
2605
|
});
|
|
2606
|
+
var setAttributesOperation = attributeUpdateSchema.extend({ op: z.literal("setAttributes") });
|
|
2607
|
+
var updateTagsOperation = tagUpdateSchema.extend({ op: z.literal("updateTags") });
|
|
2256
2608
|
var mutationOperation = z.discriminatedUnion("op", [
|
|
2257
2609
|
setPropertiesOperation,
|
|
2258
2610
|
createOperation,
|
|
2259
2611
|
cloneOperation,
|
|
2260
|
-
moveOperation
|
|
2612
|
+
moveOperation,
|
|
2613
|
+
setAttributesOperation,
|
|
2614
|
+
updateTagsOperation
|
|
2261
2615
|
]);
|
|
2262
2616
|
var uiNodeSchema = z.lazy(
|
|
2263
2617
|
() => z.object({
|
|
@@ -2367,7 +2721,7 @@ function registerStudioTools(server, bridge) {
|
|
|
2367
2721
|
"studio_apply",
|
|
2368
2722
|
{
|
|
2369
2723
|
title: "Apply Atomic Studio Mutations",
|
|
2370
|
-
description: "Atomically set properties, create instances, deep-clone 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.",
|
|
2371
2725
|
inputSchema: { operations: z.array(mutationOperation).min(1).max(100) },
|
|
2372
2726
|
outputSchema: toolOutputSchema,
|
|
2373
2727
|
annotations: {
|
|
@@ -2493,7 +2847,7 @@ function registerStudioTools(server, bridge) {
|
|
|
2493
2847
|
"studio_get_reflection",
|
|
2494
2848
|
{
|
|
2495
2849
|
title: "Read Live Roblox Reflection",
|
|
2496
|
-
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.",
|
|
2497
2851
|
inputSchema: { className: z.string().min(1).max(100) },
|
|
2498
2852
|
outputSchema: toolOutputSchema,
|
|
2499
2853
|
annotations: {
|
|
@@ -2508,7 +2862,7 @@ function registerStudioTools(server, bridge) {
|
|
|
2508
2862
|
}
|
|
2509
2863
|
|
|
2510
2864
|
// src/mcp/server.ts
|
|
2511
|
-
var VERSION = "2.
|
|
2865
|
+
var VERSION = "2.4.0";
|
|
2512
2866
|
function createDominusMcpServer(bridge) {
|
|
2513
2867
|
const server = new McpServer(
|
|
2514
2868
|
{
|
|
@@ -2522,6 +2876,7 @@ function createDominusMcpServer(bridge) {
|
|
|
2522
2876
|
registerConnectionTools(server, bridge);
|
|
2523
2877
|
registerStudioTools(server, bridge);
|
|
2524
2878
|
registerBuildingTools(server, bridge);
|
|
2879
|
+
registerMetadataTools(server, bridge);
|
|
2525
2880
|
registerParallelTaskTool(server, bridge);
|
|
2526
2881
|
registerDocsTools(server);
|
|
2527
2882
|
registerDominusResources(server, bridge);
|