unreal-engine-mcp-server 0.4.7 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (438) hide show
  1. package/.env.example +26 -0
  2. package/.env.production +38 -7
  3. package/.eslintrc.json +0 -54
  4. package/.eslintrc.override.json +8 -0
  5. package/.github/ISSUE_TEMPLATE/bug_report.yml +94 -0
  6. package/.github/ISSUE_TEMPLATE/config.yml +8 -0
  7. package/.github/ISSUE_TEMPLATE/feature_request.yml +56 -0
  8. package/.github/copilot-instructions.md +478 -45
  9. package/.github/dependabot.yml +19 -0
  10. package/.github/labeler.yml +24 -0
  11. package/.github/labels.yml +70 -0
  12. package/.github/pull_request_template.md +42 -0
  13. package/.github/release-drafter.yml +148 -0
  14. package/.github/workflows/auto-merge.yml +38 -0
  15. package/.github/workflows/ci.yml +38 -0
  16. package/.github/workflows/dependency-review.yml +17 -0
  17. package/.github/workflows/gemini-issue-triage.yml +172 -0
  18. package/.github/workflows/greetings.yml +23 -0
  19. package/.github/workflows/labeler.yml +16 -0
  20. package/.github/workflows/links.yml +80 -0
  21. package/.github/workflows/pr-size-labeler.yml +137 -0
  22. package/.github/workflows/publish-mcp.yml +12 -7
  23. package/.github/workflows/release-drafter.yml +23 -0
  24. package/.github/workflows/release.yml +112 -0
  25. package/.github/workflows/semantic-pull-request.yml +35 -0
  26. package/.github/workflows/smoke-test.yml +36 -0
  27. package/.github/workflows/stale.yml +28 -0
  28. package/CHANGELOG.md +267 -31
  29. package/CONTRIBUTING.md +140 -0
  30. package/README.md +166 -71
  31. package/claude_desktop_config_example.json +7 -6
  32. package/dist/automation/bridge.d.ts +50 -0
  33. package/dist/automation/bridge.js +452 -0
  34. package/dist/automation/connection-manager.d.ts +23 -0
  35. package/dist/automation/connection-manager.js +107 -0
  36. package/dist/automation/handshake.d.ts +11 -0
  37. package/dist/automation/handshake.js +89 -0
  38. package/dist/automation/index.d.ts +3 -0
  39. package/dist/automation/index.js +3 -0
  40. package/dist/automation/message-handler.d.ts +12 -0
  41. package/dist/automation/message-handler.js +149 -0
  42. package/dist/automation/request-tracker.d.ts +25 -0
  43. package/dist/automation/request-tracker.js +98 -0
  44. package/dist/automation/types.d.ts +130 -0
  45. package/dist/automation/types.js +2 -0
  46. package/dist/cli.js +32 -5
  47. package/dist/config.d.ts +27 -0
  48. package/dist/config.js +60 -0
  49. package/dist/constants.d.ts +12 -0
  50. package/dist/constants.js +12 -0
  51. package/dist/graphql/resolvers.d.ts +268 -0
  52. package/dist/graphql/resolvers.js +743 -0
  53. package/dist/graphql/schema.d.ts +5 -0
  54. package/dist/graphql/schema.js +437 -0
  55. package/dist/graphql/server.d.ts +26 -0
  56. package/dist/graphql/server.js +115 -0
  57. package/dist/graphql/types.d.ts +7 -0
  58. package/dist/graphql/types.js +2 -0
  59. package/dist/handlers/resource-handlers.d.ts +20 -0
  60. package/dist/handlers/resource-handlers.js +180 -0
  61. package/dist/index.d.ts +31 -18
  62. package/dist/index.js +119 -619
  63. package/dist/prompts/index.js +4 -4
  64. package/dist/resources/actors.d.ts +17 -12
  65. package/dist/resources/actors.js +56 -76
  66. package/dist/resources/assets.d.ts +6 -14
  67. package/dist/resources/assets.js +115 -147
  68. package/dist/resources/levels.d.ts +13 -13
  69. package/dist/resources/levels.js +25 -34
  70. package/dist/server/resource-registry.d.ts +20 -0
  71. package/dist/server/resource-registry.js +37 -0
  72. package/dist/server/tool-registry.d.ts +23 -0
  73. package/dist/server/tool-registry.js +322 -0
  74. package/dist/server-setup.d.ts +21 -0
  75. package/dist/server-setup.js +111 -0
  76. package/dist/services/health-monitor.d.ts +34 -0
  77. package/dist/services/health-monitor.js +105 -0
  78. package/dist/services/metrics-server.d.ts +11 -0
  79. package/dist/services/metrics-server.js +105 -0
  80. package/dist/tools/actors.d.ts +147 -9
  81. package/dist/tools/actors.js +350 -311
  82. package/dist/tools/animation.d.ts +135 -4
  83. package/dist/tools/animation.js +510 -411
  84. package/dist/tools/assets.d.ts +117 -19
  85. package/dist/tools/assets.js +259 -284
  86. package/dist/tools/audio.d.ts +102 -42
  87. package/dist/tools/audio.js +272 -685
  88. package/dist/tools/base-tool.d.ts +17 -0
  89. package/dist/tools/base-tool.js +46 -0
  90. package/dist/tools/behavior-tree.d.ts +94 -0
  91. package/dist/tools/behavior-tree.js +39 -0
  92. package/dist/tools/blueprint/helpers.d.ts +29 -0
  93. package/dist/tools/blueprint/helpers.js +182 -0
  94. package/dist/tools/blueprint.d.ts +228 -118
  95. package/dist/tools/blueprint.js +685 -832
  96. package/dist/tools/consolidated-tool-definitions.d.ts +5462 -1781
  97. package/dist/tools/consolidated-tool-definitions.js +829 -496
  98. package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
  99. package/dist/tools/consolidated-tool-handlers.js +211 -1026
  100. package/dist/tools/debug.d.ts +143 -85
  101. package/dist/tools/debug.js +234 -180
  102. package/dist/tools/dynamic-handler-registry.d.ts +11 -0
  103. package/dist/tools/dynamic-handler-registry.js +101 -0
  104. package/dist/tools/editor.d.ts +139 -18
  105. package/dist/tools/editor.js +239 -244
  106. package/dist/tools/engine.d.ts +10 -4
  107. package/dist/tools/engine.js +13 -5
  108. package/dist/tools/environment.d.ts +36 -0
  109. package/dist/tools/environment.js +267 -0
  110. package/dist/tools/foliage.d.ts +105 -14
  111. package/dist/tools/foliage.js +219 -331
  112. package/dist/tools/handlers/actor-handlers.d.ts +3 -0
  113. package/dist/tools/handlers/actor-handlers.js +232 -0
  114. package/dist/tools/handlers/animation-handlers.d.ts +3 -0
  115. package/dist/tools/handlers/animation-handlers.js +185 -0
  116. package/dist/tools/handlers/argument-helper.d.ts +16 -0
  117. package/dist/tools/handlers/argument-helper.js +80 -0
  118. package/dist/tools/handlers/asset-handlers.d.ts +3 -0
  119. package/dist/tools/handlers/asset-handlers.js +496 -0
  120. package/dist/tools/handlers/audio-handlers.d.ts +3 -0
  121. package/dist/tools/handlers/audio-handlers.js +166 -0
  122. package/dist/tools/handlers/blueprint-handlers.d.ts +4 -0
  123. package/dist/tools/handlers/blueprint-handlers.js +358 -0
  124. package/dist/tools/handlers/common-handlers.d.ts +14 -0
  125. package/dist/tools/handlers/common-handlers.js +56 -0
  126. package/dist/tools/handlers/editor-handlers.d.ts +3 -0
  127. package/dist/tools/handlers/editor-handlers.js +119 -0
  128. package/dist/tools/handlers/effect-handlers.d.ts +3 -0
  129. package/dist/tools/handlers/effect-handlers.js +171 -0
  130. package/dist/tools/handlers/environment-handlers.d.ts +3 -0
  131. package/dist/tools/handlers/environment-handlers.js +170 -0
  132. package/dist/tools/handlers/graph-handlers.d.ts +3 -0
  133. package/dist/tools/handlers/graph-handlers.js +90 -0
  134. package/dist/tools/handlers/input-handlers.d.ts +3 -0
  135. package/dist/tools/handlers/input-handlers.js +21 -0
  136. package/dist/tools/handlers/inspect-handlers.d.ts +3 -0
  137. package/dist/tools/handlers/inspect-handlers.js +383 -0
  138. package/dist/tools/handlers/level-handlers.d.ts +3 -0
  139. package/dist/tools/handlers/level-handlers.js +237 -0
  140. package/dist/tools/handlers/lighting-handlers.d.ts +3 -0
  141. package/dist/tools/handlers/lighting-handlers.js +144 -0
  142. package/dist/tools/handlers/performance-handlers.d.ts +3 -0
  143. package/dist/tools/handlers/performance-handlers.js +130 -0
  144. package/dist/tools/handlers/pipeline-handlers.d.ts +3 -0
  145. package/dist/tools/handlers/pipeline-handlers.js +110 -0
  146. package/dist/tools/handlers/sequence-handlers.d.ts +3 -0
  147. package/dist/tools/handlers/sequence-handlers.js +376 -0
  148. package/dist/tools/handlers/system-handlers.d.ts +4 -0
  149. package/dist/tools/handlers/system-handlers.js +506 -0
  150. package/dist/tools/input.d.ts +19 -0
  151. package/dist/tools/input.js +89 -0
  152. package/dist/tools/introspection.d.ts +103 -40
  153. package/dist/tools/introspection.js +425 -568
  154. package/dist/tools/landscape.d.ts +97 -36
  155. package/dist/tools/landscape.js +280 -409
  156. package/dist/tools/level.d.ts +130 -10
  157. package/dist/tools/level.js +639 -675
  158. package/dist/tools/lighting.d.ts +77 -38
  159. package/dist/tools/lighting.js +441 -943
  160. package/dist/tools/logs.d.ts +3 -3
  161. package/dist/tools/logs.js +5 -57
  162. package/dist/tools/materials.d.ts +91 -24
  163. package/dist/tools/materials.js +190 -118
  164. package/dist/tools/niagara.d.ts +149 -39
  165. package/dist/tools/niagara.js +232 -182
  166. package/dist/tools/performance.d.ts +27 -12
  167. package/dist/tools/performance.js +204 -122
  168. package/dist/tools/physics.d.ts +32 -77
  169. package/dist/tools/physics.js +171 -582
  170. package/dist/tools/property-dictionary.d.ts +13 -0
  171. package/dist/tools/property-dictionary.js +82 -0
  172. package/dist/tools/sequence.d.ts +73 -48
  173. package/dist/tools/sequence.js +196 -748
  174. package/dist/tools/tool-definition-utils.d.ts +59 -0
  175. package/dist/tools/tool-definition-utils.js +35 -0
  176. package/dist/tools/ui.d.ts +66 -34
  177. package/dist/tools/ui.js +134 -214
  178. package/dist/types/env.d.ts +0 -3
  179. package/dist/types/env.js +0 -7
  180. package/dist/types/tool-interfaces.d.ts +898 -0
  181. package/dist/types/tool-interfaces.js +2 -0
  182. package/dist/types/tool-types.d.ts +183 -19
  183. package/dist/types/tool-types.js +0 -4
  184. package/dist/unreal-bridge.d.ts +24 -131
  185. package/dist/unreal-bridge.js +364 -1506
  186. package/dist/utils/command-validator.d.ts +9 -0
  187. package/dist/utils/command-validator.js +67 -0
  188. package/dist/utils/elicitation.d.ts +1 -1
  189. package/dist/utils/elicitation.js +12 -15
  190. package/dist/utils/error-handler.d.ts +2 -51
  191. package/dist/utils/error-handler.js +11 -87
  192. package/dist/utils/ini-reader.d.ts +3 -0
  193. package/dist/utils/ini-reader.js +69 -0
  194. package/dist/utils/logger.js +9 -6
  195. package/dist/utils/normalize.d.ts +3 -0
  196. package/dist/utils/normalize.js +56 -0
  197. package/dist/utils/response-factory.d.ts +7 -0
  198. package/dist/utils/response-factory.js +33 -0
  199. package/dist/utils/response-validator.d.ts +3 -24
  200. package/dist/utils/response-validator.js +130 -81
  201. package/dist/utils/result-helpers.d.ts +4 -5
  202. package/dist/utils/result-helpers.js +15 -16
  203. package/dist/utils/safe-json.js +5 -11
  204. package/dist/utils/unreal-command-queue.d.ts +24 -0
  205. package/dist/utils/unreal-command-queue.js +120 -0
  206. package/dist/utils/validation.d.ts +0 -40
  207. package/dist/utils/validation.js +1 -78
  208. package/dist/wasm/index.d.ts +70 -0
  209. package/dist/wasm/index.js +535 -0
  210. package/docs/GraphQL-API.md +888 -0
  211. package/docs/Migration-Guide-v0.5.0.md +692 -0
  212. package/docs/Roadmap.md +53 -0
  213. package/docs/WebAssembly-Integration.md +628 -0
  214. package/docs/editor-plugin-extension.md +370 -0
  215. package/docs/handler-mapping.md +242 -0
  216. package/docs/native-automation-progress.md +128 -0
  217. package/docs/testing-guide.md +423 -0
  218. package/mcp-config-example.json +6 -6
  219. package/package.json +60 -27
  220. package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +8 -0
  221. package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +64 -0
  222. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +189 -0
  223. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +22 -0
  224. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +30 -0
  225. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +1983 -0
  226. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +72 -0
  227. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +46 -0
  228. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +581 -0
  229. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +2394 -0
  230. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +300 -0
  231. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +2807 -0
  232. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +1087 -0
  233. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +488 -0
  234. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +643 -0
  235. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +31 -0
  236. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +1184 -0
  237. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +5652 -0
  238. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +152 -0
  239. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +2614 -0
  240. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +42 -0
  241. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +1237 -0
  242. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +1701 -0
  243. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +2145 -0
  244. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +954 -0
  245. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +209 -0
  246. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +41 -0
  247. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +1164 -0
  248. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +762 -0
  249. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +634 -0
  250. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +136 -0
  251. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +494 -0
  252. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +278 -0
  253. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +625 -0
  254. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +401 -0
  255. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +67 -0
  256. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +735 -0
  257. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +2634 -0
  258. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +189 -0
  259. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +917 -0
  260. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +39 -0
  261. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +2670 -0
  262. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +519 -0
  263. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +38 -0
  264. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +668 -0
  265. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +346 -0
  266. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +1330 -0
  267. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +149 -0
  268. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +783 -0
  269. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +115 -0
  270. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +796 -0
  271. package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +117 -0
  272. package/scripts/check-unreal-connection.mjs +19 -0
  273. package/scripts/clean-tmp.js +23 -0
  274. package/scripts/patch-wasm.js +26 -0
  275. package/scripts/run-all-tests.mjs +131 -0
  276. package/scripts/smoke-test.ts +94 -0
  277. package/scripts/sync-mcp-plugin.js +143 -0
  278. package/scripts/test-no-plugin-alternates.mjs +113 -0
  279. package/scripts/validate-server.js +46 -0
  280. package/scripts/verify-automation-bridge.js +200 -0
  281. package/server.json +57 -21
  282. package/src/automation/bridge.ts +558 -0
  283. package/src/automation/connection-manager.ts +130 -0
  284. package/src/automation/handshake.ts +99 -0
  285. package/src/automation/index.ts +2 -0
  286. package/src/automation/message-handler.ts +167 -0
  287. package/src/automation/request-tracker.ts +123 -0
  288. package/src/automation/types.ts +107 -0
  289. package/src/cli.ts +33 -6
  290. package/src/config.ts +73 -0
  291. package/src/constants.ts +12 -0
  292. package/src/graphql/resolvers.ts +1010 -0
  293. package/src/graphql/schema.ts +452 -0
  294. package/src/graphql/server.ts +154 -0
  295. package/src/graphql/types.ts +7 -0
  296. package/src/handlers/resource-handlers.ts +186 -0
  297. package/src/index.ts +152 -663
  298. package/src/prompts/index.ts +4 -4
  299. package/src/resources/actors.ts +58 -76
  300. package/src/resources/assets.ts +147 -134
  301. package/src/resources/levels.ts +28 -33
  302. package/src/server/resource-registry.ts +47 -0
  303. package/src/server/tool-registry.ts +354 -0
  304. package/src/server-setup.ts +148 -0
  305. package/src/services/health-monitor.ts +132 -0
  306. package/src/services/metrics-server.ts +142 -0
  307. package/src/tools/actors.ts +417 -322
  308. package/src/tools/animation.ts +671 -461
  309. package/src/tools/assets.ts +353 -289
  310. package/src/tools/audio.ts +323 -766
  311. package/src/tools/base-tool.ts +52 -0
  312. package/src/tools/behavior-tree.ts +45 -0
  313. package/src/tools/blueprint/helpers.ts +189 -0
  314. package/src/tools/blueprint.ts +787 -965
  315. package/src/tools/consolidated-tool-definitions.ts +993 -515
  316. package/src/tools/consolidated-tool-handlers.ts +272 -1139
  317. package/src/tools/debug.ts +292 -187
  318. package/src/tools/dynamic-handler-registry.ts +151 -0
  319. package/src/tools/editor.ts +309 -246
  320. package/src/tools/engine.ts +14 -3
  321. package/src/tools/environment.ts +287 -0
  322. package/src/tools/foliage.ts +314 -379
  323. package/src/tools/handlers/actor-handlers.ts +271 -0
  324. package/src/tools/handlers/animation-handlers.ts +237 -0
  325. package/src/tools/handlers/argument-helper.ts +142 -0
  326. package/src/tools/handlers/asset-handlers.ts +532 -0
  327. package/src/tools/handlers/audio-handlers.ts +194 -0
  328. package/src/tools/handlers/blueprint-handlers.ts +380 -0
  329. package/src/tools/handlers/common-handlers.ts +87 -0
  330. package/src/tools/handlers/editor-handlers.ts +123 -0
  331. package/src/tools/handlers/effect-handlers.ts +220 -0
  332. package/src/tools/handlers/environment-handlers.ts +183 -0
  333. package/src/tools/handlers/graph-handlers.ts +116 -0
  334. package/src/tools/handlers/input-handlers.ts +28 -0
  335. package/src/tools/handlers/inspect-handlers.ts +450 -0
  336. package/src/tools/handlers/level-handlers.ts +252 -0
  337. package/src/tools/handlers/lighting-handlers.ts +147 -0
  338. package/src/tools/handlers/performance-handlers.ts +132 -0
  339. package/src/tools/handlers/pipeline-handlers.ts +127 -0
  340. package/src/tools/handlers/sequence-handlers.ts +415 -0
  341. package/src/tools/handlers/system-handlers.ts +564 -0
  342. package/src/tools/input.ts +101 -0
  343. package/src/tools/introspection.ts +493 -584
  344. package/src/tools/landscape.ts +394 -489
  345. package/src/tools/level.ts +752 -694
  346. package/src/tools/lighting.ts +583 -984
  347. package/src/tools/logs.ts +9 -57
  348. package/src/tools/materials.ts +231 -121
  349. package/src/tools/niagara.ts +293 -168
  350. package/src/tools/performance.ts +320 -168
  351. package/src/tools/physics.ts +268 -613
  352. package/src/tools/property-dictionary.ts +98 -0
  353. package/src/tools/sequence.ts +255 -815
  354. package/src/tools/tool-definition-utils.ts +35 -0
  355. package/src/tools/ui.ts +207 -283
  356. package/src/types/env.ts +0 -10
  357. package/src/types/tool-interfaces.ts +250 -0
  358. package/src/types/tool-types.ts +243 -21
  359. package/src/unreal-bridge.ts +460 -1550
  360. package/src/utils/command-validator.ts +75 -0
  361. package/src/utils/elicitation.ts +10 -7
  362. package/src/utils/error-handler.ts +14 -90
  363. package/src/utils/ini-reader.ts +86 -0
  364. package/src/utils/logger.ts +8 -3
  365. package/src/utils/normalize.ts +60 -0
  366. package/src/utils/response-factory.ts +39 -0
  367. package/src/utils/response-validator.ts +176 -56
  368. package/src/utils/result-helpers.ts +21 -19
  369. package/src/utils/safe-json.ts +14 -11
  370. package/src/utils/unreal-command-queue.ts +152 -0
  371. package/src/utils/validation.ts +4 -1
  372. package/src/wasm/index.ts +838 -0
  373. package/test-server.mjs +100 -0
  374. package/tests/run-unreal-tool-tests.mjs +242 -14
  375. package/tests/test-animation.mjs +44 -0
  376. package/tests/test-asset-advanced.mjs +82 -0
  377. package/tests/test-asset-errors.mjs +35 -0
  378. package/tests/test-audio.mjs +219 -0
  379. package/tests/test-automation-timeouts.mjs +98 -0
  380. package/tests/test-behavior-tree.mjs +261 -0
  381. package/tests/test-blueprint-events.mjs +35 -0
  382. package/tests/test-blueprint-graph.mjs +79 -0
  383. package/tests/test-blueprint.mjs +577 -0
  384. package/tests/test-client-mode.mjs +86 -0
  385. package/tests/test-console-command.mjs +56 -0
  386. package/tests/test-control-actor.mjs +425 -0
  387. package/tests/test-control-editor.mjs +80 -0
  388. package/tests/test-extra-tools.mjs +38 -0
  389. package/tests/test-graphql.mjs +322 -0
  390. package/tests/test-inspect.mjs +72 -0
  391. package/tests/test-landscape.mjs +60 -0
  392. package/tests/test-manage-asset.mjs +438 -0
  393. package/tests/test-manage-level.mjs +70 -0
  394. package/tests/test-materials.mjs +356 -0
  395. package/tests/test-niagara.mjs +185 -0
  396. package/tests/test-no-inline-python.mjs +122 -0
  397. package/tests/test-plugin-handshake.mjs +82 -0
  398. package/tests/test-render.mjs +33 -0
  399. package/tests/test-runner.mjs +933 -0
  400. package/tests/test-search-assets.mjs +66 -0
  401. package/tests/test-sequence.mjs +68 -0
  402. package/tests/test-system.mjs +57 -0
  403. package/tests/test-wasm.mjs +193 -0
  404. package/tests/test-world-partition.mjs +215 -0
  405. package/tsconfig.json +3 -3
  406. package/wasm/Cargo.lock +363 -0
  407. package/wasm/Cargo.toml +42 -0
  408. package/wasm/LICENSE +21 -0
  409. package/wasm/README.md +253 -0
  410. package/wasm/src/dependency_resolver.rs +377 -0
  411. package/wasm/src/lib.rs +153 -0
  412. package/wasm/src/property_parser.rs +271 -0
  413. package/wasm/src/transform_math.rs +396 -0
  414. package/wasm/tests/integration.rs +109 -0
  415. package/.github/workflows/smithery-build.yml +0 -29
  416. package/dist/tools/build_environment_advanced.d.ts +0 -65
  417. package/dist/tools/build_environment_advanced.js +0 -633
  418. package/dist/tools/rc.d.ts +0 -110
  419. package/dist/tools/rc.js +0 -437
  420. package/dist/tools/visual.d.ts +0 -40
  421. package/dist/tools/visual.js +0 -282
  422. package/dist/utils/http.d.ts +0 -6
  423. package/dist/utils/http.js +0 -151
  424. package/dist/utils/python-output.d.ts +0 -18
  425. package/dist/utils/python-output.js +0 -290
  426. package/dist/utils/python.d.ts +0 -2
  427. package/dist/utils/python.js +0 -4
  428. package/dist/utils/stdio-redirect.d.ts +0 -2
  429. package/dist/utils/stdio-redirect.js +0 -20
  430. package/docs/unreal-tool-test-cases.md +0 -574
  431. package/smithery.yaml +0 -29
  432. package/src/tools/build_environment_advanced.ts +0 -732
  433. package/src/tools/rc.ts +0 -515
  434. package/src/tools/visual.ts +0 -281
  435. package/src/utils/http.ts +0 -187
  436. package/src/utils/python-output.ts +0 -351
  437. package/src/utils/python.ts +0 -3
  438. package/src/utils/stdio-redirect.ts +0 -18
