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.
Files changed (175) hide show
  1. package/README.md +67 -14
  2. package/dist/__tests__/bridge-service.test.js +25 -13
  3. package/dist/__tests__/bridge-service.test.js.map +1 -1
  4. package/dist/__tests__/bridge-session.test.d.ts +2 -0
  5. package/dist/__tests__/bridge-session.test.d.ts.map +1 -0
  6. package/dist/__tests__/bridge-session.test.js +171 -0
  7. package/dist/__tests__/bridge-session.test.js.map +1 -0
  8. package/dist/__tests__/chunker.test.d.ts +2 -0
  9. package/dist/__tests__/chunker.test.d.ts.map +1 -0
  10. package/dist/__tests__/chunker.test.js +201 -0
  11. package/dist/__tests__/chunker.test.js.map +1 -0
  12. package/dist/__tests__/docs-core.test.d.ts +2 -0
  13. package/dist/__tests__/docs-core.test.d.ts.map +1 -0
  14. package/dist/__tests__/docs-core.test.js +137 -0
  15. package/dist/__tests__/docs-core.test.js.map +1 -0
  16. package/dist/__tests__/docs-fetcher.test.d.ts +2 -0
  17. package/dist/__tests__/docs-fetcher.test.d.ts.map +1 -0
  18. package/dist/__tests__/docs-fetcher.test.js +173 -0
  19. package/dist/__tests__/docs-fetcher.test.js.map +1 -0
  20. package/dist/__tests__/helpers.d.ts +8 -0
  21. package/dist/__tests__/helpers.d.ts.map +1 -0
  22. package/dist/__tests__/helpers.js +23 -0
  23. package/dist/__tests__/helpers.js.map +1 -0
  24. package/dist/__tests__/http-routes.test.d.ts +2 -0
  25. package/dist/__tests__/http-routes.test.d.ts.map +1 -0
  26. package/dist/__tests__/http-routes.test.js +233 -0
  27. package/dist/__tests__/http-routes.test.js.map +1 -0
  28. package/dist/__tests__/http-server.test.js +13 -6
  29. package/dist/__tests__/http-server.test.js.map +1 -1
  30. package/dist/__tests__/integration.test.js +9 -4
  31. package/dist/__tests__/integration.test.js.map +1 -1
  32. package/dist/__tests__/semantic-search.test.d.ts +2 -0
  33. package/dist/__tests__/semantic-search.test.d.ts.map +1 -0
  34. package/dist/__tests__/semantic-search.test.js +202 -0
  35. package/dist/__tests__/semantic-search.test.js.map +1 -0
  36. package/dist/__tests__/smoke.test.js +7 -3
  37. package/dist/__tests__/smoke.test.js.map +1 -1
  38. package/dist/__tests__/studio-client.test.d.ts +2 -0
  39. package/dist/__tests__/studio-client.test.d.ts.map +1 -0
  40. package/dist/__tests__/studio-client.test.js +25 -0
  41. package/dist/__tests__/studio-client.test.js.map +1 -0
  42. package/dist/__tests__/tool-nudges.test.d.ts +2 -0
  43. package/dist/__tests__/tool-nudges.test.d.ts.map +1 -0
  44. package/dist/__tests__/tool-nudges.test.js +60 -0
  45. package/dist/__tests__/tool-nudges.test.js.map +1 -0
  46. package/dist/__tests__/tool-registry.test.d.ts +2 -0
  47. package/dist/__tests__/tool-registry.test.d.ts.map +1 -0
  48. package/dist/__tests__/tool-registry.test.js +365 -0
  49. package/dist/__tests__/tool-registry.test.js.map +1 -0
  50. package/dist/__tests__/tools-bridge.test.d.ts +2 -0
  51. package/dist/__tests__/tools-bridge.test.d.ts.map +1 -0
  52. package/dist/__tests__/tools-bridge.test.js +396 -0
  53. package/dist/__tests__/tools-bridge.test.js.map +1 -0
  54. package/dist/__tests__/tools-docs.test.d.ts +2 -0
  55. package/dist/__tests__/tools-docs.test.d.ts.map +1 -0
  56. package/dist/__tests__/tools-docs.test.js +112 -0
  57. package/dist/__tests__/tools-docs.test.js.map +1 -0
  58. package/dist/__tests__/tools-guards.test.d.ts +2 -0
  59. package/dist/__tests__/tools-guards.test.d.ts.map +1 -0
  60. package/dist/__tests__/tools-guards.test.js +131 -0
  61. package/dist/__tests__/tools-guards.test.js.map +1 -0
  62. package/dist/__tests__/tools-runtime.test.d.ts +2 -0
  63. package/dist/__tests__/tools-runtime.test.d.ts.map +1 -0
  64. package/dist/__tests__/tools-runtime.test.js +214 -0
  65. package/dist/__tests__/tools-runtime.test.js.map +1 -0
  66. package/dist/__tests__/tools-visual.test.d.ts +2 -0
  67. package/dist/__tests__/tools-visual.test.d.ts.map +1 -0
  68. package/dist/__tests__/tools-visual.test.js +149 -0
  69. package/dist/__tests__/tools-visual.test.js.map +1 -0
  70. package/dist/bridge-service.d.ts +99 -12
  71. package/dist/bridge-service.d.ts.map +1 -1
  72. package/dist/bridge-service.js +238 -21
  73. package/dist/bridge-service.js.map +1 -1
  74. package/dist/docs/cache.d.ts +50 -0
  75. package/dist/docs/cache.d.ts.map +1 -0
  76. package/dist/docs/cache.js +123 -0
  77. package/dist/docs/cache.js.map +1 -0
  78. package/dist/docs/embeddings/chunker.d.ts +120 -0
  79. package/dist/docs/embeddings/chunker.d.ts.map +1 -0
  80. package/dist/docs/embeddings/chunker.js +395 -0
  81. package/dist/docs/embeddings/chunker.js.map +1 -0
  82. package/dist/docs/embeddings/embedder.d.ts +41 -0
  83. package/dist/docs/embeddings/embedder.d.ts.map +1 -0
  84. package/dist/docs/embeddings/embedder.js +113 -0
  85. package/dist/docs/embeddings/embedder.js.map +1 -0
  86. package/dist/docs/embeddings/index.d.ts +102 -0
  87. package/dist/docs/embeddings/index.d.ts.map +1 -0
  88. package/dist/docs/embeddings/index.js +250 -0
  89. package/dist/docs/embeddings/index.js.map +1 -0
  90. package/dist/docs/embeddings/manager.d.ts +68 -0
  91. package/dist/docs/embeddings/manager.d.ts.map +1 -0
  92. package/dist/docs/embeddings/manager.js +97 -0
  93. package/dist/docs/embeddings/manager.js.map +1 -0
  94. package/dist/docs/fetcher.d.ts +29 -0
  95. package/dist/docs/fetcher.d.ts.map +1 -0
  96. package/dist/docs/fetcher.js +244 -0
  97. package/dist/docs/fetcher.js.map +1 -0
  98. package/dist/docs/reference.d.ts +37 -0
  99. package/dist/docs/reference.d.ts.map +1 -0
  100. package/dist/docs/reference.js +108 -0
  101. package/dist/docs/reference.js.map +1 -0
  102. package/dist/docs/search.d.ts +194 -0
  103. package/dist/docs/search.d.ts.map +1 -0
  104. package/dist/docs/search.js +733 -0
  105. package/dist/docs/search.js.map +1 -0
  106. package/dist/http-server.d.ts.map +1 -1
  107. package/dist/http-server.js +52 -5
  108. package/dist/http-server.js.map +1 -1
  109. package/dist/index.d.ts +8 -9
  110. package/dist/index.d.ts.map +1 -1
  111. package/dist/index.js +35 -1035
  112. package/dist/index.js.map +1 -1
  113. package/dist/instructions.d.ts +15 -0
  114. package/dist/instructions.d.ts.map +1 -0
  115. package/dist/instructions.js +26 -0
  116. package/dist/instructions.js.map +1 -0
  117. package/dist/tools/defs/attributes.d.ts +6 -0
  118. package/dist/tools/defs/attributes.d.ts.map +1 -0
  119. package/dist/tools/defs/attributes.js +85 -0
  120. package/dist/tools/defs/attributes.js.map +1 -0
  121. package/dist/tools/defs/docs.d.ts +17 -0
  122. package/dist/tools/defs/docs.d.ts.map +1 -0
  123. package/dist/tools/defs/docs.js +151 -0
  124. package/dist/tools/defs/docs.js.map +1 -0
  125. package/dist/tools/defs/execute.d.ts +6 -0
  126. package/dist/tools/defs/execute.d.ts.map +1 -0
  127. package/dist/tools/defs/execute.js +21 -0
  128. package/dist/tools/defs/execute.js.map +1 -0
  129. package/dist/tools/defs/inspection.d.ts +7 -0
  130. package/dist/tools/defs/inspection.d.ts.map +1 -0
  131. package/dist/tools/defs/inspection.js +202 -0
  132. package/dist/tools/defs/inspection.js.map +1 -0
  133. package/dist/tools/defs/objects.d.ts +6 -0
  134. package/dist/tools/defs/objects.d.ts.map +1 -0
  135. package/dist/tools/defs/objects.js +111 -0
  136. package/dist/tools/defs/objects.js.map +1 -0
  137. package/dist/tools/defs/properties.d.ts +6 -0
  138. package/dist/tools/defs/properties.d.ts.map +1 -0
  139. package/dist/tools/defs/properties.js +71 -0
  140. package/dist/tools/defs/properties.js.map +1 -0
  141. package/dist/tools/defs/runtime.d.ts +6 -0
  142. package/dist/tools/defs/runtime.d.ts.map +1 -0
  143. package/dist/tools/defs/runtime.js +145 -0
  144. package/dist/tools/defs/runtime.js.map +1 -0
  145. package/dist/tools/defs/scripts.d.ts +18 -0
  146. package/dist/tools/defs/scripts.d.ts.map +1 -0
  147. package/dist/tools/defs/scripts.js +163 -0
  148. package/dist/tools/defs/scripts.js.map +1 -0
  149. package/dist/tools/defs/tags.d.ts +6 -0
  150. package/dist/tools/defs/tags.d.ts.map +1 -0
  151. package/dist/tools/defs/tags.js +74 -0
  152. package/dist/tools/defs/tags.js.map +1 -0
  153. package/dist/tools/defs/visual.d.ts +7 -0
  154. package/dist/tools/defs/visual.d.ts.map +1 -0
  155. package/dist/tools/defs/visual.js +208 -0
  156. package/dist/tools/defs/visual.js.map +1 -0
  157. package/dist/tools/index.d.ts +101 -25
  158. package/dist/tools/index.d.ts.map +1 -1
  159. package/dist/tools/index.js +580 -63
  160. package/dist/tools/index.js.map +1 -1
  161. package/dist/tools/nudges.d.ts +25 -0
  162. package/dist/tools/nudges.d.ts.map +1 -0
  163. package/dist/tools/nudges.js +34 -0
  164. package/dist/tools/nudges.js.map +1 -0
  165. package/dist/tools/registry.d.ts +20 -0
  166. package/dist/tools/registry.d.ts.map +1 -0
  167. package/dist/tools/registry.js +65 -0
  168. package/dist/tools/registry.js.map +1 -0
  169. package/dist/tools/types.d.ts +24 -0
  170. package/dist/tools/types.d.ts.map +1 -0
  171. package/dist/tools/types.js +2 -0
  172. package/dist/tools/types.js.map +1 -0
  173. package/package.json +7 -6
  174. package/studio-plugin/MCPPlugin.rbxmx +3 -238
  175. 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(promise1).rejects.toThrow('Connection closed');
