unreal-engine-mcp-server 0.4.7 → 0.5.1

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 (454) 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-config.yml +51 -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 +27 -0
  19. package/.github/workflows/labeler.yml +17 -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 +13 -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 +338 -31
  29. package/CONTRIBUTING.md +140 -0
  30. package/GEMINI.md +115 -0
  31. package/Public/Plugin_setup_guide.mp4 +0 -0
  32. package/README.md +189 -128
  33. package/claude_desktop_config_example.json +7 -6
  34. package/dist/automation/bridge.d.ts +50 -0
  35. package/dist/automation/bridge.js +452 -0
  36. package/dist/automation/connection-manager.d.ts +23 -0
  37. package/dist/automation/connection-manager.js +107 -0
  38. package/dist/automation/handshake.d.ts +11 -0
  39. package/dist/automation/handshake.js +89 -0
  40. package/dist/automation/index.d.ts +3 -0
  41. package/dist/automation/index.js +3 -0
  42. package/dist/automation/message-handler.d.ts +12 -0
  43. package/dist/automation/message-handler.js +149 -0
  44. package/dist/automation/request-tracker.d.ts +25 -0
  45. package/dist/automation/request-tracker.js +98 -0
  46. package/dist/automation/types.d.ts +130 -0
  47. package/dist/automation/types.js +2 -0
  48. package/dist/cli.js +32 -5
  49. package/dist/config.d.ts +26 -0
  50. package/dist/config.js +59 -0
  51. package/dist/constants.d.ts +16 -0
  52. package/dist/constants.js +16 -0
  53. package/dist/graphql/loaders.d.ts +64 -0
  54. package/dist/graphql/loaders.js +117 -0
  55. package/dist/graphql/resolvers.d.ts +268 -0
  56. package/dist/graphql/resolvers.js +746 -0
  57. package/dist/graphql/schema.d.ts +5 -0
  58. package/dist/graphql/schema.js +437 -0
  59. package/dist/graphql/server.d.ts +26 -0
  60. package/dist/graphql/server.js +117 -0
  61. package/dist/graphql/types.d.ts +9 -0
  62. package/dist/graphql/types.js +2 -0
  63. package/dist/handlers/resource-handlers.d.ts +20 -0
  64. package/dist/handlers/resource-handlers.js +180 -0
  65. package/dist/index.d.ts +33 -18
  66. package/dist/index.js +130 -619
  67. package/dist/resources/actors.d.ts +17 -12
  68. package/dist/resources/actors.js +56 -76
  69. package/dist/resources/assets.d.ts +6 -14
  70. package/dist/resources/assets.js +115 -147
  71. package/dist/resources/levels.d.ts +13 -13
  72. package/dist/resources/levels.js +25 -34
  73. package/dist/server/resource-registry.d.ts +20 -0
  74. package/dist/server/resource-registry.js +37 -0
  75. package/dist/server/tool-registry.d.ts +23 -0
  76. package/dist/server/tool-registry.js +322 -0
  77. package/dist/server-setup.d.ts +20 -0
  78. package/dist/server-setup.js +71 -0
  79. package/dist/services/health-monitor.d.ts +34 -0
  80. package/dist/services/health-monitor.js +105 -0
  81. package/dist/services/metrics-server.d.ts +11 -0
  82. package/dist/services/metrics-server.js +105 -0
  83. package/dist/tools/actors.d.ts +163 -9
  84. package/dist/tools/actors.js +356 -311
  85. package/dist/tools/animation.d.ts +135 -4
  86. package/dist/tools/animation.js +510 -411
  87. package/dist/tools/assets.d.ts +75 -29
  88. package/dist/tools/assets.js +265 -284
  89. package/dist/tools/audio.d.ts +102 -42
  90. package/dist/tools/audio.js +272 -685
  91. package/dist/tools/base-tool.d.ts +17 -0
  92. package/dist/tools/base-tool.js +46 -0
  93. package/dist/tools/behavior-tree.d.ts +94 -0
  94. package/dist/tools/behavior-tree.js +39 -0
  95. package/dist/tools/blueprint.d.ts +208 -126
  96. package/dist/tools/blueprint.js +685 -832
  97. package/dist/tools/consolidated-tool-definitions.d.ts +5462 -1781
  98. package/dist/tools/consolidated-tool-definitions.js +829 -496
  99. package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
  100. package/dist/tools/consolidated-tool-handlers.js +198 -1027
  101. package/dist/tools/debug.d.ts +143 -85
  102. package/dist/tools/debug.js +234 -180
  103. package/dist/tools/dynamic-handler-registry.d.ts +13 -0
  104. package/dist/tools/dynamic-handler-registry.js +23 -0
  105. package/dist/tools/editor.d.ts +30 -83
  106. package/dist/tools/editor.js +247 -244
  107. package/dist/tools/engine.d.ts +10 -4
  108. package/dist/tools/engine.js +13 -5
  109. package/dist/tools/environment.d.ts +30 -0
  110. package/dist/tools/environment.js +267 -0
  111. package/dist/tools/foliage.d.ts +65 -99
  112. package/dist/tools/foliage.js +221 -331
  113. package/dist/tools/handlers/actor-handlers.d.ts +3 -0
  114. package/dist/tools/handlers/actor-handlers.js +227 -0
  115. package/dist/tools/handlers/animation-handlers.d.ts +3 -0
  116. package/dist/tools/handlers/animation-handlers.js +185 -0
  117. package/dist/tools/handlers/argument-helper.d.ts +16 -0
  118. package/dist/tools/handlers/argument-helper.js +80 -0
  119. package/dist/tools/handlers/asset-handlers.d.ts +3 -0
  120. package/dist/tools/handlers/asset-handlers.js +496 -0
  121. package/dist/tools/handlers/audio-handlers.d.ts +3 -0
  122. package/dist/tools/handlers/audio-handlers.js +166 -0
  123. package/dist/tools/handlers/blueprint-handlers.d.ts +4 -0
  124. package/dist/tools/handlers/blueprint-handlers.js +358 -0
  125. package/dist/tools/handlers/common-handlers.d.ts +14 -0
  126. package/dist/tools/handlers/common-handlers.js +56 -0
  127. package/dist/tools/handlers/editor-handlers.d.ts +3 -0
  128. package/dist/tools/handlers/editor-handlers.js +119 -0
  129. package/dist/tools/handlers/effect-handlers.d.ts +3 -0
  130. package/dist/tools/handlers/effect-handlers.js +171 -0
  131. package/dist/tools/handlers/environment-handlers.d.ts +3 -0
  132. package/dist/tools/handlers/environment-handlers.js +170 -0
  133. package/dist/tools/handlers/graph-handlers.d.ts +3 -0
  134. package/dist/tools/handlers/graph-handlers.js +90 -0
  135. package/dist/tools/handlers/input-handlers.d.ts +3 -0
  136. package/dist/tools/handlers/input-handlers.js +21 -0
  137. package/dist/tools/handlers/inspect-handlers.d.ts +3 -0
  138. package/dist/tools/handlers/inspect-handlers.js +383 -0
  139. package/dist/tools/handlers/level-handlers.d.ts +3 -0
  140. package/dist/tools/handlers/level-handlers.js +237 -0
  141. package/dist/tools/handlers/lighting-handlers.d.ts +3 -0
  142. package/dist/tools/handlers/lighting-handlers.js +144 -0
  143. package/dist/tools/handlers/performance-handlers.d.ts +3 -0
  144. package/dist/tools/handlers/performance-handlers.js +130 -0
  145. package/dist/tools/handlers/pipeline-handlers.d.ts +3 -0
  146. package/dist/tools/handlers/pipeline-handlers.js +110 -0
  147. package/dist/tools/handlers/sequence-handlers.d.ts +3 -0
  148. package/dist/tools/handlers/sequence-handlers.js +376 -0
  149. package/dist/tools/handlers/system-handlers.d.ts +4 -0
  150. package/dist/tools/handlers/system-handlers.js +506 -0
  151. package/dist/tools/input.d.ts +19 -0
  152. package/dist/tools/input.js +89 -0
  153. package/dist/tools/introspection.d.ts +103 -40
  154. package/dist/tools/introspection.js +425 -568
  155. package/dist/tools/landscape.d.ts +54 -93
  156. package/dist/tools/landscape.js +284 -409
  157. package/dist/tools/level.d.ts +66 -27
  158. package/dist/tools/level.js +647 -675
  159. package/dist/tools/lighting.d.ts +77 -38
  160. package/dist/tools/lighting.js +445 -943
  161. package/dist/tools/logs.d.ts +3 -3
  162. package/dist/tools/logs.js +5 -57
  163. package/dist/tools/materials.d.ts +91 -24
  164. package/dist/tools/materials.js +194 -118
  165. package/dist/tools/niagara.d.ts +149 -39
  166. package/dist/tools/niagara.js +267 -182
  167. package/dist/tools/performance.d.ts +27 -13
  168. package/dist/tools/performance.js +203 -122
  169. package/dist/tools/physics.d.ts +32 -77
  170. package/dist/tools/physics.js +175 -582
  171. package/dist/tools/property-dictionary.d.ts +13 -0
  172. package/dist/tools/property-dictionary.js +82 -0
  173. package/dist/tools/sequence.d.ts +85 -60
  174. package/dist/tools/sequence.js +208 -747
  175. package/dist/tools/tool-definition-utils.d.ts +59 -0
  176. package/dist/tools/tool-definition-utils.js +35 -0
  177. package/dist/tools/ui.d.ts +64 -34
  178. package/dist/tools/ui.js +134 -214
  179. package/dist/types/automation-responses.d.ts +115 -0
  180. package/dist/types/automation-responses.js +2 -0
  181. package/dist/types/env.d.ts +0 -3
  182. package/dist/types/env.js +0 -7
  183. package/dist/types/responses.d.ts +249 -0
  184. package/dist/types/responses.js +2 -0
  185. package/dist/types/tool-interfaces.d.ts +898 -0
  186. package/dist/types/tool-interfaces.js +2 -0
  187. package/dist/types/tool-types.d.ts +183 -19
  188. package/dist/types/tool-types.js +0 -4
  189. package/dist/unreal-bridge.d.ts +24 -131
  190. package/dist/unreal-bridge.js +364 -1506
  191. package/dist/utils/command-validator.d.ts +9 -0
  192. package/dist/utils/command-validator.js +68 -0
  193. package/dist/utils/elicitation.d.ts +1 -1
  194. package/dist/utils/elicitation.js +12 -15
  195. package/dist/utils/error-handler.d.ts +2 -51
  196. package/dist/utils/error-handler.js +11 -87
  197. package/dist/utils/ini-reader.d.ts +3 -0
  198. package/dist/utils/ini-reader.js +69 -0
  199. package/dist/utils/logger.js +9 -6
  200. package/dist/utils/normalize.d.ts +3 -0
  201. package/dist/utils/normalize.js +56 -0
  202. package/dist/utils/path-security.d.ts +2 -0
  203. package/dist/utils/path-security.js +24 -0
  204. package/dist/utils/response-factory.d.ts +7 -0
  205. package/dist/utils/response-factory.js +27 -0
  206. package/dist/utils/response-validator.d.ts +3 -24
  207. package/dist/utils/response-validator.js +130 -81
  208. package/dist/utils/result-helpers.d.ts +4 -5
  209. package/dist/utils/result-helpers.js +15 -16
  210. package/dist/utils/safe-json.js +5 -11
  211. package/dist/utils/unreal-command-queue.d.ts +24 -0
  212. package/dist/utils/unreal-command-queue.js +120 -0
  213. package/dist/utils/validation.d.ts +0 -40
  214. package/dist/utils/validation.js +1 -78
  215. package/dist/wasm/index.d.ts +70 -0
  216. package/dist/wasm/index.js +535 -0
  217. package/docs/GraphQL-API.md +888 -0
  218. package/docs/Migration-Guide-v0.5.0.md +684 -0
  219. package/docs/Roadmap.md +53 -0
  220. package/docs/WebAssembly-Integration.md +628 -0
  221. package/docs/editor-plugin-extension.md +370 -0
  222. package/docs/handler-mapping.md +242 -0
  223. package/docs/native-automation-progress.md +128 -0
  224. package/docs/testing-guide.md +423 -0
  225. package/mcp-config-example.json +6 -6
  226. package/package.json +67 -28
  227. package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +8 -0
  228. package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +64 -0
  229. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +189 -0
  230. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +22 -0
  231. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +30 -0
  232. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +1983 -0
  233. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +72 -0
  234. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +46 -0
  235. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +581 -0
  236. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +2394 -0
  237. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +300 -0
  238. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +2807 -0
  239. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +1087 -0
  240. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +488 -0
  241. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +643 -0
  242. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +31 -0
  243. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +1184 -0
  244. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +5652 -0
  245. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +152 -0
  246. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +2614 -0
  247. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +42 -0
  248. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +1237 -0
  249. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +1701 -0
  250. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +2145 -0
  251. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +954 -0
  252. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +209 -0
  253. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +41 -0
  254. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +1164 -0
  255. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +762 -0
  256. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +634 -0
  257. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +136 -0
  258. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +494 -0
  259. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +278 -0
  260. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +625 -0
  261. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +401 -0
  262. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +67 -0
  263. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +735 -0
  264. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +2634 -0
  265. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +189 -0
  266. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +917 -0
  267. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +39 -0
  268. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +2670 -0
  269. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +519 -0
  270. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +38 -0
  271. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +668 -0
  272. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +346 -0
  273. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +1330 -0
  274. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +149 -0
  275. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +783 -0
  276. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +115 -0
  277. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +796 -0
  278. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +117 -0
  279. package/scripts/check-unreal-connection.mjs +19 -0
  280. package/scripts/clean-tmp.js +23 -0
  281. package/scripts/patch-wasm.js +26 -0
  282. package/scripts/run-all-tests.mjs +136 -0
  283. package/scripts/smoke-test.ts +94 -0
  284. package/scripts/sync-mcp-plugin.js +143 -0
  285. package/scripts/test-no-plugin-alternates.mjs +113 -0
  286. package/scripts/validate-server.js +46 -0
  287. package/scripts/verify-automation-bridge.js +200 -0
  288. package/server.json +58 -21
  289. package/src/automation/bridge.ts +558 -0
  290. package/src/automation/connection-manager.ts +130 -0
  291. package/src/automation/handshake.ts +99 -0
  292. package/src/automation/index.ts +2 -0
  293. package/src/automation/message-handler.ts +167 -0
  294. package/src/automation/request-tracker.ts +123 -0
  295. package/src/automation/types.ts +107 -0
  296. package/src/cli.ts +33 -6
  297. package/src/config.ts +73 -0
  298. package/src/constants.ts +19 -0
  299. package/src/graphql/loaders.ts +244 -0
  300. package/src/graphql/resolvers.ts +1008 -0
  301. package/src/graphql/schema.ts +452 -0
  302. package/src/graphql/server.ts +156 -0
  303. package/src/graphql/types.ts +10 -0
  304. package/src/handlers/resource-handlers.ts +186 -0
  305. package/src/index.ts +166 -664
  306. package/src/resources/actors.ts +58 -76
  307. package/src/resources/assets.ts +148 -134
  308. package/src/resources/levels.ts +28 -33
  309. package/src/server/resource-registry.ts +47 -0
  310. package/src/server/tool-registry.ts +354 -0
  311. package/src/server-setup.ts +114 -0
  312. package/src/services/health-monitor.ts +132 -0
  313. package/src/services/metrics-server.ts +142 -0
  314. package/src/tools/actors.ts +426 -323
  315. package/src/tools/animation.ts +672 -461
  316. package/src/tools/assets.ts +364 -289
  317. package/src/tools/audio.ts +323 -766
  318. package/src/tools/base-tool.ts +52 -0
  319. package/src/tools/behavior-tree.ts +45 -0
  320. package/src/tools/blueprint.ts +792 -970
  321. package/src/tools/consolidated-tool-definitions.ts +993 -515
  322. package/src/tools/consolidated-tool-handlers.ts +258 -1146
  323. package/src/tools/debug.ts +292 -187
  324. package/src/tools/dynamic-handler-registry.ts +33 -0
  325. package/src/tools/editor.ts +329 -253
  326. package/src/tools/engine.ts +14 -3
  327. package/src/tools/environment.ts +281 -0
  328. package/src/tools/foliage.ts +330 -392
  329. package/src/tools/handlers/actor-handlers.ts +265 -0
  330. package/src/tools/handlers/animation-handlers.ts +237 -0
  331. package/src/tools/handlers/argument-helper.ts +142 -0
  332. package/src/tools/handlers/asset-handlers.ts +532 -0
  333. package/src/tools/handlers/audio-handlers.ts +194 -0
  334. package/src/tools/handlers/blueprint-handlers.ts +380 -0
  335. package/src/tools/handlers/common-handlers.ts +87 -0
  336. package/src/tools/handlers/editor-handlers.ts +123 -0
  337. package/src/tools/handlers/effect-handlers.ts +220 -0
  338. package/src/tools/handlers/environment-handlers.ts +183 -0
  339. package/src/tools/handlers/graph-handlers.ts +116 -0
  340. package/src/tools/handlers/input-handlers.ts +28 -0
  341. package/src/tools/handlers/inspect-handlers.ts +450 -0
  342. package/src/tools/handlers/level-handlers.ts +252 -0
  343. package/src/tools/handlers/lighting-handlers.ts +147 -0
  344. package/src/tools/handlers/performance-handlers.ts +132 -0
  345. package/src/tools/handlers/pipeline-handlers.ts +127 -0
  346. package/src/tools/handlers/sequence-handlers.ts +415 -0
  347. package/src/tools/handlers/system-handlers.ts +564 -0
  348. package/src/tools/input.ts +101 -0
  349. package/src/tools/introspection.ts +493 -584
  350. package/src/tools/landscape.ts +418 -507
  351. package/src/tools/level.ts +786 -708
  352. package/src/tools/lighting.ts +588 -984
  353. package/src/tools/logs.ts +9 -57
  354. package/src/tools/materials.ts +237 -121
  355. package/src/tools/niagara.ts +335 -168
  356. package/src/tools/performance.ts +320 -169
  357. package/src/tools/physics.ts +274 -613
  358. package/src/tools/property-dictionary.ts +98 -0
  359. package/src/tools/sequence.ts +276 -820
  360. package/src/tools/tool-definition-utils.ts +35 -0
  361. package/src/tools/ui.ts +205 -283
  362. package/src/types/automation-responses.ts +119 -0
  363. package/src/types/env.ts +0 -10
  364. package/src/types/responses.ts +355 -0
  365. package/src/types/tool-interfaces.ts +250 -0
  366. package/src/types/tool-types.ts +243 -21
  367. package/src/unreal-bridge.ts +460 -1550
  368. package/src/utils/command-validator.ts +76 -0
  369. package/src/utils/elicitation.ts +10 -7
  370. package/src/utils/error-handler.ts +14 -90
  371. package/src/utils/ini-reader.ts +86 -0
  372. package/src/utils/logger.ts +8 -3
  373. package/src/utils/normalize.test.ts +162 -0
  374. package/src/utils/normalize.ts +60 -0
  375. package/src/utils/path-security.ts +43 -0
  376. package/src/utils/response-factory.ts +44 -0
  377. package/src/utils/response-validator.ts +176 -56
  378. package/src/utils/result-helpers.ts +21 -19
  379. package/src/utils/safe-json.test.ts +90 -0
  380. package/src/utils/safe-json.ts +14 -11
  381. package/src/utils/unreal-command-queue.ts +152 -0
  382. package/src/utils/validation.test.ts +184 -0
  383. package/src/utils/validation.ts +4 -1
  384. package/src/wasm/index.ts +838 -0
  385. package/test-server.mjs +100 -0
  386. package/tests/run-unreal-tool-tests.mjs +242 -14
  387. package/tests/test-animation.mjs +369 -0
  388. package/tests/test-asset-advanced.mjs +82 -0
  389. package/tests/test-asset-errors.mjs +35 -0
  390. package/tests/test-asset-graph.mjs +311 -0
  391. package/tests/test-audio.mjs +417 -0
  392. package/tests/test-automation-timeouts.mjs +98 -0
  393. package/tests/test-behavior-tree.mjs +444 -0
  394. package/tests/test-blueprint-graph.mjs +410 -0
  395. package/tests/test-blueprint.mjs +577 -0
  396. package/tests/test-client-mode.mjs +86 -0
  397. package/tests/test-console-command.mjs +56 -0
  398. package/tests/test-control-actor.mjs +425 -0
  399. package/tests/test-control-editor.mjs +112 -0
  400. package/tests/test-graphql.mjs +372 -0
  401. package/tests/test-input.mjs +349 -0
  402. package/tests/test-inspect.mjs +302 -0
  403. package/tests/test-landscape.mjs +316 -0
  404. package/tests/test-lighting.mjs +428 -0
  405. package/tests/test-manage-asset.mjs +438 -0
  406. package/tests/test-manage-level.mjs +89 -0
  407. package/tests/test-materials.mjs +356 -0
  408. package/tests/test-niagara.mjs +185 -0
  409. package/tests/test-no-inline-python.mjs +122 -0
  410. package/tests/test-performance.mjs +539 -0
  411. package/tests/test-plugin-handshake.mjs +82 -0
  412. package/tests/test-runner.mjs +933 -0
  413. package/tests/test-sequence.mjs +104 -0
  414. package/tests/test-system.mjs +96 -0
  415. package/tests/test-wasm.mjs +283 -0
  416. package/tests/test-world-partition.mjs +215 -0
  417. package/tsconfig.json +3 -3
  418. package/vitest.config.ts +35 -0
  419. package/wasm/Cargo.lock +363 -0
  420. package/wasm/Cargo.toml +42 -0
  421. package/wasm/LICENSE +21 -0
  422. package/wasm/README.md +253 -0
  423. package/wasm/src/dependency_resolver.rs +377 -0
  424. package/wasm/src/lib.rs +153 -0
  425. package/wasm/src/property_parser.rs +271 -0
  426. package/wasm/src/transform_math.rs +396 -0
  427. package/wasm/tests/integration.rs +109 -0
  428. package/.github/workflows/smithery-build.yml +0 -29
  429. package/dist/prompts/index.d.ts +0 -21
  430. package/dist/prompts/index.js +0 -217
  431. package/dist/tools/build_environment_advanced.d.ts +0 -65
  432. package/dist/tools/build_environment_advanced.js +0 -633
  433. package/dist/tools/rc.d.ts +0 -110
  434. package/dist/tools/rc.js +0 -437
  435. package/dist/tools/visual.d.ts +0 -40
  436. package/dist/tools/visual.js +0 -282
  437. package/dist/utils/http.d.ts +0 -6
  438. package/dist/utils/http.js +0 -151
  439. package/dist/utils/python-output.d.ts +0 -18
  440. package/dist/utils/python-output.js +0 -290
  441. package/dist/utils/python.d.ts +0 -2
  442. package/dist/utils/python.js +0 -4
  443. package/dist/utils/stdio-redirect.d.ts +0 -2
  444. package/dist/utils/stdio-redirect.js +0 -20
  445. package/docs/unreal-tool-test-cases.md +0 -574
  446. package/smithery.yaml +0 -29
  447. package/src/prompts/index.ts +0 -249
  448. package/src/tools/build_environment_advanced.ts +0 -732
  449. package/src/tools/rc.ts +0 -515
  450. package/src/tools/visual.ts +0 -281
  451. package/src/utils/http.ts +0 -187
  452. package/src/utils/python-output.ts +0 -351
  453. package/src/utils/python.ts +0 -3
  454. package/src/utils/stdio-redirect.ts +0 -18
