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,797 +1,258 @@
1
- import { Logger } from '../utils/logger.js';
2
- import { interpretStandardResult } from '../utils/result-helpers.js';
3
- export class SequenceTools {
4
- bridge;
5
- log = new Logger('SequenceTools');
1
+ import { BaseTool } from './base-tool.js';
2
+ import { wasmIntegration } from '../wasm/index.js';
3
+ export class SequenceTools extends BaseTool {
6
4
  sequenceCache = new Map();
7
- retryAttempts = 3;
8
- retryDelay = 1000;
9
- constructor(bridge) {
10
- this.bridge = bridge;
11
- }
12
- async ensureSequencerPrerequisites(operation) {
13
- const missing = await this.bridge.ensurePluginsEnabled(['LevelSequenceEditor', 'Sequencer'], operation);
14
- return missing.length ? missing : null;
15
- }
16
- /**
17
- * Execute with retry logic for transient failures
18
- */
19
- async executeWithRetry(operation, operationName) {
20
- let lastError;
21
- for (let attempt = 1; attempt <= this.retryAttempts; attempt++) {
22
- try {
23
- return await operation();
24
- }
25
- catch (error) {
26
- lastError = error;
27
- this.log.warn(`${operationName} attempt ${attempt} failed: ${error.message || error}`);
28
- if (attempt < this.retryAttempts) {
29
- await new Promise(resolve => setTimeout(resolve, this.retryDelay * attempt));
30
- }
31
- }
5
+ activeSequencePath;
6
+ resolveSequencePath(explicitPath) {
7
+ if (typeof explicitPath === 'string' && explicitPath.trim().length > 0) {
8
+ return explicitPath.trim();
32
9
  }
33
- throw lastError;
10
+ return this.activeSequencePath;
34
11
  }
35
- /**
36
- * Parse Python execution result with better error handling
37
- */
38
- parsePythonResult(resp, operationName) {
39
- const interpreted = interpretStandardResult(resp, {
40
- successMessage: `${operationName} succeeded`,
41
- failureMessage: `${operationName} failed`
42
- });
43
- if (interpreted.success) {
44
- return {
45
- ...interpreted.payload,
46
- success: true
47
- };
48
- }
49
- const baseError = interpreted.error ?? `${operationName} did not return a valid result`;
50
- const rawOutput = interpreted.rawText ?? '';
51
- const cleanedOutput = interpreted.cleanText && interpreted.cleanText.trim().length > 0
52
- ? interpreted.cleanText.trim()
53
- : baseError;
54
- if (rawOutput.includes('ModuleNotFoundError')) {
55
- return { success: false, error: 'Sequencer module not available. Ensure Sequencer is enabled.' };
12
+ async sendAction(action, payload = {}, timeoutMs) {
13
+ const envDefault = Number(process.env.MCP_AUTOMATION_REQUEST_TIMEOUT_MS ?? '120000');
14
+ const defaultTimeout = Number.isFinite(envDefault) && envDefault > 0 ? envDefault : 120000;
15
+ const finalTimeout = typeof timeoutMs === 'number' && timeoutMs > 0 ? timeoutMs : defaultTimeout;
16
+ try {
17
+ const response = await this.sendAutomationRequest(action, payload, { timeoutMs: finalTimeout, waitForEvent: false });
18
+ const success = response && response.success !== false;
19
+ const result = response.result ?? response;
20
+ return { success, message: response.message ?? undefined, error: response.success === false ? (response.error ?? response.message) : undefined, result, requestId: response.requestId };
56
21
  }
57
- if (rawOutput.includes('AttributeError')) {
58
- return { success: false, error: 'Sequencer API method not found. Check Unreal Engine version compatibility.' };
22
+ catch (err) {
23
+ return { success: false, error: String(err), message: String(err) };
59
24
  }
60
- this.log.error(`${operationName} returned no parsable result: ${cleanedOutput}`);
61
- return {
62
- success: false,
63
- error: (() => {
64
- const detail = cleanedOutput === baseError
65
- ? ''
66
- : (cleanedOutput ?? '').substring(0, 200).trim();
67
- return detail ? `${baseError}: ${detail}` : baseError;
68
- })()
69
- };
25
+ }
26
+ isUnknownActionResponse(res) {
27
+ if (!res)
28
+ return false;
29
+ const txt = String((res.error ?? res.message ?? '')).toLowerCase();
30
+ return txt.includes('unknown_action') || txt.includes('unknown automation action') || txt.includes('not_implemented') || txt === 'unknown_plugin_action';
70
31
  }
71
32
  async create(params) {
72
33
  const name = params.name?.trim();
73
34
  const base = (params.path || '/Game/Sequences').replace(/\/$/, '');
74
35
  if (!name)
75
36
  return { success: false, error: 'name is required' };
76
- const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.create');
77
- if (missingPlugins?.length) {
78
- const sequencePath = `${base}/${name}`;
79
- this.log.warn('Sequencer plugins missing for create; returning simulated success', { missingPlugins, sequencePath });
80
- return {
81
- success: true,
82
- simulated: true,
83
- sequencePath,
84
- message: 'Sequencer plugins disabled; reported simulated sequence creation.',
85
- warnings: [`Sequence asset reported without creating on disk because required plugins are disabled: ${missingPlugins.join(', ')}`]
86
- };
37
+ const payload = { name, path: base };
38
+ const resp = await this.sendAction('sequence_create', payload, params.timeoutMs);
39
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
40
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_create' };
87
41
  }
88
- const py = `
89
- import unreal, json
90
- name = r"${name}"
91
- base = r"${base}"
92
- full = f"{base}/{name}"
93
- try:
94
- # Ensure directory exists
95
- try:
96
- if not unreal.EditorAssetLibrary.does_directory_exist(base):
97
- unreal.EditorAssetLibrary.make_directory(base)
98
- except Exception:
99
- pass
100
-
101
- if unreal.EditorAssetLibrary.does_asset_exist(full):
102
- print('RESULT:' + json.dumps({'success': True, 'sequencePath': full, 'existing': True}))
103
- else:
104
- asset_tools = unreal.AssetToolsHelpers.get_asset_tools()
105
- factory = unreal.LevelSequenceFactoryNew()
106
- seq = asset_tools.create_asset(asset_name=name, package_path=base, asset_class=unreal.LevelSequence, factory=factory)
107
- if seq:
108
- unreal.EditorAssetLibrary.save_asset(full)
109
- print('RESULT:' + json.dumps({'success': True, 'sequencePath': full}))
110
- else:
111
- print('RESULT:' + json.dumps({'success': False, 'error': 'Create returned None'}))
112
- except Exception as e:
113
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
114
- `.trim();
115
- const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'createSequence');
116
- const result = this.parsePythonResult(resp, 'createSequence');
117
- // Cache the sequence if successful
118
- if (result.success && result.sequencePath) {
119
- const sequence = {
120
- path: result.sequencePath,
121
- name: name
122
- };
42
+ if (resp.success && resp.result && resp.result.sequencePath) {
43
+ const sequence = { path: resp.result.sequencePath, name };
123
44
  this.sequenceCache.set(sequence.path, sequence);
45
+ return { ...resp, sequence: resp.result.sequencePath };
124
46
  }
125
- return result;
47
+ return resp;
126
48
  }
127
49
  async open(params) {
128
- const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.open');
129
- if (missingPlugins) {
130
- return {
131
- success: false,
132
- error: `Required Unreal plugins are not enabled: ${missingPlugins.join(', ')}`
133
- };
50
+ const path = params.path?.trim();
51
+ const resp = await this.sendAction('sequence_open', { path });
52
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
53
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_open' };
54
+ }
55
+ if (resp && resp.success !== false && path) {
56
+ this.activeSequencePath = path;
134
57
  }
135
- const py = `
136
- import unreal, json
137
- path = r"${params.path}"
138
- try:
139
- seq = unreal.load_asset(path)
140
- if not seq:
141
- print('RESULT:' + json.dumps({'success': False, 'error': 'Sequence not found'}))
142
- else:
143
- unreal.LevelSequenceEditorBlueprintLibrary.open_level_sequence(seq)
144
- print('RESULT:' + json.dumps({'success': True, 'sequencePath': path}))
145
- except Exception as e:
146
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
147
- `.trim();
148
- const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'openSequence');
149
- return this.parsePythonResult(resp, 'openSequence');
58
+ return resp;
150
59
  }
151
60
  async addCamera(params) {
152
- const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.addCamera');
153
- if (missingPlugins?.length) {
154
- this.log.warn('Sequencer plugins missing for addCamera; returning simulated success', { missingPlugins });
155
- return {
156
- success: true,
157
- simulated: true,
158
- cameraBindingId: 'simulated_camera',
159
- cameraName: 'SimulatedCamera',
160
- warnings: [`Camera binding simulated because required plugins are disabled: ${missingPlugins.join(', ')}`]
161
- };
61
+ const path = this.resolveSequencePath(params.path);
62
+ const resp = await this.sendAction('sequence_add_camera', { path, spawnable: params.spawnable !== false });
63
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
64
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_add_camera' };
162
65
  }
163
- const py = `
164
- import unreal, json
165
- try:
166
- ls = unreal.get_editor_subsystem(unreal.LevelSequenceEditorSubsystem)
167
- if not ls:
168
- print('RESULT:' + json.dumps({'success': False, 'error': 'LevelSequenceEditorSubsystem unavailable'}))
169
- else:
170
- # create_camera returns tuple: (binding_proxy, camera_actor)
171
- result = ls.create_camera(spawnable=${params.spawnable !== false ? 'True' : 'False'})
172
- binding_id = ''
173
- camera_name = ''
174
-
175
- if result and len(result) >= 2:
176
- binding_proxy = result[0]
177
- camera_actor = result[1]
178
-
179
- # Get the current sequence
180
- seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence()
181
-
182
- if seq and binding_proxy:
183
- try:
184
- # Get GUID directly from binding proxy - this is more reliable
185
- binding_guid = unreal.MovieSceneBindingExtensions.get_id(binding_proxy)
186
- # The GUID itself is what we need
187
- binding_id = str(binding_guid).replace('<Guid ', '').replace('>', '').split(' ')[0] if str(binding_guid).startswith('<') else str(binding_guid)
188
-
189
- # If that didn't work, try the binding object
190
- if binding_id.startswith('<') or not binding_id:
191
- binding_obj = unreal.MovieSceneSequenceExtensions.get_binding_id(seq, binding_proxy)
192
- # Try to extract GUID from the object representation
193
- obj_str = str(binding_obj)
194
- if 'guid=' in obj_str:
195
- binding_id = obj_str.split('guid=')[1].split(',')[0].split('}')[0].strip()
196
- elif hasattr(binding_obj, 'guid'):
197
- binding_id = str(binding_obj.guid)
198
- else:
199
- # Use a hash of the binding for a consistent ID
200
- import hashlib
201
- binding_id = hashlib.md5(str(binding_proxy).encode()).hexdigest()[:8]
202
- except Exception as e:
203
- # Generate a unique ID based on camera
204
- import hashlib
205
- camera_str = camera_actor.get_name() if camera_actor else 'spawned'
206
- binding_id = f'cam_{hashlib.md5(camera_str.encode()).hexdigest()[:8]}'
207
-
208
- if camera_actor:
209
- try:
210
- camera_name = camera_actor.get_actor_label()
211
- except:
212
- camera_name = 'CineCamera'
213
-
214
- print('RESULT:' + json.dumps({
215
- 'success': True,
216
- 'cameraBindingId': binding_id,
217
- 'cameraName': camera_name
218
- }))
219
- else:
220
- # Even if result format is different, camera might still be created
221
- print('RESULT:' + json.dumps({
222
- 'success': True,
223
- 'cameraBindingId': 'camera_created',
224
- 'warning': 'Camera created but binding format unexpected'
225
- }))
226
- except Exception as e:
227
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
228
- `.trim();
229
- const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'addCamera');
230
- return this.parsePythonResult(resp, 'addCamera');
66
+ return resp;
231
67
  }
232
68
  async addActor(params) {
233
- const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.addActor');
234
- if (missingPlugins) {
235
- return {
236
- success: false,
237
- error: `Required Unreal plugins are not enabled: ${missingPlugins.join(', ')}`
238
- };
69
+ const path = this.resolveSequencePath(params.path);
70
+ const resp = await this.sendAction('sequence_add_actor', { path, actorName: params.actorName, createBinding: params.createBinding });
71
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
72
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_add_actor' };
239
73
  }
240
- const py = `
241
- import unreal, json
242
- try:
243
- actor_sub = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
244
- ls = unreal.get_editor_subsystem(unreal.LevelSequenceEditorSubsystem)
245
- if not ls or not actor_sub:
246
- print('RESULT:' + json.dumps({'success': False, 'error': 'Subsystem unavailable'}))
247
- else:
248
- target = None
249
- actors = actor_sub.get_all_level_actors()
250
- for a in actors:
251
- if not a: continue
252
- label = a.get_actor_label()
253
- name = a.get_name()
254
- # Check label, name, and partial matches
255
- if label == r"${params.actorName}" or name == r"${params.actorName}" or label.startswith(r"${params.actorName}"):
256
- target = a
257
- break
258
-
259
- if not target:
260
- # Try to find any actors to debug
261
- actor_info = []
262
- for a in actors[:5]:
263
- if a:
264
- actor_info.append({'label': a.get_actor_label(), 'name': a.get_name()})
265
- print('RESULT:' + json.dumps({'success': False, 'error': f'Actor "${params.actorName}" not found. Sample actors: {actor_info}'}))
266
- else:
267
- # Make sure we have a focused sequence
268
- seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence()
269
- if seq:
270
- # Use add_actors method which returns binding proxies
271
- bindings = ls.add_actors([target])
272
- binding_info = []
273
-
274
- # bindings might be a list or might be empty if actor already exists
275
- if bindings and len(bindings) > 0:
276
- for binding in bindings:
277
- try:
278
- # Get binding name and GUID
279
- binding_name = unreal.MovieSceneBindingExtensions.get_name(binding)
280
- binding_guid = unreal.MovieSceneBindingExtensions.get_id(binding)
281
-
282
- # Extract clean GUID string
283
- guid_str = str(binding_guid)
284
- if guid_str.startswith('<Guid '):
285
- # Extract the actual GUID value from <Guid 'XXXX-XXXX-XXXX-XXXX'>
286
- guid_clean = guid_str.replace('<Guid ', '').replace('>', '').replace("'", '').split(' ')[0]
287
- else:
288
- guid_clean = guid_str
289
-
290
- binding_info.append({
291
- 'id': guid_clean,
292
- 'guid': guid_clean,
293
- 'name': binding_name if binding_name else target.get_actor_label()
294
- })
295
- except Exception as e:
296
- # If binding methods fail, still count it
297
- binding_info.append({
298
- 'id': 'binding_' + str(len(binding_info)),
299
- 'name': target.get_actor_label(),
300
- 'error': str(e)
301
- })
302
-
303
- print('RESULT:' + json.dumps({
304
- 'success': True,
305
- 'count': len(bindings),
306
- 'actorAdded': target.get_actor_label(),
307
- 'bindings': binding_info
308
- }))
309
- else:
310
- # Actor was likely added but no new binding returned (might already exist)
311
- # Still report success since the actor is in the sequence
312
- print('RESULT:' + json.dumps({
313
- 'success': True,
314
- 'count': 1,
315
- 'actorAdded': target.get_actor_label(),
316
- 'bindings': [{'name': target.get_actor_label(), 'note': 'Actor added to sequence'}],
317
- 'info': 'Actor processed successfully'
318
- }))
319
- else:
320
- print('RESULT:' + json.dumps({'success': False, 'error': 'No sequence is currently focused'}))
321
- except Exception as e:
322
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
323
- `.trim();
324
- const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'addActor');
325
- return this.parsePythonResult(resp, 'addActor');
74
+ return resp;
326
75
  }
327
- /**
328
- * Play the current level sequence
329
- */
330
76
  async play(params) {
331
- const loop = params?.loopMode || '';
332
- const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.play');
333
- if (missingPlugins) {
334
- this.log.warn('Sequencer plugins missing for play; returning simulated success', { missingPlugins, loopMode: loop });
335
- return {
336
- success: true,
337
- simulated: true,
338
- playing: true,
339
- loopMode: loop || 'default',
340
- warnings: [`Playback simulated because required plugins are disabled: ${missingPlugins.join(', ')}`],
341
- message: 'Sequencer plugins disabled; playback simulated.'
342
- };
77
+ const path = this.resolveSequencePath(params?.path);
78
+ const resp = await this.sendAction('sequence_play', { path, startTime: params?.startTime, loopMode: params?.loopMode });
79
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
80
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_play' };
343
81
  }
344
- const py = `
345
- import unreal, json
346
-
347
- # Helper to resolve SequencerLoopMode from a friendly string
348
- def _resolve_loop_mode(mode_str):
349
- try:
350
- m = str(mode_str).lower()
351
- slm = unreal.SequencerLoopMode
352
- if m in ('once','noloop','no_loop'):
353
- return getattr(slm, 'SLM_NoLoop', getattr(slm, 'NoLoop'))
354
- if m in ('loop',):
355
- return getattr(slm, 'SLM_Loop', getattr(slm, 'Loop'))
356
- if m in ('pingpong','ping_pong'):
357
- return getattr(slm, 'SLM_PingPong', getattr(slm, 'PingPong'))
358
- except Exception:
359
- pass
360
- return None
361
-
362
- try:
363
- unreal.LevelSequenceEditorBlueprintLibrary.play()
364
- loop_mode = _resolve_loop_mode('${loop}')
365
- if loop_mode is not None:
366
- unreal.LevelSequenceEditorBlueprintLibrary.set_loop_mode(loop_mode)
367
- print('RESULT:' + json.dumps({'success': True, 'playing': True, 'loopMode': '${loop || 'default'}'}))
368
- except Exception as e:
369
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
370
- `.trim();
371
- const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'playSequence');
372
- return this.parsePythonResult(resp, 'playSequence');
82
+ return resp;
373
83
  }
374
- /**
375
- * Pause the current level sequence
376
- */
377
- async pause() {
378
- const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.pause');
379
- if (missingPlugins) {
380
- return {
381
- success: false,
382
- error: `Required Unreal plugins are not enabled: ${missingPlugins.join(', ')}`
383
- };
84
+ async pause(params) {
85
+ const path = this.resolveSequencePath(params?.path);
86
+ const resp = await this.sendAction('sequence_pause', { path });
87
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
88
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_pause' };
384
89
  }
385
- const py = `
386
- import unreal, json
387
- try:
388
- unreal.LevelSequenceEditorBlueprintLibrary.pause()
389
- print('RESULT:' + json.dumps({'success': True, 'paused': True}))
390
- except Exception as e:
391
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
392
- `.trim();
393
- const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'pauseSequence');
394
- return this.parsePythonResult(resp, 'pauseSequence');
90
+ return resp;
395
91
  }
396
- /**
397
- * Stop/close the current level sequence
398
- */
399
- async stop() {
400
- const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.stop');
401
- if (missingPlugins) {
402
- return {
403
- success: false,
404
- error: `Required Unreal plugins are not enabled: ${missingPlugins.join(', ')}`
405
- };
92
+ async stop(params) {
93
+ const path = this.resolveSequencePath(params?.path);
94
+ const resp = await this.sendAction('sequence_stop', { path });
95
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
96
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_stop' };
406
97
  }
407
- const py = `
408
- import unreal, json
409
- try:
410
- unreal.LevelSequenceEditorBlueprintLibrary.close_level_sequence()
411
- print('RESULT:' + json.dumps({'success': True, 'stopped': True}))
412
- except Exception as e:
413
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
414
- `.trim();
415
- const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'stopSequence');
416
- return this.parsePythonResult(resp, 'stopSequence');
98
+ return resp;
417
99
  }
418
- /**
419
- * Set sequence properties including frame rate and length
420
- */
421
100
  async setSequenceProperties(params) {
422
- const missingPlugins = await this.ensureSequencerPrerequisites('SequenceTools.setSequenceProperties');
423
- if (missingPlugins) {
424
- this.log.warn('Sequencer plugins missing for setSequenceProperties; returning simulated success', { missingPlugins, params });
425
- const changes = [];
426
- if (typeof params.frameRate === 'number') {
427
- changes.push({ property: 'frameRate', value: params.frameRate });
428
- }
429
- if (typeof params.lengthInFrames === 'number') {
430
- changes.push({ property: 'lengthInFrames', value: params.lengthInFrames });
431
- }
432
- if (params.playbackStart !== undefined || params.playbackEnd !== undefined) {
433
- changes.push({
434
- property: 'playbackRange',
435
- start: params.playbackStart,
436
- end: params.playbackEnd
437
- });
438
- }
439
- return {
440
- success: true,
441
- simulated: true,
442
- message: 'Sequencer plugins disabled; property update simulated.',
443
- warnings: [`Property update simulated because required plugins are disabled: ${missingPlugins.join(', ')}`],
444
- changes,
445
- finalProperties: {
446
- frameRate: params.frameRate ? { numerator: params.frameRate, denominator: 1 } : undefined,
447
- playbackStart: params.playbackStart,
448
- playbackEnd: params.playbackEnd,
449
- duration: params.lengthInFrames
450
- }
451
- };
101
+ const payload = {
102
+ path: params.path,
103
+ frameRate: params.frameRate,
104
+ lengthInFrames: params.lengthInFrames,
105
+ playbackStart: params.playbackStart,
106
+ playbackEnd: params.playbackEnd
107
+ };
108
+ const resp = await this.sendAction('sequence_set_properties', payload);
109
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
110
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_set_properties' };
452
111
  }
453
- const py = `
454
- import unreal, json
455
- try:
456
- # Load the sequence
457
- seq_path = r"${params.path || ''}"
458
- if seq_path:
459
- seq = unreal.load_asset(seq_path)
460
- else:
461
- # Try to get the currently open sequence
462
- seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence()
463
-
464
- if not seq:
465
- print('RESULT:' + json.dumps({'success': False, 'error': 'No sequence found or loaded'}))
466
- else:
467
- result = {'success': True, 'changes': []}
468
-
469
- # Set frame rate if provided
470
- ${params.frameRate ? `
471
- frame_rate = unreal.FrameRate(numerator=${params.frameRate}, denominator=1)
472
- unreal.MovieSceneSequenceExtensions.set_display_rate(seq, frame_rate)
473
- result['changes'].append({'property': 'frameRate', 'value': ${params.frameRate}})
474
- ` : ''}
475
-
476
- # Set playback range if provided
477
- ${(params.playbackStart !== undefined || params.playbackEnd !== undefined) ? `
478
- current_range = unreal.MovieSceneSequenceExtensions.get_playback_range(seq)
479
- start = ${params.playbackStart !== undefined ? params.playbackStart : 'current_range.get_start_frame()'}
480
- end = ${params.playbackEnd !== undefined ? params.playbackEnd : 'current_range.get_end_frame()'}
481
- # Use set_playback_start and set_playback_end instead
482
- if ${params.playbackStart !== undefined}:
483
- unreal.MovieSceneSequenceExtensions.set_playback_start(seq, ${params.playbackStart})
484
- if ${params.playbackEnd !== undefined}:
485
- unreal.MovieSceneSequenceExtensions.set_playback_end(seq, ${params.playbackEnd})
486
- result['changes'].append({'property': 'playbackRange', 'start': start, 'end': end})
487
- ` : ''}
488
-
489
- # Set total length in frames if provided
490
- ${params.lengthInFrames ? `
491
- # This sets the playback end to match the desired length
492
- start = unreal.MovieSceneSequenceExtensions.get_playback_start(seq)
493
- end = start + ${params.lengthInFrames}
494
- unreal.MovieSceneSequenceExtensions.set_playback_end(seq, end)
495
- result['changes'].append({'property': 'lengthInFrames', 'value': ${params.lengthInFrames}})
496
- ` : ''}
497
-
498
- # Get final properties for confirmation
499
- final_rate = unreal.MovieSceneSequenceExtensions.get_display_rate(seq)
500
- final_range = unreal.MovieSceneSequenceExtensions.get_playback_range(seq)
501
- result['finalProperties'] = {
502
- 'frameRate': {'numerator': final_rate.numerator, 'denominator': final_rate.denominator},
503
- 'playbackStart': final_range.get_start_frame(),
504
- 'playbackEnd': final_range.get_end_frame(),
505
- 'duration': final_range.get_end_frame() - final_range.get_start_frame()
112
+ return resp;
113
+ }
114
+ async setDisplayRate(params) {
115
+ const resp = await this.sendAction('sequence_set_display_rate', { path: params.path, frameRate: params.frameRate });
116
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
117
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_set_display_rate' };
506
118
  }
507
-
508
- print('RESULT:' + json.dumps(result))
509
- except Exception as e:
510
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
511
- `.trim();
512
- const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'setSequenceProperties');
513
- return this.parsePythonResult(resp, 'setSequenceProperties');
119
+ return resp;
514
120
  }
515
- /**
516
- * Get sequence properties
517
- */
518
121
  async getSequenceProperties(params) {
519
- const py = `
520
- import unreal, json
521
- try:
522
- # Load the sequence
523
- seq_path = r"${params.path || ''}"
524
- if seq_path:
525
- seq = unreal.load_asset(seq_path)
526
- else:
527
- # Try to get the currently open sequence
528
- seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence()
529
-
530
- if not seq:
531
- print('RESULT:' + json.dumps({'success': False, 'error': 'No sequence found or loaded'}))
532
- else:
533
- # Get all properties
534
- display_rate = unreal.MovieSceneSequenceExtensions.get_display_rate(seq)
535
- playback_range = unreal.MovieSceneSequenceExtensions.get_playback_range(seq)
536
-
537
- # Get marked frames if any
538
- marked_frames = []
539
- try:
540
- frames = unreal.MovieSceneSequenceExtensions.get_marked_frames(seq)
541
- marked_frames = [{'frame': f.frame_number.value, 'label': f.label} for f in frames]
542
- except:
543
- pass
544
-
545
- result = {
546
- 'success': True,
547
- 'path': seq.get_path_name(),
548
- 'name': seq.get_name(),
549
- 'frameRate': {
550
- 'numerator': display_rate.numerator,
551
- 'denominator': display_rate.denominator,
552
- 'fps': float(display_rate.numerator) / float(display_rate.denominator) if display_rate.denominator > 0 else 0
553
- },
554
- 'playbackStart': playback_range.get_start_frame(),
555
- 'playbackEnd': playback_range.get_end_frame(),
556
- 'duration': playback_range.get_end_frame() - playback_range.get_start_frame(),
557
- 'markedFrames': marked_frames
122
+ const resp = await this.sendAction('sequence_get_properties', { path: params.path });
123
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
124
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_get_properties' };
558
125
  }
559
-
560
- print('RESULT:' + json.dumps(result))
561
- except Exception as e:
562
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
563
- `.trim();
564
- const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'getSequenceProperties');
565
- return this.parsePythonResult(resp, 'getSequenceProperties');
126
+ return resp;
566
127
  }
567
- /**
568
- * Set playback speed/rate
569
- */
570
128
  async setPlaybackSpeed(params) {
571
- const py = `
572
- import unreal, json
573
- try:
574
- unreal.LevelSequenceEditorBlueprintLibrary.set_playback_speed(${params.speed})
575
- print('RESULT:' + json.dumps({'success': True, 'playbackSpeed': ${params.speed}}))
576
- except Exception as e:
577
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
578
- `.trim();
579
- const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'setPlaybackSpeed');
580
- return this.parsePythonResult(resp, 'setPlaybackSpeed');
129
+ const path = this.resolveSequencePath(params.path);
130
+ const resp = await this.sendAction('sequence_set_playback_speed', { path, speed: params.speed });
131
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
132
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_set_playback_speed' };
133
+ }
134
+ return resp;
581
135
  }
