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.
- package/.env.example +26 -0
- package/.env.production +38 -7
- package/.eslintrc.json +0 -54
- package/.eslintrc.override.json +8 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +94 -0
- package/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +56 -0
- package/.github/copilot-instructions.md +478 -45
- package/.github/dependabot.yml +19 -0
- package/.github/labeler.yml +24 -0
- package/.github/labels.yml +70 -0
- package/.github/pull_request_template.md +42 -0
- package/.github/release-drafter.yml +148 -0
- package/.github/workflows/auto-merge.yml +38 -0
- package/.github/workflows/ci.yml +38 -0
- package/.github/workflows/dependency-review.yml +17 -0
- package/.github/workflows/gemini-issue-triage.yml +172 -0
- package/.github/workflows/greetings.yml +23 -0
- package/.github/workflows/labeler.yml +16 -0
- package/.github/workflows/links.yml +80 -0
- package/.github/workflows/pr-size-labeler.yml +137 -0
- package/.github/workflows/publish-mcp.yml +12 -7
- package/.github/workflows/release-drafter.yml +23 -0
- package/.github/workflows/release.yml +112 -0
- package/.github/workflows/semantic-pull-request.yml +35 -0
- package/.github/workflows/smoke-test.yml +36 -0
- package/.github/workflows/stale.yml +28 -0
- package/CHANGELOG.md +267 -31
- package/CONTRIBUTING.md +140 -0
- package/README.md +166 -71
- package/claude_desktop_config_example.json +7 -6
- package/dist/automation/bridge.d.ts +50 -0
- package/dist/automation/bridge.js +452 -0
- package/dist/automation/connection-manager.d.ts +23 -0
- package/dist/automation/connection-manager.js +107 -0
- package/dist/automation/handshake.d.ts +11 -0
- package/dist/automation/handshake.js +89 -0
- package/dist/automation/index.d.ts +3 -0
- package/dist/automation/index.js +3 -0
- package/dist/automation/message-handler.d.ts +12 -0
- package/dist/automation/message-handler.js +149 -0
- package/dist/automation/request-tracker.d.ts +25 -0
- package/dist/automation/request-tracker.js +98 -0
- package/dist/automation/types.d.ts +130 -0
- package/dist/automation/types.js +2 -0
- package/dist/cli.js +32 -5
- package/dist/config.d.ts +27 -0
- package/dist/config.js +60 -0
- package/dist/constants.d.ts +12 -0
- package/dist/constants.js +12 -0
- package/dist/graphql/resolvers.d.ts +268 -0
- package/dist/graphql/resolvers.js +743 -0
- package/dist/graphql/schema.d.ts +5 -0
- package/dist/graphql/schema.js +437 -0
- package/dist/graphql/server.d.ts +26 -0
- package/dist/graphql/server.js +115 -0
- package/dist/graphql/types.d.ts +7 -0
- package/dist/graphql/types.js +2 -0
- package/dist/handlers/resource-handlers.d.ts +20 -0
- package/dist/handlers/resource-handlers.js +180 -0
- package/dist/index.d.ts +31 -18
- package/dist/index.js +119 -619
- package/dist/prompts/index.js +4 -4
- package/dist/resources/actors.d.ts +17 -12
- package/dist/resources/actors.js +56 -76
- package/dist/resources/assets.d.ts +6 -14
- package/dist/resources/assets.js +115 -147
- package/dist/resources/levels.d.ts +13 -13
- package/dist/resources/levels.js +25 -34
- package/dist/server/resource-registry.d.ts +20 -0
- package/dist/server/resource-registry.js +37 -0
- package/dist/server/tool-registry.d.ts +23 -0
- package/dist/server/tool-registry.js +322 -0
- package/dist/server-setup.d.ts +21 -0
- package/dist/server-setup.js +111 -0
- package/dist/services/health-monitor.d.ts +34 -0
- package/dist/services/health-monitor.js +105 -0
- package/dist/services/metrics-server.d.ts +11 -0
- package/dist/services/metrics-server.js +105 -0
- package/dist/tools/actors.d.ts +147 -9
- package/dist/tools/actors.js +350 -311
- package/dist/tools/animation.d.ts +135 -4
- package/dist/tools/animation.js +510 -411
- package/dist/tools/assets.d.ts +117 -19
- package/dist/tools/assets.js +259 -284
- package/dist/tools/audio.d.ts +102 -42
- package/dist/tools/audio.js +272 -685
- package/dist/tools/base-tool.d.ts +17 -0
- package/dist/tools/base-tool.js +46 -0
- package/dist/tools/behavior-tree.d.ts +94 -0
- package/dist/tools/behavior-tree.js +39 -0
- package/dist/tools/blueprint/helpers.d.ts +29 -0
- package/dist/tools/blueprint/helpers.js +182 -0
- package/dist/tools/blueprint.d.ts +228 -118
- package/dist/tools/blueprint.js +685 -832
- package/dist/tools/consolidated-tool-definitions.d.ts +5462 -1781
- package/dist/tools/consolidated-tool-definitions.js +829 -496
- package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
- package/dist/tools/consolidated-tool-handlers.js +211 -1026
- package/dist/tools/debug.d.ts +143 -85
- package/dist/tools/debug.js +234 -180
- package/dist/tools/dynamic-handler-registry.d.ts +11 -0
- package/dist/tools/dynamic-handler-registry.js +101 -0
- package/dist/tools/editor.d.ts +139 -18
- package/dist/tools/editor.js +239 -244
- package/dist/tools/engine.d.ts +10 -4
- package/dist/tools/engine.js +13 -5
- package/dist/tools/environment.d.ts +36 -0
- package/dist/tools/environment.js +267 -0
- package/dist/tools/foliage.d.ts +105 -14
- package/dist/tools/foliage.js +219 -331
- package/dist/tools/handlers/actor-handlers.d.ts +3 -0
- package/dist/tools/handlers/actor-handlers.js +232 -0
- package/dist/tools/handlers/animation-handlers.d.ts +3 -0
- package/dist/tools/handlers/animation-handlers.js +185 -0
- package/dist/tools/handlers/argument-helper.d.ts +16 -0
- package/dist/tools/handlers/argument-helper.js +80 -0
- package/dist/tools/handlers/asset-handlers.d.ts +3 -0
- package/dist/tools/handlers/asset-handlers.js +496 -0
- package/dist/tools/handlers/audio-handlers.d.ts +3 -0
- package/dist/tools/handlers/audio-handlers.js +166 -0
- package/dist/tools/handlers/blueprint-handlers.d.ts +4 -0
- package/dist/tools/handlers/blueprint-handlers.js +358 -0
- package/dist/tools/handlers/common-handlers.d.ts +14 -0
- package/dist/tools/handlers/common-handlers.js +56 -0
- package/dist/tools/handlers/editor-handlers.d.ts +3 -0
- package/dist/tools/handlers/editor-handlers.js +119 -0
- package/dist/tools/handlers/effect-handlers.d.ts +3 -0
- package/dist/tools/handlers/effect-handlers.js +171 -0
- package/dist/tools/handlers/environment-handlers.d.ts +3 -0
- package/dist/tools/handlers/environment-handlers.js +170 -0
- package/dist/tools/handlers/graph-handlers.d.ts +3 -0
- package/dist/tools/handlers/graph-handlers.js +90 -0
- package/dist/tools/handlers/input-handlers.d.ts +3 -0
- package/dist/tools/handlers/input-handlers.js +21 -0
- package/dist/tools/handlers/inspect-handlers.d.ts +3 -0
- package/dist/tools/handlers/inspect-handlers.js +383 -0
- package/dist/tools/handlers/level-handlers.d.ts +3 -0
- package/dist/tools/handlers/level-handlers.js +237 -0
- package/dist/tools/handlers/lighting-handlers.d.ts +3 -0
- package/dist/tools/handlers/lighting-handlers.js +144 -0
- package/dist/tools/handlers/performance-handlers.d.ts +3 -0
- package/dist/tools/handlers/performance-handlers.js +130 -0
- package/dist/tools/handlers/pipeline-handlers.d.ts +3 -0
- package/dist/tools/handlers/pipeline-handlers.js +110 -0
- package/dist/tools/handlers/sequence-handlers.d.ts +3 -0
- package/dist/tools/handlers/sequence-handlers.js +376 -0
- package/dist/tools/handlers/system-handlers.d.ts +4 -0
- package/dist/tools/handlers/system-handlers.js +506 -0
- package/dist/tools/input.d.ts +19 -0
- package/dist/tools/input.js +89 -0
- package/dist/tools/introspection.d.ts +103 -40
- package/dist/tools/introspection.js +425 -568
- package/dist/tools/landscape.d.ts +97 -36
- package/dist/tools/landscape.js +280 -409
- package/dist/tools/level.d.ts +130 -10
- package/dist/tools/level.js +639 -675
- package/dist/tools/lighting.d.ts +77 -38
- package/dist/tools/lighting.js +441 -943
- package/dist/tools/logs.d.ts +3 -3
- package/dist/tools/logs.js +5 -57
- package/dist/tools/materials.d.ts +91 -24
- package/dist/tools/materials.js +190 -118
- package/dist/tools/niagara.d.ts +149 -39
- package/dist/tools/niagara.js +232 -182
- package/dist/tools/performance.d.ts +27 -12
- package/dist/tools/performance.js +204 -122
- package/dist/tools/physics.d.ts +32 -77
- package/dist/tools/physics.js +171 -582
- package/dist/tools/property-dictionary.d.ts +13 -0
- package/dist/tools/property-dictionary.js +82 -0
- package/dist/tools/sequence.d.ts +73 -48
- package/dist/tools/sequence.js +196 -748
- package/dist/tools/tool-definition-utils.d.ts +59 -0
- package/dist/tools/tool-definition-utils.js +35 -0
- package/dist/tools/ui.d.ts +66 -34
- package/dist/tools/ui.js +134 -214
- package/dist/types/env.d.ts +0 -3
- package/dist/types/env.js +0 -7
- package/dist/types/tool-interfaces.d.ts +898 -0
- package/dist/types/tool-interfaces.js +2 -0
- package/dist/types/tool-types.d.ts +183 -19
- package/dist/types/tool-types.js +0 -4
- package/dist/unreal-bridge.d.ts +24 -131
- package/dist/unreal-bridge.js +364 -1506
- package/dist/utils/command-validator.d.ts +9 -0
- package/dist/utils/command-validator.js +67 -0
- package/dist/utils/elicitation.d.ts +1 -1
- package/dist/utils/elicitation.js +12 -15
- package/dist/utils/error-handler.d.ts +2 -51
- package/dist/utils/error-handler.js +11 -87
- package/dist/utils/ini-reader.d.ts +3 -0
- package/dist/utils/ini-reader.js +69 -0
- package/dist/utils/logger.js +9 -6
- package/dist/utils/normalize.d.ts +3 -0
- package/dist/utils/normalize.js +56 -0
- package/dist/utils/response-factory.d.ts +7 -0
- package/dist/utils/response-factory.js +33 -0
- package/dist/utils/response-validator.d.ts +3 -24
- package/dist/utils/response-validator.js +130 -81
- package/dist/utils/result-helpers.d.ts +4 -5
- package/dist/utils/result-helpers.js +15 -16
- package/dist/utils/safe-json.js +5 -11
- package/dist/utils/unreal-command-queue.d.ts +24 -0
- package/dist/utils/unreal-command-queue.js +120 -0
- package/dist/utils/validation.d.ts +0 -40
- package/dist/utils/validation.js +1 -78
- package/dist/wasm/index.d.ts +70 -0
- package/dist/wasm/index.js +535 -0
- package/docs/GraphQL-API.md +888 -0
- package/docs/Migration-Guide-v0.5.0.md +692 -0
- package/docs/Roadmap.md +53 -0
- package/docs/WebAssembly-Integration.md +628 -0
- package/docs/editor-plugin-extension.md +370 -0
- package/docs/handler-mapping.md +242 -0
- package/docs/native-automation-progress.md +128 -0
- package/docs/testing-guide.md +423 -0
- package/mcp-config-example.json +6 -6
- package/package.json +60 -27
- package/plugins/McpAutomationBridge/Config/FilterPlugin.ini +8 -0
- package/plugins/McpAutomationBridge/McpAutomationBridge.uplugin +64 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/McpAutomationBridge.Build.cs +189 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.cpp +22 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeGlobals.h +30 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeHelpers.h +1983 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeModule.cpp +72 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSettings.cpp +46 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridgeSubsystem.cpp +581 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AnimationHandlers.cpp +2394 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetQueryHandlers.cpp +300 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AssetWorkflowHandlers.cpp +2807 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_AudioHandlers.cpp +1087 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BehaviorTreeHandlers.cpp +488 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.cpp +643 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintCreationHandlers.h +31 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintGraphHandlers.cpp +1184 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers.cpp +5652 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_BlueprintHandlers_List.cpp +152 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ControlHandlers.cpp +2614 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_DebugHandlers.cpp +42 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EditorFunctionHandlers.cpp +1237 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EffectHandlers.cpp +1701 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_EnvironmentHandlers.cpp +2145 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_FoliageHandlers.cpp +954 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InputHandlers.cpp +209 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_InsightsHandlers.cpp +41 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LandscapeHandlers.cpp +1164 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LevelHandlers.cpp +762 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LightingHandlers.cpp +634 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_LogHandlers.cpp +136 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_MaterialGraphHandlers.cpp +494 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraGraphHandlers.cpp +278 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_NiagaraHandlers.cpp +625 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PerformanceHandlers.cpp +401 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PipelineHandlers.cpp +67 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_ProcessRequest.cpp +735 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_PropertyHandlers.cpp +2634 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_RenderHandlers.cpp +189 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.cpp +917 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SCSHandlers.h +39 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequenceHandlers.cpp +2670 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_SequencerHandlers.cpp +519 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_TestHandlers.cpp +38 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_UiHandlers.cpp +668 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpAutomationBridge_WorldPartitionHandlers.cpp +346 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.cpp +1330 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpBridgeWebSocket.h +149 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Private/McpConnectionManager.cpp +783 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSettings.h +115 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpAutomationBridgeSubsystem.h +796 -0
- package/plugins/McpAutomationBridge/Source/McpAutomationBridge/Public/McpConnectionManager.h +117 -0
- package/scripts/check-unreal-connection.mjs +19 -0
- package/scripts/clean-tmp.js +23 -0
- package/scripts/patch-wasm.js +26 -0
- package/scripts/run-all-tests.mjs +131 -0
- package/scripts/smoke-test.ts +94 -0
- package/scripts/sync-mcp-plugin.js +143 -0
- package/scripts/test-no-plugin-alternates.mjs +113 -0
- package/scripts/validate-server.js +46 -0
- package/scripts/verify-automation-bridge.js +200 -0
- package/server.json +57 -21
- package/src/automation/bridge.ts +558 -0
- package/src/automation/connection-manager.ts +130 -0
- package/src/automation/handshake.ts +99 -0
- package/src/automation/index.ts +2 -0
- package/src/automation/message-handler.ts +167 -0
- package/src/automation/request-tracker.ts +123 -0
- package/src/automation/types.ts +107 -0
- package/src/cli.ts +33 -6
- package/src/config.ts +73 -0
- package/src/constants.ts +12 -0
- package/src/graphql/resolvers.ts +1010 -0
- package/src/graphql/schema.ts +452 -0
- package/src/graphql/server.ts +154 -0
- package/src/graphql/types.ts +7 -0
- package/src/handlers/resource-handlers.ts +186 -0
- package/src/index.ts +152 -663
- package/src/prompts/index.ts +4 -4
- package/src/resources/actors.ts +58 -76
- package/src/resources/assets.ts +147 -134
- package/src/resources/levels.ts +28 -33
- package/src/server/resource-registry.ts +47 -0
- package/src/server/tool-registry.ts +354 -0
- package/src/server-setup.ts +148 -0
- package/src/services/health-monitor.ts +132 -0
- package/src/services/metrics-server.ts +142 -0
- package/src/tools/actors.ts +417 -322
- package/src/tools/animation.ts +671 -461
- package/src/tools/assets.ts +353 -289
- package/src/tools/audio.ts +323 -766
- package/src/tools/base-tool.ts +52 -0
- package/src/tools/behavior-tree.ts +45 -0
- package/src/tools/blueprint/helpers.ts +189 -0
- package/src/tools/blueprint.ts +787 -965
- package/src/tools/consolidated-tool-definitions.ts +993 -515
- package/src/tools/consolidated-tool-handlers.ts +272 -1139
- package/src/tools/debug.ts +292 -187
- package/src/tools/dynamic-handler-registry.ts +151 -0
- package/src/tools/editor.ts +309 -246
- package/src/tools/engine.ts +14 -3
- package/src/tools/environment.ts +287 -0
- package/src/tools/foliage.ts +314 -379
- package/src/tools/handlers/actor-handlers.ts +271 -0
- package/src/tools/handlers/animation-handlers.ts +237 -0
- package/src/tools/handlers/argument-helper.ts +142 -0
- package/src/tools/handlers/asset-handlers.ts +532 -0
- package/src/tools/handlers/audio-handlers.ts +194 -0
- package/src/tools/handlers/blueprint-handlers.ts +380 -0
- package/src/tools/handlers/common-handlers.ts +87 -0
- package/src/tools/handlers/editor-handlers.ts +123 -0
- package/src/tools/handlers/effect-handlers.ts +220 -0
- package/src/tools/handlers/environment-handlers.ts +183 -0
- package/src/tools/handlers/graph-handlers.ts +116 -0
- package/src/tools/handlers/input-handlers.ts +28 -0
- package/src/tools/handlers/inspect-handlers.ts +450 -0
- package/src/tools/handlers/level-handlers.ts +252 -0
- package/src/tools/handlers/lighting-handlers.ts +147 -0
- package/src/tools/handlers/performance-handlers.ts +132 -0
- package/src/tools/handlers/pipeline-handlers.ts +127 -0
- package/src/tools/handlers/sequence-handlers.ts +415 -0
- package/src/tools/handlers/system-handlers.ts +564 -0
- package/src/tools/input.ts +101 -0
- package/src/tools/introspection.ts +493 -584
- package/src/tools/landscape.ts +394 -489
- package/src/tools/level.ts +752 -694
- package/src/tools/lighting.ts +583 -984
- package/src/tools/logs.ts +9 -57
- package/src/tools/materials.ts +231 -121
- package/src/tools/niagara.ts +293 -168
- package/src/tools/performance.ts +320 -168
- package/src/tools/physics.ts +268 -613
- package/src/tools/property-dictionary.ts +98 -0
- package/src/tools/sequence.ts +255 -815
- package/src/tools/tool-definition-utils.ts +35 -0
- package/src/tools/ui.ts +207 -283
- package/src/types/env.ts +0 -10
- package/src/types/tool-interfaces.ts +250 -0
- package/src/types/tool-types.ts +243 -21
- package/src/unreal-bridge.ts +460 -1550
- package/src/utils/command-validator.ts +75 -0
- package/src/utils/elicitation.ts +10 -7
- package/src/utils/error-handler.ts +14 -90
- package/src/utils/ini-reader.ts +86 -0
- package/src/utils/logger.ts +8 -3
- package/src/utils/normalize.ts +60 -0
- package/src/utils/response-factory.ts +39 -0
- package/src/utils/response-validator.ts +176 -56
- package/src/utils/result-helpers.ts +21 -19
- package/src/utils/safe-json.ts +14 -11
- package/src/utils/unreal-command-queue.ts +152 -0
- package/src/utils/validation.ts +4 -1
- package/src/wasm/index.ts +838 -0
- package/test-server.mjs +100 -0
- package/tests/run-unreal-tool-tests.mjs +242 -14
- package/tests/test-animation.mjs +44 -0
- package/tests/test-asset-advanced.mjs +82 -0
- package/tests/test-asset-errors.mjs +35 -0
- package/tests/test-audio.mjs +219 -0
- package/tests/test-automation-timeouts.mjs +98 -0
- package/tests/test-behavior-tree.mjs +261 -0
- package/tests/test-blueprint-events.mjs +35 -0
- package/tests/test-blueprint-graph.mjs +79 -0
- package/tests/test-blueprint.mjs +577 -0
- package/tests/test-client-mode.mjs +86 -0
- package/tests/test-console-command.mjs +56 -0
- package/tests/test-control-actor.mjs +425 -0
- package/tests/test-control-editor.mjs +80 -0
- package/tests/test-extra-tools.mjs +38 -0
- package/tests/test-graphql.mjs +322 -0
- package/tests/test-inspect.mjs +72 -0
- package/tests/test-landscape.mjs +60 -0
- package/tests/test-manage-asset.mjs +438 -0
- package/tests/test-manage-level.mjs +70 -0
- package/tests/test-materials.mjs +356 -0
- package/tests/test-niagara.mjs +185 -0
- package/tests/test-no-inline-python.mjs +122 -0
- package/tests/test-plugin-handshake.mjs +82 -0
- package/tests/test-render.mjs +33 -0
- package/tests/test-runner.mjs +933 -0
- package/tests/test-search-assets.mjs +66 -0
- package/tests/test-sequence.mjs +68 -0
- package/tests/test-system.mjs +57 -0
- package/tests/test-wasm.mjs +193 -0
- package/tests/test-world-partition.mjs +215 -0
- package/tsconfig.json +3 -3
- package/wasm/Cargo.lock +363 -0
- package/wasm/Cargo.toml +42 -0
- package/wasm/LICENSE +21 -0
- package/wasm/README.md +253 -0
- package/wasm/src/dependency_resolver.rs +377 -0
- package/wasm/src/lib.rs +153 -0
- package/wasm/src/property_parser.rs +271 -0
- package/wasm/src/transform_math.rs +396 -0
- package/wasm/tests/integration.rs +109 -0
- package/.github/workflows/smithery-build.yml +0 -29
- package/dist/tools/build_environment_advanced.d.ts +0 -65
- package/dist/tools/build_environment_advanced.js +0 -633
- package/dist/tools/rc.d.ts +0 -110
- package/dist/tools/rc.js +0 -437
- package/dist/tools/visual.d.ts +0 -40
- package/dist/tools/visual.js +0 -282
- package/dist/utils/http.d.ts +0 -6
- package/dist/utils/http.js +0 -151
- package/dist/utils/python-output.d.ts +0 -18
- package/dist/utils/python-output.js +0 -290
- package/dist/utils/python.d.ts +0 -2
- package/dist/utils/python.js +0 -4
- package/dist/utils/stdio-redirect.d.ts +0 -2
- package/dist/utils/stdio-redirect.js +0 -20
- package/docs/unreal-tool-test-cases.md +0 -574
- package/smithery.yaml +0 -29
- package/src/tools/build_environment_advanced.ts +0 -732
- package/src/tools/rc.ts +0 -515
- package/src/tools/visual.ts +0 -281
- package/src/utils/http.ts +0 -187
- package/src/utils/python-output.ts +0 -351
- package/src/utils/python.ts +0 -3
- package/src/utils/stdio-redirect.ts +0 -18
package/src/tools/actors.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { UnrealBridge } from '../unreal-bridge.js';
|
|
2
2
|
import { ensureRotation, ensureVector3 } from '../utils/validation.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { BaseTool } from './base-tool.js';
|
|
4
|
+
import { IActorTools, StandardActionResponse } from '../types/tool-interfaces.js';
|
|
5
5
|
|
|
6
|
-
export class ActorTools {
|
|
7
|
-
constructor(
|
|
6
|
+
export class ActorTools extends BaseTool implements IActorTools {
|
|
7
|
+
constructor(bridge: UnrealBridge) {
|
|
8
|
+
super(bridge);
|
|
9
|
+
}
|
|
8
10
|
|
|
9
|
-
async spawn(params: { classPath: string; location?: { x: number; y: number; z: number }; rotation?: { pitch: number; yaw: number; roll: number }; actorName?: string }) {
|
|
11
|
+
async spawn(params: { classPath: string; location?: { x: number; y: number; z: number }; rotation?: { pitch: number; yaw: number; roll: number }; actorName?: string; meshPath?: string; timeoutMs?: number }) {
|
|
10
12
|
if (!params.classPath || typeof params.classPath !== 'string' || params.classPath.trim().length === 0) {
|
|
11
13
|
throw new Error(`Invalid classPath: ${params.classPath}`);
|
|
12
14
|
}
|
|
@@ -16,7 +18,7 @@ export class ActorTools {
|
|
|
16
18
|
if (params.actorName !== undefined && (!requestedActorName || requestedActorName.length === 0)) {
|
|
17
19
|
throw new Error(`Invalid actorName: ${params.actorName}`);
|
|
18
20
|
}
|
|
19
|
-
|
|
21
|
+
const sanitizedActorName = requestedActorName?.replace(/[^A-Za-z0-9_-]/g, '_');
|
|
20
22
|
const lowerName = className.toLowerCase();
|
|
21
23
|
|
|
22
24
|
const shapeMapping: Record<string, string> = {
|
|
@@ -39,315 +41,152 @@ export class ActorTools {
|
|
|
39
41
|
'actor rotation'
|
|
40
42
|
);
|
|
41
43
|
|
|
42
|
-
const escapedResolvedClassPath = escapePythonString(mappedClassPath);
|
|
43
|
-
const escapedRequestedPath = escapePythonString(className);
|
|
44
|
-
const escapedRequestedActorName = sanitizedActorName ? escapePythonString(sanitizedActorName) : '';
|
|
45
|
-
|
|
46
|
-
const pythonCmd = `
|
|
47
|
-
import unreal
|
|
48
|
-
import json
|
|
49
|
-
import time
|
|
50
|
-
|
|
51
|
-
result = {
|
|
52
|
-
"success": False,
|
|
53
|
-
"message": "",
|
|
54
|
-
"error": "",
|
|
55
|
-
"actorName": "",
|
|
56
|
-
"requestedClass": "${escapedRequestedPath}",
|
|
57
|
-
"resolvedClass": "${escapedResolvedClassPath}",
|
|
58
|
-
"location": [${locX}, ${locY}, ${locZ}],
|
|
59
|
-
"rotation": [${rotPitch}, ${rotYaw}, ${rotRoll}],
|
|
60
|
-
"requestedActorName": "${escapedRequestedActorName}",
|
|
61
|
-
"warnings": [],
|
|
62
|
-
"details": []
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
${this.getPythonSpawnHelper()}
|
|
66
|
-
|
|
67
|
-
abstract_classes = ['PlaneReflectionCapture', 'ReflectionCapture', 'Actor', 'Pawn', 'Character']
|
|
68
|
-
|
|
69
|
-
def finalize():
|
|
70
|
-
data = dict(result)
|
|
71
|
-
if data.get("success"):
|
|
72
|
-
if not data.get("message"):
|
|
73
|
-
data["message"] = "Actor spawned successfully"
|
|
74
|
-
data.pop("error", None)
|
|
75
|
-
else:
|
|
76
|
-
if not data.get("error"):
|
|
77
|
-
data["error"] = data.get("message") or "Failed to spawn actor"
|
|
78
|
-
if not data.get("message"):
|
|
79
|
-
data["message"] = data["error"]
|
|
80
|
-
if not data.get("warnings"):
|
|
81
|
-
data.pop("warnings", None)
|
|
82
|
-
if not data.get("details"):
|
|
83
|
-
data.pop("details", None)
|
|
84
|
-
return data
|
|
85
|
-
|
|
86
|
-
try:
|
|
87
|
-
les = unreal.get_editor_subsystem(unreal.LevelEditorSubsystem)
|
|
88
|
-
if les and les.is_in_play_in_editor():
|
|
89
|
-
result["message"] = "Cannot spawn actors while in Play In Editor mode. Please stop PIE first."
|
|
90
|
-
result["error"] = result["message"]
|
|
91
|
-
result["details"].append("Play In Editor mode detected")
|
|
92
|
-
print('RESULT:' + json.dumps(finalize()))
|
|
93
|
-
raise SystemExit(0)
|
|
94
|
-
except SystemExit:
|
|
95
|
-
raise
|
|
96
|
-
except Exception:
|
|
97
|
-
result["warnings"].append("Unable to determine Play In Editor state")
|
|
98
|
-
|
|
99
|
-
if result["requestedClass"] in abstract_classes:
|
|
100
|
-
result["message"] = f"Cannot spawn {result['requestedClass']}: class is abstract and cannot be instantiated"
|
|
101
|
-
result["error"] = result["message"]
|
|
102
|
-
else:
|
|
103
|
-
try:
|
|
104
|
-
class_path = result["resolvedClass"]
|
|
105
|
-
requested_path = result["requestedClass"]
|
|
106
|
-
location = unreal.Vector(${locX}, ${locY}, ${locZ})
|
|
107
|
-
rotation = unreal.Rotator(${rotPitch}, ${rotYaw}, ${rotRoll})
|
|
108
|
-
actor = None
|
|
109
|
-
|
|
110
|
-
simple_name = requested_path.split('/')[-1] if '/' in requested_path else requested_path
|
|
111
|
-
if '.' in simple_name:
|
|
112
|
-
simple_name = simple_name.split('.')[-1]
|
|
113
|
-
simple_name_lower = simple_name.lower()
|
|
114
|
-
class_lookup_name = class_path.split('.')[-1] if '.' in class_path else simple_name
|
|
115
|
-
|
|
116
|
-
result["details"].append(f"Attempting spawn using class path: {class_path}")
|
|
117
|
-
|
|
118
|
-
if class_path.startswith('/Game') or class_path.startswith('/Engine'):
|
|
119
|
-
try:
|
|
120
|
-
asset = unreal.EditorAssetLibrary.load_asset(class_path)
|
|
121
|
-
except Exception as asset_error:
|
|
122
|
-
asset = None
|
|
123
|
-
result["warnings"].append(f"Failed to load asset for {class_path}: {asset_error}")
|
|
124
|
-
if asset:
|
|
125
|
-
if isinstance(asset, unreal.Blueprint):
|
|
126
|
-
try:
|
|
127
|
-
actor_class = asset.generated_class()
|
|
128
|
-
except Exception as blueprint_error:
|
|
129
|
-
actor_class = None
|
|
130
|
-
result["warnings"].append(f"Failed to resolve blueprint class: {blueprint_error}")
|
|
131
|
-
if actor_class:
|
|
132
|
-
actor = spawn_actor_from_class(actor_class, location, rotation)
|
|
133
|
-
if actor:
|
|
134
|
-
result["details"].append("Spawned using Blueprint generated class")
|
|
135
|
-
elif isinstance(asset, unreal.StaticMesh):
|
|
136
|
-
actor = spawn_actor_from_class(unreal.StaticMeshActor, location, rotation)
|
|
137
|
-
if actor:
|
|
138
|
-
mesh_component = actor.get_component_by_class(unreal.StaticMeshComponent)
|
|
139
|
-
if mesh_component:
|
|
140
|
-
mesh_component.set_static_mesh(asset)
|
|
141
|
-
mesh_component.set_editor_property('mobility', unreal.ComponentMobility.MOVABLE)
|
|
142
|
-
result["details"].append("Applied static mesh to spawned StaticMeshActor")
|
|
143
|
-
|
|
144
|
-
if not actor:
|
|
145
|
-
shape_map = {
|
|
146
|
-
'cube': '/Engine/BasicShapes/Cube',
|
|
147
|
-
'sphere': '/Engine/BasicShapes/Sphere',
|
|
148
|
-
'cylinder': '/Engine/BasicShapes/Cylinder',
|
|
149
|
-
'cone': '/Engine/BasicShapes/Cone',
|
|
150
|
-
'plane': '/Engine/BasicShapes/Plane',
|
|
151
|
-
'torus': '/Engine/BasicShapes/Torus'
|
|
152
|
-
}
|
|
153
|
-
mesh_path = shape_map.get(simple_name_lower)
|
|
154
|
-
if not mesh_path and class_path.startswith('/Engine/BasicShapes'):
|
|
155
|
-
mesh_path = class_path
|
|
156
|
-
if mesh_path:
|
|
157
|
-
try:
|
|
158
|
-
shape_mesh = unreal.EditorAssetLibrary.load_asset(mesh_path)
|
|
159
|
-
except Exception as shape_error:
|
|
160
|
-
shape_mesh = None
|
|
161
|
-
result["warnings"].append(f"Failed to load shape mesh {mesh_path}: {shape_error}")
|
|
162
|
-
if shape_mesh:
|
|
163
|
-
actor = spawn_actor_from_class(unreal.StaticMeshActor, location, rotation)
|
|
164
|
-
if actor:
|
|
165
|
-
mesh_component = actor.get_component_by_class(unreal.StaticMeshComponent)
|
|
166
|
-
if mesh_component:
|
|
167
|
-
mesh_component.set_static_mesh(shape_mesh)
|
|
168
|
-
mesh_component.set_editor_property('mobility', unreal.ComponentMobility.MOVABLE)
|
|
169
|
-
result["details"].append(f"Spawned StaticMeshActor with mesh {mesh_path}")
|
|
170
|
-
|
|
171
|
-
if not actor:
|
|
172
|
-
if class_lookup_name == "StaticMeshActor":
|
|
173
|
-
actor = spawn_actor_from_class(unreal.StaticMeshActor, location, rotation)
|
|
174
|
-
if actor:
|
|
175
|
-
try:
|
|
176
|
-
cube_mesh = unreal.EditorAssetLibrary.load_asset('/Engine/BasicShapes/Cube')
|
|
177
|
-
except Exception as cube_error:
|
|
178
|
-
cube_mesh = None
|
|
179
|
-
result["warnings"].append(f"Failed to load default cube mesh: {cube_error}")
|
|
180
|
-
if cube_mesh:
|
|
181
|
-
mesh_component = actor.get_component_by_class(unreal.StaticMeshComponent)
|
|
182
|
-
if mesh_component:
|
|
183
|
-
mesh_component.set_static_mesh(cube_mesh)
|
|
184
|
-
mesh_component.set_editor_property('mobility', unreal.ComponentMobility.MOVABLE)
|
|
185
|
-
result["details"].append("Applied default cube mesh to StaticMeshActor")
|
|
186
|
-
elif class_lookup_name == "CameraActor":
|
|
187
|
-
actor = spawn_actor_from_class(unreal.CameraActor, location, rotation)
|
|
188
|
-
if actor:
|
|
189
|
-
result["details"].append("Spawned CameraActor via reflected class lookup")
|
|
190
|
-
else:
|
|
191
|
-
actor_class = getattr(unreal, class_lookup_name, None)
|
|
192
|
-
if actor_class:
|
|
193
|
-
actor = spawn_actor_from_class(actor_class, location, rotation)
|
|
194
|
-
if actor:
|
|
195
|
-
result["details"].append(f"Spawned {class_lookup_name} via reflected class lookup")
|
|
196
|
-
|
|
197
|
-
if actor:
|
|
198
|
-
desired_name = (result.get("requestedActorName") or "").strip()
|
|
199
|
-
actor_name = ""
|
|
200
|
-
if desired_name:
|
|
201
|
-
try:
|
|
202
|
-
try:
|
|
203
|
-
actor.set_actor_label(desired_name, True)
|
|
204
|
-
except TypeError:
|
|
205
|
-
actor.set_actor_label(desired_name)
|
|
206
|
-
actor_name = actor.get_actor_label() or desired_name
|
|
207
|
-
except Exception as label_error:
|
|
208
|
-
result["warnings"].append(f"Failed to honor requested actor name '{desired_name}': {label_error}")
|
|
209
|
-
if not actor_name:
|
|
210
|
-
timestamp = int(time.time() * 1000) % 10000
|
|
211
|
-
base_name = simple_name or class_lookup_name or class_path.split('/')[-1]
|
|
212
|
-
fallback_name = f"{base_name}_{timestamp}"
|
|
213
|
-
try:
|
|
214
|
-
actor.set_actor_label(fallback_name)
|
|
215
|
-
except Exception as label_error:
|
|
216
|
-
result["warnings"].append(f"Failed to set actor label: {label_error}")
|
|
217
|
-
actor_name = actor.get_actor_label() or fallback_name
|
|
218
|
-
result["success"] = True
|
|
219
|
-
result["actorName"] = actor_name
|
|
220
|
-
if not result["message"]:
|
|
221
|
-
result["message"] = f"Spawned {actor_name} at ({location.x}, {location.y}, {location.z})"
|
|
222
|
-
else:
|
|
223
|
-
result["message"] = f"Failed to spawn actor from: {class_path}. Try using /Engine/BasicShapes/Cube or StaticMeshActor"
|
|
224
|
-
result["error"] = result["message"]
|
|
225
|
-
except Exception as spawn_error:
|
|
226
|
-
result["error"] = f"Error spawning actor: {spawn_error}"
|
|
227
|
-
if not result["message"]:
|
|
228
|
-
result["message"] = result["error"]
|
|
229
|
-
|
|
230
|
-
print('RESULT:' + json.dumps(finalize()))
|
|
231
|
-
`.trim();
|
|
232
|
-
|
|
233
44
|
try {
|
|
234
|
-
const
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
45
|
+
const bridge = this.getAutomationBridge();
|
|
46
|
+
const timeoutMs = typeof params.timeoutMs === 'number' && params.timeoutMs > 0 ? params.timeoutMs : undefined;
|
|
47
|
+
const response = await bridge.sendAutomationRequest(
|
|
48
|
+
'control_actor',
|
|
49
|
+
{
|
|
50
|
+
action: 'spawn',
|
|
51
|
+
classPath: mappedClassPath,
|
|
52
|
+
location: { x: locX, y: locY, z: locZ },
|
|
53
|
+
rotation: { pitch: rotPitch, yaw: rotYaw, roll: rotRoll },
|
|
54
|
+
actorName: sanitizedActorName,
|
|
55
|
+
meshPath: params.meshPath
|
|
56
|
+
},
|
|
57
|
+
timeoutMs ? { timeoutMs } : undefined
|
|
58
|
+
);
|
|
239
59
|
|
|
240
|
-
if (!
|
|
241
|
-
throw new Error(
|
|
60
|
+
if (!response || !response.success) {
|
|
61
|
+
throw new Error(response?.error || response?.message || 'Failed to spawn actor');
|
|
242
62
|
}
|
|
243
63
|
|
|
244
|
-
const
|
|
245
|
-
const
|
|
246
|
-
const requestedClass = coerceString(interpreted.payload.requestedClass) ?? className;
|
|
247
|
-
const locationVector = coerceVector3(interpreted.payload.location) ?? [locX, locY, locZ];
|
|
248
|
-
const rotationVector = coerceVector3(interpreted.payload.rotation) ?? [rotPitch, rotYaw, rotRoll];
|
|
249
|
-
|
|
250
|
-
const result: Record<string, unknown> = {
|
|
64
|
+
const data = (response as any).data || {};
|
|
65
|
+
const result: StandardActionResponse = {
|
|
251
66
|
success: true,
|
|
252
|
-
message:
|
|
253
|
-
actorName:
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
67
|
+
message: response.message || `Spawned actor ${className}`,
|
|
68
|
+
actorName: data.name || (response as any).actorName,
|
|
69
|
+
actorPath: data.objectPath || (response as any).actorPath,
|
|
70
|
+
resolvedClass: mappedClassPath,
|
|
71
|
+
requestedClass: className,
|
|
72
|
+
location: { x: locX, y: locY, z: locZ },
|
|
73
|
+
rotation: { pitch: rotPitch, yaw: rotYaw, roll: rotRoll },
|
|
74
|
+
data: data,
|
|
75
|
+
actor: {
|
|
76
|
+
name: data.name || (response as any).actorName,
|
|
77
|
+
path: data.objectPath || (response as any).actorPath || mappedClassPath
|
|
78
|
+
}
|
|
258
79
|
};
|
|
259
80
|
|
|
260
|
-
if (
|
|
261
|
-
result.warnings =
|
|
81
|
+
if ((response as any).warnings?.length) {
|
|
82
|
+
result.warnings = (response as any).warnings;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Legacy support for older fields if they exist at top level
|
|
86
|
+
if ((response as any).details?.length) {
|
|
87
|
+
result.details = (response as any).details;
|
|
262
88
|
}
|
|
263
|
-
if (
|
|
264
|
-
result.
|
|
89
|
+
if ((response as any).componentPaths?.length) {
|
|
90
|
+
result.componentPaths = (response as any).componentPaths;
|
|
265
91
|
}
|
|
266
92
|
|
|
267
93
|
return result;
|
|
268
94
|
} catch (err) {
|
|
269
|
-
throw new Error(`Failed to spawn actor
|
|
95
|
+
throw new Error(`Failed to spawn actor: ${err}`);
|
|
270
96
|
}
|
|
271
97
|
}
|
|
272
|
-
|
|
273
|
-
async
|
|
274
|
-
|
|
275
|
-
const
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const outputStr = typeof pieCheckResult === 'string' ? pieCheckResult : JSON.stringify(pieCheckResult);
|
|
289
|
-
|
|
290
|
-
if (outputStr.includes('PIE_ACTIVE')) {
|
|
291
|
-
throw new Error('Cannot spawn actors while in Play In Editor mode. Please stop PIE first.');
|
|
292
|
-
}
|
|
293
|
-
} catch (pieErr: any) {
|
|
294
|
-
// If the error is about PIE, throw it
|
|
295
|
-
if (String(pieErr).includes('Play In Editor')) {
|
|
296
|
-
throw pieErr;
|
|
297
|
-
}
|
|
298
|
-
// Otherwise ignore and continue
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// List of known abstract classes that cannot be spawned
|
|
302
|
-
const abstractClasses = ['PlaneReflectionCapture', 'ReflectionCapture', 'Actor'];
|
|
303
|
-
|
|
304
|
-
// Check if this is an abstract class
|
|
305
|
-
if (abstractClasses.includes(params.classPath)) {
|
|
306
|
-
throw new Error(`Cannot spawn ${params.classPath}: class is abstract and cannot be instantiated`);
|
|
98
|
+
|
|
99
|
+
async delete(params: { actorName?: string; actorNames?: string[] }) {
|
|
100
|
+
if (params.actorNames && Array.isArray(params.actorNames)) {
|
|
101
|
+
const names = params.actorNames
|
|
102
|
+
.filter(name => typeof name === 'string')
|
|
103
|
+
.map(name => name.trim())
|
|
104
|
+
.filter(name => name.length > 0);
|
|
105
|
+
|
|
106
|
+
// Edge-case: empty batch should be treated as a no-op success
|
|
107
|
+
if (names.length === 0) {
|
|
108
|
+
return {
|
|
109
|
+
success: true,
|
|
110
|
+
message: 'No actors provided for deletion; no-op',
|
|
111
|
+
deleted: [],
|
|
112
|
+
noOp: true
|
|
113
|
+
};
|
|
307
114
|
}
|
|
308
|
-
|
|
309
|
-
//
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
objectPath: '/Script/Engine.Default__KismetSystemLibrary',
|
|
317
|
-
functionName: 'ExecuteConsoleCommand',
|
|
318
|
-
parameters: {
|
|
319
|
-
WorldContextObject: null,
|
|
320
|
-
Command: command,
|
|
321
|
-
SpecificPlayer: null
|
|
322
|
-
},
|
|
323
|
-
generateTransaction: false
|
|
115
|
+
|
|
116
|
+
// Call the underlying automation action directly so we can treat
|
|
117
|
+
// DELETE_PARTIAL as a handled, partial-success cleanup instead
|
|
118
|
+
// of surfacing it as a hard error to the consolidated handler.
|
|
119
|
+
const bridge = this.getAutomationBridge();
|
|
120
|
+
const response: any = await bridge.sendAutomationRequest('control_actor', {
|
|
121
|
+
action: 'delete',
|
|
122
|
+
actorNames: names
|
|
324
123
|
});
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
124
|
+
|
|
125
|
+
const result = (response?.data || response?.result || response) ?? {};
|
|
126
|
+
const deleted = result.deleted ?? names;
|
|
127
|
+
const missing = result.missing ?? [];
|
|
128
|
+
// Check for structured error in response.error OR legacy top-level error
|
|
129
|
+
const errorObj = response?.error;
|
|
130
|
+
const errorCode = (typeof errorObj === 'object' ? errorObj.code : String(errorObj || result.error || '')).toUpperCase();
|
|
131
|
+
|
|
132
|
+
// If some actors were removed and others were already missing,
|
|
133
|
+
// surface this as a partial but still successful cleanup so the
|
|
134
|
+
// tests treat it as handled rather than as an MCP transport error.
|
|
135
|
+
if (response && response.success === false && errorCode === 'DELETE_PARTIAL') {
|
|
136
|
+
return {
|
|
137
|
+
success: true,
|
|
138
|
+
message: errorObj?.message || response.message || 'Some actors could not be deleted',
|
|
139
|
+
deleted,
|
|
140
|
+
missing,
|
|
141
|
+
partial: true
|
|
142
|
+
} as StandardActionResponse;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!response || response.success === false) {
|
|
146
|
+
throw new Error(errorObj?.message || response?.message || 'Failed to delete actors');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
success: true,
|
|
151
|
+
message: response.message || 'Deleted actors',
|
|
152
|
+
deleted: result.deleted || deleted,
|
|
153
|
+
...result
|
|
154
|
+
} as StandardActionResponse;
|
|
335
155
|
}
|
|
156
|
+
|
|
157
|
+
if (!params.actorName || typeof params.actorName !== 'string') {
|
|
158
|
+
throw new Error('Invalid actorName');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return this.sendRequest('delete', { actorName: params.actorName }, 'control_actor');
|
|
336
162
|
}
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
163
|
+
|
|
164
|
+
async applyForce(params: { actorName: string; force: { x: number; y: number; z: number } }) {
|
|
165
|
+
if (!params.actorName || typeof params.actorName !== 'string') {
|
|
166
|
+
throw new Error('Invalid actorName');
|
|
167
|
+
}
|
|
168
|
+
if (!params.force || typeof params.force !== 'object') {
|
|
169
|
+
throw new Error('Invalid force vector');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const [forceX, forceY, forceZ] = ensureVector3(params.force, 'force vector');
|
|
173
|
+
|
|
174
|
+
// Edge-case: zero force vector is treated as a safe no-op. This avoids
|
|
175
|
+
// spurious ACTOR_NOT_FOUND errors when the physics actor has already been
|
|
176
|
+
// cleaned up in prior tests.
|
|
177
|
+
if (forceX === 0 && forceY === 0 && forceZ === 0) {
|
|
178
|
+
return {
|
|
179
|
+
success: true,
|
|
180
|
+
message: `Zero force provided for ${params.actorName}; no-op`,
|
|
181
|
+
physicsEnabled: false,
|
|
182
|
+
noOp: true
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return this.sendRequest('apply_force', {
|
|
187
|
+
actorName: params.actorName,
|
|
188
|
+
force: { x: forceX, y: forceY, z: forceZ }
|
|
189
|
+
}, 'control_actor');
|
|
351
190
|
}
|
|
352
191
|
|
|
353
192
|
private resolveActorClass(classPath: string): string {
|
|
@@ -377,12 +216,12 @@ def spawn_actor_from_class(actor_class, location, rotation):
|
|
|
377
216
|
// PlaneReflectionCapture is abstract and cannot be spawned
|
|
378
217
|
'DecalActor': '/Script/Engine.DecalActor'
|
|
379
218
|
};
|
|
380
|
-
|
|
219
|
+
|
|
381
220
|
// Check if it's a simple name that needs mapping
|
|
382
221
|
if (classMap[classPath]) {
|
|
383
222
|
return classMap[classPath];
|
|
384
223
|
}
|
|
385
|
-
|
|
224
|
+
|
|
386
225
|
// Check if it already looks like a full path
|
|
387
226
|
if (classPath.startsWith('/Script/') || classPath.startsWith('/Game/')) {
|
|
388
227
|
return classPath;
|
|
@@ -391,7 +230,7 @@ def spawn_actor_from_class(actor_class, location, rotation):
|
|
|
391
230
|
if (classPath.startsWith('/Engine/')) {
|
|
392
231
|
return classPath;
|
|
393
232
|
}
|
|
394
|
-
|
|
233
|
+
|
|
395
234
|
// Check for Blueprint paths
|
|
396
235
|
if (classPath.includes('Blueprint') || classPath.includes('BP_')) {
|
|
397
236
|
// Ensure it has the proper prefix
|
|
@@ -400,37 +239,293 @@ def spawn_actor_from_class(actor_class, location, rotation):
|
|
|
400
239
|
}
|
|
401
240
|
return classPath;
|
|
402
241
|
}
|
|
403
|
-
|
|
242
|
+
|
|
404
243
|
// Default: assume it's an engine class
|
|
405
244
|
return '/Script/Engine.' + classPath;
|
|
406
245
|
}
|
|
407
|
-
|
|
408
|
-
private getConsoleClassName(classPath: string): string {
|
|
409
|
-
// Normalize class path for console 'summon'
|
|
410
|
-
const input = classPath;
|
|
411
246
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
247
|
+
async spawnBlueprint(params: { blueprintPath: string; actorName?: string; location?: { x: number; y: number; z: number }; rotation?: { pitch: number; yaw: number; roll: number } }) {
|
|
248
|
+
const blueprintPath = typeof params.blueprintPath === 'string' ? params.blueprintPath.trim() : '';
|
|
249
|
+
if (!blueprintPath) {
|
|
250
|
+
throw new Error('Invalid blueprintPath');
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const actorName = typeof params.actorName === 'string' && params.actorName.trim().length > 0 ? params.actorName.trim() : undefined;
|
|
254
|
+
const location = params.location ? ensureVector3(params.location, 'spawn_blueprint location') : undefined;
|
|
255
|
+
const rotation = params.rotation ? ensureRotation(params.rotation, 'spawn_blueprint rotation') : undefined;
|
|
256
|
+
|
|
257
|
+
const payload: Record<string, unknown> = { blueprintPath };
|
|
258
|
+
if (actorName) payload.actorName = actorName;
|
|
259
|
+
if (location) payload.location = { x: location[0], y: location[1], z: location[2] };
|
|
260
|
+
if (rotation) payload.rotation = { pitch: rotation[0], yaw: rotation[1], roll: rotation[2] };
|
|
261
|
+
|
|
262
|
+
return this.sendRequest('spawn_blueprint', payload, 'control_actor');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async setTransform(params: { actorName: string; location?: { x: number; y: number; z: number }; rotation?: { pitch: number; yaw: number; roll: number }; scale?: { x: number; y: number; z: number } }) {
|
|
266
|
+
const actorName = typeof params.actorName === 'string' ? params.actorName.trim() : '';
|
|
267
|
+
if (!actorName) {
|
|
268
|
+
throw new Error('Invalid actorName');
|
|
415
269
|
}
|
|
416
270
|
|
|
417
|
-
|
|
418
|
-
if (
|
|
419
|
-
|
|
420
|
-
|
|
271
|
+
const payload: Record<string, unknown> = { actorName };
|
|
272
|
+
if (params.location) {
|
|
273
|
+
const loc = ensureVector3(params.location, 'set_transform location');
|
|
274
|
+
payload.location = { x: loc[0], y: loc[1], z: loc[2] };
|
|
421
275
|
}
|
|
276
|
+
if (params.rotation) {
|
|
277
|
+
const rot = ensureRotation(params.rotation, 'set_transform rotation');
|
|
278
|
+
payload.rotation = { pitch: rot[0], yaw: rot[1], roll: rot[2] };
|
|
279
|
+
}
|
|
280
|
+
if (params.scale) {
|
|
281
|
+
const scl = ensureVector3(params.scale, 'set_transform scale');
|
|
282
|
+
payload.scale = { x: scl[0], y: scl[1], z: scl[2] };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return this.sendRequest('set_transform', payload, 'control_actor');
|
|
286
|
+
}
|
|
422
287
|
|
|
423
|
-
|
|
424
|
-
if (
|
|
425
|
-
|
|
426
|
-
const pathWithoutSuffix = input.split('.')[0];
|
|
427
|
-
const parts = pathWithoutSuffix.split('/');
|
|
428
|
-
const assetName = parts[parts.length - 1].replace(/_C$/, '');
|
|
429
|
-
const normalized = `${pathWithoutSuffix}.${assetName}_C`;
|
|
430
|
-
return normalized;
|
|
288
|
+
async getTransform(actorName: string) {
|
|
289
|
+
if (typeof actorName !== 'string' || actorName.trim().length === 0) {
|
|
290
|
+
throw new Error('Invalid actorName');
|
|
431
291
|
}
|
|
292
|
+
return this.sendRequest('get_transform', { actorName }, 'control_actor')
|
|
293
|
+
.then(response => {
|
|
294
|
+
// If response is standardized, extract data or return as is.
|
|
295
|
+
// For now, return the full response which includes data.
|
|
296
|
+
return response;
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async setVisibility(params: { actorName: string; visible: boolean }) {
|
|
301
|
+
const actorName = typeof params.actorName === 'string' ? params.actorName.trim() : '';
|
|
302
|
+
if (!actorName) {
|
|
303
|
+
throw new Error('Invalid actorName');
|
|
304
|
+
}
|
|
305
|
+
return this.sendRequest('set_visibility', { actorName, visible: Boolean(params.visible) }, 'control_actor');
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async addComponent(params: { actorName: string; componentType: string; componentName?: string; properties?: Record<string, unknown> }) {
|
|
309
|
+
const actorName = typeof params.actorName === 'string' ? params.actorName.trim() : '';
|
|
310
|
+
const componentType = typeof params.componentType === 'string' ? params.componentType.trim() : '';
|
|
311
|
+
if (!actorName) throw new Error('Invalid actorName');
|
|
312
|
+
if (!componentType) throw new Error('Invalid componentType');
|
|
313
|
+
|
|
314
|
+
return this.sendRequest('add_component', {
|
|
315
|
+
actorName,
|
|
316
|
+
componentType,
|
|
317
|
+
componentName: typeof params.componentName === 'string' ? params.componentName : undefined,
|
|
318
|
+
properties: params.properties
|
|
319
|
+
}, 'control_actor');
|
|
320
|
+
}
|
|
432
321
|
|
|
433
|
-
|
|
434
|
-
|
|
322
|
+
async setComponentProperties(params: { actorName: string; componentName: string; properties: Record<string, unknown> }) {
|
|
323
|
+
const actorName = typeof params.actorName === 'string' ? params.actorName.trim() : '';
|
|
324
|
+
const componentName = typeof params.componentName === 'string' ? params.componentName.trim() : '';
|
|
325
|
+
if (!actorName) throw new Error('Invalid actorName');
|
|
326
|
+
if (!componentName) throw new Error('Invalid componentName');
|
|
327
|
+
|
|
328
|
+
return this.sendRequest('set_component_properties', {
|
|
329
|
+
actorName,
|
|
330
|
+
componentName,
|
|
331
|
+
properties: params.properties ?? {}
|
|
332
|
+
}, 'control_actor');
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
async getComponents(actorName: string) {
|
|
336
|
+
if (typeof actorName !== 'string' || actorName.trim().length === 0) {
|
|
337
|
+
throw new Error('Invalid actorName');
|
|
338
|
+
}
|
|
339
|
+
const response = await this.sendRequest('get_components', { actorName }, 'control_actor');
|
|
340
|
+
if (!response.success) {
|
|
341
|
+
return { success: false, error: response.error || `Failed to get components for actor ${actorName}` };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const data: any = response.data ?? response.result ?? response;
|
|
345
|
+
const components = Array.isArray(data)
|
|
346
|
+
? data
|
|
347
|
+
: (Array.isArray(data?.components) ? data.components : []);
|
|
348
|
+
const count = typeof data?.count === 'number' ? data.count : components.length;
|
|
349
|
+
|
|
350
|
+
return {
|
|
351
|
+
success: true,
|
|
352
|
+
message: 'Actor components retrieved',
|
|
353
|
+
components,
|
|
354
|
+
count
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async duplicate(params: { actorName: string; newName?: string; offset?: { x: number; y: number; z: number } }) {
|
|
359
|
+
const actorName = typeof params.actorName === 'string' ? params.actorName.trim() : '';
|
|
360
|
+
if (!actorName) throw new Error('Invalid actorName');
|
|
361
|
+
|
|
362
|
+
const payload: Record<string, unknown> = { actorName };
|
|
363
|
+
if (typeof params.newName === 'string' && params.newName.trim().length > 0) {
|
|
364
|
+
payload.newName = params.newName.trim();
|
|
365
|
+
}
|
|
366
|
+
if (params.offset) {
|
|
367
|
+
const offs = ensureVector3(params.offset, 'duplicate offset');
|
|
368
|
+
payload.offset = { x: offs[0], y: offs[1], z: offs[2] };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
return this.sendRequest('duplicate', payload, 'control_actor');
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async addTag(params: { actorName: string; tag: string }) {
|
|
375
|
+
const actorName = typeof params.actorName === 'string' ? params.actorName.trim() : '';
|
|
376
|
+
const tag = typeof params.tag === 'string' ? params.tag.trim() : '';
|
|
377
|
+
if (!actorName) throw new Error('Invalid actorName');
|
|
378
|
+
if (!tag) throw new Error('Invalid tag');
|
|
379
|
+
|
|
380
|
+
return this.sendRequest('add_tag', { actorName, tag }, 'control_actor');
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async removeTag(params: { actorName: string; tag: string }) {
|
|
384
|
+
const actorName = typeof params.actorName === 'string' ? params.actorName.trim() : '';
|
|
385
|
+
const tag = typeof params.tag === 'string' ? params.tag.trim() : '';
|
|
386
|
+
if (!actorName) throw new Error('Invalid actorName');
|
|
387
|
+
if (!tag) throw new Error('Invalid tag');
|
|
388
|
+
|
|
389
|
+
return this.sendRequest('remove_tag', { actorName, tag }, 'control_actor');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
async findByTag(params: { tag: string; matchType?: string }) {
|
|
393
|
+
const tag = typeof params.tag === 'string' ? params.tag.trim() : '';
|
|
394
|
+
|
|
395
|
+
// Edge-case: empty tag should return an empty result set instead of throwing
|
|
396
|
+
if (!tag) {
|
|
397
|
+
return {
|
|
398
|
+
success: true,
|
|
399
|
+
message: 'Empty tag query; no actors matched',
|
|
400
|
+
data: {
|
|
401
|
+
actors: [],
|
|
402
|
+
count: 0
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return this.sendRequest('find_by_tag', {
|
|
408
|
+
tag,
|
|
409
|
+
matchType: typeof params.matchType === 'string' ? params.matchType : undefined
|
|
410
|
+
}, 'control_actor');
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async findByName(name: string) {
|
|
414
|
+
if (typeof name !== 'string' || name.trim().length === 0) {
|
|
415
|
+
throw new Error('Invalid actor name query');
|
|
416
|
+
}
|
|
417
|
+
return this.sendRequest('find_by_name', { name: name.trim() }, 'control_actor');
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
async detach(actorName: string) {
|
|
421
|
+
// Support 'childActor' as alias for 'actorName' since attach() uses childActor.
|
|
422
|
+
// If actorName is missing/empty but childActor is present in the underlying request (handled by caller or if we expand args),
|
|
423
|
+
// we should handle it. However, the signature here is specific.
|
|
424
|
+
// We'll rely on the handler to map it, or we can expand the signature if needed.
|
|
425
|
+
// For now, let's keep the strict signature but ensure the handler passes it correctly.
|
|
426
|
+
// Actually, looking at the handler (actor-handlers.ts), it calls tools.actors.detach(args.actorName).
|
|
427
|
+
// So we should modify actor-handlers.ts instead to map childActor -> actorName.
|
|
428
|
+
if (typeof actorName !== 'string' || actorName.trim().length === 0) {
|
|
429
|
+
throw new Error('Invalid actorName');
|
|
430
|
+
}
|
|
431
|
+
return this.sendRequest('detach', { actorName }, 'control_actor');
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async attach(params: { childActor: string; parentActor: string }) {
|
|
435
|
+
const child = typeof params.childActor === 'string' ? params.childActor.trim() : '';
|
|
436
|
+
const parent = typeof params.parentActor === 'string' ? params.parentActor.trim() : '';
|
|
437
|
+
if (!child) throw new Error('Invalid childActor');
|
|
438
|
+
if (!parent) throw new Error('Invalid parentActor');
|
|
439
|
+
|
|
440
|
+
return this.sendRequest('attach', { childActor: child, parentActor: parent }, 'control_actor');
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
async deleteByTag(tag: string) {
|
|
444
|
+
if (typeof tag !== 'string' || tag.trim().length === 0) {
|
|
445
|
+
throw new Error('Invalid tag');
|
|
446
|
+
}
|
|
447
|
+
return this.sendRequest('delete_by_tag', { tag: tag.trim() }, 'control_actor');
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
async setBlueprintVariables(params: { actorName: string; variables: Record<string, unknown> }) {
|
|
451
|
+
const actorName = typeof params.actorName === 'string' ? params.actorName.trim() : '';
|
|
452
|
+
if (!actorName) throw new Error('Invalid actorName');
|
|
453
|
+
return this.sendRequest('set_blueprint_variables', { actorName, variables: params.variables ?? {} }, 'control_actor');
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
async createSnapshot(params: { actorName: string; snapshotName: string }) {
|
|
457
|
+
const actorName = typeof params.actorName === 'string' ? params.actorName.trim() : '';
|
|
458
|
+
const snapshotName = typeof params.snapshotName === 'string' ? params.snapshotName.trim() : '';
|
|
459
|
+
if (!actorName) throw new Error('Invalid actorName');
|
|
460
|
+
if (!snapshotName) throw new Error('Invalid snapshotName');
|
|
461
|
+
return this.sendRequest('create_snapshot', { actorName, snapshotName }, 'control_actor');
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
async restoreSnapshot(params: { actorName: string; snapshotName: string }) {
|
|
465
|
+
const actorName = typeof params.actorName === 'string' ? params.actorName.trim() : '';
|
|
466
|
+
const snapshotName = typeof params.snapshotName === 'string' ? params.snapshotName.trim() : '';
|
|
467
|
+
if (!actorName) throw new Error('Invalid actorName');
|
|
468
|
+
if (!snapshotName) throw new Error('Invalid snapshotName');
|
|
469
|
+
return this.sendRequest('restore_snapshot', { actorName, snapshotName }, 'control_actor');
|
|
470
|
+
}
|
|
471
|
+
async exportActor(params: { actorName: string; destinationPath?: string }) {
|
|
472
|
+
const actorName = typeof params.actorName === 'string' ? params.actorName.trim() : '';
|
|
473
|
+
if (!actorName) throw new Error('Invalid actorName');
|
|
474
|
+
return this.sendRequest('export', {
|
|
475
|
+
actorName,
|
|
476
|
+
destinationPath: params.destinationPath
|
|
477
|
+
}, 'control_actor');
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async getBoundingBox(actorName: string) {
|
|
481
|
+
if (typeof actorName !== 'string' || actorName.trim().length === 0) {
|
|
482
|
+
throw new Error('Invalid actorName');
|
|
483
|
+
}
|
|
484
|
+
const response = await this.sendRequest('get_bounding_box', { actorName }, 'control_actor');
|
|
485
|
+
if (!response.success) {
|
|
486
|
+
return { success: false, error: response.error || `Failed to get bounding box for actor ${actorName}` };
|
|
487
|
+
}
|
|
488
|
+
return {
|
|
489
|
+
success: true,
|
|
490
|
+
message: 'Bounding box retrieved',
|
|
491
|
+
boundingBox: response.data || response.result || {}
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
async getMetadata(actorName: string) {
|
|
496
|
+
if (typeof actorName !== 'string' || actorName.trim().length === 0) {
|
|
497
|
+
throw new Error('Invalid actorName');
|
|
498
|
+
}
|
|
499
|
+
const response = await this.sendRequest('get_metadata', { actorName }, 'control_actor');
|
|
500
|
+
if (!response.success) {
|
|
501
|
+
return { success: false, error: response.error || `Failed to get metadata for actor ${actorName}` };
|
|
502
|
+
}
|
|
503
|
+
return {
|
|
504
|
+
success: true,
|
|
505
|
+
message: 'Actor metadata retrieved',
|
|
506
|
+
metadata: response.data || response.result || {}
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
async listActors(params?: { filter?: string }) {
|
|
511
|
+
const payload: any = {};
|
|
512
|
+
if (params?.filter) {
|
|
513
|
+
payload.filter = params.filter;
|
|
514
|
+
}
|
|
515
|
+
const response = await this.sendRequest('list_actors', payload, 'control_actor');
|
|
516
|
+
if (!response.success) {
|
|
517
|
+
return { success: false, error: response.error || 'Failed to list actors' };
|
|
518
|
+
}
|
|
519
|
+
// C++ returns actors in data.actors, or directly in actors field
|
|
520
|
+
// Handle both: response.data?.actors, response.actors, or response.data as array
|
|
521
|
+
const dataObj = response.data || response.result || {};
|
|
522
|
+
const actorsRaw = response.actors || (dataObj && dataObj.actors) || (Array.isArray(dataObj) ? dataObj : []);
|
|
523
|
+
const actors = Array.isArray(actorsRaw) ? actorsRaw : [];
|
|
524
|
+
return {
|
|
525
|
+
success: true,
|
|
526
|
+
message: `Found ${actors.length} actors`,
|
|
527
|
+
actors,
|
|
528
|
+
count: actors.length
|
|
529
|
+
};
|
|
435
530
|
}
|
|
436
531
|
}
|