@el4cteo/rbx-studio-mcp 0.8.4 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +20 -3
  2. package/dist/bridge/rpc.js +1 -1
  3. package/dist/bridge/rpc.js.map +1 -1
  4. package/dist/index.js +7 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/apidump.js +75 -0
  7. package/dist/lib/apidump.js.map +1 -1
  8. package/dist/lib/errors.js +5 -4
  9. package/dist/lib/errors.js.map +1 -1
  10. package/dist/lib/format.js +92 -25
  11. package/dist/lib/format.js.map +1 -1
  12. package/dist/lib/notices.js +29 -0
  13. package/dist/lib/notices.js.map +1 -0
  14. package/dist/lib/protocol.js.map +1 -1
  15. package/dist/lib/sync.js +1141 -0
  16. package/dist/lib/sync.js.map +1 -0
  17. package/dist/lib/syncplan.js +338 -0
  18. package/dist/lib/syncplan.js.map +1 -0
  19. package/dist/lib/tool.js +9 -2
  20. package/dist/lib/tool.js.map +1 -1
  21. package/dist/tools/discover.js +6 -1
  22. package/dist/tools/discover.js.map +1 -1
  23. package/dist/tools/exec.js +5 -0
  24. package/dist/tools/exec.js.map +1 -1
  25. package/dist/tools/instances.js +2 -2
  26. package/dist/tools/instances.js.map +1 -1
  27. package/dist/tools/perf.js +28 -3
  28. package/dist/tools/perf.js.map +1 -1
  29. package/dist/tools/screenshot.js +7 -3
  30. package/dist/tools/screenshot.js.map +1 -1
  31. package/dist/tools/scripts.js +110 -28
  32. package/dist/tools/scripts.js.map +1 -1
  33. package/dist/tools/sync.js +169 -0
  34. package/dist/tools/sync.js.map +1 -0
  35. package/package.json +4 -4
  36. package/plugin/src/Config.luau +1 -1
  37. package/plugin/src/Dispatch.luau +131 -90
  38. package/plugin/src/ExecRuntime.luau +190 -168
  39. package/plugin/src/LogBuffer.luau +38 -9
  40. package/plugin/src/Paths.luau +42 -0
  41. package/plugin/src/Phrase.luau +41 -0
  42. package/plugin/src/ScriptEdit.luau +94 -8
  43. package/plugin/src/Serialize.luau +16 -1
  44. package/plugin/src/Transport.luau +5 -2
  45. package/plugin/src/Undo.luau +74 -10
  46. package/plugin/src/handlers/Capture.luau +818 -809
  47. package/plugin/src/handlers/Debug.luau +19 -16
  48. package/plugin/src/handlers/Discover.luau +31 -1
  49. package/plugin/src/handlers/Perf.luau +100 -21
  50. package/plugin/src/handlers/Scripts.luau +274 -90
  51. package/plugin/src/handlers/Sync.luau +968 -0
  52. package/plugin/src/init.server.luau +33 -2
  53. package/scripts/build.mjs +14 -0
  54. package/scripts/sync-fake.mjs +191 -0
  55. package/scripts/test-live-sync-scale.mjs +150 -0
  56. package/scripts/test-live-sync.mjs +232 -0
  57. package/scripts/test-live-tools.mjs +6 -1
  58. package/scripts/test-plugin.mjs +16 -0
  59. package/scripts/test-results.mjs +41 -0
  60. package/scripts/test-sync-more.mjs +228 -0
  61. package/scripts/test-sync.mjs +245 -0
  62. package/dist/tools/spatial.js +0 -135
  63. package/dist/tools/spatial.js.map +0 -1
  64. package/dist/tools/upload.js +0 -294
  65. package/dist/tools/upload.js.map +0 -1
