unreal-engine-mcp-server 0.4.7 → 0.5.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 (438) hide show
  1. package/.env.example +26 -0
  2. package/.env.production +38 -7
  3. package/.eslintrc.json +0 -54
  4. package/.eslintrc.override.json +8 -0
  5. package/.github/ISSUE_TEMPLATE/bug_report.yml +94 -0
  6. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  7. package/.github/ISSUE_TEMPLATE/feature_request.yml +56 -0
  8. package/.github/copilot-instructions.md +478 -45
  9. package/.github/dependabot.yml +19 -0
  10. package/.github/labeler.yml +24 -0
  11. package/.github/labels.yml +70 -0
  12. package/.github/pull_request_template.md +42 -0
  13. package/.github/release-drafter.yml +148 -0
  14. package/.github/workflows/auto-merge.yml +38 -0
  15. package/.github/workflows/ci.yml +38 -0
  16. package/.github/workflows/dependency-review.yml +17 -0
  17. package/.github/workflows/gemini-issue-triage.yml +172 -0
  18. package/.github/workflows/greetings.yml +23 -0
  19. package/.github/workflows/labeler.yml +16 -0
  20. package/.github/workflows/links.yml +80 -0
  21. package/.github/workflows/pr-size-labeler.yml +137 -0
  22. package/.github/workflows/publish-mcp.yml +12 -7
  23. package/.github/workflows/release-drafter.yml +23 -0
  24. package/.github/workflows/release.yml +112 -0
  25. package/.github/workflows/semantic-pull-request.yml +35 -0
  26. package/.github/workflows/smoke-test.yml +36 -0
  27. package/.github/workflows/stale.yml +28 -0
  28. package/CHANGELOG.md +267 -31
  29. package/CONTRIBUTING.md +140 -0
  30. package/README.md +166 -71
  31. package/claude_desktop_config_example.json +7 -6
  32. package/dist/automation/bridge.d.ts +50 -0
  33. package/dist/automation/bridge.js +452 -0
  34. package/dist/automation/connection-manager.d.ts +23 -0
  35. package/dist/automation/connection-manager.js +107 -0
  36. package/dist/automation/handshake.d.ts +11 -0
  37. package/dist/automation/handshake.js +89 -0
  38. package/dist/automation/index.d.ts +3 -0
  39. package/dist/automation/index.js +3 -0
  40. package/dist/automation/message-handler.d.ts +12 -0
  41. package/dist/automation/message-handler.js +149 -0
  42. package/dist/automation/request-tracker.d.ts +25 -0
  43. package/dist/automation/request-tracker.js +98 -0
  44. package/dist/automation/types.d.ts +130 -0
  45. package/dist/automation/types.js +2 -0
  46. package/dist/cli.js +32 -5
  47. package/dist/config.d.ts +27 -0
  48. package/dist/config.js +60 -0
  49. package/dist/constants.d.ts +12 -0
  50. package/dist/constants.js +12 -0
  51. package/dist/graphql/resolvers.d.ts +268 -0
  52. package/dist/graphql/resolvers.js +743 -0
  53. package/dist/graphql/schema.d.ts +5 -0
  54. package/dist/graphql/schema.js +437 -0
  55. package/dist/graphql/server.d.ts +26 -0
  56. package/dist/graphql/server.js +115 -0
  57. package/dist/graphql/types.d.ts +7 -0
  58. package/dist/graphql/types.js +2 -0
  59. package/dist/handlers/resource-handlers.d.ts +20 -0
  60. package/dist/handlers/resource-handlers.js +180 -0
  61. package/dist/index.d.ts +31 -18
  62. package/dist/index.js +119 -619
  63. package/dist/prompts/index.js +4 -4
  64. package/dist/resources/actors.d.ts +17 -12
  65. package/dist/resources/actors.js +56 -76
  66. package/dist/resources/assets.d.ts +6 -14
  67. package/dist/resources/assets.js +115 -147
  68. package/dist/resources/levels.d.ts +13 -13
  69. package/dist/resources/levels.js +25 -34
  70. package/dist/server/resource-registry.d.ts +20 -0
  71. package/dist/server/resource-registry.js +37 -0
  72. package/dist/server/tool-registry.d.ts +23 -0
  73. package/dist/server/tool-registry.js +322 -0
  74. package/dist/server-setup.d.ts +21 -0
  75. package/dist/server-setup.js +111 -0
  76. package/dist/services/health-monitor.d.ts +34 -0
  77. package/dist/services/health-monitor.js +105 -0
  78. package/dist/services/metrics-server.d.ts +11 -0
  79. package/dist/services/metrics-server.js +105 -0
  80. package/dist/tools/actors.d.ts +147 -9
  81. package/dist/tools/actors.js +350 -311
  82. package/dist/tools/animation.d.ts +135 -4
  83. package/dist/tools/animation.js +510 -411
  84. package/dist/tools/assets.d.ts +117 -19
  85. package/dist/tools/assets.js +259 -284
  86. package/dist/tools/audio.d.ts +102 -42
  87. package/dist/tools/audio.js +272 -685
  88. package/dist/tools/base-tool.d.ts +17 -0
  89. package/dist/tools/base-tool.js +46 -0
  90. package/dist/tools/behavior-tree.d.ts +94 -0
  91. package/dist/tools/behavior-tree.js +39 -0
  92. package/dist/tools/blueprint/helpers.d.ts +29 -0
  93. package/dist/tools/blueprint/helpers.js +182 -0
  94. package/dist/tools/blueprint.d.ts +228 -118
  95. package/dist/tools/blueprint.js +685 -832
  96. package/dist/tools/consolidated-tool-definitions.d.ts +5462 -1781
  97. package/dist/tools/consolidated-tool-definitions.js +829 -496
  98. package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
  99. package/dist/tools/consolidated-tool-handlers.js +211 -1026
  100. package/dist/tools/debug.d.ts +143 -85
  101. package/dist/tools/debug.js +234 -180
  102. package/dist/tools/dynamic-handler-registry.d.ts +11 -0
  103. package/dist/tools/dynamic-handler-registry.js +101 -0
  104. package/dist/tools/editor.d.ts +139 -18
  105. package/dist/tools/editor.js +239 -244
  106. package/dist/tools/engine.d.ts +10 -4
  107. package/dist/tools/engine.js +13 -5
  108. package/dist/tools/environment.d.ts +36 -0
  109. package/dist/tools/environment.js +267 -0
  110. package/dist/tools/foliage.d.ts +105 -14
  111. package/dist/tools/foliage.js +219 -331
  112. package/dist/tools/handlers/actor-handlers.d.ts +3 -0
  113. package/dist/tools/handlers/actor-handlers.js +232 -0
  114. package/dist/tools/handlers/animation-handlers.d.ts +3 -0
  115. package/dist/tools/handlers/animation-handlers.js +185 -0
  116. package/dist/tools/handlers/argument-helper.d.ts +16 -0
  117. package/dist/tools/handlers/argument-helper.js +80 -0
  118. package/dist/tools/handlers/asset-handlers.d.ts +3 -0
  119. package/dist/tools/handlers/asset-handlers.js +496 -0
  120. package/dist/tools/handlers/audio-handlers.d.ts +3 -0
  121. package/dist/tools/handlers/audio-handlers.js +166 -0
  122. package/dist/tools/handlers/blueprint-handlers.d.ts +4 -0
  123. package/dist/tools/handlers/blueprint-handlers.js +358 -0
  124. package/dist/tools/handlers/common-handlers.d.ts +14 -0
  125. package/dist/tools/handlers/common-handlers.js +56 -0
  126. package/dist/tools/handlers/editor-handlers.d.ts +3 -0
  127. package/dist/tools/handlers/editor-handlers.js +119 -0
  128. package/dist/tools/handlers/effect-handlers.d.ts +3 -0
  129. package/dist/tools/handlers/effect-handlers.js +171 -0
  130. package/dist/tools/handlers/environment-handlers.d.ts +3 -0
  131. package/dist/tools/handlers/environment-handlers.js +170 -0
  132. package/dist/tools/handlers/graph-handlers.d.ts +3 -0
  133. package/dist/tools/handlers/graph-handlers.js +90 -0
  134. package/dist/tools/handlers/input-handlers.d.ts +3 -0
  135. package/dist/tools/handlers/input-handlers.js +21 -0
  136. package/dist/tools/handlers/inspect-handlers.d.ts +3 -0
  137. package/dist/tools/handlers/inspect-handlers.js +383 -0
  138. package/dist/tools/handlers/level-handlers.d.ts +3 -0
  139. package/dist/tools/handlers/level-handlers.js +237 -0
  140. package/dist/tools/handlers/lighting-handlers.d.ts +3 -0
  141. package/dist/tools/handlers/lighting-handlers.js +144 -0
  142. package/dist/tools/handlers/performance-handlers.d.ts +3 -0
  143. package/dist/tools/handlers/performance-handlers.js +130 -0
  144. package/dist/tools/handlers/pipeline-handlers.d.ts +3 -0
  145. package/dist/tools/handlers/pipeline-handlers.js +110 -0
  146. package/dist/tools/handlers/sequence-handlers.d.ts +3 -0
  147. package/dist/tools/handlers/sequence-handlers.js +376 -0
  148. package/dist/tools/handlers/system-handlers.d.ts +4 -0
  149. package/dist/tools/handlers/system-handlers.js +506 -0
  150. package/dist/tools/input.d.ts +19 -0
  151. package/dist/tools/input.js +89 -0
  152. package/dist/tools/introspection.d.ts +103 -40
  153. package/dist/tools/introspection.js +425 -568
  154. package/dist/tools/landscape.d.ts +97 -36
  155. package/dist/tools/landscape.js +280 -409
  156. package/dist/tools/level.d.ts +130 -10
  157. package/dist/tools/level.js +639 -675
  158. package/dist/tools/lighting.d.ts +77 -38
  159. package/dist/tools/lighting.js +441 -943
  160. package/dist/tools/logs.d.ts +3 -3
  161. package/dist/tools/logs.js +5 -57
  162. package/dist/tools/materials.d.ts +91 -24
  163. package/dist/tools/materials.js +190 -118
  164. package/dist/tools/niagara.d.ts +149 -39
  165. package/dist/tools/niagara.js +232 -182
  166. package/dist/tools/performance.d.ts +27 -12
  167. package/dist/tools/performance.js +204 -122
  168. package/dist/tools/physics.d.ts +32 -77
  169. package/dist/tools/physics.js +171 -582
  170. package/dist/tools/property-dictionary.d.ts +13 -0
  171. package/dist/tools/property-dictionary.js +82 -0
  172. package/dist/tools/sequence.d.ts +73 -48
  173. package/dist/tools/sequence.js +196 -748
  174. package/dist/tools/tool-definition-utils.d.ts +59 -0
  175. package/dist/tools/tool-definition-utils.js +35 -0
  176. package/dist/tools/ui.d.ts +66 -34
  177. package/dist/tools/ui.js +134 -214
  178. package/dist/types/env.d.ts +0 -3
  179. package/dist/types/env.js +0 -7
  180. package/dist/types/tool-interfaces.d.ts +898 -0
  181. package/dist/types/tool-interfaces.js +2 -0
  182. package/dist/types/tool-types.d.ts +183 -19
  183. package/dist/types/tool-types.js +0 -4
  184. package/dist/unreal-bridge.d.ts +24 -131
  185. package/dist/unreal-bridge.js +364 -1506
  186. package/dist/utils/command-validator.d.ts +9 -0
  187. package/dist/utils/command-validator.js +67 -0
  188. package/dist/utils/elicitation.d.ts +1 -1
  189. package/dist/utils/elicitation.js +12 -15
  190. package/dist/utils/error-handler.d.ts +2 -51
  191. package/dist/utils/error-handler.js +11 -87
  192. package/dist/utils/ini-reader.d.ts +3 -0
  193. package/dist/utils/ini-reader.js +69 -0
  194. package/dist/utils/logger.js +9 -6
  195. package/dist/utils/normalize.d.ts +3 -0
  196. package/dist/utils/normalize.js +56 -0
  197. package/dist/utils/response-factory.d.ts +7 -0
  198. package/dist/utils/response-factory.js +33 -0
  199. package/dist/utils/response-validator.d.ts +3 -24
  200. package/dist/utils/response-validator.js +130 -81
  201. package/dist/utils/result-helpers.d.ts +4 -5
  202. package/dist/utils/result-helpers.js +15 -16
  203. package/dist/utils/safe-json.js +5 -11
  204. package/dist/utils/unreal-command-queue.d.ts +24 -0
  205. package/dist/utils/unreal-command-queue.js +120 -0
  206. package/dist/utils/validation.d.ts +0 -40
  207. package/dist/utils/validation.js +1 -78
  208. package/dist/wasm/index.d.ts +70 -0
  209. package/dist/wasm/index.js +535 -0
  210. package/docs/GraphQL-API.md +888 -0
  211. package/docs/Migration-Guide-v0.5.0.md +692 -0
  212. package/docs/Roadmap.md +53 -0
  213. package/docs/WebAssembly-Integration.md +628 -0
  214. package/docs/editor-plugin-extension.md +370 -0
  215. package/docs/handler-mapping.md +242 -0
  216. package/docs/native-automation-progress.md +128 -0
  217. package/docs/testing-guide.md +423 -0
  218. package/mcp-config-example.json +6 -6
  219. package/package.json +60 -27
  220. package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +8 -0
  221. package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +64 -0
  222. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +189 -0
  223. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +22 -0
  224. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +30 -0
  225. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +1983 -0
  226. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +72 -0
  227. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +46 -0
  228. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +581 -0
  229. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +2394 -0
  230. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +300 -0
  231. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +2807 -0
  232. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +1087 -0
  233. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +488 -0
  234. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +643 -0
  235. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +31 -0
  236. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +1184 -0
  237. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +5652 -0
  238. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +152 -0
  239. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +2614 -0
  240. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +42 -0
  241. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +1237 -0
  242. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +1701 -0
  243. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +2145 -0
  244. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +954 -0
  245. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +209 -0
  246. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +41 -0
  247. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +1164 -0
  248. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +762 -0
  249. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +634 -0
  250. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +136 -0
  251. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +494 -0
  252. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +278 -0
  253. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +625 -0
  254. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +401 -0
  255. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +67 -0
  256. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +735 -0
  257. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +2634 -0
  258. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +189 -0
  259. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +917 -0
  260. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +39 -0
  261. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +2670 -0
  262. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +519 -0
  263. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +38 -0
  264. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +668 -0
  265. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +346 -0
  266. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +1330 -0
  267. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +149 -0
  268. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +783 -0
  269. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +115 -0
  270. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +796 -0
  271. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +117 -0
  272. package/scripts/check-unreal-connection.mjs +19 -0
  273. package/scripts/clean-tmp.js +23 -0
  274. package/scripts/patch-wasm.js +26 -0
  275. package/scripts/run-all-tests.mjs +131 -0
  276. package/scripts/smoke-test.ts +94 -0
  277. package/scripts/sync-mcp-plugin.js +143 -0
  278. package/scripts/test-no-plugin-alternates.mjs +113 -0
  279. package/scripts/validate-server.js +46 -0
  280. package/scripts/verify-automation-bridge.js +200 -0
  281. package/server.json +57 -21
  282. package/src/automation/bridge.ts +558 -0
  283. package/src/automation/connection-manager.ts +130 -0
  284. package/src/automation/handshake.ts +99 -0
  285. package/src/automation/index.ts +2 -0
  286. package/src/automation/message-handler.ts +167 -0
  287. package/src/automation/request-tracker.ts +123 -0
  288. package/src/automation/types.ts +107 -0
  289. package/src/cli.ts +33 -6
  290. package/src/config.ts +73 -0
  291. package/src/constants.ts +12 -0
  292. package/src/graphql/resolvers.ts +1010 -0
  293. package/src/graphql/schema.ts +452 -0
  294. package/src/graphql/server.ts +154 -0
  295. package/src/graphql/types.ts +7 -0
  296. package/src/handlers/resource-handlers.ts +186 -0
  297. package/src/index.ts +152 -663
  298. package/src/prompts/index.ts +4 -4
  299. package/src/resources/actors.ts +58 -76
  300. package/src/resources/assets.ts +147 -134
  301. package/src/resources/levels.ts +28 -33
  302. package/src/server/resource-registry.ts +47 -0
  303. package/src/server/tool-registry.ts +354 -0
  304. package/src/server-setup.ts +148 -0
  305. package/src/services/health-monitor.ts +132 -0
  306. package/src/services/metrics-server.ts +142 -0
  307. package/src/tools/actors.ts +417 -322
  308. package/src/tools/animation.ts +671 -461
  309. package/src/tools/assets.ts +353 -289
  310. package/src/tools/audio.ts +323 -766
  311. package/src/tools/base-tool.ts +52 -0
  312. package/src/tools/behavior-tree.ts +45 -0
  313. package/src/tools/blueprint/helpers.ts +189 -0
  314. package/src/tools/blueprint.ts +787 -965
  315. package/src/tools/consolidated-tool-definitions.ts +993 -515
  316. package/src/tools/consolidated-tool-handlers.ts +272 -1139
  317. package/src/tools/debug.ts +292 -187
  318. package/src/tools/dynamic-handler-registry.ts +151 -0
  319. package/src/tools/editor.ts +309 -246
  320. package/src/tools/engine.ts +14 -3
  321. package/src/tools/environment.ts +287 -0
  322. package/src/tools/foliage.ts +314 -379
  323. package/src/tools/handlers/actor-handlers.ts +271 -0
  324. package/src/tools/handlers/animation-handlers.ts +237 -0
  325. package/src/tools/handlers/argument-helper.ts +142 -0
  326. package/src/tools/handlers/asset-handlers.ts +532 -0
  327. package/src/tools/handlers/audio-handlers.ts +194 -0
  328. package/src/tools/handlers/blueprint-handlers.ts +380 -0
  329. package/src/tools/handlers/common-handlers.ts +87 -0
  330. package/src/tools/handlers/editor-handlers.ts +123 -0
  331. package/src/tools/handlers/effect-handlers.ts +220 -0
  332. package/src/tools/handlers/environment-handlers.ts +183 -0
  333. package/src/tools/handlers/graph-handlers.ts +116 -0
  334. package/src/tools/handlers/input-handlers.ts +28 -0
  335. package/src/tools/handlers/inspect-handlers.ts +450 -0
  336. package/src/tools/handlers/level-handlers.ts +252 -0
  337. package/src/tools/handlers/lighting-handlers.ts +147 -0
  338. package/src/tools/handlers/performance-handlers.ts +132 -0
  339. package/src/tools/handlers/pipeline-handlers.ts +127 -0
  340. package/src/tools/handlers/sequence-handlers.ts +415 -0
  341. package/src/tools/handlers/system-handlers.ts +564 -0
  342. package/src/tools/input.ts +101 -0
  343. package/src/tools/introspection.ts +493 -584
  344. package/src/tools/landscape.ts +394 -489
  345. package/src/tools/level.ts +752 -694
  346. package/src/tools/lighting.ts +583 -984
  347. package/src/tools/logs.ts +9 -57
  348. package/src/tools/materials.ts +231 -121
  349. package/src/tools/niagara.ts +293 -168
  350. package/src/tools/performance.ts +320 -168
  351. package/src/tools/physics.ts +268 -613
  352. package/src/tools/property-dictionary.ts +98 -0
  353. package/src/tools/sequence.ts +255 -815
  354. package/src/tools/tool-definition-utils.ts +35 -0
  355. package/src/tools/ui.ts +207 -283
  356. package/src/types/env.ts +0 -10
  357. package/src/types/tool-interfaces.ts +250 -0
  358. package/src/types/tool-types.ts +243 -21
  359. package/src/unreal-bridge.ts +460 -1550
  360. package/src/utils/command-validator.ts +75 -0
  361. package/src/utils/elicitation.ts +10 -7
  362. package/src/utils/error-handler.ts +14 -90
  363. package/src/utils/ini-reader.ts +86 -0
  364. package/src/utils/logger.ts +8 -3
  365. package/src/utils/normalize.ts +60 -0
  366. package/src/utils/response-factory.ts +39 -0
  367. package/src/utils/response-validator.ts +176 -56
  368. package/src/utils/result-helpers.ts +21 -19
  369. package/src/utils/safe-json.ts +14 -11
  370. package/src/utils/unreal-command-queue.ts +152 -0
  371. package/src/utils/validation.ts +4 -1
  372. package/src/wasm/index.ts +838 -0
  373. package/test-server.mjs +100 -0
  374. package/tests/run-unreal-tool-tests.mjs +242 -14
  375. package/tests/test-animation.mjs +44 -0
  376. package/tests/test-asset-advanced.mjs +82 -0
  377. package/tests/test-asset-errors.mjs +35 -0
  378. package/tests/test-audio.mjs +219 -0
  379. package/tests/test-automation-timeouts.mjs +98 -0
  380. package/tests/test-behavior-tree.mjs +261 -0
  381. package/tests/test-blueprint-events.mjs +35 -0
  382. package/tests/test-blueprint-graph.mjs +79 -0
  383. package/tests/test-blueprint.mjs +577 -0
  384. package/tests/test-client-mode.mjs +86 -0
  385. package/tests/test-console-command.mjs +56 -0
  386. package/tests/test-control-actor.mjs +425 -0
  387. package/tests/test-control-editor.mjs +80 -0
  388. package/tests/test-extra-tools.mjs +38 -0
  389. package/tests/test-graphql.mjs +322 -0
  390. package/tests/test-inspect.mjs +72 -0
  391. package/tests/test-landscape.mjs +60 -0
  392. package/tests/test-manage-asset.mjs +438 -0
  393. package/tests/test-manage-level.mjs +70 -0
  394. package/tests/test-materials.mjs +356 -0
  395. package/tests/test-niagara.mjs +185 -0
  396. package/tests/test-no-inline-python.mjs +122 -0
  397. package/tests/test-plugin-handshake.mjs +82 -0
  398. package/tests/test-render.mjs +33 -0
  399. package/tests/test-runner.mjs +933 -0
  400. package/tests/test-search-assets.mjs +66 -0
  401. package/tests/test-sequence.mjs +68 -0
  402. package/tests/test-system.mjs +57 -0
  403. package/tests/test-wasm.mjs +193 -0
  404. package/tests/test-world-partition.mjs +215 -0
  405. package/tsconfig.json +3 -3
  406. package/wasm/Cargo.lock +363 -0
  407. package/wasm/Cargo.toml +42 -0
  408. package/wasm/LICENSE +21 -0
  409. package/wasm/README.md +253 -0
  410. package/wasm/src/dependency_resolver.rs +377 -0
  411. package/wasm/src/lib.rs +153 -0
  412. package/wasm/src/property_parser.rs +271 -0
  413. package/wasm/src/transform_math.rs +396 -0
  414. package/wasm/tests/integration.rs +109 -0
  415. package/.github/workflows/smithery-build.yml +0 -29
  416. package/dist/tools/build_environment_advanced.d.ts +0 -65
  417. package/dist/tools/build_environment_advanced.js +0 -633
  418. package/dist/tools/rc.d.ts +0 -110
  419. package/dist/tools/rc.js +0 -437
  420. package/dist/tools/visual.d.ts +0 -40
  421. package/dist/tools/visual.js +0 -282
  422. package/dist/utils/http.d.ts +0 -6
  423. package/dist/utils/http.js +0 -151
  424. package/dist/utils/python-output.d.ts +0 -18
  425. package/dist/utils/python-output.js +0 -290
  426. package/dist/utils/python.d.ts +0 -2
  427. package/dist/utils/python.js +0 -4
  428. package/dist/utils/stdio-redirect.d.ts +0 -2
  429. package/dist/utils/stdio-redirect.js +0 -20
  430. package/docs/unreal-tool-test-cases.md +0 -574
  431. package/smithery.yaml +0 -29
  432. package/src/tools/build_environment_advanced.ts +0 -732
  433. package/src/tools/rc.ts +0 -515
  434. package/src/tools/visual.ts +0 -281
  435. package/src/utils/http.ts +0 -187
  436. package/src/utils/python-output.ts +0 -351
  437. package/src/utils/python.ts +0 -3
  438. package/src/utils/stdio-redirect.ts +0 -18
