unreal-engine-mcp-server 0.4.6 → 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 +269 -22
- package/CONTRIBUTING.md +140 -0
- package/README.md +166 -72
- 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 -604
- 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 +5475 -1627
- package/dist/tools/consolidated-tool-definitions.js +829 -482
- package/dist/tools/consolidated-tool-handlers.d.ts +2 -1
- package/dist/tools/consolidated-tool-handlers.js +211 -1009
- 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 +45 -0
- package/dist/tools/logs.js +210 -0
- 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 +195 -11
- 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 -649
- 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 -500
- package/src/tools/consolidated-tool-handlers.ts +272 -1122
- 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 +219 -0
- 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 +250 -13
- 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 -572
- 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
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Ajv from 'ajv';
|
|
2
2
|
import { Logger } from './logger.js';
|
|
3
3
|
import { cleanObject } from './safe-json.js';
|
|
4
|
+
import { wasmIntegration } from '../wasm/index.js';
|
|
4
5
|
|
|
5
6
|
const log = new Logger('ResponseValidator');
|
|
6
7
|
|
|
@@ -26,45 +27,117 @@ function buildSummaryText(toolName: string, payload: unknown): string {
|
|
|
26
27
|
return `${toolName} responded`;
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
// 1. Check for specific "data" or "result" wrapper
|
|
31
|
+
const effectivePayload: Record<string, any> = { ...(payload as object) };
|
|
32
|
+
if (isRecord(effectivePayload.data)) {
|
|
33
|
+
Object.assign(effectivePayload, effectivePayload.data);
|
|
34
|
+
}
|
|
35
|
+
if (isRecord(effectivePayload.result)) {
|
|
36
|
+
Object.assign(effectivePayload, effectivePayload.result);
|
|
37
|
+
}
|
|
38
|
+
|
|
29
39
|
const parts: string[] = [];
|
|
40
|
+
|
|
41
|
+
// 2. Identify "List" responses (Arrays) - Prioritize showing content
|
|
42
|
+
const listKeys = ['actors', 'levels', 'assets', 'folders', 'blueprints', 'components', 'pawnClasses', 'foliageTypes', 'nodes', 'tracks', 'bindings', 'keys'];
|
|
43
|
+
for (const key of listKeys) {
|
|
44
|
+
if (Array.isArray(effectivePayload[key])) {
|
|
45
|
+
const arr = effectivePayload[key] as any[];
|
|
46
|
+
const names = arr.map(i => isRecord(i) ? (i.name || i.path || i.id || i.assetName || i.objectPath || i.packageName || i.nodeName || '<?>') : String(i));
|
|
47
|
+
const count = arr.length;
|
|
48
|
+
const preview = names.slice(0, 100).join(', '); // Show up to 100
|
|
49
|
+
const suffix = count > 100 ? `, ... (+${count - 100} more)` : '';
|
|
50
|
+
parts.push(`${key}: ${preview}${suffix} (Total: ${count})`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 3. Identify "Entity" operations (Single significant object)
|
|
55
|
+
if (typeof effectivePayload.actor === 'string' || isRecord(effectivePayload.actor)) {
|
|
56
|
+
const a = effectivePayload.actor;
|
|
57
|
+
const name = isRecord(a) ? (a.name || a.path) : a;
|
|
58
|
+
const loc = isRecord(effectivePayload.location) ? ` at [${effectivePayload.location.x},${effectivePayload.location.y},${effectivePayload.location.z}]` : '';
|
|
59
|
+
parts.push(`Actor: ${name}${loc}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (typeof effectivePayload.asset === 'string' || isRecord(effectivePayload.asset)) {
|
|
63
|
+
const a = effectivePayload.asset;
|
|
64
|
+
const path = isRecord(a) ? (a.path || a.name) : a;
|
|
65
|
+
parts.push(`Asset: ${path}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (typeof effectivePayload.blueprint === 'string' || isRecord(effectivePayload.blueprint)) {
|
|
69
|
+
const bp = effectivePayload.blueprint;
|
|
70
|
+
const name = isRecord(bp) ? (bp.name || bp.path || effectivePayload.blueprintPath) : bp;
|
|
71
|
+
parts.push(`Blueprint: ${name}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (typeof effectivePayload.sequence === 'string' || isRecord(effectivePayload.sequence)) {
|
|
75
|
+
const seq = effectivePayload.sequence;
|
|
76
|
+
const name = isRecord(seq) ? (seq.name || seq.path) : seq;
|
|
77
|
+
parts.push(`Sequence: ${name}`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 4. Generic Key-Value Summary (Contextual)
|
|
81
|
+
// Added: sequencePath, graphName, nodeName, variableName, memberName, scriptName, etc.
|
|
82
|
+
const usefulKeys = [
|
|
83
|
+
'success', 'error', 'message', 'assets', 'folders', 'count', 'totalCount',
|
|
84
|
+
'saved', 'valid', 'issues', 'class', 'skeleton', 'parent',
|
|
85
|
+
'package', 'dependencies', 'graph', 'tags', 'metadata', 'properties'
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
for (const key of usefulKeys) {
|
|
89
|
+
if (effectivePayload[key] !== undefined && effectivePayload[key] !== null) {
|
|
90
|
+
const val = effectivePayload[key];
|
|
91
|
+
// Special handling for objects like metadata
|
|
92
|
+
if (typeof val === 'object') {
|
|
93
|
+
if (key === 'metadata' || key === 'properties' || key === 'tags') {
|
|
94
|
+
const entries = Object.entries(val as object);
|
|
95
|
+
// Format as "Key=Value", skip generic types if possible, or just show raw
|
|
96
|
+
const formatted = entries.map(([k, v]) => `${k}=${v}`);
|
|
97
|
+
const limit = 50; // Show more items as requested
|
|
98
|
+
parts.push(`${key}: { ${formatted.slice(0, limit).join(', ')}${formatted.length > limit ? '...' : ''} }`);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
// Try to find a name if it's an object
|
|
102
|
+
// Skip complex objects unless handled above
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const strVal = String(val);
|
|
107
|
+
// Avoid traversing huge strings
|
|
108
|
+
if (strVal.length > 100) continue;
|
|
109
|
+
|
|
110
|
+
if (!parts.some(p => p.includes(strVal))) {
|
|
111
|
+
parts.push(`${key}: ${strVal}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// 5. Add standard status messages LAST
|
|
117
|
+
const success = typeof payload.success === 'boolean' ? payload.success : undefined;
|
|
30
118
|
const message = typeof payload.message === 'string' ? normalizeText(payload.message) : '';
|
|
31
119
|
const error = typeof payload.error === 'string' ? normalizeText(payload.error) : '';
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (message) parts.push(message);
|
|
38
|
-
if (error && (!message || !message.includes(error))) parts.push(`error: ${error}`);
|
|
39
|
-
if (success) parts.push(success);
|
|
40
|
-
if (path) parts.push(`path: ${path}`);
|
|
41
|
-
if (name) parts.push(`name: ${name}`);
|
|
42
|
-
if (warningCount > 0) parts.push(`warnings: ${warningCount}`);
|
|
43
|
-
|
|
44
|
-
const summary = isRecord(payload.summary) ? payload.summary : undefined;
|
|
45
|
-
if (summary) {
|
|
46
|
-
const summaryParts: string[] = [];
|
|
47
|
-
for (const [key, value] of Object.entries(summary)) {
|
|
48
|
-
if (typeof value === 'number' || typeof value === 'string') {
|
|
49
|
-
summaryParts.push(`${key}: ${value}`);
|
|
50
|
-
}
|
|
51
|
-
if (summaryParts.length >= 3) break;
|
|
120
|
+
|
|
121
|
+
if (parts.length > 0) {
|
|
122
|
+
if (message && message.toLowerCase() !== 'success') {
|
|
123
|
+
parts.push(message);
|
|
52
124
|
}
|
|
53
|
-
|
|
54
|
-
|
|
125
|
+
} else {
|
|
126
|
+
// No data parts, rely on message/error
|
|
127
|
+
if (message) parts.push(message);
|
|
128
|
+
if (error) parts.push(`Error: ${error}`);
|
|
129
|
+
if (parts.length === 0 && success !== undefined) {
|
|
130
|
+
parts.push(success ? 'Success' : 'Failed');
|
|
55
131
|
}
|
|
56
132
|
}
|
|
57
133
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
134
|
+
// 6. Warnings
|
|
135
|
+
const warnings = Array.isArray(payload.warnings) ? payload.warnings : [];
|
|
136
|
+
if (warnings.length > 0) {
|
|
137
|
+
parts.push(`Warnings: ${warnings.length}`);
|
|
63
138
|
}
|
|
64
139
|
|
|
65
|
-
return parts.length > 0
|
|
66
|
-
? parts.join(' | ')
|
|
67
|
-
: `${toolName} responded`;
|
|
140
|
+
return parts.length > 0 ? parts.join(' | ') : `${toolName} responded`;
|
|
68
141
|
}
|
|
69
142
|
|
|
70
143
|
/**
|
|
@@ -84,7 +157,7 @@ export class ResponseValidator {
|
|
|
84
157
|
this.ajv = new AjvCtor({
|
|
85
158
|
allErrors: true,
|
|
86
159
|
verbose: true,
|
|
87
|
-
strict:
|
|
160
|
+
strict: true // Enforce strict schema validation
|
|
88
161
|
});
|
|
89
162
|
}
|
|
90
163
|
|
|
@@ -110,13 +183,13 @@ export class ResponseValidator {
|
|
|
110
183
|
/**
|
|
111
184
|
* Validate a tool's response against its schema
|
|
112
185
|
*/
|
|
113
|
-
validateResponse(toolName: string, response: any): {
|
|
114
|
-
valid: boolean;
|
|
186
|
+
async validateResponse(toolName: string, response: any): Promise<{
|
|
187
|
+
valid: boolean;
|
|
115
188
|
errors?: string[];
|
|
116
189
|
structuredContent?: any;
|
|
117
|
-
} {
|
|
190
|
+
}> {
|
|
118
191
|
const validator = this.validators.get(toolName);
|
|
119
|
-
|
|
192
|
+
|
|
120
193
|
if (!validator) {
|
|
121
194
|
log.debug(`No validator found for tool: ${toolName}`);
|
|
122
195
|
return { valid: true }; // Pass through if no schema defined
|
|
@@ -124,41 +197,52 @@ export class ResponseValidator {
|
|
|
124
197
|
|
|
125
198
|
// Extract structured content from response
|
|
126
199
|
let structuredContent = response;
|
|
127
|
-
|
|
200
|
+
|
|
128
201
|
// If response has MCP format with content array
|
|
129
202
|
if (response.content && Array.isArray(response.content)) {
|
|
130
203
|
// Try to extract structured data from text content
|
|
131
204
|
const textContent = response.content.find((c: any) => c.type === 'text');
|
|
132
205
|
if (textContent?.text) {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
206
|
+
const rawText = String(textContent.text);
|
|
207
|
+
const trimmed = rawText.trim();
|
|
208
|
+
const looksLikeJson = trimmed.startsWith('{') || trimmed.startsWith('[');
|
|
209
|
+
|
|
210
|
+
if (looksLikeJson) {
|
|
211
|
+
try {
|
|
212
|
+
// Check if text is JSON - use WASM for high-performance parsing (5-8x faster)
|
|
213
|
+
structuredContent = await wasmIntegration.parseProperties(rawText);
|
|
214
|
+
} catch {
|
|
215
|
+
// If JSON parsing fails, fall back to using the full response without
|
|
216
|
+
// emitting noisy parse errors for plain-text messages.
|
|
217
|
+
structuredContent = response;
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
// Plain-text summary or error message; keep the original structured
|
|
221
|
+
// response object instead of attempting JSON parsing.
|
|
138
222
|
structuredContent = response;
|
|
139
223
|
}
|
|
140
224
|
}
|
|
141
225
|
}
|
|
142
226
|
|
|
143
227
|
const valid = validator(structuredContent);
|
|
144
|
-
|
|
228
|
+
|
|
145
229
|
if (!valid) {
|
|
146
|
-
const errors = validator.errors?.map((err: any) =>
|
|
230
|
+
const errors = validator.errors?.map((err: any) =>
|
|
147
231
|
`${err.instancePath || 'root'}: ${err.message}`
|
|
148
232
|
);
|
|
149
|
-
|
|
233
|
+
|
|
150
234
|
log.warn(`Response validation failed for ${toolName}:`, errors);
|
|
151
|
-
|
|
152
|
-
return {
|
|
153
|
-
valid: false,
|
|
235
|
+
|
|
236
|
+
return {
|
|
237
|
+
valid: false,
|
|
154
238
|
errors,
|
|
155
|
-
structuredContent
|
|
239
|
+
structuredContent
|
|
156
240
|
};
|
|
157
241
|
}
|
|
158
242
|
|
|
159
|
-
return {
|
|
243
|
+
return {
|
|
160
244
|
valid: true,
|
|
161
|
-
structuredContent
|
|
245
|
+
structuredContent
|
|
162
246
|
};
|
|
163
247
|
}
|
|
164
248
|
|
|
@@ -170,7 +254,7 @@ export class ResponseValidator {
|
|
|
170
254
|
* This wrapper serializes such objects into a single text block while keeping
|
|
171
255
|
* existing `content` responses intact.
|
|
172
256
|
*/
|
|
173
|
-
wrapResponse(toolName: string, response: any): any {
|
|
257
|
+
async wrapResponse(toolName: string, response: any): Promise<any> {
|
|
174
258
|
// Ensure response is safe to serialize first
|
|
175
259
|
try {
|
|
176
260
|
if (response && typeof response === 'object') {
|
|
@@ -186,7 +270,7 @@ export class ResponseValidator {
|
|
|
186
270
|
|
|
187
271
|
// Choose the payload to validate: if already MCP-shaped, validate the
|
|
188
272
|
// structured content extracted from text; otherwise validate the object directly.
|
|
189
|
-
const validation = this.validateResponse(toolName, response);
|
|
273
|
+
const validation = await this.validateResponse(toolName, response);
|
|
190
274
|
const structuredPayload = validation.structuredContent;
|
|
191
275
|
|
|
192
276
|
if (!validation.valid) {
|
|
@@ -195,17 +279,25 @@ export class ResponseValidator {
|
|
|
195
279
|
|
|
196
280
|
// If it's already MCP-shaped, return as-is (optionally append validation meta)
|
|
197
281
|
if (alreadyMcpShaped) {
|
|
198
|
-
if (structuredPayload !== undefined && response && typeof response === 'object' && response.structuredContent === undefined) {
|
|
282
|
+
if (structuredPayload !== undefined && response && typeof response === 'object' && (response as any).structuredContent === undefined) {
|
|
199
283
|
try {
|
|
200
284
|
(response as any).structuredContent = structuredPayload && typeof structuredPayload === 'object'
|
|
201
285
|
? cleanObject(structuredPayload)
|
|
202
286
|
: structuredPayload;
|
|
203
|
-
} catch {}
|
|
287
|
+
} catch { }
|
|
204
288
|
}
|
|
289
|
+
// Promote failure semantics to top-level isError when obvious
|
|
290
|
+
try {
|
|
291
|
+
const sc: any = (response as any).structuredContent || structuredPayload || {};
|
|
292
|
+
const hasExplicitFailure = (typeof sc.success === 'boolean' && sc.success === false) || (typeof sc.error === 'string' && sc.error.length > 0);
|
|
293
|
+
if (hasExplicitFailure && (response as any).isError !== true) {
|
|
294
|
+
(response as any).isError = true;
|
|
295
|
+
}
|
|
296
|
+
} catch { }
|
|
205
297
|
if (!validation.valid) {
|
|
206
298
|
try {
|
|
207
299
|
(response as any)._validation = { valid: false, errors: validation.errors };
|
|
208
|
-
} catch {}
|
|
300
|
+
} catch { }
|
|
209
301
|
}
|
|
210
302
|
return response;
|
|
211
303
|
}
|
|
@@ -223,6 +315,16 @@ export class ResponseValidator {
|
|
|
223
315
|
]
|
|
224
316
|
} as any;
|
|
225
317
|
|
|
318
|
+
// Surface a top-level success flag when available so clients and test
|
|
319
|
+
// harnesses do not have to infer success from the absence of isError.
|
|
320
|
+
try {
|
|
321
|
+
if (structuredPayload && typeof (structuredPayload as any).success === 'boolean') {
|
|
322
|
+
(wrapped as any).success = Boolean((structuredPayload as any).success);
|
|
323
|
+
} else if (response && typeof (response as any).success === 'boolean') {
|
|
324
|
+
(wrapped as any).success = Boolean((response as any).success);
|
|
325
|
+
}
|
|
326
|
+
} catch { }
|
|
327
|
+
|
|
226
328
|
if (structuredPayload !== undefined) {
|
|
227
329
|
try {
|
|
228
330
|
wrapped.structuredContent = structuredPayload && typeof structuredPayload === 'object'
|
|
@@ -239,10 +341,28 @@ export class ResponseValidator {
|
|
|
239
341
|
}
|
|
240
342
|
}
|
|
241
343
|
|
|
344
|
+
// Promote failure semantics to top-level isError when obvious
|
|
345
|
+
try {
|
|
346
|
+
const sc: any = wrapped.structuredContent || {};
|
|
347
|
+
const hasExplicitFailure = (typeof sc.success === 'boolean' && sc.success === false) || (typeof sc.error === 'string' && sc.error.length > 0);
|
|
348
|
+
if (hasExplicitFailure) {
|
|
349
|
+
wrapped.isError = true;
|
|
350
|
+
}
|
|
351
|
+
} catch { }
|
|
352
|
+
|
|
242
353
|
if (!validation.valid) {
|
|
243
354
|
wrapped._validation = { valid: false, errors: validation.errors };
|
|
244
355
|
}
|
|
245
356
|
|
|
357
|
+
// Mark explicit error when success is false to avoid false positives in
|
|
358
|
+
// clients that check only for the absence of isError.
|
|
359
|
+
try {
|
|
360
|
+
const s = (wrapped as any).success;
|
|
361
|
+
if (typeof s === 'boolean' && s === false) {
|
|
362
|
+
(wrapped as any).isError = true;
|
|
363
|
+
}
|
|
364
|
+
} catch { }
|
|
365
|
+
|
|
246
366
|
return wrapped;
|
|
247
367
|
}
|
|
248
368
|
|
|
@@ -258,4 +378,4 @@ export class ResponseValidator {
|
|
|
258
378
|
}
|
|
259
379
|
|
|
260
380
|
// Singleton instance
|
|
261
|
-
export const responseValidator = new ResponseValidator();
|
|
381
|
+
export const responseValidator = new ResponseValidator();
|
|
@@ -1,26 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// Generic result interpretation for automation bridge responses
|
|
3
2
|
export interface InterpretedStandardResult {
|
|
4
3
|
success: boolean;
|
|
5
4
|
message: string;
|
|
6
5
|
error?: string;
|
|
7
6
|
warnings?: string[];
|
|
8
7
|
details?: string[];
|
|
9
|
-
payload:
|
|
8
|
+
payload: Record<string, unknown>;
|
|
10
9
|
cleanText?: string;
|
|
11
10
|
rawText: string;
|
|
12
11
|
raw: unknown;
|
|
13
12
|
}
|
|
14
13
|
|
|
14
|
+
/** Interprets automation bridge responses into a standard format */
|
|
15
15
|
export function interpretStandardResult(
|
|
16
16
|
response: unknown,
|
|
17
17
|
defaults: { successMessage: string; failureMessage: string }
|
|
18
18
|
): InterpretedStandardResult {
|
|
19
|
-
|
|
20
|
-
const payload = (
|
|
19
|
+
// Handle automation bridge response format
|
|
20
|
+
const payload = (response && typeof response === 'object' ? response : {}) as Record<string, unknown>;
|
|
21
21
|
const success = payload.success === true;
|
|
22
|
-
const rawText = typeof
|
|
23
|
-
|
|
22
|
+
const rawText = typeof payload.message === 'string' ? payload.message :
|
|
23
|
+
typeof payload.output === 'string' ? payload.output :
|
|
24
|
+
String(payload.result ?? '');
|
|
24
25
|
|
|
25
26
|
const messageFromPayload = typeof payload.message === 'string' ? payload.message.trim() : '';
|
|
26
27
|
const errorFromPayload = typeof payload.error === 'string' ? payload.error.trim() : '';
|
|
@@ -35,32 +36,33 @@ export function interpretStandardResult(
|
|
|
35
36
|
warnings: coerceStringArray(payload.warnings),
|
|
36
37
|
details: coerceStringArray(payload.details),
|
|
37
38
|
payload,
|
|
38
|
-
cleanText:
|
|
39
|
+
cleanText: rawText || undefined,
|
|
39
40
|
rawText,
|
|
40
|
-
raw:
|
|
41
|
+
raw: response
|
|
41
42
|
};
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
/** Cleans result text by removing tags */
|
|
44
46
|
export function cleanResultText(
|
|
45
47
|
text: string | undefined,
|
|
46
|
-
options: { tag?: string;
|
|
48
|
+
options: { tag?: string; defaultValue?: string } = {}
|
|
47
49
|
): string | undefined {
|
|
48
|
-
const {
|
|
50
|
+
const { defaultValue } = options;
|
|
49
51
|
if (!text) {
|
|
50
|
-
return
|
|
52
|
+
return defaultValue;
|
|
51
53
|
}
|
|
52
54
|
|
|
53
|
-
const cleaned =
|
|
55
|
+
const cleaned = text.trim();
|
|
54
56
|
if (cleaned.length > 0) {
|
|
55
57
|
return cleaned;
|
|
56
58
|
}
|
|
57
59
|
|
|
58
|
-
return
|
|
60
|
+
return defaultValue;
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
export function bestEffortInterpretedText(
|
|
62
64
|
interpreted: Pick<InterpretedStandardResult, 'cleanText' | 'rawText'>,
|
|
63
|
-
|
|
65
|
+
defaultValue?: string
|
|
64
66
|
): string | undefined {
|
|
65
67
|
const cleaned = interpreted.cleanText?.trim();
|
|
66
68
|
if (cleaned) {
|
|
@@ -68,11 +70,11 @@ export function bestEffortInterpretedText(
|
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
const raw = interpreted.rawText?.trim?.();
|
|
71
|
-
if (raw
|
|
73
|
+
if (raw) {
|
|
72
74
|
return raw;
|
|
73
75
|
}
|
|
74
76
|
|
|
75
|
-
return
|
|
77
|
+
return defaultValue;
|
|
76
78
|
}
|
|
77
79
|
|
|
78
80
|
export function coerceString(value: unknown): string | undefined {
|
|
@@ -103,7 +105,7 @@ export function coerceStringArray(value: unknown): string[] | undefined {
|
|
|
103
105
|
return items.length > 0 ? items : undefined;
|
|
104
106
|
}
|
|
105
107
|
|
|
106
|
-
export function coerceBoolean(value: unknown,
|
|
108
|
+
export function coerceBoolean(value: unknown, defaultValue?: boolean): boolean | undefined {
|
|
107
109
|
if (typeof value === 'boolean') {
|
|
108
110
|
return value;
|
|
109
111
|
}
|
|
@@ -127,7 +129,7 @@ export function coerceBoolean(value: unknown, fallback?: boolean): boolean | und
|
|
|
127
129
|
}
|
|
128
130
|
}
|
|
129
131
|
|
|
130
|
-
return
|
|
132
|
+
return defaultValue;
|
|
131
133
|
}
|
|
132
134
|
|
|
133
135
|
export function coerceNumber(value: unknown): number | undefined {
|
package/src/utils/safe-json.ts
CHANGED
|
@@ -1,18 +1,21 @@
|
|
|
1
|
+
import { Logger } from './logger.js';
|
|
2
|
+
|
|
1
3
|
// Remove circular references and non-serializable properties from an object
|
|
2
4
|
export function cleanObject(obj: any, maxDepth: number = 10): any {
|
|
3
5
|
const seen = new WeakSet();
|
|
4
|
-
|
|
6
|
+
const logger = new Logger('safe-json');
|
|
7
|
+
|
|
5
8
|
function clean(value: any, depth: number, path: string = 'root'): any {
|
|
6
9
|
// Prevent infinite recursion
|
|
7
10
|
if (depth > maxDepth) {
|
|
8
11
|
return '[Max depth reached]';
|
|
9
12
|
}
|
|
10
|
-
|
|
13
|
+
|
|
11
14
|
// Handle primitives
|
|
12
15
|
if (value === null || value === undefined) {
|
|
13
16
|
return value;
|
|
14
17
|
}
|
|
15
|
-
|
|
18
|
+
|
|
16
19
|
if (typeof value !== 'object') {
|
|
17
20
|
if (typeof value === 'function' || typeof value === 'symbol') {
|
|
18
21
|
return undefined;
|
|
@@ -22,24 +25,24 @@ export function cleanObject(obj: any, maxDepth: number = 10): any {
|
|
|
22
25
|
}
|
|
23
26
|
return value;
|
|
24
27
|
}
|
|
25
|
-
|
|
28
|
+
|
|
26
29
|
// Check for circular reference
|
|
27
30
|
if (seen.has(value)) {
|
|
28
31
|
return '[Circular Reference]';
|
|
29
32
|
}
|
|
30
|
-
|
|
33
|
+
|
|
31
34
|
seen.add(value);
|
|
32
|
-
|
|
35
|
+
|
|
33
36
|
// Handle arrays
|
|
34
37
|
if (Array.isArray(value)) {
|
|
35
38
|
const result = value.map((item, index) => clean(item, depth + 1, `${path}[${index}]`));
|
|
36
39
|
seen.delete(value); // Remove from seen after processing
|
|
37
40
|
return result;
|
|
38
41
|
}
|
|
39
|
-
|
|
42
|
+
|
|
40
43
|
// Handle objects
|
|
41
44
|
const cleaned: any = {};
|
|
42
|
-
|
|
45
|
+
|
|
43
46
|
// Use Object.keys to avoid prototype properties
|
|
44
47
|
const keys = Object.keys(value);
|
|
45
48
|
for (const key of keys) {
|
|
@@ -50,13 +53,13 @@ export function cleanObject(obj: any, maxDepth: number = 10): any {
|
|
|
50
53
|
}
|
|
51
54
|
} catch (e) {
|
|
52
55
|
// Skip properties that throw errors when accessed
|
|
53
|
-
|
|
56
|
+
logger.error(`Error cleaning property ${path}.${key}`, e);
|
|
54
57
|
}
|
|
55
58
|
}
|
|
56
|
-
|
|
59
|
+
|
|
57
60
|
seen.delete(value); // Remove from seen after processing
|
|
58
61
|
return cleaned;
|
|
59
62
|
}
|
|
60
|
-
|
|
63
|
+
|
|
61
64
|
return clean(obj, 0);
|
|
62
65
|
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { Logger } from './logger.js';
|
|
2
|
+
|
|
3
|
+
export interface CommandQueueItem<T = any> {
|
|
4
|
+
command: () => Promise<T>;
|
|
5
|
+
resolve: (value: T) => void;
|
|
6
|
+
reject: (reason?: any) => void;
|
|
7
|
+
priority: number;
|
|
8
|
+
retryCount?: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class UnrealCommandQueue {
|
|
12
|
+
private log = new Logger('UnrealCommandQueue');
|
|
13
|
+
private queue: CommandQueueItem[] = [];
|
|
14
|
+
private isProcessing = false;
|
|
15
|
+
private lastCommandTime = 0;
|
|
16
|
+
private lastStatCommandTime = 0;
|
|
17
|
+
|
|
18
|
+
// Config
|
|
19
|
+
private readonly MIN_COMMAND_DELAY = 100;
|
|
20
|
+
private readonly MAX_COMMAND_DELAY = 500;
|
|
21
|
+
private readonly STAT_COMMAND_DELAY = 300;
|
|
22
|
+
|
|
23
|
+
constructor() {
|
|
24
|
+
this.startProcessor();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Execute a command with priority-based throttling
|
|
29
|
+
*/
|
|
30
|
+
async execute<T>(command: () => Promise<T>, priority: number = 5): Promise<T> {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
this.queue.push({
|
|
33
|
+
command,
|
|
34
|
+
resolve,
|
|
35
|
+
reject,
|
|
36
|
+
priority
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Sort by priority (lower number = higher priority)
|
|
40
|
+
this.queue.sort((a, b) => a.priority - b.priority);
|
|
41
|
+
|
|
42
|
+
// Process queue if not already processing
|
|
43
|
+
if (!this.isProcessing) {
|
|
44
|
+
this.processQueue();
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private async processQueue(): Promise<void> {
|
|
50
|
+
if (this.isProcessing || this.queue.length === 0) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.isProcessing = true;
|
|
55
|
+
|
|
56
|
+
while (this.queue.length > 0) {
|
|
57
|
+
const item = this.queue.shift();
|
|
58
|
+
if (!item) continue;
|
|
59
|
+
|
|
60
|
+
// Calculate delay based on time since last command
|
|
61
|
+
const timeSinceLastCommand = Date.now() - this.lastCommandTime;
|
|
62
|
+
const requiredDelay = this.calculateDelay(item.priority);
|
|
63
|
+
|
|
64
|
+
if (timeSinceLastCommand < requiredDelay) {
|
|
65
|
+
await this.delay(requiredDelay - timeSinceLastCommand);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const result = await item.command();
|
|
70
|
+
item.resolve(result);
|
|
71
|
+
} catch (error: any) {
|
|
72
|
+
// Enhanced retry policy
|
|
73
|
+
const msgRaw = error?.message ?? String(error);
|
|
74
|
+
const msg = String(msgRaw).toLowerCase();
|
|
75
|
+
if (item.retryCount === undefined) item.retryCount = 0;
|
|
76
|
+
|
|
77
|
+
const isTransient = (
|
|
78
|
+
msg.includes('timeout') ||
|
|
79
|
+
msg.includes('timed out') ||
|
|
80
|
+
msg.includes('connect') ||
|
|
81
|
+
msg.includes('econnrefused') ||
|
|
82
|
+
msg.includes('econnreset') ||
|
|
83
|
+
msg.includes('broken pipe') ||
|
|
84
|
+
msg.includes('automation bridge') ||
|
|
85
|
+
msg.includes('not connected')
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const isDeterministicFailure = (
|
|
89
|
+
msg.includes('command not executed') ||
|
|
90
|
+
msg.includes('exec_failed') ||
|
|
91
|
+
msg.includes('invalid command') ||
|
|
92
|
+
msg.includes('invalid argument') ||
|
|
93
|
+
msg.includes('unknown_plugin_action') ||
|
|
94
|
+
msg.includes('unknown action')
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
if (isTransient && item.retryCount < 3) {
|
|
98
|
+
item.retryCount++;
|
|
99
|
+
this.log.warn(`Command failed (transient), retrying (${item.retryCount}/3)`);
|
|
100
|
+
this.queue.unshift({
|
|
101
|
+
command: item.command,
|
|
102
|
+
resolve: item.resolve,
|
|
103
|
+
reject: item.reject,
|
|
104
|
+
priority: Math.max(1, item.priority - 1),
|
|
105
|
+
retryCount: item.retryCount
|
|
106
|
+
});
|
|
107
|
+
await this.delay(500);
|
|
108
|
+
} else {
|
|
109
|
+
if (isDeterministicFailure) {
|
|
110
|
+
this.log.warn(`Command failed (non-retryable): ${msgRaw}`);
|
|
111
|
+
}
|
|
112
|
+
item.reject(error);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
this.lastCommandTime = Date.now();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
this.isProcessing = false;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private calculateDelay(priority: number): number {
|
|
123
|
+
if (priority <= 3) {
|
|
124
|
+
return this.MAX_COMMAND_DELAY;
|
|
125
|
+
} else if (priority <= 6) {
|
|
126
|
+
return 200;
|
|
127
|
+
} else if (priority === 8) {
|
|
128
|
+
const timeSinceLastStat = Date.now() - this.lastStatCommandTime;
|
|
129
|
+
if (timeSinceLastStat < this.STAT_COMMAND_DELAY) {
|
|
130
|
+
return this.STAT_COMMAND_DELAY;
|
|
131
|
+
}
|
|
132
|
+
this.lastStatCommandTime = Date.now();
|
|
133
|
+
return 150;
|
|
134
|
+
} else {
|
|
135
|
+
const baseDelay = this.MIN_COMMAND_DELAY;
|
|
136
|
+
const jitter = Math.random() * 50;
|
|
137
|
+
return baseDelay + jitter;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
private startProcessor(): void {
|
|
142
|
+
setInterval(() => {
|
|
143
|
+
if (!this.isProcessing && this.queue.length > 0) {
|
|
144
|
+
this.processQueue();
|
|
145
|
+
}
|
|
146
|
+
}, 1000);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
private delay(ms: number): Promise<void> {
|
|
150
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
151
|
+
}
|
|
152
|
+
}
|