582
- /**
583
- * Get all bindings in the current sequence
584
- */
585
136
  async getBindings(params) {
586
- const py = `
587
- import unreal, json
588
- try:
589
- # Load the sequence
590
- seq_path = r"${params?.path || ''}"
591
- if seq_path:
592
- seq = unreal.load_asset(seq_path)
593
- else:
594
- # Try to get the currently open sequence
595
- seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence()
596
-
597
- if not seq:
598
- print('RESULT:' + json.dumps({'success': False, 'error': 'No sequence found or loaded'}))
599
- else:
600
- bindings = unreal.MovieSceneSequenceExtensions.get_bindings(seq)
601
- binding_list = []
602
- for binding in bindings:
603
- try:
604
- binding_name = unreal.MovieSceneBindingExtensions.get_name(binding)
605
- binding_guid = unreal.MovieSceneBindingExtensions.get_id(binding)
606
-
607
- # Extract clean GUID string
608
- guid_str = str(binding_guid)
609
- if guid_str.startswith('<Guid '):
610
- # Extract the actual GUID value from <Guid 'XXXX-XXXX-XXXX-XXXX'>
611
- guid_clean = guid_str.replace('<Guid ', '').replace('>', '').replace("'", '').split(' ')[0]
612
- else:
613
- guid_clean = guid_str
614
-
615
- binding_list.append({
616
- 'id': guid_clean,
617
- 'name': binding_name,
618
- 'guid': guid_clean
619
- })
620
- except:
621
- pass
622
-
623
- print('RESULT:' + json.dumps({'success': True, 'bindings': binding_list, 'count': len(binding_list)}))
624
- except Exception as e:
625
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
626
- `.trim();
627
- const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'getBindings');
628
- return this.parsePythonResult(resp, 'getBindings');
137
+ const resp = await this.sendAction('sequence_get_bindings', { path: params?.path });
138
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
139
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_get_bindings' };
140
+ }
141
+ return resp;
629
142
  }