@@ -1,6 +1,7 @@
1
1
  import Ajv from 'ajv';
2
2
  import { Logger } from './logger.js';
3
3
  import { cleanObject } from './safe-json.js';
4
+ import { wasmIntegration } from '../wasm/index.js';
4
5
 
5
6
  const log = new Logger('ResponseValidator');
6
7
 
@@ -26,45 +27,117 @@ function buildSummaryText(toolName: string, payload: unknown): string {
26
27
  return `${toolName} responded`;
27
28
  }
28
29
 
30
+ // 1. Check for specific "data" or "result" wrapper
31
+ const effectivePayload: Record<string, any> = { ...(payload as object) };
32
+ if (isRecord(effectivePayload.data)) {
33
+ Object.assign(effectivePayload, effectivePayload.data);
34
+ }
35
+ if (isRecord(effectivePayload.result)) {
36
+ Object.assign(effectivePayload, effectivePayload.result);
37
+ }
38
+
29
39
  const parts: string[] = [];
40
+
41
+ // 2. Identify "List" responses (Arrays) - Prioritize showing content
42
+ const listKeys = ['actors', 'levels', 'assets', 'folders', 'blueprints', 'components', 'pawnClasses', 'foliageTypes', 'nodes', 'tracks', 'bindings', 'keys'];
43
+ for (const key of listKeys) {
44
+ if (Array.isArray(effectivePayload[key])) {
45
+ const arr = effectivePayload[key] as any[];
46
+ const names = arr.map(i => isRecord(i) ? (i.name || i.path || i.id || i.assetName || i.objectPath || i.packageName || i.nodeName || '<?>') : String(i));
47
+ const count = arr.length;
48
+ const preview = names.slice(0, 100).join(', '); // Show up to 100
49
+ const suffix = count > 100 ? `, ... (+${count - 100} more)` : '';
50
+ parts.push(`${key}: ${preview}${suffix} (Total: ${count})`);
51
+ }
52
+ }
53
+
54
+ // 3. Identify "Entity" operations (Single significant object)
55
+ if (typeof effectivePayload.actor === 'string' || isRecord(effectivePayload.actor)) {
56
+ const a = effectivePayload.actor;
57
+ const name = isRecord(a) ? (a.name || a.path) : a;
58
+ const loc = isRecord(effectivePayload.location) ? ` at [${effectivePayload.location.x},${effectivePayload.location.y},${effectivePayload.location.z}]` : '';
59
+ parts.push(`Actor: ${name}${loc}`);
60
+ }
61
+
62
+ if (typeof effectivePayload.asset === 'string' || isRecord(effectivePayload.asset)) {
63
+ const a = effectivePayload.asset;
64
+ const path = isRecord(a) ? (a.path || a.name) : a;
65
+ parts.push(`Asset: ${path}`);
66
+ }
67
+
68
+ if (typeof effectivePayload.blueprint === 'string' || isRecord(effectivePayload.blueprint)) {
69
+ const bp = effectivePayload.blueprint;
70
+ const name = isRecord(bp) ? (bp.name || bp.path || effectivePayload.blueprintPath) : bp;
71
+ parts.push(`Blueprint: ${name}`);
72
+ }
73
+
74
+ if (typeof effectivePayload.sequence === 'string' || isRecord(effectivePayload.sequence)) {
75
+ const seq = effectivePayload.sequence;
76
+ const name = isRecord(seq) ? (seq.name || seq.path) : seq;
77
+ parts.push(`Sequence: ${name}`);
78
+ }
79
+
80
+ // 4. Generic Key-Value Summary (Contextual)
81
+ // Added: sequencePath, graphName, nodeName, variableName, memberName, scriptName, etc.
82
+ const usefulKeys = [
83
+ 'success', 'error', 'message', 'assets', 'folders', 'count', 'totalCount',
84
+ 'saved', 'valid', 'issues', 'class', 'skeleton', 'parent',
85
+ 'package', 'dependencies', 'graph', 'tags', 'metadata', 'properties'
86
+ ];
87
+
88
+ for (const key of usefulKeys) {
89
+ if (effectivePayload[key] !== undefined && effectivePayload[key] !== null) {
90
+ const val = effectivePayload[key];
91
+ // Special handling for objects like metadata
92
+ if (typeof val === 'object') {
93
+ if (key === 'metadata' || key === 'properties' || key === 'tags') {
94
+ const entries = Object.entries(val as object);
95
+ // Format as "Key=Value", skip generic types if possible, or just show raw
96
+ const formatted = entries.map(([k, v]) => `${k}=${v}`);
97
+ const limit = 50; // Show more items as requested
98
+ parts.push(`${key}: { ${formatted.slice(0, limit).join(', ')}${formatted.length > limit ? '...' : ''} }`);
99
+ continue;
100
+ }
101
+ // Try to find a name if it's an object
102
+ // Skip complex objects unless handled above
103
+ continue;
104
+ }
105
+
106
+ const strVal = String(val);
107
+ // Avoid traversing huge strings
108
+ if (strVal.length > 100) continue;
109
+
110
+ if (!parts.some(p => p.includes(strVal))) {
111
+ parts.push(`${key}: ${strVal}`);
112
+ }
113
+ }
114
+ }
115
+
116
+ // 5. Add standard status messages LAST
117
+ const success = typeof payload.success === 'boolean' ? payload.success : undefined;
30
118
  const message = typeof payload.message === 'string' ? normalizeText(payload.message) : '';
