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
|
@@ -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
|
const log = new Logger('ResponseValidator');
|
|
5
6
|
function isRecord(value) {
|
|
6
7
|
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
@@ -19,71 +20,106 @@ function buildSummaryText(toolName, payload) {
|
|
|
19
20
|
if (!isRecord(payload)) {
|
|
20
21
|
return `${toolName} responded`;
|
|
21
22
|
}
|
|
23
|
+
const effectivePayload = { ...payload };
|
|
24
|
+
if (isRecord(effectivePayload.data)) {
|
|
25
|
+
Object.assign(effectivePayload, effectivePayload.data);
|
|
26
|
+
}
|
|
27
|
+
if (isRecord(effectivePayload.result)) {
|
|
28
|
+
Object.assign(effectivePayload, effectivePayload.result);
|
|
29
|
+
}
|
|
22
30
|
const parts = [];
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
parts.push(`
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
31
|
+
const listKeys = ['actors', 'levels', 'assets', 'folders', 'blueprints', 'components', 'pawnClasses', 'foliageTypes', 'nodes', 'tracks', 'bindings', 'keys'];
|
|
32
|
+
for (const key of listKeys) {
|
|
33
|
+
if (Array.isArray(effectivePayload[key])) {
|
|
34
|
+
const arr = effectivePayload[key];
|
|
35
|
+
const names = arr.map(i => isRecord(i) ? (i.name || i.path || i.id || i.assetName || i.objectPath || i.packageName || i.nodeName || '<?>') : String(i));
|
|
36
|
+
const count = arr.length;
|
|
37
|
+
const preview = names.slice(0, 100).join(', ');
|
|
38
|
+
const suffix = count > 100 ? `, ... (+${count - 100} more)` : '';
|
|
39
|
+
parts.push(`${key}: ${preview}${suffix} (Total: ${count})`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (typeof effectivePayload.actor === 'string' || isRecord(effectivePayload.actor)) {
|
|
43
|
+
const a = effectivePayload.actor;
|
|
44
|
+
const name = isRecord(a) ? (a.name || a.path) : a;
|
|
45
|
+
const loc = isRecord(effectivePayload.location) ? ` at [${effectivePayload.location.x},${effectivePayload.location.y},${effectivePayload.location.z}]` : '';
|
|
46
|
+
parts.push(`Actor: ${name}${loc}`);
|
|
47
|
+
}
|
|
48
|
+
if (typeof effectivePayload.asset === 'string' || isRecord(effectivePayload.asset)) {
|
|
49
|
+
const a = effectivePayload.asset;
|
|
50
|
+
const path = isRecord(a) ? (a.path || a.name) : a;
|
|
51
|
+
parts.push(`Asset: ${path}`);
|
|
52
|
+
}
|
|
53
|
+
if (typeof effectivePayload.blueprint === 'string' || isRecord(effectivePayload.blueprint)) {
|
|
54
|
+
const bp = effectivePayload.blueprint;
|
|
55
|
+
const name = isRecord(bp) ? (bp.name || bp.path || effectivePayload.blueprintPath) : bp;
|
|
56
|
+
parts.push(`Blueprint: ${name}`);
|
|
57
|
+
}
|
|
58
|
+
if (typeof effectivePayload.sequence === 'string' || isRecord(effectivePayload.sequence)) {
|
|
59
|
+
const seq = effectivePayload.sequence;
|
|
60
|
+
const name = isRecord(seq) ? (seq.name || seq.path) : seq;
|
|
61
|
+
parts.push(`Sequence: ${name}`);
|
|
62
|
+
}
|
|
63
|
+
const usefulKeys = [
|
|
64
|
+
'success', 'error', 'message', 'assets', 'folders', 'count', 'totalCount',
|
|
65
|
+
'saved', 'valid', 'issues', 'class', 'skeleton', 'parent',
|
|
66
|
+
'package', 'dependencies', 'graph', 'tags', 'metadata', 'properties'
|
|
67
|
+
];
|
|
68
|
+
for (const key of usefulKeys) {
|
|
69
|
+
if (effectivePayload[key] !== undefined && effectivePayload[key] !== null) {
|
|
70
|
+
const val = effectivePayload[key];
|
|
71
|
+
if (typeof val === 'object') {
|
|
72
|
+
if (key === 'metadata' || key === 'properties' || key === 'tags') {
|
|
73
|
+
const entries = Object.entries(val);
|
|
74
|
+
const formatted = entries.map(([k, v]) => `${k}=${v}`);
|
|
75
|
+
const limit = 50;
|
|
76
|
+
parts.push(`${key}: { ${formatted.slice(0, limit).join(', ')}${formatted.length > limit ? '...' : ''} }`);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const strVal = String(val);
|
|
82
|
+
if (strVal.length > 100)
|
|
83
|
+
continue;
|
|
84
|
+
if (!parts.some(p => p.includes(strVal))) {
|
|
85
|
+
parts.push(`${key}: ${strVal}`);
|
|
47
86
|
}
|
|
48
|
-
if (summaryParts.length >= 3)
|
|
49
|
-
break;
|
|
50
87
|
}
|
|
51
|
-
|
|
52
|
-
|
|
88
|
+
}
|
|
89
|
+
const success = typeof payload.success === 'boolean' ? payload.success : undefined;
|
|
90
|
+
const message = typeof payload.message === 'string' ? normalizeText(payload.message) : '';
|
|
91
|
+
const error = typeof payload.error === 'string' ? normalizeText(payload.error) : '';
|
|
92
|
+
if (parts.length > 0) {
|
|
93
|
+
if (message && message.toLowerCase() !== 'success') {
|
|
94
|
+
parts.push(message);
|
|
53
95
|
}
|
|
54
96
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
97
|
+
else {
|
|
98
|
+
if (message)
|
|
99
|
+
parts.push(message);
|
|
100
|
+
if (error)
|
|
101
|
+
parts.push(`Error: ${error}`);
|
|
102
|
+
if (parts.length === 0 && success !== undefined) {
|
|
103
|
+
parts.push(success ? 'Success' : 'Failed');
|
|
59
104
|
}
|
|
60
105
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
:
|
|
106
|
+
const warnings = Array.isArray(payload.warnings) ? payload.warnings : [];
|
|
107
|
+
if (warnings.length > 0) {
|
|
108
|
+
parts.push(`Warnings: ${warnings.length}`);
|
|
109
|
+
}
|
|
110
|
+
return parts.length > 0 ? parts.join(' | ') : `${toolName} responded`;
|
|
64
111
|
}
|
|
65
|
-
/**
|
|
66
|
-
* Response Validator for MCP Tool Outputs
|
|
67
|
-
* Validates tool responses against their defined output schemas
|
|
68
|
-
*/
|
|
69
112
|
export class ResponseValidator {
|
|
70
|
-
// Keep ajv as any to avoid complex interop typing issues with Ajv's ESM/CJS dual export
|
|
71
|
-
// shape when using NodeNext module resolution.
|
|
72
113
|
ajv;
|
|
73
114
|
validators = new Map();
|
|
74
115
|
constructor() {
|
|
75
|
-
// Cast Ajv to any for construction to avoid errors when TypeScript's NodeNext
|
|
76
|
-
// module resolution represents the import as a namespace object.
|
|
77
116
|
const AjvCtor = Ajv?.default ?? Ajv;
|
|
78
117
|
this.ajv = new AjvCtor({
|
|
79
118
|
allErrors: true,
|
|
80
119
|
verbose: true,
|
|
81
|
-
strict:
|
|
120
|
+
strict: true
|
|
82
121
|
});
|
|
83
122
|
}
|
|
84
|
-
/**
|
|
85
|
-
* Register a tool's output schema for validation
|
|
86
|
-
*/
|
|
87
123
|
registerSchema(toolName, outputSchema) {
|
|
88
124
|
if (!outputSchema) {
|
|
89
125
|
log.warn(`No output schema defined for tool: ${toolName}`);
|
|
@@ -92,35 +128,34 @@ export class ResponseValidator {
|
|
|
92
128
|
try {
|
|
93
129
|
const validator = this.ajv.compile(outputSchema);
|
|
94
130
|
this.validators.set(toolName, validator);
|
|
95
|
-
// Demote per-tool schema registration to debug to reduce log noise
|
|
96
131
|
log.debug(`Registered output schema for tool: ${toolName}`);
|
|
97
132
|
}
|
|
98
133
|
catch (_error) {
|
|
99
134
|
log.error(`Failed to compile output schema for ${toolName}:`, _error);
|
|
100
135
|
}
|
|
101
136
|
}
|
|
102
|
-
|
|
103
|
-
* Validate a tool's response against its schema
|
|
104
|
-
*/
|
|
105
|
-
validateResponse(toolName, response) {
|
|
137
|
+
async validateResponse(toolName, response) {
|
|
106
138
|
const validator = this.validators.get(toolName);
|
|
107
139
|
if (!validator) {
|
|
108
140
|
log.debug(`No validator found for tool: ${toolName}`);
|
|
109
|
-
return { valid: true };
|
|
141
|
+
return { valid: true };
|
|
110
142
|
}
|
|
111
|
-
// Extract structured content from response
|
|
112
143
|
let structuredContent = response;
|
|
113
|
-
// If response has MCP format with content array
|
|
114
144
|
if (response.content && Array.isArray(response.content)) {
|
|
115
|
-
// Try to extract structured data from text content
|
|
116
145
|
const textContent = response.content.find((c) => c.type === 'text');
|
|
117
146
|
if (textContent?.text) {
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
147
|
+
const rawText = String(textContent.text);
|
|
148
|
+
const trimmed = rawText.trim();
|
|
149
|
+
const looksLikeJson = trimmed.startsWith('{') || trimmed.startsWith('[');
|
|
150
|
+
if (looksLikeJson) {
|
|
151
|
+
try {
|
|
152
|
+
structuredContent = await wasmIntegration.parseProperties(rawText);
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
structuredContent = response;
|
|
156
|
+
}
|
|
121
157
|
}
|
|
122
|
-
|
|
123
|
-
// Not JSON, use the full response
|
|
158
|
+
else {
|
|
124
159
|
structuredContent = response;
|
|
125
160
|
}
|
|
126
161
|
}
|
|
@@ -140,16 +175,7 @@ export class ResponseValidator {
|
|
|
140
175
|
structuredContent
|
|
141
176
|
};
|
|
142
177
|
}
|
|
143
|
-
|
|
144
|
-
* Wrap a tool response with validation and MCP-compliant content shape.
|
|
145
|
-
*
|
|
146
|
-
* MCP tools/call responses must contain a `content` array. Many internal
|
|
147
|
-
* handlers return structured JSON objects (e.g., { success, message, ... }).
|
|
148
|
-
* This wrapper serializes such objects into a single text block while keeping
|
|
149
|
-
* existing `content` responses intact.
|
|
150
|
-
*/
|
|
151
|
-
wrapResponse(toolName, response) {
|
|
152
|
-
// Ensure response is safe to serialize first
|
|
178
|
+
async wrapResponse(toolName, response) {
|
|
153
179
|
try {
|
|
154
180
|
if (response && typeof response === 'object') {
|
|
155
181
|
JSON.stringify(response);
|
|
@@ -159,16 +185,12 @@ export class ResponseValidator {
|
|
|
159
185
|
log.error(`Response for ${toolName} contains circular references, cleaning...`);
|
|
160
186
|
response = cleanObject(response);
|
|
161
187
|
}
|
|
162
|
-
// If handler already returned MCP content, keep it as-is (still validate)
|
|
163
188
|
const alreadyMcpShaped = response && typeof response === 'object' && Array.isArray(response.content);
|
|
164
|
-
|
|
165
|
-
// structured content extracted from text; otherwise validate the object directly.
|
|
166
|
-
const validation = this.validateResponse(toolName, response);
|
|
189
|
+
const validation = await this.validateResponse(toolName, response);
|
|
167
190
|
const structuredPayload = validation.structuredContent;
|
|
168
191
|
if (!validation.valid) {
|
|
169
192
|
log.warn(`Tool ${toolName} response validation failed:`, validation.errors);
|
|
170
193
|
}
|
|
171
|
-
// If it's already MCP-shaped, return as-is (optionally append validation meta)
|
|
172
194
|
if (alreadyMcpShaped) {
|
|
173
195
|
if (structuredPayload !== undefined && response && typeof response === 'object' && response.structuredContent === undefined) {
|
|
174
196
|
try {
|
|
@@ -178,6 +200,14 @@ export class ResponseValidator {
|
|
|
178
200
|
}
|
|
179
201
|
catch { }
|
|
180
202
|
}
|
|
203
|
+
try {
|
|
204
|
+
const sc = response.structuredContent || structuredPayload || {};
|
|
205
|
+
const hasExplicitFailure = (typeof sc.success === 'boolean' && sc.success === false) || (typeof sc.error === 'string' && sc.error.length > 0);
|
|
206
|
+
if (hasExplicitFailure && response.isError !== true) {
|
|
207
|
+
response.isError = true;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch { }
|
|
181
211
|
if (!validation.valid) {
|
|
182
212
|
try {
|
|
183
213
|
response._validation = { valid: false, errors: validation.errors };
|
|
@@ -186,7 +216,6 @@ export class ResponseValidator {
|
|
|
186
216
|
}
|
|
187
217
|
return response;
|
|
188
218
|
}
|
|
189
|
-
// Otherwise, wrap structured result into MCP content
|
|
190
219
|
const summarySource = structuredPayload !== undefined ? structuredPayload : response;
|
|
191
220
|
let text = buildSummaryText(toolName, summarySource);
|
|
192
221
|
if (!text || !text.trim()) {
|
|
@@ -197,6 +226,15 @@ export class ResponseValidator {
|
|
|
197
226
|
{ type: 'text', text }
|
|
198
227
|
]
|
|
199
228
|
};
|
|
229
|
+
try {
|
|
230
|
+
if (structuredPayload && typeof structuredPayload.success === 'boolean') {
|
|
231
|
+
wrapped.success = Boolean(structuredPayload.success);
|
|
232
|
+
}
|
|
233
|
+
else if (response && typeof response.success === 'boolean') {
|
|
234
|
+
wrapped.success = Boolean(response.success);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
catch { }
|
|
200
238
|
if (structuredPayload !== undefined) {
|
|
201
239
|
try {
|
|
202
240
|
wrapped.structuredContent = structuredPayload && typeof structuredPayload === 'object'
|
|
@@ -215,14 +253,26 @@ export class ResponseValidator {
|
|
|
215
253
|
wrapped.structuredContent = response;
|
|
216
254
|
}
|
|
217
255
|
}
|
|
256
|
+
try {
|
|
257
|
+
const sc = wrapped.structuredContent || {};
|
|
258
|
+
const hasExplicitFailure = (typeof sc.success === 'boolean' && sc.success === false) || (typeof sc.error === 'string' && sc.error.length > 0);
|
|
259
|
+
if (hasExplicitFailure) {
|
|
260
|
+
wrapped.isError = true;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch { }
|
|
218
264
|
if (!validation.valid) {
|
|
219
265
|
wrapped._validation = { valid: false, errors: validation.errors };
|
|
220
266
|
}
|
|
267
|
+
try {
|
|
268
|
+
const s = wrapped.success;
|
|
269
|
+
if (typeof s === 'boolean' && s === false) {
|
|
270
|
+
wrapped.isError = true;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch { }
|
|
221
274
|
return wrapped;
|
|
222
275
|
}
|
|
223
|
-
/**
|
|
224
|
-
* Get validation statistics
|
|
225
|
-
*/
|
|
226
276
|
getStats() {
|
|
227
277
|
return {
|
|
228
278
|
totalSchemas: this.validators.size,
|
|
@@ -230,6 +280,5 @@ export class ResponseValidator {
|
|
|
230
280
|
};
|
|
231
281
|
}
|
|
232
282
|
}
|
|
233
|
-
// Singleton instance
|
|
234
283
|
export const responseValidator = new ResponseValidator();
|
|
235
284
|
//# sourceMappingURL=response-validator.js.map
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { StandardResultPayload } from './python-output.js';
|
|
2
1
|
export interface InterpretedStandardResult {
|
|
3
2
|
success: boolean;
|
|
4
3
|
message: string;
|
|
5
4
|
error?: string;
|
|
6
5
|
warnings?: string[];
|
|
7
6
|
details?: string[];
|
|
8
|
-
payload:
|
|
7
|
+
payload: Record<string, unknown>;
|
|
9
8
|
cleanText?: string;
|
|
10
9
|
rawText: string;
|
|
11
10
|
raw: unknown;
|
|
@@ -16,12 +15,12 @@ export declare function interpretStandardResult(response: unknown, defaults: {
|
|
|
16
15
|
}): InterpretedStandardResult;
|
|
17
16
|
export declare function cleanResultText(text: string | undefined, options?: {
|
|
18
17
|
tag?: string;
|
|
19
|
-
|
|
18
|
+
defaultValue?: string;
|
|
20
19
|
}): string | undefined;
|
|
21
|
-
export declare function bestEffortInterpretedText(interpreted: Pick<InterpretedStandardResult, 'cleanText' | 'rawText'>,
|
|
20
|
+
export declare function bestEffortInterpretedText(interpreted: Pick<InterpretedStandardResult, 'cleanText' | 'rawText'>, defaultValue?: string): string | undefined;
|
|
22
21
|
export declare function coerceString(value: unknown): string | undefined;
|
|
23
22
|
export declare function coerceStringArray(value: unknown): string[] | undefined;
|
|
24
|
-
export declare function coerceBoolean(value: unknown,
|
|
23
|
+
export declare function coerceBoolean(value: unknown, defaultValue?: boolean): boolean | undefined;
|
|
25
24
|
export declare function coerceNumber(value: unknown): number | undefined;
|
|
26
25
|
export declare function coerceVector3(value: unknown): [number, number, number] | undefined;
|
|
27
26
|
//# sourceMappingURL=result-helpers.d.ts.map
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { parseStandardResult, stripTaggedResultLines } from './python-output.js';
|
|
2
1
|
export function interpretStandardResult(response, defaults) {
|
|
3
|
-
const
|
|
4
|
-
const payload = (parsed.data ?? {});
|
|
2
|
+
const payload = (response && typeof response === 'object' ? response : {});
|
|
5
3
|
const success = payload.success === true;
|
|
6
|
-
const rawText = typeof
|
|
7
|
-
|
|
4
|
+
const rawText = typeof payload.message === 'string' ? payload.message :
|
|
5
|
+
typeof payload.output === 'string' ? payload.output :
|
|
6
|
+
String(payload.result ?? '');
|
|
8
7
|
const messageFromPayload = typeof payload.message === 'string' ? payload.message.trim() : '';
|
|
9
8
|
const errorFromPayload = typeof payload.error === 'string' ? payload.error.trim() : '';
|
|
10
9
|
const message = messageFromPayload || (success ? defaults.successMessage : defaults.failureMessage);
|
|
@@ -16,32 +15,32 @@ export function interpretStandardResult(response, defaults) {
|
|
|
16
15
|
warnings: coerceStringArray(payload.warnings),
|
|
17
16
|
details: coerceStringArray(payload.details),
|
|
18
17
|
payload,
|
|
19
|
-
cleanText:
|
|
18
|
+
cleanText: rawText || undefined,
|
|
20
19
|
rawText,
|
|
21
|
-
raw:
|
|
20
|
+
raw: response
|
|
22
21
|
};
|
|
23
22
|
}
|
|
24
23
|
export function cleanResultText(text, options = {}) {
|
|
25
|
-
const {
|
|
24
|
+
const { defaultValue } = options;
|
|
26
25
|
if (!text) {
|
|
27
|
-
return
|
|
26
|
+
return defaultValue;
|
|
28
27
|
}
|
|
29
|
-
const cleaned =
|
|
28
|
+
const cleaned = text.trim();
|
|
30
29
|
if (cleaned.length > 0) {
|
|
31
30
|
return cleaned;
|
|
32
31
|
}
|
|
33
|
-
return
|
|
32
|
+
return defaultValue;
|
|
34
33
|
}
|
|
35
|
-
export function bestEffortInterpretedText(interpreted,
|
|
34
|
+
export function bestEffortInterpretedText(interpreted, defaultValue) {
|
|
36
35
|
const cleaned = interpreted.cleanText?.trim();
|
|
37
36
|
if (cleaned) {
|
|
38
37
|
return cleaned;
|
|
39
38
|
}
|
|
40
39
|
const raw = interpreted.rawText?.trim?.();
|
|
41
|
-
if (raw
|
|
40
|
+
if (raw) {
|
|
42
41
|
return raw;
|
|
43
42
|
}
|
|
44
|
-
return
|
|
43
|
+
return defaultValue;
|
|
45
44
|
}
|
|
46
45
|
export function coerceString(value) {
|
|
47
46
|
if (typeof value === 'string') {
|
|
@@ -67,7 +66,7 @@ export function coerceStringArray(value) {
|
|
|
67
66
|
}
|
|
68
67
|
return items.length > 0 ? items : undefined;
|
|
69
68
|
}
|
|
70
|
-
export function coerceBoolean(value,
|
|
69
|
+
export function coerceBoolean(value, defaultValue) {
|
|
71
70
|
if (typeof value === 'boolean') {
|
|
72
71
|
return value;
|
|
73
72
|
}
|
|
@@ -88,7 +87,7 @@ export function coerceBoolean(value, fallback) {
|
|
|
88
87
|
return false;
|
|
89
88
|
}
|
|
90
89
|
}
|
|
91
|
-
return
|
|
90
|
+
return defaultValue;
|
|
92
91
|
}
|
|
93
92
|
export function coerceNumber(value) {
|
|
94
93
|
if (typeof value === 'number' && Number.isFinite(value)) {
|
package/dist/utils/safe-json.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import { Logger } from './logger.js';
|
|
2
2
|
export function cleanObject(obj, maxDepth = 10) {
|
|
3
3
|
const seen = new WeakSet();
|
|
4
|
+
const logger = new Logger('safe-json');
|
|
4
5
|
function clean(value, depth, path = 'root') {
|
|
5
|
-
// Prevent infinite recursion
|
|
6
6
|
if (depth > maxDepth) {
|
|
7
7
|
return '[Max depth reached]';
|
|
8
8
|
}
|
|
9
|
-
// Handle primitives
|
|
10
9
|
if (value === null || value === undefined) {
|
|
11
10
|
return value;
|
|
12
11
|
}
|
|
@@ -19,20 +18,16 @@ export function cleanObject(obj, maxDepth = 10) {
|
|
|
19
18
|
}
|
|
20
19
|
return value;
|
|
21
20
|
}
|
|
22
|
-
// Check for circular reference
|
|
23
21
|
if (seen.has(value)) {
|
|
24
22
|
return '[Circular Reference]';
|
|
25
23
|
}
|
|
26
24
|
seen.add(value);
|
|
27
|
-
// Handle arrays
|
|
28
25
|
if (Array.isArray(value)) {
|
|
29
26
|
const result = value.map((item, index) => clean(item, depth + 1, `${path}[${index}]`));
|
|
30
|
-
seen.delete(value);
|
|
27
|
+
seen.delete(value);
|
|
31
28
|
return result;
|
|
32
29
|
}
|
|
33
|
-
// Handle objects
|
|
34
30
|
const cleaned = {};
|
|
35
|
-
// Use Object.keys to avoid prototype properties
|
|
36
31
|
const keys = Object.keys(value);
|
|
37
32
|
for (const key of keys) {
|
|
38
33
|
try {
|
|
@@ -42,11 +37,10 @@ export function cleanObject(obj, maxDepth = 10) {
|
|
|
42
37
|
}
|
|
43
38
|
}
|
|
44
39
|
catch (e) {
|
|
45
|
-
|
|
46
|
-
console.error(`Error cleaning property ${path}.${key}:`, e);
|
|
40
|
+
logger.error(`Error cleaning property ${path}.${key}`, e);
|
|
47
41
|
}
|
|
48
42
|
}
|
|
49
|
-
seen.delete(value);
|
|
43
|
+
seen.delete(value);
|
|
50
44
|
return cleaned;
|
|
51
45
|
}
|
|
52
46
|
return clean(obj, 0);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface CommandQueueItem<T = any> {
|
|
2
|
+
command: () => Promise<T>;
|
|
3
|
+
resolve: (value: T) => void;
|
|
4
|
+
reject: (reason?: any) => void;
|
|
5
|
+
priority: number;
|
|
6
|
+
retryCount?: number;
|
|
7
|
+
}
|
|
8
|
+
export declare class UnrealCommandQueue {
|
|
9
|
+
private log;
|
|
10
|
+
private queue;
|
|
11
|
+
private isProcessing;
|
|
12
|
+
private lastCommandTime;
|
|
13
|
+
private lastStatCommandTime;
|
|
14
|
+
private readonly MIN_COMMAND_DELAY;
|
|
15
|
+
private readonly MAX_COMMAND_DELAY;
|
|
16
|
+
private readonly STAT_COMMAND_DELAY;
|
|
17
|
+
constructor();
|
|
18
|
+
execute<T>(command: () => Promise<T>, priority?: number): Promise<T>;
|
|
19
|
+
private processQueue;
|
|
20
|
+
private calculateDelay;
|
|
21
|
+
private startProcessor;
|
|
22
|
+
private delay;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=unreal-command-queue.d.ts.map
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Logger } from './logger.js';
|
|
2
|
+
export class UnrealCommandQueue {
|
|
3
|
+
log = new Logger('UnrealCommandQueue');
|
|
4
|
+
queue = [];
|
|
5
|
+
isProcessing = false;
|
|
6
|
+
lastCommandTime = 0;
|
|
7
|
+
lastStatCommandTime = 0;
|
|
8
|
+
MIN_COMMAND_DELAY = 100;
|
|
9
|
+
MAX_COMMAND_DELAY = 500;
|
|
10
|
+
STAT_COMMAND_DELAY = 300;
|
|
11
|
+
constructor() {
|
|
12
|
+
this.startProcessor();
|
|
13
|
+
}
|
|
14
|
+
async execute(command, priority = 5) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
this.queue.push({
|
|
17
|
+
command,
|
|
18
|
+
resolve,
|
|
19
|
+
reject,
|
|
20
|
+
priority
|
|
21
|
+
});
|
|
22
|
+
this.queue.sort((a, b) => a.priority - b.priority);
|
|
23
|
+
if (!this.isProcessing) {
|
|
24
|
+
this.processQueue();
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
async processQueue() {
|
|
29
|
+
if (this.isProcessing || this.queue.length === 0) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
this.isProcessing = true;
|
|
33
|
+
while (this.queue.length > 0) {
|
|
34
|
+
const item = this.queue.shift();
|
|
35
|
+
if (!item)
|
|
36
|
+
continue;
|
|
37
|
+
const timeSinceLastCommand = Date.now() - this.lastCommandTime;
|
|
38
|
+
const requiredDelay = this.calculateDelay(item.priority);
|
|
39
|
+
if (timeSinceLastCommand < requiredDelay) {
|
|
40
|
+
await this.delay(requiredDelay - timeSinceLastCommand);
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const result = await item.command();
|
|
44
|
+
item.resolve(result);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
const msgRaw = error?.message ?? String(error);
|
|
48
|
+
const msg = String(msgRaw).toLowerCase();
|
|
49
|
+
if (item.retryCount === undefined)
|
|
50
|
+
item.retryCount = 0;
|
|
51
|
+
const isTransient = (msg.includes('timeout') ||
|
|
52
|
+
msg.includes('timed out') ||
|
|
53
|
+
msg.includes('connect') ||
|
|
54
|
+
msg.includes('econnrefused') ||
|
|
55
|
+
msg.includes('econnreset') ||
|
|
56
|
+
msg.includes('broken pipe') ||
|
|
57
|
+
msg.includes('automation bridge') ||
|
|
58
|
+
msg.includes('not connected'));
|
|
59
|
+
const isDeterministicFailure = (msg.includes('command not executed') ||
|
|
60
|
+
msg.includes('exec_failed') ||
|
|
61
|
+
msg.includes('invalid command') ||
|
|
62
|
+
msg.includes('invalid argument') ||
|
|
63
|
+
msg.includes('unknown_plugin_action') ||
|
|
64
|
+
msg.includes('unknown action'));
|
|
65
|
+
if (isTransient && item.retryCount < 3) {
|
|
66
|
+
item.retryCount++;
|
|
67
|
+
this.log.warn(`Command failed (transient), retrying (${item.retryCount}/3)`);
|
|
68
|
+
this.queue.unshift({
|
|
69
|
+
command: item.command,
|
|
70
|
+
resolve: item.resolve,
|
|
71
|
+
reject: item.reject,
|
|
72
|
+
priority: Math.max(1, item.priority - 1),
|
|
73
|
+
retryCount: item.retryCount
|
|
74
|
+
});
|
|
75
|
+
await this.delay(500);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
if (isDeterministicFailure) {
|
|
79
|
+
this.log.warn(`Command failed (non-retryable): ${msgRaw}`);
|
|
80
|
+
}
|
|
81
|
+
item.reject(error);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
this.lastCommandTime = Date.now();
|
|
85
|
+
}
|
|
86
|
+
this.isProcessing = false;
|
|
87
|
+
}
|
|
88
|
+
calculateDelay(priority) {
|
|
89
|
+
if (priority <= 3) {
|
|
90
|
+
return this.MAX_COMMAND_DELAY;
|
|
91
|
+
}
|
|
92
|
+
else if (priority <= 6) {
|
|
93
|
+
return 200;
|
|
94
|
+
}
|
|
95
|
+
else if (priority === 8) {
|
|
96
|
+
const timeSinceLastStat = Date.now() - this.lastStatCommandTime;
|
|
97
|
+
if (timeSinceLastStat < this.STAT_COMMAND_DELAY) {
|
|
98
|
+
return this.STAT_COMMAND_DELAY;
|
|
99
|
+
}
|
|
100
|
+
this.lastStatCommandTime = Date.now();
|
|
101
|
+
return 150;
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
const baseDelay = this.MIN_COMMAND_DELAY;
|
|
105
|
+
const jitter = Math.random() * 50;
|
|
106
|
+
return baseDelay + jitter;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
startProcessor() {
|
|
110
|
+
setInterval(() => {
|
|
111
|
+
if (!this.isProcessing && this.queue.length > 0) {
|
|
112
|
+
this.processQueue();
|
|
113
|
+
}
|
|
114
|
+
}, 1000);
|
|
115
|
+
}
|
|
116
|
+
delay(ms) {
|
|
117
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=unreal-command-queue.js.map
|