34
- await expect(promise2).rejects.toThrow('Connection closed');
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(pendingPromise).rejects.toThrow('Connection closed');
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;AAEhC,QAAQ,CAAC,gCAAgC,EAAE,GAAG,EAAE;IAC9C,IAAI,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAChD,MAAM,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;QACnC,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;QAC7B,MAAM,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;IAChD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,sDAAsD,EAAE,KAAK,IAAI,EAAE;QACtE,MAAM,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,IAAI,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAC5C,MAAM,GAAG,GAAG,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAE5C,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;aAChC,GAAG,CAAC,SAAS,CAAC;aACd,MAAM,CAAC,GAAG,CAAC,CAAC;QAEf,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACxC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;IACzD,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QACnE,MAAM,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;QAEnC,sDAAsD;QACtD,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAClD,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAElD,+BAA+B;QAC/B,MAAM,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC;QAEhD,qBAAqB;QACrB,MAAM,CAAC,uBAAuB,EAAE,CAAC;QAEjC,kCAAkC;QAClC,MAAM,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;QAE9C,yBAAyB;QACzB,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;QAC5D,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,mDAAmD,EAAE,KAAK,IAAI,EAAE;QACnE,MAAM,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,IAAI,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAC5C,MAAM,GAAG,GAAG,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;QAE5C,yCAAyC;QACzC,MAAM,cAAc,GAAG,MAAM,CAAC,WAAW,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QAErE,6BAA6B;QAC7B,MAAM,OAAO,CAAC,GAAG,CAAC;aACf,IAAI,CAAC,aAAa,CAAC;aACnB,MAAM,CAAC,GAAG,CAAC,CAAC;QAEf,6BAA6B;QAC7B,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACpE,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;QAC3D,MAAM,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,IAAI,iBAAiB,CAAC,MAAM,CAAC,CAAC;QAC5C,MAAM,GAAG,GAAG,gBAAgB,CAAC,KAAK,EAAE,MAAM,CAAQ,CAAC;QAEnD,0BAA0B;QAC1B,MAAM,CAAC,GAAG,CAAC,iBAAiB,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAE5C,cAAc;QACd,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC9C,MAAM,CAAC,GAAG,CAAC,iBAAiB,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAE3C,mBAAmB;QACnB,MAAM,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACnD,MAAM,CAAC,GAAG,CAAC,iBAAiB,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC9C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"smoke.test.js","sourceRoot":"","sources":["../../src/__tests__/smoke.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=studio-client.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tool-nudges.test.d.ts.map
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tool-registry.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-registry.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/tool-registry.test.ts"],"names":[],"mappings":""}