31
119
  const error = typeof payload.error === 'string' ? normalizeText(payload.error) : '';
32
- const success = typeof payload.success === 'boolean' ? (payload.success ? 'success' : 'failed') : '';
33
- const path = typeof payload.path === 'string' ? payload.path : '';
34
- const name = typeof payload.name === 'string' ? payload.name : '';
35
- const warningCount = Array.isArray(payload.warnings) ? payload.warnings.length : 0;
36
-
37
- if (message) parts.push(message);
38
- if (error && (!message || !message.includes(error))) parts.push(`error: ${error}`);
39
- if (success) parts.push(success);
40
- if (path) parts.push(`path: ${path}`);
41
- if (name) parts.push(`name: ${name}`);
42
- if (warningCount > 0) parts.push(`warnings: ${warningCount}`);
43
-
44
- const summary = isRecord(payload.summary) ? payload.summary : undefined;
45
- if (summary) {
46
- const summaryParts: string[] = [];
47
- for (const [key, value] of Object.entries(summary)) {
48
- if (typeof value === 'number' || typeof value === 'string') {
49
- summaryParts.push(`${key}: ${value}`);
50
- }
51
- if (summaryParts.length >= 3) break;
120
+
121
+ if (parts.length > 0) {
122
+ if (message && message.toLowerCase() !== 'success') {
123
+ parts.push(message);
52
124
  }
53
- if (summaryParts.length) {
54
- parts.push(`summary(${summaryParts.join(', ')})`);
125
+ } else {
126
+ // No data parts, rely on message/error
127
+ if (message) parts.push(message);
128
+ if (error) parts.push(`Error: ${error}`);
129
+ if (parts.length === 0 && success !== undefined) {
130
+ parts.push(success ? 'Success' : 'Failed');
55
131
  }
56
132
  }
57
133
 
58
- if (parts.length === 0) {
59
- const keys = Object.keys(payload).slice(0, 3);
60
- if (keys.length) {
61
- return `${toolName} responded (${keys.join(', ')})`;
62
- }
134
+ // 6. Warnings
135
+ const warnings = Array.isArray(payload.warnings) ? payload.warnings : [];
136
+ if (warnings.length > 0) {
137
+ parts.push(`Warnings: ${warnings.length}`);
63
138
  }
64
139
 
65
- return parts.length > 0
66
- ? parts.join(' | ')
67
- : `${toolName} responded`;
140
+ return parts.length > 0 ? parts.join(' | ') : `${toolName} responded`;
68
141
  }
69
142
 
70
143
  /**
@@ -84,7 +157,7 @@ export class ResponseValidator {
84
157
  this.ajv = new AjvCtor({
85
158
  allErrors: true,
86
159
  verbose: true,
87
- strict: false // Allow additional properties for flexibility
160
+ strict: true // Enforce strict schema validation
88
161
  });
89
162
  }
90
163
 
@@ -110,13 +183,13 @@ export class ResponseValidator {
110
183
  /**
111
184
  * Validate a tool's response against its schema
112
185
  */
113
- validateResponse(toolName: string, response: any): {
114
- valid: boolean;
186
+ async validateResponse(toolName: string, response: any): Promise<{
187
+ valid: boolean;
115
188
  errors?: string[];
116
189
  structuredContent?: any;
117
- } {
190
+ }> {
118
191
  const validator = this.validators.get(toolName);
119
-
192
+
120
193
  if (!validator) {
121
194
  log.debug(`No validator found for tool: ${toolName}`);
122
195
  return { valid: true }; // Pass through if no schema defined
@@ -124,41 +197,52 @@ export class ResponseValidator {
124
197
 
125
198
  // Extract structured content from response
126
199
  let structuredContent = response;
127
-
200
+
128
201
  // If response has MCP format with content array
129
202
  if (response.content && Array.isArray(response.content)) {
130
203
  // Try to extract structured data from text content
131
204
  const textContent = response.content.find((c: any) => c.type === 'text');
132
205
  if (textContent?.text) {
133
- try {
134
- // Check if text is JSON
135
- structuredContent = JSON.parse(textContent.text);
136
- } catch {
137
- // Not JSON, use the full response
206
+ const rawText = String(textContent.text);
207
+ const trimmed = rawText.trim();
208
+ const looksLikeJson = trimmed.startsWith('{') || trimmed.startsWith('[');
209
+
210
+ if (looksLikeJson) {
211
+ try {
212
+ // Check if text is JSON - use WASM for high-performance parsing (5-8x faster)
213
+ structuredContent = await wasmIntegration.parseProperties(rawText);
214
+ } catch {
215
+ // If JSON parsing fails, fall back to using the full response without
216
+ // emitting noisy parse errors for plain-text messages.
217
+ structuredContent = response;
218
+ }
219
+ } else {
220
+ // Plain-text summary or error message; keep the original structured
221
+ // response object instead of attempting JSON parsing.
138
222
  structuredContent = response;
139
223
  }
140
224
  }
141
225
  }
142
226
 
143
227
  const valid = validator(structuredContent);
144
-
228
+
145
229
  if (!valid) {
146
- const errors = validator.errors?.map((err: any) =>
230
+ const errors = validator.errors?.map((err: any) =>
147
231
  `${err.instancePath || 'root'}: ${err.message}`
148
232
  );
149
-
233
+
150
234
  log.warn(`Response validation failed for ${toolName}:`, errors);
151
-
152
- return {
153
- valid: false,
235
+
236
+ return {
237
+ valid: false,
154
238
  errors,
155
- structuredContent
239
+ structuredContent
156
240
  };
157
241
  }
158
242
 
159
- return {
243
+ return {
160
244
  valid: true,
161
- structuredContent
245
+ structuredContent
162
246
  };
163
247
  }
164
248
 
@@ -170,7 +254,7 @@ export class ResponseValidator {
170
254
  * This wrapper serializes such objects into a single text block while keeping
171
255
  * existing `content` responses intact.
172
256
  */
173
- wrapResponse(toolName: string, response: any): any {
257
+ async wrapResponse(toolName: string, response: any): Promise<any> {
174
258
  // Ensure response is safe to serialize first
175
259
  try {
176
260
  if (response && typeof response === 'object') {
@@ -186,7 +270,7 @@ export class ResponseValidator {
186
270
 
187
271
  // Choose the payload to validate: if already MCP-shaped, validate the
188
272
  // structured content extracted from text; otherwise validate the object directly.
189
- const validation = this.validateResponse(toolName, response);
273
+ const validation = await this.validateResponse(toolName, response);
190
274
  const structuredPayload = validation.structuredContent;
191
275
 
192
276
  if (!validation.valid) {
@@ -195,17 +279,25 @@ export class ResponseValidator {
195
279
 
196
280
  // If it's already MCP-shaped, return as-is (optionally append validation meta)
197
281
  if (alreadyMcpShaped) {
198
- if (structuredPayload !== undefined && response && typeof response === 'object' && response.structuredContent === undefined) {
282
+ if (structuredPayload !== undefined && response && typeof response === 'object' && (response as any).structuredContent === undefined) {
199
283
  try {
200
284
  (response as any).structuredContent = structuredPayload && typeof structuredPayload === 'object'
201
285
  ? cleanObject(structuredPayload)
202
286
  : structuredPayload;
203
- } catch {}
287
+ } catch { }
204
288
  }
289
+ // Promote failure semantics to top-level isError when obvious
290
+ try {
291
+ const sc: any = (response as any).structuredContent || structuredPayload || {};
292
+ const hasExplicitFailure = (typeof sc.success === 'boolean' && sc.success === false) || (typeof sc.error === 'string' && sc.error.length > 0);
293
+ if (hasExplicitFailure && (response as any).isError !== true) {
294
+ (response as any).isError = true;
295
+ }
296
+ } catch { }
205
297
  if (!validation.valid) {
206
298
  try {
207
299
  (response as any)._validation = { valid: false, errors: validation.errors };
208
- } catch {}
300
+ } catch { }
209
301
  }
210
302
  return response;
211
303
  }
@@ -223,6 +315,16 @@ export class ResponseValidator {
223
315
  ]
224
316
  } as any;
225
317
 
318
+ // Surface a top-level success flag when available so clients and test
319
+ // harnesses do not have to infer success from the absence of isError.
320
+ try {
321
+ if (structuredPayload && typeof (structuredPayload as any).success === 'boolean') {
322
+ (wrapped as any).success = Boolean((structuredPayload as any).success);
323
+ } else if (response && typeof (response as any).success === 'boolean') {
324
+ (wrapped as any).success = Boolean((response as any).success);
325
+ }
326
+ } catch { }
327
+
226
328
  if (structuredPayload !== undefined) {
227
329
  try {
228
330
  wrapped.structuredContent = structuredPayload && typeof structuredPayload === 'object'
@@ -239,10 +341,28 @@ export class ResponseValidator {
239
341
  }
240
342
  }
241
343
 
344
+ // Promote failure semantics to top-level isError when obvious
345
+ try {
346
+ const sc: any = wrapped.structuredContent || {};
347
+ const hasExplicitFailure = (typeof sc.success === 'boolean' && sc.success === false) || (typeof sc.error === 'string' && sc.error.length > 0);
348
+ if (hasExplicitFailure) {
349
+ wrapped.isError = true;
350
+ }
351
+ } catch { }
352
+
242
353
  if (!validation.valid) {
243
354
  wrapped._validation = { valid: false, errors: validation.errors };
244
355
  }
245
356
 
357
+ // Mark explicit error when success is false to avoid false positives in
358
+ // clients that check only for the absence of isError.
359
+ try {
360
+ const s = (wrapped as any).success;
361
+ if (typeof s === 'boolean' && s === false) {
362
+ (wrapped as any).isError = true;
363
+ }
364
+ } catch { }
365
+
246
366
  return wrapped;
247
367
  }
248
368
 
@@ -258,4 +378,4 @@ export class ResponseValidator {
258
378
  }
259
379
 
260
380
  // Singleton instance
261
- export const responseValidator = new ResponseValidator();
381
+ export const responseValidator = new ResponseValidator();
@@ -1,26 +1,27 @@
1
- import { parseStandardResult, StandardResultPayload, stripTaggedResultLines } from './python-output.js';
2
-
1
+ // Generic result interpretation for automation bridge responses
3
2
  export interface InterpretedStandardResult {
4
3
  success: boolean;
5
4
  message: string;
6
5
  error?: string;
7
6
  warnings?: string[];
8
7
  details?: string[];
9
- payload: StandardResultPayload & Record<string, unknown>;
8
+ payload: Record<string, unknown>;
10
9
  cleanText?: string;
11
10
  rawText: string;
12
11
  raw: unknown;
13
12
  }
14
13
 
14
+ /** Interprets automation bridge responses into a standard format */
15
15
  export function interpretStandardResult(
16
16
  response: unknown,
17
17
  defaults: { successMessage: string; failureMessage: string }
18
18
  ): InterpretedStandardResult {
19
- const parsed = parseStandardResult(response);
20
- const payload = (parsed.data ?? {}) as StandardResultPayload & Record<string, unknown>;
19
+ // Handle automation bridge response format
20
+ const payload = (response && typeof response === 'object' ? response : {}) as Record<string, unknown>;
21
21
  const success = payload.success === true;
22
- const rawText = typeof parsed.text === 'string' ? parsed.text : String(parsed.text ?? '');
23
- const cleanedText = cleanResultText(rawText, { fallback: undefined });
22
+ const rawText = typeof payload.message === 'string' ? payload.message :
23
+ typeof payload.output === 'string' ? payload.output :
24
+ String(payload.result ?? '');
24
25
 
25
26
  const messageFromPayload = typeof payload.message === 'string' ? payload.message.trim() : '';
26
27
  const errorFromPayload = typeof payload.error === 'string' ? payload.error.trim() : '';
@@ -35,32 +36,33 @@ export function interpretStandardResult(
35
36
  warnings: coerceStringArray(payload.warnings),
36
37
  details: coerceStringArray(payload.details),
37
38
  payload,
38
- cleanText: cleanedText,
39
+ cleanText: rawText || undefined,
39
40
  rawText,
40
- raw: parsed.raw
41
+ raw: response
41
42
  };
42
43
  }
43
44
 
45
+ /** Cleans result text by removing tags */
44
46
  export function cleanResultText(
45
47
  text: string | undefined,
46
- options: { tag?: string; fallback?: string } = {}
48
+ options: { tag?: string; defaultValue?: string } = {}
47
49
  ): string | undefined {
48
- const { tag = 'RESULT:', fallback } = options;
50
+ const { defaultValue } = options;
49
51
  if (!text) {
50
- return fallback;
52
+ return defaultValue;
51
53
  }
52
54
 
53
- const cleaned = stripTaggedResultLines(text, tag).trim();
55
+ const cleaned = text.trim();
54
56
  if (cleaned.length > 0) {
55
57
  return cleaned;
56
58
  }
57
59
 
58
- return fallback;
60
+ return defaultValue;
59
61
  }
60
62
 
61
63
  export function bestEffortInterpretedText(
62
64
  interpreted: Pick<InterpretedStandardResult, 'cleanText' | 'rawText'>,
63
- fallback?: string
65
+ defaultValue?: string
64
66
  ): string | undefined {
65
67
  const cleaned = interpreted.cleanText?.trim();
66
68
  if (cleaned) {
@@ -68,11 +70,11 @@ export function bestEffortInterpretedText(
68
70
  }
69
71
 
70
72
  const raw = interpreted.rawText?.trim?.();
71
- if (raw && !raw.startsWith('RESULT:')) {
73
+ if (raw) {
72
74
  return raw;
73
75
  }
74
76
 
75
- return fallback;
77
+ return defaultValue;
76
78
  }
77
79
 
78
80
  export function coerceString(value: unknown): string | undefined {
@@ -103,7 +105,7 @@ export function coerceStringArray(value: unknown): string[] | undefined {
103
105
  return items.length > 0 ? items : undefined;
104
106
  }
105
107
 
106
- export function coerceBoolean(value: unknown, fallback?: boolean): boolean | undefined {
108
+ export function coerceBoolean(value: unknown, defaultValue?: boolean): boolean | undefined {
107
109
  if (typeof value === 'boolean') {
108
110
  return value;
109
111
  }
@@ -127,7 +129,7 @@ export function coerceBoolean(value: unknown, fallback?: boolean): boolean | und
127
129
  }
128
130
  }
129
131
 
130
- return fallback;
132
+ return defaultValue;
131
133
  }
132
134
 
133
135
  export function coerceNumber(value: unknown): number | undefined {
@@ -1,18 +1,21 @@
1
+ import { Logger } from './logger.js';
2
+
1
3
  // Remove circular references and non-serializable properties from an object
2
4
  export function cleanObject(obj: any, maxDepth: number = 10): any {
3
5
  const seen = new WeakSet();
4
-
6
+ const logger = new Logger('safe-json');
7
+
5
8
  function clean(value: any, depth: number, path: string = 'root'): any {
6
9
  // Prevent infinite recursion
7
10
  if (depth > maxDepth) {
8
11
  return '[Max depth reached]';
9
12
  }
10
-
13
+
11
14
  // Handle primitives
12
15
  if (value === null || value === undefined) {
13
16
  return value;
14
17
  }
15
-
18
+
16
19
  if (typeof value !== 'object') {
17
20
  if (typeof value === 'function' || typeof value === 'symbol') {
18
21
  return undefined;
@@ -22,24 +25,24 @@ export function cleanObject(obj: any, maxDepth: number = 10): any {
22
25
  }
23
26
  return value;
24
27
  }
25
-
28
+
26
29
  // Check for circular reference
27
30
  if (seen.has(value)) {
28
31
  return '[Circular Reference]';
29
32
  }
30
-
33
+
31
34
  seen.add(value);
32
-
35
+
33
36
  // Handle arrays
34
37
  if (Array.isArray(value)) {
35
38
  const result = value.map((item, index) => clean(item, depth + 1, `${path}[${index}]`));
36
39
  seen.delete(value); // Remove from seen after processing
37
40
  return result;
38
41
  }
39
-
42
+
40
43
  // Handle objects
41
44
  const cleaned: any = {};
42
-
45
+
43
46
  // Use Object.keys to avoid prototype properties
44
47
  const keys = Object.keys(value);
45
48
  for (const key of keys) {
@@ -50,13 +53,13 @@ export function cleanObject(obj: any, maxDepth: number = 10): any {
50
53
  }
51
54
  } catch (e) {
52
55
  // Skip properties that throw errors when accessed
53
- console.error(`Error cleaning property ${path}.${key}:`, e);
56
+ logger.error(`Error cleaning property ${path}.${key}`, e);
54
57
  }
55
58
  }
56
-
59
+
57
60
  seen.delete(value); // Remove from seen after processing
58
61
  return cleaned;
59
62
  }
60
-
63
+
61
64
  return clean(obj, 0);
62
65
  }
@@ -0,0 +1,152 @@
1
+ import { Logger } from './logger.js';
2
+
3
+ export interface CommandQueueItem<T = any> {
4
+ command: () => Promise<T>;
5
+ resolve: (value: T) => void;
6
+ reject: (reason?: any) => void;
7
+ priority: number;
8
+ retryCount?: number;
9
+ }
10
+
11
+ export class UnrealCommandQueue {
12
+ private log = new Logger('UnrealCommandQueue');
13
+ private queue: CommandQueueItem[] = [];
14
+ private isProcessing = false;
15
+ private lastCommandTime = 0;
16
+ private lastStatCommandTime = 0;
17
+
18
+ // Config
19
+ private readonly MIN_COMMAND_DELAY = 100;
20
+ private readonly MAX_COMMAND_DELAY = 500;
21
+ private readonly STAT_COMMAND_DELAY = 300;
22
+
23
+ constructor() {
24
+ this.startProcessor();
25
+ }
26
+
27
+ /**
28
+ * Execute a command with priority-based throttling
29
+ */
30
+ async execute<T>(command: () => Promise<T>, priority: number = 5): Promise<T> {
31
+ return new Promise((resolve, reject) => {
32
+ this.queue.push({
33
+ command,
34
+ resolve,
35
+ reject,
36
+ priority
37
+ });
38
+
39
+ // Sort by priority (lower number = higher priority)
40
+ this.queue.sort((a, b) => a.priority - b.priority);
41
+
42
+ // Process queue if not already processing
43
+ if (!this.isProcessing) {
44
+ this.processQueue();
45
+ }
46
+ });
47
+ }
48
+
49
+ private async processQueue(): Promise<void> {
50
+ if (this.isProcessing || this.queue.length === 0) {
51
+ return;
52
+ }
53
+
54
+ this.isProcessing = true;
55
+
56
+ while (this.queue.length > 0) {
57
+ const item = this.queue.shift();
58
+ if (!item) continue;
59
+
60
+ // Calculate delay based on time since last command
61
+ const timeSinceLastCommand = Date.now() - this.lastCommandTime;
62
+ const requiredDelay = this.calculateDelay(item.priority);
63
+
64
+ if (timeSinceLastCommand < requiredDelay) {
65
+ await this.delay(requiredDelay - timeSinceLastCommand);
66
+ }
67
+
68
+ try {
69
+ const result = await item.command();
70
+ item.resolve(result);
71
+ } catch (error: any) {
72
+ // Enhanced retry policy
73
+ const msgRaw = error?.message ?? String(error);
74
+ const msg = String(msgRaw).toLowerCase();
75
+ if (item.retryCount === undefined) item.retryCount = 0;
76
+
77
+ const isTransient = (
78
+ msg.includes('timeout') ||
79
+ msg.includes('timed out') ||
80
+ msg.includes('connect') ||
81
+ msg.includes('econnrefused') ||
82
+ msg.includes('econnreset') ||
83
+ msg.includes('broken pipe') ||
84
+ msg.includes('automation bridge') ||
85
+ msg.includes('not connected')
86
+ );
87
+
88
+ const isDeterministicFailure = (
89
+ msg.includes('command not executed') ||
90
+ msg.includes('exec_failed') ||
91
+ msg.includes('invalid command') ||
92
+ msg.includes('invalid argument') ||
93
+ msg.includes('unknown_plugin_action') ||
94
+ msg.includes('unknown action')
95
+ );
96
+
97
+ if (isTransient && item.retryCount < 3) {
98
+ item.retryCount++;
99
+ this.log.warn(`Command failed (transient), retrying (${item.retryCount}/3)`);
100
+ this.queue.unshift({
101
+ command: item.command,
102
+ resolve: item.resolve,
103
+ reject: item.reject,
104
+ priority: Math.max(1, item.priority - 1),
105
+ retryCount: item.retryCount
106
+ });
107
+ await this.delay(500);
108
+ } else {
109
+ if (isDeterministicFailure) {
110
+ this.log.warn(`Command failed (non-retryable): ${msgRaw}`);
111
+ }
112
+ item.reject(error);
113
+ }
114
+ }
115
+
116
+ this.lastCommandTime = Date.now();
117
+ }
118
+
119
+ this.isProcessing = false;
120
+ }
121
+
122
+ private calculateDelay(priority: number): number {
123
+ if (priority <= 3) {
124
+ return this.MAX_COMMAND_DELAY;
125
+ } else if (priority <= 6) {
126
+ return 200;
127
+ } else if (priority === 8) {
128
+ const timeSinceLastStat = Date.now() - this.lastStatCommandTime;
129
+ if (timeSinceLastStat < this.STAT_COMMAND_DELAY) {
130
+ return this.STAT_COMMAND_DELAY;
131
+ }
132
+ this.lastStatCommandTime = Date.now();
133
+ return 150;
134
+ } else {
135
+ const baseDelay = this.MIN_COMMAND_DELAY;
136
+ const jitter = Math.random() * 50;
137
+ return baseDelay + jitter;
138
+ }
139
+ }
140
+
141
+ private startProcessor(): void {
142
+ setInterval(() => {
143
+ if (!this.isProcessing && this.queue.length > 0) {
144
+ this.processQueue();
145
+ }
146
+ }, 1000);
147
+ }
148
+
149
+ private delay(ms: number): Promise<void> {
150
+ return new Promise(resolve => setTimeout(resolve, ms));
151
+ }
152
+ }