@@ -1,135 +0,0 @@
1
- import { z } from "zod";
2
- import { json } from "../lib/format.js";
3
- import { defineTool } from "../lib/tool.js";
4
- /** A position, written the way the Properties panel shows one. */
5
- const position = z
6
- .string()
7
- .describe('A position, written as the Properties panel shows it: "12, 0, 5".');
8
- export function registerSpatialTools(context) {
9
- const { bridge } = context;
10
- defineTool(context, {
11
- name: "spatial",
12
- title: "Ask what is actually there",
13
- description: "Casts a ray, block or sphere through the world, or lists every part " +
14
- "inside a volume.\n\n" +
15
- "This answers the one question the Explorer cannot: what is physically " +
16
- "THERE. A path tells you an instance exists and where its pivot sits. " +
17
- "It does not tell you the door frame is clipping into the wall, that " +
18
- "the spawn is buried a stud inside the floor, or that nothing stands " +
19
- "between the turret and the player. Every one of those is a query " +
20
- "against the world, and geometry that is wrong in exactly that way " +
21
- "looks completely correct in `inspect`.\n\n" +
22
- "`op=\"cast\"` fires a shape and reports the first thing it meets: the " +
23
- "part, where on it, the surface normal, the material and the distance. " +
24
- "Aim it with `to` (a point to look at) or with `direction` plus " +
25
- "`distance`. A miss comes back as `hit: false`, which is a real answer " +
26
- "— that is how you confirm a sightline is clear or that there is no " +
27
- "ground below.\n\n" +
28
- "`op=\"overlap\"` lists what sits inside a volume: a box, a radius, or " +
29
- "`region=\"part\"`, which takes an existing part and reports what " +
30
- "overlaps it. That last one is the fastest way to find things clipping " +
31
- "through each other.\n\n" +
32
- "Narrow with `only` or `ignore` — a query over a whole place returns a " +
33
- "transcript, not an answer. Set `collisionGroup` when the place uses " +
34
- "them: a cast run in the wrong group reports a clear path through a " +
35
- "wall the player cannot walk through, and a wrong answer of that shape " +
36
- "is indistinguishable from a right one.\n\n" +
37
- "`worldModel` points the query at a WorldModel inside a ViewportFrame " +
38
- "instead of the Workspace. Those are separate worlds with their own " +
39
- "parts and their own collision groups.",
40
- readOnly: true,
41
- inputSchema: {
42
- op: z
43
- .enum(["cast", "overlap"])
44
- .default("cast")
45
- .describe("'cast' fires a ray/block/sphere and reports the first hit; " +
46
- "'overlap' lists everything inside a volume."),
47
- shape: z
48
- .enum(["ray", "block", "sphere"])
49
- .optional()
50
- .describe("cast only: 'ray' is a line and the usual choice. 'block' and " +
51
- "'sphere' sweep a volume along the same path — use them when the " +
52
- "thing moving has width, e.g. checking whether a character fits " +
53
- "through a gap rather than whether a point does."),
54
- from: position.optional().describe("cast only: where the cast starts."),
55
- to: position
56
- .optional()
57
- .describe("cast only: a point to aim at. Use this for sightlines — it saves " +
58
- "working out a direction vector, which is where sign errors live."),
59
- direction: position
60
- .optional()
61
- .describe('cast only: which way to go, e.g. "0, -1, 0" for straight down. ' +
62
- "Used with `distance`. Ignored when `to` is given."),
63
- distance: z
64
- .number()
65
- .optional()
66
- .describe("cast only: how far to travel along `direction`. Defaults to 100."),
67
- size: position
68
- .optional()
69
- .describe('cast shape="block" or overlap region="box": the volume\'s size.'),
70
- radius: z
71
- .number()
72
- .optional()
73
- .describe('cast shape="sphere" or overlap region="radius": the radius.'),
74
- region: z
75
- .enum(["box", "radius", "part"])
76
- .optional()
77
- .describe("overlap only: 'box' and 'radius' need `at`; 'part' takes `path` " +
78
- "and reports what overlaps that part. Defaults to 'box'."),
79
- at: position.optional().describe('overlap only: the centre, for region "box" or "radius".'),
80
- path: z
81
- .string()
82
- .optional()
83
- .describe('overlap region="part" only: the part to test against.'),
84
- only: z
85
- .array(z.string())
86
- .optional()
87
- .describe("Consider ONLY these instances and their descendants. Use it to " +
88
- 'ask a narrow question — "does anything tagged Door block this".'),
89
- ignore: z
90
- .array(z.string())
91
- .optional()
92
- .describe("Skip these instances and their descendants. The usual case: " +
93
- "ignore the character doing the looking, which otherwise blocks " +
94
- "its own cast at zero distance."),
95
- collisionGroup: z
96
- .string()
97
- .optional()
98
- .describe("Run the query as if it came from a part in this collision group. " +
99
- "Required for a truthful answer in any place that uses groups."),
100
- respectCanCollide: z
101
- .boolean()
102
- .optional()
103
- .describe("Skip parts with CanCollide off. Off by default, matching the " +
104
- "engine — leave it off to ask what is there, turn it on to ask " +
105
- "what would stop a player."),
106
- ignoreWater: z
107
- .boolean()
108
- .optional()
109
- .describe("cast only: pass through terrain water instead of hitting it."),
110
- limit: z
111
- .number()
112
- .int()
113
- .min(1)
114
- .max(500)
115
- .optional()
116
- .describe("overlap only: how many parts to list. Defaults to 50."),
117
- worldModel: z
118
- .string()
119
- .optional()
120
- .describe("Query this WorldModel instead of the Workspace. Omit for the " +
121
- "normal case."),
122
- studioId: z.string().optional().describe("Target Studio; omit for the active one."),
123
- },
124
- }, async (args) => {
125
- const { op, studioId, ...rest } = args;
126
- const response = await bridge.call(op === "overlap" ? "spatial.overlap" : "spatial.cast", rest, { studioId, timeoutMs: 30_000 });
127
- if (op === "cast" && response["hit"] === false) {
128
- return json(response, "Nothing was hit. If that is a surprise, the usual causes are a " +
129
- "`direction` pointing the wrong way, a `distance` shorter than the " +
130
- "gap, or an `only` filter that excludes what you meant to find.");
131
- }
132
- return json(response);
133
- });
134
- }
135
- //# sourceMappingURL=spatial.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"spatial.js","sourceRoot":"","sources":["../../src/tools/spatial.ts"],"names":[],"mappings":"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"}
@@ -1,294 +0,0 @@
1
- import { readFile, stat } from "node:fs/promises";
2
- import { basename, extname, resolve } from "node:path";
3
- import { z } from "zod";
4
- import { credentialStatus, loadCredentials } from "../lib/credentials.js";
5
- import { json, text } from "../lib/format.js";
6
- import { defineTool } from "../lib/tool.js";
7
- /**
8
- * Uploading goes out over Open Cloud rather than through the plugin.
9
- *
10
- * Studio's HttpService is not a route to this: a plugin cannot reach
11
- * apis.roblox.com with a creator API key without shipping that key into the
12
- * place, and the upload endpoint is multipart, which HttpService cannot build.
13
- * The server is an ordinary Node process with the file already on disk, so it
14
- * does the upload itself and hands Studio nothing but the finished asset id.
15
- */
16
- const ASSETS_ENDPOINT = "https://apis.roblox.com/assets/v1/assets";
17
- const OPERATIONS_ENDPOINT = "https://apis.roblox.com/assets/v1/operations";
18
- /** Open Cloud's own cap, per call. */
19
- const MAX_BYTES = 20 * 1024 * 1024;
20
- /**
21
- * Moderation runs before an asset is usable and it is not instant. Roblox
22
- * returns an operation to poll rather than the asset, and audio in particular
23
- * sits in review for a few seconds. Give up well before an agent's own patience
24
- * runs out, and say what happened rather than reporting a failure.
25
- */
26
- const POLL_TIMEOUT_MS = 90_000;
27
- const POLL_INTERVAL_MS = 2_000;
28
- /**
29
- * Extension to (assetType, content type).
30
- *
31
- * Roblox validates both, and rejects mismatches with a message that names
32
- * neither. Derived from the file rather than asked for, because an agent
33
- * choosing "Model" for a .png is a failure that costs a round trip and a
34
- * confusing error.
35
- */
36
- const KINDS = {
37
- ".mp3": { assetType: "Audio", contentType: "audio/mpeg" },
38
- ".ogg": { assetType: "Audio", contentType: "audio/ogg" },
39
- ".wav": { assetType: "Audio", contentType: "audio/wav" },
40
- ".flac": { assetType: "Audio", contentType: "audio/flac" },
41
- ".png": { assetType: "Decal", contentType: "image/png" },
42
- ".jpg": { assetType: "Decal", contentType: "image/jpeg" },
43
- ".jpeg": { assetType: "Decal", contentType: "image/jpeg" },
44
- ".bmp": { assetType: "Decal", contentType: "image/bmp" },
45
- ".tga": { assetType: "Decal", contentType: "image/tga" },
46
- ".fbx": { assetType: "Model", contentType: "model/fbx" },
47
- ".gltf": { assetType: "Model", contentType: "model/gltf+json" },
48
- ".glb": { assetType: "Model", contentType: "model/gltf-binary" },
49
- ".mp4": { assetType: "Video", contentType: "video/mp4" },
50
- ".mov": { assetType: "Video", contentType: "video/mov" },
51
- };
52
- /**
53
- * The credentials, or a sentence explaining what is missing.
54
- *
55
- * Never taken as a tool argument. A key passed as one would be written into the
56
- * conversation, sent to the model, and logged by the client — and it is exactly
57
- * the credential that can publish under the user's name. It comes from the
58
- * environment or from the panel's `cloud key` command instead, and this file
59
- * never returns it to a caller.
60
- */
61
- async function credentials() {
62
- const found = await loadCredentials();
63
- if (found !== null)
64
- return found;
65
- const status = await credentialStatus();
66
- if (status.hasKey) {
67
- return ("An Open Cloud key is set but there is no creator, so Roblox does not " +
68
- "know who the asset belongs to.\n\n" +
69
- "Type `cloud user <id>` in the Studio panel — the id is the number in " +
70
- "your roblox.com/users/NNN/profile URL — or `cloud group <id>` to upload " +
71
- "as a group. Setting ROBLOX_USER_ID in the environment works too.");
72
- }
73
- return ("No Open Cloud key is set, so nothing can be uploaded.\n\n" +
74
- "The quickest way is the Studio panel: type `cloud` there and it walks " +
75
- "through it. The line you type is masked in the log and in the history, and " +
76
- "the key is stored in a file only you can read — it never reaches this " +
77
- "conversation.\n\n" +
78
- "It has to be done by the user, not by an agent, because it needs a " +
79
- "logged-in browser:\n" +
80
- "1. create.roblox.com/dashboard/credentials -> Create API Key\n" +
81
- "2. Access Permissions -> add `assets`, with Read and Write\n" +
82
- "3. Add your IP, save, copy the key (it is shown once)\n" +
83
- "4. In the Studio panel: `cloud key <paste>` then `cloud user <your id>`\n\n" +
84
- "Treat the key like a password: it can publish assets under that account.");
85
- }
86
- /** Turns Open Cloud's failure shapes into one sentence worth reading. */
87
- function describeFailure(status, body) {
88
- let detail = body.slice(0, 400);
89
- try {
90
- const parsed = JSON.parse(body);
91
- detail = parsed.message ?? parsed.errors?.[0]?.message ?? detail;
92
- }
93
- catch {
94
- // Not JSON — Roblox returns bare HTML for some gateway errors.
95
- }
96
- if (status === 401 || status === 403) {
97
- return (`Roblox rejected the API key (${status}): ${detail}\n\n` +
98
- "Check that the key has the `assets` permission with Write, that the " +
99
- "creator id matches who the key was made for, and that your current IP " +
100
- "is in the key's allowed list — an IP that has changed since the key was " +
101
- "made is the most common cause of a 403.");
102
- }
103
- if (status === 429) {
104
- return `Rate limited by Roblox: ${detail}\n\nWait and retry.`;
105
- }
106
- if (status === 400) {
107
- return (`Roblox refused the file (400): ${detail}\n\n` +
108
- "Usually the file does not match its extension, or it breaks a limit: " +
109
- "audio over 7 minutes, images over 8000x8000.");
110
- }
111
- return `Roblox returned ${status}: ${detail}`;
112
- }
113
- async function poll(operationPath, apiKey) {
114
- const id = operationPath.replace(/^operations\//, "");
115
- const deadline = Date.now() + POLL_TIMEOUT_MS;
116
- for (;;) {
117
- const response = await fetch(`${OPERATIONS_ENDPOINT}/${id}`, {
118
- headers: { "x-api-key": apiKey },
119
- });
120
- if (!response.ok) {
121
- throw new Error(describeFailure(response.status, await response.text()));
122
- }
123
- const operation = (await response.json());
124
- if (operation.done)
125
- return operation;
126
- if (Date.now() >= deadline) {
127
- throw new Error(`The upload is still processing after ${POLL_TIMEOUT_MS / 1000}s. It has not ` +
128
- "failed — moderation can take longer. Check the Development Items tab " +
129
- "on the Creator Dashboard in a minute.");
130
- }
131
- await new Promise((done) => setTimeout(done, POLL_INTERVAL_MS));
132
- }
133
- }
134
- export function registerUploadTools(context) {
135
- const { bridge } = context;
136
- defineTool(context, {
137
- name: "upload",
138
- title: "Upload a file to Roblox as an asset",
139
- description: "Uploads a local file — audio, an image, a 3D model or a video — to " +
140
- "Roblox and returns the asset id, so it can be used immediately.\n\n" +
141
- "This closes the one hole nothing else here covers. Every other tool " +
142
- "works with assets that already exist: `assets op=\"search\"` finds " +
143
- "published ones, `generate` makes geometry from a prompt. A sound " +
144
- "effect or a texture sitting in a folder on disk had to be dragged " +
145
- "through Studio's import dialog by hand before anything could reference " +
146
- "it. Now it does not.\n\n" +
147
- "The asset type is worked out from the extension — .mp3/.ogg/.wav/.flac " +
148
- "become Audio, .png/.jpg/.bmp/.tga become a Decal, .fbx/.gltf/.glb " +
149
- "become a Model, .mp4/.mov become a Video.\n\n" +
150
- "Set `insert` with a parent path to drop the result straight into the " +
151
- "place once it is approved: a Decal upload becomes a Decal, a Model " +
152
- "becomes the model. Audio is NOT inserted — an audio id belongs in an " +
153
- "`AudioPlayer` or a `Sound`, so wire it up with `audio op=\"graph\"` " +
154
- "and the id this returns.\n\n" +
155
- "Uploads are moderated and the result says the verdict. They also count " +
156
- "against a real quota: 10 audio uploads a month, or 100 if the account " +
157
- "is ID-verified. Do not upload speculatively, and do not re-upload a " +
158
- "file to retry — the first one probably worked.\n\n" +
159
- "Needs a Roblox Open Cloud API key. The user sets it once by typing " +
160
- "`cloud` in the Studio panel; never ask them to paste a key into this " +
161
- "conversation. Call this tool with no arguments to check whether it is " +
162
- "already set up.",
163
- destructive: false,
164
- inputSchema: {
165
- file: z
166
- .string()
167
- .optional()
168
- .describe("Path to the file on disk. Omit to check whether the credentials " +
169
- "are set up without uploading anything."),
170
- name: z
171
- .string()
172
- .optional()
173
- .describe("Display name for the asset. Defaults to the file name. This is " +
174
- "public and moderated — a name that breaks the rules fails the " +
175
- "whole upload."),
176
- description: z.string().optional().describe("Public description. Also moderated."),
177
- assetType: z
178
- .enum(["Audio", "Decal", "Model", "Video"])
179
- .optional()
180
- .describe("Override the type derived from the extension. Rarely right — " +
181
- "Roblox validates the type against the file's real content."),
182
- insert: z
183
- .string()
184
- .optional()
185
- .describe('Where to put the asset in the place once approved, e.g. "Workspace". ' +
186
- "Applies to Decal and Model uploads. Omit to only get the id back."),
187
- studioId: z.string().optional().describe("Target Studio; omit for the active one."),
188
- },
189
- }, async (args) => {
190
- const credential = await credentials();
191
- if (typeof credential === "string")
192
- return text(credential);
193
- if (!args.file) {
194
- return text(`Ready to upload: key set, uploading as ${credential.creatorField} ` +
195
- `${credential.creatorId}. Call again with \`file\` pointing at the ` +
196
- "file you want uploaded.");
197
- }
198
- const path = resolve(args.file);
199
- let size;
200
- try {
201
- size = (await stat(path)).size;
202
- }
203
- catch {
204
- return text(`There is no file at ${path}. Give an absolute path, or one relative ` +
205
- "to the directory the server was started in.");
206
- }
207
- if (size === 0)
208
- return text(`${path} is empty.`);
209
- if (size > MAX_BYTES) {
210
- return text(`${path} is ${(size / 1024 / 1024).toFixed(1)}MB. Open Cloud accepts ` +
211
- "20MB per upload.");
212
- }
213
- const extension = extname(path).toLowerCase();
214
- const kind = KINDS[extension];
215
- if (!kind) {
216
- return text(`Roblox does not take ${extension || "files with no extension"} through ` +
217
- `Open Cloud. It takes: ${Object.keys(KINDS).join(", ")}.`);
218
- }
219
- const assetType = args.assetType ?? kind.assetType;
220
- const form = new FormData();
221
- form.append("request", JSON.stringify({
222
- assetType,
223
- displayName: args.name ?? basename(path, extension),
224
- description: args.description ?? "",
225
- creationContext: {
226
- creator: { [credential.creatorField]: credential.creatorId },
227
- },
228
- }));
229
- form.append("fileContent", new Blob([new Uint8Array(await readFile(path))], { type: kind.contentType }), basename(path));
230
- const started = await fetch(ASSETS_ENDPOINT, {
231
- method: "POST",
232
- headers: { "x-api-key": credential.apiKey },
233
- body: form,
234
- });
235
- if (!started.ok) {
236
- return text(describeFailure(started.status, await started.text()));
237
- }
238
- const operation = (await started.json());
239
- if (!operation.path) {
240
- return text("Roblox accepted the upload but did not say where to track it. " +
241
- "Check the Development Items tab on the Creator Dashboard.");
242
- }
243
- const finished = await poll(operation.path, credential.apiKey);
244
- if (finished.error || !finished.response?.assetId) {
245
- return text(`The upload failed moderation or validation: ` +
246
- `${finished.error?.message ?? finished.message ?? "no reason given"}`);
247
- }
248
- const assetId = finished.response.assetId;
249
- const moderation = finished.response.moderationResult?.moderationState ?? "unknown";
250
- const result = {
251
- assetId,
252
- assetType,
253
- uri: `rbxassetid://${assetId}`,
254
- name: finished.response.displayName,
255
- moderation,
256
- uploadedAs: `${credential.creatorField} ${credential.creatorId}`,
257
- file: path,
258
- };
259
- // Only inserted once Roblox says it is approved. Inserting a rejected
260
- // asset puts an instance in the place that resolves to nothing, which
261
- // reads as a bug in this tool rather than as a moderation decision.
262
- if (args.insert && moderation === "MODERATION_STATE_APPROVED") {
263
- if (assetType === "Audio") {
264
- result["inserted"] = false;
265
- result["note"] =
266
- "Audio is not inserted on its own — the id goes into an AudioPlayer " +
267
- 'or a Sound. Build one with `audio op="graph" asset="' +
268
- `rbxassetid://${assetId}"\`.`;
269
- }
270
- else if (assetType === "Decal") {
271
- const created = await bridge.call("instances.create", {
272
- instances: [
273
- {
274
- className: "Decal",
275
- name: args.name ?? basename(path, extension),
276
- parent: args.insert,
277
- properties: { Texture: { value: `rbxassetid://${assetId}`, type: "ContentId" } },
278
- },
279
- ],
280
- }, { studioId: args.studioId, timeoutMs: 30_000 });
281
- result["inserted"] = created;
282
- }
283
- else {
284
- const inserted = await bridge.call("assets.insert", { assetId: Number(assetId), parent: args.insert }, { studioId: args.studioId, timeoutMs: 90_000 });
285
- result["inserted"] = inserted;
286
- }
287
- }
288
- return json(result, moderation === "MODERATION_STATE_APPROVED"
289
- ? `Use it as rbxassetid://${assetId}.`
290
- : `Moderation says ${moderation}. The id exists but may not load until ` +
291
- "review finishes.");
292
- });
293
- }
294
- //# sourceMappingURL=upload.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"upload.js","sourceRoot":"","sources":["../../src/tools/upload.ts"],"names":[],"mappings":"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"}