@@ -1,797 +1,245 @@
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
+ export class SequenceTools extends BaseTool {
6
3
  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
- }
32
- }
33
- throw lastError;
34
- }
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
- };
4
+ activeSequencePath;
5
+ resolveSequencePath(explicitPath) {
6
+ if (typeof explicitPath === 'string' && explicitPath.trim().length > 0) {
7
+ return explicitPath.trim();
48
8
  }
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.' };
9
+ return this.activeSequencePath;
10
+ }
11
+ async sendAction(action, payload = {}, timeoutMs) {
12
+ const envDefault = Number(process.env.MCP_AUTOMATION_REQUEST_TIMEOUT_MS ?? '120000');
13
+ const defaultTimeout = Number.isFinite(envDefault) && envDefault > 0 ? envDefault : 120000;
14
+ const finalTimeout = typeof timeoutMs === 'number' && timeoutMs > 0 ? timeoutMs : defaultTimeout;
15
+ try {
16
+ const response = await this.sendAutomationRequest(action, payload, { timeoutMs: finalTimeout, waitForEvent: false });
17
+ const success = response && response.success !== false;
18
+ const result = response.result ?? response;
19
+ return { success, message: response.message ?? undefined, error: response.success === false ? (response.error ?? response.message) : undefined, result, requestId: response.requestId };
56
20
  }
57
- if (rawOutput.includes('AttributeError')) {
58
- return { success: false, error: 'Sequencer API method not found. Check Unreal Engine version compatibility.' };
21
+ catch (err) {
22
+ return { success: false, error: String(err), message: String(err) };
59
23
  }
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
- };
24
+ }
25
+ isUnknownActionResponse(res) {
26
+ if (!res)
27
+ return false;
28
+ const txt = String((res.error ?? res.message ?? '')).toLowerCase();
29
+ return txt.includes('unknown_action') || txt.includes('unknown automation action') || txt.includes('not_implemented') || txt === 'unknown_plugin_action';
70
30
  }
