unreal-engine-mcp-server 0.5.2 → 0.5.4
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/CHANGELOG.md +195 -0
- package/README.md +9 -6
- package/dist/automation/bridge.d.ts +1 -0
- package/dist/automation/bridge.js +62 -4
- package/dist/automation/types.d.ts +1 -0
- package/dist/config/class-aliases.d.ts +5 -0
- package/dist/config/class-aliases.js +30 -0
- package/dist/constants.d.ts +5 -0
- package/dist/constants.js +5 -0
- package/dist/graphql/server.d.ts +0 -1
- package/dist/graphql/server.js +15 -16
- package/dist/index.js +1 -1
- package/dist/services/metrics-server.d.ts +2 -1
- package/dist/services/metrics-server.js +29 -4
- package/dist/tools/consolidated-tool-definitions.js +3 -3
- package/dist/tools/debug.d.ts +5 -0
- package/dist/tools/debug.js +7 -0
- package/dist/tools/handlers/actor-handlers.js +4 -27
- package/dist/tools/handlers/asset-handlers.js +13 -1
- package/dist/tools/handlers/blueprint-handlers.d.ts +4 -1
- package/dist/tools/handlers/common-handlers.d.ts +11 -11
- package/dist/tools/handlers/common-handlers.js +6 -4
- package/dist/tools/handlers/editor-handlers.d.ts +2 -1
- package/dist/tools/handlers/editor-handlers.js +6 -6
- package/dist/tools/handlers/effect-handlers.js +3 -0
- package/dist/tools/handlers/graph-handlers.d.ts +2 -1
- package/dist/tools/handlers/graph-handlers.js +1 -1
- package/dist/tools/handlers/input-handlers.d.ts +5 -1
- package/dist/tools/handlers/level-handlers.d.ts +2 -1
- package/dist/tools/handlers/level-handlers.js +3 -3
- package/dist/tools/handlers/lighting-handlers.d.ts +2 -1
- package/dist/tools/handlers/lighting-handlers.js +3 -0
- package/dist/tools/handlers/pipeline-handlers.d.ts +2 -1
- package/dist/tools/handlers/pipeline-handlers.js +64 -10
- package/dist/tools/handlers/sequence-handlers.d.ts +1 -1
- package/dist/tools/handlers/system-handlers.d.ts +1 -1
- package/dist/tools/input.d.ts +5 -1
- package/dist/tools/input.js +37 -1
- package/dist/tools/lighting.d.ts +1 -0
- package/dist/tools/lighting.js +7 -0
- package/dist/tools/physics.d.ts +1 -1
- package/dist/tools/sequence.d.ts +1 -0
- package/dist/tools/sequence.js +7 -0
- package/dist/types/handler-types.d.ts +343 -0
- package/dist/types/handler-types.js +2 -0
- package/dist/unreal-bridge.d.ts +1 -1
- package/dist/unreal-bridge.js +8 -6
- package/dist/utils/command-validator.d.ts +1 -0
- package/dist/utils/command-validator.js +11 -1
- package/dist/utils/error-handler.js +3 -1
- package/dist/utils/response-validator.js +2 -2
- package/dist/utils/safe-json.d.ts +1 -1
- package/dist/utils/safe-json.js +3 -6
- package/dist/utils/unreal-command-queue.js +1 -1
- package/dist/utils/validation.js +6 -2
- package/docs/handler-mapping.md +6 -1
- package/package.json +2 -2
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +25 -1
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +40 -58
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +27 -46
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +16 -1
- package/server.json +2 -2
- package/src/automation/bridge.ts +80 -10
- package/src/automation/types.ts +1 -0
- package/src/config/class-aliases.ts +65 -0
- package/src/constants.ts +10 -0
- package/src/graphql/server.ts +23 -23
- package/src/index.ts +1 -1
- package/src/services/metrics-server.ts +40 -6
- package/src/tools/consolidated-tool-definitions.ts +3 -3
- package/src/tools/debug.ts +8 -0
- package/src/tools/handlers/actor-handlers.ts +5 -31
- package/src/tools/handlers/asset-handlers.ts +19 -1
- package/src/tools/handlers/blueprint-handlers.ts +1 -1
- package/src/tools/handlers/common-handlers.ts +32 -11
- package/src/tools/handlers/editor-handlers.ts +8 -7
- package/src/tools/handlers/effect-handlers.ts +4 -0
- package/src/tools/handlers/graph-handlers.ts +7 -6
- package/src/tools/handlers/level-handlers.ts +5 -4
- package/src/tools/handlers/lighting-handlers.ts +5 -1
- package/src/tools/handlers/pipeline-handlers.ts +83 -16
- package/src/tools/input.ts +60 -1
- package/src/tools/lighting.ts +11 -0
- package/src/tools/physics.ts +1 -1
- package/src/tools/sequence.ts +11 -0
- package/src/types/handler-types.ts +442 -0
- package/src/unreal-bridge.ts +8 -6
- package/src/utils/command-validator.ts +23 -1
- package/src/utils/error-handler.ts +4 -1
- package/src/utils/response-validator.ts +7 -9
- package/src/utils/safe-json.ts +20 -15
- package/src/utils/unreal-command-queue.ts +3 -1
- package/src/utils/validation.test.ts +3 -3
- package/src/utils/validation.ts +36 -26
- package/tests/test-console-command.mjs +1 -1
- package/tests/test-runner.mjs +63 -3
- package/tests/run-unreal-tool-tests.mjs +0 -948
- package/tests/test-asset-errors.mjs +0 -35
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared type definitions for handler arguments and responses.
|
|
3
|
+
* Used across all *-handlers.ts files to replace 'any' types.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Common Geometry Types
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
/** 3D Vector - used for locations, forces, scales */
|
|
11
|
+
export interface Vector3 {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
z: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Rotation in Unreal format (Pitch, Yaw, Roll in degrees) */
|
|
18
|
+
export interface Rotator {
|
|
19
|
+
pitch: number;
|
|
20
|
+
yaw: number;
|
|
21
|
+
roll: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Transform combining location, rotation, and scale */
|
|
25
|
+
export interface Transform {
|
|
26
|
+
location?: Vector3;
|
|
27
|
+
rotation?: Rotator;
|
|
28
|
+
scale?: Vector3;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// Base Handler Types
|
|
33
|
+
// ============================================================================
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Base interface for handler arguments.
|
|
37
|
+
* All handler args should extend this or use it directly for loose typing.
|
|
38
|
+
*/
|
|
39
|
+
export interface HandlerArgs {
|
|
40
|
+
action?: string;
|
|
41
|
+
subAction?: string;
|
|
42
|
+
[key: string]: unknown;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Standard response from automation bridge requests.
|
|
47
|
+
*/
|
|
48
|
+
export interface AutomationResponse {
|
|
49
|
+
success: boolean;
|
|
50
|
+
message?: string;
|
|
51
|
+
error?: string;
|
|
52
|
+
result?: unknown;
|
|
53
|
+
[key: string]: unknown;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Component information returned from getComponents.
|
|
58
|
+
*/
|
|
59
|
+
export interface ComponentInfo {
|
|
60
|
+
name: string;
|
|
61
|
+
class?: string;
|
|
62
|
+
objectPath?: string;
|
|
63
|
+
[key: string]: unknown;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ============================================================================
|
|
67
|
+
// Actor Types
|
|
68
|
+
// ============================================================================
|
|
69
|
+
|
|
70
|
+
export interface ActorArgs extends HandlerArgs {
|
|
71
|
+
actorName?: string;
|
|
72
|
+
name?: string;
|
|
73
|
+
classPath?: string;
|
|
74
|
+
class?: string;
|
|
75
|
+
type?: string;
|
|
76
|
+
location?: Vector3;
|
|
77
|
+
rotation?: Rotator;
|
|
78
|
+
scale?: Vector3;
|
|
79
|
+
meshPath?: string;
|
|
80
|
+
timeoutMs?: number;
|
|
81
|
+
force?: Vector3;
|
|
82
|
+
parentActor?: string;
|
|
83
|
+
childActor?: string;
|
|
84
|
+
tag?: string;
|
|
85
|
+
newName?: string;
|
|
86
|
+
offset?: Vector3;
|
|
87
|
+
visible?: boolean;
|
|
88
|
+
componentName?: string;
|
|
89
|
+
componentType?: string;
|
|
90
|
+
properties?: Record<string, unknown>;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// Asset Types
|
|
95
|
+
// ============================================================================
|
|
96
|
+
|
|
97
|
+
export interface AssetArgs extends HandlerArgs {
|
|
98
|
+
assetPath?: string;
|
|
99
|
+
path?: string;
|
|
100
|
+
directory?: string;
|
|
101
|
+
directoryPath?: string;
|
|
102
|
+
sourcePath?: string;
|
|
103
|
+
destinationPath?: string;
|
|
104
|
+
newName?: string;
|
|
105
|
+
name?: string;
|
|
106
|
+
filter?: string;
|
|
107
|
+
recursive?: boolean;
|
|
108
|
+
overwrite?: boolean;
|
|
109
|
+
classNames?: string[];
|
|
110
|
+
packagePaths?: string[];
|
|
111
|
+
parentMaterial?: string;
|
|
112
|
+
parameters?: Record<string, unknown>;
|
|
113
|
+
assetPaths?: string[];
|
|
114
|
+
meshPath?: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ============================================================================
|
|
118
|
+
// Blueprint Types
|
|
119
|
+
// ============================================================================
|
|
120
|
+
|
|
121
|
+
export interface BlueprintArgs extends HandlerArgs {
|
|
122
|
+
blueprintPath?: string;
|
|
123
|
+
name?: string;
|
|
124
|
+
savePath?: string;
|
|
125
|
+
blueprintType?: string;
|
|
126
|
+
componentType?: string;
|
|
127
|
+
componentName?: string;
|
|
128
|
+
attachTo?: string;
|
|
129
|
+
variableName?: string;
|
|
130
|
+
eventType?: string;
|
|
131
|
+
customEventName?: string;
|
|
132
|
+
nodeType?: string;
|
|
133
|
+
graphName?: string;
|
|
134
|
+
x?: number;
|
|
135
|
+
y?: number;
|
|
136
|
+
memberName?: string;
|
|
137
|
+
nodeId?: string;
|
|
138
|
+
pinName?: string;
|
|
139
|
+
linkedTo?: string;
|
|
140
|
+
fromNodeId?: string;
|
|
141
|
+
fromPin?: string;
|
|
142
|
+
fromPinName?: string;
|
|
143
|
+
toNodeId?: string;
|
|
144
|
+
toPin?: string;
|
|
145
|
+
toPinName?: string;
|
|
146
|
+
propertyName?: string;
|
|
147
|
+
value?: unknown;
|
|
148
|
+
properties?: Record<string, unknown>;
|
|
149
|
+
compile?: boolean;
|
|
150
|
+
save?: boolean;
|
|
151
|
+
metadata?: Record<string, unknown>;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ============================================================================
|
|
155
|
+
// Editor Types
|
|
156
|
+
// ============================================================================
|
|
157
|
+
|
|
158
|
+
export interface EditorArgs extends HandlerArgs {
|
|
159
|
+
command?: string;
|
|
160
|
+
filename?: string;
|
|
161
|
+
resolution?: string;
|
|
162
|
+
location?: Vector3;
|
|
163
|
+
rotation?: Rotator;
|
|
164
|
+
fov?: number;
|
|
165
|
+
speed?: number;
|
|
166
|
+
viewMode?: string;
|
|
167
|
+
width?: number;
|
|
168
|
+
height?: number;
|
|
169
|
+
enabled?: boolean;
|
|
170
|
+
realtime?: boolean;
|
|
171
|
+
bookmarkName?: string;
|
|
172
|
+
assetPath?: string;
|
|
173
|
+
path?: string;
|
|
174
|
+
category?: string;
|
|
175
|
+
preferences?: Record<string, unknown>;
|
|
176
|
+
timeoutMs?: number;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ============================================================================
|
|
180
|
+
// Level Types
|
|
181
|
+
// ============================================================================
|
|
182
|
+
|
|
183
|
+
export interface LevelArgs extends HandlerArgs {
|
|
184
|
+
levelPath?: string;
|
|
185
|
+
levelName?: string;
|
|
186
|
+
levelPaths?: string[];
|
|
187
|
+
destinationPath?: string;
|
|
188
|
+
savePath?: string;
|
|
189
|
+
subLevelPath?: string;
|
|
190
|
+
parentLevel?: string;
|
|
191
|
+
parentPath?: string;
|
|
192
|
+
streamingMethod?: 'Blueprint' | 'AlwaysLoaded';
|
|
193
|
+
exportPath?: string;
|
|
194
|
+
packagePath?: string;
|
|
195
|
+
sourcePath?: string;
|
|
196
|
+
lightType?: 'Directional' | 'Point' | 'Spot' | 'Rect';
|
|
197
|
+
name?: string;
|
|
198
|
+
location?: Vector3;
|
|
199
|
+
rotation?: Rotator;
|
|
200
|
+
intensity?: number;
|
|
201
|
+
color?: number[];
|
|
202
|
+
quality?: string;
|
|
203
|
+
streaming?: boolean;
|
|
204
|
+
shouldBeLoaded?: boolean;
|
|
205
|
+
shouldBeVisible?: boolean;
|
|
206
|
+
dataLayerLabel?: string;
|
|
207
|
+
dataLayerName?: string;
|
|
208
|
+
dataLayerState?: string;
|
|
209
|
+
actorPath?: string;
|
|
210
|
+
min?: number[];
|
|
211
|
+
max?: number[];
|
|
212
|
+
origin?: number[];
|
|
213
|
+
extent?: number[];
|
|
214
|
+
metadata?: Record<string, unknown>;
|
|
215
|
+
timeoutMs?: number;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// ============================================================================
|
|
219
|
+
// Sequence Types
|
|
220
|
+
// ============================================================================
|
|
221
|
+
|
|
222
|
+
export interface SequenceArgs extends HandlerArgs {
|
|
223
|
+
path?: string;
|
|
224
|
+
name?: string;
|
|
225
|
+
actorName?: string;
|
|
226
|
+
actorNames?: string[];
|
|
227
|
+
spawnable?: boolean;
|
|
228
|
+
trackName?: string;
|
|
229
|
+
trackType?: string;
|
|
230
|
+
property?: string;
|
|
231
|
+
frame?: number;
|
|
232
|
+
value?: unknown;
|
|
233
|
+
speed?: number;
|
|
234
|
+
lengthInFrames?: number;
|
|
235
|
+
start?: number;
|
|
236
|
+
end?: number;
|
|
237
|
+
startFrame?: number;
|
|
238
|
+
endFrame?: number;
|
|
239
|
+
assetPath?: string;
|
|
240
|
+
muted?: boolean;
|
|
241
|
+
solo?: boolean;
|
|
242
|
+
locked?: boolean;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ============================================================================
|
|
246
|
+
// Effect Types
|
|
247
|
+
// ============================================================================
|
|
248
|
+
|
|
249
|
+
export interface EffectArgs extends HandlerArgs {
|
|
250
|
+
location?: Vector3;
|
|
251
|
+
rotation?: Rotator;
|
|
252
|
+
scale?: number;
|
|
253
|
+
preset?: string;
|
|
254
|
+
systemPath?: string;
|
|
255
|
+
shape?: string;
|
|
256
|
+
size?: number;
|
|
257
|
+
color?: number[];
|
|
258
|
+
name?: string;
|
|
259
|
+
emitterName?: string;
|
|
260
|
+
modulePath?: string;
|
|
261
|
+
parameterName?: string;
|
|
262
|
+
parameterType?: string;
|
|
263
|
+
type?: string;
|
|
264
|
+
filter?: string;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ============================================================================
|
|
268
|
+
// Environment Types
|
|
269
|
+
// ============================================================================
|
|
270
|
+
|
|
271
|
+
export interface EnvironmentArgs extends HandlerArgs {
|
|
272
|
+
name?: string;
|
|
273
|
+
landscapeName?: string;
|
|
274
|
+
location?: Vector3;
|
|
275
|
+
scale?: Vector3;
|
|
276
|
+
componentCount?: { x: number; y: number };
|
|
277
|
+
sectionSize?: number;
|
|
278
|
+
sectionsPerComponent?: number;
|
|
279
|
+
materialPath?: string;
|
|
280
|
+
foliageType?: string;
|
|
281
|
+
foliageTypePath?: string;
|
|
282
|
+
meshPath?: string;
|
|
283
|
+
density?: number;
|
|
284
|
+
radius?: number;
|
|
285
|
+
minScale?: number;
|
|
286
|
+
maxScale?: number;
|
|
287
|
+
alignToNormal?: boolean;
|
|
288
|
+
randomYaw?: boolean;
|
|
289
|
+
cullDistance?: number;
|
|
290
|
+
transforms?: Transform[];
|
|
291
|
+
locations?: Vector3[];
|
|
292
|
+
bounds?: { min: Vector3; max: Vector3 };
|
|
293
|
+
seed?: number;
|
|
294
|
+
heightData?: number[];
|
|
295
|
+
layerName?: string;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ============================================================================
|
|
299
|
+
// Lighting Types
|
|
300
|
+
// ============================================================================
|
|
301
|
+
|
|
302
|
+
export interface LightingArgs extends HandlerArgs {
|
|
303
|
+
lightType?: 'Directional' | 'Point' | 'Spot' | 'Rect';
|
|
304
|
+
name?: string;
|
|
305
|
+
location?: Vector3;
|
|
306
|
+
rotation?: Rotator;
|
|
307
|
+
intensity?: number;
|
|
308
|
+
color?: number[];
|
|
309
|
+
temperature?: number;
|
|
310
|
+
radius?: number;
|
|
311
|
+
falloffExponent?: number;
|
|
312
|
+
innerCone?: number;
|
|
313
|
+
outerCone?: number;
|
|
314
|
+
castShadows?: boolean;
|
|
315
|
+
method?: 'Lightmass' | 'LumenGI' | 'ScreenSpace' | 'None';
|
|
316
|
+
bounces?: number;
|
|
317
|
+
quality?: string;
|
|
318
|
+
enabled?: boolean;
|
|
319
|
+
density?: number;
|
|
320
|
+
fogHeight?: number;
|
|
321
|
+
cubemapPath?: string;
|
|
322
|
+
sourceType?: 'CapturedScene' | 'SpecifiedCubemap';
|
|
323
|
+
recapture?: boolean;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// ============================================================================
|
|
327
|
+
// Performance Types
|
|
328
|
+
// ============================================================================
|
|
329
|
+
|
|
330
|
+
export interface PerformanceArgs extends HandlerArgs {
|
|
331
|
+
type?: 'CPU' | 'GPU' | 'Memory' | 'RenderThread' | 'GameThread' | 'All';
|
|
332
|
+
category?: string;
|
|
333
|
+
duration?: number;
|
|
334
|
+
outputPath?: string;
|
|
335
|
+
level?: number;
|
|
336
|
+
scale?: number;
|
|
337
|
+
enabled?: boolean;
|
|
338
|
+
maxFPS?: number;
|
|
339
|
+
verbose?: boolean;
|
|
340
|
+
detailed?: boolean;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// ============================================================================
|
|
344
|
+
// Inspect Types
|
|
345
|
+
// ============================================================================
|
|
346
|
+
|
|
347
|
+
export interface InspectArgs extends HandlerArgs {
|
|
348
|
+
objectPath?: string;
|
|
349
|
+
name?: string;
|
|
350
|
+
actorName?: string;
|
|
351
|
+
componentName?: string;
|
|
352
|
+
propertyName?: string;
|
|
353
|
+
propertyPath?: string;
|
|
354
|
+
value?: unknown;
|
|
355
|
+
className?: string;
|
|
356
|
+
classPath?: string;
|
|
357
|
+
filter?: string;
|
|
358
|
+
tag?: string;
|
|
359
|
+
snapshotName?: string;
|
|
360
|
+
destinationPath?: string;
|
|
361
|
+
outputPath?: string;
|
|
362
|
+
format?: string;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// ============================================================================
|
|
366
|
+
// Graph Types (Blueprint, Material, Niagara, BehaviorTree)
|
|
367
|
+
// ============================================================================
|
|
368
|
+
|
|
369
|
+
export interface GraphArgs extends HandlerArgs {
|
|
370
|
+
assetPath?: string;
|
|
371
|
+
blueprintPath?: string;
|
|
372
|
+
systemPath?: string;
|
|
373
|
+
graphName?: string;
|
|
374
|
+
nodeType?: string;
|
|
375
|
+
nodeId?: string;
|
|
376
|
+
x?: number;
|
|
377
|
+
y?: number;
|
|
378
|
+
memberName?: string;
|
|
379
|
+
variableName?: string;
|
|
380
|
+
eventName?: string;
|
|
381
|
+
functionName?: string;
|
|
382
|
+
targetClass?: string;
|
|
383
|
+
memberClass?: string;
|
|
384
|
+
componentClass?: string;
|
|
385
|
+
pinName?: string;
|
|
386
|
+
linkedTo?: string;
|
|
387
|
+
fromNodeId?: string;
|
|
388
|
+
fromPinName?: string;
|
|
389
|
+
toNodeId?: string;
|
|
390
|
+
toPinName?: string;
|
|
391
|
+
parentNodeId?: string;
|
|
392
|
+
childNodeId?: string;
|
|
393
|
+
properties?: Record<string, unknown>;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// ============================================================================
|
|
397
|
+
// System Types
|
|
398
|
+
// ============================================================================
|
|
399
|
+
|
|
400
|
+
export interface SystemArgs extends HandlerArgs {
|
|
401
|
+
command?: string;
|
|
402
|
+
category?: string;
|
|
403
|
+
profileType?: string;
|
|
404
|
+
level?: number;
|
|
405
|
+
key?: string;
|
|
406
|
+
value?: string;
|
|
407
|
+
section?: string;
|
|
408
|
+
configName?: string;
|
|
409
|
+
resolution?: string;
|
|
410
|
+
enabled?: boolean;
|
|
411
|
+
widgetPath?: string;
|
|
412
|
+
parentName?: string;
|
|
413
|
+
childClass?: string;
|
|
414
|
+
target?: string;
|
|
415
|
+
platform?: string;
|
|
416
|
+
configuration?: string;
|
|
417
|
+
arguments?: string;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// ============================================================================
|
|
421
|
+
// Input Types
|
|
422
|
+
// ============================================================================
|
|
423
|
+
|
|
424
|
+
export interface InputArgs extends HandlerArgs {
|
|
425
|
+
name?: string;
|
|
426
|
+
path?: string;
|
|
427
|
+
actionPath?: string;
|
|
428
|
+
contextPath?: string;
|
|
429
|
+
key?: string;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// ============================================================================
|
|
433
|
+
// Pipeline Types
|
|
434
|
+
// ============================================================================
|
|
435
|
+
|
|
436
|
+
export interface PipelineArgs extends HandlerArgs {
|
|
437
|
+
target?: string;
|
|
438
|
+
platform?: string;
|
|
439
|
+
configuration?: string;
|
|
440
|
+
arguments?: string;
|
|
441
|
+
projectPath?: string;
|
|
442
|
+
}
|
package/src/unreal-bridge.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Logger } from './utils/logger.js';
|
|
2
2
|
import { ErrorHandler } from './utils/error-handler.js';
|
|
3
3
|
import type { AutomationBridge } from './automation/index.js';
|
|
4
|
-
import { DEFAULT_AUTOMATION_HOST, DEFAULT_AUTOMATION_PORT } from './constants.js';
|
|
4
|
+
import { DEFAULT_AUTOMATION_HOST, DEFAULT_AUTOMATION_PORT, CONSOLE_COMMAND_TIMEOUT_MS, ENGINE_QUERY_TIMEOUT_MS } from './constants.js';
|
|
5
5
|
import { UnrealCommandQueue } from './utils/unreal-command-queue.js';
|
|
6
6
|
import { CommandValidator } from './utils/command-validator.js';
|
|
7
7
|
|
|
@@ -153,7 +153,9 @@ export class UnrealBridge {
|
|
|
153
153
|
if (this.connectPromise) {
|
|
154
154
|
try {
|
|
155
155
|
await this.connectPromise;
|
|
156
|
-
} catch {
|
|
156
|
+
} catch (err) {
|
|
157
|
+
this.log.debug('Existing connect promise rejected', err instanceof Error ? err.message : String(err));
|
|
158
|
+
}
|
|
157
159
|
return this.connected;
|
|
158
160
|
}
|
|
159
161
|
|
|
@@ -483,7 +485,7 @@ export class UnrealBridge {
|
|
|
483
485
|
}
|
|
484
486
|
|
|
485
487
|
// Execute a console command safely with validation and throttling
|
|
486
|
-
async executeConsoleCommand(command: string
|
|
488
|
+
async executeConsoleCommand(command: string): Promise<any> {
|
|
487
489
|
const automationAvailable = Boolean(
|
|
488
490
|
this.automationBridge && typeof this.automationBridge.sendAutomationRequest === 'function'
|
|
489
491
|
);
|
|
@@ -517,7 +519,7 @@ export class UnrealBridge {
|
|
|
517
519
|
const pluginResp: ConsoleCommandResponse = await this.automationBridge.sendAutomationRequest(
|
|
518
520
|
'console_command',
|
|
519
521
|
{ command: cmdTrimmed },
|
|
520
|
-
{ timeoutMs:
|
|
522
|
+
{ timeoutMs: CONSOLE_COMMAND_TIMEOUT_MS }
|
|
521
523
|
);
|
|
522
524
|
|
|
523
525
|
if (pluginResp && pluginResp.success) {
|
|
@@ -601,7 +603,7 @@ export class UnrealBridge {
|
|
|
601
603
|
const resp = await bridge.sendAutomationRequest(
|
|
602
604
|
'system_control',
|
|
603
605
|
{ action: 'get_engine_version' },
|
|
604
|
-
{ timeoutMs:
|
|
606
|
+
{ timeoutMs: ENGINE_QUERY_TIMEOUT_MS }
|
|
605
607
|
);
|
|
606
608
|
const raw: EngineVersionResult = resp && typeof resp.result === 'object'
|
|
607
609
|
? (resp.result as Record<string, unknown>)
|
|
@@ -644,7 +646,7 @@ export class UnrealBridge {
|
|
|
644
646
|
const resp = await bridge.sendAutomationRequest(
|
|
645
647
|
'system_control',
|
|
646
648
|
{ action: 'get_feature_flags' },
|
|
647
|
-
{ timeoutMs:
|
|
649
|
+
{ timeoutMs: ENGINE_QUERY_TIMEOUT_MS }
|
|
648
650
|
);
|
|
649
651
|
const raw = resp && typeof resp.result === 'object'
|
|
650
652
|
? (resp.result as Record<string, unknown>)
|
|
@@ -48,6 +48,14 @@ export class CommandValidator {
|
|
|
48
48
|
/^this_is_not_a_valid/i,
|
|
49
49
|
];
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Pre-compiled patterns for dangerous commands using word boundaries.
|
|
53
|
+
* This prevents false positives like 'show exit menu' matching 'exit'.
|
|
54
|
+
*/
|
|
55
|
+
private static readonly DANGEROUS_PATTERNS = CommandValidator.DANGEROUS_COMMANDS.map(
|
|
56
|
+
cmd => new RegExp(`(?:^|\\s)${cmd.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?:\\s|$)`, 'i')
|
|
57
|
+
);
|
|
58
|
+
|
|
51
59
|
/**
|
|
52
60
|
* Validates a console command for safety before execution.
|
|
53
61
|
* @param command - The console command string to validate
|
|
@@ -73,7 +81,8 @@ export class CommandValidator {
|
|
|
73
81
|
throw new Error('Python console commands are blocked from external calls for safety.');
|
|
74
82
|
}
|
|
75
83
|
|
|
76
|
-
|
|
84
|
+
// Use word-boundary matching to avoid false positives like 'show exit menu'
|
|
85
|
+
if (this.DANGEROUS_PATTERNS.some(pattern => pattern.test(cmdLower))) {
|
|
77
86
|
throw new Error(`Dangerous command blocked: ${command}`);
|
|
78
87
|
}
|
|
79
88
|
|
|
@@ -81,9 +90,22 @@ export class CommandValidator {
|
|
|
81
90
|
throw new Error('Command chaining with && or || is blocked for safety.');
|
|
82
91
|
}
|
|
83
92
|
|
|
93
|
+
// Block semicolon and pipe which can also be used for command chaining/injection
|
|
94
|
+
if (cmdTrimmed.includes(';')) {
|
|
95
|
+
throw new Error('Command chaining with ; (semicolon) is blocked for safety.');
|
|
96
|
+
}
|
|
97
|
+
if (cmdTrimmed.includes('|')) {
|
|
98
|
+
throw new Error('Command piping with | is blocked for safety.');
|
|
99
|
+
}
|
|
100
|
+
|
|
84
101
|
if (this.FORBIDDEN_TOKENS.some(token => cmdLower.includes(token))) {
|
|
85
102
|
throw new Error(`Command contains unsafe token and was blocked: ${command}`);
|
|
86
103
|
}
|
|
104
|
+
|
|
105
|
+
// Block backticks which can be used for shell execution
|
|
106
|
+
if (cmdTrimmed.includes('`')) {
|
|
107
|
+
throw new Error('Backtick characters are blocked for safety.');
|
|
108
|
+
}
|
|
87
109
|
}
|
|
88
110
|
|
|
89
111
|
/**
|
|
@@ -240,7 +240,10 @@ export class ErrorHandler {
|
|
|
240
240
|
if (['ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'EPIPE'].includes(code)) return true;
|
|
241
241
|
if (/timeout|timed out|network|connection|closed|unavailable|busy|temporar/.test(msg)) return true;
|
|
242
242
|
if (!isNaN(status) && (status === 429 || (status >= 500 && status < 600))) return true;
|
|
243
|
-
} catch {
|
|
243
|
+
} catch (err) {
|
|
244
|
+
// Error checking retriability is uncommon; log at debug level
|
|
245
|
+
log.debug('isRetriable check failed', err instanceof Error ? err.message : String(err));
|
|
246
|
+
}
|
|
244
247
|
return false;
|
|
245
248
|
}
|
|
246
249
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import Ajv from 'ajv';
|
|
1
|
+
import Ajv, { ValidateFunction } from 'ajv';
|
|
2
2
|
import { Logger } from './logger.js';
|
|
3
3
|
import { cleanObject } from './safe-json.js';
|
|
4
4
|
import { wasmIntegration } from '../wasm/index.js';
|
|
@@ -161,16 +161,14 @@ function buildSummaryText(toolName: string, payload: unknown): string {
|
|
|
161
161
|
* Validates tool responses against their defined output schemas
|
|
162
162
|
*/
|
|
163
163
|
export class ResponseValidator {
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
private
|
|
167
|
-
private validators: Map<string, any> = new Map();
|
|
164
|
+
// Ajv instance - using Ajv.default for ESM/CJS interop
|
|
165
|
+
private ajv: Ajv.default;
|
|
166
|
+
private validators: Map<string, ValidateFunction> = new Map();
|
|
168
167
|
|
|
169
168
|
constructor() {
|
|
170
|
-
//
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
this.ajv = new AjvCtor({
|
|
169
|
+
// Ajv exports differ between ESM and CJS - handle both patterns
|
|
170
|
+
const AjvClass = (Ajv as unknown as { default: typeof Ajv.default }).default ?? Ajv.default;
|
|
171
|
+
this.ajv = new AjvClass({
|
|
174
172
|
allErrors: true,
|
|
175
173
|
verbose: true,
|
|
176
174
|
strict: true // Enforce strict schema validation
|
package/src/utils/safe-json.ts
CHANGED
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import { Logger } from './logger.js';
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
|
|
5
|
-
const seen = new WeakSet();
|
|
6
|
-
const logger = new Logger('safe-json');
|
|
3
|
+
// Module-level logger to avoid creating new instances on every call
|
|
4
|
+
const log = new Logger('safe-json');
|
|
7
5
|
|
|
8
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Remove circular references and non-serializable properties from an object.
|
|
8
|
+
* @param obj - The object to clean
|
|
9
|
+
* @param maxDepth - Maximum recursion depth (default: 10)
|
|
10
|
+
* @returns Cleaned object safe for JSON serialization
|
|
11
|
+
*/
|
|
12
|
+
export function cleanObject<T = unknown>(obj: T, maxDepth: number = 10): T {
|
|
13
|
+
const seen = new WeakSet<object>();
|
|
14
|
+
|
|
15
|
+
function clean(value: unknown, depth: number, path: string = 'root'): unknown {
|
|
9
16
|
// Prevent infinite recursion
|
|
10
17
|
if (depth > maxDepth) {
|
|
11
18
|
return '[Max depth reached]';
|
|
@@ -26,7 +33,8 @@ export function cleanObject(obj: any, maxDepth: number = 10): any {
|
|
|
26
33
|
return value;
|
|
27
34
|
}
|
|
28
35
|
|
|
29
|
-
// Check for circular reference
|
|
36
|
+
// Check for circular reference - keep in set permanently for this call
|
|
37
|
+
// This prevents the same object from appearing in multiple branches
|
|
30
38
|
if (seen.has(value)) {
|
|
31
39
|
return '[Circular Reference]';
|
|
32
40
|
}
|
|
@@ -35,31 +43,28 @@ export function cleanObject(obj: any, maxDepth: number = 10): any {
|
|
|
35
43
|
|
|
36
44
|
// Handle arrays
|
|
37
45
|
if (Array.isArray(value)) {
|
|
38
|
-
|
|
39
|
-
seen.delete(value); // Remove from seen after processing
|
|
40
|
-
return result;
|
|
46
|
+
return value.map((item, index) => clean(item, depth + 1, `${path}[${index}]`));
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
// Handle objects
|
|
44
|
-
const cleaned:
|
|
50
|
+
const cleaned: Record<string, unknown> = {};
|
|
45
51
|
|
|
46
52
|
// Use Object.keys to avoid prototype properties
|
|
47
|
-
const keys = Object.keys(value);
|
|
53
|
+
const keys = Object.keys(value as object);
|
|
48
54
|
for (const key of keys) {
|
|
49
55
|
try {
|
|
50
|
-
const cleanedValue = clean(value[key], depth + 1, `${path}.${key}`);
|
|
56
|
+
const cleanedValue = clean((value as Record<string, unknown>)[key], depth + 1, `${path}.${key}`);
|
|
51
57
|
if (cleanedValue !== undefined) {
|
|
52
58
|
cleaned[key] = cleanedValue;
|
|
53
59
|
}
|
|
54
60
|
} catch (e) {
|
|
55
61
|
// Skip properties that throw errors when accessed
|
|
56
|
-
|
|
62
|
+
log.error(`Error cleaning property ${path}.${key}`, e);
|
|
57
63
|
}
|
|
58
64
|
}
|
|
59
65
|
|
|
60
|
-
seen.delete(value); // Remove from seen after processing
|
|
61
66
|
return cleaned;
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
return clean(obj, 0);
|
|
69
|
+
return clean(obj, 0) as T;
|
|
65
70
|
}
|
|
@@ -140,11 +140,13 @@ export class UnrealCommandQueue {
|
|
|
140
140
|
}
|
|
141
141
|
|
|
142
142
|
private startProcessor(): void {
|
|
143
|
+
// Fallback processor - primary processing is triggered directly from execute()
|
|
144
|
+
// Reduced from 1000ms to 250ms for faster recovery if processor stalls
|
|
143
145
|
this.processorInterval = setInterval(() => {
|
|
144
146
|
if (!this.isProcessing && this.queue.length > 0) {
|
|
145
147
|
this.processQueue();
|
|
146
148
|
}
|
|
147
|
-
},
|
|
149
|
+
}, 250);
|
|
148
150
|
}
|
|
149
151
|
|
|
150
152
|
/**
|
|
@@ -59,9 +59,9 @@ describe('sanitizePath', () => {
|
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
it('sanitizes path segments with dots', () => {
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
expect(() => sanitizePath('/Game/../MyAsset')).toThrow(
|
|
63
|
+
'Path traversal (..) is not allowed'
|
|
64
|
+
);
|
|
65
65
|
});
|
|
66
66
|
});
|
|
67
67
|
|