rbxstudio-mcp 2.3.2 → 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/README.md +67 -14
- package/dist/__tests__/bridge-service.test.js +25 -13
- package/dist/__tests__/bridge-service.test.js.map +1 -1
- package/dist/__tests__/bridge-session.test.d.ts +2 -0
- package/dist/__tests__/bridge-session.test.d.ts.map +1 -0
- package/dist/__tests__/bridge-session.test.js +171 -0
- package/dist/__tests__/bridge-session.test.js.map +1 -0
- package/dist/__tests__/chunker.test.d.ts +2 -0
- package/dist/__tests__/chunker.test.d.ts.map +1 -0
- package/dist/__tests__/chunker.test.js +201 -0
- package/dist/__tests__/chunker.test.js.map +1 -0
- package/dist/__tests__/docs-core.test.d.ts +2 -0
- package/dist/__tests__/docs-core.test.d.ts.map +1 -0
- package/dist/__tests__/docs-core.test.js +137 -0
- package/dist/__tests__/docs-core.test.js.map +1 -0
- package/dist/__tests__/docs-fetcher.test.d.ts +2 -0
- package/dist/__tests__/docs-fetcher.test.d.ts.map +1 -0
- package/dist/__tests__/docs-fetcher.test.js +173 -0
- package/dist/__tests__/docs-fetcher.test.js.map +1 -0
- package/dist/__tests__/helpers.d.ts +8 -0
- package/dist/__tests__/helpers.d.ts.map +1 -0
- package/dist/__tests__/helpers.js +23 -0
- package/dist/__tests__/helpers.js.map +1 -0
- package/dist/__tests__/http-routes.test.d.ts +2 -0
- package/dist/__tests__/http-routes.test.d.ts.map +1 -0
- package/dist/__tests__/http-routes.test.js +233 -0
- package/dist/__tests__/http-routes.test.js.map +1 -0
- package/dist/__tests__/http-server.test.js +13 -6
- package/dist/__tests__/http-server.test.js.map +1 -1
- package/dist/__tests__/integration.test.js +9 -4
- package/dist/__tests__/integration.test.js.map +1 -1
- package/dist/__tests__/semantic-search.test.d.ts +2 -0
- package/dist/__tests__/semantic-search.test.d.ts.map +1 -0
- package/dist/__tests__/semantic-search.test.js +202 -0
- package/dist/__tests__/semantic-search.test.js.map +1 -0
- package/dist/__tests__/smoke.test.js +7 -3
- package/dist/__tests__/smoke.test.js.map +1 -1
- package/dist/__tests__/studio-client.test.d.ts +2 -0
- package/dist/__tests__/studio-client.test.d.ts.map +1 -0
- package/dist/__tests__/studio-client.test.js +25 -0
- package/dist/__tests__/studio-client.test.js.map +1 -0
- package/dist/__tests__/tool-nudges.test.d.ts +2 -0
- package/dist/__tests__/tool-nudges.test.d.ts.map +1 -0
- package/dist/__tests__/tool-nudges.test.js +60 -0
- package/dist/__tests__/tool-nudges.test.js.map +1 -0
- package/dist/__tests__/tool-registry.test.d.ts +2 -0
- package/dist/__tests__/tool-registry.test.d.ts.map +1 -0
- package/dist/__tests__/tool-registry.test.js +365 -0
- package/dist/__tests__/tool-registry.test.js.map +1 -0
- package/dist/__tests__/tools-bridge.test.d.ts +2 -0
- package/dist/__tests__/tools-bridge.test.d.ts.map +1 -0
- package/dist/__tests__/tools-bridge.test.js +396 -0
- package/dist/__tests__/tools-bridge.test.js.map +1 -0
- package/dist/__tests__/tools-docs.test.d.ts +2 -0
- package/dist/__tests__/tools-docs.test.d.ts.map +1 -0
- package/dist/__tests__/tools-docs.test.js +112 -0
- package/dist/__tests__/tools-docs.test.js.map +1 -0
- package/dist/__tests__/tools-guards.test.d.ts +2 -0
- package/dist/__tests__/tools-guards.test.d.ts.map +1 -0
- package/dist/__tests__/tools-guards.test.js +131 -0
- package/dist/__tests__/tools-guards.test.js.map +1 -0
- package/dist/__tests__/tools-runtime.test.d.ts +2 -0
- package/dist/__tests__/tools-runtime.test.d.ts.map +1 -0
- package/dist/__tests__/tools-runtime.test.js +214 -0
- package/dist/__tests__/tools-runtime.test.js.map +1 -0
- package/dist/__tests__/tools-visual.test.d.ts +2 -0
- package/dist/__tests__/tools-visual.test.d.ts.map +1 -0
- package/dist/__tests__/tools-visual.test.js +149 -0
- package/dist/__tests__/tools-visual.test.js.map +1 -0
- package/dist/bridge-service.d.ts +99 -12
- package/dist/bridge-service.d.ts.map +1 -1
- package/dist/bridge-service.js +238 -21
- package/dist/bridge-service.js.map +1 -1
- package/dist/docs/cache.d.ts +50 -0
- package/dist/docs/cache.d.ts.map +1 -0
- package/dist/docs/cache.js +123 -0
- package/dist/docs/cache.js.map +1 -0
- package/dist/docs/embeddings/chunker.d.ts +120 -0
- package/dist/docs/embeddings/chunker.d.ts.map +1 -0
- package/dist/docs/embeddings/chunker.js +395 -0
- package/dist/docs/embeddings/chunker.js.map +1 -0
- package/dist/docs/embeddings/embedder.d.ts +41 -0
- package/dist/docs/embeddings/embedder.d.ts.map +1 -0
- package/dist/docs/embeddings/embedder.js +113 -0
- package/dist/docs/embeddings/embedder.js.map +1 -0
- package/dist/docs/embeddings/index.d.ts +102 -0
- package/dist/docs/embeddings/index.d.ts.map +1 -0
- package/dist/docs/embeddings/index.js +250 -0
- package/dist/docs/embeddings/index.js.map +1 -0
- package/dist/docs/embeddings/manager.d.ts +68 -0
- package/dist/docs/embeddings/manager.d.ts.map +1 -0
- package/dist/docs/embeddings/manager.js +97 -0
- package/dist/docs/embeddings/manager.js.map +1 -0
- package/dist/docs/fetcher.d.ts +29 -0
- package/dist/docs/fetcher.d.ts.map +1 -0
- package/dist/docs/fetcher.js +244 -0
- package/dist/docs/fetcher.js.map +1 -0
- package/dist/docs/reference.d.ts +37 -0
- package/dist/docs/reference.d.ts.map +1 -0
- package/dist/docs/reference.js +108 -0
- package/dist/docs/reference.js.map +1 -0
- package/dist/docs/search.d.ts +194 -0
- package/dist/docs/search.d.ts.map +1 -0
- package/dist/docs/search.js +733 -0
- package/dist/docs/search.js.map +1 -0
- package/dist/http-server.d.ts.map +1 -1
- package/dist/http-server.js +52 -5
- package/dist/http-server.js.map +1 -1
- package/dist/index.d.ts +8 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -1035
- package/dist/index.js.map +1 -1
- package/dist/instructions.d.ts +15 -0
- package/dist/instructions.d.ts.map +1 -0
- package/dist/instructions.js +26 -0
- package/dist/instructions.js.map +1 -0
- package/dist/tools/defs/attributes.d.ts +6 -0
- package/dist/tools/defs/attributes.d.ts.map +1 -0
- package/dist/tools/defs/attributes.js +85 -0
- package/dist/tools/defs/attributes.js.map +1 -0
- package/dist/tools/defs/docs.d.ts +17 -0
- package/dist/tools/defs/docs.d.ts.map +1 -0
- package/dist/tools/defs/docs.js +151 -0
- package/dist/tools/defs/docs.js.map +1 -0
- package/dist/tools/defs/execute.d.ts +6 -0
- package/dist/tools/defs/execute.d.ts.map +1 -0
- package/dist/tools/defs/execute.js +21 -0
- package/dist/tools/defs/execute.js.map +1 -0
- package/dist/tools/defs/inspection.d.ts +7 -0
- package/dist/tools/defs/inspection.d.ts.map +1 -0
- package/dist/tools/defs/inspection.js +202 -0
- package/dist/tools/defs/inspection.js.map +1 -0
- package/dist/tools/defs/objects.d.ts +6 -0
- package/dist/tools/defs/objects.d.ts.map +1 -0
- package/dist/tools/defs/objects.js +111 -0
- package/dist/tools/defs/objects.js.map +1 -0
- package/dist/tools/defs/properties.d.ts +6 -0
- package/dist/tools/defs/properties.d.ts.map +1 -0
- package/dist/tools/defs/properties.js +71 -0
- package/dist/tools/defs/properties.js.map +1 -0
- package/dist/tools/defs/runtime.d.ts +6 -0
- package/dist/tools/defs/runtime.d.ts.map +1 -0
- package/dist/tools/defs/runtime.js +145 -0
- package/dist/tools/defs/runtime.js.map +1 -0
- package/dist/tools/defs/scripts.d.ts +18 -0
- package/dist/tools/defs/scripts.d.ts.map +1 -0
- package/dist/tools/defs/scripts.js +163 -0
- package/dist/tools/defs/scripts.js.map +1 -0
- package/dist/tools/defs/tags.d.ts +6 -0
- package/dist/tools/defs/tags.d.ts.map +1 -0
- package/dist/tools/defs/tags.js +74 -0
- package/dist/tools/defs/tags.js.map +1 -0
- package/dist/tools/defs/visual.d.ts +7 -0
- package/dist/tools/defs/visual.d.ts.map +1 -0
- package/dist/tools/defs/visual.js +208 -0
- package/dist/tools/defs/visual.js.map +1 -0
- package/dist/tools/index.d.ts +101 -25
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +580 -63
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/nudges.d.ts +25 -0
- package/dist/tools/nudges.d.ts.map +1 -0
- package/dist/tools/nudges.js +34 -0
- package/dist/tools/nudges.js.map +1 -0
- package/dist/tools/registry.d.ts +20 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +65 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/types.d.ts +24 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +2 -0
- package/dist/tools/types.js.map +1 -0
- package/package.json +7 -6
- package/studio-plugin/MCPPlugin.rbxmx +3 -238
- package/studio-plugin/plugin.luau +2041 -365
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic-search integration tests with a STUBBED embedder.
|
|
3
|
+
*
|
|
4
|
+
* We don't want CI to download a 25MB ONNX model on every run, so
|
|
5
|
+
* `__setEmbedderForTests` lets us inject a tiny deterministic
|
|
6
|
+
* pseudo-embedding: hash each token to a 384-dim sparse vector. The
|
|
7
|
+
* result isn't great for real retrieval but is *deterministic* and
|
|
8
|
+
* lets us verify:
|
|
9
|
+
*
|
|
10
|
+
* 1. Index build → load → query happy path.
|
|
11
|
+
* 2. SHA mismatch invalidates the on-disk index.
|
|
12
|
+
* 3. Hybrid mode falls back to keyword when no SHA is provided.
|
|
13
|
+
* 4. Hybrid mode actually reorders hits when the index is hot.
|
|
14
|
+
* 5. The `semantic: false` knob bypasses semantic entirely.
|
|
15
|
+
* 6. Every returned hit still satisfies token-AND (no lexical cheating).
|
|
16
|
+
*/
|
|
17
|
+
import { promises as fs } from 'fs';
|
|
18
|
+
import * as os from 'os';
|
|
19
|
+
import * as path from 'path';
|
|
20
|
+
import { __setEmbedderForTests, EMBED_DIM, } from '../docs/embeddings/embedder.js';
|
|
21
|
+
import { buildIndex, loadIndex } from '../docs/embeddings/index.js';
|
|
22
|
+
import { getOrBuild, __setIndexForTests, invalidate } from '../docs/embeddings/manager.js';
|
|
23
|
+
import { searchDocs } from '../docs/search.js';
|
|
24
|
+
/**
|
|
25
|
+
* Deterministic fake embedder.
|
|
26
|
+
*
|
|
27
|
+
* Build a 384-dim sparse vector by hashing each whitespace-separated
|
|
28
|
+
* token to a couple of dimensions. Same token → same bits. Two texts
|
|
29
|
+
* sharing tokens will have positive cosine; texts with no shared
|
|
30
|
+
* tokens will have ~0 cosine. Good enough to test ranking logic.
|
|
31
|
+
*/
|
|
32
|
+
function fakeEmbed(texts) {
|
|
33
|
+
const out = [];
|
|
34
|
+
for (const text of texts) {
|
|
35
|
+
const v = new Float32Array(EMBED_DIM);
|
|
36
|
+
const tokens = text.toLowerCase().split(/\s+/).filter(Boolean);
|
|
37
|
+
for (const tok of tokens) {
|
|
38
|
+
// Two-hash sparse encoding to spread tokens across the vector.
|
|
39
|
+
let h1 = 2166136261;
|
|
40
|
+
let h2 = 5381;
|
|
41
|
+
for (let i = 0; i < tok.length; i++) {
|
|
42
|
+
h1 = ((h1 ^ tok.charCodeAt(i)) * 16777619) >>> 0;
|
|
43
|
+
h2 = ((h2 << 5) + h2 + tok.charCodeAt(i)) >>> 0;
|
|
44
|
+
}
|
|
45
|
+
v[h1 % EMBED_DIM] += 1;
|
|
46
|
+
v[h2 % EMBED_DIM] += 0.5;
|
|
47
|
+
}
|
|
48
|
+
// L2-normalize so dot = cosine.
|
|
49
|
+
let norm = 0;
|
|
50
|
+
for (let i = 0; i < EMBED_DIM; i++)
|
|
51
|
+
norm += v[i] * v[i];
|
|
52
|
+
norm = Math.sqrt(norm) || 1;
|
|
53
|
+
for (let i = 0; i < EMBED_DIM; i++)
|
|
54
|
+
v[i] /= norm;
|
|
55
|
+
out.push(v);
|
|
56
|
+
}
|
|
57
|
+
return Promise.resolve(out);
|
|
58
|
+
}
|
|
59
|
+
async function makeFixtureCache() {
|
|
60
|
+
const dir = await fs.mkdtemp(path.join(os.tmpdir(), 'rbxdocs-sem-'));
|
|
61
|
+
const en = path.join(dir, 'content', 'en-us');
|
|
62
|
+
await fs.mkdir(path.join(en, 'reference', 'engine', 'classes'), { recursive: true });
|
|
63
|
+
await fs.mkdir(path.join(en, 'animation'), { recursive: true });
|
|
64
|
+
// A "rotate body part smoothly" query should rank AlignOrientation
|
|
65
|
+
// higher than a passing mention in an unrelated guide.
|
|
66
|
+
await fs.writeFile(path.join(en, 'reference', 'engine', 'classes', 'AlignOrientation.yaml'), `name: AlignOrientation
|
|
67
|
+
type: class
|
|
68
|
+
summary: |
|
|
69
|
+
AlignOrientation smoothly rotates an Attachment toward a target orientation.
|
|
70
|
+
description: |
|
|
71
|
+
Use AlignOrientation when you want to smoothly rotate one part to face
|
|
72
|
+
a direction over time. It applies torque to align the orientation.
|
|
73
|
+
properties:
|
|
74
|
+
- name: MaxTorque
|
|
75
|
+
summary: |
|
|
76
|
+
The maximum torque applied to rotate the attachment.
|
|
77
|
+
type: number
|
|
78
|
+
- name: Responsiveness
|
|
79
|
+
summary: |
|
|
80
|
+
Higher responsiveness rotates the part more aggressively.
|
|
81
|
+
type: number
|
|
82
|
+
`, 'utf8');
|
|
83
|
+
await fs.writeFile(path.join(en, 'reference', 'engine', 'classes', 'Motor6D.yaml'), `name: Motor6D
|
|
84
|
+
type: class
|
|
85
|
+
summary: |
|
|
86
|
+
A Motor6D is a joint that rotates between two parts using C0 and C1 offsets.
|
|
87
|
+
description: |
|
|
88
|
+
Motor6D drives character animation by rotating Part1 relative to Part0.
|
|
89
|
+
properties:
|
|
90
|
+
- name: C0
|
|
91
|
+
summary: |
|
|
92
|
+
The CFrame offset of the joint on Part0. Setting C0 rotates Part1 around Part0.
|
|
93
|
+
type: CFrame
|
|
94
|
+
- name: C1
|
|
95
|
+
summary: |
|
|
96
|
+
The CFrame offset of the joint on Part1.
|
|
97
|
+
type: CFrame
|
|
98
|
+
`, 'utf8');
|
|
99
|
+
// Unrelated guide that *mentions* rotate / smooth / part in passing
|
|
100
|
+
// — keyword token-AND will match it, but semantic rerank should
|
|
101
|
+
// push it down below AlignOrientation.
|
|
102
|
+
await fs.writeFile(path.join(en, 'animation', 'unrelated-rotate.md'), `# Unrelated guide that happens to mention rotate and part
|
|
103
|
+
This page is about something else entirely.
|
|
104
|
+
It might smoothly rotate a part but only as an aside.
|
|
105
|
+
The main topic is something else: building a UI, scripting a server, configuring inventories.
|
|
106
|
+
You can rotate a part by setting CFrame directly, smoothly or not.
|
|
107
|
+
Most of this page talks about UI buttons and inventory grids and server scripts.
|
|
108
|
+
${'unrelated text '.repeat(40)}
|
|
109
|
+
`, 'utf8');
|
|
110
|
+
return dir;
|
|
111
|
+
}
|
|
112
|
+
describe('semantic-search: end-to-end with fake embedder', () => {
|
|
113
|
+
let cacheDir;
|
|
114
|
+
const FAKE_SHA = 'deadbeefcafe1234';
|
|
115
|
+
beforeAll(async () => {
|
|
116
|
+
__setEmbedderForTests(fakeEmbed);
|
|
117
|
+
cacheDir = await makeFixtureCache();
|
|
118
|
+
});
|
|
119
|
+
afterAll(async () => {
|
|
120
|
+
__setEmbedderForTests(null);
|
|
121
|
+
__setIndexForTests(null);
|
|
122
|
+
if (cacheDir)
|
|
123
|
+
await fs.rm(cacheDir, { recursive: true, force: true });
|
|
124
|
+
});
|
|
125
|
+
beforeEach(() => {
|
|
126
|
+
invalidate(); // clear in-memory manager between tests
|
|
127
|
+
});
|
|
128
|
+
test('buildIndex → loadIndex round-trips', async () => {
|
|
129
|
+
const meta = await buildIndex(cacheDir, FAKE_SHA);
|
|
130
|
+
expect(meta.sha).toBe(FAKE_SHA);
|
|
131
|
+
expect(meta.dim).toBe(EMBED_DIM);
|
|
132
|
+
expect(meta.chunkCount).toBeGreaterThan(0);
|
|
133
|
+
const loaded = await loadIndex(cacheDir, FAKE_SHA);
|
|
134
|
+
expect(loaded).not.toBeNull();
|
|
135
|
+
expect(loaded.chunks.length).toBe(meta.chunkCount);
|
|
136
|
+
expect(loaded.vectors.length).toBe(meta.chunkCount * meta.dim);
|
|
137
|
+
});
|
|
138
|
+
test('loadIndex rejects on SHA mismatch', async () => {
|
|
139
|
+
await buildIndex(cacheDir, FAKE_SHA);
|
|
140
|
+
const wrong = await loadIndex(cacheDir, 'different-sha');
|
|
141
|
+
expect(wrong).toBeNull();
|
|
142
|
+
});
|
|
143
|
+
test('getOrBuild memoizes after first call', async () => {
|
|
144
|
+
const first = await getOrBuild(cacheDir, FAKE_SHA);
|
|
145
|
+
expect(first).not.toBeNull();
|
|
146
|
+
// Stash a sentinel; if memoization works the second call returns the
|
|
147
|
+
// exact same reference.
|
|
148
|
+
const second = await getOrBuild(cacheDir, FAKE_SHA);
|
|
149
|
+
expect(second).toBe(first);
|
|
150
|
+
});
|
|
151
|
+
test('searchDocs hybrid mode reranks AlignOrientation above unrelated guide', async () => {
|
|
152
|
+
// Warm the index.
|
|
153
|
+
await getOrBuild(cacheDir, FAKE_SHA);
|
|
154
|
+
const result = await searchDocs(cacheDir, 'rotate part smoothly', { maxHits: 20 }, { docsSha: FAKE_SHA });
|
|
155
|
+
expect(result.mode).toBe('hybrid');
|
|
156
|
+
expect(result.semanticUsed).toBe(true);
|
|
157
|
+
expect(result.hits.length).toBeGreaterThan(0);
|
|
158
|
+
// Find the rank of AlignOrientation vs. the unrelated guide.
|
|
159
|
+
const alignRank = result.hits.findIndex((h) => h.path.includes('AlignOrientation'));
|
|
160
|
+
const unrelatedRank = result.hits.findIndex((h) => h.path.includes('unrelated-rotate'));
|
|
161
|
+
// Both should appear (token-AND filter lets them through).
|
|
162
|
+
expect(alignRank).toBeGreaterThanOrEqual(0);
|
|
163
|
+
expect(unrelatedRank).toBeGreaterThanOrEqual(0);
|
|
164
|
+
// AlignOrientation has more topical density → higher fake-cosine
|
|
165
|
+
// → should rank above the unrelated guide.
|
|
166
|
+
expect(alignRank).toBeLessThan(unrelatedRank);
|
|
167
|
+
// And every hit has a score field.
|
|
168
|
+
for (const h of result.hits) {
|
|
169
|
+
expect(typeof h.score).toBe('number');
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
test('semantic: false skips rerank and stays in token-AND mode', async () => {
|
|
173
|
+
const result = await searchDocs(cacheDir, 'rotate part smoothly', { maxHits: 20, semantic: false }, { docsSha: FAKE_SHA });
|
|
174
|
+
expect(result.mode).toBe('token-and');
|
|
175
|
+
for (const h of result.hits) {
|
|
176
|
+
// No score field in plain token-AND mode.
|
|
177
|
+
expect(h.score).toBeUndefined();
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
test('hybrid mode without docsSha falls back to keyword ranking', async () => {
|
|
181
|
+
const result = await searchDocs(cacheDir, 'rotate part smoothly', {
|
|
182
|
+
maxHits: 20,
|
|
183
|
+
});
|
|
184
|
+
expect(result.mode).toBe('hybrid');
|
|
185
|
+
expect(result.semanticUsed).toBe(false);
|
|
186
|
+
});
|
|
187
|
+
test('hybrid mode preserves token-AND guarantee (every hit contains all tokens)', async () => {
|
|
188
|
+
await getOrBuild(cacheDir, FAKE_SHA);
|
|
189
|
+
const result = await searchDocs(cacheDir, 'rotate part smoothly', { maxHits: 20 }, { docsSha: FAKE_SHA });
|
|
190
|
+
for (const hit of result.hits) {
|
|
191
|
+
// matchedTokens should be present (carried through from token-AND
|
|
192
|
+
// pool). The token-AND guarantee is "every token appears in the
|
|
193
|
+
// window", which the keyword pass already enforced.
|
|
194
|
+
expect(Array.isArray(hit.matchedTokens)).toBe(true);
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
test('single-token query stays in literal mode (no semantic touch)', async () => {
|
|
198
|
+
const result = await searchDocs(cacheDir, 'Motor6D', { maxHits: 20 }, { docsSha: FAKE_SHA });
|
|
199
|
+
expect(result.mode).toBe('literal');
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
//# sourceMappingURL=semantic-search.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semantic-search.test.js","sourceRoot":"","sources":["../../src/__tests__/semantic-search.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@ import { BridgeService } from '../bridge-service.js';
|
|
|
2
2
|
import { createHttpServer } from '../http-server.js';
|
|
3
3
|
import { RobloxStudioTools } from '../tools/index.js';
|
|
4
4
|
import request from 'supertest';
|
|
5
|
+
import { observeRejection } from './helpers.js';
|
|
5
6
|
describe('Smoke Tests - Connection Fixes', () => {
|
|
6
7
|
test('BridgeService should be instantiable', () => {
|
|
7
8
|
const bridge = new BridgeService();
|
|
@@ -23,6 +24,8 @@ describe('Smoke Tests - Connection Fixes', () => {
|
|
|
23
24
|
// Don't wait for these promises - they'll be rejected
|
|
24
25
|
const promise1 = bridge.sendRequest('/test1', {});
|
|
25
26
|
const promise2 = bridge.sendRequest('/test2', {});
|
|
27
|
+
const rejection1 = observeRejection(promise1);
|
|
28
|
+
const rejection2 = observeRejection(promise2);
|
|
26
29
|
// Should have pending requests
|
|
27
30
|
expect(bridge.getPendingRequest()).toBeTruthy();
|
|
28
31
|
// Clear all requests
|
|
@@ -30,8 +33,8 @@ describe('Smoke Tests - Connection Fixes', () => {
|
|
|
30
33
|
// Should have no pending requests
|
|
31
34
|
expect(bridge.getPendingRequest()).toBeNull();
|
|
32
35
|
// Promises should reject
|
|
33
|
-
await expect(
|
|
34
|
-
await expect(
|
|
36
|
+
await expect(rejection1).resolves.toThrow('Connection closed');
|
|
37
|
+
await expect(rejection2).resolves.toThrow('Connection closed');
|
|
35
38
|
});
|
|
36
39
|
test('Disconnect endpoint should clear pending requests', async () => {
|
|
37
40
|
const bridge = new BridgeService();
|
|
@@ -39,12 +42,13 @@ describe('Smoke Tests - Connection Fixes', () => {
|
|
|
39
42
|
const app = createHttpServer(tools, bridge);
|
|
40
43
|
// Add a pending request (don't await it)
|
|
41
44
|
const pendingPromise = bridge.sendRequest('/test', { data: 'test' });
|
|
45
|
+
const rejection = observeRejection(pendingPromise);
|
|
42
46
|
// Disconnect should clear it
|
|
43
47
|
await request(app)
|
|
44
48
|
.post('/disconnect')
|
|
45
49
|
.expect(200);
|
|
46
50
|
// Request should be rejected
|
|
47
|
-
await expect(
|
|
51
|
+
await expect(rejection).resolves.toThrow('Connection closed');
|
|
48
52
|
});
|
|
49
53
|
test('Connection states should update correctly', async () => {
|
|
50
54
|
const bridge = new BridgeService();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"smoke.test.js","sourceRoot":"","sources":["../../src/__tests__/smoke.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,OAAO,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"smoke.test.js","sourceRoot":"","sources":["../../src/__tests__/smoke.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"studio-client.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/studio-client.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { StudioHttpClient } from '../tools/studio-client';
|
|
2
|
+
describe('StudioHttpClient', () => {
|
|
3
|
+
test('returns bridge responses unchanged', async () => {
|
|
4
|
+
const bridge = {
|
|
5
|
+
sendRequest: jest.fn().mockResolvedValue({ ok: true }),
|
|
6
|
+
};
|
|
7
|
+
await expect(new StudioHttpClient(bridge).request('/api/test', { a: 1 })).resolves.toEqual({
|
|
8
|
+
ok: true,
|
|
9
|
+
});
|
|
10
|
+
expect(bridge.sendRequest).toHaveBeenCalledWith('/api/test', { a: 1 });
|
|
11
|
+
});
|
|
12
|
+
test('maps bridge request timeouts to actionable Studio plugin guidance', async () => {
|
|
13
|
+
const bridge = {
|
|
14
|
+
sendRequest: jest.fn().mockRejectedValue(new Error('Request timeout')),
|
|
15
|
+
};
|
|
16
|
+
await expect(new StudioHttpClient(bridge).request('/api/test', {})).rejects.toThrow('Studio plugin connection timeout');
|
|
17
|
+
});
|
|
18
|
+
test('preserves non-timeout bridge errors', async () => {
|
|
19
|
+
const bridge = {
|
|
20
|
+
sendRequest: jest.fn().mockRejectedValue(new Error('boom')),
|
|
21
|
+
};
|
|
22
|
+
await expect(new StudioHttpClient(bridge).request('/api/test', {})).rejects.toThrow('boom');
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
//# sourceMappingURL=studio-client.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"studio-client.test.js","sourceRoot":"","sources":["../../src/__tests__/studio-client.test.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAE1D,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;IAChC,IAAI,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;QACpD,MAAM,MAAM,GAAG;YACb,WAAW,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;SAC3B,CAAC;QAE9B,MAAM,MAAM,CAAC,IAAI,gBAAgB,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC;YACzF,EAAE,EAAE,IAAI;SACT,CAAC,CAAC;QACH,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC,oBAAoB,CAAC,WAAW,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,mEAAmE,EAAE,KAAK,IAAI,EAAE;QACnF,MAAM,MAAM,GAAG;YACb,WAAW,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,iBAAiB,CAAC,CAAC;SAC3C,CAAC;QAE9B,MAAM,MAAM,CAAC,IAAI,gBAAgB,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CACjF,kCAAkC,CACnC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,qCAAqC,EAAE,KAAK,IAAI,EAAE;QACrD,MAAM,MAAM,GAAG;YACb,WAAW,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC;SAChC,CAAC;QAE9B,MAAM,MAAM,CAAC,IAAI,gBAAgB,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9F,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-nudges.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/tool-nudges.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { applyNudge, toolsByName } from '../tools/registry';
|
|
2
|
+
import { INSTRUMENT_OVER_PLAYTEST, RUN_LIVE_LUA_NUDGE, STILL_FRAME_NUDGE, } from '../tools/nudges';
|
|
3
|
+
describe('applyNudge', () => {
|
|
4
|
+
test('is a no-op when there is no nudge', () => {
|
|
5
|
+
const result = { content: [{ type: 'text', text: 'body' }] };
|
|
6
|
+
expect(applyNudge(result, undefined)).toBe(result);
|
|
7
|
+
});
|
|
8
|
+
test('appends the nudge as a trailing text block on MCP-shaped results', () => {
|
|
9
|
+
const result = { content: [{ type: 'text', text: 'body' }] };
|
|
10
|
+
const out = applyNudge(result, 'be careful');
|
|
11
|
+
// Original is not mutated.
|
|
12
|
+
expect(result.content).toHaveLength(1);
|
|
13
|
+
// New result has the nudge appended last.
|
|
14
|
+
expect(out.content).toHaveLength(2);
|
|
15
|
+
expect(out.content[0]).toEqual({ type: 'text', text: 'body' });
|
|
16
|
+
expect(out.content[1]).toEqual({ type: 'text', text: 'be careful' });
|
|
17
|
+
});
|
|
18
|
+
test('preserves sibling result fields (e.g. image blocks, isError)', () => {
|
|
19
|
+
const result = {
|
|
20
|
+
isError: false,
|
|
21
|
+
content: [
|
|
22
|
+
{ type: 'text', text: 'meta' },
|
|
23
|
+
{ type: 'image', data: 'abc', mimeType: 'image/png' },
|
|
24
|
+
],
|
|
25
|
+
};
|
|
26
|
+
const out = applyNudge(result, 'hint');
|
|
27
|
+
expect(out.isError).toBe(false);
|
|
28
|
+
expect(out.content).toHaveLength(3);
|
|
29
|
+
expect(out.content[2]).toEqual({ type: 'text', text: 'hint' });
|
|
30
|
+
// Image block survives unchanged.
|
|
31
|
+
expect(out.content[1]).toEqual({ type: 'image', data: 'abc', mimeType: 'image/png' });
|
|
32
|
+
});
|
|
33
|
+
test('returns non-MCP-shaped results untouched even with a nudge', () => {
|
|
34
|
+
const plain = { foo: 'bar' };
|
|
35
|
+
expect(applyNudge(plain, 'hint')).toBe(plain);
|
|
36
|
+
expect(applyNudge('just-a-string', 'hint')).toBe('just-a-string');
|
|
37
|
+
expect(applyNudge(undefined, 'hint')).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
describe('tool nudge wiring', () => {
|
|
41
|
+
const expected = {
|
|
42
|
+
play_solo: INSTRUMENT_OVER_PLAYTEST,
|
|
43
|
+
get_playtest_output: INSTRUMENT_OVER_PLAYTEST,
|
|
44
|
+
run_live_lua: RUN_LIVE_LUA_NUDGE,
|
|
45
|
+
capture_screenshot: STILL_FRAME_NUDGE,
|
|
46
|
+
render_object_view: STILL_FRAME_NUDGE,
|
|
47
|
+
render_gui: STILL_FRAME_NUDGE,
|
|
48
|
+
};
|
|
49
|
+
test.each(Object.entries(expected))('%s carries the expected nudge', (name, nudge) => {
|
|
50
|
+
expect(toolsByName[name].nudge).toBe(nudge);
|
|
51
|
+
});
|
|
52
|
+
test('non-instrumentation tools do not carry a nudge', () => {
|
|
53
|
+
// A nudge on every tool would defeat the point (and waste tokens). Spot-check
|
|
54
|
+
// that read/mutation tools stay quiet.
|
|
55
|
+
expect(toolsByName['get_instance_properties'].nudge).toBeUndefined();
|
|
56
|
+
expect(toolsByName['set_property'].nudge).toBeUndefined();
|
|
57
|
+
expect(toolsByName['edit_script'].nudge).toBeUndefined();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
//# sourceMappingURL=tool-nudges.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-nudges.test.js","sourceRoot":"","sources":["../../src/__tests__/tool-nudges.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-registry.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/tool-registry.test.ts"],"names":[],"mappings":""}
|