630
- /**
631
- * Add multiple actors to sequence at once
632
- */
633
143
  async addActors(params) {
634
- const py = `
635
- import unreal, json
636
- try:
637
- actor_sub = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
638
- ls = unreal.get_editor_subsystem(unreal.LevelSequenceEditorSubsystem)
639
- if not ls or not actor_sub:
640
- print('RESULT:' + json.dumps({'success': False, 'error': 'Subsystem unavailable'}))
641
- else:
642
- actor_names = ${JSON.stringify(params.actorNames)}
643
- actors_to_add = []
644
- not_found = []
645
-
646
- all_actors = actor_sub.get_all_level_actors()
647
- for name in actor_names:
648
- found = False
649
- for a in all_actors:
650
- if not a: continue
651
- label = a.get_actor_label()
652
- actor_name = a.get_name()
653
- if label == name or actor_name == name or label.startswith(name):
654
- actors_to_add.append(a)
655
- found = True
656
- break
657
- if not found:
658
- not_found.append(name)
659
-
660
- # Make sure we have a focused sequence
661
- seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence()
662
- if not seq:
663
- print('RESULT:' + json.dumps({'success': False, 'error': 'No sequence is currently focused'}))
664
- elif len(actors_to_add) == 0:
665
- print('RESULT:' + json.dumps({'success': False, 'error': f'No actors found: {not_found}'}))
666
- else:
667
- # Add all actors at once
668
- bindings = ls.add_actors(actors_to_add)
669
- added_actors = [a.get_actor_label() for a in actors_to_add]
670
- print('RESULT:' + json.dumps({
671
- 'success': True,
672
- 'count': len(bindings) if bindings else len(actors_to_add),
673
- 'actorsAdded': added_actors,
674
- 'notFound': not_found
675
- }))
676
- except Exception as e:
677
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
678
- `.trim();
679
- const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'addActors');
680
- return this.parsePythonResult(resp, 'addActors');
144
+ const path = this.resolveSequencePath(params.path);
145
+ const resp = await this.sendAction('sequence_add_actors', { path, actorNames: params.actorNames });
146
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
147
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_add_actors' };
148
+ }
149
+ return resp;
681
150
  }