71
31
  async create(params) {
72
32
  const name = params.name?.trim();
73
33
  const base = (params.path || '/Game/Sequences').replace(/\/$/, '');
74
34
  if (!name)
75
35
  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
- };
36
+ const payload = { name, path: base };
37
+ const resp = await this.sendAction('sequence_create', payload, params.timeoutMs);
38
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
39
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_create' };
87
40
  }
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
- };
41
+ if (resp.success && resp.result && resp.result.sequencePath) {
42
+ const sequence = { path: resp.result.sequencePath, name };
123
43
  this.sequenceCache.set(sequence.path, sequence);
44
+ return { ...resp, sequence: resp.result.sequencePath };
124
45
  }
125
- return result;
46
+ return resp;
126
47
  }
127
48
  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
- };
49
+ const path = params.path?.trim();
50
+ const resp = await this.sendAction('sequence_open', { path });
51
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
52
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_open' };
134
53
  }
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');
54
+ if (resp && resp.success !== false && path) {
55
+ this.activeSequencePath = path;
56
+ }
57
+ return resp;
150
58
  }
151
59
  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
- };
60
+ const path = this.resolveSequencePath(params.path);
61
+ const resp = await this.sendAction('sequence_add_camera', { path, spawnable: params.spawnable !== false });
62
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
63
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_add_camera' };
162
64
  }
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');
65
+ return resp;
231
66
  }