@@ -1,29 +1,33 @@
1
- import { UnrealBridge } from '../unreal-bridge.js';
1
+ import { BaseTool } from './base-tool.js';
2
+ import { IEditorTools, StandardActionResponse } 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
+ import { EditorResponse } from '../types/automation-responses.js';
6
+ import { wasmIntegration } from '../wasm/index.js';
7
+
8
+ export class EditorTools extends BaseTool implements IEditorTools {
9
+ private cameraBookmarks = new Map<string, { location: [number, number, number]; rotation: [number, number, number]; savedAt: number }>();
10
+ private editorPreferences = new Map<string, Record<string, unknown>>();
11
+ private activeRecording?: { name?: string; options?: Record<string, unknown>; startedAt: number };
4
12
 
5
- export class EditorTools {
6
- constructor(private bridge: UnrealBridge) {}
7
-
8
13
  async isInPIE(): Promise<boolean> {
9
14
  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');
15
+ const response = await this.sendAutomationRequest<EditorResponse>(
16
+ 'check_pie_state',
17
+ {},
18
+ { timeoutMs: 5000 }
19
+ );
20
+
21
+ if (response && response.success !== false) {
22
+ return response.isInPIE === true || (response.result as any)?.isInPIE === true;
23
+ }
24
+
25
+ return false;
22
26
  } catch {
23
27
  return false;
24
28
  }
25
29
  }
26
-
30
+
27
31
  async ensureNotInPIE(): Promise<void> {
28
32
  if (await this.isInPIE()) {
29
33
  await this.stopPlayInEditor();
@@ -32,108 +36,56 @@ else:
32
36
  }
33
37
  }
34
38
 
35
- async playInEditor() {
39
+ async playInEditor(timeoutMs: number = 30000): Promise<StandardActionResponse> {
36
40
  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
41
  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})` };
42
+ const response = await this.sendAutomationRequest<EditorResponse>(
43
+ 'control_editor',
44
+ { action: 'play' },
45
+ { timeoutMs }
46
+ );
47
+ if (response && response.success === true) {
48
+ return { success: true, message: response.message || 'PIE started' };
80
49
  }
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);
50
+ return { success: false, error: response?.error || response?.message || 'Failed to start PIE' };
51
+ } catch (err: any) {
52
+ // If it's a timeout, return error instead of falling back
53
+ if (err.message && /time.*out/i.test(err.message)) {
54
+ return { success: false, error: `Timeout waiting for PIE to start: ${err.message}` };
55
+ }
56
+
57
+ // Fallback to console commands if automation bridge is unavailable or fails (non-timeout)
58
+ await this.bridge.executeConsoleCommand('t.MaxFPS 60');
59
+ await this.bridge.executeConsoleCommand('PlayInViewport');
60
+ return { success: true, message: 'PIE start command sent' };
85
61
  }
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
62
  } catch (err) {
100
63
  return { success: false, error: `Failed to start PIE: ${err}` };
101
64
  }
102
65
  }
103
66
 
104
- async stopPlayInEditor() {
67
+ async stopPlayInEditor(): Promise<StandardActionResponse> {
105
68
  try {
106
- // Try Python first using the modern LevelEditorSubsystem
107
69
  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
- }
70
+ const response = await this.sendAutomationRequest<EditorResponse>(
71
+ 'control_editor',
72
+ { action: 'stop' },
73
+ { timeoutMs: 30000 }
74
+ );
129
75
 
130
- if (interpreted.error) {
131
- return { success: false, error: interpreted.error };
76
+ if (response.success !== false) {
77
+ return {
78
+ success: true,
79
+ message: response.message || 'PIE stopped successfully'
80
+ };
132
81
  }
133
82
 
134
- return { success: false, error: 'Failed to stop PIE' };
135
- } catch {
136
- // Fallback to console command
83
+ return {
84
+ success: false,
85
+ error: response.error || response.message || 'Failed to stop PIE'
86
+ };
87
+ } catch (_pluginErr) {
88
+ // Fallback to console command if plugin fails
137
89
  await this.bridge.executeConsoleCommand('stop');
138
90
  return { success: true, message: 'PIE stopped via console command' };
139
91
  }
@@ -141,71 +93,60 @@ else:
141
93
  return { success: false, error: `Failed to stop PIE: ${err}` };
142
94
  }
143
95
  }
144
-
145
- async pausePlayInEditor() {
96
+
97
+ async pausePlayInEditor(): Promise<StandardActionResponse> {
146
98
  try {
147
99
  // 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
- });
100
+ await this.bridge.executeConsoleCommand('pause');
158
101
  return { success: true, message: 'PIE paused/resumed' };
159
102
  } catch (err) {
160
103
  return { success: false, error: `Failed to pause PIE: ${err}` };
161
104
  }
162
105
  }
163
-
106
+
164
107
  // Alias for consistency with naming convention
165
- async pauseInEditor() {
108
+ async pauseInEditor(): Promise<StandardActionResponse> {
166
109
  return this.pausePlayInEditor();
167
110
  }
168
111
 
169
- async buildLighting() {
112
+ async buildLighting(): Promise<StandardActionResponse> {
170
113
  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
- };
114
+ // Use console command to build lighting
115
+ await this.bridge.executeConsoleCommand('BuildLighting');
116
+ return { success: true, message: 'Lighting build started' };
203
117
  } catch (err) {
204
118
  return { success: false, error: `Failed to build lighting: ${err}` };
205
119
  }
206
120
  }
207
121
 
208
- 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) {
122
+ private async getViewportCameraInfo(): Promise<{
123
+ success: boolean;
124
+ location?: [number, number, number];
125
+ rotation?: [number, number, number];
126
+ error?: string;
127
+ message?: string;
128
+ }> {
129
+ try {
130
+ const resp = await this.sendAutomationRequest<EditorResponse>(
131
+ 'control_editor',
132
+ { action: 'get_camera' },
133
+ { timeoutMs: 3000 }
134
+ );
135
+ const result: any = resp?.result ?? resp;
136
+ const loc = result?.location ?? result?.camera?.location;
137
+ const rot = result?.rotation ?? result?.camera?.rotation;
138
+ 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;
139
+ 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;
140
+ if (resp && resp.success !== false && locArr && rotArr) {
141
+ return { success: true, location: locArr, rotation: rotArr };
142
+ }
143
+ return { success: false, error: 'Failed to get camera information' };
144
+ } catch (err) {
145
+ return { success: false, error: `Camera query failed: ${err}` };
146
+ }
147
+ }
148
+
149
+ 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): Promise<StandardActionResponse> {
209
150
  // Special handling for when both location and rotation are missing/invalid
210
151
  // Allow rotation-only updates
211
152
  if (location === null) {
@@ -224,7 +165,7 @@ except Exception as e:
224
165
  locObj.z = Math.max(-MAX_COORD, Math.min(MAX_COORD, locObj.z));
225
166
  location = locObj as any;
226
167
  }
227
-
168
+
228
169
  // Validate rotation if provided
229
170
  if (rotation !== undefined) {
230
171
  if (rotation === null) {
@@ -240,119 +181,254 @@ except Exception as e:
240
181
  rotObj.roll = ((rotObj.roll % 360) + 360) % 360;
241
182
  rotation = rotObj as any;
242
183
  }
243
-
184
+
185
+ // Use native control_editor.set_camera when available
244
186
  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
- };
187
+ // Use WASM composeTransform for camera transform calculation
188
+ const locArray: [number, number, number] = location
189
+ ? [((location as any).x ?? (location as any)[0] ?? 0), ((location as any).y ?? (location as any)[1] ?? 0), ((location as any).z ?? (location as any)[2] ?? 0)]
190
+ : [0, 0, 0];
191
+ const rotArray: [number, number, number] = rotation
192
+ ? [((rotation as any).pitch ?? (rotation as any)[0] ?? 0), ((rotation as any).yaw ?? (rotation as any)[1] ?? 0), ((rotation as any).roll ?? (rotation as any)[2] ?? 0)]
193
+ : [0, 0, 0];
194
+ // Compose transform to validate and process camera positioning via WASM
195
+ wasmIntegration.composeTransform(locArray, rotArray, [1, 1, 1]);
196
+ // console.error('[WASM] Using composeTransform for camera positioning');
197
+
198
+ const resp = await this.sendAutomationRequest<EditorResponse>('control_editor', {
199
+ action: 'set_camera',
200
+ location: location as any,
201
+ rotation: rotation as any
202
+ }, { timeoutMs: 10000 });
203
+ if (resp && resp.success === true) {
204
+ return { success: true, message: resp.message || 'Camera set', location, rotation };
317
205
  }
206
+ return { success: false, error: resp?.error || resp?.message || 'Failed to set camera' };
318
207
  } catch (err) {
319
- return { success: false, error: `Failed to set camera: ${err}` };
208
+ return { success: false, error: `Camera control failed: ${err}` };
320
209
  }
321
210
  }
322
-
323
- async setCameraSpeed(speed: number) {
211
+
212
+ async setCameraSpeed(speed: number): Promise<StandardActionResponse> {
324
213
  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
- });
214
+ await this.bridge.executeConsoleCommand(`camspeed ${speed}`);
335
215
  return { success: true, message: `Camera speed set to ${speed}` };
336
216
  } catch (err) {
337
217
  return { success: false, error: `Failed to set camera speed: ${err}` };
338
218
  }
339
219
  }
340
-
341
- async setFOV(fov: number) {
220
+
221
+ async setFOV(fov: number): Promise<StandardActionResponse> {
342
222
  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
- });
223
+ await this.bridge.executeConsoleCommand(`fov ${fov}`);
353
224
  return { success: true, message: `FOV set to ${fov}` };
354
225
  } catch (err) {
355
226
  return { success: false, error: `Failed to set FOV: ${err}` };
356
227
  }
357
228
  }
229
+
230
+ async takeScreenshot(filename?: string, resolution?: string): Promise<StandardActionResponse> {
231
+ try {
232
+ if (resolution && !/^\d+x\d+$/.test(resolution)) {
233
+ return { success: false, error: 'Invalid resolution format. Use WxH (e.g. 1920x1080)' };
234
+ }
235
+
236
+ const sanitizedFilename = filename ? filename.replace(/[<>:*?"|]/g, '_') : `Screenshot_${Date.now()}`;
237
+ const resString = resolution || DEFAULT_SCREENSHOT_RESOLUTION;
238
+ const command = filename ? `highresshot ${resString} filename="${sanitizedFilename}"` : 'shot';
239
+
240
+ await this.bridge.executeConsoleCommand(command);
241
+
242
+ return {
243
+ success: true,
244
+ message: `Screenshot captured: ${sanitizedFilename}`,
245
+ filename: sanitizedFilename,
246
+ command
247
+ };
248
+ } catch (err) {
249
+ return { success: false, error: `Failed to take screenshot: ${err}` };
250
+ }
251
+ }
252
+
253
+ async resumePlayInEditor(): Promise<StandardActionResponse> {
254
+ try {
255
+ // Use console command to toggle pause (resumes if paused)
256
+ await this.bridge.executeConsoleCommand('pause');
257
+ return {
258
+ success: true,
259
+ message: 'PIE resume toggled via pause command'
260
+ };
261
+ } catch (err) {
262
+ return { success: false, error: `Failed to resume PIE: ${err}` };
263
+ }
264
+ }
265
+
266
+ async stepPIEFrame(steps: number = 1): Promise<StandardActionResponse> {
267
+ const clampedSteps = Number.isFinite(steps) ? Math.max(1, Math.floor(steps)) : 1;
268
+ try {
269
+ // Use console command to step frames
270
+ for (let index = 0; index < clampedSteps; index += 1) {
271
+ await this.bridge.executeConsoleCommand('Step=1');
272
+ }
273
+ return {
274
+ success: true,
275
+ message: `Advanced PIE by ${clampedSteps} frame(s)`,
276
+ steps: clampedSteps
277
+ };
278
+ } catch (err) {
279
+ return { success: false, error: `Failed to step PIE: ${err}` };
280
+ }
281
+ }
282
+
283
+ async startRecording(options?: { filename?: string; frameRate?: number; durationSeconds?: number; metadata?: Record<string, unknown> }): Promise<StandardActionResponse> {
284
+ const startedAt = Date.now();
285
+ this.activeRecording = {
286
+ name: typeof options?.filename === 'string' ? options.filename.trim() : undefined,
287
+ options: options ? { ...options } : undefined,
288
+ startedAt
289
+ };
290
+
291
+ return {
292
+ success: true as const,
293
+ message: 'Recording session started',
294
+ recording: {
295
+ name: this.activeRecording.name,
296
+ startedAt,
297
+ options: this.activeRecording.options
298
+ }
299
+ };
300
+ }
301
+
302
+ async stopRecording(): Promise<StandardActionResponse> {
303
+ if (!this.activeRecording) {
304
+ return {
305
+ success: true as const,
306
+ message: 'No active recording session to stop'
307
+ };
308
+ }
309
+
310
+ const stoppedRecording = this.activeRecording;
311
+ this.activeRecording = undefined;
312
+
313
+ return {
314
+ success: true as const,
315
+ message: 'Recording session stopped',
316
+ recording: stoppedRecording
317
+ };
318
+ }
319
+
320
+ async createCameraBookmark(name: string): Promise<StandardActionResponse> {
321
+ const trimmedName = name.trim();
322
+ if (!trimmedName) {
323
+ return { success: false as const, error: 'bookmarkName is required' };
324
+ }
325
+
326
+ const cameraInfo = await this.getViewportCameraInfo();
327
+ if (!cameraInfo.success || !cameraInfo.location || !cameraInfo.rotation) {
328
+ return {
329
+ success: false as const,
330
+ error: cameraInfo.error || 'Failed to capture viewport camera'
331
+ };
332
+ }
333
+
334
+ this.cameraBookmarks.set(trimmedName, {
335
+ location: cameraInfo.location,
336
+ rotation: cameraInfo.rotation,
337
+ savedAt: Date.now()
338
+ });
339
+
340
+ return {
341
+ success: true as const,
342
+ message: `Bookmark '${trimmedName}' saved`,
343
+ bookmark: {
344
+ name: trimmedName,
345
+ location: cameraInfo.location,
346
+ rotation: cameraInfo.rotation
347
+ }
348
+ };
349
+ }
350
+
351
+ async jumpToCameraBookmark(name: string): Promise<StandardActionResponse> {
352
+ const trimmedName = name.trim();
353
+ if (!trimmedName) {
354
+ return { success: false as const, error: 'bookmarkName is required' };
355
+ }
356
+
357
+ const bookmark = this.cameraBookmarks.get(trimmedName);
358
+ if (!bookmark) {
359
+ return {
360
+ success: false as const,
361
+ error: `Bookmark '${trimmedName}' not found`
362
+ };
363
+ }
364
+
365
+ await this.setViewportCamera(
366
+ { x: bookmark.location[0], y: bookmark.location[1], z: bookmark.location[2] },
367
+ { pitch: bookmark.rotation[0], yaw: bookmark.rotation[1], roll: bookmark.rotation[2] }
368
+ );
369
+
370
+ return {
371
+ success: true as const,
372
+ message: `Jumped to bookmark '${trimmedName}'`
373
+ };
374
+ }
375
+
376
+ async setEditorPreferences(category: string | undefined, preferences: Record<string, unknown>): Promise<StandardActionResponse> {
377
+ const resolvedCategory = typeof category === 'string' && category.trim().length > 0 ? category.trim() : 'General';
378
+ const existing = this.editorPreferences.get(resolvedCategory) ?? {};
379
+ this.editorPreferences.set(resolvedCategory, { ...existing, ...preferences });
380
+
381
+ return {
382
+ success: true as const,
383
+ message: `Preferences stored for ${resolvedCategory}`,
384
+ preferences: this.editorPreferences.get(resolvedCategory)
385
+ };
386
+ }
387
+
388
+ async setViewportResolution(width: number, height: number): Promise<StandardActionResponse> {
389
+ try {
390
+ // Clamp to reasonable limits
391
+ const clampedWidth = Math.max(320, Math.min(7680, width));
392
+ const clampedHeight = Math.max(240, Math.min(4320, height));
393
+
394
+ // Use console command directly instead of Python
395
+ const command = `r.SetRes ${clampedWidth}x${clampedHeight}`;
396
+ await this.bridge.executeConsoleCommand(command);
397
+
398
+ return {
399
+ success: true,
400
+ message: `Viewport resolution set to ${clampedWidth}x${clampedHeight}`,
401
+ width: clampedWidth,
402
+ height: clampedHeight
403
+ };
404
+ } catch (err) {
405
+ return { success: false, error: `Failed to set viewport resolution: ${err}` };
406
+ }
407
+ }
408
+
409
+ async executeConsoleCommand(command: string): Promise<StandardActionResponse> {
410
+ try {
411
+ // Sanitize and validate command
412
+ if (!command || typeof command !== 'string') {
413
+ return { success: false, error: 'Invalid command: must be a non-empty string' };
414
+ }
415
+
416
+ if (command.length > 1000) {
417
+ return {
418
+ success: false,
419
+ error: `Command too long (${command.length} chars). Maximum is 1000 characters.`
420
+ };
421
+ }
422
+
423
+ const res = await this.bridge.executeConsoleCommand(command);
424
+
425
+ return {
426
+ success: true,
427
+ message: `Console command executed: ${command}`,
428
+ output: res
429
+ };
430
+ } catch (err) {
431
+ return { success: false, error: `Failed to execute console command: ${err}` };
432
+ }
433
+ }
358
434
  }