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,29 +1,31 @@
1
- import { UnrealBridge } from '../unreal-bridge.js';
1
+ import { BaseTool } from './base-tool.js';
2
+ import { IEditorTools } from '../types/tool-interfaces.js';
2
3
  import { toVec3Object, toRotObject } from '../utils/normalize.js';
3
- import { bestEffortInterpretedText, coerceString, interpretStandardResult } from '../utils/result-helpers.js';
4
+ import { DEFAULT_SCREENSHOT_RESOLUTION } from '../constants.js';
5
+
6
+ export class EditorTools extends BaseTool implements IEditorTools {
7
+ private cameraBookmarks = new Map<string, { location: [number, number, number]; rotation: [number, number, number]; savedAt: number }>();
8
+ private editorPreferences = new Map<string, Record<string, unknown>>();
9
+ private activeRecording?: { name?: string; options?: Record<string, unknown>; startedAt: number };
4
10
 
5
- export class EditorTools {
6
- constructor(private bridge: UnrealBridge) {}
7
-
8
11
  async isInPIE(): Promise<boolean> {
9
12
  try {
10
- const pythonCmd = `
11
- import unreal
12
- les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
13
- if les:
14
- print("PIE_STATE:" + str(les.is_in_play_in_editor()))
15
- else:
16
- print("PIE_STATE:False")
17
- `.trim();
18
-
19
- const resp: any = await this.bridge.executePython(pythonCmd);
20
- const out = typeof resp === 'string' ? resp : JSON.stringify(resp);
21
- return out.includes('PIE_STATE:True');
13
+ const response = await this.sendAutomationRequest(
14
+ 'check_pie_state',
15
+ {},
16
+ { timeoutMs: 5000 }
17
+ );
18
+
19
+ if (response && response.success !== false) {
20
+ return response.isInPIE === true || response.result?.isInPIE === true;
21
+ }
22
+
23
+ return false;
22
24
  } catch {
23
25
  return false;
24
26
  }
25
27
  }
26
-
28
+
27
29
  async ensureNotInPIE(): Promise<void> {
28
30
  if (await this.isInPIE()) {
29
31
  await this.stopPlayInEditor();
@@ -32,70 +34,29 @@ else:
32
34
  }
33
35
  }
34
36
 
35
- async playInEditor() {
37
+ async playInEditor(timeoutMs: number = 30000) {
36
38
  try {
37
- // Set tick rate to match UI play (60 fps for game mode)
38
- await this.bridge.executeConsoleCommand('t.MaxFPS 60');
39
-
40
- // Try Python first using the modern LevelEditorSubsystem
41
39
  try {
42
- // Use LevelEditorSubsystem to play in the selected viewport (modern API)
43
- const pythonCmd = `
44
- import unreal, time, json
45
- # Start PIE using LevelEditorSubsystem (modern approach)
46
- les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
47
- if les:
48
- # Store initial state
49
- was_playing = les.is_in_play_in_editor()
50
-
51
- # Request PIE in the current viewport
52
- les.editor_play_simulate()
53
-
54
- # Wait for PIE to start with multiple checks
55
- max_attempts = 10
56
- for i in range(max_attempts):
57
- time.sleep(0.2) # Wait 200ms between checks
58
- is_playing = les.is_in_play_in_editor()
59
- if is_playing and not was_playing:
60
- # PIE has started
61
- print('RESULT:' + json.dumps({'success': True, 'method': 'LevelEditorSubsystem'}))
62
- break
63
- else:
64
- # If we've waited 2 seconds total and PIE hasn't started,
65
- # but the command was sent, assume it will start
66
- print('RESULT:' + json.dumps({'success': True, 'method': 'LevelEditorSubsystem'}))
67
- else:
68
- # If subsystem not available, report error
69
- print('RESULT:' + json.dumps({'success': False, 'error': 'LevelEditorSubsystem not available'}))
70
- `.trim();
71
-
72
- const resp: any = await this.bridge.executePython(pythonCmd);
73
- const interpreted = interpretStandardResult(resp, {
74
- successMessage: 'PIE started',
75
- failureMessage: 'Failed to start PIE'
76
- });
77
- if (interpreted.success) {
78
- const method = coerceString(interpreted.payload.method) ?? 'LevelEditorSubsystem';
79
- return { success: true, message: `PIE started (via ${method})` };
40
+ const response = await this.sendAutomationRequest(
41
+ 'control_editor',
42
+ { action: 'play' },
43
+ { timeoutMs }
44
+ );
45
+ if (response && response.success === true) {
46
+ return { success: true, message: response.message || 'PIE started' };
80
47
  }
81
- // If not verified, fall through to fallback
82
- } catch (err) {
83
- // Log the error for debugging but continue
84
- console.error('Python PIE start issue:', err);
48
+ return { success: false, error: response?.error || response?.message || 'Failed to start PIE' };
49
+ } catch (err: any) {
50
+ // If it's a timeout, return error instead of falling back
51
+ if (err.message && /time.*out/i.test(err.message)) {
52
+ return { success: false, error: `Timeout waiting for PIE to start: ${err.message}` };
53
+ }
54
+
55
+ // Fallback to console commands if automation bridge is unavailable or fails (non-timeout)
56
+ await this.bridge.executeConsoleCommand('t.MaxFPS 60');
57
+ await this.bridge.executeConsoleCommand('PlayInViewport');
58
+ return { success: true, message: 'PIE start command sent' };
85
59
  }
86
- // Fallback to console command which is more reliable
87
- await this.bridge.executeConsoleCommand('PlayInViewport');
88
-
89
- // Wait a moment and verify PIE started
90
- await new Promise(resolve => setTimeout(resolve, 1000));
91
-
92
- // Check if PIE is now active
93
- const isPlaying = await this.isInPIE();
94
-
95
- return {
96
- success: true,
97
- message: isPlaying ? 'PIE started successfully' : 'PIE start command sent (may take a moment)'
98
- };
99
60
  } catch (err) {
100
61
  return { success: false, error: `Failed to start PIE: ${err}` };
101
62
  }
@@ -103,37 +64,26 @@ else:
103
64
 
104
65
  async stopPlayInEditor() {
105
66
  try {
106
- // Try Python first using the modern LevelEditorSubsystem
107
67
  try {
108
- const pythonCmd = `
109
- import unreal, time, json
110
- les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
111
- if les:
112
- # Use correct method name for stopping PIE
113
- les.editor_request_end_play() # Modern API method
114
- print('RESULT:' + json.dumps({'success': True, 'method': 'LevelEditorSubsystem'}))
115
- else:
116
- # If subsystem not available, report error
117
- print('RESULT:' + json.dumps({'success': False, 'error': 'LevelEditorSubsystem not available'}))
118
- `.trim();
119
- const resp: any = await this.bridge.executePython(pythonCmd);
120
- const interpreted = interpretStandardResult(resp, {
121
- successMessage: 'PIE stopped successfully',
122
- failureMessage: 'Failed to stop PIE'
123
- });
124
-
125
- if (interpreted.success) {
126
- const method = coerceString(interpreted.payload.method) ?? 'LevelEditorSubsystem';
127
- return { success: true, message: `PIE stopped via ${method}` };
128
- }
68
+ const response = await this.sendAutomationRequest(
69
+ 'control_editor',
70
+ { action: 'stop' },
71
+ { timeoutMs: 30000 }
72
+ );
129
73
 
130
- if (interpreted.error) {
131
- return { success: false, error: interpreted.error };
74
+ if (response.success !== false) {
75
+ return {
76
+ success: true,
77
+ message: response.message || 'PIE stopped successfully'
78
+ };
132
79
  }
133
80
 
134
- return { success: false, error: 'Failed to stop PIE' };
135
- } catch {
136
- // Fallback to console command
81
+ return {
82
+ success: false,
83
+ error: response.error || response.message || 'Failed to stop PIE'
84
+ };
85
+ } catch (_pluginErr) {
86
+ // Fallback to console command if plugin fails
137
87
  await this.bridge.executeConsoleCommand('stop');
138
88
  return { success: true, message: 'PIE stopped via console command' };
139
89
  }
@@ -141,26 +91,17 @@ else:
141
91
  return { success: false, error: `Failed to stop PIE: ${err}` };
142
92
  }
143
93
  }
144
-
94
+
145
95
  async pausePlayInEditor() {
146
96
  try {
147
97
  // Pause/Resume PIE
148
- await this.bridge.httpCall('/remote/object/call', 'PUT', {
149
- objectPath: '/Script/Engine.Default__KismetSystemLibrary',
150
- functionName: 'ExecuteConsoleCommand',
151
- parameters: {
152
- WorldContextObject: null,
153
- Command: 'pause',
154
- SpecificPlayer: null
155
- },
156
- generateTransaction: false
157
- });
98
+ await this.bridge.executeConsoleCommand('pause');
158
99
  return { success: true, message: 'PIE paused/resumed' };
159
100
  } catch (err) {
160
101
  return { success: false, error: `Failed to pause PIE: ${err}` };
161
102
  }
162
103
  }
163
-
104
+
164
105
  // Alias for consistency with naming convention
165
106
  async pauseInEditor() {
166
107
  return this.pausePlayInEditor();
@@ -168,43 +109,41 @@ else:
168
109
 
169
110
  async buildLighting() {
170
111
  try {
171
- // Use modern LevelEditorSubsystem to build lighting
172
- const py = `
173
- import unreal
174
- import json
175
- try:
176
- # Use modern LevelEditorSubsystem API
177
- les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
178
- if les:
179
- # build_light_maps(quality, with_reflection_captures)
180
- les.build_light_maps(unreal.LightingBuildQuality.QUALITY_HIGH, True)
181
- print('RESULT:' + json.dumps({'success': True, 'message': 'Lighting build started via LevelEditorSubsystem'}))
182
- else:
183
- # If subsystem not available, report error
184
- print('RESULT:' + json.dumps({'success': False, 'error': 'LevelEditorSubsystem not available'}))
185
- except Exception as e:
186
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
187
- `.trim();
188
- const resp: any = await this.bridge.executePython(py);
189
- const interpreted = interpretStandardResult(resp, {
190
- successMessage: 'Lighting build started',
191
- failureMessage: 'Failed to build lighting'
192
- });
193
-
194
- if (interpreted.success) {
195
- return { success: true, message: interpreted.message };
196
- }
197
-
198
- return {
199
- success: false,
200
- error: interpreted.error ?? 'Failed to build lighting',
201
- details: bestEffortInterpretedText(interpreted)
202
- };
112
+ // Use console command to build lighting
113
+ await this.bridge.executeConsoleCommand('BuildLighting');
114
+ return { success: true, message: 'Lighting build started' };
203
115
  } catch (err) {
204
116
  return { success: false, error: `Failed to build lighting: ${err}` };
205
117
  }
206
118
  }
207
119
 
120
+ private async getViewportCameraInfo(): Promise<{
121
+ success: boolean;
122
+ location?: [number, number, number];
123
+ rotation?: [number, number, number];
124
+ error?: string;
125
+ message?: string;
126
+ }> {
127
+ try {
128
+ const resp = await this.sendAutomationRequest(
129
+ 'control_editor',
130
+ { action: 'get_camera' },
131
+ { timeoutMs: 3000 }
132
+ );
133
+ const result = resp?.result ?? resp;
134
+ const loc = result?.location ?? result?.camera?.location;
135
+ const rot = result?.rotation ?? result?.camera?.rotation;
136
+ const locArr: [number, number, number] | undefined = Array.isArray(loc) && loc.length === 3 ? [Number(loc[0]) || 0, Number(loc[1]) || 0, Number(loc[2]) || 0] : undefined;
137
+ const rotArr: [number, number, number] | undefined = Array.isArray(rot) && rot.length === 3 ? [Number(rot[0]) || 0, Number(rot[1]) || 0, Number(rot[2]) || 0] : undefined;
138
+ if (resp && resp.success !== false && locArr && rotArr) {
139
+ return { success: true, location: locArr, rotation: rotArr };
140
+ }
141
+ return { success: false, error: 'Failed to get camera information' };
142
+ } catch (err) {
143
+ return { success: false, error: `Camera query failed: ${err}` };
144
+ }
145
+ }
146
+
208
147
  async setViewportCamera(location?: { x: number; y: number; z: number } | [number, number, number] | null | undefined, rotation?: { pitch: number; yaw: number; roll: number } | [number, number, number] | null | undefined) {
209
148
  // Special handling for when both location and rotation are missing/invalid
210
149
  // Allow rotation-only updates
@@ -224,7 +163,7 @@ except Exception as e:
224
163
  locObj.z = Math.max(-MAX_COORD, Math.min(MAX_COORD, locObj.z));
225
164
  location = locObj as any;
226
165
  }
227
-
166
+
228
167
  // Validate rotation if provided
229
168
  if (rotation !== undefined) {
230
169
  if (rotation === null) {
@@ -240,119 +179,243 @@ except Exception as e:
240
179
  rotObj.roll = ((rotObj.roll % 360) + 360) % 360;
241
180
  rotation = rotObj as any;
242
181
  }
243
-
182
+
183
+ // Use native control_editor.set_camera when available
244
184
  try {
245
- // Try Python for actual viewport camera positioning
246
- // Only proceed if we have a valid location
247
- if (location) {
248
- try {
249
- const rot = (rotation as any) || { pitch: 0, yaw: 0, roll: 0 };
250
- const pythonCmd = `
251
- import unreal
252
- # Use UnrealEditorSubsystem instead of deprecated EditorLevelLibrary
253
- ues = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
254
- les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
255
- location = unreal.Vector(${(location as any).x}, ${(location as any).y}, ${(location as any).z})
256
- rotation = unreal.Rotator(${rot.pitch}, ${rot.yaw}, ${rot.roll})
257
- if ues:
258
- ues.set_level_viewport_camera_info(location, rotation)
259
- # Invalidate viewports to ensure visual update
260
- try:
261
- if les:
262
- les.editor_invalidate_viewports()
263
- except Exception:
264
- pass
265
- `.trim();
266
- await this.bridge.executePython(pythonCmd);
267
- return {
268
- success: true,
269
- message: 'Viewport camera positioned via UnrealEditorSubsystem'
270
- };
271
- } catch {
272
- // Fallback to camera speed control
273
- await this.bridge.executeConsoleCommand('camspeed 4');
274
- return {
275
- success: true,
276
- message: 'Camera speed set. Use debug camera (toggledebugcamera) for manual positioning'
277
- };
278
- }
279
- } else if (rotation) {
280
- // Only rotation provided, try to set just rotation
281
- try {
282
- const pythonCmd = `
283
- import unreal
284
- # Use UnrealEditorSubsystem to read/write viewport camera
285
- ues = unreal.get_editor_subsystem(unreal.UnrealEditorSubsystem)
286
- les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
287
- rotation = unreal.Rotator(${(rotation as any).pitch}, ${(rotation as any).yaw}, ${(rotation as any).roll})
288
- if ues:
289
- info = ues.get_level_viewport_camera_info()
290
- if info is not None:
291
- current_location, _ = info
292
- ues.set_level_viewport_camera_info(current_location, rotation)
293
- try:
294
- if les:
295
- les.editor_invalidate_viewports()
296
- except Exception:
297
- pass
298
- `.trim();
299
- await this.bridge.executePython(pythonCmd);
300
- return {
301
- success: true,
302
- message: 'Viewport camera rotation set via UnrealEditorSubsystem'
303
- };
304
- } catch {
305
- // Fallback
306
- return {
307
- success: true,
308
- message: 'Camera rotation update attempted'
309
- };
310
- }
311
- } else {
312
- // Neither location nor rotation provided - this is valid, just no-op
313
- return {
314
- success: true,
315
- message: 'No camera changes requested'
316
- };
185
+ const resp = await this.sendAutomationRequest('control_editor', {
186
+ action: 'set_camera',
187
+ location: location as any,
188
+ rotation: rotation as any
189
+ }, { timeoutMs: 10000 });
190
+ if (resp && resp.success === true) {
191
+ return { success: true, message: resp.message || 'Camera set', location, rotation };
317
192
  }
193
+ return { success: false, error: resp?.error || resp?.message || 'Failed to set camera' };
318
194
  } catch (err) {
319
- return { success: false, error: `Failed to set camera: ${err}` };
195
+ return { success: false, error: `Camera control failed: ${err}` };
320
196
  }
321
197
  }
322
-
198
+
323
199
  async setCameraSpeed(speed: number) {
324
200
  try {
325
- await this.bridge.httpCall('/remote/object/call', 'PUT', {
326
- objectPath: '/Script/Engine.Default__KismetSystemLibrary',
327
- functionName: 'ExecuteConsoleCommand',
328
- parameters: {
329
- WorldContextObject: null,
330
- Command: `camspeed ${speed}`,
331
- SpecificPlayer: null
332
- },
333
- generateTransaction: false
334
- });
201
+ await this.bridge.executeConsoleCommand(`camspeed ${speed}`);
335
202
  return { success: true, message: `Camera speed set to ${speed}` };
336
203
  } catch (err) {
337
204
  return { success: false, error: `Failed to set camera speed: ${err}` };
338
205
  }
339
206
  }
340
-
207
+
341
208
  async setFOV(fov: number) {
342
209
  try {
343
- await this.bridge.httpCall('/remote/object/call', 'PUT', {
344
- objectPath: '/Script/Engine.Default__KismetSystemLibrary',
345
- functionName: 'ExecuteConsoleCommand',
346
- parameters: {
347
- WorldContextObject: null,
348
- Command: `fov ${fov}`,
349
- SpecificPlayer: null
350
- },
351
- generateTransaction: false
352
- });
210
+ await this.bridge.executeConsoleCommand(`fov ${fov}`);
353
211
  return { success: true, message: `FOV set to ${fov}` };
354
212
  } catch (err) {
355
213
  return { success: false, error: `Failed to set FOV: ${err}` };
356
214
  }
357
215
  }
216
+
217
+ async takeScreenshot(filename?: string, resolution?: string) {
218
+ try {
219
+ if (resolution && !/^\d+x\d+$/.test(resolution)) {
220
+ return { success: false, error: 'Invalid resolution format. Use WxH (e.g. 1920x1080)' };
221
+ }
222
+
223
+ const sanitizedFilename = filename ? filename.replace(/[<>:*?"|]/g, '_') : `Screenshot_${Date.now()}`;
224
+ const resString = resolution || DEFAULT_SCREENSHOT_RESOLUTION;
225
+ const command = filename ? `highresshot ${resString} filename="${sanitizedFilename}"` : 'shot';
226
+
227
+ await this.bridge.executeConsoleCommand(command);
228
+
229
+ return {
230
+ success: true,
231
+ message: `Screenshot captured: ${sanitizedFilename}`,
232
+ filename: sanitizedFilename,
233
+ command
234
+ };
235
+ } catch (err) {
236
+ return { success: false, error: `Failed to take screenshot: ${err}` };
237
+ }
238
+ }
239
+
240
+ async resumePlayInEditor() {
241
+ try {
242
+ // Use console command to toggle pause (resumes if paused)
243
+ await this.bridge.executeConsoleCommand('pause');
244
+ return {
245
+ success: true,
246
+ message: 'PIE resume toggled via pause command'
247
+ };
248
+ } catch (err) {
249
+ return { success: false, error: `Failed to resume PIE: ${err}` };
250
+ }
251
+ }
252
+
253
+ async stepPIEFrame(steps: number = 1) {
254
+ const clampedSteps = Number.isFinite(steps) ? Math.max(1, Math.floor(steps)) : 1;
255
+ try {
256
+ // Use console command to step frames
257
+ for (let index = 0; index < clampedSteps; index += 1) {
258
+ await this.bridge.executeConsoleCommand('Step=1');
259
+ }
260
+ return {
261
+ success: true,
262
+ message: `Advanced PIE by ${clampedSteps} frame(s)`,
263
+ steps: clampedSteps
264
+ };
265
+ } catch (err) {
266
+ return { success: false, error: `Failed to step PIE: ${err}` };
267
+ }
268
+ }
269
+
270
+ async startRecording(options?: { filename?: string; frameRate?: number; durationSeconds?: number; metadata?: Record<string, unknown> }) {
271
+ const startedAt = Date.now();
272
+ this.activeRecording = {
273
+ name: typeof options?.filename === 'string' ? options.filename.trim() : undefined,
274
+ options: options ? { ...options } : undefined,
275
+ startedAt
276
+ };
277
+
278
+ return {
279
+ success: true as const,
280
+ message: 'Recording session started',
281
+ recording: {
282
+ name: this.activeRecording.name,
283
+ startedAt,
284
+ options: this.activeRecording.options
285
+ }
286
+ };
287
+ }
288
+
289
+ async stopRecording() {
290
+ if (!this.activeRecording) {
291
+ return {
292
+ success: true as const,
293
+ message: 'No active recording session to stop'
294
+ };
295
+ }
296
+
297
+ const stoppedRecording = this.activeRecording;
298
+ this.activeRecording = undefined;
299
+
300
+ return {
301
+ success: true as const,
302
+ message: 'Recording session stopped',
303
+ recording: stoppedRecording
304
+ };
305
+ }
306
+
307
+ async createCameraBookmark(name: string) {
308
+ const trimmedName = name.trim();
309
+ if (!trimmedName) {
310
+ return { success: false as const, error: 'bookmarkName is required' };
311
+ }
312
+
313
+ const cameraInfo = await this.getViewportCameraInfo();
314
+ if (!cameraInfo.success || !cameraInfo.location || !cameraInfo.rotation) {
315
+ return {
316
+ success: false as const,
317
+ error: cameraInfo.error || 'Failed to capture viewport camera'
318
+ };
319
+ }
320
+
321
+ this.cameraBookmarks.set(trimmedName, {
322
+ location: cameraInfo.location,
323
+ rotation: cameraInfo.rotation,
324
+ savedAt: Date.now()
325
+ });
326
+
327
+ return {
328
+ success: true as const,
329
+ message: `Bookmark '${trimmedName}' saved`,
330
+ bookmark: {
331
+ name: trimmedName,
332
+ location: cameraInfo.location,
333
+ rotation: cameraInfo.rotation
334
+ }
335
+ };
336
+ }
337
+
338
+ async jumpToCameraBookmark(name: string) {
339
+ const trimmedName = name.trim();
340
+ if (!trimmedName) {
341
+ return { success: false as const, error: 'bookmarkName is required' };
342
+ }
343
+
344
+ const bookmark = this.cameraBookmarks.get(trimmedName);
345
+ if (!bookmark) {
346
+ return {
347
+ success: false as const,
348
+ error: `Bookmark '${trimmedName}' not found`
349
+ };
350
+ }
351
+
352
+ await this.setViewportCamera(
353
+ { x: bookmark.location[0], y: bookmark.location[1], z: bookmark.location[2] },
354
+ { pitch: bookmark.rotation[0], yaw: bookmark.rotation[1], roll: bookmark.rotation[2] }
355
+ );
356
+
357
+ return {
358
+ success: true as const,
359
+ message: `Jumped to bookmark '${trimmedName}'`
360
+ };
361
+ }
362
+
363
+ async setEditorPreferences(category: string | undefined, preferences: Record<string, unknown>) {
364
+ const resolvedCategory = typeof category === 'string' && category.trim().length > 0 ? category.trim() : 'General';
365
+ const existing = this.editorPreferences.get(resolvedCategory) ?? {};
366
+ this.editorPreferences.set(resolvedCategory, { ...existing, ...preferences });
367
+
368
+ return {
369
+ success: true as const,
370
+ message: `Preferences stored for ${resolvedCategory}`,
371
+ preferences: this.editorPreferences.get(resolvedCategory)
372
+ };
373
+ }
374
+
375
+ async setViewportResolution(width: number, height: number) {
376
+ try {
377
+ // Clamp to reasonable limits
378
+ const clampedWidth = Math.max(320, Math.min(7680, width));
379
+ const clampedHeight = Math.max(240, Math.min(4320, height));
380
+
381
+ // Use console command directly instead of Python
382
+ const command = `r.SetRes ${clampedWidth}x${clampedHeight}`;
383
+ await this.bridge.executeConsoleCommand(command);
384
+
385
+ return {
386
+ success: true,
387
+ message: `Viewport resolution set to ${clampedWidth}x${clampedHeight}`,
388
+ width: clampedWidth,
389
+ height: clampedHeight
390
+ };
391
+ } catch (err) {
392
+ return { success: false, error: `Failed to set viewport resolution: ${err}` };
393
+ }
394
+ }
395
+
396
+ async executeConsoleCommand(command: string) {
397
+ try {
398
+ // Sanitize and validate command
399
+ if (!command || typeof command !== 'string') {
400
+ return { success: false, error: 'Invalid command: must be a non-empty string' };
401
+ }
402
+
403
+ if (command.length > 1000) {
404
+ return {
405
+ success: false,
406
+ error: `Command too long (${command.length} chars). Maximum is 1000 characters.`
407
+ };
408
+ }
409
+
410
+ const res = await this.bridge.executeConsoleCommand(command);
411
+
412
+ return {
413
+ success: true,
414
+ message: `Console command executed: ${command}`,
415
+ output: res
416
+ };
417
+ } catch (err) {
418
+ return { success: false, error: `Failed to execute console command: ${err}` };
419
+ }
420
+ }
358
421
  }
@@ -1,10 +1,15 @@
1
1
  import { UnrealBridge } from '../unreal-bridge.js';
2
+ import { AutomationBridge } from '../automation/index.js';
2
3
  import { loadEnv } from '../types/env.js';
3
4
  import { spawn } from 'child_process';
4
5
 
5
6
  export class EngineTools {
6
7
  private env = loadEnv();
7
- constructor(private bridge: UnrealBridge) {}
8
+ constructor(_bridge: UnrealBridge, private automationBridge?: AutomationBridge) { }
9
+
10
+ setAutomationBridge(automationBridge?: AutomationBridge) {
11
+ this.automationBridge = automationBridge;
12
+ }
8
13
 
9
14
  async launchEditor(params?: { editorExe?: string; projectPath?: string }) {
10
15
  const exe = params?.editorExe || this.env.UE_EDITOR_EXE;
@@ -21,9 +26,15 @@ export class EngineTools {
21
26
  }
22
27
 
23
28
  async quitEditor() {
29
+ if (!this.automationBridge) {
30
+ return { success: false, error: 'AUTOMATION_BRIDGE_UNAVAILABLE', message: 'Automation bridge is not available for quit_editor' };
31
+ }
32
+
24
33
  try {
25
- // Use Python SystemLibrary.quit_editor if available
26
- await this.bridge.executePython('import unreal; unreal.SystemLibrary.quit_editor()');
34
+ const resp: any = await this.automationBridge.sendAutomationRequest('quit_editor', {});
35
+ if (resp && resp.success === false) {
36
+ return { success: false, error: resp.error || resp.message || 'Quit request failed' };
37
+ }
27
38
  return { success: true, message: 'Quit command sent' };
28
39
  } catch (err: any) {
29
40
  return { success: false, error: String(err?.message || err) };