232
67
  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
- };
68
+ const path = this.resolveSequencePath(params.path);
69
+ const resp = await this.sendAction('sequence_add_actor', { path, actorName: params.actorName, createBinding: params.createBinding });
70
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
71
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_add_actor' };
239
72
  }
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');
73
+ return resp;
326
74
  }
327
- /**
328
- * Play the current level sequence
329
- */
330
75
  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
- };
76
+ const path = this.resolveSequencePath(params?.path);
77
+ const resp = await this.sendAction('sequence_play', { path, startTime: params?.startTime, loopMode: params?.loopMode });
78
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
79
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_play' };
343
80
  }
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');
81
+ return resp;
373
82
  }
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
- };
83
+ async pause(params) {
84
+ const path = this.resolveSequencePath(params?.path);
85
+ const resp = await this.sendAction('sequence_pause', { path });
86
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
87
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_pause' };
384
88
  }
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');
89
+ return resp;
395
90
  }
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
- };
91
+ async stop(params) {
92
+ const path = this.resolveSequencePath(params?.path);
93
+ const resp = await this.sendAction('sequence_stop', { path });
94
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
95
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_stop' };
406
96
  }
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');
97
+ return resp;
417
98
  }
418
- /**
419
- * Set sequence properties including frame rate and length
420
- */
421
99
  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