682
- /**
683
- * Remove actors from binding
684
- */
685
151
  async removeActors(params) {
686
- const py = `
687
- import unreal, json
688
- try:
689
- actor_sub = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
690
- ls = unreal.get_editor_subsystem(unreal.LevelSequenceEditorSubsystem)
691
-
692
- if not ls or not actor_sub:
693
- print('RESULT:' + json.dumps({'success': False, 'error': 'Subsystem unavailable'}))
694
- else:
695
- # Get current sequence
696
- seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence()
697
- if not seq:
698
- print('RESULT:' + json.dumps({'success': False, 'error': 'No sequence is currently focused'}))
699
- else:
700
- actor_names = ${JSON.stringify(params.actorNames)}
701
- actors_to_remove = []
702
-
703
- all_actors = actor_sub.get_all_level_actors()
704
- for name in actor_names:
705
- for a in all_actors:
706
- if not a: continue
707
- label = a.get_actor_label()
708
- actor_name = a.get_name()
709
- if label == name or actor_name == name:
710
- actors_to_remove.append(a)
711
- break
712
-
713
- # Get all bindings and remove matching actors
714
- bindings = unreal.MovieSceneSequenceExtensions.get_bindings(seq)
715
- removed_count = 0
716
- for binding in bindings:
717
- try:
718
- ls.remove_actors_from_binding(actors_to_remove, binding)
719
- removed_count += 1
720
- except:
721
- pass
722
-
723
- print('RESULT:' + json.dumps({
724
- 'success': True,
725
- 'removedActors': [a.get_actor_label() for a in actors_to_remove],
726
- 'bindingsProcessed': removed_count
727
- }))
728
- except Exception as e:
729
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
730
- `.trim();
731
- const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'removeActors');
732
- return this.parsePythonResult(resp, 'removeActors');
152
+ const path = this.resolveSequencePath(params.path);
153
+ const resp = await this.sendAction('sequence_remove_actors', { path, actorNames: params.actorNames });
154
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
155
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_remove_actors' };
156
+ }
157
+ return resp;
733
158
  }
