ue-mcp 1.3.0 → 1.3.1-beta.2
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/README.md +5 -3
- package/dist/action-class.js +141 -3
- package/dist/action-class.js.map +1 -1
- package/dist/action-schema.d.ts +157 -0
- package/dist/action-schema.js +731 -0
- package/dist/action-schema.js.map +1 -0
- package/dist/bridge-timeouts.js +23 -0
- package/dist/bridge-timeouts.js.map +1 -1
- package/dist/bridge.js +28 -11
- package/dist/bridge.js.map +1 -1
- package/dist/call-pipeline.d.ts +126 -0
- package/dist/call-pipeline.js +144 -0
- package/dist/call-pipeline.js.map +1 -0
- package/dist/content-index.d.ts +41 -0
- package/dist/content-index.js +149 -0
- package/dist/content-index.js.map +1 -0
- package/dist/cpp-correctness.d.ts +165 -0
- package/dist/cpp-correctness.js +562 -0
- package/dist/cpp-correctness.js.map +1 -0
- package/dist/dialog-cli.d.ts +15 -0
- package/dist/dialog-cli.js +146 -0
- package/dist/dialog-cli.js.map +1 -0
- package/dist/editor-control.d.ts +268 -18
- package/dist/editor-control.js +789 -54
- package/dist/editor-control.js.map +1 -1
- package/dist/editor-target.d.ts +31 -0
- package/dist/editor-target.js +34 -0
- package/dist/editor-target.js.map +1 -1
- package/dist/engine-analysis.d.ts +286 -0
- package/dist/engine-analysis.js +874 -0
- package/dist/engine-analysis.js.map +1 -0
- package/dist/engine-index.d.ts +159 -0
- package/dist/engine-index.js +496 -0
- package/dist/engine-index.js.map +1 -0
- package/dist/epic-enrich.js +2 -2
- package/dist/epic-enrich.js.map +1 -1
- package/dist/field-select.d.ts +57 -0
- package/dist/field-select.js +204 -0
- package/dist/field-select.js.map +1 -0
- package/dist/flow/bridge-task.d.ts +5 -0
- package/dist/flow/bridge-task.js +11 -1
- package/dist/flow/bridge-task.js.map +1 -1
- package/dist/flow/flow-tool.js +207 -34
- package/dist/flow/flow-tool.js.map +1 -1
- package/dist/flow/guarded-bridge.js +23 -7
- package/dist/flow/guarded-bridge.js.map +1 -1
- package/dist/flow/handler-outcome.d.ts +97 -0
- package/dist/flow/handler-outcome.js +127 -0
- package/dist/flow/handler-outcome.js.map +1 -0
- package/dist/flow/journal-actions.d.ts +24 -0
- package/dist/flow/journal-actions.js +364 -0
- package/dist/flow/journal-actions.js.map +1 -0
- package/dist/flow/registry.js +18 -2
- package/dist/flow/registry.js.map +1 -1
- package/dist/flow/schema.d.ts +30 -30
- package/dist/flow/skill-actions.d.ts +14 -0
- package/dist/flow/skill-actions.js +269 -0
- package/dist/flow/skill-actions.js.map +1 -0
- package/dist/flow/task-factory.d.ts +9 -3
- package/dist/flow/task-factory.js +55 -11
- package/dist/flow/task-factory.js.map +1 -1
- package/dist/index.js +169 -13
- package/dist/index.js.map +1 -1
- package/dist/init.js +14 -0
- package/dist/init.js.map +1 -1
- package/dist/install-check.d.ts +101 -0
- package/dist/install-check.js +366 -0
- package/dist/install-check.js.map +1 -0
- package/dist/journal.d.ts +212 -0
- package/dist/journal.js +406 -0
- package/dist/journal.js.map +1 -0
- package/dist/lean-context.js +22 -5
- package/dist/lean-context.js.map +1 -1
- package/dist/offline.d.ts +162 -0
- package/dist/offline.js +303 -0
- package/dist/offline.js.map +1 -0
- package/dist/pagination.d.ts +99 -0
- package/dist/pagination.js +175 -0
- package/dist/pagination.js.map +1 -0
- package/dist/path-params.d.ts +61 -0
- package/dist/path-params.js +120 -0
- package/dist/path-params.js.map +1 -0
- package/dist/plugin/injection.js +2 -2
- package/dist/plugin/injection.js.map +1 -1
- package/dist/plugin/manifest.d.ts +15 -15
- package/dist/plugin/provision.js +2 -2
- package/dist/plugin/provision.js.map +1 -1
- package/dist/project.d.ts +35 -0
- package/dist/project.js +72 -8
- package/dist/project.js.map +1 -1
- package/dist/session-env.js +12 -0
- package/dist/session-env.js.map +1 -1
- package/dist/session-surface.js +2 -19
- package/dist/session-surface.js.map +1 -1
- package/dist/session.d.ts +29 -0
- package/dist/session.js +54 -1
- package/dist/session.js.map +1 -1
- package/dist/skill-packs.d.ts +141 -0
- package/dist/skill-packs.js +478 -0
- package/dist/skill-packs.js.map +1 -0
- package/dist/skills.d.ts +14 -11
- package/dist/skills.js +38 -65
- package/dist/skills.js.map +1 -1
- package/dist/tool-counts.json +20 -20
- package/dist/tools/animation.js +81 -17
- package/dist/tools/animation.js.map +1 -1
- package/dist/tools/asset.js +237 -39
- package/dist/tools/asset.js.map +1 -1
- package/dist/tools/audio.js +43 -8
- package/dist/tools/audio.js.map +1 -1
- package/dist/tools/blueprint.js +76 -10
- package/dist/tools/blueprint.js.map +1 -1
- package/dist/tools/demo.js +3 -3
- package/dist/tools/demo.js.map +1 -1
- package/dist/tools/editor.js +159 -42
- package/dist/tools/editor.js.map +1 -1
- package/dist/tools/fab.js +7 -7
- package/dist/tools/fab.js.map +1 -1
- package/dist/tools/feedback.js +14 -4
- package/dist/tools/feedback.js.map +1 -1
- package/dist/tools/foliage.js +50 -4
- package/dist/tools/foliage.js.map +1 -1
- package/dist/tools/gameplay.js +137 -51
- package/dist/tools/gameplay.js.map +1 -1
- package/dist/tools/gas.js +74 -9
- package/dist/tools/gas.js.map +1 -1
- package/dist/tools/landscape.js +158 -10
- package/dist/tools/landscape.js.map +1 -1
- package/dist/tools/level.js +37 -18
- package/dist/tools/level.js.map +1 -1
- package/dist/tools/material.js +43 -8
- package/dist/tools/material.js.map +1 -1
- package/dist/tools/networking.js +10 -10
- package/dist/tools/networking.js.map +1 -1
- package/dist/tools/niagara.js +44 -8
- package/dist/tools/niagara.js.map +1 -1
- package/dist/tools/pcg.js +25 -8
- package/dist/tools/pcg.js.map +1 -1
- package/dist/tools/plugins.js +1 -1
- package/dist/tools/plugins.js.map +1 -1
- package/dist/tools/project.d.ts +17 -1
- package/dist/tools/project.js +571 -17
- package/dist/tools/project.js.map +1 -1
- package/dist/tools/reflection.js +15 -5
- package/dist/tools/reflection.js.map +1 -1
- package/dist/tools/statetree.js +44 -3
- package/dist/tools/statetree.js.map +1 -1
- package/dist/tools/widget.js +70 -4
- package/dist/tools/widget.js.map +1 -1
- package/dist/types.d.ts +75 -10
- package/dist/types.js +95 -20
- package/dist/types.js.map +1 -1
- package/dist/ue-mcp.default.yml +1571 -201
- package/dist/user-state.d.ts +42 -0
- package/dist/user-state.js +366 -38
- package/dist/user-state.js.map +1 -1
- package/dist/version-check.js +46 -5
- package/dist/version-check.js.map +1 -1
- package/package.json +4 -4
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.cpp +130 -24
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/BridgeServer.h +44 -8
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/EngineStatusHooks.cpp +22 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.cpp +135 -66
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/GameThreadExecutor.h +9 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerJsonProperty.h +117 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerPoseSearchSchema.h +92 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/HandlerStateTreeSchema.h +224 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.cpp +467 -13
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers.h +36 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRig.cpp +4 -5
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_ControlRigSequencer.cpp +132 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Depth.cpp +1432 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRetargeterAuthoring.cpp +41 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_IKRigAuthoring.cpp +45 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_MotionMatching.cpp +186 -15
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Sequence.cpp +319 -4
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_SkeletalLive.cpp +45 -4
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Skeleton.cpp +2866 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_StateMachine.cpp +547 -10
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AnimationHandlers_Validation.cpp +28 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.cpp +551 -57
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers.h +40 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_GeometryScript.cpp +2697 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Hygiene.cpp +1700 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Import.cpp +494 -57
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Mesh.cpp +121 -3
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Redirectors.cpp +11 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Sockets.cpp +55 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_Subobject.cpp +12 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AssetHandlers_UV.cpp +2972 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers.cpp +55 -22
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers.h +32 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers_Depth.cpp +1559 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers_MetaSound.cpp +562 -67
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers_MetaSoundRead.cpp +1415 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers_Mixing.cpp +210 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/AudioHandlers_SoundCue.cpp +61 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.cpp +506 -25
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers.h +43 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Depth.cpp +2579 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Functions.cpp +162 -13
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Graph.cpp +694 -25
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Internal.h +96 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Properties.cpp +124 -11
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_Search.cpp +73 -24
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/BlueprintHandlers_UserTypes.cpp +1344 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ChooserHandlers.cpp +136 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ChooserHandlers_Nested.cpp +14 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DemoHandlers.cpp +120 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DialogHandlers.cpp +588 -133
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/DialogHandlers.h +65 -5
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.cpp +679 -26
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers.h +50 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_Build.cpp +70 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIE.cpp +261 -4
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_PIERuntime.cpp +270 -23
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_Profiling.cpp +1665 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_RuntimeVisibility.cpp +41 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EditorHandlers_ViewportControl.cpp +1229 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/EpicHandlers.cpp +22 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FabHandlers.cpp +103 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.cpp +120 -12
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers.h +14 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers_Batch.cpp +87 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/FoliageHandlers_Depth.cpp +1482 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.cpp +954 -168
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers.h +63 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BTAuthoring.cpp +12 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_BTRuntime.cpp +1556 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_EQS.cpp +760 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_Input.cpp +1297 -4
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_Mass.cpp +1303 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GameplayHandlers_Perception.cpp +1493 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.cpp +164 -3
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers.h +77 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers_Abilities.cpp +1392 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers_Runtime.cpp +882 -3
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/GasHandlers_Snapshot.cpp +1002 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.cpp +113 -3
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers.h +31 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LandscapeHandlers_Sculpt.cpp +3778 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers.cpp +790 -44
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_BatchWrite.cpp +44 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Convert.cpp +6 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_InstanceProjection.cpp +37 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Lights.cpp +64 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_MultiLevel.cpp +13 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_PostProcess.cpp +156 -2
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Query.cpp +1 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Refresh.cpp +18 -3
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Save.cpp +9 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Transient.cpp +79 -11
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_Volumes.cpp +174 -5
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartition.cpp +77 -14
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/LevelHandlers_WorldPartitionSettings.cpp +79 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.cpp +591 -32
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers.h +16 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Advanced.cpp +168 -8
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Build.cpp +30 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Function.cpp +106 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_Graph.cpp +156 -23
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/MaterialHandlers_RVT.cpp +1510 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NetworkingHandlers.cpp +232 -13
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.cpp +471 -45
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers.h +23 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers_Advanced.cpp +1936 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/NiagaraHandlers_Compile.cpp +300 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PCGHandlers.cpp +520 -5
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/PhysicsHandlers.cpp +369 -13
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ProjectHandlers.cpp +44 -5
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ProjectHandlers.h +13 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ProjectHandlers_Plugins.cpp +482 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.cpp +346 -31
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers.h +5 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/ReflectionHandlers_Schema.cpp +842 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SequencerHandlers.cpp +270 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/SkeletalMeshHandlers.cpp +65 -3
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/StateTreeHandlers.cpp +912 -23
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/StateTreeHandlers.h +22 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/StateTreeHandlers_Depth.cpp +1782 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.cpp +579 -185
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers.h +37 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Animation.cpp +2691 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_CommonUI.cpp +612 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Extraction.cpp +14 -11
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_Properties.cpp +253 -7
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Handlers/WidgetHandlers_RuntimeState.cpp +1 -1
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/Tests/DataTableRowWriteTests.cpp +352 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Private/UE_MCP_Bridge.cpp +48 -25
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerPagination.h +404 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/HandlerUtils.h +593 -18
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/Public/JsonSerializer.cpp +19 -0
- package/plugin/ue_mcp_bridge/Source/UE_MCP_Bridge/UE_MCP_Bridge.Build.cs +31 -0
- package/plugin/ue_mcp_bridge/UE_MCP_Bridge.uplugin +2 -2
- package/skills/ue-mcp-animation/ue-mcp.yml +14 -0
- package/skills/ue-mcp-blueprint/ue-mcp.yml +8 -0
- package/skills/ue-mcp-epic-routing/ue-mcp.yml +6 -0
- package/skills/ue-mcp-native-cpp/ue-mcp.yml +7 -0
- package/skills/ue-mcp-niagara/ue-mcp.yml +9 -0
- package/skills/ue-mcp-workflow/SKILL.md +3 -2
- package/skills/ue-mcp-workflow/ue-mcp.yml +3 -0
|
@@ -0,0 +1,731 @@
|
|
|
1
|
+
import { classifyActionClass } from "./action-class.js";
|
|
2
|
+
function defOf(schema) {
|
|
3
|
+
return schema._def ?? {};
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Peel the wrappers that only change optionality, keeping the first
|
|
7
|
+
* description and default found on the way in. The description belongs to
|
|
8
|
+
* whichever wrapper `.describe()` was called on, which for the prevailing
|
|
9
|
+
* `z.string().optional().describe(...)` spelling is the outer one.
|
|
10
|
+
*/
|
|
11
|
+
function unwrap(schema) {
|
|
12
|
+
let cur = schema;
|
|
13
|
+
let optional = false;
|
|
14
|
+
let description = cur.description;
|
|
15
|
+
let dflt;
|
|
16
|
+
// Bounded: the wrapper chains in this codebase are two or three deep, and a
|
|
17
|
+
// cycle would otherwise hang the server rather than fail a call.
|
|
18
|
+
for (let i = 0; i < 16; i++) {
|
|
19
|
+
const def = defOf(cur);
|
|
20
|
+
const name = def.typeName;
|
|
21
|
+
if (name === "ZodOptional" || name === "ZodNullable") {
|
|
22
|
+
optional = true;
|
|
23
|
+
}
|
|
24
|
+
else if (name === "ZodDefault") {
|
|
25
|
+
optional = true;
|
|
26
|
+
if (dflt === undefined && typeof def.defaultValue === "function")
|
|
27
|
+
dflt = def.defaultValue();
|
|
28
|
+
}
|
|
29
|
+
else if (name !== "ZodEffects" && name !== "ZodBranded" && name !== "ZodReadonly") {
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
const next = def.innerType ?? def.schema;
|
|
33
|
+
if (!next)
|
|
34
|
+
break;
|
|
35
|
+
cur = next;
|
|
36
|
+
description = description ?? cur.description;
|
|
37
|
+
}
|
|
38
|
+
return { inner: cur, optional, description, default: dflt };
|
|
39
|
+
}
|
|
40
|
+
/** A short, agent-readable name for a zod type. */
|
|
41
|
+
function typeName(schema) {
|
|
42
|
+
const def = defOf(schema);
|
|
43
|
+
switch (def.typeName) {
|
|
44
|
+
case "ZodString": return "string";
|
|
45
|
+
case "ZodNumber": return "number";
|
|
46
|
+
case "ZodBoolean": return "boolean";
|
|
47
|
+
case "ZodUnknown":
|
|
48
|
+
case "ZodAny": return "any";
|
|
49
|
+
case "ZodEnum": return "enum";
|
|
50
|
+
case "ZodLiteral": return "literal";
|
|
51
|
+
case "ZodRecord": return "object";
|
|
52
|
+
case "ZodObject": return "object";
|
|
53
|
+
case "ZodArray": {
|
|
54
|
+
const el = def.type;
|
|
55
|
+
return el ? `${typeName(unwrap(el).inner)}[]` : "array";
|
|
56
|
+
}
|
|
57
|
+
case "ZodUnion": {
|
|
58
|
+
const opts = def.options ?? [];
|
|
59
|
+
const names = [...new Set(opts.map((o) => typeName(unwrap(o).inner)))];
|
|
60
|
+
return names.join("|") || "union";
|
|
61
|
+
}
|
|
62
|
+
default: return def.typeName ? def.typeName.replace(/^Zod/, "").toLowerCase() : "unknown";
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/** Allowed values for an enum, or a union made entirely of string literals. */
|
|
66
|
+
function enumValues(schema) {
|
|
67
|
+
const def = defOf(schema);
|
|
68
|
+
if (def.typeName === "ZodEnum" && Array.isArray(def.values)) {
|
|
69
|
+
return def.values.map((v) => String(v));
|
|
70
|
+
}
|
|
71
|
+
if (def.typeName === "ZodUnion" && Array.isArray(def.options)) {
|
|
72
|
+
const literals = [];
|
|
73
|
+
for (const opt of def.options) {
|
|
74
|
+
const od = defOf(unwrap(opt).inner);
|
|
75
|
+
if (od.typeName !== "ZodLiteral")
|
|
76
|
+
return undefined;
|
|
77
|
+
literals.push(String(od.value));
|
|
78
|
+
}
|
|
79
|
+
return literals.length > 0 ? literals : undefined;
|
|
80
|
+
}
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The grammar of a `Params:` clause, as the descriptions in this repo actually
|
|
85
|
+
* write it rather than as one might wish they did.
|
|
86
|
+
*
|
|
87
|
+
* The clause is a list of ITEMS separated by `,` or `;` at bracket depth zero.
|
|
88
|
+
* Each item names one parameter and then, optionally, commentary. Inside an
|
|
89
|
+
* item the names are joined by separators:
|
|
90
|
+
*
|
|
91
|
+
* OR or | either a choice: supply one side, not both
|
|
92
|
+
* and/or a choice where more than one side is also allowed
|
|
93
|
+
* + with plus a conjunction: the names go together in one branch
|
|
94
|
+
* / two spellings, or two keys, of the same idea
|
|
95
|
+
*
|
|
96
|
+
* An item may open with a quantifier (`at least one of`, `exactly one of`),
|
|
97
|
+
* which makes the names behind it a choice, or with a connective (`plus`,
|
|
98
|
+
* `then`, `EITHER`) continuing the previous item.
|
|
99
|
+
*
|
|
100
|
+
* Everything else at depth zero ends the item, because the prose has resumed:
|
|
101
|
+
* a bare word that is not a separator (`renames[] where each entry is ...`),
|
|
102
|
+
* or a character that cannot start a name (`all=true`, `- uses its agent`).
|
|
103
|
+
*
|
|
104
|
+
* The rule that matters most is what is NOT here. There is no list of English
|
|
105
|
+
* words that are refused in parameter position. `value`, `all`, `from`, `to`,
|
|
106
|
+
* `min`, `max` and `name` are real parameters on this surface, and a filter
|
|
107
|
+
* that reads them as prose deletes a required field from the schema an agent
|
|
108
|
+
* is handed. Position decides, not vocabulary: the head of an item, and
|
|
109
|
+
* whatever follows a separator, is a parameter.
|
|
110
|
+
*/
|
|
111
|
+
/** Joins two names into a choice. */
|
|
112
|
+
const CHOICE_WORD = /^(?:or|either)$/i;
|
|
113
|
+
/** Joins two names into one branch of a choice. */
|
|
114
|
+
const JOIN_WORD = /^(?:and|plus|with)$/i;
|
|
115
|
+
/** Opens an item that continues the previous one rather than naming a parameter. */
|
|
116
|
+
const LEAD_WORD = /^(?:or|either|and|plus|with|then|also)$/i;
|
|
117
|
+
/** `at least one of a/b/c`, `exactly one of x/y`, `any one of ...`. */
|
|
118
|
+
const QUANTIFIER = /^\s*(?:at\s+least\s+|exactly\s+|any\s+)?(?:one|two)\s+of\s+/i;
|
|
119
|
+
/**
|
|
120
|
+
* `Params: none` says the action takes nothing of its own. It is a sentinel,
|
|
121
|
+
* not a terminator: `paged()` appends `cursor?, limit?` behind it, and those
|
|
122
|
+
* are real parameters.
|
|
123
|
+
*/
|
|
124
|
+
const NONE_WORD = /^none$/i;
|
|
125
|
+
/**
|
|
126
|
+
* Commentary that shares a bracket with real parameters, for the case where
|
|
127
|
+
* the category's declared keys are not to hand to settle it.
|
|
128
|
+
*/
|
|
129
|
+
const BRACKET_PROSE = /^(?:default|defaults|e|g|i|see|note|optional|required|omit|when|flat|legacy|socket)$/i;
|
|
130
|
+
/** Cut a clause at the first match of `stop` that is not inside brackets. */
|
|
131
|
+
function cutAtDepthZero(text, stop) {
|
|
132
|
+
let depth = 0;
|
|
133
|
+
for (let i = 0; i < text.length; i++) {
|
|
134
|
+
const c = text[i];
|
|
135
|
+
if (c === "(" || c === "[" || c === "{")
|
|
136
|
+
depth++;
|
|
137
|
+
else if (c === ")" || c === "]" || c === "}")
|
|
138
|
+
depth = Math.max(0, depth - 1);
|
|
139
|
+
else if (depth === 0) {
|
|
140
|
+
stop.lastIndex = i;
|
|
141
|
+
const m = stop.exec(text);
|
|
142
|
+
if (m && m.index === i)
|
|
143
|
+
return text.slice(0, i);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return text;
|
|
147
|
+
}
|
|
148
|
+
/** The bracket group opening at `i`, and the index just past its close. */
|
|
149
|
+
function bracketAt(text, i) {
|
|
150
|
+
let depth = 0;
|
|
151
|
+
for (let j = i; j < text.length; j++) {
|
|
152
|
+
const c = text[j];
|
|
153
|
+
if (c === "(" || c === "[" || c === "{")
|
|
154
|
+
depth++;
|
|
155
|
+
else if (c === ")" || c === "]" || c === "}") {
|
|
156
|
+
depth--;
|
|
157
|
+
if (depth === 0)
|
|
158
|
+
return { inner: text.slice(i + 1, j), end: j + 1 };
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return { inner: text.slice(i + 1), end: text.length };
|
|
162
|
+
}
|
|
163
|
+
/** Split on the given separators, ignoring any that sits inside brackets. */
|
|
164
|
+
function splitDepthZero(text, separators) {
|
|
165
|
+
const out = [];
|
|
166
|
+
let depth = 0;
|
|
167
|
+
let start = 0;
|
|
168
|
+
let separator = "";
|
|
169
|
+
for (let i = 0; i < text.length; i++) {
|
|
170
|
+
const c = text[i];
|
|
171
|
+
if (c === "(" || c === "[" || c === "{")
|
|
172
|
+
depth++;
|
|
173
|
+
else if (c === ")" || c === "]" || c === "}")
|
|
174
|
+
depth = Math.max(0, depth - 1);
|
|
175
|
+
else if (depth === 0 && separators.includes(c)) {
|
|
176
|
+
out.push({ text: text.slice(start, i), separator });
|
|
177
|
+
separator = c;
|
|
178
|
+
start = i + 1;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
out.push({ text: text.slice(start), separator });
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
/** The atoms of a `(+ x?, y?)` group: further parameters, plus commentary. */
|
|
185
|
+
function scanPlusGroup(inner, known) {
|
|
186
|
+
const out = [];
|
|
187
|
+
for (const part of splitDepthZero(inner.replace(/^\s*\+/, ""), ",")) {
|
|
188
|
+
const m = /^\s*([A-Za-z_][A-Za-z0-9_]*)(\[\])?\s*(\?)?/.exec(part.text);
|
|
189
|
+
if (!m)
|
|
190
|
+
continue;
|
|
191
|
+
const accepted = known ? known.has(m[1]) : !BRACKET_PROSE.test(m[1]);
|
|
192
|
+
if (accepted)
|
|
193
|
+
out.push({ name: m[1], optional: m[3] === "?" });
|
|
194
|
+
}
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Read one item of the clause.
|
|
199
|
+
*
|
|
200
|
+
* Scans left to right at depth zero, alternating between "a name may appear
|
|
201
|
+
* here" and "a separator may appear here". The first thing that is neither
|
|
202
|
+
* ends the item: the rest of it is prose about the parameter, not more
|
|
203
|
+
* parameters.
|
|
204
|
+
*/
|
|
205
|
+
function scanItem(text, options) {
|
|
206
|
+
const { known, strict } = options;
|
|
207
|
+
const branches = [[]];
|
|
208
|
+
const extra = [];
|
|
209
|
+
let choice = false;
|
|
210
|
+
let quantified = false;
|
|
211
|
+
let expectName = true;
|
|
212
|
+
let atHead = true;
|
|
213
|
+
// A head connective (`plus shortcuts:`) is as often the prose resuming as it
|
|
214
|
+
// is a continuation, so the name behind one has to be a declared key.
|
|
215
|
+
let headGuarded = false;
|
|
216
|
+
let rejected = false;
|
|
217
|
+
// Set once a `+` (or `and`/`plus`/`with`) has joined something into the
|
|
218
|
+
// current branch. After that a `/` is reading inside the branch rather than
|
|
219
|
+
// starting a new one: `systemPath + emitterName?/emitterIndex?` is one way
|
|
220
|
+
// of addressing the emitter, not two ways of addressing the system.
|
|
221
|
+
let conjoined = false;
|
|
222
|
+
const branch = () => branches[branches.length - 1];
|
|
223
|
+
const openBranch = (markChoice) => {
|
|
224
|
+
if (markChoice)
|
|
225
|
+
choice = true;
|
|
226
|
+
if (branch().length > 0)
|
|
227
|
+
branches.push([]);
|
|
228
|
+
conjoined = false;
|
|
229
|
+
};
|
|
230
|
+
const addAtom = (atom) => {
|
|
231
|
+
if (known && (strict || headGuarded) && !known.has(atom.name))
|
|
232
|
+
return false;
|
|
233
|
+
branch().push(atom);
|
|
234
|
+
headGuarded = false;
|
|
235
|
+
return true;
|
|
236
|
+
};
|
|
237
|
+
let i = 0;
|
|
238
|
+
while (i < text.length) {
|
|
239
|
+
const c = text[i];
|
|
240
|
+
if (/\s/.test(c)) {
|
|
241
|
+
i++;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (atHead) {
|
|
245
|
+
const q = QUANTIFIER.exec(text.slice(i));
|
|
246
|
+
if (q) {
|
|
247
|
+
quantified = true;
|
|
248
|
+
choice = true;
|
|
249
|
+
i += q[0].length;
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (c === "(" || c === "[" || c === "{") {
|
|
254
|
+
const { inner, end } = bracketAt(text, i);
|
|
255
|
+
i = end;
|
|
256
|
+
if (expectName) {
|
|
257
|
+
// `EITHER (nodeId + inputName) OR graphInput`: a bracket where a name
|
|
258
|
+
// was due groups names, it does not comment on one.
|
|
259
|
+
const sub = scanItem(inner, { known, strict: true });
|
|
260
|
+
const atoms = [...sub.branches.flat(), ...sub.extra];
|
|
261
|
+
if (atoms.length > 0) {
|
|
262
|
+
for (const atom of atoms)
|
|
263
|
+
branch().push(atom);
|
|
264
|
+
conjoined = atoms.length > 1;
|
|
265
|
+
headGuarded = false;
|
|
266
|
+
expectName = false;
|
|
267
|
+
}
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
const trimmed = inner.trim();
|
|
271
|
+
if (trimmed.startsWith("+")) {
|
|
272
|
+
extra.push(...scanPlusGroup(trimmed, known));
|
|
273
|
+
}
|
|
274
|
+
else if (/^or\b/i.test(trimmed)) {
|
|
275
|
+
// `(or point, or worldX + worldY)` names further ways to say the same
|
|
276
|
+
// thing. It is only ever read as parameters when every name in it is
|
|
277
|
+
// declared, because the same shape carries prose (`(or socket name)`).
|
|
278
|
+
const alt = [];
|
|
279
|
+
let ok = true;
|
|
280
|
+
for (const part of splitDepthZero(trimmed, ",")) {
|
|
281
|
+
const sub = scanItem(part.text, { known, strict: true });
|
|
282
|
+
if (sub.branches.length === 0 && sub.extra.length === 0) {
|
|
283
|
+
ok = false;
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
for (const b of sub.branches)
|
|
287
|
+
alt.push(b);
|
|
288
|
+
if (sub.extra.length > 0)
|
|
289
|
+
alt.push(sub.extra);
|
|
290
|
+
}
|
|
291
|
+
if (ok && alt.length > 0) {
|
|
292
|
+
choice = true;
|
|
293
|
+
for (const b of alt)
|
|
294
|
+
branches.push(b);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
if (c === "|") {
|
|
300
|
+
openBranch(true);
|
|
301
|
+
expectName = true;
|
|
302
|
+
atHead = false;
|
|
303
|
+
i++;
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
if (c === "+") {
|
|
307
|
+
conjoined = true;
|
|
308
|
+
expectName = true;
|
|
309
|
+
atHead = false;
|
|
310
|
+
i++;
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (c === "/") {
|
|
314
|
+
// A slash separates: `target/targetLabel` are two spellings of one
|
|
315
|
+
// parameter and `frames?/times?` are two ways of asking. Whether that
|
|
316
|
+
// separation is a CHOICE is decided by the rest of the item - an item
|
|
317
|
+
// with no `OR` in it is a plain list, and its branches never surface.
|
|
318
|
+
if (!conjoined)
|
|
319
|
+
openBranch(false);
|
|
320
|
+
expectName = true;
|
|
321
|
+
atHead = false;
|
|
322
|
+
i++;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
if (c === ":") {
|
|
326
|
+
i++;
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
const m = /^([A-Za-z_][A-Za-z0-9_]*)(\[\])?\s*(\?)?/.exec(text.slice(i));
|
|
330
|
+
if (!m)
|
|
331
|
+
break;
|
|
332
|
+
const word = m[1];
|
|
333
|
+
if (atHead && NONE_WORD.test(word) && !known?.has(word))
|
|
334
|
+
return { branches: [], extra: [], choice: false, quantified: false };
|
|
335
|
+
if (CHOICE_WORD.test(word) || JOIN_WORD.test(word) || (atHead && LEAD_WORD.test(word))) {
|
|
336
|
+
if (CHOICE_WORD.test(word))
|
|
337
|
+
openBranch(true);
|
|
338
|
+
else
|
|
339
|
+
conjoined = true;
|
|
340
|
+
if (atHead)
|
|
341
|
+
headGuarded = true;
|
|
342
|
+
expectName = true;
|
|
343
|
+
atHead = false;
|
|
344
|
+
i += m[1].length;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
if (!expectName)
|
|
348
|
+
break;
|
|
349
|
+
if (!addAtom({ name: word, optional: m[3] === "?" })) {
|
|
350
|
+
rejected = true;
|
|
351
|
+
break;
|
|
352
|
+
}
|
|
353
|
+
expectName = false;
|
|
354
|
+
atHead = false;
|
|
355
|
+
i += m[0].length;
|
|
356
|
+
}
|
|
357
|
+
if (rejected && branch().length === 0 && branches.length === 1) {
|
|
358
|
+
return { branches: [], extra: [], choice: false, quantified: false };
|
|
359
|
+
}
|
|
360
|
+
const filled = branches.filter((b) => b.length > 0);
|
|
361
|
+
// A branch is reached, or not, through its first name. Everything conjoined
|
|
362
|
+
// behind an optional one is therefore optional too, however the clause spelt
|
|
363
|
+
// it: `axisHorizontal?/axisVertical? + horizontalMin/horizontalMax/...` is a
|
|
364
|
+
// back-compat spelling of an optional axis, not six required numbers.
|
|
365
|
+
for (const b of filled) {
|
|
366
|
+
if (b[0].optional)
|
|
367
|
+
for (const atom of b)
|
|
368
|
+
atom.optional = true;
|
|
369
|
+
}
|
|
370
|
+
// A quantifier is a choice even before a second branch turns up: `at least
|
|
371
|
+
// one of a, b, c` spells its list with commas, so the rest of it arrives as
|
|
372
|
+
// the items that follow.
|
|
373
|
+
const isChoice = (choice && filled.length > 1) || (quantified && filled.length > 0);
|
|
374
|
+
return { branches: filled, extra, choice: isChoice, quantified };
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Pull the parameters out of the `Params:` clause every action carries.
|
|
378
|
+
*
|
|
379
|
+
* `known`, when given, is the category's declared keys. It settles the two
|
|
380
|
+
* places where the clause alone is ambiguous - a name behind a head connective
|
|
381
|
+
* and a name inside a bracket - by asking whether the wire would accept it.
|
|
382
|
+
* It only ever admits a name, never refuses one that stands in plain parameter
|
|
383
|
+
* position, so a name the description invents is still reported and still
|
|
384
|
+
* shows up as drift.
|
|
385
|
+
*/
|
|
386
|
+
export function parseParams(description, known) {
|
|
387
|
+
const at = description.search(/\bParams:/);
|
|
388
|
+
if (at < 0)
|
|
389
|
+
return { params: [], alternatives: [] };
|
|
390
|
+
let clause = description.slice(at + "Params:".length);
|
|
391
|
+
// The clause runs until the prose resumes. Two things end it: a `Returns`
|
|
392
|
+
// section, whose names are result fields rather than parameters, and a
|
|
393
|
+
// sentence break, after which the description is explaining rather than
|
|
394
|
+
// listing. Both are only terminators at depth zero, so `(e.g. 'foot_l')`
|
|
395
|
+
// and `{op:'set'}` stay attached to the parameter they document.
|
|
396
|
+
clause = cutAtDepthZero(clause, /\.\s+|\bReturns\b|\bReturn:/g);
|
|
397
|
+
// A trailing issue reference is not part of the list.
|
|
398
|
+
clause = clause.replace(/\(#[\d/#\s,]+\)\s*$/, "").trim();
|
|
399
|
+
const params = [];
|
|
400
|
+
const byName = new Map();
|
|
401
|
+
const alternatives = [];
|
|
402
|
+
const add = (atom, group) => {
|
|
403
|
+
const existing = byName.get(atom.name);
|
|
404
|
+
if (existing) {
|
|
405
|
+
if (group !== undefined && existing.group === undefined)
|
|
406
|
+
existing.group = group;
|
|
407
|
+
return existing;
|
|
408
|
+
}
|
|
409
|
+
const param = { name: atom.name, optional: atom.optional };
|
|
410
|
+
if (group !== undefined)
|
|
411
|
+
param.group = group;
|
|
412
|
+
byName.set(atom.name, param);
|
|
413
|
+
params.push(param);
|
|
414
|
+
return param;
|
|
415
|
+
};
|
|
416
|
+
const items = splitDepthZero(clause, ",;");
|
|
417
|
+
// A quantifier that ran out of item (`at least one of a, b, c`) keeps
|
|
418
|
+
// collecting the bare names behind it until something else appears.
|
|
419
|
+
let openGroup;
|
|
420
|
+
for (const item of items) {
|
|
421
|
+
const bare = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*$/.exec(item.text);
|
|
422
|
+
if (openGroup && item.separator === "," && bare) {
|
|
423
|
+
alternatives[openGroup.index].branches.push([bare[1]]);
|
|
424
|
+
add({ name: bare[1], optional: false }, openGroup.index);
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
openGroup = undefined;
|
|
428
|
+
const scanned = scanItem(item.text, { known });
|
|
429
|
+
if (scanned.branches.length === 0 && scanned.extra.length === 0)
|
|
430
|
+
continue;
|
|
431
|
+
if (scanned.choice) {
|
|
432
|
+
const index = alternatives.length;
|
|
433
|
+
const required = scanned.quantified
|
|
434
|
+
|| scanned.branches.every((b) => b.some((a) => !a.optional));
|
|
435
|
+
alternatives.push({ branches: scanned.branches.map((b) => b.map((a) => a.name)), required });
|
|
436
|
+
for (const b of scanned.branches)
|
|
437
|
+
for (const atom of b)
|
|
438
|
+
add(atom, index);
|
|
439
|
+
if (scanned.quantified)
|
|
440
|
+
openGroup = { index, scanned };
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
for (const b of scanned.branches)
|
|
444
|
+
for (const atom of b)
|
|
445
|
+
add(atom);
|
|
446
|
+
}
|
|
447
|
+
for (const atom of scanned.extra)
|
|
448
|
+
add(atom);
|
|
449
|
+
}
|
|
450
|
+
return { params, alternatives };
|
|
451
|
+
}
|
|
452
|
+
/** The parameters the `Params:` clause names, without the choice structure. */
|
|
453
|
+
export function parseParamsClause(description, known) {
|
|
454
|
+
return parseParams(description, known).params;
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* The parameter keys an action's `mapParams` closure reads.
|
|
458
|
+
*
|
|
459
|
+
* Reading the compiled source is the only way to see this: `mapParams` is an
|
|
460
|
+
* opaque function by the time the registry is built. It is a best-effort
|
|
461
|
+
* signal - a closure that spreads its argument reads everything and shows
|
|
462
|
+
* nothing here - so it only ever adds names, never removes them.
|
|
463
|
+
*/
|
|
464
|
+
export function forwardedParams(spec) {
|
|
465
|
+
// A bridge action maps its parameters through `mapParams`; a local one
|
|
466
|
+
// reads them out of its handler's second argument. Both are the same
|
|
467
|
+
// question - which keys does this action look at - so both are scanned.
|
|
468
|
+
const fn = spec.mapParams ?? spec.handler;
|
|
469
|
+
if (!fn)
|
|
470
|
+
return [];
|
|
471
|
+
let src;
|
|
472
|
+
try {
|
|
473
|
+
src = fn.toString();
|
|
474
|
+
}
|
|
475
|
+
catch {
|
|
476
|
+
return [];
|
|
477
|
+
}
|
|
478
|
+
const names = new Set();
|
|
479
|
+
// Bind the scan to the closure's own parameter, so `arr.length` and
|
|
480
|
+
// `Array.isArray` are not mistaken for parameters the action reads.
|
|
481
|
+
// For a handler that is `(ctx, p) => ...`, the bag is the second argument.
|
|
482
|
+
const argsMatch = /^\s*(?:async\s*)?\(?\s*([^)=]*?)\s*\)?\s*=>/.exec(src);
|
|
483
|
+
const argNames = (argsMatch?.[1] ?? "")
|
|
484
|
+
.split(",")
|
|
485
|
+
.map((s) => s.trim())
|
|
486
|
+
.filter((s) => /^[A-Za-z_$][\w$]*$/.test(s));
|
|
487
|
+
const bagName = spec.mapParams ? argNames[0] : argNames[1];
|
|
488
|
+
if (bagName) {
|
|
489
|
+
const bag = bagName.replace(/\$/g, "\\$");
|
|
490
|
+
// The prevailing spelling is `(p) => ({ x: p.x, y: p.y ?? p.path })`.
|
|
491
|
+
// Match the accessor rather than the key, so an alias like
|
|
492
|
+
// `p.assetPath ?? p.path` reports both spellings the action accepts.
|
|
493
|
+
for (const m of src.matchAll(new RegExp(`\\b${bag}\\.([A-Za-z_][A-Za-z0-9_]*)\\b`, "g"))) {
|
|
494
|
+
names.add(m[1]);
|
|
495
|
+
}
|
|
496
|
+
for (const m of src.matchAll(new RegExp(`\\b${bag}\\[\\s*["']([A-Za-z_][A-Za-z0-9_]*)["']\\s*\\]`, "g"))) {
|
|
497
|
+
names.add(m[1]);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
// Destructuring in the parameter position: `({ assetPath, save })`, or
|
|
501
|
+
// `(ctx, { assetPath })` for a local handler.
|
|
502
|
+
const destructured = spec.mapParams
|
|
503
|
+
? /^\s*\(?\s*\{([^{}]*)\}/.exec(src)
|
|
504
|
+
: /^\s*(?:async\s*)?\([^,)]*,\s*\{([^{}]*)\}/.exec(src);
|
|
505
|
+
if (destructured) {
|
|
506
|
+
for (const part of destructured[1].split(",")) {
|
|
507
|
+
const key = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*(?::|=|$)/.exec(part);
|
|
508
|
+
if (key)
|
|
509
|
+
names.add(key[1]);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
names.delete("action");
|
|
513
|
+
return [...names];
|
|
514
|
+
}
|
|
515
|
+
/* ── the schema itself ─────────────────────────────────────────────── */
|
|
516
|
+
/** Routing instructions the dispatcher consumes and strips before a handler
|
|
517
|
+
* ever sees them. They are real wire parameters, so they are reported, but
|
|
518
|
+
* they are never counted as drift against an action's own documentation. */
|
|
519
|
+
const ROUTING_PARAMS = new Set(["action", "timeoutMs", "select", "omit", "editor", "toEditor"]);
|
|
520
|
+
/** Build the full schema for one action of one tool. */
|
|
521
|
+
export function actionSchema(tool, action) {
|
|
522
|
+
const spec = tool.actions[action];
|
|
523
|
+
if (!spec) {
|
|
524
|
+
throw new Error(`Unknown action '${action}' on tool '${tool.name}'. Available: ${Object.keys(tool.actions).join(", ")}`);
|
|
525
|
+
}
|
|
526
|
+
const description = spec.description ?? "";
|
|
527
|
+
// The category's declared keys settle the two spots where the clause alone
|
|
528
|
+
// is ambiguous, so the parse is done against them rather than in the dark.
|
|
529
|
+
const declaredNames = new Set(Object.keys(tool.schema));
|
|
530
|
+
const parsed = parseParams(description, declaredNames);
|
|
531
|
+
const documented = parsed.params;
|
|
532
|
+
const forwards = new Set(forwardedParams(spec));
|
|
533
|
+
const documentedByName = new Map(documented.map((d) => [d.name, d]));
|
|
534
|
+
// A choice only survives while more than one of its branches is reachable.
|
|
535
|
+
// `asset.migrate` documents `toEditor OR destinationContentDir`, and with
|
|
536
|
+
// one editor registered `toEditor` is not in the shape at all, which leaves
|
|
537
|
+
// an ordinary required parameter rather than a choice.
|
|
538
|
+
const alternatives = [];
|
|
539
|
+
const groupIndex = new Map();
|
|
540
|
+
parsed.alternatives.forEach((group, i) => {
|
|
541
|
+
const branches = group.branches
|
|
542
|
+
.map((b) => b.filter((n) => declaredNames.has(n)))
|
|
543
|
+
.filter((b) => b.length > 0);
|
|
544
|
+
if (branches.length < 2)
|
|
545
|
+
return;
|
|
546
|
+
groupIndex.set(i, alternatives.length);
|
|
547
|
+
alternatives.push({ branches, required: group.required });
|
|
548
|
+
});
|
|
549
|
+
const params = [];
|
|
550
|
+
const covered = new Set();
|
|
551
|
+
for (const [name, schema] of Object.entries(tool.schema)) {
|
|
552
|
+
if (name === "action")
|
|
553
|
+
continue;
|
|
554
|
+
const { inner, optional, description: paramDoc, default: dflt } = unwrap(schema);
|
|
555
|
+
const doc = documentedByName.get(name);
|
|
556
|
+
const group = doc?.group === undefined ? undefined : groupIndex.get(doc.group);
|
|
557
|
+
const sources = ["declared"];
|
|
558
|
+
if (doc)
|
|
559
|
+
sources.unshift("documented");
|
|
560
|
+
if (forwards.has(name))
|
|
561
|
+
sources.splice(sources.length - 1, 0, "forwards");
|
|
562
|
+
// A category's shape is one flat bag shared by all its actions, so most of
|
|
563
|
+
// its keys belong to some other action. Report the ones this action uses,
|
|
564
|
+
// plus the routing parameters, which every action accepts.
|
|
565
|
+
if (!doc && !forwards.has(name) && !ROUTING_PARAMS.has(name))
|
|
566
|
+
continue;
|
|
567
|
+
covered.add(name);
|
|
568
|
+
params.push({
|
|
569
|
+
name,
|
|
570
|
+
type: typeName(inner),
|
|
571
|
+
// The description's `?` marker wins: it is per-action, whereas the
|
|
572
|
+
// category shape has to declare nearly everything optional to let its
|
|
573
|
+
// other actions through.
|
|
574
|
+
required: doc ? group === undefined && !doc.optional : !optional,
|
|
575
|
+
description: paramDoc,
|
|
576
|
+
enumValues: enumValues(inner),
|
|
577
|
+
default: dflt,
|
|
578
|
+
sources,
|
|
579
|
+
alternativeGroup: group,
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
const drift = [];
|
|
583
|
+
const noteDrift = (name) => {
|
|
584
|
+
// Routing parameters are consumed by the dispatcher, and two of them are
|
|
585
|
+
// injected into the shape only while this server drives more than one
|
|
586
|
+
// editor, so their absence from a single-editor graph is not drift.
|
|
587
|
+
if (ROUTING_PARAMS.has(name))
|
|
588
|
+
return;
|
|
589
|
+
if (covered.has(name) || name in tool.schema || drift.includes(name))
|
|
590
|
+
return;
|
|
591
|
+
drift.push(name);
|
|
592
|
+
};
|
|
593
|
+
for (const { name } of documented)
|
|
594
|
+
noteDrift(name);
|
|
595
|
+
for (const name of forwards)
|
|
596
|
+
noteDrift(name);
|
|
597
|
+
params.sort((a, b) => {
|
|
598
|
+
if (a.required !== b.required)
|
|
599
|
+
return a.required ? -1 : 1;
|
|
600
|
+
return a.name.localeCompare(b.name);
|
|
601
|
+
});
|
|
602
|
+
return {
|
|
603
|
+
tool: tool.name,
|
|
604
|
+
action,
|
|
605
|
+
description,
|
|
606
|
+
bridge: spec.bridge,
|
|
607
|
+
local: !spec.bridge,
|
|
608
|
+
timeoutMs: spec.timeoutMs,
|
|
609
|
+
class: classifyActionClass(tool.name, action).class,
|
|
610
|
+
params,
|
|
611
|
+
alternatives: alternatives.length > 0 ? alternatives : undefined,
|
|
612
|
+
drift,
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Resolve an action reference to the tools that provide it.
|
|
617
|
+
*
|
|
618
|
+
* Accepts `tool.action`, `tool:action`, `tool action`, or a bare action name,
|
|
619
|
+
* which may be provided by more than one category (`list`, `save`, `create`).
|
|
620
|
+
* All matches come back so the caller can disambiguate rather than being
|
|
621
|
+
* handed whichever one sorted first.
|
|
622
|
+
*/
|
|
623
|
+
export function resolveActionRef(ref, tools) {
|
|
624
|
+
const trimmed = (ref ?? "").trim();
|
|
625
|
+
if (!trimmed)
|
|
626
|
+
return [];
|
|
627
|
+
const split = /^([A-Za-z_][A-Za-z0-9_]*)\s*[.: ]\s*([A-Za-z_][A-Za-z0-9_]*)$/.exec(trimmed);
|
|
628
|
+
if (split) {
|
|
629
|
+
const [, toolName, actionName] = split;
|
|
630
|
+
const tool = tools.find((t) => t.name === toolName.toLowerCase());
|
|
631
|
+
if (tool && tool.actions[actionName])
|
|
632
|
+
return [{ tool, action: actionName }];
|
|
633
|
+
// A bare action name containing a dot is not a thing, so fall through only
|
|
634
|
+
// when the qualified form found nothing at all.
|
|
635
|
+
if (tool)
|
|
636
|
+
return [];
|
|
637
|
+
}
|
|
638
|
+
const out = [];
|
|
639
|
+
for (const tool of tools) {
|
|
640
|
+
if (tool.actions[trimmed])
|
|
641
|
+
out.push({ tool, action: trimmed });
|
|
642
|
+
}
|
|
643
|
+
return out;
|
|
644
|
+
}
|
|
645
|
+
/** Close spellings for an action name that did not resolve. */
|
|
646
|
+
export function suggestActions(ref, tools, limit = 8) {
|
|
647
|
+
const needle = (ref ?? "").trim().toLowerCase().replace(/^[a-z_]+[.:]/, "");
|
|
648
|
+
if (!needle)
|
|
649
|
+
return [];
|
|
650
|
+
const scored = [];
|
|
651
|
+
for (const tool of tools) {
|
|
652
|
+
for (const action of Object.keys(tool.actions)) {
|
|
653
|
+
const score = similarity(needle, action.toLowerCase());
|
|
654
|
+
if (score > 0)
|
|
655
|
+
scored.push({ label: `${tool.name}.${action}`, score });
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
return scored
|
|
659
|
+
.sort((a, b) => b.score - a.score || a.label.localeCompare(b.label))
|
|
660
|
+
.slice(0, limit)
|
|
661
|
+
.map((s) => s.label);
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* How close two action names are, on 0..1.
|
|
665
|
+
*
|
|
666
|
+
* Substring containment dominates, because the realistic miss is a caller who
|
|
667
|
+
* remembers part of the name (`bones` for `list_skeleton_bones`) rather than
|
|
668
|
+
* one who transposes two letters. Edit distance catches the typo case below
|
|
669
|
+
* that, and anything under a third of the name matching scores zero so the
|
|
670
|
+
* suggestion list stays short enough to read.
|
|
671
|
+
*/
|
|
672
|
+
export function similarity(a, b) {
|
|
673
|
+
if (a === b)
|
|
674
|
+
return 1;
|
|
675
|
+
if (b.includes(a))
|
|
676
|
+
return 0.9 * (a.length / b.length) + 0.05;
|
|
677
|
+
if (a.includes(b))
|
|
678
|
+
return 0.85 * (b.length / a.length);
|
|
679
|
+
const distance = editDistance(a, b);
|
|
680
|
+
const longest = Math.max(a.length, b.length);
|
|
681
|
+
const closeness = 1 - distance / longest;
|
|
682
|
+
return closeness >= 0.6 ? closeness * 0.8 : 0;
|
|
683
|
+
}
|
|
684
|
+
function editDistance(a, b) {
|
|
685
|
+
// Two rows rather than the full matrix: this runs over every action name on
|
|
686
|
+
// the surface for every miss.
|
|
687
|
+
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
688
|
+
let cur = new Array(b.length + 1);
|
|
689
|
+
for (let i = 1; i <= a.length; i++) {
|
|
690
|
+
cur[0] = i;
|
|
691
|
+
for (let j = 1; j <= b.length; j++) {
|
|
692
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
693
|
+
cur[j] = Math.min(cur[j - 1] + 1, prev[j] + 1, prev[j - 1] + cost);
|
|
694
|
+
}
|
|
695
|
+
[prev, cur] = [cur, prev];
|
|
696
|
+
}
|
|
697
|
+
return prev[b.length];
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* The closest spellings to a missed action name, out of a plain name list.
|
|
701
|
+
*
|
|
702
|
+
* Separate from `suggestActions` because the dispatcher has only its own
|
|
703
|
+
* category's keys at the point it fails, and importing the whole graph there
|
|
704
|
+
* would tie a per-call error path to the session registry.
|
|
705
|
+
*/
|
|
706
|
+
export function nearestActions(ref, available, limit = 5) {
|
|
707
|
+
const needle = (ref ?? "").trim().toLowerCase();
|
|
708
|
+
if (!needle)
|
|
709
|
+
return [];
|
|
710
|
+
return available
|
|
711
|
+
.map((a) => ({ a, score: similarity(needle, a.toLowerCase()) }))
|
|
712
|
+
.filter((x) => x.score > 0)
|
|
713
|
+
.sort((x, y) => y.score - x.score || x.a.localeCompare(y.a))
|
|
714
|
+
.slice(0, limit)
|
|
715
|
+
.map((x) => x.a);
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Every action on the surface, with the drift each one carries. Used by the
|
|
719
|
+
* schema-drift unit test and by `project(describe_action)` when it is asked
|
|
720
|
+
* for a whole category rather than one action.
|
|
721
|
+
*/
|
|
722
|
+
export function allActionSchemas(tools) {
|
|
723
|
+
const out = [];
|
|
724
|
+
for (const tool of tools) {
|
|
725
|
+
for (const action of Object.keys(tool.actions)) {
|
|
726
|
+
out.push(actionSchema(tool, action));
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
return out;
|
|
730
|
+
}
|
|
731
|
+
//# sourceMappingURL=action-schema.js.map
|