- };
100
+ const payload = {
101
+ path: params.path,
102
+ frameRate: params.frameRate,
103
+ lengthInFrames: params.lengthInFrames,
104
+ playbackStart: params.playbackStart,
105
+ playbackEnd: params.playbackEnd
106
+ };
107
+ const resp = await this.sendAction('sequence_set_properties', payload);
108
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
109
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_set_properties' };
452
110
  }
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()
111
+ return resp;
112
+ }
113
+ async setDisplayRate(params) {
114
+ const resp = await this.sendAction('sequence_set_display_rate', { path: params.path, frameRate: params.frameRate });
115
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
116
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_set_display_rate' };
506
117
  }
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');
118
+ return resp;
514
119
  }
515
- /**
516
- * Get sequence properties
517
- */
518
120
  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
121
+ const resp = await this.sendAction('sequence_get_properties', { path: params.path });
122
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
123
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_get_properties' };
558
124
  }
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');
125
+ return resp;
566
126
  }
567
- /**
568
- * Set playback speed/rate
569
- */
570
127
  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');
128
+ const path = this.resolveSequencePath(params.path);
129
+ const resp = await this.sendAction('sequence_set_playback_speed', { path, speed: params.speed });
130
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
131
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_set_playback_speed' };
132
+ }
133
+ return resp;
581
134
  }
