rbxstudio-mcp 2.3.1 → 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
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Roblox Studio MCP Server
|
|
2
2
|
|
|
3
|
-
MCP server for AI-powered Roblox Studio integration.
|
|
3
|
+
MCP server for AI-powered Roblox Studio integration. 43 specialized tools for exploring projects, analyzing scripts, and performing bulk operations. **Now with Claude Code-style script editing and visual feedback!**
|
|
4
4
|
|
|
5
5
|
https://devforum.roblox.com/t/v180-roblox-studio-mcp-speed-up-your-workflow-by-letting-ai-read-paths-and-properties/3707071
|
|
6
6
|
|
|
@@ -132,12 +132,12 @@ graph TB
|
|
|
132
132
|
```
|
|
133
133
|
|
|
134
134
|
### Key Components:
|
|
135
|
-
- MCP Server (Node.js/TypeScript) - Exposes
|
|
135
|
+
- MCP Server (Node.js/TypeScript) - Exposes 46 tools via stdio
|
|
136
136
|
- HTTP Bridge - Request/response queue on localhost:3002
|
|
137
137
|
- Studio Plugin (Luau) - Polls server and executes API calls
|
|
138
138
|
- Smart Caching - Efficient data transfer
|
|
139
139
|
|
|
140
|
-
##
|
|
140
|
+
## 43 AI Tools
|
|
141
141
|
|
|
142
142
|
### File System Tools
|
|
143
143
|
- `get_file_tree` - Complete project hierarchy with scripts, models, folders
|
|
@@ -179,17 +179,23 @@ graph TB
|
|
|
179
179
|
### Script Validation Tool (NEW in v1.9.0)
|
|
180
180
|
- `validate_script` - Validate Lua/Luau syntax without running, includes deprecation warnings
|
|
181
181
|
|
|
182
|
-
### Undo/Redo Tools (
|
|
183
|
-
- `undo` - Undo the last MCP operation in Studio's history
|
|
184
|
-
- `redo` - Redo
|
|
182
|
+
### Undo/Redo Tools (expanded in v2.5.0)
|
|
183
|
+
- `undo(count?)` - Undo the last MCP operation(s) in Studio's history. Pass `count` (default 1, max 100) to roll back several steps in one call. Returns an `entries` array describing each step undone; reports `stopped_early: true` if Studio's undo stack runs out.
|
|
184
|
+
- `redo(count?)` - Mirror of `undo`. Redo previously undone change(s).
|
|
185
|
+
- `get_history({ limit?, include_details? })` - **NEW.** Read-only peek at the MCP undo/redo stacks. Returns Studio's authoritative `can_undo`/`can_redo` flags plus the recent entries (default last 20, max 100) with action/target/summary/timestamp. Useful for "what am I about to undo?" before committing, or for situational awareness after a session resume.
|
|
185
186
|
|
|
186
|
-
|
|
187
|
+
**Coverage:** Every mutation tool — including `execute_lua`, `edit_script`, and `find_and_replace_in_scripts` — is wrapped in a single ChangeHistoryService recording per call (one tool call = one Ctrl+Z). If a handler errors, the recording is canceled and Studio rolls back any partial mutations. The plugin keeps an in-memory log of the last 100 MCP actions for human-readable summaries; Studio's own undo stack is authoritative.
|
|
188
|
+
|
|
189
|
+
**`execute_lua` caveat:** mutations performed inside `task.spawn`/`task.delay` callbacks that run AFTER the handler returns are not captured in the waypoint. Keep mutation work synchronous within the call for reliable undo.
|
|
190
|
+
|
|
191
|
+
### Claude Code-Style Script Editing ⭐
|
|
187
192
|
String-based script editing that works just like Claude Code's Edit tool - no more line number guessing!
|
|
188
193
|
|
|
189
|
-
- `edit_script` - **RECOMMENDED
|
|
194
|
+
- `edit_script` - **RECOMMENDED** for partial edits: find exact text and replace it, no line numbers needed. Auto-validates Luau syntax and rejects broken code.
|
|
190
195
|
- `search_script` - Search for patterns within scripts (like grep), with optional context lines
|
|
191
196
|
- `get_script_function` - Extract a specific function by name with line numbers
|
|
192
197
|
- `find_and_replace_in_scripts` - Batch find & replace across multiple scripts
|
|
198
|
+
- `validate_script` - Standalone Luau syntax check (also runs automatically after every `edit_script`)
|
|
193
199
|
|
|
194
200
|
```typescript
|
|
195
201
|
// Example: Safe string-based editing
|
|
@@ -203,14 +209,21 @@ edit_script({
|
|
|
203
209
|
// If it matches → clean replacement ✓
|
|
204
210
|
```
|
|
205
211
|
|
|
206
|
-
**Why
|
|
207
|
-
| Line-based (
|
|
212
|
+
**Why string-based editing replaced line-based editing:**
|
|
213
|
+
| Line-based (removed) | String-based (current) |
|
|
208
214
|
|---|---|
|
|
209
215
|
| "Edit lines 45-52" 🤞 | "Find this exact code" ✓ |
|
|
210
216
|
| Line numbers shift after edits | Matches the actual content |
|
|
211
217
|
| Can break `end` statements | Validates syntax before applying |
|
|
212
218
|
| Requires counting lines | Just copy the text to replace |
|
|
213
219
|
|
|
220
|
+
> **Note:** The legacy line-based partial editors (`edit_script_lines`,
|
|
221
|
+
> `insert_script_lines`, `delete_script_lines`) were removed. They've been
|
|
222
|
+
> fully superseded by `edit_script` — for example, "delete lines 10-15"
|
|
223
|
+
> becomes `edit_script(old_string=<those lines>, new_string="")`,
|
|
224
|
+
> and "insert after line 20" becomes
|
|
225
|
+
> `edit_script(old_string=<line 20>, new_string=<line 20 + new code>)`.
|
|
226
|
+
|
|
214
227
|
### Visual Feedback Tools (NEW in v2.2.0) 👁️
|
|
215
228
|
AI can now "see" what it creates! Camera control + screenshot = complete visual feedback loop.
|
|
216
229
|
|
|
@@ -231,10 +244,37 @@ capture_screenshot() // → AI sees what it created! 👀
|
|
|
231
244
|
|
|
232
245
|
**Supported angles:** `front`, `back`, `left`, `right`, `top`, `bottom`, `iso` (isometric), `iso_front`, `iso_back`, `low_angle`, `high_angle`, or custom `{pitch, yaw, roll}` angles.
|
|
233
246
|
|
|
234
|
-
> **Note:** All mutation tools
|
|
247
|
+
> **Note:** All mutation tools — including arbitrary `execute_lua` execution and the script editing tools — are automatically wrapped in ChangeHistoryService recordings, making every AI change undoable via Ctrl+Z in Studio or the `undo` tool. Errors auto-rollback partial mutations. See the **Undo/Redo Tools** section above for `count` and `get_history`.
|
|
235
248
|
|
|
236
249
|
> Note: Previous tools removed: `get_file_content`, `get_file_properties`, `get_selection`, `get_dependencies`, `validate_references`. Use Rojo/Argon workflows instead.
|
|
237
250
|
|
|
251
|
+
### Roblox Docs Tools (NEW in v2.4.0) 📚
|
|
252
|
+
|
|
253
|
+
AI models often have stale or imprecise training data on Roblox-specific topics — animation, `Motor6D` `C0`/`C1`, R6/R15 rig anatomy, `AlignOrientation`, and so on. These tools give the model first-class access to the canonical reference: a local mirror of [github.com/Roblox/creator-docs](https://github.com/Roblox/creator-docs).
|
|
254
|
+
|
|
255
|
+
- `search_roblox_docs` - Pure-JS regex search across the docs (like `grep`). Returns line-numbered hits with optional context.
|
|
256
|
+
- `get_roblox_doc` - Read a full doc file by relative path.
|
|
257
|
+
- `list_roblox_docs` - List a directory's contents (like `ls`).
|
|
258
|
+
- `get_roblox_api_reference` - Resolve a class/datatype/enum/global/library by name (e.g. `"Motor6D"`, `"CFrame"`, `"Material"`) and return parsed YAML.
|
|
259
|
+
|
|
260
|
+
**How the cache works:**
|
|
261
|
+
- First use lazily downloads ~30MB of docs (~2s) to a per-OS cache directory:
|
|
262
|
+
- Linux: `~/.cache/rbxstudio-mcp-nodejs/docs`
|
|
263
|
+
- macOS: `~/Library/Caches/rbxstudio-mcp-nodejs/docs`
|
|
264
|
+
- Windows: `%LOCALAPPDATA%\rbxstudio-mcp-nodejs\Cache\docs`
|
|
265
|
+
- Override with `RBXSTUDIO_DOCS_DIR=/some/path`.
|
|
266
|
+
- Subsequent calls hit the cache instantly.
|
|
267
|
+
- Every 24h, the next call hits GitHub's commits API for the latest SHA — if unchanged, no redownload (~20ms); if changed, the tarball is re-fetched.
|
|
268
|
+
|
|
269
|
+
**Filtering:** to keep the cache small the tarball is filtered during extract to the parts AI tends to need: `reference/engine/` (full API ref) plus `animation/`, `characters/`, `ui/`, `scripting/`, `physics/`, `workspace/`, `players/`, `input/`, `cloud-services/`, `sound/`. The full creator-docs repo is ~200MB; our cached subset is ~7MB.
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
// Typical workflow when stuck on Motor6D:
|
|
273
|
+
search_roblox_docs("Motor6D C0", { extensions: ["yaml"] })
|
|
274
|
+
get_roblox_api_reference("Motor6D") // structured YAML
|
|
275
|
+
get_roblox_doc("en-us/animation/using.md") // long-form guide
|
|
276
|
+
```
|
|
277
|
+
|
|
238
278
|
## AI-Optimized Features
|
|
239
279
|
|
|
240
280
|
### Mass Operations (v1.3.0)
|
|
@@ -356,13 +396,26 @@ validate_script("game.ServerScriptService.MainScript")
|
|
|
356
396
|
// Monitor game output after testing
|
|
357
397
|
get_output({ limit: 50, messageTypes: ["MessageError", "MessageWarning"] })
|
|
358
398
|
|
|
359
|
-
// ===
|
|
399
|
+
// === Undo / Redo / History (expanded in v2.5.0) ===
|
|
400
|
+
|
|
401
|
+
// Peek at recent changes before undoing — non-destructive
|
|
402
|
+
get_history({ limit: 5 })
|
|
403
|
+
// → { can_undo: true, tracked_undo_count: 12,
|
|
404
|
+
// undo_stack: [{ index: 0, action: "set_property",
|
|
405
|
+
// target: "game.Workspace.Part", summary: "Position = ...",
|
|
406
|
+
// age_seconds: 3 }, ...] }
|
|
360
407
|
|
|
361
|
-
// Undo the last MCP operation
|
|
408
|
+
// Undo the last MCP operation (one Ctrl+Z worth)
|
|
362
409
|
undo()
|
|
363
410
|
|
|
364
|
-
//
|
|
411
|
+
// Roll back several steps in one round-trip
|
|
412
|
+
undo({ count: 5 })
|
|
413
|
+
// → { undone_count: 5, entries: [...], remaining_undos: 7,
|
|
414
|
+
// stopped_early: false }
|
|
415
|
+
|
|
416
|
+
// Redo (mirror of undo)
|
|
365
417
|
redo()
|
|
418
|
+
redo({ count: 3 })
|
|
366
419
|
```
|
|
367
420
|
|
|
368
421
|
## Configuration
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BridgeService } from '../bridge-service';
|
|
2
|
+
import { observeRejection } from './helpers';
|
|
2
3
|
describe('BridgeService', () => {
|
|
3
4
|
let bridgeService;
|
|
4
5
|
beforeEach(() => {
|
|
@@ -12,7 +13,7 @@ describe('BridgeService', () => {
|
|
|
12
13
|
test('should create and store a pending request', async () => {
|
|
13
14
|
const endpoint = '/api/test';
|
|
14
15
|
const data = { test: 'data' };
|
|
15
|
-
|
|
16
|
+
bridgeService.sendRequest(endpoint, data);
|
|
16
17
|
// Check that request is pending
|
|
17
18
|
const pendingRequest = bridgeService.getPendingRequest();
|
|
18
19
|
expect(pendingRequest).toBeTruthy();
|
|
@@ -44,9 +45,10 @@ describe('BridgeService', () => {
|
|
|
44
45
|
const endpoint = '/api/test';
|
|
45
46
|
const data = { test: 'data' };
|
|
46
47
|
const requestPromise = bridgeService.sendRequest(endpoint, data);
|
|
48
|
+
const rejection = observeRejection(requestPromise);
|
|
47
49
|
// Fast-forward time by 31 seconds
|
|
48
50
|
jest.advanceTimersByTime(31000);
|
|
49
|
-
await expect(
|
|
51
|
+
await expect(rejection).resolves.toThrow('Request timeout');
|
|
50
52
|
});
|
|
51
53
|
});
|
|
52
54
|
describe('Cleanup Operations', () => {
|
|
@@ -57,13 +59,14 @@ describe('BridgeService', () => {
|
|
|
57
59
|
bridgeService.sendRequest('/api/test2', {}),
|
|
58
60
|
bridgeService.sendRequest('/api/test3', {})
|
|
59
61
|
];
|
|
62
|
+
const rejections = promises.map(observeRejection);
|
|
60
63
|
// Fast-forward time by 31 seconds
|
|
61
64
|
jest.advanceTimersByTime(31000);
|
|
62
65
|
// Clean up old requests
|
|
63
66
|
bridgeService.cleanupOldRequests();
|
|
64
67
|
// All requests should be rejected
|
|
65
|
-
for (const
|
|
66
|
-
await expect(
|
|
68
|
+
for (const rejection of rejections) {
|
|
69
|
+
await expect(rejection).resolves.toThrow('Request timeout');
|
|
67
70
|
}
|
|
68
71
|
// No pending requests should remain
|
|
69
72
|
expect(bridgeService.getPendingRequest()).toBeNull();
|
|
@@ -75,34 +78,43 @@ describe('BridgeService', () => {
|
|
|
75
78
|
bridgeService.sendRequest('/api/test2', {}),
|
|
76
79
|
bridgeService.sendRequest('/api/test3', {})
|
|
77
80
|
];
|
|
81
|
+
const rejections = promises.map(observeRejection);
|
|
78
82
|
// Clear all requests
|
|
79
83
|
bridgeService.clearAllPendingRequests();
|
|
80
84
|
// All requests should be rejected with connection closed error
|
|
81
|
-
for (const
|
|
82
|
-
await expect(
|
|
85
|
+
for (const rejection of rejections) {
|
|
86
|
+
await expect(rejection).resolves.toThrow('Connection closed');
|
|
83
87
|
}
|
|
84
88
|
// No pending requests should remain
|
|
85
89
|
expect(bridgeService.getPendingRequest()).toBeNull();
|
|
86
90
|
});
|
|
87
91
|
});
|
|
88
92
|
describe('Request Priority', () => {
|
|
89
|
-
test('should return oldest request
|
|
93
|
+
test('should return the oldest pending request without consuming it', async () => {
|
|
90
94
|
// Create requests with small delays
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
bridgeService.sendRequest('/api/test2', { order: 2 });
|
|
95
|
-
|
|
96
|
-
bridgeService.sendRequest('/api/test3', { order: 3 });
|
|
95
|
+
jest.setSystemTime(1000);
|
|
96
|
+
const first = bridgeService.sendRequest('/api/test1', { order: 1 });
|
|
97
|
+
jest.setSystemTime(1010);
|
|
98
|
+
const second = bridgeService.sendRequest('/api/test2', { order: 2 });
|
|
99
|
+
jest.setSystemTime(1020);
|
|
100
|
+
const third = bridgeService.sendRequest('/api/test3', { order: 3 });
|
|
97
101
|
// Should get the first (oldest) request
|
|
98
102
|
const firstRequest = bridgeService.getPendingRequest();
|
|
99
103
|
expect(firstRequest?.request.data.order).toBe(1);
|
|
104
|
+
// Polling peeks; the same request remains pending until resolved.
|
|
105
|
+
expect(bridgeService.getPendingRequest()?.request.data.order).toBe(1);
|
|
106
|
+
bridgeService.resolveRequest(firstRequest.requestId, { ok: 1 });
|
|
100
107
|
// Should get the second request next
|
|
101
108
|
const secondRequest = bridgeService.getPendingRequest();
|
|
102
109
|
expect(secondRequest?.request.data.order).toBe(2);
|
|
110
|
+
bridgeService.resolveRequest(secondRequest.requestId, { ok: 2 });
|
|
103
111
|
// Should get the third request last
|
|
104
112
|
const thirdRequest = bridgeService.getPendingRequest();
|
|
105
113
|
expect(thirdRequest?.request.data.order).toBe(3);
|
|
114
|
+
bridgeService.resolveRequest(thirdRequest.requestId, { ok: 3 });
|
|
115
|
+
await expect(first).resolves.toEqual({ ok: 1 });
|
|
116
|
+
await expect(second).resolves.toEqual({ ok: 2 });
|
|
117
|
+
await expect(third).resolves.toEqual({ ok: 3 });
|
|
106
118
|
});
|
|
107
119
|
});
|
|
108
120
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge-service.test.js","sourceRoot":"","sources":["../../src/__tests__/bridge-service.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"bridge-service.test.js","sourceRoot":"","sources":["../../src/__tests__/bridge-service.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge-session.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/bridge-session.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { BridgeService } from '../bridge-service';
|
|
2
|
+
describe('BridgeService test sessions', () => {
|
|
3
|
+
afterEach(() => {
|
|
4
|
+
jest.useRealTimers();
|
|
5
|
+
});
|
|
6
|
+
test('startTestSession initializes state and supersedes an active session', async () => {
|
|
7
|
+
jest.useFakeTimers();
|
|
8
|
+
const bridge = new BridgeService();
|
|
9
|
+
const first = bridge.startTestSession();
|
|
10
|
+
const waiter = bridge.waitForTestEnd(1000);
|
|
11
|
+
const second = bridge.startTestSession();
|
|
12
|
+
await expect(waiter).resolves.toBe(true);
|
|
13
|
+
expect(second).not.toBe(first);
|
|
14
|
+
expect(bridge.testSession).toMatchObject({
|
|
15
|
+
sessionId: second,
|
|
16
|
+
status: 'active',
|
|
17
|
+
serverReady: false,
|
|
18
|
+
serverLoadstringReady: false,
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
test('server companion polling records readiness and pops queued commands', () => {
|
|
22
|
+
const bridge = new BridgeService();
|
|
23
|
+
const sessionId = bridge.startTestSession();
|
|
24
|
+
expect(bridge.enqueueTestCommand({ cmd: 'end', args: 'stop' })).toBe(true);
|
|
25
|
+
const firstPoll = bridge.popTestCommand(sessionId, 'server', {
|
|
26
|
+
hello: { loadstringReady: true },
|
|
27
|
+
});
|
|
28
|
+
expect(firstPoll).toEqual({
|
|
29
|
+
command: { cmd: 'end', args: 'stop' },
|
|
30
|
+
ended: false,
|
|
31
|
+
sessionMatch: true,
|
|
32
|
+
});
|
|
33
|
+
expect(bridge.getTestSessionStatus()).toMatchObject({
|
|
34
|
+
sessionId,
|
|
35
|
+
serverReady: true,
|
|
36
|
+
serverLoadstringReady: true,
|
|
37
|
+
});
|
|
38
|
+
expect(bridge.popTestCommand(sessionId, 'server').command).toBeNull();
|
|
39
|
+
});
|
|
40
|
+
test('client companion polling registers, updates, and receives per-client commands', () => {
|
|
41
|
+
const bridge = new BridgeService();
|
|
42
|
+
const sessionId = bridge.startTestSession();
|
|
43
|
+
const firstPoll = bridge.popTestCommand(sessionId, 'client', {
|
|
44
|
+
userId: 42,
|
|
45
|
+
playerName: 'Alice',
|
|
46
|
+
hello: { loadstringReady: true },
|
|
47
|
+
});
|
|
48
|
+
expect(firstPoll).toMatchObject({ command: null, ended: false, sessionMatch: true });
|
|
49
|
+
expect(bridge.getTestSessionStatus()?.clients).toEqual([
|
|
50
|
+
{ userId: 42, name: 'Alice', ready: true, loadstringReady: true },
|
|
51
|
+
]);
|
|
52
|
+
expect(bridge.enqueueTestCommand({ cmd: 'eval', args: { replyId: 'client-reply' } }, { client: 42 })).toBe(true);
|
|
53
|
+
const secondPoll = bridge.popTestCommand(sessionId, 'client', {
|
|
54
|
+
userId: 42,
|
|
55
|
+
playerName: 'AliceRenamed',
|
|
56
|
+
hello: { loadstringReady: false },
|
|
57
|
+
});
|
|
58
|
+
expect(secondPoll.command).toEqual({ cmd: 'eval', args: { replyId: 'client-reply' } });
|
|
59
|
+
expect(bridge.getTestSessionStatus()?.clients).toEqual([
|
|
60
|
+
{ userId: 42, name: 'AliceRenamed', ready: true, loadstringReady: false },
|
|
61
|
+
]);
|
|
62
|
+
expect(bridge.enqueueTestCommand({ cmd: 'eval' }, { client: 404 })).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
test('polling with the wrong or missing session returns non-matching ended state', () => {
|
|
65
|
+
const bridge = new BridgeService();
|
|
66
|
+
expect(bridge.popTestCommand('missing')).toEqual({
|
|
67
|
+
command: null,
|
|
68
|
+
ended: true,
|
|
69
|
+
sessionMatch: false,
|
|
70
|
+
});
|
|
71
|
+
const sessionId = bridge.startTestSession();
|
|
72
|
+
expect(bridge.popTestCommand(sessionId, 'client')).toEqual({
|
|
73
|
+
command: null,
|
|
74
|
+
ended: false,
|
|
75
|
+
sessionMatch: true,
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
test('appendTestOutput filters, limits, cursors, and trims the ring buffer', () => {
|
|
79
|
+
const bridge = new BridgeService();
|
|
80
|
+
const sessionId = bridge.startTestSession();
|
|
81
|
+
expect(bridge.appendTestOutput('wrong', [{ message: 'ignored', messageType: 'MessageOutput', timestamp: 1 }])).toEqual({ accepted: 0, sessionMatch: false });
|
|
82
|
+
bridge.appendTestOutput(sessionId, [
|
|
83
|
+
{ message: 'one', messageType: 'MessageOutput', timestamp: 1 },
|
|
84
|
+
{ message: 'warn', messageType: 'MessageWarning', timestamp: 2 },
|
|
85
|
+
{ message: 'two', messageType: 'MessageOutput', timestamp: 3 },
|
|
86
|
+
]);
|
|
87
|
+
expect(bridge.getTestOutput({ sinceSeq: 1, messageTypes: ['MessageOutput'], limit: 1 })).toMatchObject({
|
|
88
|
+
sessionId,
|
|
89
|
+
status: 'active',
|
|
90
|
+
nextSinceSeq: 3,
|
|
91
|
+
entries: [{ seq: 3, message: 'two' }],
|
|
92
|
+
});
|
|
93
|
+
const many = Array.from({ length: 5005 }, (_, index) => ({
|
|
94
|
+
message: `m${index}`,
|
|
95
|
+
messageType: 'MessageOutput',
|
|
96
|
+
timestamp: index,
|
|
97
|
+
}));
|
|
98
|
+
bridge.appendTestOutput(sessionId, many);
|
|
99
|
+
const capped = bridge.getTestOutput({ limit: 5000 });
|
|
100
|
+
expect(capped.entries).toHaveLength(5000);
|
|
101
|
+
expect(capped.entries[0].seq).toBeGreaterThan(1);
|
|
102
|
+
});
|
|
103
|
+
test('endTestSession is idempotent and wakes waiters', async () => {
|
|
104
|
+
jest.useFakeTimers();
|
|
105
|
+
const bridge = new BridgeService();
|
|
106
|
+
const sessionId = bridge.startTestSession();
|
|
107
|
+
const waiter = bridge.waitForTestEnd(1000);
|
|
108
|
+
expect(bridge.endTestSession('wrong', 'done')).toBe(false);
|
|
109
|
+
expect(bridge.endTestSession(sessionId, 'done', { ok: true })).toBe(true);
|
|
110
|
+
expect(bridge.endTestSession(sessionId, 'late')).toBe(true);
|
|
111
|
+
await expect(waiter).resolves.toBe(true);
|
|
112
|
+
expect(bridge.testSession).toMatchObject({
|
|
113
|
+
status: 'ended',
|
|
114
|
+
endReason: 'done',
|
|
115
|
+
endValue: { ok: true },
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
test('waitForTestEnd resolves false on timeout and true without an active session', async () => {
|
|
119
|
+
jest.useFakeTimers();
|
|
120
|
+
const bridge = new BridgeService();
|
|
121
|
+
await expect(bridge.waitForTestEnd(100)).resolves.toBe(true);
|
|
122
|
+
bridge.startTestSession();
|
|
123
|
+
const waiter = bridge.waitForTestEnd(100);
|
|
124
|
+
jest.advanceTimersByTime(100);
|
|
125
|
+
await expect(waiter).resolves.toBe(false);
|
|
126
|
+
});
|
|
127
|
+
test('registerEvalReply resolves delivered replies and rejects stale sessions', async () => {
|
|
128
|
+
const bridge = new BridgeService();
|
|
129
|
+
await expect(bridge.registerEvalReply('none', 'server', 1000)).resolves.toMatchObject({
|
|
130
|
+
ok: false,
|
|
131
|
+
errorType: 'companion_error',
|
|
132
|
+
});
|
|
133
|
+
const sessionId = bridge.startTestSession();
|
|
134
|
+
const reply = bridge.registerEvalReply('reply-1', 'server', 1000);
|
|
135
|
+
expect(bridge.resolveEvalReply('wrong', 'reply-1', { ok: true })).toBe(false);
|
|
136
|
+
expect(bridge.resolveEvalReply(sessionId, 'reply-1', { ok: true, values: ['ok'] })).toBe(true);
|
|
137
|
+
expect(bridge.resolveEvalReply(sessionId, 'reply-1', { ok: true })).toBe(false);
|
|
138
|
+
await expect(reply).resolves.toEqual({ ok: true, values: ['ok'] });
|
|
139
|
+
});
|
|
140
|
+
test('registerEvalReply watchdog distinguishes unclaimed commands from claimed timeouts', async () => {
|
|
141
|
+
jest.useFakeTimers();
|
|
142
|
+
const bridge = new BridgeService();
|
|
143
|
+
const sessionId = bridge.startTestSession();
|
|
144
|
+
const unclaimed = bridge.registerEvalReply('unclaimed', 'server', 1000);
|
|
145
|
+
jest.advanceTimersByTime(2500);
|
|
146
|
+
await expect(unclaimed).resolves.toMatchObject({
|
|
147
|
+
ok: false,
|
|
148
|
+
errorType: 'companion_error',
|
|
149
|
+
});
|
|
150
|
+
const claimed = bridge.registerEvalReply('claimed', 'server', 1000);
|
|
151
|
+
bridge.enqueueTestCommand({ cmd: 'eval', args: { replyId: 'claimed' } });
|
|
152
|
+
bridge.popTestCommand(sessionId, 'server');
|
|
153
|
+
jest.advanceTimersByTime(2500);
|
|
154
|
+
await expect(claimed).resolves.toMatchObject({
|
|
155
|
+
ok: false,
|
|
156
|
+
errorType: 'timeout',
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
test('ending a session resolves pending eval replies with companion_error', async () => {
|
|
160
|
+
const bridge = new BridgeService();
|
|
161
|
+
const sessionId = bridge.startTestSession();
|
|
162
|
+
const reply = bridge.registerEvalReply('reply', 'server', 5000);
|
|
163
|
+
bridge.endTestSession(sessionId, 'manual_stop');
|
|
164
|
+
await expect(reply).resolves.toMatchObject({
|
|
165
|
+
ok: false,
|
|
166
|
+
errorType: 'companion_error',
|
|
167
|
+
error: expect.stringContaining('manual_stop'),
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
//# sourceMappingURL=bridge-session.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bridge-session.test.js","sourceRoot":"","sources":["../../src/__tests__/bridge-session.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chunker.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/chunker.test.ts"],"names":[],"mappings":""}
|