unreal-engine-mcp-server 0.3.0 → 0.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 (43) hide show
  1. package/.env.production +6 -1
  2. package/Dockerfile +11 -28
  3. package/README.md +1 -2
  4. package/dist/index.js +120 -54
  5. package/dist/resources/actors.js +71 -13
  6. package/dist/resources/assets.d.ts +3 -2
  7. package/dist/resources/assets.js +96 -72
  8. package/dist/resources/levels.js +2 -2
  9. package/dist/tools/assets.js +6 -2
  10. package/dist/tools/build_environment_advanced.js +46 -42
  11. package/dist/tools/consolidated-tool-definitions.d.ts +232 -15
  12. package/dist/tools/consolidated-tool-definitions.js +173 -8
  13. package/dist/tools/consolidated-tool-handlers.js +331 -718
  14. package/dist/tools/debug.js +4 -6
  15. package/dist/tools/rc.js +2 -2
  16. package/dist/tools/sequence.js +21 -2
  17. package/dist/unreal-bridge.d.ts +4 -1
  18. package/dist/unreal-bridge.js +211 -53
  19. package/dist/utils/http.js +4 -2
  20. package/dist/utils/response-validator.d.ts +6 -1
  21. package/dist/utils/response-validator.js +43 -15
  22. package/package.json +5 -5
  23. package/server.json +2 -2
  24. package/src/index.ts +120 -56
  25. package/src/resources/actors.ts +51 -13
  26. package/src/resources/assets.ts +97 -73
  27. package/src/resources/levels.ts +2 -2
  28. package/src/tools/assets.ts +6 -2
  29. package/src/tools/build_environment_advanced.ts +46 -42
  30. package/src/tools/consolidated-tool-definitions.ts +173 -8
  31. package/src/tools/consolidated-tool-handlers.ts +318 -747
  32. package/src/tools/debug.ts +4 -6
  33. package/src/tools/rc.ts +2 -2
  34. package/src/tools/sequence.ts +21 -2
  35. package/src/unreal-bridge.ts +163 -60
  36. package/src/utils/http.ts +7 -4
  37. package/src/utils/response-validator.ts +48 -19
  38. package/dist/tools/tool-definitions.d.ts +0 -4919
  39. package/dist/tools/tool-definitions.js +0 -1007
  40. package/dist/tools/tool-handlers.d.ts +0 -47
  41. package/dist/tools/tool-handlers.js +0 -863
  42. package/src/tools/tool-definitions.ts +0 -1023
  43. package/src/tools/tool-handlers.ts +0 -973
@@ -1,16 +1,16 @@
1
1
  // Consolidated tool handlers - maps 13 tools to all 36 operations
2
- import { handleToolCall } from './tool-handlers.js';
3
2
  import { cleanObject } from '../utils/safe-json.js';