582
- /**
583
- * Get all bindings in the current sequence
584
- */
585
135
  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');
136
+ const resp = await this.sendAction('sequence_get_bindings', { path: params?.path });
137
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
138
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_get_bindings' };
139
+ }
140
+ return resp;
629
141
  }
630
- /**
631
- * Add multiple actors to sequence at once
632
- */
633
142
  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');
143
+ const path = this.resolveSequencePath(params.path);
144
+ const resp = await this.sendAction('sequence_add_actors', { path, actorNames: params.actorNames });
145
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
146
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_add_actors' };
147
+ }
148
+ return resp;
681
149
  }
682
- /**
683
- * Remove actors from binding
684
- */
685
150
  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');
151
+ const path = this.resolveSequencePath(params.path);
152
+ const resp = await this.sendAction('sequence_remove_actors', { path, actorNames: params.actorNames });
153
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
154
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_remove_actors' };
155
+ }
156
+ return resp;
733
157
  }
734
- /**
735
- * Create a spawnable from an actor class
736
- */
737
158
  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');
159
+ const resp = await this.sendAction('sequence_add_spawnable_from_class', { className: params.className, path: params.path });
160
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
161
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_add_spawnable_from_class' };
162
+ }
163
+ return resp;
164
+ }
165
+ async list(params) {
166
+ const resp = await this.sendAction('sequence_list', { path: params?.path });
167
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
168
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_list' };
169
+ }
170
+ if (resp.success) {
171
+ const sequences = resp.sequences || resp.data || resp.result || [];
172
+ return {
173
+ ...resp,
174
+ sequences,
175
+ count: Array.isArray(sequences) ? sequences.length : undefined
176
+ };
177
+ }
178
+ return resp;
179
+ }
180
+ async duplicate(params) {
181
+ const resp = await this.sendAction('sequence_duplicate', { path: params.path, destinationPath: params.destinationPath });
182
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
183
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_duplicate' };
184
+ }
185
+ return resp;
186
+ }
187
+ async rename(params) {
188
+ const resp = await this.sendAction('sequence_rename', { path: params.path, newName: params.newName });
189
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
190
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_rename' };
191
+ }
192
+ return resp;
193
+ }
194
+ async deleteSequence(params) {
195
+ const resp = await this.sendAction('sequence_delete', { path: params.path });
196
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
197
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_delete' };
198
+ }
199
+ return resp;
200
+ }
201
+ async getMetadata(params) {
202
+ const resp = await this.sendAction('sequence_get_metadata', { path: params.path });
203
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
204
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_get_metadata' };
205
+ }
206
+ return resp;
207
+ }
208
+ async addKeyframe(params) {
209
+ const resp = await this.sendAction('sequence_add_keyframe', {
210
+ path: params.path,
211
+ bindingId: params.bindingId,
212
+ actorName: params.actorName,
213
+ property: params.property,
214
+ frame: params.frame,
215
+ value: params.value
216
+ });
217
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
218
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_add_keyframe' };
219
+ }
220
+ return resp;
221
+ }
222
+ async listTracks(params) {
223
+ const resp = await this.sendAction('sequence_list_tracks', { path: params.path });
224
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
225
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_list_tracks' };
226
+ }
227
+ if (resp.success) {
228
+ const tracks = resp.tracks || resp.data || resp.result || [];
229
+ return {
230
+ ...resp,
231
+ tracks,
232
+ count: Array.isArray(tracks) ? tracks.length : undefined
233
+ };
234
+ }
235
+ return resp;
236
+ }
237
+ async setWorkRange(params) {
238
+ const resp = await this.sendAction('sequence_set_work_range', { path: params.path, start: params.start, end: params.end });
239
+ if (!resp.success && this.isUnknownActionResponse(resp)) {
240
+ return { success: false, error: 'UNKNOWN_PLUGIN_ACTION', message: 'Automation plugin does not implement sequence_set_work_range' };
241
+ }
242
+ return resp;
795
243
  }
796
244
  }
797
245
  //# sourceMappingURL=sequence.js.map