734
- /**
735
- * Create a spawnable from an actor class
736
- */
737
159
  async addSpawnableFromClass(params) {
738
- const py = `
739
- import unreal, json
740
- try:
741
- ls = unreal.get_editor_subsystem(unreal.LevelSequenceEditorSubsystem)
742
-
743
- # Load the sequence
744
- seq_path = r"${params.path || ''}"
745
- if seq_path:
746
- seq = unreal.load_asset(seq_path)
747
- else:
748
- seq = unreal.LevelSequenceEditorBlueprintLibrary.get_focused_level_sequence()
749
-
750
- if not seq:
751
- print('RESULT:' + json.dumps({'success': False, 'error': 'No sequence found'}))
752
- else:
753
- # Try to find the class
754
- class_name = r"${params.className}"
755
- actor_class = None
756
-
757
- # Try common actor classes
758
- if class_name == "StaticMeshActor":
759
- actor_class = unreal.StaticMeshActor
760
- elif class_name == "CineCameraActor":
761
- actor_class = unreal.CineCameraActor
762
- elif class_name == "CameraActor":
763
- actor_class = unreal.CameraActor
764
- elif class_name == "PointLight":
765
- actor_class = unreal.PointLight
766
- elif class_name == "DirectionalLight":
767
- actor_class = unreal.DirectionalLight
768
- elif class_name == "SpotLight":
769
- actor_class = unreal.SpotLight
770
- else:
771
- # Try to load as a blueprint class
772
- try:
773
- actor_class = unreal.EditorAssetLibrary.load_asset(class_name)
774
- except:
775
- pass
776
-
777
- if not actor_class:
778
- print('RESULT:' + json.dumps({'success': False, 'error': f'Class {class_name} not found'}))
779
- else:
780
- spawnable = ls.add_spawnable_from_class(seq, actor_class)
781
- if spawnable:
782
- binding_id = unreal.MovieSceneSequenceExtensions.get_binding_id(seq, spawnable)
783
- print('RESULT:' + json.dumps({
784
- 'success': True,
785
- 'spawnableId': str(binding_id),
786
- 'className': class_name
787
- }))
788
- else:
789
- print('RESULT:' + json.dumps({'success': False, 'error': 'Failed to create spawnable'}))
790
- except Exception as e:
791
- print('RESULT:' + json.dumps({'success': False, 'error': str(e)}))
792
- `.trim();
793
- const resp = await this.executeWithRetry(() => this.bridge.executePython(py), 'addSpawnableFromClass');
794
- return this.parsePythonResult(resp, 'addSpawnableFromClass');
160
+ const resp = await this.sendAction('sequence_add_spawnable_from_class', { className: params.className, path: params.path });
161
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
162
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_add_spawnable_from_class' };
163
+ }
164
+ return resp;
165
+ }
166
+ async list(params) {
167
+ const resp = await this.sendAction('sequence_list', { path: params?.path });
168
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
169
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_list' };
170
+ }
171
+ if (resp.success) {
172
+ const sequences = resp.sequences || resp.data || resp.result || [];
173
+ return {
174
+ ...resp,
175
+ sequences,
176
+ count: Array.isArray(sequences) ? sequences.length : undefined
177
+ };
178
+ }
179
+ return resp;
180
+ }
181
+ async duplicate(params) {
182
+ const resp = await this.sendAction('sequence_duplicate', { path: params.path, destinationPath: params.destinationPath });
183
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
184
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_duplicate' };
185
+ }
186
+ return resp;
187
+ }
188
+ async rename(params) {
189
+ const resp = await this.sendAction('sequence_rename', { path: params.path, newName: params.newName });
190
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
191
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_rename' };
192
+ }
193
+ return resp;
194
+ }
195
+ async deleteSequence(params) {
196
+ const resp = await this.sendAction('sequence_delete', { path: params.path });
197
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
198
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_delete' };
199
+ }
200
+ return resp;
201
+ }
202
+ async getMetadata(params) {
203
+ const resp = await this.sendAction('sequence_get_metadata', { path: params.path });
204
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
205
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_get_metadata' };
206
+ }
207
+ return resp;
208
+ }
209
+ async addKeyframe(params) {
210
+ const resp = await this.sendAction('sequence_add_keyframe', {
211
+ path: params.path,
212
+ bindingId: params.bindingId,
213
+ actorName: params.actorName,
214
+ property: params.property,
215
+ frame: params.frame,
216
+ value: params.value
217
+ });
218
+ if (params.property === 'Transform' && params.value) {
219
+ const loc = params.value.location;
220
+ const rot = params.value.rotation;
221
+ const scale = params.value.scale;
222
+ if (loc && rot && scale) {
223
+ const locArr = [loc.x, loc.y, loc.z];
224
+ const rotArr = [rot.pitch, rot.yaw, rot.roll];
225
+ const scaleArr = [scale.x, scale.y, scale.z];
226
+ wasmIntegration.composeTransform(locArr, rotArr, scaleArr);
227
+ console.error('[WASM] Using composeTransform for keyframe validation');
228
+ }
229
+ }
230
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
231
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_add_keyframe' };
232
+ }
233
+ return resp;
234
+ }
235
+ async listTracks(params) {
236
+ const resp = await this.sendAction('sequence_list_tracks', { path: params.path });
237
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
238
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_list_tracks' };
239
+ }
240
+ if (resp.success) {
241
+ const tracks = resp.tracks || resp.data || resp.result || [];
242
+ return {
243
+ ...resp,
244
+ tracks,
245
+ count: Array.isArray(tracks) ? tracks.length : undefined
246
+ };
247
+ }
248
+ return resp;
249
+ }
250
+ async setWorkRange(params) {
251
+ const resp = await this.sendAction('sequence_set_work_range', { path: params.path, start: params.start, end: params.end });
252
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
253
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_set_work_range' };
254
+ }
255
+ return resp;
795
256
  }
796
257
  }
797
258
  //# sourceMappingURL=sequence.js.map