3
+ import { Logger } from '../utils/logger.js';
4
+ const log = new Logger('ConsolidatedToolHandler');
4
5
  export async function handleConsolidatedToolCall(name, args, tools) {
5
6
  const startTime = Date.now();
6
- console.log(`[ConsolidatedToolHandler] Starting execution of ${name} at ${new Date().toISOString()}`);
7
+ // Use scoped logger (stderr) to avoid polluting stdout JSON
8
+ log.debug(`Starting execution of ${name} at ${new Date().toISOString()}`);
7
9
  try {
8
10
  // Validate args is not null/undefined
9
11
  if (args === null || args === undefined) {
10
12
  throw new Error('Invalid arguments: null or undefined');
11
13
  }
12
- let mappedName;
13
- let mappedArgs = { ...args };
14
14
  switch (name) {
15
15
  // 1. ASSET MANAGER
16
16
  case 'manage_asset':
@@ -23,72 +23,33 @@ export async function handleConsolidatedToolCall(name, args, tools) {
23
23
  throw new Error('Missing required parameter: action');
24
24
  }
25
25
  switch (args.action) {
26
- case 'list':
27
- // Directory is optional
28
- if (args.directory !== undefined && args.directory !== null) {
29
- if (typeof args.directory !== 'string') {
30
- throw new Error('Invalid directory: must be a string');
31
- }
32
- }
33
- mappedName = 'list_assets';
34
- mappedArgs = {
35
- directory: args.directory
36
- // recursive removed - always false internally
37
- };
38
- break;
39
- case 'import':
40
- // Validate required parameters
41
- if (args.sourcePath === undefined || args.sourcePath === null) {
42
- throw new Error('Missing required parameter: sourcePath');
43
- }
44
- if (typeof args.sourcePath !== 'string') {
45
- throw new Error('Invalid sourcePath: must be a string');
46
- }
47
- if (args.sourcePath.trim() === '') {
48
- throw new Error('Invalid sourcePath: cannot be empty');
49
- }
50
- if (args.destinationPath === undefined || args.destinationPath === null) {
51
- throw new Error('Missing required parameter: destinationPath');
26
+ case 'list': {
27
+ if (args.directory !== undefined && args.directory !== null && typeof args.directory !== 'string') {
28
+ throw new Error('Invalid directory: must be a string');
52
29
  }
53
- if (typeof args.destinationPath !== 'string') {
54
- throw new Error('Invalid destinationPath: must be a string');
55
- }
56
- if (args.destinationPath.trim() === '') {
57
- throw new Error('Invalid destinationPath: cannot be empty');
58
- }
59
- mappedName = 'import_asset';
60
- mappedArgs = {
61
- sourcePath: args.sourcePath,
62
- destinationPath: args.destinationPath
63
- };
64
- break;
65
- case 'create_material':
66
- // Validate required parameters
67
- if (args.name === undefined || args.name === null) {
68
- throw new Error('Missing required parameter: name');
69
- }
70
- if (typeof args.name !== 'string') {
71
- throw new Error('Invalid name: must be a string');
30
+ const res = await tools.assetResources.list(args.directory || '/Game', false);
31
+ return cleanObject({ success: true, ...res });
32
+ }
33
+ case 'import': {
34
+ if (typeof args.sourcePath !== 'string' || args.sourcePath.trim() === '') {
35
+ throw new Error('Invalid sourcePath');
72
36
  }
73
- if (args.name.trim() === '') {
74
- throw new Error('Invalid name: cannot be empty');
37
+ if (typeof args.destinationPath !== 'string' || args.destinationPath.trim() === '') {
38
+ throw new Error('Invalid destinationPath');
75
39
  }
76
- // Path is optional
77
- if (args.path !== undefined && args.path !== null) {
78
- if (typeof args.path !== 'string') {
79
- throw new Error('Invalid path: must be a string');
80
- }
40
+ const res = await tools.assetTools.importAsset(args.sourcePath, args.destinationPath);
41
+ return cleanObject(res);
42
+ }
43
+ case 'create_material': {
44
+ if (typeof args.name !== 'string' || args.name.trim() === '') {
45
+ throw new Error('Invalid name: must be a non-empty string');
81
46
  }
82
- mappedName = 'create_material';
83
- mappedArgs = {
84
- name: args.name,
85
- path: args.path
86
- };
87
- break;
47
+ const res = await tools.materialTools.createMaterial(args.name, args.path || '/Game/Materials');
48
+ return cleanObject(res);
49
+ }
88
50
  default:
89
51
  throw new Error(`Unknown asset action: ${args.action}`);
90
52
  }
91
- break;
92
53
  // 2. ACTOR CONTROL
93
54
  case 'control_actor':
94
55
  // Validate action exists
@@ -96,63 +57,41 @@ export async function handleConsolidatedToolCall(name, args, tools) {
96
57
  throw new Error('Missing required parameter: action');
97
58
  }
98
59
  switch (args.action) {
99
- case 'spawn':
100
- // Validate spawn parameters
101
- if (!args.classPath) {
102
- throw new Error('Missing required parameter: classPath');
103
- }
104
- if (typeof args.classPath !== 'string' || args.classPath.trim() === '') {
60
+ case 'spawn': {
61
+ if (!args.classPath || typeof args.classPath !== 'string' || args.classPath.trim() === '') {
105
62
  throw new Error('Invalid classPath: must be a non-empty string');
106
63
  }
107
- mappedName = 'spawn_actor';
108
- mappedArgs = {
64
+ const res = await tools.actorTools.spawn({
109
65
  classPath: args.classPath,
110
66
  location: args.location,
111
67
  rotation: args.rotation
112
- };
113
- break;
114
- case 'delete':
115
- // Validate delete parameters
116
- if (!args.actorName) {
117
- throw new Error('Missing required parameter: actorName');
118
- }
119
- if (typeof args.actorName !== 'string' || args.actorName.trim() === '') {
120
- throw new Error('Invalid actorName: must be a non-empty string');
121
- }
122
- mappedName = 'delete_actor';
123
- mappedArgs = {
124
- actorName: args.actorName
125
- };
126
- break;
127
- case 'apply_force':
128
- // Validate apply_force parameters
129
- if (!args.actorName) {
130
- throw new Error('Missing required parameter: actorName');
131
- }
132
- if (typeof args.actorName !== 'string' || args.actorName.trim() === '') {
133
- throw new Error('Invalid actorName: must be a non-empty string');
134
- }
135
- if (!args.force) {
136
- throw new Error('Missing required parameter: force');
68
+ });
69
+ return cleanObject(res);
70
+ }
71
+ case 'delete': {
72
+ if (!args.actorName || typeof args.actorName !== 'string' || args.actorName.trim() === '') {
73
+ throw new Error('Invalid actorName');
137
74
  }
138
- if (typeof args.force !== 'object' || args.force === null) {
139
- throw new Error('Invalid force: must be an object with x, y, z properties');
75
+ const res = await tools.bridge.executeEditorFunction('DELETE_ACTOR', { actor_name: args.actorName });
76
+ return cleanObject(res);
77
+ }
78
+ case 'apply_force': {
79
+ if (!args.actorName || typeof args.actorName !== 'string' || args.actorName.trim() === '') {
80
+ throw new Error('Invalid actorName');
140
81
  }
141
- if (typeof args.force.x !== 'number' ||
142
- typeof args.force.y !== 'number' ||
143
- typeof args.force.z !== 'number') {
144
- throw new Error('Invalid force: x, y, z must all be numbers');
82
+ if (!args.force || typeof args.force.x !== 'number' || typeof args.force.y !== 'number' || typeof args.force.z !== 'number') {
83
+ throw new Error('Invalid force: must have numeric x,y,z');
145
84
  }
146
- mappedName = 'apply_force';
147
- mappedArgs = {
85
+ const res = await tools.physicsTools.applyForce({
148
86
  actorName: args.actorName,
149
- force: args.force
150
- };
151
- break;
87
+ forceType: 'Force',
88
+ vector: [args.force.x, args.force.y, args.force.z]
89
+ });
90
+ return cleanObject(res);
91
+ }
152
92
  default:
153
93
  throw new Error(`Unknown actor action: ${args.action}`);
154
94
  }
155
- break;
156
95
  // 3. EDITOR CONTROL
157
96
  case 'control_editor':
158
97
  // Validate action exists
@@ -160,210 +99,91 @@ export async function handleConsolidatedToolCall(name, args, tools) {
160
99
  throw new Error('Missing required parameter: action');
161
100
  }
162
101
  switch (args.action) {
163
- case 'play':
164
- mappedName = 'play_in_editor';
165
- mappedArgs = {};
166
- break;
167
- case 'stop':
168
- mappedName = 'stop_play_in_editor';
169
- mappedArgs = {};
170
- break;
171
- case 'pause':
172
- mappedName = 'pause_play_in_editor';
173
- mappedArgs = {};
174
- break;
175
- case 'set_game_speed':
176
- // Validate game speed parameter
177
- if (args.speed === undefined || args.speed === null) {
178
- throw new Error('Missing required parameter: speed');
179
- }
180
- if (typeof args.speed !== 'number') {
181
- throw new Error('Invalid speed: must be a number');
182
- }
183
- if (isNaN(args.speed)) {
184
- throw new Error('Invalid speed: cannot be NaN');
185
- }
186
- if (!isFinite(args.speed)) {
187
- throw new Error('Invalid speed: must be finite');
188
- }
189
- if (args.speed <= 0) {
190
- throw new Error('Invalid speed: must be positive');
102
+ case 'play': {
103
+ const res = await tools.editorTools.playInEditor();
104
+ return cleanObject(res);
105
+ }
106
+ case 'stop': {
107
+ const res = await tools.editorTools.stopPlayInEditor();
108
+ return cleanObject(res);
109
+ }
110
+ case 'pause': {
111
+ const res = await tools.editorTools.pausePlayInEditor();
112
+ return cleanObject(res);
113
+ }
114
+ case 'set_game_speed': {
115
+ if (typeof args.speed !== 'number' || !isFinite(args.speed) || args.speed <= 0) {
116
+ throw new Error('Invalid speed: must be a positive number');
191
117
  }
192
- mappedName = 'set_game_speed';
193
- mappedArgs = {
194
- speed: args.speed
195
- };
196
- break;
197
- case 'eject':
198
- mappedName = 'eject_from_pawn';
199
- mappedArgs = {};
200
- break;
201
- case 'possess':
202
- mappedName = 'possess_pawn';
203
- mappedArgs = {};
204
- break;
205
- case 'set_camera':
206
- // Allow either location or rotation or both
207
- // Don't require both to be present
208
- mappedName = 'set_camera';
209
- mappedArgs = {
210
- location: args.location,
211
- rotation: args.rotation
212
- };
213
- break;
214
- case 'set_view_mode':
215
- // Validate view mode parameter
216
- if (!args.viewMode) {
118
+ // Use console command via bridge
119
+ const res = await tools.bridge.executeConsoleCommand(`slomo ${args.speed}`);
120
+ return cleanObject(res);
121
+ }
122
+ case 'eject': {
123
+ const res = await tools.bridge.executeConsoleCommand('eject');
124
+ return cleanObject(res);
125
+ }
126
+ case 'possess': {
127
+ const res = await tools.bridge.executeConsoleCommand('viewself');
128
+ return cleanObject(res);
129
+ }
130
+ case 'set_camera': {
131
+ const res = await tools.editorTools.setViewportCamera(args.location, args.rotation);
132
+ return cleanObject(res);
133
+ }
134
+ case 'set_view_mode': {
135
+ if (!args.viewMode || typeof args.viewMode !== 'string')
217
136
  throw new Error('Missing required parameter: viewMode');
218
- }
219
- if (typeof args.viewMode !== 'string' || args.viewMode.trim() === '') {
220
- throw new Error('Invalid viewMode: must be a non-empty string');
221
- }
222
- // Normalize view mode to match what debug.ts expects
223
- const validModes = ['lit', 'unlit', 'wireframe', 'detail_lighting', 'lighting_only',
224
- 'light_complexity', 'shader_complexity', 'lightmap_density',
225
- 'stationary_light_overlap', 'reflections', 'visualize_buffer',
226
- 'collision_pawn', 'collision_visibility', 'lod_coloration', 'quad_overdraw'];
227
- const normalizedMode = args.viewMode.toLowerCase().replace(/_/g, '');
228
- // Map to proper case for debug.ts
229
- let mappedMode = '';
230
- switch (normalizedMode) {
231
- case 'lit':
232
- mappedMode = 'Lit';
233
- break;
234
- case 'unlit':
235
- mappedMode = 'Unlit';
236
- break;
237
- case 'wireframe':
238
- mappedMode = 'Wireframe';
239
- break;
240
- case 'detaillighting':
241
- mappedMode = 'DetailLighting';
242
- break;
243
- case 'lightingonly':
244
- mappedMode = 'LightingOnly';
245
- break;
246
- case 'lightcomplexity':
247
- mappedMode = 'LightComplexity';
248
- break;
249
- case 'shadercomplexity':
250
- mappedMode = 'ShaderComplexity';
251
- break;
252
- case 'lightmapdensity':
253
- mappedMode = 'LightmapDensity';
254
- break;
255
- case 'stationarylightoverlap':
256
- mappedMode = 'StationaryLightOverlap';
257
- break;
258
- case 'reflections':
259
- mappedMode = 'ReflectionOverride';
260
- break;
261
- case 'visualizebuffer':
262
- mappedMode = 'VisualizeBuffer';
263
- break;
264
- case 'collisionpawn':
265
- mappedMode = 'CollisionPawn';
266
- break;
267
- case 'collisionvisibility':
268
- mappedMode = 'CollisionVisibility';
269
- break;
270
- case 'lodcoloration':
271
- mappedMode = 'LODColoration';
272
- break;
273
- case 'quadoverdraw':
274
- mappedMode = 'QuadOverdraw';
275
- break;
276
- default:
277
- throw new Error(`Invalid viewMode: '${args.viewMode}'. Valid modes are: ${validModes.join(', ')}`);
278
- }
279
- mappedName = 'set_view_mode';
280
- mappedArgs = {
281
- mode: mappedMode
282
- };
283
- break;
137
+ const res = await tools.bridge.setSafeViewMode(args.viewMode);
138
+ return cleanObject(res);
139
+ }
284
140
  default:
285
141
  throw new Error(`Unknown editor action: ${args.action}`);
286
142
  }
287
- break;
288
143
  // 4. LEVEL MANAGER
289
144
  case 'manage_level':
145
+ if (!args.action)
146
+ throw new Error('Missing required parameter: action');
290
147
  switch (args.action) {
291
- case 'load':
292
- mappedName = 'load_level';
293
- mappedArgs = {
294
- levelPath: args.levelPath
295
- };
296
- if (args.streaming !== undefined) {
297
- mappedArgs.streaming = args.streaming;
298
- }
299
- break;
300
- case 'save':
301
- mappedName = 'save_level';
302
- mappedArgs = {
303
- levelName: args.levelName
304
- };
305
- if (args.savePath !== undefined) {
306
- mappedArgs.savePath = args.savePath;
307
- }
308
- break;
309
- case 'stream':
310
- mappedName = 'stream_level';
311
- mappedArgs = {
312
- levelName: args.levelName,
313
- shouldBeLoaded: args.shouldBeLoaded,
314
- shouldBeVisible: args.shouldBeVisible
315
- };
316
- break;
317
- case 'create_light':
318
- // Validate light type
319
- if (!args.lightType) {
148
+ case 'load': {
149
+ if (!args.levelPath || typeof args.levelPath !== 'string')
150
+ throw new Error('Missing required parameter: levelPath');
151
+ const res = await tools.levelTools.loadLevel({ levelPath: args.levelPath, streaming: !!args.streaming });
152
+ return cleanObject(res);
153
+ }
154
+ case 'save': {
155
+ const res = await tools.levelTools.saveLevel({ levelName: args.levelName, savePath: args.savePath });
156
+ return cleanObject(res);
157
+ }
158
+ case 'stream': {
159
+ if (!args.levelName || typeof args.levelName !== 'string')
160
+ throw new Error('Missing required parameter: levelName');
161
+ const res = await tools.levelTools.streamLevel({ levelName: args.levelName, shouldBeLoaded: !!args.shouldBeLoaded, shouldBeVisible: !!args.shouldBeVisible });
162
+ return cleanObject(res);
163
+ }
164
+ case 'create_light': {
165
+ if (!args.lightType)
320
166
  throw new Error('Missing required parameter: lightType');
321
- }
322
- const validLightTypes = ['directional', 'point', 'spot', 'rect', 'sky'];
323
- const normalizedLightType = String(args.lightType).toLowerCase();
324
- if (!validLightTypes.includes(normalizedLightType)) {
325
- throw new Error(`Invalid lightType: '${args.lightType}'. Valid types are: ${validLightTypes.join(', ')}`);
326
- }
327
- // Validate name
328
- if (!args.name) {
329
- throw new Error('Missing required parameter: name');
330
- }
331
- if (typeof args.name !== 'string' || args.name.trim() === '') {
332
- throw new Error('Invalid name: must be a non-empty string');
333
- }
334
- // Validate intensity if provided
335
- if (args.intensity !== undefined) {
336
- if (typeof args.intensity !== 'number' || !isFinite(args.intensity)) {
337
- throw new Error(`Invalid intensity: must be a finite number, got ${typeof args.intensity}`);
338
- }
339
- if (args.intensity < 0) {
340
- throw new Error('Invalid intensity: must be non-negative');
341
- }
342
- }
343
- // Validate location if provided
344
- if (args.location !== undefined && args.location !== null) {
345
- if (!Array.isArray(args.location) && typeof args.location !== 'object') {
346
- throw new Error('Invalid location: must be an array [x,y,z] or object {x,y,z}');
347
- }
348
- }
349
- mappedName = 'create_light';
350
- mappedArgs = {
351
- lightType: args.lightType,
352
- name: args.name,
353
- location: args.location,
354
- intensity: args.intensity
355
- };
356
- break;
357
- case 'build_lighting':
358
- mappedName = 'build_lighting';
359
- mappedArgs = {
360
- quality: args.quality
361
- };
362
- break;
167
+ if (!args.name || typeof args.name !== 'string' || args.name.trim() === '')
168
+ throw new Error('Invalid name');
169
+ const t = String(args.lightType).toLowerCase();
170
+ if (t === 'directional')
171
+ return cleanObject(await tools.lightingTools.createDirectionalLight({ name: args.name, intensity: args.intensity }));
172
+ if (t === 'point')
173
+ return cleanObject(await tools.lightingTools.createPointLight({ name: args.name, location: args.location ? [args.location.x, args.location.y, args.location.z] : [0, 0, 0], intensity: args.intensity }));
174
+ if (t === 'spot')
175
+ return cleanObject(await tools.lightingTools.createSpotLight({ name: args.name, location: args.location ? [args.location.x, args.location.y, args.location.z] : [0, 0, 0], rotation: [0, 0, 0], intensity: args.intensity }));
176
+ if (t === 'rect')
177
+ return cleanObject(await tools.lightingTools.createRectLight({ name: args.name, location: args.location ? [args.location.x, args.location.y, args.location.z] : [0, 0, 0], rotation: [0, 0, 0], intensity: args.intensity }));
178
+ throw new Error(`Unknown light type: ${args.lightType}`);
179
+ }
180
+ case 'build_lighting': {
181
+ const res = await tools.lightingTools.buildLighting({ quality: args.quality || 'High', buildReflectionCaptures: true });
182
+ return cleanObject(res);
183
+ }
363
184
  default:
364
185
  throw new Error(`Unknown level action: ${args.action}`);
365
186
  }
366
- break;
367
187
  // 5. ANIMATION & PHYSICS
368
188
  case 'animation_physics':
369
189
  // Validate action exists
@@ -371,438 +191,244 @@ export async function handleConsolidatedToolCall(name, args, tools) {
371
191
  throw new Error('Missing required parameter: action');
372
192
  }
373
193
  switch (args.action) {
374
- case 'create_animation_bp':
375
- // Validate required parameters
376
- if (args.name === undefined || args.name === null) {
377
- throw new Error('Missing required parameter: name');
378
- }
379
- if (typeof args.name !== 'string') {
380
- throw new Error('Invalid name: must be a string');
381
- }
382
- if (args.name.trim() === '') {
383
- throw new Error('Invalid name: cannot be empty');
384
- }
385
- if (args.skeletonPath === undefined || args.skeletonPath === null) {
386
- throw new Error('Missing required parameter: skeletonPath');
387
- }
388
- if (typeof args.skeletonPath !== 'string') {
389
- throw new Error('Invalid skeletonPath: must be a string');
390
- }
391
- if (args.skeletonPath.trim() === '') {
392
- throw new Error('Invalid skeletonPath: cannot be empty');
393
- }
394
- // Optional savePath validation
395
- if (args.savePath !== undefined && args.savePath !== null) {
396
- if (typeof args.savePath !== 'string') {
397
- throw new Error('Invalid savePath: must be a string');
398
- }
399
- }
400
- mappedName = 'create_animation_blueprint';
401
- mappedArgs = {
402
- name: args.name,
403
- skeletonPath: args.skeletonPath,
404
- savePath: args.savePath
405
- };
406
- break;
407
- case 'play_montage':
408
- // Validate required parameters
409
- if (args.actorName === undefined || args.actorName === null) {
410
- throw new Error('Missing required parameter: actorName');
411
- }
412
- if (typeof args.actorName !== 'string') {
413
- throw new Error('Invalid actorName: must be a string');
414
- }
415
- if (args.actorName.trim() === '') {
416
- throw new Error('Invalid actorName: cannot be empty');
417
- }
418
- // Check for montagePath or animationPath
194
+ case 'create_animation_bp': {
195
+ if (typeof args.name !== 'string' || args.name.trim() === '')
196
+ throw new Error('Invalid name');
197
+ if (typeof args.skeletonPath !== 'string' || args.skeletonPath.trim() === '')
198
+ throw new Error('Invalid skeletonPath');
199
+ const res = await tools.animationTools.createAnimationBlueprint({ name: args.name, skeletonPath: args.skeletonPath, savePath: args.savePath });
200
+ return cleanObject(res);
201
+ }
202
+ case 'play_montage': {
203
+ if (typeof args.actorName !== 'string' || args.actorName.trim() === '')
204
+ throw new Error('Invalid actorName');
419
205
  const montagePath = args.montagePath || args.animationPath;
420
- if (montagePath === undefined || montagePath === null) {
421
- throw new Error('Missing required parameter: montagePath or animationPath');
422
- }
423
- if (typeof montagePath !== 'string') {
424
- throw new Error('Invalid montagePath: must be a string');
425
- }
426
- if (montagePath.trim() === '') {
427
- throw new Error('Invalid montagePath: cannot be empty');
428
- }
429
- // Optional playRate validation
430
- if (args.playRate !== undefined && args.playRate !== null) {
431
- if (typeof args.playRate !== 'number') {
432
- throw new Error('Invalid playRate: must be a number');
433
- }
434
- if (isNaN(args.playRate)) {
435
- throw new Error('Invalid playRate: cannot be NaN');
436
- }
437
- if (!isFinite(args.playRate)) {
438
- throw new Error('Invalid playRate: must be finite');
439
- }
440
- }
441
- mappedName = 'play_animation_montage';
442
- mappedArgs = {
443
- actorName: args.actorName,
444
- montagePath: montagePath,
445
- playRate: args.playRate
446
- };
447
- break;
448
- case 'setup_ragdoll':
449
- // Validate required parameters
450
- if (args.skeletonPath === undefined || args.skeletonPath === null) {
451
- throw new Error('Missing required parameter: skeletonPath');
452
- }
453
- if (typeof args.skeletonPath !== 'string') {
454
- throw new Error('Invalid skeletonPath: must be a string');
455
- }
456
- if (args.skeletonPath.trim() === '') {
457
- throw new Error('Invalid skeletonPath: cannot be empty');
458
- }
459
- if (args.physicsAssetName === undefined || args.physicsAssetName === null) {
460
- throw new Error('Missing required parameter: physicsAssetName');
461
- }
462
- if (typeof args.physicsAssetName !== 'string') {
463
- throw new Error('Invalid physicsAssetName: must be a string');
464
- }
465
- if (args.physicsAssetName.trim() === '') {
466
- throw new Error('Invalid physicsAssetName: cannot be empty');
467
- }
468
- // Optional blendWeight validation
469
- if (args.blendWeight !== undefined && args.blendWeight !== null) {
470
- if (typeof args.blendWeight !== 'number') {
471
- throw new Error('Invalid blendWeight: must be a number');
472
- }
473
- if (isNaN(args.blendWeight)) {
474
- throw new Error('Invalid blendWeight: cannot be NaN');
475
- }
476
- if (!isFinite(args.blendWeight)) {
477
- throw new Error('Invalid blendWeight: must be finite');
478
- }
479
- }
480
- // Optional savePath validation
481
- if (args.savePath !== undefined && args.savePath !== null) {
482
- if (typeof args.savePath !== 'string') {
483
- throw new Error('Invalid savePath: must be a string');
484
- }
485
- }
486
- mappedName = 'setup_ragdoll';
487
- mappedArgs = {
488
- skeletonPath: args.skeletonPath,
489
- physicsAssetName: args.physicsAssetName,
490
- blendWeight: args.blendWeight,
491
- savePath: args.savePath
492
- };
493
- break;
206
+ if (typeof montagePath !== 'string' || montagePath.trim() === '')
207
+ throw new Error('Invalid montagePath');
208
+ const res = await tools.animationTools.playAnimation({ actorName: args.actorName, animationType: 'Montage', animationPath: montagePath, playRate: args.playRate });
209
+ return cleanObject(res);
210
+ }
211
+ case 'setup_ragdoll': {
212
+ if (typeof args.skeletonPath !== 'string' || args.skeletonPath.trim() === '')
213
+ throw new Error('Invalid skeletonPath');
214
+ if (typeof args.physicsAssetName !== 'string' || args.physicsAssetName.trim() === '')
215
+ throw new Error('Invalid physicsAssetName');
216
+ const res = await tools.physicsTools.setupRagdoll({ skeletonPath: args.skeletonPath, physicsAssetName: args.physicsAssetName, blendWeight: args.blendWeight, savePath: args.savePath });
217
+ return cleanObject(res);
218
+ }
494
219
  default:
495
220
  throw new Error(`Unknown animation/physics action: ${args.action}`);
496
221
  }
497
- break;
498
222
  // 6. EFFECTS SYSTEM
499
223
  case 'create_effect':
500
224
  switch (args.action) {
501
- case 'particle':
502
- mappedName = 'create_particle_effect';
503
- mappedArgs = {
504
- effectType: args.effectType,
505
- name: args.name,
506
- location: args.location
507
- };
508
- break;
509
- case 'niagara':
510
- mappedName = 'spawn_niagara_system';
511
- mappedArgs = {
512
- systemPath: args.systemPath,
513
- location: args.location,
514
- scale: args.scale
515
- };
516
- break;
517
- case 'debug_shape':
518
- mappedName = 'draw_debug_shape';
519
- // Convert location object to array for position
520
- const pos = args.location || args.position;
521
- mappedArgs = {
522
- shape: args.shape,
523
- position: pos ? [pos.x || 0, pos.y || 0, pos.z || 0] : [0, 0, 0],
524
- size: args.size,
525
- color: args.color,
526
- duration: args.duration
527
- };
528
- break;
225
+ case 'particle': {
226
+ const res = await tools.niagaraTools.createEffect({ effectType: args.effectType, name: args.name, location: args.location, scale: args.scale, customParameters: args.customParameters });
227
+ return cleanObject(res);
228
+ }
229
+ case 'niagara': {
230
+ if (typeof args.systemPath !== 'string' || args.systemPath.trim() === '')
231
+ throw new Error('Invalid systemPath');
232
+ // Create or ensure system exists (spawning in editor is not universally supported via RC)
233
+ const name = args.name || args.systemPath.split('/').pop();
234
+ const res = await tools.niagaraTools.createSystem({ name, savePath: args.savePath || '/Game/Effects/Niagara' });
235
+ return cleanObject(res);
236
+ }
237
+ case 'debug_shape': {
238
+ const shape = String(args.shape || 'Sphere').toLowerCase();
239
+ const loc = args.location || { x: 0, y: 0, z: 0 };
240
+ const size = args.size || 100;
241
+ const color = args.color || [255, 0, 0, 255];
242
+ const duration = args.duration || 5;
243
+ if (shape === 'line') {
244
+ const end = args.end || { x: loc.x + size, y: loc.y, z: loc.z };
245
+ return cleanObject(await tools.debugTools.drawDebugLine({ start: [loc.x, loc.y, loc.z], end: [end.x, end.y, end.z], color, duration }));
246
+ }
247
+ else if (shape === 'box') {
248
+ const extent = [size, size, size];
249
+ return cleanObject(await tools.debugTools.drawDebugBox({ center: [loc.x, loc.y, loc.z], extent, color, duration }));
250
+ }
251
+ else if (shape === 'sphere') {
252
+ return cleanObject(await tools.debugTools.drawDebugSphere({ center: [loc.x, loc.y, loc.z], radius: size, color, duration }));
253
+ }
254
+ else if (shape === 'capsule') {
255
+ return cleanObject(await tools.debugTools.drawDebugCapsule({ center: [loc.x, loc.y, loc.z], halfHeight: size, radius: Math.max(10, size / 3), color, duration }));
256
+ }
257
+ else if (shape === 'cone') {
258
+ return cleanObject(await tools.debugTools.drawDebugCone({ origin: [loc.x, loc.y, loc.z], direction: [0, 0, 1], length: size, angleWidth: 0.5, angleHeight: 0.5, color, duration }));
259
+ }
260
+ else if (shape === 'arrow') {
261
+ const end = args.end || { x: loc.x + size, y: loc.y, z: loc.z };
262
+ return cleanObject(await tools.debugTools.drawDebugArrow({ start: [loc.x, loc.y, loc.z], end: [end.x, end.y, end.z], color, duration }));
263
+ }
264
+ else if (shape === 'point') {
265
+ return cleanObject(await tools.debugTools.drawDebugPoint({ location: [loc.x, loc.y, loc.z], size, color, duration }));
266
+ }
267
+ else if (shape === 'text' || shape === 'string') {
268
+ const text = args.text || 'Debug';
269
+ return cleanObject(await tools.debugTools.drawDebugString({ location: [loc.x, loc.y, loc.z], text, color, duration }));
270
+ }
271
+ // Default fallback
272
+ return cleanObject(await tools.debugTools.drawDebugSphere({ center: [loc.x, loc.y, loc.z], radius: size, color, duration }));
273
+ }
529
274
  default:
530
275
  throw new Error(`Unknown effect action: ${args.action}`);
531
276
  }
532
- break;
533
277
  // 7. BLUEPRINT MANAGER
534
278
  case 'manage_blueprint':
535
279
  switch (args.action) {
536
- case 'create':
537
- mappedName = 'create_blueprint';
538
- mappedArgs = {
539
- name: args.name,
540
- blueprintType: args.blueprintType,
541
- savePath: args.savePath
542
- };
543
- break;
544
- case 'add_component':
545
- mappedName = 'add_blueprint_component';
546
- mappedArgs = {
547
- blueprintName: args.name,
548
- componentType: args.componentType,
549
- componentName: args.componentName
550
- };
551
- break;
280
+ case 'create': {
281
+ const res = await tools.blueprintTools.createBlueprint({ name: args.name, blueprintType: args.blueprintType || 'Actor', savePath: args.savePath });
282
+ return cleanObject(res);
283
+ }
284
+ case 'add_component': {
285
+ const res = await tools.blueprintTools.addComponent({ blueprintName: args.name, componentType: args.componentType, componentName: args.componentName });
286
+ return cleanObject(res);
287
+ }
552
288
  default:
553
289
  throw new Error(`Unknown blueprint action: ${args.action}`);
554
290
  }
555
- break;
556
291
  // 8. ENVIRONMENT BUILDER
557
292
  case 'build_environment':
558
293
  switch (args.action) {
559
- case 'create_landscape':
560
- mappedName = 'create_landscape';
561
- mappedArgs = {
562
- name: args.name,
294
+ case 'create_landscape': {
295
+ const res = await tools.landscapeTools.createLandscape({ name: args.name, sizeX: args.sizeX, sizeY: args.sizeY, materialPath: args.materialPath });
296
+ return cleanObject(res);
297
+ }
298
+ case 'sculpt': {
299
+ const res = await tools.landscapeTools.sculptLandscape({ landscapeName: args.name, tool: args.tool, brushSize: args.brushSize, strength: args.strength });
300
+ return cleanObject(res);
301
+ }
302
+ case 'add_foliage': {
303
+ const res = await tools.foliageTools.addFoliageType({ name: args.name, meshPath: args.meshPath, density: args.density });
304
+ return cleanObject(res);
305
+ }
306
+ case 'paint_foliage': {
307
+ const pos = args.position ? [args.position.x || 0, args.position.y || 0, args.position.z || 0] : [0, 0, 0];
308
+ const res = await tools.foliageTools.paintFoliage({ foliageType: args.foliageType, position: pos, brushSize: args.brushSize, paintDensity: args.paintDensity, eraseMode: args.eraseMode });
309
+ return cleanObject(res);
310
+ }
311
+ case 'create_procedural_terrain': {
312
+ const loc = args.location ? [args.location.x || 0, args.location.y || 0, args.location.z || 0] : [0, 0, 0];
313
+ const res = await tools.buildEnvAdvanced.createProceduralTerrain({
314
+ name: args.name || 'ProceduralTerrain',
315
+ location: loc,
563
316
  sizeX: args.sizeX,
564
317
  sizeY: args.sizeY,
565
- materialPath: args.materialPath
566
- };
567
- break;
568
- case 'sculpt':
569
- mappedName = 'sculpt_landscape';
570
- mappedArgs = {
571
- landscapeName: args.name,
572
- tool: args.tool,
573
- brushSize: args.brushSize,
574
- strength: args.strength
575
- };
576
- break;
577
- case 'add_foliage':
578
- // Validate foliage creation parameters to avoid bad console commands / engine warnings
579
- if (args.name === undefined || args.name === null || typeof args.name !== 'string' || args.name.trim() === '' || String(args.name).toLowerCase() === 'undefined' || String(args.name).toLowerCase() === 'any') {
580
- throw new Error(`Invalid foliage name: '${args.name}'`);
581
- }
582
- if (args.meshPath === undefined || args.meshPath === null || typeof args.meshPath !== 'string' || args.meshPath.trim() === '' || String(args.meshPath).toLowerCase() === 'undefined') {
583
- throw new Error(`Invalid meshPath: '${args.meshPath}'`);
584
- }
585
- if (args.density !== undefined) {
586
- if (typeof args.density !== 'number' || !isFinite(args.density) || args.density < 0) {
587
- throw new Error(`Invalid density: '${args.density}' (must be non-negative finite number)`);
588
- }
589
- }
590
- mappedName = 'add_foliage_type';
591
- mappedArgs = {
592
- name: args.name,
318
+ subdivisions: args.subdivisions,
319
+ heightFunction: args.heightFunction,
320
+ material: args.materialPath
321
+ });
322
+ return cleanObject(res);
323
+ }
324
+ case 'create_procedural_foliage': {
325
+ if (!args.bounds || !args.bounds.location || !args.bounds.size)
326
+ throw new Error('bounds.location and bounds.size are required');
327
+ const bounds = {
328
+ location: [args.bounds.location.x || 0, args.bounds.location.y || 0, args.bounds.location.z || 0],
329
+ size: [args.bounds.size.x || 1000, args.bounds.size.y || 1000, args.bounds.size.z || 100]
330
+ };
331
+ const res = await tools.buildEnvAdvanced.createProceduralFoliage({
332
+ name: args.name || 'ProceduralFoliage',
333
+ bounds,
334
+ foliageTypes: args.foliageTypes || [],
335
+ seed: args.seed
336
+ });
337
+ return cleanObject(res);
338
+ }
339
+ case 'add_foliage_instances': {
340
+ if (!args.foliageType)
341
+ throw new Error('foliageType is required');
342
+ if (!Array.isArray(args.transforms))
343
+ throw new Error('transforms array is required');
344
+ const transforms = args.transforms.map(t => ({
345
+ location: [t.location?.x || 0, t.location?.y || 0, t.location?.z || 0],
346
+ rotation: t.rotation ? [t.rotation.pitch || 0, t.rotation.yaw || 0, t.rotation.roll || 0] : undefined,
347
+ scale: t.scale ? [t.scale.x || 1, t.scale.y || 1, t.scale.z || 1] : undefined
348
+ }));
349
+ const res = await tools.buildEnvAdvanced.addFoliageInstances({ foliageType: args.foliageType, transforms });
350
+ return cleanObject(res);
351
+ }
352
+ case 'create_landscape_grass_type': {
353
+ const res = await tools.buildEnvAdvanced.createLandscapeGrassType({
354
+ name: args.name || 'GrassType',
593
355
  meshPath: args.meshPath,
594
- density: args.density
595
- };
596
- break;
597
- case 'paint_foliage':
598
- // Validate paint parameters
599
- if (args.foliageType === undefined || args.foliageType === null || typeof args.foliageType !== 'string' || args.foliageType.trim() === '' || String(args.foliageType).toLowerCase() === 'undefined' || String(args.foliageType).toLowerCase() === 'any') {
600
- throw new Error(`Invalid foliageType: '${args.foliageType}'`);
601
- }
602
- // Convert position object to array if needed
603
- let positionArray;
604
- if (args.position) {
605
- if (Array.isArray(args.position)) {
606
- positionArray = args.position;
607
- }
608
- else if (typeof args.position === 'object') {
609
- positionArray = [args.position.x || 0, args.position.y || 0, args.position.z || 0];
610
- }
611
- else {
612
- positionArray = [0, 0, 0];
613
- }
614
- }
615
- else {
616
- positionArray = [0, 0, 0];
617
- }
618
- // Validate numbers in position
619
- if (!Array.isArray(positionArray) || positionArray.length !== 3 || positionArray.some(v => typeof v !== 'number' || !isFinite(v))) {
620
- throw new Error(`Invalid position: '${JSON.stringify(args.position)}'`);
621
- }
622
- if (args.brushSize !== undefined) {
623
- if (typeof args.brushSize !== 'number' || !isFinite(args.brushSize) || args.brushSize < 0) {
624
- throw new Error(`Invalid brushSize: '${args.brushSize}' (must be non-negative finite number)`);
625
- }
626
- }
627
- mappedName = 'paint_foliage';
628
- mappedArgs = {
629
- foliageType: args.foliageType,
630
- position: positionArray,
631
- brushSize: args.brushSize
632
- };
633
- break;
356
+ density: args.density,
357
+ minScale: args.minScale,
358
+ maxScale: args.maxScale
359
+ });
360
+ return cleanObject(res);
361
+ }
634
362
  default:
635
363
  throw new Error(`Unknown environment action: ${args.action}`);
636
364
  }
637
- break;
638
365
  // 9. SYSTEM CONTROL
639
366
  case 'system_control':
640
- // Validate args is not null/undefined
641
- if (args === null || args === undefined) {
642
- throw new Error('Invalid arguments: null or undefined');
643
- }
644
- if (typeof args !== 'object') {
645
- throw new Error('Invalid arguments: must be an object');
646
- }
647
- // Validate action exists
648
- if (!args.action) {
367
+ if (!args.action)
649
368
  throw new Error('Missing required parameter: action');
650
- }
651
369
  switch (args.action) {
652
- case 'profile':
653
- // Validate profile type
654
- const validProfileTypes = ['CPU', 'GPU', 'Memory', 'RenderThread', 'GameThread', 'All'];
655
- if (!args.profileType || !validProfileTypes.includes(args.profileType)) {
656
- throw new Error(`Invalid profileType: '${args.profileType}'. Valid types: ${validProfileTypes.join(', ')}`);
657
- }
658
- mappedName = 'start_profiling';
659
- mappedArgs = {
660
- type: args.profileType,
661
- duration: args.duration
662
- };
663
- break;
664
- case 'show_fps':
665
- // Validate enabled is boolean
666
- if (args.enabled !== undefined && typeof args.enabled !== 'boolean') {
667
- throw new Error(`Invalid enabled: must be boolean, got ${typeof args.enabled}`);
668
- }
669
- mappedName = 'show_fps';
670
- mappedArgs = {
671
- enabled: args.enabled,
672
- verbose: args.verbose
673
- };
674
- break;
675
- case 'set_quality':
676
- // Validate category - normalize aliases and singular forms used by sg.*Quality
677
- const validCategories = ['ViewDistance', 'AntiAliasing', 'PostProcessing', 'PostProcess',
678
- 'Shadows', 'Shadow', 'GlobalIllumination', 'Reflections', 'Reflection', 'Textures', 'Texture',
679
- 'Effects', 'Foliage', 'Shading'];
680
- if (!args.category || !validCategories.includes(args.category)) {
681
- throw new Error(`Invalid category: '${args.category}'. Valid categories: ${validCategories.join(', ')}`);
682
- }
683
- // Validate level
684
- if (args.level === undefined || args.level === null) {
685
- throw new Error('Missing required parameter: level');
686
- }
687
- if (typeof args.level !== 'number' || !Number.isInteger(args.level) || args.level < 0 || args.level > 4) {
688
- throw new Error(`Invalid level: must be integer 0-4, got ${args.level}`);
689
- }
690
- // Normalize category to sg.<Base>Quality base (singular where needed)
691
- const map = {
692
- ViewDistance: 'ViewDistance',
693
- AntiAliasing: 'AntiAliasing',
694
- PostProcessing: 'PostProcess',
695
- PostProcess: 'PostProcess',
696
- Shadows: 'Shadow',
697
- Shadow: 'Shadow',
698
- GlobalIllumination: 'GlobalIllumination',
699
- Reflections: 'Reflection',
700
- Reflection: 'Reflection',
701
- Textures: 'Texture',
702
- Texture: 'Texture',
703
- Effects: 'Effects',
704
- Foliage: 'Foliage',
705
- Shading: 'Shading',
706
- };
707
- const categoryName = map[String(args.category)] || args.category;
708
- mappedName = 'set_scalability';
709
- mappedArgs = {
710
- category: categoryName,
711
- level: args.level
712
- };
713
- break;
714
- case 'play_sound':
715
- // Validate sound path
716
- if (!args.soundPath || typeof args.soundPath !== 'string') {
717
- throw new Error('Invalid soundPath: must be a non-empty string');
718
- }
719
- // Validate volume if provided
720
- if (args.volume !== undefined) {
721
- if (typeof args.volume !== 'number' || args.volume < 0 || args.volume > 1) {
722
- throw new Error(`Invalid volume: must be 0-1, got ${args.volume}`);
723
- }
724
- }
725
- mappedName = 'play_sound';
726
- mappedArgs = {
727
- soundPath: args.soundPath,
728
- location: args.location,
729
- volume: args.volume,
730
- is3D: args.is3D
731
- };
732
- break;
733
- case 'create_widget':
734
- // Validate widget name
735
- if (!args.widgetName || typeof args.widgetName !== 'string' || args.widgetName.trim() === '') {
736
- throw new Error('Invalid widgetName: must be a non-empty string');
737
- }
738
- mappedName = 'create_widget';
739
- mappedArgs = {
740
- name: args.widgetName,
741
- type: args.widgetType,
742
- savePath: args.savePath
743
- };
744
- break;
745
- case 'show_widget':
746
- // Validate widget name
747
- if (!args.widgetName || typeof args.widgetName !== 'string') {
748
- throw new Error('Invalid widgetName: must be a non-empty string');
749
- }
750
- // Validate visible is boolean (default to true if not provided)
751
- const isVisible = args.visible !== undefined ? args.visible : true;
752
- if (typeof isVisible !== 'boolean') {
753
- throw new Error(`Invalid visible: must be boolean, got ${typeof isVisible}`);
754
- }
755
- mappedName = 'show_widget';
756
- mappedArgs = {
757
- widgetName: args.widgetName,
758
- visible: isVisible
759
- };
760
- break;
761
- case 'screenshot':
762
- mappedName = 'take_screenshot';
763
- mappedArgs = { resolution: args.resolution };
764
- break;
765
- case 'engine_start':
766
- mappedName = 'launch_editor';
767
- mappedArgs = { editorExe: args.editorExe, projectPath: args.projectPath };
768
- break;
769
- case 'engine_quit':
770
- mappedName = 'quit_editor';
771
- mappedArgs = {};
772
- break;
370
+ case 'profile': {
371
+ const res = await tools.performanceTools.startProfiling({ type: args.profileType, duration: args.duration });
372
+ return cleanObject(res);
373
+ }
374
+ case 'show_fps': {
375
+ const res = await tools.performanceTools.showFPS({ enabled: !!args.enabled, verbose: !!args.verbose });
376
+ return cleanObject(res);
377
+ }
378
+ case 'set_quality': {
379
+ const res = await tools.performanceTools.setScalability({ category: args.category, level: args.level });
380
+ return cleanObject(res);
381
+ }
382
+ case 'play_sound': {
383
+ if (args.location && typeof args.location === 'object') {
384
+ const loc = [args.location.x || 0, args.location.y || 0, args.location.z || 0];
385
+ const res = await tools.audioTools.playSoundAtLocation({ soundPath: args.soundPath, location: loc, volume: args.volume, pitch: args.pitch, startTime: args.startTime });
386
+ return cleanObject(res);
387
+ }
388
+ const res = await tools.audioTools.playSound2D({ soundPath: args.soundPath, volume: args.volume, pitch: args.pitch, startTime: args.startTime });
389
+ return cleanObject(res);
390
+ }
391
+ case 'create_widget': {
392
+ const res = await tools.uiTools.createWidget({ name: args.widgetName, type: args.widgetType, savePath: args.savePath });
393
+ return cleanObject(res);
394
+ }
395
+ case 'show_widget': {
396
+ const res = await tools.uiTools.setWidgetVisibility({ widgetName: args.widgetName, visible: args.visible !== false });
397
+ return cleanObject(res);
398
+ }
399
+ case 'screenshot': {
400
+ const res = await tools.visualTools.takeScreenshot({ resolution: args.resolution });
401
+ return cleanObject(res);
402
+ }
403
+ case 'engine_start': {
404
+ const res = await tools.engineTools.launchEditor({ editorExe: args.editorExe, projectPath: args.projectPath });
405
+ return cleanObject(res);
406
+ }
407
+ case 'engine_quit': {
408
+ const res = await tools.engineTools.quitEditor();
409
+ return cleanObject(res);
410
+ }
773
411
  default:
774
412
  throw new Error(`Unknown system action: ${args.action}`);
775
413
  }
776
- break;
777
414
  // 10. CONSOLE COMMAND - handle validation here
778
415
  case 'console_command':
779
- // Handle empty/invalid commands gracefully
780
- if (args.command === undefined || args.command === null || args.command === '' || typeof args.command !== 'string') {
781
- return {
782
- content: [{
783
- type: 'text',
784
- text: 'Empty command ignored'
785
- }],
786
- isError: false,
787
- success: true,
788
- message: 'Empty command'
789
- };
416
+ if (!args.command || typeof args.command !== 'string' || args.command.trim() === '') {
417
+ return { success: true, message: 'Empty command' };
418
+ }
419
+ // Basic safety filter
420
+ const cmd = String(args.command).trim();
421
+ const blocked = [/\bquit\b/i, /\bexit\b/i, /debugcrash/i];
422
+ if (blocked.some(r => r.test(cmd))) {
423
+ return { success: false, error: 'Command blocked for safety' };
790
424
  }
791
- const cmdTrimmed = args.command.trim();
792
- if (cmdTrimmed.length === 0) {
793
- return {
794
- content: [{
795
- type: 'text',
796
- text: 'Empty command ignored'
797
- }],
798
- isError: false,
799
- success: true,
800
- message: 'Empty command'
801
- };
425
+ try {
426
+ const res = await tools.bridge.executeConsoleCommand(cmd);
427
+ return cleanObject({ success: true, command: cmd, result: res });
428
+ }
429
+ catch (e) {
430
+ return cleanObject({ success: false, command: cmd, error: e?.message || String(e) });
802
431
  }
803
- mappedName = 'console_command';
804
- mappedArgs = args;
805
- break;
806
432
  // 11. REMOTE CONTROL PRESETS - Direct implementation
807
433
  case 'manage_rc':
808
434
  if (!args.action)
@@ -1001,34 +627,21 @@ export async function handleConsolidatedToolCall(name, args, tools) {
1001
627
  if (!args.action)
1002
628
  throw new Error('Missing required parameter: action');
1003
629
  switch (args.action) {
1004
- case 'inspect_object':
1005
- mappedName = 'inspect_object';
1006
- mappedArgs = { objectPath: args.objectPath };
1007
- break;
1008
- case 'set_property':
1009
- mappedName = 'inspect_set_property';
1010
- mappedArgs = { objectPath: args.objectPath, propertyName: args.propertyName, value: args.value };
1011
- break;
630
+ case 'inspect_object': {
631
+ const res = await tools.introspectionTools.inspectObject({ objectPath: args.objectPath, detailed: args.detailed });
632
+ return cleanObject(res);
633
+ }
634
+ case 'set_property': {
635
+ const res = await tools.introspectionTools.setProperty({ objectPath: args.objectPath, propertyName: args.propertyName, value: args.value });
636
+ return cleanObject(res);
637
+ }
1012
638
  default:
1013
639
  throw new Error(`Unknown inspect action: ${args.action}`);
1014
640
  }
1015
- break;
1016
641
  default:
1017
642
  throw new Error(`Unknown consolidated tool: ${name}`);
1018
643
  }
1019
- // Call the original handler with mapped name and args with timeout
1020
- const TOOL_TIMEOUT = 15000; // 15 seconds timeout for tool execution
1021
- const toolPromise = handleToolCall(mappedName, mappedArgs, tools);
1022
- const timeoutPromise = new Promise((_, reject) => {
1023
- setTimeout(() => {
1024
- reject(new Error(`Tool execution timeout after ${TOOL_TIMEOUT}ms`));
1025
- }, TOOL_TIMEOUT);
1026
- });
1027
- const result = await Promise.race([toolPromise, timeoutPromise]);
1028
- const duration = Date.now() - startTime;
1029
- console.log(`[ConsolidatedToolHandler] Completed execution of ${name} in ${duration}ms`);
1030
- // Clean the result to prevent circular reference errors
1031
- return cleanObject(result);
644
+ // All cases return (or throw) above; this is a type guard for exhaustiveness.
1032
645
  }
1033
646
  catch (err) {
1034
647
  const duration = Date.now() - startTime;