@el4cteo/rbx-studio-mcp 0.1.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/LICENSE +21 -0
- package/README.md +203 -0
- package/dist/bridge/rpc.js +243 -0
- package/dist/bridge/rpc.js.map +1 -0
- package/dist/bridge/server.js +281 -0
- package/dist/bridge/server.js.map +1 -0
- package/dist/index.js +104 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/apidump.js +269 -0
- package/dist/lib/apidump.js.map +1 -0
- package/dist/lib/errors.js +38 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/format.js +191 -0
- package/dist/lib/format.js.map +1 -0
- package/dist/lib/pluginbuild.js +83 -0
- package/dist/lib/pluginbuild.js.map +1 -0
- package/dist/lib/png.js +84 -0
- package/dist/lib/png.js.map +1 -0
- package/dist/lib/protocol.js +22 -0
- package/dist/lib/protocol.js.map +1 -0
- package/dist/lib/tool.js +27 -0
- package/dist/lib/tool.js.map +1 -0
- package/dist/resources.js +70 -0
- package/dist/resources.js.map +1 -0
- package/dist/tools/api.js +78 -0
- package/dist/tools/api.js.map +1 -0
- package/dist/tools/character.js +94 -0
- package/dist/tools/character.js.map +1 -0
- package/dist/tools/debug.js +211 -0
- package/dist/tools/debug.js.map +1 -0
- package/dist/tools/device.js +74 -0
- package/dist/tools/device.js.map +1 -0
- package/dist/tools/discover.js +217 -0
- package/dist/tools/discover.js.map +1 -0
- package/dist/tools/exec.js +191 -0
- package/dist/tools/exec.js.map +1 -0
- package/dist/tools/input.js +96 -0
- package/dist/tools/input.js.map +1 -0
- package/dist/tools/instances.js +261 -0
- package/dist/tools/instances.js.map +1 -0
- package/dist/tools/perf.js +367 -0
- package/dist/tools/perf.js.map +1 -0
- package/dist/tools/playtest.js +153 -0
- package/dist/tools/playtest.js.map +1 -0
- package/dist/tools/screenshot.js +75 -0
- package/dist/tools/screenshot.js.map +1 -0
- package/dist/tools/scripts.js +316 -0
- package/dist/tools/scripts.js.map +1 -0
- package/dist/tools/session.js +152 -0
- package/dist/tools/session.js.map +1 -0
- package/dist/tools/world.js +281 -0
- package/dist/tools/world.js.map +1 -0
- package/package.json +62 -0
- package/plugin/default.project.json +6 -0
- package/plugin/src/Config.luau +59 -0
- package/plugin/src/Console.luau +657 -0
- package/plugin/src/Context.luau +35 -0
- package/plugin/src/Dispatch.luau +90 -0
- package/plugin/src/Editor.luau +142 -0
- package/plugin/src/Emulation.luau +151 -0
- package/plugin/src/LogBuffer.luau +277 -0
- package/plugin/src/Net.luau +102 -0
- package/plugin/src/Paths.luau +255 -0
- package/plugin/src/Phrase.luau +465 -0
- package/plugin/src/Png.luau +238 -0
- package/plugin/src/Scope.luau +78 -0
- package/plugin/src/ScriptEdit.luau +100 -0
- package/plugin/src/Serialize.luau +287 -0
- package/plugin/src/TextEdit.luau +296 -0
- package/plugin/src/Transport.luau +328 -0
- package/plugin/src/Undo.luau +72 -0
- package/plugin/src/Visuals.luau +710 -0
- package/plugin/src/handlers/Api.luau +242 -0
- package/plugin/src/handlers/Assets.luau +145 -0
- package/plugin/src/handlers/Capture.luau +187 -0
- package/plugin/src/handlers/Character.luau +361 -0
- package/plugin/src/handlers/Debug.luau +391 -0
- package/plugin/src/handlers/Device.luau +119 -0
- package/plugin/src/handlers/Discover.luau +289 -0
- package/plugin/src/handlers/Exec.luau +270 -0
- package/plugin/src/handlers/Geometry.luau +261 -0
- package/plugin/src/handlers/Input.luau +287 -0
- package/plugin/src/handlers/Instances.luau +389 -0
- package/plugin/src/handlers/Perf.luau +645 -0
- package/plugin/src/handlers/Playtest.luau +205 -0
- package/plugin/src/handlers/Scripts.luau +387 -0
- package/plugin/src/handlers/Session.luau +168 -0
- package/plugin/src/handlers/Viewport.luau +302 -0
- package/plugin/src/handlers/World.luau +176 -0
- package/plugin/src/init.server.luau +317 -0
- package/scripts/build-plugin.mjs +157 -0
- package/scripts/check-plugin.mjs +97 -0
- package/scripts/install-plugin.mjs +39 -0
- package/scripts/latency.mjs +201 -0
- package/scripts/locate-luau.mjs +51 -0
- package/scripts/sourcemap.mjs +58 -0
- package/scripts/test-plugin.mjs +82 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Live Roblox API reflection, sourced from the official API dump.
|
|
6
|
+
*
|
|
7
|
+
* Every other Studio MCP server hardcodes a property list per class, which goes
|
|
8
|
+
* stale the moment Roblox ships an engine update — the agent then gets told a
|
|
9
|
+
* real property "does not exist". Here the dump is downloaded once a day and
|
|
10
|
+
* cached, so property enumeration, validation and "did you mean" suggestions
|
|
11
|
+
* track whatever engine version the user is actually running.
|
|
12
|
+
*
|
|
13
|
+
* Doing this on the Node side rather than in the plugin keeps it off Studio's
|
|
14
|
+
* HTTP permission prompt and lets the cache survive Studio restarts.
|
|
15
|
+
*/
|
|
16
|
+
/** Mirror of the dump Roblox publishes for each Studio build, updated per release. */
|
|
17
|
+
const DUMP_URL = "https://raw.githubusercontent.com/MaximumADHD/Roblox-Client-Tracker/roblox/API-Dump.json";
|
|
18
|
+
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
19
|
+
let loaded = null;
|
|
20
|
+
const propertyCache = new Map();
|
|
21
|
+
function cachePath() {
|
|
22
|
+
return join(tmpdir(), "roblox-studio-mcp", "api-dump.json");
|
|
23
|
+
}
|
|
24
|
+
async function readCache() {
|
|
25
|
+
try {
|
|
26
|
+
const raw = await readFile(cachePath(), "utf8");
|
|
27
|
+
const cached = JSON.parse(raw);
|
|
28
|
+
if (Date.now() - cached.fetchedAt > CACHE_TTL_MS)
|
|
29
|
+
return null;
|
|
30
|
+
return cached.dump;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function writeCache(dump) {
|
|
37
|
+
try {
|
|
38
|
+
const path = cachePath();
|
|
39
|
+
await mkdir(join(path, ".."), { recursive: true });
|
|
40
|
+
await writeFile(path, JSON.stringify({ fetchedAt: Date.now(), dump }), "utf8");
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// A read-only or full temp directory only costs us the cache, not the feature.
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Returns the API dump, or null if it cannot be obtained.
|
|
48
|
+
*
|
|
49
|
+
* Null is a supported state, not a failure: the server must keep working
|
|
50
|
+
* offline, so callers fall back to whatever the plugin reports about an
|
|
51
|
+
* instance rather than refusing to answer.
|
|
52
|
+
*/
|
|
53
|
+
export function loadApiDump() {
|
|
54
|
+
loaded ??= (async () => {
|
|
55
|
+
const cached = await readCache();
|
|
56
|
+
if (cached)
|
|
57
|
+
return cached;
|
|
58
|
+
try {
|
|
59
|
+
const response = await fetch(DUMP_URL, {
|
|
60
|
+
signal: AbortSignal.timeout(15_000),
|
|
61
|
+
});
|
|
62
|
+
if (!response.ok)
|
|
63
|
+
return null;
|
|
64
|
+
const dump = (await response.json());
|
|
65
|
+
await writeCache(dump);
|
|
66
|
+
return dump;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Stale beats absent: fall back to an expired cache when the network is
|
|
70
|
+
// unavailable, since engine APIs change slowly.
|
|
71
|
+
try {
|
|
72
|
+
const raw = await readFile(cachePath(), "utf8");
|
|
73
|
+
return JSON.parse(raw).dump;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
})();
|
|
80
|
+
return loaded;
|
|
81
|
+
}
|
|
82
|
+
function isReadable(member) {
|
|
83
|
+
if (member.MemberType !== "Property")
|
|
84
|
+
return false;
|
|
85
|
+
const security = member.Security;
|
|
86
|
+
const read = typeof security === "string" ? security : security?.Read;
|
|
87
|
+
// PluginSecurity is fine — the plugin runs with it. Anything higher is not
|
|
88
|
+
// reachable from a plugin and would just produce errors if we offered it.
|
|
89
|
+
if (read && read !== "None" && read !== "PluginSecurity")
|
|
90
|
+
return false;
|
|
91
|
+
// Only `NotScriptable` actually blocks Luau access. `Hidden` means "not
|
|
92
|
+
// serialized / not shown in the Properties widget", which is true of
|
|
93
|
+
// BasePart.Position and .Orientation — both derived from CFrame, both
|
|
94
|
+
// perfectly scriptable, and both among the properties agents reach for most.
|
|
95
|
+
// Filtering on `Hidden` would silently hide them.
|
|
96
|
+
return !(member.Tags ?? []).includes("NotScriptable");
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Every readable property of a class, walking the inheritance chain. Ordered
|
|
100
|
+
* most-derived first so callers can take a prefix and get the properties that
|
|
101
|
+
* actually characterise the instance.
|
|
102
|
+
*/
|
|
103
|
+
export async function propertiesOf(className) {
|
|
104
|
+
const cached = propertyCache.get(className);
|
|
105
|
+
if (cached)
|
|
106
|
+
return cached;
|
|
107
|
+
const dump = await loadApiDump();
|
|
108
|
+
if (!dump)
|
|
109
|
+
return [];
|
|
110
|
+
const byName = new Map(dump.Classes.map((entry) => [entry.Name, entry]));
|
|
111
|
+
const properties = [];
|
|
112
|
+
const seen = new Set();
|
|
113
|
+
let current = byName.get(className);
|
|
114
|
+
while (current) {
|
|
115
|
+
for (const member of current.Members) {
|
|
116
|
+
if (!isReadable(member) || seen.has(member.Name))
|
|
117
|
+
continue;
|
|
118
|
+
seen.add(member.Name);
|
|
119
|
+
const tags = member.Tags ?? [];
|
|
120
|
+
properties.push({
|
|
121
|
+
name: member.Name,
|
|
122
|
+
valueType: member.ValueType?.Name ?? "unknown",
|
|
123
|
+
category: member.ValueType?.Category ?? "unknown",
|
|
124
|
+
declaredBy: current.Name,
|
|
125
|
+
readOnly: tags.includes("ReadOnly"),
|
|
126
|
+
deprecated: tags.includes("Deprecated"),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
current = current.Superclass ? byName.get(current.Superclass) : undefined;
|
|
130
|
+
}
|
|
131
|
+
propertyCache.set(className, properties);
|
|
132
|
+
return properties;
|
|
133
|
+
}
|
|
134
|
+
/** Bases that describe every instance and so distinguish none of them. */
|
|
135
|
+
const GENERIC_BASES = new Set(["Instance", "PVInstance"]);
|
|
136
|
+
/** Ceiling for `standard`, so wide classes like Humanoid stay affordable. */
|
|
137
|
+
const STANDARD_LIMIT = 25;
|
|
138
|
+
/**
|
|
139
|
+
* Properties that lead, when the class has them.
|
|
140
|
+
*
|
|
141
|
+
* The dump lists members alphabetically, so a plain prefix of `BasePart` would
|
|
142
|
+
* cut off at `Reflectance` and lose Size, Position and Transparency — the three
|
|
143
|
+
* an agent reaches for most. This is the one place a curated list is worth its
|
|
144
|
+
* maintenance cost; everything after it still comes straight from the dump, so
|
|
145
|
+
* new engine properties appear without a code change.
|
|
146
|
+
*/
|
|
147
|
+
const PRIORITY_PROPERTIES = [
|
|
148
|
+
"Size",
|
|
149
|
+
"Position",
|
|
150
|
+
"CFrame",
|
|
151
|
+
"Orientation",
|
|
152
|
+
"Rotation",
|
|
153
|
+
"Anchored",
|
|
154
|
+
"CanCollide",
|
|
155
|
+
"Transparency",
|
|
156
|
+
"Color",
|
|
157
|
+
"BrickColor",
|
|
158
|
+
"Material",
|
|
159
|
+
"Shape",
|
|
160
|
+
"Visible",
|
|
161
|
+
"Enabled",
|
|
162
|
+
"Disabled",
|
|
163
|
+
"Text",
|
|
164
|
+
"Value",
|
|
165
|
+
"Source",
|
|
166
|
+
"Image",
|
|
167
|
+
"MeshId",
|
|
168
|
+
"SoundId",
|
|
169
|
+
"Health",
|
|
170
|
+
"MaxHealth",
|
|
171
|
+
"WalkSpeed",
|
|
172
|
+
"PrimaryPart",
|
|
173
|
+
"RunContext",
|
|
174
|
+
];
|
|
175
|
+
/** Sorts priority names first (in listed order), leaving the rest untouched. */
|
|
176
|
+
function rankProperties(names) {
|
|
177
|
+
const rank = new Map(PRIORITY_PROPERTIES.map((name, index) => [name, index]));
|
|
178
|
+
return [...names].sort((a, b) => {
|
|
179
|
+
const left = rank.get(a) ?? Number.MAX_SAFE_INTEGER;
|
|
180
|
+
const right = rank.get(b) ?? Number.MAX_SAFE_INTEGER;
|
|
181
|
+
if (left !== right)
|
|
182
|
+
return left - right;
|
|
183
|
+
return 0; // ties keep dump order, which is most-derived class first
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* The properties worth showing at `detail: "standard"`.
|
|
188
|
+
*
|
|
189
|
+
* Heuristic: everything the class and its non-generic bases declare — for a
|
|
190
|
+
* Part that is `Shape` plus all of `BasePart` (Size, Anchored, CFrame,
|
|
191
|
+
* Material...), which is exactly what an agent needs to reason about it.
|
|
192
|
+
* `Instance` itself is skipped because `Archivable` and `Parent` say nothing
|
|
193
|
+
* about a specific instance, and the path already encodes the parent.
|
|
194
|
+
*
|
|
195
|
+
* Deprecated and read-only entries are dropped: an agent cannot act on them,
|
|
196
|
+
* and `detail: "full"` is there when the whole surface is genuinely wanted.
|
|
197
|
+
*/
|
|
198
|
+
export async function standardProperties(className) {
|
|
199
|
+
const all = await propertiesOf(className);
|
|
200
|
+
if (all.length === 0)
|
|
201
|
+
return [];
|
|
202
|
+
const useful = all.filter((property) => !GENERIC_BASES.has(property.declaredBy) && !property.deprecated && !property.readOnly);
|
|
203
|
+
// Classes that add nothing of their own (Folder, Model) fall back to the full
|
|
204
|
+
// set, minus the noise, rather than returning just the name.
|
|
205
|
+
const chosen = useful.length > 0 ? useful : all.filter((p) => !p.deprecated && !p.readOnly);
|
|
206
|
+
const ranked = rankProperties(chosen.map((property) => property.name));
|
|
207
|
+
const names = ranked.slice(0, STANDARD_LIMIT);
|
|
208
|
+
return ["Name", ...names.filter((name) => name !== "Name")];
|
|
209
|
+
}
|
|
210
|
+
/** True when the dump knows this class. False also covers "dump unavailable". */
|
|
211
|
+
export async function isKnownClass(className) {
|
|
212
|
+
const dump = await loadApiDump();
|
|
213
|
+
return dump?.Classes.some((entry) => entry.Name === className) ?? false;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Suggests real property names close to a mistyped one. Turns a dead-end
|
|
217
|
+
* "property does not exist" into a correction the agent can apply immediately.
|
|
218
|
+
*/
|
|
219
|
+
export async function suggestProperty(className, attempted) {
|
|
220
|
+
const all = await propertiesOf(className);
|
|
221
|
+
const target = attempted.toLowerCase();
|
|
222
|
+
return all
|
|
223
|
+
.map((property) => ({
|
|
224
|
+
name: property.name,
|
|
225
|
+
distance: editDistance(target, property.name.toLowerCase()),
|
|
226
|
+
}))
|
|
227
|
+
.filter((entry) => entry.distance <= Math.max(2, Math.floor(target.length / 3)))
|
|
228
|
+
.sort((a, b) => a.distance - b.distance)
|
|
229
|
+
.slice(0, 5)
|
|
230
|
+
.map((entry) => entry.name);
|
|
231
|
+
}
|
|
232
|
+
/** Suggests real class names close to a mistyped one, for `create`. */
|
|
233
|
+
export async function suggestClass(attempted) {
|
|
234
|
+
const dump = await loadApiDump();
|
|
235
|
+
if (!dump)
|
|
236
|
+
return [];
|
|
237
|
+
const target = attempted.toLowerCase();
|
|
238
|
+
return dump.Classes.map((entry) => ({
|
|
239
|
+
name: entry.Name,
|
|
240
|
+
distance: editDistance(target, entry.Name.toLowerCase()),
|
|
241
|
+
}))
|
|
242
|
+
.filter((entry) => entry.distance <= Math.max(2, Math.floor(target.length / 3)))
|
|
243
|
+
.sort((a, b) => a.distance - b.distance)
|
|
244
|
+
.slice(0, 5)
|
|
245
|
+
.map((entry) => entry.name);
|
|
246
|
+
}
|
|
247
|
+
/** Levenshtein distance, two-row variant. Inputs here are short identifiers. */
|
|
248
|
+
function editDistance(a, b) {
|
|
249
|
+
if (a === b)
|
|
250
|
+
return 0;
|
|
251
|
+
if (a.length === 0)
|
|
252
|
+
return b.length;
|
|
253
|
+
if (b.length === 0)
|
|
254
|
+
return a.length;
|
|
255
|
+
let previous = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
256
|
+
let current = new Array(b.length + 1);
|
|
257
|
+
for (let i = 1; i <= a.length; i++) {
|
|
258
|
+
current[0] = i;
|
|
259
|
+
for (let j = 1; j <= b.length; j++) {
|
|
260
|
+
const substitution = previous[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1);
|
|
261
|
+
const deletion = previous[j] + 1;
|
|
262
|
+
const insertion = current[j - 1] + 1;
|
|
263
|
+
current[j] = Math.min(substitution, deletion, insertion);
|
|
264
|
+
}
|
|
265
|
+
[previous, current] = [current, previous];
|
|
266
|
+
}
|
|
267
|
+
return previous[b.length];
|
|
268
|
+
}
|
|
269
|
+
//# sourceMappingURL=apidump.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apidump.js","sourceRoot":"","sources":["../../src/lib/apidump.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error whose message is written for an agent, not a human log reader: it says
|
|
3
|
+
* what failed and what to try next, because that is the only feedback channel
|
|
4
|
+
* the model has for learning correct tool usage.
|
|
5
|
+
*/
|
|
6
|
+
export class ToolError extends Error {
|
|
7
|
+
code;
|
|
8
|
+
hint;
|
|
9
|
+
constructor(code, message, hint) {
|
|
10
|
+
super(hint ? `${message}\n${hint}` : message);
|
|
11
|
+
this.name = "ToolError";
|
|
12
|
+
this.code = code;
|
|
13
|
+
this.hint = hint;
|
|
14
|
+
}
|
|
15
|
+
static fromCommandError(error) {
|
|
16
|
+
return new ToolError(error.code, error.message, error.hint);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
export const NO_STUDIO = () => new ToolError("NO_STUDIO", "No Roblox Studio instance is connected to this MCP server.", "Open Roblox Studio, install the companion plugin, and make sure it is enabled " +
|
|
20
|
+
"(Plugins tab -> Studio MCP -> Connect). If the plugin asks for permission to " +
|
|
21
|
+
"reach 127.0.0.1, accept it. Then retry.");
|
|
22
|
+
export const AMBIGUOUS_STUDIO = (names) => new ToolError("AMBIGUOUS_STUDIO", `${names.length} Studio instances are connected and the user has not said ` +
|
|
23
|
+
"which one to work in.", "Ask the user which place they mean, then call set_active_studio with its " +
|
|
24
|
+
`studioId. Connected now: ${names.join("; ")}. To act on one place without ` +
|
|
25
|
+
"changing the default, pass `studioId` to the tool directly.");
|
|
26
|
+
export const TIMEOUT = (op, ms) => new ToolError("TIMEOUT", `Studio did not answer "${op}" within ${ms}ms.`, "Studio is usually busy compiling, mid-playtest, or blocked on a modal dialog. " +
|
|
27
|
+
"Call studio_status to check, then retry. If the work is genuinely long, " +
|
|
28
|
+
"run it through execute_luau with your own coroutine instead.");
|
|
29
|
+
export const DISCONNECTED = () => new ToolError("DISCONNECTED", "The Studio connection dropped while the request was in flight.", "Studio was closed, the place was switched, or the plugin was disabled. " +
|
|
30
|
+
"Call studio_status to confirm a live connection, then retry the request.");
|
|
31
|
+
/** Wraps unknown throwables so every tool boundary reports the same shape. */
|
|
32
|
+
export function toToolError(cause) {
|
|
33
|
+
if (cause instanceof ToolError)
|
|
34
|
+
return cause;
|
|
35
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
36
|
+
return new ToolError("INTERNAL", message);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,OAAO,SAAU,SAAQ,KAAK;IACzB,IAAI,CAAS;IACb,IAAI,CAAqB;IAElC,YAAY,IAAY,EAAE,OAAe,EAAE,IAAa;QACtD,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,OAAO,KAAK,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;QAC9C,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,MAAM,CAAC,gBAAgB,CAAC,KAAmB;QACzC,OAAO,IAAI,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;IAC9D,CAAC;CACF;AAED,MAAM,CAAC,MAAM,SAAS,GAAG,GAAc,EAAE,CACvC,IAAI,SAAS,CACX,WAAW,EACX,4DAA4D,EAC5D,gFAAgF;IAC9E,+EAA+E;IAC/E,yCAAyC,CAC5C,CAAC;AAEJ,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAe,EAAa,EAAE,CAC7D,IAAI,SAAS,CACX,kBAAkB,EAClB,GAAG,KAAK,CAAC,MAAM,4DAA4D;IACzE,uBAAuB,EACzB,2EAA2E;IACzE,4BAA4B,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,gCAAgC;IAC5E,6DAA6D,CAChE,CAAC;AAEJ,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,EAAU,EAAE,EAAU,EAAa,EAAE,CAC3D,IAAI,SAAS,CACX,SAAS,EACT,0BAA0B,EAAE,YAAY,EAAE,KAAK,EAC/C,gFAAgF;IAC9E,0EAA0E;IAC1E,8DAA8D,CACjE,CAAC;AAEJ,MAAM,CAAC,MAAM,YAAY,GAAG,GAAc,EAAE,CAC1C,IAAI,SAAS,CACX,cAAc,EACd,gEAAgE,EAChE,yEAAyE;IACvE,0EAA0E,CAC7E,CAAC;AAEJ,8EAA8E;AAC9E,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,IAAI,KAAK,YAAY,SAAS;QAAE,OAAO,KAAK,CAAC;IAC7C,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,IAAI,SAAS,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;AAC5C,CAAC"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/**
|
|
3
|
+
* Hard ceiling on a single tool response, in characters (~6k tokens). Roblox
|
|
4
|
+
* data models routinely run to tens of thousands of instances, so an unbounded
|
|
5
|
+
* response can blow an agent's whole context on one call. Everything that can
|
|
6
|
+
* grow is paged instead of truncated silently.
|
|
7
|
+
*/
|
|
8
|
+
export const CHARACTER_LIMIT = 25_000;
|
|
9
|
+
/** Default page size for list-shaped results, tuned to stay well under the cap. */
|
|
10
|
+
export const DEFAULT_PAGE_SIZE = 100;
|
|
11
|
+
export const MAX_PAGE_SIZE = 500;
|
|
12
|
+
export const detailSchema = z
|
|
13
|
+
.enum(["concise", "standard", "full"])
|
|
14
|
+
.default("standard")
|
|
15
|
+
.describe("How much to return per item. 'concise' = name + class only, cheapest, use " +
|
|
16
|
+
"when scanning or counting. 'standard' = the properties that matter for most " +
|
|
17
|
+
"edits. 'full' = every readable property, expensive — use only after you have " +
|
|
18
|
+
"narrowed to a handful of instances.");
|
|
19
|
+
export const cursorSchema = z
|
|
20
|
+
.string()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe("Opaque cursor from a previous call's `nextCursor`. Omit for the first page.");
|
|
23
|
+
export const limitSchema = z
|
|
24
|
+
.number()
|
|
25
|
+
.int()
|
|
26
|
+
.min(1)
|
|
27
|
+
.max(MAX_PAGE_SIZE)
|
|
28
|
+
.default(DEFAULT_PAGE_SIZE)
|
|
29
|
+
.describe(`Maximum items to return (1-${MAX_PAGE_SIZE}).`);
|
|
30
|
+
/**
|
|
31
|
+
* Cursors are just offsets. They are encoded so callers treat them as opaque
|
|
32
|
+
* and pass them back verbatim instead of doing arithmetic on a number whose
|
|
33
|
+
* meaning may change.
|
|
34
|
+
*/
|
|
35
|
+
export const encodeCursor = (offset) => Buffer.from(String(offset)).toString("base64url");
|
|
36
|
+
export function decodeCursor(cursor) {
|
|
37
|
+
if (!cursor)
|
|
38
|
+
return 0;
|
|
39
|
+
const parsed = Number.parseInt(Buffer.from(cursor, "base64url").toString("utf8"), 10);
|
|
40
|
+
return Number.isInteger(parsed) && parsed >= 0 ? parsed : 0;
|
|
41
|
+
}
|
|
42
|
+
export function text(body) {
|
|
43
|
+
return { content: [{ type: "text", text: body }] };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* An image the model can actually look at, with a line of text alongside it.
|
|
47
|
+
*
|
|
48
|
+
* The caption is not decoration: an image arrives with no path, no size and no
|
|
49
|
+
* indication of which of several Studio windows it came from, and none of that
|
|
50
|
+
* is recoverable from the pixels.
|
|
51
|
+
*/
|
|
52
|
+
export function image(data, caption, mimeType = "image/png") {
|
|
53
|
+
return {
|
|
54
|
+
content: [
|
|
55
|
+
{ type: "image", data, mimeType },
|
|
56
|
+
{ type: "text", text: caption },
|
|
57
|
+
],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
export function errorText(body) {
|
|
61
|
+
return { content: [{ type: "text", text: body }], isError: true };
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The rendered text of a result, for composing one helper's output into another.
|
|
65
|
+
*
|
|
66
|
+
* Results can now carry an image, so reading `content[0].text` is no longer
|
|
67
|
+
* always meaningful. Callers that stitch tables together want the text and
|
|
68
|
+
* nothing else, which is exactly what this returns.
|
|
69
|
+
*/
|
|
70
|
+
export function textOf(result) {
|
|
71
|
+
return result.content
|
|
72
|
+
.filter((part) => part.type === "text")
|
|
73
|
+
.map((part) => part.text)
|
|
74
|
+
.join("\n");
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Renders a page of records as JSON plus a short trailer telling the agent
|
|
78
|
+
* whether anything was left behind and how to reach it. The trailer is the
|
|
79
|
+
* point: a bare truncated array reads as a complete answer and the agent
|
|
80
|
+
* confidently reports the wrong total.
|
|
81
|
+
*/
|
|
82
|
+
export function page(items, meta = {}) {
|
|
83
|
+
const parts = [];
|
|
84
|
+
let body = JSON.stringify(items, null, 2);
|
|
85
|
+
if (body.length > CHARACTER_LIMIT) {
|
|
86
|
+
const kept = fitToLimit(items, CHARACTER_LIMIT - 500);
|
|
87
|
+
body = JSON.stringify(kept, null, 2);
|
|
88
|
+
parts.push(body);
|
|
89
|
+
parts.push(`\n[${items.length - kept.length} of ${items.length} items dropped: the page ` +
|
|
90
|
+
`exceeded the ${CHARACTER_LIMIT}-character response limit. Re-run with a ` +
|
|
91
|
+
`smaller \`limit\`, or with \`detail: "concise"\` to fit more per page.]`);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
parts.push(body);
|
|
95
|
+
}
|
|
96
|
+
if (meta.total !== undefined && meta.total > items.length) {
|
|
97
|
+
parts.push(`\n[showing ${items.length} of ${meta.total} matches]`);
|
|
98
|
+
}
|
|
99
|
+
if (meta.nextCursor) {
|
|
100
|
+
parts.push(`\n[more results available — call again with cursor: "${meta.nextCursor}"]`);
|
|
101
|
+
}
|
|
102
|
+
if (meta.more)
|
|
103
|
+
parts.push(`\n[${meta.more}]`);
|
|
104
|
+
return text(parts.join(""));
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Renders a list of uniform records as a pipe-delimited table.
|
|
108
|
+
*
|
|
109
|
+
* A hierarchy listing pretty-printed as JSON spends five lines and a dozen
|
|
110
|
+
* punctuation tokens per instance to repeat the same three keys. One header
|
|
111
|
+
* plus one line per row carries the same information for roughly a quarter of
|
|
112
|
+
* the tokens, and reads better in a transcript. `json` is still the right
|
|
113
|
+
* choice for nested or heterogeneous data.
|
|
114
|
+
*/
|
|
115
|
+
export function table(columns, rows, meta = {}) {
|
|
116
|
+
if (rows.length === 0) {
|
|
117
|
+
return text(meta.more ?? "No matches.");
|
|
118
|
+
}
|
|
119
|
+
const render = (row) => columns.map((column) => formatCell(row[column])).join(" | ");
|
|
120
|
+
const lines = [columns.join(" | ")];
|
|
121
|
+
let used = lines[0].length;
|
|
122
|
+
let dropped = 0;
|
|
123
|
+
for (const row of rows) {
|
|
124
|
+
const line = render(row);
|
|
125
|
+
if (used + line.length + 1 > CHARACTER_LIMIT - 500) {
|
|
126
|
+
dropped = rows.length - (lines.length - 1);
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
lines.push(line);
|
|
130
|
+
used += line.length + 1;
|
|
131
|
+
}
|
|
132
|
+
const trailer = [];
|
|
133
|
+
if (dropped > 0) {
|
|
134
|
+
trailer.push(`[${dropped} rows dropped to stay under the ${CHARACTER_LIMIT}-character ` +
|
|
135
|
+
`response limit — re-run with a smaller \`limit\`]`);
|
|
136
|
+
}
|
|
137
|
+
if (meta.total !== undefined && meta.total > rows.length) {
|
|
138
|
+
trailer.push(`[showing ${rows.length} of ${meta.total} matches]`);
|
|
139
|
+
}
|
|
140
|
+
if (meta.nextCursor) {
|
|
141
|
+
trailer.push(`[more results — call again with cursor: "${meta.nextCursor}"]`);
|
|
142
|
+
}
|
|
143
|
+
if (meta.more)
|
|
144
|
+
trailer.push(`[${meta.more}]`);
|
|
145
|
+
return text([...lines, ...(trailer.length > 0 ? ["", ...trailer] : [])].join("\n"));
|
|
146
|
+
}
|
|
147
|
+
function formatCell(value) {
|
|
148
|
+
if (value === null || value === undefined)
|
|
149
|
+
return "";
|
|
150
|
+
if (typeof value === "object")
|
|
151
|
+
return JSON.stringify(value);
|
|
152
|
+
return String(value);
|
|
153
|
+
}
|
|
154
|
+
/** Greedily keeps the longest prefix of `items` that serialises under `budget`. */
|
|
155
|
+
function fitToLimit(items, budget) {
|
|
156
|
+
const kept = [];
|
|
157
|
+
let used = 2; // the enclosing brackets
|
|
158
|
+
for (const item of items) {
|
|
159
|
+
const cost = JSON.stringify(item, null, 2).length + 2;
|
|
160
|
+
if (used + cost > budget)
|
|
161
|
+
break;
|
|
162
|
+
used += cost;
|
|
163
|
+
kept.push(item);
|
|
164
|
+
}
|
|
165
|
+
return kept;
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Emits a plain text body, clipping at the character limit with an explicit
|
|
169
|
+
* marker. Used for source listings, where the content is already line-oriented
|
|
170
|
+
* and reflowing it into JSON would only add escaping noise.
|
|
171
|
+
*/
|
|
172
|
+
export function body(content, advice) {
|
|
173
|
+
if (content.length <= CHARACTER_LIMIT)
|
|
174
|
+
return text(content);
|
|
175
|
+
return text(`${content.slice(0, CHARACTER_LIMIT)}\n\n[clipped at ${CHARACTER_LIMIT} characters — ${advice}]`);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Renders arbitrary structured data, clipping at the character limit with an
|
|
179
|
+
* explicit marker so the agent never mistakes a clipped blob for the whole one.
|
|
180
|
+
*/
|
|
181
|
+
export function json(value, note) {
|
|
182
|
+
let body = JSON.stringify(value, null, 2);
|
|
183
|
+
if (body.length > CHARACTER_LIMIT) {
|
|
184
|
+
body =
|
|
185
|
+
body.slice(0, CHARACTER_LIMIT) +
|
|
186
|
+
`\n\n[response clipped at ${CHARACTER_LIMIT} characters — narrow the request ` +
|
|
187
|
+
`(fewer paths, lower depth, or detail: "concise") to see the rest]`;
|
|
188
|
+
}
|
|
189
|
+
return text(note ? `${body}\n\n${note}` : body);
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=format.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/lib/format.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
/**
|
|
6
|
+
* Fingerprint of the Luau sources this server package ships.
|
|
7
|
+
*
|
|
8
|
+
* Roblox Studio only reloads a plugin when it next takes focus, so a developer
|
|
9
|
+
* who rebuilds the plugin while working in a terminal keeps talking to the
|
|
10
|
+
* previous build — and the symptom is a handler quietly behaving like an older
|
|
11
|
+
* version, which is miserable to debug. The plugin reports the fingerprint it
|
|
12
|
+
* was built from, the server compares it against this one, and any mismatch is
|
|
13
|
+
* surfaced in `studio_status` and `list_studios` with the fix.
|
|
14
|
+
*
|
|
15
|
+
* Must stay byte-identical to the hash in scripts/build-plugin.mjs.
|
|
16
|
+
*/
|
|
17
|
+
let cached = null;
|
|
18
|
+
function pluginSourceDir() {
|
|
19
|
+
// dist/lib/pluginbuild.js -> package root -> plugin/src
|
|
20
|
+
return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..", "plugin", "src");
|
|
21
|
+
}
|
|
22
|
+
function collect(dir, root, into) {
|
|
23
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
24
|
+
const path = join(dir, entry.name);
|
|
25
|
+
if (entry.isDirectory()) {
|
|
26
|
+
collect(path, root, into);
|
|
27
|
+
}
|
|
28
|
+
else if (entry.name.endsWith(".luau")) {
|
|
29
|
+
into.push([
|
|
30
|
+
relative(root, path).split("\\").join("/"),
|
|
31
|
+
readFileSync(path, "utf8"),
|
|
32
|
+
]);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Hashes every .luau file under plugin/src, sorted by path. Line endings are
|
|
38
|
+
* normalised because git checkouts differ between platforms and the plugin
|
|
39
|
+
* would otherwise look stale on Windows.
|
|
40
|
+
*/
|
|
41
|
+
export function expectedPluginBuildId() {
|
|
42
|
+
if (cached !== null)
|
|
43
|
+
return cached;
|
|
44
|
+
try {
|
|
45
|
+
const root = pluginSourceDir();
|
|
46
|
+
const files = [];
|
|
47
|
+
collect(root, root, files);
|
|
48
|
+
files.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
49
|
+
const hash = createHash("sha256");
|
|
50
|
+
for (const [path, contents] of files) {
|
|
51
|
+
hash.update(path);
|
|
52
|
+
hash.update("\n");
|
|
53
|
+
hash.update(contents.replace(/\r\n/g, "\n"));
|
|
54
|
+
hash.update("\n");
|
|
55
|
+
}
|
|
56
|
+
cached = hash.digest("hex").slice(0, 12);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
// Sources missing (an unusual install layout) means we cannot compare, and
|
|
60
|
+
// an unverifiable version is better reported as "unknown" than as stale.
|
|
61
|
+
cached = "unknown";
|
|
62
|
+
}
|
|
63
|
+
return cached;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Human-readable warning when the connected plugin was built from different
|
|
67
|
+
* sources, or null when it matches. Returned to the agent so it knows a
|
|
68
|
+
* surprising result may just be a stale plugin.
|
|
69
|
+
*/
|
|
70
|
+
export function pluginStalenessWarning(reported) {
|
|
71
|
+
const expected = expectedPluginBuildId();
|
|
72
|
+
if (expected === "unknown" || !reported || reported === expected)
|
|
73
|
+
return null;
|
|
74
|
+
if (reported === "dev") {
|
|
75
|
+
return ("The connected plugin was loaded from source rather than a build, so its " +
|
|
76
|
+
"version cannot be verified against this server.");
|
|
77
|
+
}
|
|
78
|
+
return (`The connected Studio plugin was built from different sources than this ` +
|
|
79
|
+
`server (plugin ${reported}, server ${expected}). Studio only reloads a ` +
|
|
80
|
+
`plugin when its window next takes focus — click into Studio, then retry. ` +
|
|
81
|
+
`If that does not clear it, run \`npm run install:plugin\` and restart Studio.`);
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=pluginbuild.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pluginbuild.js","sourceRoot":"","sources":["../../src/lib/pluginbuild.ts"],"names":[],"mappings":"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"}
|