@alpsckr/unitycli 0.4.2 → 0.4.4
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 -5
- package/contracts/command-contract.json +109 -217
- package/contracts/package-files.json +374 -294
- package/contracts/stable-session/direct-call-semantics.json +8 -2
- package/contracts/tool-completion-contract.json +148 -672
- package/dist/bridge/admission.d.ts +17 -1
- package/dist/bridge/admission.js +2 -0
- package/dist/bridge/admission.js.map +1 -1
- package/dist/bridge/domains/asset.js +3 -1
- package/dist/bridge/domains/asset.js.map +1 -1
- package/dist/bridge/domains/camera.js +39 -7
- package/dist/bridge/domains/camera.js.map +1 -1
- package/dist/bridge/domains/console.js +2 -1
- package/dist/bridge/domains/console.js.map +1 -1
- package/dist/bridge/domains/doctor.d.ts +3 -0
- package/dist/bridge/domains/doctor.js +8 -0
- package/dist/bridge/domains/doctor.js.map +1 -0
- package/dist/bridge/domains/editor.js +0 -4
- package/dist/bridge/domains/editor.js.map +1 -1
- package/dist/bridge/domains/game-object.js +4 -12
- package/dist/bridge/domains/game-object.js.map +1 -1
- package/dist/bridge/domains/graphics.js +1 -16
- package/dist/bridge/domains/graphics.js.map +1 -1
- package/dist/bridge/domains/object.js +3 -0
- package/dist/bridge/domains/object.js.map +1 -1
- package/dist/bridge/domains/physics.js +1 -2
- package/dist/bridge/domains/physics.js.map +1 -1
- package/dist/bridge/domains/profiler.js +18 -7
- package/dist/bridge/domains/profiler.js.map +1 -1
- package/dist/bridge/domains/project-tool.js +4 -0
- package/dist/bridge/domains/project-tool.js.map +1 -1
- package/dist/bridge/domains/reflection.js +10 -2
- package/dist/bridge/domains/reflection.js.map +1 -1
- package/dist/bridge/domains/runtime-tool.js +3 -0
- package/dist/bridge/domains/runtime-tool.js.map +1 -1
- package/dist/bridge/domains/scene.js +3 -8
- package/dist/bridge/domains/scene.js.map +1 -1
- package/dist/bridge/domains/screenshot.js +30 -6
- package/dist/bridge/domains/screenshot.js.map +1 -1
- package/dist/bridge/domains/ui.js +3 -1
- package/dist/bridge/domains/ui.js.map +1 -1
- package/dist/bridge/index.d.ts +5 -5
- package/dist/bridge/index.js +6 -9
- package/dist/bridge/index.js.map +1 -1
- package/dist/bridge/registry.js +3 -5
- package/dist/bridge/registry.js.map +1 -1
- package/dist/bridge/session-supervisor.d.ts +6 -3
- package/dist/bridge/session-supervisor.js +122 -34
- package/dist/bridge/session-supervisor.js.map +1 -1
- package/dist/bridge/types.d.ts +6 -5
- package/dist/bridge/types.js +16 -2
- package/dist/bridge/types.js.map +1 -1
- package/dist/catalog-protocol.d.ts +64 -0
- package/dist/catalog-protocol.js +460 -0
- package/dist/catalog-protocol.js.map +1 -0
- package/dist/cli-commands/command.js +84 -61
- package/dist/cli-commands/command.js.map +1 -1
- package/dist/cli-commands/extensions.js +70 -50
- package/dist/cli-commands/extensions.js.map +1 -1
- package/dist/cli-commands/instances.js +1 -1
- package/dist/cli-commands/instances.js.map +1 -1
- package/dist/cli-process-owners.d.ts +6 -0
- package/dist/cli-process-owners.js +27 -0
- package/dist/cli-process-owners.js.map +1 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +20 -5
- package/dist/cli.js.map +1 -1
- package/dist/contracts/command-contract.js +14 -4
- package/dist/contracts/command-contract.js.map +1 -1
- package/dist/doctor.d.ts +4 -6
- package/dist/doctor.js +429 -398
- package/dist/doctor.js.map +1 -1
- package/dist/editor-blocker-observer.d.ts +2 -1
- package/dist/editor-blocker-observer.js +40 -13
- package/dist/editor-blocker-observer.js.map +1 -1
- package/dist/extension-availability.d.ts +27 -9
- package/dist/extension-availability.js +96 -56
- package/dist/extension-availability.js.map +1 -1
- package/dist/extensions/descriptor-loader.d.ts +2 -2
- package/dist/extensions/descriptor-loader.js +3 -4
- package/dist/extensions/descriptor-loader.js.map +1 -1
- package/dist/extensions/index.d.ts +1 -1
- package/dist/extensions/index.js +1 -1
- package/dist/extensions/index.js.map +1 -1
- package/dist/extensions/installer.d.ts +24 -7
- package/dist/extensions/installer.js +142 -66
- package/dist/extensions/installer.js.map +1 -1
- package/dist/extensions/registry.js +3 -5
- package/dist/extensions/registry.js.map +1 -1
- package/dist/extensions/state-contract.d.ts +11 -0
- package/dist/extensions/state-contract.js +50 -0
- package/dist/extensions/state-contract.js.map +1 -0
- package/dist/instances.d.ts +13 -1
- package/dist/instances.js +26 -0
- package/dist/instances.js.map +1 -1
- package/dist/package-manager.d.ts +0 -24
- package/dist/package-manager.js +5 -58
- package/dist/package-manager.js.map +1 -1
- package/dist/project-tool-discovery.d.ts +5 -2
- package/dist/project-tool-discovery.js +22 -18
- package/dist/project-tool-discovery.js.map +1 -1
- package/dist/runtime-code.js +1 -1
- package/dist/runtime-code.js.map +1 -1
- package/dist/tool-name.d.ts +4 -0
- package/dist/tool-name.js +15 -0
- package/dist/tool-name.js.map +1 -0
- package/dist/tools/.generated-completion-registry.d.ts +2 -0
- package/dist/tools/.generated-completion-registry.js +763 -325
- package/dist/tools/.generated-completion-registry.js.map +1 -1
- package/dist/tools/.generated-registry.d.ts +6 -11
- package/dist/tools/.generated-registry.js +6 -11
- package/dist/tools/.generated-registry.js.map +1 -1
- package/dist/tools/.generated-schemas.js +11 -16
- package/dist/tools/.generated-schemas.js.map +1 -1
- package/dist/tools/asset/refresh-status.d.ts +3 -0
- package/dist/tools/asset/refresh-status.js +12 -0
- package/dist/tools/asset/refresh-status.js.map +1 -0
- package/dist/tools/asset/refresh.js +7 -3
- package/dist/tools/asset/refresh.js.map +1 -1
- package/dist/tools/build/player-status.d.ts +3 -0
- package/dist/tools/build/player-status.js +12 -0
- package/dist/tools/build/player-status.js.map +1 -0
- package/dist/tools/build/run.js +1 -1
- package/dist/tools/build/run.js.map +1 -1
- package/dist/tools/catalog-control.d.ts +22 -0
- package/dist/tools/catalog-control.js +335 -0
- package/dist/tools/catalog-control.js.map +1 -0
- package/dist/tools/catalog.d.ts +7 -9
- package/dist/tools/catalog.js +66 -45
- package/dist/tools/catalog.js.map +1 -1
- package/dist/tools/commands.d.ts +1 -1
- package/dist/tools/commands.js +44 -11
- package/dist/tools/commands.js.map +1 -1
- package/dist/tools/define.js +3 -2
- package/dist/tools/define.js.map +1 -1
- package/dist/tools/dispatcher.d.ts +53 -6
- package/dist/tools/dispatcher.js +277 -108
- package/dist/tools/dispatcher.js.map +1 -1
- package/dist/tools/editor/compile.js +1 -1
- package/dist/tools/editor/compile.js.map +1 -1
- package/dist/tools/editor/play-mode-status.d.ts +3 -0
- package/dist/tools/editor/play-mode-status.js +12 -0
- package/dist/tools/editor/play-mode-status.js.map +1 -0
- package/dist/tools/editor/readiness.d.ts +0 -1
- package/dist/tools/editor/readiness.js +0 -1
- package/dist/tools/editor/readiness.js.map +1 -1
- package/dist/tools/editor/refresh.d.ts +7 -3
- package/dist/tools/editor/refresh.js +20 -15
- package/dist/tools/editor/refresh.js.map +1 -1
- package/dist/tools/editor/state.d.ts +2 -2
- package/dist/tools/editor/state.js +13 -12
- package/dist/tools/editor/state.js.map +1 -1
- package/dist/tools/metadata/derived.js +1 -7
- package/dist/tools/metadata/derived.js.map +1 -1
- package/dist/tools/metadata/status.js +1 -12
- package/dist/tools/metadata/status.js.map +1 -1
- package/dist/tools/metadata/types.d.ts +0 -2
- package/dist/tools/navmesh/live.js +1 -1
- package/dist/tools/navmesh/live.js.map +1 -1
- package/dist/tools/occlusion/live.js +1 -1
- package/dist/tools/occlusion/live.js.map +1 -1
- package/dist/tools/package/deploy.js +2 -2
- package/dist/tools/package/deploy.js.map +1 -1
- package/dist/tools/package/embed.js +1 -1
- package/dist/tools/package/embed.js.map +1 -1
- package/dist/tools/package/install-status.d.ts +3 -0
- package/dist/tools/package/install-status.js +12 -0
- package/dist/tools/package/install-status.js.map +1 -0
- package/dist/tools/package/install.js +3 -3
- package/dist/tools/package/install.js.map +1 -1
- package/dist/tools/package/live-resolve.js +45 -12
- package/dist/tools/package/live-resolve.js.map +1 -1
- package/dist/tools/package/registry-clear.js.map +1 -1
- package/dist/tools/package/registry-set.js.map +1 -1
- package/dist/tools/package/remove-status.d.ts +3 -0
- package/dist/tools/package/remove-status.js +12 -0
- package/dist/tools/package/remove-status.js.map +1 -0
- package/dist/tools/package/remove.js +3 -3
- package/dist/tools/package/remove.js.map +1 -1
- package/dist/tools/package/resolve.js +3 -3
- package/dist/tools/package/resolve.js.map +1 -1
- package/dist/tools/package/restore.js +2 -2
- package/dist/tools/package/restore.js.map +1 -1
- package/dist/tools/package/scoped-registry-add.js.map +1 -1
- package/dist/tools/status/support.d.ts +13 -0
- package/dist/tools/status/support.js +28 -0
- package/dist/tools/status/support.js.map +1 -0
- package/dist/tools/test/cancel.js +2 -2
- package/dist/tools/test/cancel.js.map +1 -1
- package/dist/tools/test/run-status.d.ts +3 -0
- package/dist/tools/test/run-status.js +12 -0
- package/dist/tools/test/run-status.js.map +1 -0
- package/dist/tools/test/run.js +7 -4
- package/dist/tools/test/run.js.map +1 -1
- package/dist/tools/tools/group-summaries.js +10 -14
- package/dist/tools/tools/group-summaries.js.map +1 -1
- package/docs/architecture.md +4 -4
- package/docs/availability-differences.md +4 -4
- package/docs/command-execution.md +4 -3
- package/docs/error-codes.md +13 -5
- package/docs/extensions.md +1 -7
- package/docs/risk-and-side-effects.md +4 -3
- package/docs/safety-boundaries.md +3 -1
- package/docs/target-and-instance.md +1 -1
- package/docs/tool-discovery.md +17 -10
- package/docs/tool-execution.md +18 -7
- package/extensions/cinemachine/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
- package/extensions/hdrp/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
- package/extensions/probuilder/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
- package/extensions/urp/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
- package/extensions/vfx-graph/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
- package/package.json +2 -4
- package/skills/unitycli/SKILL.md +35 -42
- package/skills/unitycli/manifest.json +1 -1
- package/skills/unitycli/references/operation-protocol.md +3 -3
- package/unitypkg/Editor/AssemblyInfo.cs +5 -0
- package/unitypkg/Editor/Bridge/BridgeCommandRegistry.cs +49 -14
- package/unitypkg/Editor/Bridge/BridgeHttpServer.cs +9 -35
- package/unitypkg/Editor/Bridge/BridgeQueueState.cs +672 -0
- package/unitypkg/Editor/Bridge/BridgeWorkQueue.cs +970 -120
- package/unitypkg/Editor/BridgeStartupStateMachine.cs +59 -1
- package/unitypkg/Editor/Commands/ProjectToolCommands.cs +4 -0
- package/unitypkg/Editor/Commands/RuntimeToolCommands.cs +3 -0
- package/unitypkg/Editor/Generated/UnityCliToolCompletionRegistry.g.cs +379 -14
- package/unitypkg/Editor/Infrastructure/CodeExecSupport.cs +28 -13
- package/unitypkg/Editor/Infrastructure/RegistryOwnershipResolver.cs +132 -105
- package/unitypkg/Editor/Infrastructure/UnityCliActiveOperation.cs +138 -29
- package/unitypkg/Editor/Infrastructure/UnityCliAdmission.cs +1856 -237
- package/unitypkg/Editor/Infrastructure/UnityCliDoctorDiagnostics.cs +220 -0
- package/unitypkg/Editor/Infrastructure/UnityCliDurableStatusCoordinator.cs +118 -0
- package/unitypkg/Editor/Infrastructure/UnityCliEditorStateSnapshot.cs +2 -1
- package/unitypkg/Editor/Infrastructure/UnityCliHierarchyPager.cs +203 -0
- package/unitypkg/Editor/Infrastructure/UnityCliInstanceCleanup.cs +2 -1
- package/unitypkg/Editor/Infrastructure/UnityCliInstanceRegistry.cs +201 -63
- package/unitypkg/Editor/Infrastructure/UnityCliJson.cs +2 -3
- package/unitypkg/Editor/Infrastructure/UnityCliLiveBuildCoordinator.cs +289 -46
- package/unitypkg/Editor/Infrastructure/UnityCliLiveRefreshCoordinator.cs +87 -53
- package/unitypkg/Editor/Infrastructure/UnityCliNavMeshBakeCoordinator.cs +4 -4
- package/unitypkg/Editor/Infrastructure/UnityCliObjectRef.cs +128 -25
- package/unitypkg/Editor/Infrastructure/UnityCliObjectSerializer.cs +435 -327
- package/unitypkg/Editor/Infrastructure/UnityCliOcclusionBakeCoordinator.cs +4 -4
- package/unitypkg/Editor/Infrastructure/UnityCliOperationCoordinator.cs +351 -53
- package/unitypkg/Editor/Infrastructure/UnityCliPackageResolveCoordinator.cs +118 -18
- package/unitypkg/Editor/Infrastructure/UnityCliPackageResolveState.cs +20 -1
- package/unitypkg/Editor/Infrastructure/UnityCliReadGeneration.cs +97 -0
- package/unitypkg/Editor/Infrastructure/UnityCliReflection.cs +644 -133
- package/unitypkg/Editor/Infrastructure/UnityCliReloadOperation.cs +128 -41
- package/unitypkg/Editor/Infrastructure/UnityCliRuntimeExecutor.cs +14 -84
- package/unitypkg/Editor/Infrastructure/UnityCliSerializer.cs +220 -0
- package/unitypkg/Editor/Infrastructure/UnityCliSessionMutationLease.cs +69 -29
- package/unitypkg/Editor/Infrastructure/UnityCliTestRunCoordinator.cs +440 -54
- package/unitypkg/Editor/Infrastructure/UnityCliWaiter.cs +128 -35
- package/unitypkg/Editor/ProjectTools/IUnityCliTool.cs +9 -1
- package/unitypkg/Editor/ProjectTools/OfficialAttributeToolRegistry.cs +32 -31
- package/unitypkg/Editor/ProjectTools/ProjectToolRegistry.cs +607 -458
- package/unitypkg/Editor/ProjectTools/ProjectToolSchemaValidator.cs +35 -7
- package/unitypkg/Editor/ProjectTools/RegisteredTool.cs +501 -60
- package/unitypkg/Editor/ProjectTools/UnityCliProjectToolContext.cs +21 -0
- package/unitypkg/Editor/ProjectTools/UnityCliSchemaDeriver.cs +36 -7
- package/unitypkg/Editor/ProjectTools/UnityCliToolName.cs +30 -0
- package/unitypkg/Editor/Properties/AssemblyInfo.cs +3 -0
- package/unitypkg/Editor/Tools/asset/get-data.cs +16 -7
- package/unitypkg/Editor/Tools/build/BuildSceneStateTool.cs +23 -3
- package/unitypkg/Editor/Tools/build/BuildToolDtos.cs +2 -1
- package/unitypkg/Editor/Tools/build/scene/add.cs +6 -3
- package/unitypkg/Editor/Tools/build/scene/disable.cs +1 -1
- package/unitypkg/Editor/Tools/build/scene/enable.cs +1 -1
- package/unitypkg/Editor/Tools/build/scene/list.cs +1 -1
- package/unitypkg/Editor/Tools/build/scene/remove.cs +13 -3
- package/unitypkg/Editor/Tools/camera/CameraToolSupport.cs +38 -38
- package/unitypkg/Editor/Tools/camera/multiview.cs +13 -6
- package/unitypkg/Editor/Tools/camera/screenshot.cs +14 -7
- package/unitypkg/Editor/Tools/component/get.cs +16 -9
- package/unitypkg/Editor/Tools/component/modify.cs +6 -4
- package/unitypkg/Editor/Tools/console/clear.cs +7 -6
- package/unitypkg/Editor/Tools/console/get.cs +48 -16
- package/unitypkg/Editor/Tools/editor/EditorToolSupport.cs +143 -28
- package/unitypkg/Editor/Tools/editor/menu-items.cs +1 -1
- package/unitypkg/Editor/Tools/editor/play.cs +2 -11
- package/unitypkg/Editor/Tools/editor/selection.cs +1 -1
- package/unitypkg/Editor/Tools/editor/stop.cs +2 -11
- package/unitypkg/Editor/Tools/editor/windows.cs +1 -1
- package/unitypkg/Editor/Tools/go/GameObjectToolSupport.cs +54 -4
- package/unitypkg/Editor/Tools/go/delete.cs +1 -1
- package/unitypkg/Editor/Tools/go/find.cs +167 -70
- package/unitypkg/Editor/Tools/graphics/GraphicsToolSupport.cs +25 -0
- package/unitypkg/Editor/Tools/graphics/light-probe-group.cs +9 -3
- package/unitypkg/Editor/Tools/graphics/pipeline-info.cs +1 -1
- package/unitypkg/Editor/Tools/graphics/pipeline-settings.cs +1 -1
- package/unitypkg/Editor/Tools/graphics/quality.cs +2 -2
- package/unitypkg/Editor/Tools/graphics/reflection-probe.cs +3 -3
- package/unitypkg/Editor/Tools/graphics/stats.cs +1 -1
- package/unitypkg/Editor/Tools/object/ObjectToolSupport.cs +4 -4
- package/unitypkg/Editor/Tools/object/get-data.cs +9 -7
- package/unitypkg/Editor/Tools/object/modify.cs +7 -5
- package/unitypkg/Editor/Tools/physics/raycast.cs +24 -6
- package/unitypkg/Editor/Tools/prefab/stage.cs +1 -1
- package/unitypkg/Editor/Tools/profiler/ProfilerFrameDebuggerSupport.cs +94 -21
- package/unitypkg/Editor/Tools/profiler/ProfilerMemorySnapshotSupport.cs +397 -10
- package/unitypkg/Editor/Tools/profiler/ProfilerToolSupport.cs +18 -4
- package/unitypkg/Editor/Tools/profiler/frame-debugger/enable.cs +1 -1
- package/unitypkg/Editor/Tools/profiler/frame-debugger/events.cs +88 -87
- package/unitypkg/Editor/Tools/profiler/hierarchy.cs +278 -112
- package/unitypkg/Editor/Tools/profiler/memory-snapshot/compare.cs +9 -5
- package/unitypkg/Editor/Tools/profiler/memory-snapshot/list.cs +37 -35
- package/unitypkg/Editor/Tools/profiler/memory-snapshot/take.cs +99 -97
- package/unitypkg/Editor/Tools/project/info.cs +1 -1
- package/unitypkg/Editor/Tools/project/layers.cs +1 -1
- package/unitypkg/Editor/Tools/project/tags.cs +1 -1
- package/unitypkg/Editor/Tools/reflection/method/call.cs +9 -7
- package/unitypkg/Editor/Tools/reflection/method/find.cs +27 -37
- package/unitypkg/Editor/Tools/reflection/type/schema.cs +24 -28
- package/unitypkg/Editor/Tools/scene/gameobject/component.cs +8 -5
- package/unitypkg/Editor/Tools/scene/gameobject/components.cs +10 -7
- package/unitypkg/Editor/Tools/scene/gameobject-support.cs +104 -8
- package/unitypkg/Editor/Tools/scene/gameobject.cs +37 -16
- package/unitypkg/Editor/Tools/scene/hierarchy.cs +146 -142
- package/unitypkg/Editor/Tools/screenshot/EditorElementCaptureToolSupport.cs +77 -25
- package/unitypkg/Editor/Tools/screenshot/ScreenshotToolSupport.cs +315 -95
- package/unitypkg/Editor/Tools/screenshot/editor-element.cs +7 -5
- package/unitypkg/Editor/Tools/screenshot/game-view.cs +35 -21
- package/unitypkg/Editor/Tools/screenshot/isolated-object.cs +29 -23
- package/unitypkg/Editor/Tools/screenshot/orbit.cs +37 -25
- package/unitypkg/Editor/Tools/screenshot/scene-view.cs +25 -18
- package/unitypkg/Editor/Tools/screenshot/screen.cs +177 -17
- package/unitypkg/Editor/Tools/screenshot/surround.cs +83 -79
- package/unitypkg/Editor/Tools/ui/render-preview.cs +16 -11
- package/unitypkg/Editor/UnityCliBridge.cs +97 -34
- package/unitypkg/Runtime/Infrastructure/UnityCliRuntimeBridge.cs +753 -122
- package/unitypkg/Runtime/Properties/AssemblyInfo.cs +5 -0
- package/unitypkg/Runtime/UnityCli.Runtime.asmdef +1 -1
- package/unitypkg/Shared/AssemblyInfo.cs +4 -0
- package/unitypkg/Shared/UnityCliAdmissionPool.cs +265 -75
- package/unitypkg/Shared/UnityCliBridgeQueuePolicy.cs +410 -0
- package/unitypkg/Shared/UnityCliBridgeWorkItemState.cs +47 -0
- package/unitypkg/Shared/UnityCliExceptionProjection.cs +151 -0
- package/unitypkg/Shared/UnityCliQueuePublication.cs +30 -0
- package/unitypkg/Shared/UnityCliRequestBudget.cs +352 -0
- package/unitypkg/Shared/UnityCliRuntimeRequestPublication.cs +107 -0
- package/unitypkg/Tests/Editor/UnityCli.Editor.Tests.asmdef +2 -2
- package/unitypkg/Tests/Editor/UnityCliAdmissionCoordinatorIntegrationTests.cs +1329 -0
- package/unitypkg/Tests/Editor/UnityCliAdmissionPoolTests.cs +311 -114
- package/unitypkg/Tests/Editor/UnityCliAdmissionRepairStateTests.cs +94 -0
- package/unitypkg/Tests/Editor/UnityCliBridgeAdmissionIntegrationTests.cs +1229 -0
- package/unitypkg/Tests/Editor/UnityCliBridgeQueuePolicyTests.cs +412 -0
- package/unitypkg/Tests/Editor/UnityCliContinuationIntegrationTests.cs +292 -0
- package/unitypkg/Tests/Editor/UnityCliDiagnosticBoundaryTests.cs +120 -0
- package/unitypkg/Tests/Editor/UnityCliDurableStatusTests.cs +84 -0
- package/unitypkg/Tests/Editor/UnityCliExceptionProjectionTests.cs +72 -0
- package/unitypkg/Tests/Editor/UnityCliInstancePublicationRetryTests.cs +180 -97
- package/unitypkg/Tests/Editor/UnityCliMemoryReflectionIntegrationTests.cs +434 -0
- package/unitypkg/Tests/Editor/UnityCliRequestBudgetTests.cs +615 -0
- package/unitypkg/Tests/Editor/UnityCliRuntimeBridgeIntegrationTests.cs +520 -0
- package/unitypkg/Tests/Editor/UnityCliToolCatalogTests.cs +562 -0
- package/unitypkg/Tests/Runtime/UnityCli.Runtime.Tests.asmdef +23 -0
- package/unitypkg/Tests/Runtime/UnityCliRuntimeCatalogValidationTests.cs +29 -0
- package/unitypkg/package.json +1 -1
- package/contracts/static-capability-catalog.json +0 -48226
- package/dist/bridge/domains/diagnose.d.ts +0 -2
- package/dist/bridge/domains/diagnose.js +0 -11
- package/dist/bridge/domains/diagnose.js.map +0 -1
- package/dist/bridge/domains/extension.d.ts +0 -8
- package/dist/bridge/domains/extension.js +0 -20
- package/dist/bridge/domains/extension.js.map +0 -1
- package/dist/capabilities.d.ts +0 -5
- package/dist/capabilities.js +0 -84
- package/dist/capabilities.js.map +0 -1
- package/dist/static-capability-catalog.d.ts +0 -13
- package/dist/static-capability-catalog.js +0 -41
- package/dist/static-capability-catalog.js.map +0 -1
- package/dist/tools/diagnose/capabilities.d.ts +0 -13
- package/dist/tools/diagnose/capabilities.js +0 -34
- package/dist/tools/diagnose/capabilities.js.map +0 -1
- package/dist/tools/package/info.d.ts +0 -19
- package/dist/tools/package/info.js +0 -27
- package/dist/tools/package/info.js.map +0 -1
- package/dist/tools/package/list.d.ts +0 -27
- package/dist/tools/package/list.js +0 -21
- package/dist/tools/package/list.js.map +0 -1
- package/dist/tools/package/registry-get.d.ts +0 -10
- package/dist/tools/package/registry-get.js +0 -22
- package/dist/tools/package/registry-get.js.map +0 -1
- package/dist/tools/package/scoped-registry-list.d.ts +0 -14
- package/dist/tools/package/scoped-registry-list.js +0 -22
- package/dist/tools/package/scoped-registry-list.js.map +0 -1
- package/dist/tools/package/search.d.ts +0 -17
- package/dist/tools/package/search.js +0 -39
- package/dist/tools/package/search.js.map +0 -1
- package/dist/tools/package/status.d.ts +0 -21
- package/dist/tools/package/status.js +0 -37
- package/dist/tools/package/status.js.map +0 -1
- package/dist/tools/tools/describe.d.ts +0 -27
- package/dist/tools/tools/describe.js +0 -35
- package/dist/tools/tools/describe.js.map +0 -1
- package/dist/tools/tools/group-list.d.ts +0 -16
- package/dist/tools/tools/group-list.js +0 -38
- package/dist/tools/tools/group-list.js.map +0 -1
- package/dist/tools/tools/group.d.ts +0 -13
- package/dist/tools/tools/group.js +0 -34
- package/dist/tools/tools/group.js.map +0 -1
- package/dist/tools/tools/pagination.d.ts +0 -17
- package/dist/tools/tools/pagination.js +0 -28
- package/dist/tools/tools/pagination.js.map +0 -1
- package/dist/tools/tools/search.d.ts +0 -15
- package/dist/tools/tools/search.js +0 -42
- package/dist/tools/tools/search.js.map +0 -1
- package/unitypkg/Editor/Tools/diagnose/preflight.cs +0 -269
- package/unitypkg/Editor/Tools/editor/console.cs +0 -26
- package/unitypkg/Editor/Tools/extension/status.cs +0 -87
- package/unitypkg/Editor/Tools/go/inspect.cs +0 -49
- package/unitypkg/Editor/Tools/graphics/pipeline-quality.cs +0 -24
- package/unitypkg/Editor/Tools/graphics/probe-positions.cs +0 -58
- package/unitypkg/Editor/Tools/graphics/settings.cs +0 -104
- package/unitypkg/Editor/Tools/graphics/stats-counters.cs +0 -28
- package/unitypkg/Editor/Tools/graphics/stats-memory.cs +0 -21
- package/unitypkg/Editor/Tools/instances/list.cs +0 -71
- package/unitypkg/Editor/Tools/physics/raycast-all.cs +0 -67
- package/unitypkg/Editor/Tools/scene/build-settings.cs +0 -69
- package/unitypkg/Editor/Tools/scene/data.cs +0 -125
- package/unitypkg/Editor/Tools/tests/list.cs +0 -98
|
@@ -3,13 +3,11 @@ using System;
|
|
|
3
3
|
using System.CodeDom.Compiler;
|
|
4
4
|
using System.Collections.Generic;
|
|
5
5
|
using System.Globalization;
|
|
6
|
-
using System.IO;
|
|
7
6
|
using System.Linq;
|
|
8
7
|
using System.Net;
|
|
9
8
|
using System.Reflection;
|
|
10
9
|
using System.Security.Cryptography;
|
|
11
10
|
using System.Text;
|
|
12
|
-
using System.Text.RegularExpressions;
|
|
13
11
|
using System.Threading;
|
|
14
12
|
using System.Threading.Tasks;
|
|
15
13
|
using Microsoft.CSharp;
|
|
@@ -29,45 +27,32 @@ namespace UnityCli
|
|
|
29
27
|
{
|
|
30
28
|
public static partial class UnityCliBridge
|
|
31
29
|
{
|
|
32
|
-
private const string ProjectToolFingerprintAlgorithm = "unity-cli-project-tool-fingerprint-v2";
|
|
33
30
|
private const string OfficialAttributeToolPrefix = "UnityPipeline_";
|
|
34
31
|
private const string OfficialAttributeToolGroup = "UnityPipeline";
|
|
35
|
-
private static
|
|
36
|
-
private static
|
|
37
|
-
|
|
32
|
+
private static UnityCliPublishedToolCatalog publishedProjectToolCatalog;
|
|
33
|
+
private static UnityCliToolCatalogHealthSnapshot projectToolCatalogHealth = new UnityCliToolCatalogHealthSnapshot(
|
|
34
|
+
false, 0, "catalog was not initialized", "Let Unity compile and reload the domain before retrying.");
|
|
35
|
+
private static int projectToolCatalogConstructionCount;
|
|
36
|
+
private static readonly byte[] projectToolCatalogCursorSecret = CreateProjectToolCatalogCursorSecret();
|
|
37
|
+
internal static IUnityCliToolCatalogSnapshotReader ProjectToolCatalogSnapshotReader { get; } = new PublishedToolCatalogSnapshotReader();
|
|
38
|
+
internal static IUnityCliToolCatalogHealthSnapshotReader ProjectToolCatalogHealthSnapshotReader { get; } = new PublishedToolCatalogHealthSnapshotReader();
|
|
38
39
|
|
|
39
|
-
private
|
|
40
|
+
private sealed class PublishedToolCatalogSnapshotReader : IUnityCliToolCatalogSnapshotReader
|
|
40
41
|
{
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (
|
|
52
|
-
|
|
53
|
-
definition.RequiredInput = new string[0];
|
|
54
|
-
}
|
|
55
|
-
definition.Group = string.IsNullOrEmpty(definition.Group) ? ProjectToolGroupFromName(definition.Name) : definition.Group;
|
|
56
|
-
definition.Target = string.IsNullOrEmpty(definition.Target) ? "editor" : definition.Target;
|
|
57
|
-
definition.AvailabilityScope = string.IsNullOrEmpty(definition.AvailabilityScope) ? (definition.Target == "runtime" ? "playModeSession" : "editorSession") : definition.AvailabilityScope;
|
|
58
|
-
definition.PlayModeSupport = NormalizeProjectToolPlayModeSupport(definition.PlayModeSupport, definition.Target);
|
|
59
|
-
if (!string.Equals(definition.ContractProfile, "native", StringComparison.Ordinal)
|
|
60
|
-
&& !string.Equals(definition.ContractProfile, "official-attribute", StringComparison.Ordinal))
|
|
61
|
-
{
|
|
62
|
-
throw new ArgumentException("Tool registration requires a known contract profile: " + definition.Name);
|
|
63
|
-
}
|
|
42
|
+
public UnityCliToolCatalogSnapshot Read() { return ReadPublishedProjectToolCatalogSnapshot(); }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private sealed class PublishedToolCatalogHealthSnapshotReader : IUnityCliToolCatalogHealthSnapshotReader
|
|
46
|
+
{
|
|
47
|
+
public UnityCliToolCatalogHealthSnapshot Read() { return Volatile.Read(ref projectToolCatalogHealth); }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
private static void RegisterProjectTool(UnityCliToolCatalogBuilder builder, RegisteredTool definition, UnityCliToolProviderKind providerKind)
|
|
51
|
+
{
|
|
52
|
+
if (builder == null) throw new ArgumentNullException("builder");
|
|
53
|
+
if (definition == null) throw new ArgumentNullException("definition");
|
|
64
54
|
ValidateOfficialAttributeNamespace(definition);
|
|
65
|
-
|
|
66
|
-
definition.MaxTimeoutMs = definition.MaxTimeoutMs <= 0 ? 600000 : definition.MaxTimeoutMs;
|
|
67
|
-
definition.RegisteredBy = string.IsNullOrEmpty(definition.RegisteredBy) ? "unity-editor-package" : definition.RegisteredBy;
|
|
68
|
-
definition.DescriptorSource = string.IsNullOrEmpty(definition.DescriptorSource) ? "runtime-registration" : definition.DescriptorSource;
|
|
69
|
-
RefreshProjectToolFingerprint(definition);
|
|
70
|
-
target.Add(definition.Name, definition);
|
|
55
|
+
builder.Add(definition, providerKind);
|
|
71
56
|
}
|
|
72
57
|
|
|
73
58
|
/// <summary>保留namespace只属于official attribute投影;任一owner违规使整轮pending registry作废。</summary>
|
|
@@ -82,51 +67,125 @@ namespace UnityCli
|
|
|
82
67
|
throw new InvalidOperationException("Native Tool cannot use the reserved UnityPipeline_ name prefix or UnityPipeline group: " + definition.Name + ".");
|
|
83
68
|
}
|
|
84
69
|
|
|
85
|
-
|
|
70
|
+
/// <summary>InitializeOnLoad静态构造函数调用一次;局部pending完整成功后才原子发布。</summary>
|
|
71
|
+
private static void InitializeProjectToolCatalog()
|
|
86
72
|
{
|
|
87
|
-
if (
|
|
88
|
-
|
|
89
|
-
var
|
|
73
|
+
if (projectToolCatalogConstructionCount != 0) return;
|
|
74
|
+
projectToolCatalogConstructionCount = 1;
|
|
75
|
+
var startedAt = DateTime.UtcNow;
|
|
76
|
+
var builder = new UnityCliToolCatalogBuilder();
|
|
90
77
|
try
|
|
91
78
|
{
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
var officialRoots = ConfigureOfficialAuthoringApi(assemblies);
|
|
95
|
-
var candidates = ToolProviderAssemblies(assemblies, officialRoots);
|
|
96
|
-
registryOwnership = RegistryOwnershipResolver.Load(candidates);
|
|
97
|
-
WriteLog("tool_discovery_started assemblies=" + assemblies.Length.ToString(CultureInfo.InvariantCulture)
|
|
98
|
-
+ " candidates=" + candidates.Count.ToString(CultureInfo.InvariantCulture));
|
|
99
|
-
foreach (var assembly in candidates)
|
|
100
|
-
{
|
|
101
|
-
Type[] types;
|
|
102
|
-
try { types = assembly.GetTypes(); }
|
|
103
|
-
catch (ReflectionTypeLoadException exception) { throw new InvalidOperationException("Unable to inspect every type in Tool provider assembly " + assembly.FullName + ".", exception); }
|
|
104
|
-
catch (Exception exception) { throw new InvalidOperationException("Unable to inspect Tool provider assembly " + assembly.FullName + ".", exception); }
|
|
105
|
-
foreach (var type in types)
|
|
106
|
-
{
|
|
107
|
-
if (type == null) continue;
|
|
108
|
-
TryRegisterInterfaceProjectTool(assembly, type);
|
|
109
|
-
TryRegisterOfficialAttributeTools(assembly, type);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
WriteLog("tool_discovery_completed candidates=" + candidates.Count.ToString(CultureInfo.InvariantCulture)
|
|
113
|
-
+ " tools=" + pending.Count.ToString(CultureInfo.InvariantCulture));
|
|
114
|
-
|
|
115
|
-
ProjectTools.Clear();
|
|
116
|
-
foreach (var item in pending) ProjectTools.Add(item.Key, item.Value);
|
|
117
|
-
projectToolsDiscovered = true;
|
|
79
|
+
CollectProjectToolCatalog(AppDomain.CurrentDomain.GetAssemblies(), builder);
|
|
80
|
+
PublishProjectToolCatalog(builder, (long)Math.Ceiling((DateTime.UtcNow - startedAt).TotalMilliseconds));
|
|
118
81
|
}
|
|
119
82
|
catch (Exception exception)
|
|
120
83
|
{
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
84
|
+
builder.Abort();
|
|
85
|
+
var failure = BoundedCatalogDiagnostic(ExceptionChain(exception));
|
|
86
|
+
Volatile.Write(ref projectToolCatalogHealth, new UnityCliToolCatalogHealthSnapshot(
|
|
87
|
+
false, 0, failure, "Fix the Tool definition, then let Unity compile and reload the domain before retrying."));
|
|
88
|
+
WriteLog("tool_catalog_initialization_failed error=" + failure);
|
|
125
89
|
}
|
|
126
|
-
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// <summary>生产与大目录fixture共享同一assembly筛选、owner解析、类型扫描和pending table构造。</summary>
|
|
93
|
+
private static void CollectProjectToolCatalog(Assembly[] assemblies, UnityCliToolCatalogBuilder builder)
|
|
94
|
+
{
|
|
95
|
+
if (assemblies == null) throw new ArgumentNullException("assemblies");
|
|
96
|
+
if (builder == null) throw new ArgumentNullException("builder");
|
|
97
|
+
var officialRoots = ConfigureOfficialAuthoringApi(assemblies);
|
|
98
|
+
var candidates = ToolProviderAssemblies(assemblies, officialRoots);
|
|
99
|
+
builder.BeginDiscovery(assemblies.Length, candidates.Count);
|
|
100
|
+
var ownership = RegistryOwnershipResolver.Load(candidates);
|
|
101
|
+
foreach (var assembly in candidates)
|
|
102
|
+
{
|
|
103
|
+
builder.RecordProviderMetadata(assembly.GetName().FullName);
|
|
104
|
+
Type[] types;
|
|
105
|
+
try { types = assembly.GetTypes(); }
|
|
106
|
+
catch (ReflectionTypeLoadException exception) { throw new InvalidOperationException("Unable to inspect every type in Tool provider assembly " + assembly.FullName + ".", exception); }
|
|
107
|
+
catch (Exception exception) { throw new InvalidOperationException("Unable to inspect Tool provider assembly " + assembly.FullName + ".", exception); }
|
|
108
|
+
foreach (var type in types)
|
|
109
|
+
{
|
|
110
|
+
if (type == null) continue;
|
|
111
|
+
TryRegisterInterfaceProjectTool(builder, ownership, assembly, type);
|
|
112
|
+
TryRegisterOfficialAttributeTools(builder, ownership, assembly, type);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
ownership.ValidateComplete();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
private static bool ProjectToolCatalogReady()
|
|
119
|
+
{
|
|
120
|
+
return Volatile.Read(ref publishedProjectToolCatalog) != null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// <summary>候选完整 Publish 后才替换唯一 slot;任何第 N 项失败都保留既有 publication。</summary>
|
|
124
|
+
private static UnityCliToolCatalogSnapshot PublishProjectToolCatalog(UnityCliToolCatalogBuilder builder, long elapsedMilliseconds)
|
|
125
|
+
{
|
|
126
|
+
var candidate = builder.Publish(elapsedMilliseconds, WriteProjectToolCatalogInitializedLog);
|
|
127
|
+
Volatile.Write(ref publishedProjectToolCatalog, candidate);
|
|
128
|
+
Volatile.Write(ref projectToolCatalogHealth, new UnityCliToolCatalogHealthSnapshot(
|
|
129
|
+
true, candidate.Snapshot.ToolCount, string.Empty, string.Empty));
|
|
130
|
+
return candidate.Snapshot;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/// <summary>候选提交前写入唯一 aggregate success log;失败时旧 publication 保持不变。</summary>
|
|
134
|
+
private static void WriteProjectToolCatalogInitializedLog(UnityCliToolCatalogSnapshot snapshot)
|
|
135
|
+
{
|
|
136
|
+
WriteLog("tool_catalog_initialized assemblies=" + snapshot.AssemblyCount.ToString(CultureInfo.InvariantCulture)
|
|
137
|
+
+ " providers=" + snapshot.ProviderAssemblyCount.ToString(CultureInfo.InvariantCulture)
|
|
138
|
+
+ " tools=" + snapshot.ToolCount.ToString(CultureInfo.InvariantCulture)
|
|
139
|
+
+ " builtIn=" + snapshot.BuiltInToolCount.ToString(CultureInfo.InvariantCulture)
|
|
140
|
+
+ " extension=" + snapshot.ExtensionToolCount.ToString(CultureInfo.InvariantCulture)
|
|
141
|
+
+ " projectInterface=" + snapshot.ProjectInterfaceToolCount.ToString(CultureInfo.InvariantCulture)
|
|
142
|
+
+ " officialAttribute=" + snapshot.OfficialAttributeToolCount.ToString(CultureInfo.InvariantCulture)
|
|
143
|
+
+ " elapsedMs=" + snapshot.ElapsedMilliseconds.ToString(CultureInfo.InvariantCulture));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/// <summary>执行入口读取唯一 published executable table;失败 domain 内绝不重建。</summary>
|
|
147
|
+
private static UnityCliPublishedToolCatalog ReadPublishedProjectToolCatalog()
|
|
148
|
+
{
|
|
149
|
+
var catalog = Volatile.Read(ref publishedProjectToolCatalog);
|
|
150
|
+
if (catalog != null) return catalog;
|
|
151
|
+
var health = ProjectToolCatalogHealthSnapshotReader.Read();
|
|
152
|
+
throw new BridgeCommandException(
|
|
153
|
+
"E_TOOL_REGISTRY_UNHEALTHY",
|
|
154
|
+
"Tool catalog initialization failed in the current Unity domain: " + health.Failure,
|
|
155
|
+
new Dictionary<string, object> { { "registry", "tools" } },
|
|
156
|
+
new[] { health.NextAction },
|
|
157
|
+
true,
|
|
158
|
+
1000);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private static UnityCliToolCatalogSnapshot ReadPublishedProjectToolCatalogSnapshot()
|
|
162
|
+
{
|
|
163
|
+
return ReadPublishedProjectToolCatalog().Snapshot;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/// <summary>doctor 专用只读 health,不取得 invoker、schema 对象或 preflight 聚合。</summary>
|
|
167
|
+
private static object ProjectToolCatalogHealth(UnityCli.Protocol.BridgeRequest request)
|
|
168
|
+
{
|
|
169
|
+
RequireProjectToolCatalogInputKeys(request);
|
|
170
|
+
var health = ProjectToolCatalogHealthSnapshotReader.Read();
|
|
171
|
+
var result = new Dictionary<string, object>
|
|
172
|
+
{
|
|
173
|
+
{ "ready", health.Ready },
|
|
174
|
+
{ "toolCount", (long)health.ToolCount }
|
|
175
|
+
};
|
|
176
|
+
if (!health.Ready)
|
|
127
177
|
{
|
|
128
|
-
|
|
178
|
+
result["failure"] = BoundedCatalogDiagnostic(health.Failure);
|
|
179
|
+
result["nextActions"] = new[] { health.NextAction };
|
|
129
180
|
}
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private static string BoundedCatalogDiagnostic(string value)
|
|
185
|
+
{
|
|
186
|
+
const int maxChars = 4096;
|
|
187
|
+
if (string.IsNullOrEmpty(value) || value.Length <= maxChars) return value ?? string.Empty;
|
|
188
|
+
return value.Substring(0, maxChars) + " [truncated]";
|
|
130
189
|
}
|
|
131
190
|
|
|
132
191
|
/// <summary>
|
|
@@ -161,7 +220,7 @@ namespace UnityCli
|
|
|
161
220
|
return assemblies.Where(assembly => reachable.Contains(assembly.GetName().FullName)).ToList();
|
|
162
221
|
}
|
|
163
222
|
|
|
164
|
-
private static void TryRegisterInterfaceProjectTool(Assembly assembly, Type type)
|
|
223
|
+
private static void TryRegisterInterfaceProjectTool(UnityCliToolCatalogBuilder builder, RegistryOwnershipResolver ownershipResolver, Assembly assembly, Type type)
|
|
165
224
|
{
|
|
166
225
|
if (type == null || type.IsAbstract || type.IsInterface) return;
|
|
167
226
|
|
|
@@ -207,12 +266,12 @@ namespace UnityCli
|
|
|
207
266
|
throw new InvalidOperationException("Tool descriptor is incomplete: " + type.FullName + ".");
|
|
208
267
|
|
|
209
268
|
RegisteredTool definition;
|
|
210
|
-
var ownership =
|
|
269
|
+
var ownership = ownershipResolver.Resolve(type, descriptor.Name);
|
|
211
270
|
try
|
|
212
271
|
{
|
|
213
272
|
var factory = typeof(UnityCliBridge).GetMethod("CreateRegisteredTool", BindingFlags.Static | BindingFlags.NonPublic)
|
|
214
273
|
.MakeGenericMethod(reqType, resType);
|
|
215
|
-
definition = (RegisteredTool)factory.Invoke(null, new[] { instance, descriptor,
|
|
274
|
+
definition = (RegisteredTool)factory.Invoke(null, new object[] { instance, descriptor, ownership });
|
|
216
275
|
}
|
|
217
276
|
catch (Exception exception)
|
|
218
277
|
{
|
|
@@ -220,72 +279,54 @@ namespace UnityCli
|
|
|
220
279
|
WriteLog("custom_tool_registration_failed type=" + type.FullName + " error=" + diagnostics);
|
|
221
280
|
throw new InvalidOperationException("Tool registration failed: " + type.FullName + ": " + diagnostics, exception);
|
|
222
281
|
}
|
|
223
|
-
|
|
224
|
-
|
|
282
|
+
var providerKind = string.Equals(ownership.Source, "built-in", StringComparison.Ordinal)
|
|
283
|
+
? UnityCliToolProviderKind.BuiltIn
|
|
284
|
+
: string.Equals(ownership.Source, "extension", StringComparison.Ordinal)
|
|
285
|
+
? UnityCliToolProviderKind.Extension
|
|
286
|
+
: UnityCliToolProviderKind.ProjectInterface;
|
|
287
|
+
RegisterProjectTool(builder, definition, providerKind);
|
|
225
288
|
}
|
|
226
289
|
|
|
227
290
|
// 展开反射包装异常,确保 registry 失败能指向具体 schema/descriptor 根因。
|
|
228
291
|
private static string ExceptionChain(Exception exception)
|
|
229
292
|
{
|
|
293
|
+
const int maxDepth = 8;
|
|
230
294
|
var messages = new List<string>();
|
|
231
|
-
for (var current = exception; current != null; current = current.InnerException)
|
|
295
|
+
for (var current = exception; current != null && messages.Count < maxDepth; current = current.InnerException)
|
|
232
296
|
{
|
|
233
297
|
messages.Add(current.GetType().Name + ": " + current.Message);
|
|
234
298
|
}
|
|
235
|
-
return string.Join(" -> ", messages.ToArray());
|
|
299
|
+
return BoundedCatalogDiagnostic(string.Join(" -> ", messages.ToArray()));
|
|
236
300
|
}
|
|
237
301
|
|
|
238
|
-
private static RegisteredTool CreateRegisteredTool<TReq, TRes>(object instance, ToolDescriptor descriptor,
|
|
302
|
+
private static RegisteredTool CreateRegisteredTool<TReq, TRes>(object instance, ToolDescriptor descriptor, RegistryToolOwnership ownership)
|
|
239
303
|
{
|
|
240
304
|
var tool = (IUnityCliTool<TReq, TRes>)instance;
|
|
241
|
-
var ownership = registryOwnership.Resolve(implementationType);
|
|
242
305
|
var completion = ResolveToolCompletionContract(descriptor, ownership);
|
|
243
|
-
return new RegisteredTool
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
SideEffects = descriptor.SideEffects ?? Array.Empty<string>(),
|
|
256
|
-
SupportsDryRun = descriptor.SupportsDryRun,
|
|
257
|
-
Permissions = descriptor.Permissions ?? Array.Empty<string>(),
|
|
258
|
-
RequiresPackages = descriptor.RequiresPackages ?? Array.Empty<string>(),
|
|
259
|
-
Examples = descriptor.Examples ?? Array.Empty<ToolExample>(),
|
|
260
|
-
Target = string.IsNullOrEmpty(descriptor.Target) ? "editor" : descriptor.Target,
|
|
261
|
-
PlayModeSupport = string.IsNullOrEmpty(descriptor.PlayModeSupport) ? "editor-only" : descriptor.PlayModeSupport,
|
|
262
|
-
ContractProfile = "native",
|
|
263
|
-
DefaultTimeoutMs = descriptor.DefaultTimeoutMs <= 0 ? 30000 : descriptor.DefaultTimeoutMs,
|
|
264
|
-
MaxTimeoutMs = descriptor.MaxTimeoutMs <= 0 ? 600000 : descriptor.MaxTimeoutMs,
|
|
265
|
-
RequiredInput = Array.Empty<string>(),
|
|
266
|
-
RegisteredBy = "unity-editor-package",
|
|
267
|
-
DescriptorSource = "IUnityCliTool",
|
|
268
|
-
Assembly = assembly.GetName().Name,
|
|
269
|
-
Method = implementationType.FullName + ".ExecuteAsync",
|
|
270
|
-
Fingerprint = descriptor.Fingerprint,
|
|
271
|
-
PinnedFingerprint = descriptor.Fingerprint,
|
|
272
|
-
ExpectedFingerprint = descriptor.Fingerprint,
|
|
273
|
-
RequestType = typeof(TReq),
|
|
274
|
-
ResultType = typeof(TRes),
|
|
275
|
-
CompletionContract = completion,
|
|
276
|
-
InvokeAsync = async (request, context, cancellationToken) => (object)await tool.ExecuteAsync((TReq)request, context, cancellationToken)
|
|
277
|
-
};
|
|
306
|
+
return new RegisteredTool(
|
|
307
|
+
descriptor,
|
|
308
|
+
ownership,
|
|
309
|
+
UnityCliSchemaDeriver.DeriveSchema(typeof(TReq)),
|
|
310
|
+
UnityCliSchemaDeriver.DeriveSchema(typeof(TRes)),
|
|
311
|
+
string.IsNullOrEmpty(descriptor.Target) || descriptor.Target != "runtime" ? "editorSession" : "playModeSession",
|
|
312
|
+
"native",
|
|
313
|
+
typeof(TReq),
|
|
314
|
+
typeof(TRes),
|
|
315
|
+
completion,
|
|
316
|
+
null,
|
|
317
|
+
async (request, context, cancellationToken) => (object)await tool.ExecuteAsync((TReq)request, context, cancellationToken));
|
|
278
318
|
}
|
|
279
319
|
|
|
280
320
|
/// <summary>built-in/Extension 只信任生成 registry;自定义 Tool 必须提供完整机器契约。</summary>
|
|
281
|
-
private static UnityCliToolCompletionContract ResolveToolCompletionContract(ToolDescriptor descriptor,
|
|
321
|
+
private static UnityCliToolCompletionContract ResolveToolCompletionContract(ToolDescriptor descriptor, RegistryToolOwnership ownership)
|
|
282
322
|
{
|
|
283
323
|
UnityCliToolCompletionContract generated;
|
|
284
324
|
if (UnityCliToolCompletionRegistry.TryGet(descriptor.Name, out generated))
|
|
285
325
|
{
|
|
286
326
|
var generatedExtension = generated.DescriptorOwner.StartsWith("extension:", StringComparison.Ordinal);
|
|
287
|
-
if ((generatedExtension && !string.Equals(ownership, "extension", StringComparison.Ordinal)
|
|
288
|
-
|
|
327
|
+
if ((generatedExtension && (!string.Equals(ownership.Source, "extension", StringComparison.Ordinal)
|
|
328
|
+
|| !string.Equals(generated.DescriptorOwner, "extension:" + ownership.ExtensionId, StringComparison.Ordinal)))
|
|
329
|
+
|| (!generatedExtension && !string.Equals(generated.DescriptorOwner, ownership.Source, StringComparison.Ordinal)))
|
|
289
330
|
throw new InvalidOperationException("Completion contract owner mismatch for " + descriptor.Name + ".");
|
|
290
331
|
var descriptorMutation = descriptor.DestructiveHint
|
|
291
332
|
|| descriptor.SupportsDryRun
|
|
@@ -314,7 +355,7 @@ namespace UnityCli
|
|
|
314
355
|
|| (descriptor.SideEffects != null && descriptor.SideEffects.Length > 0)
|
|
315
356
|
|| string.Equals(descriptor.PlayModeSupport, "session-affecting", StringComparison.Ordinal);
|
|
316
357
|
return new UnityCliToolCompletionContract(
|
|
317
|
-
descriptor.Name,
|
|
358
|
+
descriptor.Name, ownership.Source, "direct-terminal", custom.ExecutionClass, custom.CompletionPredicate,
|
|
318
359
|
custom.ProofSource, custom.ReloadPolicy, custom.TimeoutError, custom.OutcomeUnknownPolicy,
|
|
319
360
|
custom.CompletionDriverId, mutation ? "required" : "none", mutation);
|
|
320
361
|
}
|
|
@@ -323,24 +364,373 @@ namespace UnityCli
|
|
|
323
364
|
private static UnityCliToolCompletionContract ResolveRegisteredCustomToolCompletionContract(string toolName)
|
|
324
365
|
{
|
|
325
366
|
if (string.IsNullOrWhiteSpace(toolName)) return null;
|
|
326
|
-
DiscoverProjectTools();
|
|
327
367
|
RegisteredTool definition;
|
|
328
|
-
return
|
|
368
|
+
return ReadPublishedProjectToolCatalog().ExecutableTools.TryGetValue(toolName, out definition) ? definition.CompletionContract : null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
private sealed class ProjectToolCatalogArgs
|
|
372
|
+
{
|
|
373
|
+
public string Group { get; set; }
|
|
374
|
+
public string Keyword { get; set; }
|
|
375
|
+
public int Limit { get; set; }
|
|
376
|
+
public string ExpectedCatalogGeneration { get; set; }
|
|
377
|
+
public string[] ReservedNames { get; set; }
|
|
378
|
+
public string[] LocalNames { get; set; }
|
|
379
|
+
public string CursorContext { get; set; }
|
|
380
|
+
public string CursorToken { get; set; }
|
|
381
|
+
public string Name { get; set; }
|
|
382
|
+
public string FallbackName { get; set; }
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/// <summary>continuation只绑定transport session与listener generation;Tool级identifier不参与stale判定。</summary>
|
|
386
|
+
private static string ProjectToolCatalogGeneration()
|
|
387
|
+
{
|
|
388
|
+
return Sha256Hex(Encoding.UTF8.GetBytes(EditorSessionId() + "\n" + ListenerId + "\n" + CurrentListenerGeneration().ToString(CultureInfo.InvariantCulture)));
|
|
329
389
|
}
|
|
330
390
|
|
|
331
|
-
|
|
391
|
+
/// <summary>generation变化时 continuation 必须稳定失败,不能混合两代目录。</summary>
|
|
392
|
+
private static void RequireProjectToolCatalogGeneration(ProjectToolCatalogArgs args, string generation)
|
|
332
393
|
{
|
|
333
|
-
if (
|
|
334
|
-
|
|
335
|
-
|
|
394
|
+
if (args != null && Utf8ByteCount(args.ExpectedCatalogGeneration) > 128)
|
|
395
|
+
throw new BridgeCommandException("E_SCHEMA_INVALID", "Catalog generation exceeds its bounded string limit.");
|
|
396
|
+
if (args != null && !string.IsNullOrEmpty(args.ExpectedCatalogGeneration)
|
|
397
|
+
&& !string.Equals(args.ExpectedCatalogGeneration, generation, StringComparison.Ordinal))
|
|
398
|
+
throw new BridgeCommandException("E_TOOL_CATALOG_CHANGED", "The Tool catalog generation changed before this continuation page was read.", null,
|
|
399
|
+
new[] { "Restart this discovery query from its first page against the current Unity generation." });
|
|
336
400
|
}
|
|
337
401
|
|
|
402
|
+
/// <summary>package-local names由CLI冻结并传入,Bridge在筛选前执行全registry ownership检查。</summary>
|
|
403
|
+
private static void RequireNoReservedProjectToolNames(UnityCliToolCatalogSnapshot catalog, ProjectToolCatalogArgs args)
|
|
404
|
+
{
|
|
405
|
+
var reservedNames = (args == null ? null : args.ReservedNames) ?? new string[0];
|
|
406
|
+
if (reservedNames.Length > 4096 || reservedNames.Any(name => string.IsNullOrWhiteSpace(name) || Utf8ByteCount(name) > 256))
|
|
407
|
+
throw new BridgeCommandException("E_SCHEMA_INVALID", "Catalog reserved names exceed their bounded item or string limits.");
|
|
408
|
+
var reserved = new HashSet<string>(reservedNames, StringComparer.Ordinal);
|
|
409
|
+
var conflict = catalog.Descriptors.Select(tool => tool.Name).FirstOrDefault(reserved.Contains);
|
|
410
|
+
if (conflict != null)
|
|
411
|
+
throw new BridgeCommandException("E_TOOL_SCHEMA_CONFLICT", "Tool catalog conflict for " + conflict + ": package-local and live registrations use the same name.",
|
|
412
|
+
new Dictionary<string, object> { { "toolName", conflict }, { "reason", "package-local and live registrations use the same name" } },
|
|
413
|
+
new[] { "Fix the duplicate Tool registration in the selected Unity project, then retry discovery." });
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
private static IEnumerable<RegisteredToolDescriptor> ProjectToolCatalogDefinitions(UnityCliToolCatalogSnapshot catalog, bool runtimeOnly)
|
|
417
|
+
{
|
|
418
|
+
return catalog.Descriptors.Where(definition => !runtimeOnly || definition.Target == "runtime" || definition.Target == "both");
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
private static void RequireProjectToolCatalogInputKeys(UnityCli.Protocol.BridgeRequest request, params string[] allowed)
|
|
422
|
+
{
|
|
423
|
+
var input = request.Input as IDictionary<string, object>;
|
|
424
|
+
if (input == null) throw new BridgeCommandException("E_SCHEMA_INVALID", "Catalog input must be an object.");
|
|
425
|
+
var keys = new HashSet<string>(allowed, StringComparer.Ordinal);
|
|
426
|
+
var unknown = input.Keys.FirstOrDefault(key => !keys.Contains(key));
|
|
427
|
+
if (unknown != null) throw new BridgeCommandException("E_SCHEMA_INVALID", "Catalog input contains unknown field " + unknown + ".");
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/// <summary>groups只读取一次published snapshot并返回group/count,不物化descriptor图。</summary>
|
|
431
|
+
private static object ProjectToolCatalogGroups(UnityCli.Protocol.BridgeRequest request, bool runtimeOnly)
|
|
432
|
+
{
|
|
433
|
+
RequireProjectToolCatalogInputKeys(request, "expectedCatalogGeneration", "reservedNames");
|
|
434
|
+
var catalog = ProjectToolCatalogSnapshotReader.Read();
|
|
435
|
+
var args = request.DeserializeArgs<ProjectToolCatalogArgs>() ?? new ProjectToolCatalogArgs();
|
|
436
|
+
RequireNoReservedProjectToolNames(catalog, args);
|
|
437
|
+
var generation = ProjectToolCatalogGeneration();
|
|
438
|
+
RequireProjectToolCatalogGeneration(args, generation);
|
|
439
|
+
var groups = ProjectToolCatalogDefinitions(catalog, runtimeOnly)
|
|
440
|
+
.GroupBy(definition => definition.Group, StringComparer.Ordinal)
|
|
441
|
+
.OrderBy(group => group.Key, StringComparer.Ordinal)
|
|
442
|
+
.Select(group => (object)new Dictionary<string, object> { { "name", group.Key }, { "toolCount", (long)group.Count() } })
|
|
443
|
+
.ToList();
|
|
444
|
+
if (groups.Count > 4096)
|
|
445
|
+
throw new BridgeCommandException("E_TOOL_REGISTRY_UNHEALTHY", "Tool catalog exceeds the bounded group count.", null,
|
|
446
|
+
new[] { "Reduce invalid project Tool group fragmentation, then let Unity reload the domain." });
|
|
447
|
+
var data = new Dictionary<string, object> { { "catalogGeneration", generation }, { "groups", groups } };
|
|
448
|
+
RequireCatalogDataBudget(data, 2 * 1024 * 1024, "Tool catalog groups exceed the bounded UTF-8 transport size.");
|
|
449
|
+
return data;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/// <summary>summary由目标domain签发opaque continuation;CLI不持有可编辑边界。</summary>
|
|
453
|
+
private static object ProjectToolCatalogSummary(UnityCli.Protocol.BridgeRequest request, bool runtimeOnly)
|
|
454
|
+
{
|
|
455
|
+
RequireProjectToolCatalogInputKeys(request, "group", "keyword", "limit", "expectedCatalogGeneration", "reservedNames",
|
|
456
|
+
"localNames", "cursorContext", "cursorToken");
|
|
457
|
+
var summaryInput = request.Input as IDictionary<string, object>;
|
|
458
|
+
if (!summaryInput.ContainsKey("reservedNames") || !summaryInput.ContainsKey("localNames") || !summaryInput.ContainsKey("cursorContext"))
|
|
459
|
+
throw new BridgeCommandException("E_SCHEMA_INVALID", "Catalog summary input is missing its cursor context fields.");
|
|
460
|
+
var catalog = ProjectToolCatalogSnapshotReader.Read();
|
|
461
|
+
var args = request.DeserializeArgs<ProjectToolCatalogArgs>() ?? new ProjectToolCatalogArgs();
|
|
462
|
+
if (args.Limit < 1 || args.Limit > 101)
|
|
463
|
+
throw new BridgeCommandException("E_SCHEMA_INVALID", "Catalog summary limit must be an integer in [1,101].");
|
|
464
|
+
RequireNoReservedProjectToolNames(catalog, args);
|
|
465
|
+
var generation = ProjectToolCatalogGeneration();
|
|
466
|
+
RequireProjectToolCatalogGeneration(args, generation);
|
|
467
|
+
if (Utf8ByteCount(args.Group) > 256 || Utf8ByteCount(args.Keyword) > 2048)
|
|
468
|
+
throw new BridgeCommandException("E_SCHEMA_INVALID", "Catalog summary filters exceed their bounded UTF-8 string limits.");
|
|
469
|
+
var tokens = (args.Keyword ?? string.Empty).Split('|').Select(token => token.Trim()).Where(token => token.Length > 0).ToArray();
|
|
470
|
+
if (tokens.Length > 16 || tokens.Any(token => Utf8ByteCount(token) > 128))
|
|
471
|
+
throw new BridgeCommandException("E_SCHEMA_INVALID", "Catalog search accepts at most 16 keywords of at most 128 UTF-8 bytes each.");
|
|
472
|
+
var matches = ProjectToolCatalogDefinitions(catalog, runtimeOnly)
|
|
473
|
+
.Where(definition => string.IsNullOrEmpty(args.Group) || string.Equals(definition.Group, args.Group, StringComparison.Ordinal))
|
|
474
|
+
.Where(definition => tokens.Length == 0 || tokens.Any(token => ProjectToolCatalogMatches(definition, token)))
|
|
475
|
+
.OrderBy(definition => definition.Name, StringComparer.Ordinal)
|
|
476
|
+
.ToList();
|
|
477
|
+
var nativeNames = new HashSet<string>(catalog.Descriptors.Where(definition => definition.ContractProfile != "official-attribute").Select(definition => definition.Name), StringComparer.Ordinal);
|
|
478
|
+
foreach (var reservedName in args.ReservedNames ?? new string[0]) nativeNames.Add(reservedName);
|
|
479
|
+
|
|
480
|
+
var localNames = ValidateCatalogCursorLocalNames(args);
|
|
481
|
+
var scope = ProjectToolCatalogCursorScope(generation, args, runtimeOnly, localNames);
|
|
482
|
+
int offset;
|
|
483
|
+
string boundary;
|
|
484
|
+
ReadProjectToolCatalogCursor(args.CursorToken, scope, out offset, out boundary);
|
|
485
|
+
var combinedNames = localNames.Concat(matches.Select(definition => definition.Name)).Distinct(StringComparer.Ordinal).OrderBy(name => name, StringComparer.Ordinal).ToList();
|
|
486
|
+
if (offset > combinedNames.Count || offset > 0 && !string.Equals(combinedNames[offset - 1], boundary, StringComparison.Ordinal))
|
|
487
|
+
throw InvalidProjectToolCatalogCursor();
|
|
488
|
+
var pageNames = combinedNames.Skip(offset).Take(args.Limit).ToList();
|
|
489
|
+
var pageSet = new HashSet<string>(pageNames, StringComparer.Ordinal);
|
|
490
|
+
var tools = matches.Where(definition => pageSet.Contains(definition.Name))
|
|
491
|
+
.Select(definition => (object)ProjectToolRoutingSummaryData(definition, runtimeOnly, nativeNames)).ToList();
|
|
492
|
+
return BoundSignedCatalogSummaryData(generation, matches.Count, combinedNames, offset, pageNames, tools, scope);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/// <summary>签名页在序列化预算内同步裁剪pageNames与live summaries,token指向真实末项。</summary>
|
|
496
|
+
private static object BoundSignedCatalogSummaryData(
|
|
497
|
+
string generation, int liveTotal, List<string> combinedNames, int offset, List<string> pageNames,
|
|
498
|
+
List<object> tools, string scope)
|
|
499
|
+
{
|
|
500
|
+
Dictionary<string, object> data;
|
|
501
|
+
while (true)
|
|
502
|
+
{
|
|
503
|
+
var end = offset + pageNames.Count;
|
|
504
|
+
var boundary = end > 0 ? combinedNames[end - 1] : string.Empty;
|
|
505
|
+
data = new Dictionary<string, object>
|
|
506
|
+
{
|
|
507
|
+
{ "catalogGeneration", generation }, { "total", (long)liveTotal },
|
|
508
|
+
{ "hasMore", end < combinedNames.Count }, { "tools", tools },
|
|
509
|
+
{ "pageNames", pageNames.Cast<object>().ToList() },
|
|
510
|
+
{ "continuationToken", SignProjectToolCatalogCursor(scope, end, boundary) }
|
|
511
|
+
};
|
|
512
|
+
if (CatalogDataUtf8Bytes(data) <= 256 * 1024) return data;
|
|
513
|
+
if (pageNames.Count == 0)
|
|
514
|
+
throw new BridgeCommandException("E_TOOL_REGISTRY_UNHEALTHY", "Tool catalog summary cannot fit the bounded UTF-8 transport size.");
|
|
515
|
+
var removed = pageNames[pageNames.Count - 1];
|
|
516
|
+
pageNames.RemoveAt(pageNames.Count - 1);
|
|
517
|
+
var toolIndex = tools.FindLastIndex(value => string.Equals(((Dictionary<string, object>)value)["name"] as string, removed, StringComparison.Ordinal));
|
|
518
|
+
if (toolIndex >= 0) tools.RemoveAt(toolIndex);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/// <summary>校验CLI提供的已筛选local名称只来自reserved ownership集合。</summary>
|
|
523
|
+
private static List<string> ValidateCatalogCursorLocalNames(ProjectToolCatalogArgs args)
|
|
524
|
+
{
|
|
525
|
+
var values = (args.LocalNames ?? new string[0]).ToList();
|
|
526
|
+
if (values.Count > 4096 || values.Any(value => string.IsNullOrWhiteSpace(value) || Utf8ByteCount(value) > 256)
|
|
527
|
+
|| !values.SequenceEqual(values.Distinct(StringComparer.Ordinal).OrderBy(value => value, StringComparer.Ordinal)))
|
|
528
|
+
throw new BridgeCommandException("E_SCHEMA_INVALID", "Catalog local names must be a bounded ordinally sorted set.");
|
|
529
|
+
var reserved = new HashSet<string>(args.ReservedNames ?? new string[0], StringComparer.Ordinal);
|
|
530
|
+
if (values.Any(value => !reserved.Contains(value)))
|
|
531
|
+
throw new BridgeCommandException("E_SCHEMA_INVALID", "Catalog local names must be a subset of reserved package-local names.");
|
|
532
|
+
if (Utf8ByteCount(args.CursorContext) != 64 || args.CursorContext.Any(value => !Uri.IsHexDigit(value)))
|
|
533
|
+
throw new BridgeCommandException("E_SCHEMA_INVALID", "Catalog cursor context is invalid.");
|
|
534
|
+
return values;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/// <summary>把目标generation、query和local投影视图绑定到签名scope。</summary>
|
|
538
|
+
private static string ProjectToolCatalogCursorScope(string generation, ProjectToolCatalogArgs args, bool runtimeOnly, List<string> localNames)
|
|
539
|
+
{
|
|
540
|
+
return Sha256Hex(Encoding.UTF8.GetBytes(generation + "\n" + (runtimeOnly ? "runtime" : "editor") + "\n" + args.CursorContext + "\n"
|
|
541
|
+
+ (args.Group ?? string.Empty) + "\n" + (args.Keyword ?? string.Empty) + "\n"
|
|
542
|
+
+ string.Join("\n", localNames) + "\n--reserved--\n" + string.Join("\n", args.ReservedNames ?? new string[0])));
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/// <summary>验证opaque token并恢复目标签发的序号与前页边界。</summary>
|
|
546
|
+
private static void ReadProjectToolCatalogCursor(string token, string scope, out int offset, out string boundary)
|
|
547
|
+
{
|
|
548
|
+
offset = 0;
|
|
549
|
+
boundary = string.Empty;
|
|
550
|
+
if (string.IsNullOrEmpty(token)) return;
|
|
551
|
+
if (Utf8ByteCount(token) > 8192) throw InvalidProjectToolCatalogCursor();
|
|
552
|
+
try
|
|
553
|
+
{
|
|
554
|
+
var parts = token.Split('.');
|
|
555
|
+
if (parts.Length != 2) throw InvalidProjectToolCatalogCursor();
|
|
556
|
+
var payloadBytes = FromBase64Url(parts[0]);
|
|
557
|
+
var signature = FromBase64Url(parts[1]);
|
|
558
|
+
byte[] expected;
|
|
559
|
+
using (var hmac = new HMACSHA256(projectToolCatalogCursorSecret)) expected = hmac.ComputeHash(Encoding.UTF8.GetBytes(parts[0]));
|
|
560
|
+
if (!FixedTimeEqual(signature, expected)) throw InvalidProjectToolCatalogCursor();
|
|
561
|
+
var fields = Encoding.UTF8.GetString(payloadBytes).Split('\n');
|
|
562
|
+
int parsedOffset;
|
|
563
|
+
if (fields.Length != 3 || !string.Equals(fields[0], scope, StringComparison.Ordinal)
|
|
564
|
+
|| !int.TryParse(fields[1], NumberStyles.None, CultureInfo.InvariantCulture, out parsedOffset) || parsedOffset < 0)
|
|
565
|
+
throw InvalidProjectToolCatalogCursor();
|
|
566
|
+
offset = parsedOffset;
|
|
567
|
+
boundary = Encoding.UTF8.GetString(Convert.FromBase64String(fields[2]));
|
|
568
|
+
if (Utf8ByteCount(boundary) > 256 || (offset == 0) != string.IsNullOrEmpty(boundary))
|
|
569
|
+
throw InvalidProjectToolCatalogCursor();
|
|
570
|
+
}
|
|
571
|
+
catch (BridgeCommandException) { throw; }
|
|
572
|
+
catch { throw InvalidProjectToolCatalogCursor(); }
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/// <summary>使用当前domain secret签发不可由CLI改写的continuation。</summary>
|
|
576
|
+
private static string SignProjectToolCatalogCursor(string scope, int offset, string boundary)
|
|
577
|
+
{
|
|
578
|
+
var payloadText = scope + "\n" + offset.ToString(CultureInfo.InvariantCulture) + "\n"
|
|
579
|
+
+ Convert.ToBase64String(Encoding.UTF8.GetBytes(boundary ?? string.Empty));
|
|
580
|
+
var payload = ToBase64Url(Encoding.UTF8.GetBytes(payloadText));
|
|
581
|
+
byte[] signature;
|
|
582
|
+
using (var hmac = new HMACSHA256(projectToolCatalogCursorSecret)) signature = hmac.ComputeHash(Encoding.UTF8.GetBytes(payload));
|
|
583
|
+
return payload + "." + ToBase64Url(signature);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/// <summary>统一返回可机器分支的cursor拒绝错误。</summary>
|
|
587
|
+
private static BridgeCommandException InvalidProjectToolCatalogCursor()
|
|
588
|
+
{
|
|
589
|
+
return new BridgeCommandException("E_DISCOVERY_CURSOR_INVALID", "The target rejected an altered or cross-query discovery continuation.", null,
|
|
590
|
+
new[] { "Restart this exact discovery query from its first page." });
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
/// <summary>secret只活在当前domain内,reload后旧token自然失效。</summary>
|
|
594
|
+
private static byte[] CreateProjectToolCatalogCursorSecret()
|
|
595
|
+
{
|
|
596
|
+
var bytes = new byte[32];
|
|
597
|
+
using (var random = RandomNumberGenerator.Create()) random.GetBytes(bytes);
|
|
598
|
+
return bytes;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/// <summary>编码URL安全token片段。</summary>
|
|
602
|
+
private static string ToBase64Url(byte[] value)
|
|
603
|
+
{
|
|
604
|
+
return Convert.ToBase64String(value).TrimEnd('=').Replace('+', '-').Replace('/', '_');
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/// <summary>解码URL安全token片段。</summary>
|
|
608
|
+
private static byte[] FromBase64Url(string value)
|
|
609
|
+
{
|
|
610
|
+
var normalized = value.Replace('-', '+').Replace('_', '/');
|
|
611
|
+
normalized = normalized.PadRight(normalized.Length + (4 - normalized.Length % 4) % 4, '=');
|
|
612
|
+
return Convert.FromBase64String(normalized);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/// <summary>常量步数比较签名,避免提前返回泄露首个差异位置。</summary>
|
|
616
|
+
private static bool FixedTimeEqual(byte[] left, byte[] right)
|
|
617
|
+
{
|
|
618
|
+
if (left == null || right == null || left.Length != right.Length) return false;
|
|
619
|
+
var difference = 0;
|
|
620
|
+
for (var index = 0; index < left.Length; index++) difference |= left[index] ^ right[index];
|
|
621
|
+
return difference == 0;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/// <summary>所有catalog string bound统一按wire UTF-8字节计算。</summary>
|
|
625
|
+
private static int Utf8ByteCount(string value)
|
|
626
|
+
{
|
|
627
|
+
return Encoding.UTF8.GetByteCount(value ?? string.Empty);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
/// <summary>以真实Bridge serializer计算完整data大小。</summary>
|
|
631
|
+
private static int CatalogDataUtf8Bytes(object data)
|
|
632
|
+
{
|
|
633
|
+
return Encoding.UTF8.GetByteCount(UnityCliSerializer.Serialize(data));
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
/// <summary>完整data超出consumer预算时在返回前失败。</summary>
|
|
637
|
+
private static void RequireCatalogDataBudget(object data, int maximumBytes, string message)
|
|
638
|
+
{
|
|
639
|
+
if (CatalogDataUtf8Bytes(data) > maximumBytes) throw new BridgeCommandException("E_TOOL_REGISTRY_UNHEALTHY", message);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
private static bool ProjectToolCatalogMatches(RegisteredToolDescriptor definition, string token)
|
|
643
|
+
{
|
|
644
|
+
return (definition.Name ?? string.Empty).IndexOf(token, StringComparison.OrdinalIgnoreCase) >= 0
|
|
645
|
+
|| (definition.Summary ?? string.Empty).IndexOf(token, StringComparison.OrdinalIgnoreCase) >= 0
|
|
646
|
+
|| (definition.Description ?? string.Empty).IndexOf(token, StringComparison.OrdinalIgnoreCase) >= 0
|
|
647
|
+
|| (definition.Group ?? string.Empty).IndexOf(token, StringComparison.OrdinalIgnoreCase) >= 0;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/// <summary>exact describe只读取一次published snapshot;native未命中时可返回一份指定fallback descriptor。</summary>
|
|
651
|
+
private static object ProjectToolCatalogDescribe(UnityCli.Protocol.BridgeRequest request, bool runtimeOnly)
|
|
652
|
+
{
|
|
653
|
+
if (runtimeOnly) RequireProjectToolCatalogInputKeys(request, "name");
|
|
654
|
+
else RequireProjectToolCatalogInputKeys(request, "name", "fallbackName");
|
|
655
|
+
var catalog = ProjectToolCatalogSnapshotReader.Read();
|
|
656
|
+
var args = request.DeserializeArgs<ProjectToolCatalogArgs>() ?? new ProjectToolCatalogArgs();
|
|
657
|
+
if (string.IsNullOrWhiteSpace(args.Name) || Utf8ByteCount(args.Name) > 256)
|
|
658
|
+
throw new BridgeCommandException("E_SCHEMA_INVALID", "Catalog describe name is required and must not exceed 256 UTF-8 bytes.");
|
|
659
|
+
if (args.FallbackName != null && (string.IsNullOrWhiteSpace(args.FallbackName) || Utf8ByteCount(args.FallbackName) > 256
|
|
660
|
+
|| string.Equals(args.Name, args.FallbackName, StringComparison.Ordinal)))
|
|
661
|
+
throw new BridgeCommandException("E_SCHEMA_INVALID", "Catalog describe fallbackName must be distinct and must not exceed 256 UTF-8 bytes.");
|
|
662
|
+
var generation = ProjectToolCatalogGeneration();
|
|
663
|
+
Func<RegisteredToolDescriptor, bool> availableForTarget = tool => !runtimeOnly || tool.Target == "runtime" || tool.Target == "both";
|
|
664
|
+
var definition = catalog.Descriptors.FirstOrDefault(tool => string.Equals(tool.Name, args.Name, StringComparison.Ordinal) && availableForTarget(tool));
|
|
665
|
+
if (definition == null && args.FallbackName != null)
|
|
666
|
+
definition = catalog.Descriptors.FirstOrDefault(tool => string.Equals(tool.Name, args.FallbackName, StringComparison.Ordinal) && availableForTarget(tool));
|
|
667
|
+
var found = definition != null;
|
|
668
|
+
var result = new Dictionary<string, object> { { "catalogGeneration", generation }, { "found", found } };
|
|
669
|
+
if (found)
|
|
670
|
+
{
|
|
671
|
+
result["descriptor"] = ProjectToolDescriptorData(definition);
|
|
672
|
+
}
|
|
673
|
+
if (CatalogDataUtf8Bytes(result) > 3 * 1024 * 1024)
|
|
674
|
+
throw new BridgeCommandException("E_TOOL_REGISTRY_UNHEALTHY", "Exact Tool descriptor exceeds the bounded UTF-8 transport size.", null,
|
|
675
|
+
new[] { "Reduce the Tool schema or examples, then let Unity reload the domain." });
|
|
676
|
+
return result;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
private static Dictionary<string, object> ProjectToolRoutingSummaryData(RegisteredToolDescriptor definition, bool runtimeOnly, HashSet<string> nativeNames)
|
|
680
|
+
{
|
|
681
|
+
var commandName = definition.Name;
|
|
682
|
+
if (definition.ContractProfile == "official-attribute" && definition.Name.StartsWith(OfficialAttributeToolPrefix, StringComparison.Ordinal))
|
|
683
|
+
{
|
|
684
|
+
var originalName = definition.Name.Substring(OfficialAttributeToolPrefix.Length);
|
|
685
|
+
if (!nativeNames.Contains(originalName)) commandName = originalName;
|
|
686
|
+
}
|
|
687
|
+
var available = ProjectToolAvailableForCatalog(definition, runtimeOnly);
|
|
688
|
+
var data = new Dictionary<string, object>
|
|
689
|
+
{
|
|
690
|
+
{ "name", definition.Name }, { "summary", definition.Summary }, { "group", definition.Group },
|
|
691
|
+
{ "target", definition.Target }, { "contractProfile", definition.ContractProfile },
|
|
692
|
+
{ "commandName", commandName }
|
|
693
|
+
};
|
|
694
|
+
if (!available)
|
|
695
|
+
{
|
|
696
|
+
data["available"] = false;
|
|
697
|
+
data["unavailableReason"] = ProjectToolUnavailableForCatalog(definition, runtimeOnly);
|
|
698
|
+
}
|
|
699
|
+
return data;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
private static bool ProjectToolAvailableForCatalog(RegisteredToolDescriptor definition, bool runtimeOnly)
|
|
703
|
+
{
|
|
704
|
+
return runtimeOnly ? playModeReady : ToolAvailableNow(definition);
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
private static string ProjectToolUnavailableForCatalog(RegisteredToolDescriptor definition, bool runtimeOnly)
|
|
708
|
+
{
|
|
709
|
+
return runtimeOnly && !playModeReady
|
|
710
|
+
? "E_RUNTIME_NOT_READY: Enter Play Mode before executing this Tool."
|
|
711
|
+
: ToolUnavailableReason(definition);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
private static bool IsProjectToolCatalogReadCommand(string command)
|
|
715
|
+
{
|
|
716
|
+
return command == "project-tool.health" || command == "project-tool.groups" || command == "project-tool.summary" || command == "project-tool.describe" || command == "project-tool.list"
|
|
717
|
+
|| command == "runtime-tool.groups" || command == "runtime-tool.summary" || command == "runtime-tool.describe" || command == "runtime-tool.list";
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
private static object ProjectToolGroups(UnityCli.Protocol.BridgeRequest request) { return ProjectToolCatalogGroups(request, false); }
|
|
721
|
+
private static object ProjectToolSummary(UnityCli.Protocol.BridgeRequest request) { return ProjectToolCatalogSummary(request, false); }
|
|
722
|
+
private static object ProjectToolDescribe(UnityCli.Protocol.BridgeRequest request) { return ProjectToolCatalogDescribe(request, false); }
|
|
723
|
+
private static object RuntimeToolGroups(UnityCli.Protocol.BridgeRequest request) { InitializeRuntimeExecutor(); return ProjectToolCatalogGroups(request, true); }
|
|
724
|
+
private static object RuntimeToolSummary(UnityCli.Protocol.BridgeRequest request) { InitializeRuntimeExecutor(); return ProjectToolCatalogSummary(request, true); }
|
|
725
|
+
private static object RuntimeToolDescribe(UnityCli.Protocol.BridgeRequest request) { InitializeRuntimeExecutor(); return ProjectToolCatalogDescribe(request, true); }
|
|
726
|
+
|
|
727
|
+
/// <summary>完整descriptor导出仅供显式doctor/static capture,不进入公开discovery。</summary>
|
|
338
728
|
private static object ProjectToolList(UnityCli.Protocol.BridgeRequest request)
|
|
339
729
|
{
|
|
340
|
-
|
|
730
|
+
var catalog = ReadPublishedProjectToolCatalogSnapshot();
|
|
341
731
|
return new Dictionary<string, object>
|
|
342
732
|
{
|
|
343
|
-
{ "tools",
|
|
733
|
+
{ "tools", catalog.Descriptors.Select(ProjectToolDescriptorData).ToList() },
|
|
344
734
|
{ "logPath", LogPath() }
|
|
345
735
|
};
|
|
346
736
|
}
|
|
@@ -349,33 +739,21 @@ namespace UnityCli
|
|
|
349
739
|
{
|
|
350
740
|
public string Name { get; set; }
|
|
351
741
|
[UnityCliAnyJson] public object Input { get; set; }
|
|
352
|
-
public string ExpectedDescriptorFingerprint { get; set; }
|
|
353
742
|
public string ArtifactDir { get; set; }
|
|
354
743
|
public string ProjectPath { get; set; }
|
|
355
744
|
}
|
|
356
745
|
|
|
357
746
|
private static async ValueTask<object> ProjectToolRun(UnityCli.Protocol.BridgeRequest request, CancellationToken cancellationToken)
|
|
358
747
|
{
|
|
359
|
-
|
|
748
|
+
var catalog = ReadPublishedProjectToolCatalog();
|
|
360
749
|
var args = request.DeserializeArgs<ProjectToolRunArgs>();
|
|
361
750
|
var name = args.Name;
|
|
362
|
-
if (string.IsNullOrEmpty(name) || !
|
|
751
|
+
if (string.IsNullOrEmpty(name) || !catalog.ExecutableTools.ContainsKey(name))
|
|
363
752
|
{
|
|
364
753
|
throw new BridgeCommandException("E_TOOL_NOT_FOUND", "Tool not found: " + name, "Run unitycli tools group --instance <id|projectPath|alias> or tools search <keyword> to inspect the connected instance catalog.");
|
|
365
754
|
}
|
|
366
755
|
|
|
367
|
-
var definition =
|
|
368
|
-
// 门禁前刷新 live handler+descriptor 指纹,旧 discovery proof 在 domain reload/descriptor 变化后必须失效。
|
|
369
|
-
var fingerprintCheck = RefreshProjectToolFingerprint(definition);
|
|
370
|
-
if (!string.IsNullOrEmpty(args.ExpectedDescriptorFingerprint)
|
|
371
|
-
&& !string.Equals(args.ExpectedDescriptorFingerprint, definition.Fingerprint, StringComparison.Ordinal))
|
|
372
|
-
{
|
|
373
|
-
throw new BridgeCommandException(
|
|
374
|
-
"E_TOOL_DESCRIPTOR_CHANGED",
|
|
375
|
-
"The Tool descriptor changed after discovery: " + name,
|
|
376
|
-
new Dictionary<string, object> { { "toolName", name }, { "expectedFingerprint", args.ExpectedDescriptorFingerprint }, { "actualFingerprint", definition.Fingerprint } },
|
|
377
|
-
new[] { "Run tools describe again against the selected Unity generation before constructing a new request." });
|
|
378
|
-
}
|
|
756
|
+
var definition = catalog.ExecutableTools[name];
|
|
379
757
|
if (EditorApplication.isPlaying && string.Equals(definition.PlayModeSupport, "editor-only", StringComparison.OrdinalIgnoreCase))
|
|
380
758
|
{
|
|
381
759
|
throw new BridgeCommandException("E_TOOL_PLAYMODE_UNAVAILABLE",
|
|
@@ -410,11 +788,6 @@ namespace UnityCli
|
|
|
410
788
|
new[] { "Use runtime-tool.run <name> or tools run <name> with a runtime target to execute this tool in Play Mode." });
|
|
411
789
|
}
|
|
412
790
|
var dryRun = request.Execution != null && request.Execution.DryRun == true;
|
|
413
|
-
if (fingerprintCheck.Blocked)
|
|
414
|
-
{
|
|
415
|
-
WriteLog("custom_tool_fingerprint_blocked requestId=" + request.RequestId + " name=" + name + " expected=" + fingerprintCheck.Expected + " actual=" + fingerprintCheck.Actual);
|
|
416
|
-
throw new BridgeCommandException("E_TOOL_FINGERPRINT_MISMATCH", fingerprintCheck.Message, ProjectToolFingerprintDetails(definition), ProjectToolFingerprintRePinSteps(definition));
|
|
417
|
-
}
|
|
418
791
|
var timeoutMs = request.ExecutionTimeoutMs(definition.DefaultTimeoutMs, definition.MaxTimeoutMs);
|
|
419
792
|
|
|
420
793
|
var inputJson = request.RawJsonArg("input") ?? "{}";
|
|
@@ -425,11 +798,11 @@ namespace UnityCli
|
|
|
425
798
|
object result;
|
|
426
799
|
object parsedInput;
|
|
427
800
|
UnityCliProjectToolContext executionContext = null;
|
|
801
|
+
using (var toolTimeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
|
|
802
|
+
{
|
|
428
803
|
try
|
|
429
804
|
{
|
|
430
805
|
WriteLog("custom_tool_run requestId=" + request.RequestId + " name=" + name + " timeoutMs=" + timeoutMs.ToString(CultureInfo.InvariantCulture));
|
|
431
|
-
using (var toolTimeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
|
|
432
|
-
{
|
|
433
806
|
toolTimeout.CancelAfter(timeoutMs);
|
|
434
807
|
parsedInput = UnityCliSerializer.Deserialize(inputJson, definition.RequestType);
|
|
435
808
|
var effectPreparation = PrepareToolEffect(request, definition.CompletionContract, parsedInput);
|
|
@@ -448,7 +821,6 @@ namespace UnityCli
|
|
|
448
821
|
result = await definition.InvokeAsync(parsedInput, executionContext, toolTimeout.Token);
|
|
449
822
|
if (toolTimeout.IsCancellationRequested && !executionContext.EffectCommitted)
|
|
450
823
|
throw new OperationCanceledException(toolTimeout.Token);
|
|
451
|
-
}
|
|
452
824
|
}
|
|
453
825
|
catch (BridgeCommandException bce)
|
|
454
826
|
{
|
|
@@ -480,6 +852,12 @@ namespace UnityCli
|
|
|
480
852
|
throw PostEffectExecutionTimeout(name, "invoke", ProjectToolFailureReason(definition, inner.GetType().Name + ": " + inner.Message));
|
|
481
853
|
throw new BridgeCommandException("E_TOOL_EXECUTION_FAILED", inner.GetType().Name + ": " + inner.Message, "Check Unity Console and UnityCliBridge.log with the returned requestId.");
|
|
482
854
|
}
|
|
855
|
+
catch (UnityCliRequestBudgetExceededException exception)
|
|
856
|
+
{
|
|
857
|
+
if (executionContext != null && executionContext.EffectCommitted)
|
|
858
|
+
throw PostEffectExecutionTimeout(name, "request-budget", exception.Message);
|
|
859
|
+
throw RequestBudgetExceeded(name, exception.Dimension);
|
|
860
|
+
}
|
|
483
861
|
catch (OperationCanceledException)
|
|
484
862
|
{
|
|
485
863
|
if (executionContext != null && executionContext.EffectCommitted)
|
|
@@ -496,7 +874,10 @@ namespace UnityCli
|
|
|
496
874
|
// method 返回后仍可能在序列化 getter、schema 或 completion driver 中失败;整个 postcondition 区域必须服从同一 effect 边界。
|
|
497
875
|
try
|
|
498
876
|
{
|
|
499
|
-
var
|
|
877
|
+
var terminalBudget = executionContext.ReadBudget ?? executionContext.UseReadBudget();
|
|
878
|
+
terminalBudget.CheckpointOrThrow();
|
|
879
|
+
var outputJson = UnityCliSerializer.Serialize(result, terminalBudget, toolTimeout.Token);
|
|
880
|
+
terminalBudget.CheckpointOrThrow();
|
|
500
881
|
var outputError = ValidateProjectToolOutput(definition, outputJson);
|
|
501
882
|
if (outputError != null)
|
|
502
883
|
{
|
|
@@ -506,9 +887,14 @@ namespace UnityCli
|
|
|
506
887
|
new Dictionary<string, object> { { "toolName", definition.Name }, { "path", outputError.Path }, { "expected", outputError.Expected }, { "actual", outputError.Actual } },
|
|
507
888
|
new[] { "Fix the Tool result DTO or completion driver before retrying." });
|
|
508
889
|
}
|
|
890
|
+
terminalBudget.CheckpointOrThrow();
|
|
509
891
|
var terminalData = CompleteProjectToolSuccess(request, definition, result);
|
|
892
|
+
terminalBudget.CheckpointOrThrow();
|
|
510
893
|
RecordToolCompletionEvidence(request, definition, result, terminalData);
|
|
511
|
-
|
|
894
|
+
terminalBudget.CheckpointOrThrow();
|
|
895
|
+
var terminalJson = SerializeTerminalSuccess(request.RequestId, terminalData, terminalBudget, toolTimeout.Token);
|
|
896
|
+
terminalBudget.CheckpointOrThrow();
|
|
897
|
+
return new SerializedBridgeEnvelope(terminalJson);
|
|
512
898
|
}
|
|
513
899
|
catch (BridgeCommandException exception)
|
|
514
900
|
{
|
|
@@ -517,6 +903,12 @@ namespace UnityCli
|
|
|
517
903
|
throw PostEffectExecutionTimeout(name, "postcondition", ProjectToolFailureReason(definition, exception.Code + ": " + exception.Message));
|
|
518
904
|
throw;
|
|
519
905
|
}
|
|
906
|
+
catch (UnityCliRequestBudgetExceededException exception)
|
|
907
|
+
{
|
|
908
|
+
if (executionContext != null && executionContext.EffectCommitted)
|
|
909
|
+
throw PostEffectExecutionTimeout(name, "request-budget", exception.Message);
|
|
910
|
+
throw RequestBudgetExceeded(name, exception.Dimension);
|
|
911
|
+
}
|
|
520
912
|
catch (Exception exception)
|
|
521
913
|
{
|
|
522
914
|
if (executionContext != null && executionContext.EffectCommitted)
|
|
@@ -527,6 +919,34 @@ namespace UnityCli
|
|
|
527
919
|
new Dictionary<string, object> { { "toolName", definition.Name }, { "stage", "postcondition" } },
|
|
528
920
|
new[] { "Fix the Tool result DTO or completion driver before retrying." });
|
|
529
921
|
}
|
|
922
|
+
}
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
/// <summary>最终 envelope 的自描述字节数最多经过8次定点收敛,且每次都服从同一 deadline 与预算。</summary>
|
|
926
|
+
internal static string SerializeTerminalSuccess(string requestId, object terminalData, UnityCliRequestBudget budget, CancellationToken cancellationToken)
|
|
927
|
+
{
|
|
928
|
+
if (budget == null) throw new ArgumentNullException("budget");
|
|
929
|
+
for (var attempt = 0; attempt < 8; attempt++)
|
|
930
|
+
{
|
|
931
|
+
budget.CheckpointOrThrow();
|
|
932
|
+
long actualBytes;
|
|
933
|
+
var json = UnityCliSerializer.Serialize(
|
|
934
|
+
UnityCli.Protocol.ProtocolEnvelopeFactory.Success(requestId, terminalData),
|
|
935
|
+
budget,
|
|
936
|
+
cancellationToken,
|
|
937
|
+
out actualBytes);
|
|
938
|
+
if (actualBytes == budget.ActualSerializedUtf8Bytes) return json;
|
|
939
|
+
budget.ObserveActualSerializedUtf8Bytes(actualBytes);
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
throw new InvalidOperationException("Final envelope UTF-8 byte accounting did not converge.");
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/// <summary>成功 Tool 已在自身 deadline 与预算内完成最终 envelope 序列化。</summary>
|
|
946
|
+
private sealed class SerializedBridgeEnvelope
|
|
947
|
+
{
|
|
948
|
+
internal SerializedBridgeEnvelope(string json) { Json = json; }
|
|
949
|
+
internal string Json { get; private set; }
|
|
530
950
|
}
|
|
531
951
|
|
|
532
952
|
/// <summary>内部 raw discovery 需要 completion contract;公开投影与 static capture 会移除此字段。</summary>
|
|
@@ -546,6 +966,31 @@ namespace UnityCli
|
|
|
546
966
|
};
|
|
547
967
|
}
|
|
548
968
|
|
|
969
|
+
private static Dictionary<string, object> CompletionContractData(PublishedToolCompletionContract contract)
|
|
970
|
+
{
|
|
971
|
+
return new Dictionary<string, object>
|
|
972
|
+
{
|
|
973
|
+
{ "executionClass", contract.ExecutionClass },
|
|
974
|
+
{ "completionPredicate", contract.CompletionPredicate },
|
|
975
|
+
{ "proofSource", contract.ProofSource },
|
|
976
|
+
{ "reloadPolicy", contract.ReloadPolicy },
|
|
977
|
+
{ "timeoutError", contract.TimeoutError },
|
|
978
|
+
{ "outcomeUnknownPolicy", contract.OutcomeUnknownPolicy },
|
|
979
|
+
{ "completionDriverId", contract.CompletionDriverId },
|
|
980
|
+
{ "journalPolicy", contract.JournalPolicy },
|
|
981
|
+
{ "mutation", contract.Mutation }
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
private static BridgeCommandException RequestBudgetExceeded(string toolName, string dimension)
|
|
986
|
+
{
|
|
987
|
+
return new BridgeCommandException(
|
|
988
|
+
"E_REQUEST_BUDGET_EXCEEDED",
|
|
989
|
+
"Tool " + toolName + " exhausted its request-wide Unity main-thread budget before a complete result was available.",
|
|
990
|
+
new Dictionary<string, object> { { "toolName", toolName }, { "dimension", dimension } },
|
|
991
|
+
new[] { "Retry with a narrower selector or continue from the returned continuation when available." });
|
|
992
|
+
}
|
|
993
|
+
|
|
549
994
|
/// <summary>effect 后无法确认终态的异常统一收敛为执行超时。</summary>
|
|
550
995
|
private static BridgeCommandException PostEffectExecutionTimeout(string toolName, string stage, string reason)
|
|
551
996
|
{
|
|
@@ -556,7 +1001,7 @@ namespace UnityCli
|
|
|
556
1001
|
new[] { "Inspect the affected business state before submitting a new request." });
|
|
557
1002
|
}
|
|
558
1003
|
|
|
559
|
-
private static Dictionary<string, object> ProjectToolDescriptorData(
|
|
1004
|
+
private static Dictionary<string, object> ProjectToolDescriptorData(RegisteredToolDescriptor definition)
|
|
560
1005
|
{
|
|
561
1006
|
var data = new Dictionary<string, object>
|
|
562
1007
|
{
|
|
@@ -567,22 +1012,21 @@ namespace UnityCli
|
|
|
567
1012
|
{ "supportsDryRun", definition.SupportsDryRun },
|
|
568
1013
|
{ "contractProfile", definition.ContractProfile },
|
|
569
1014
|
{ "target", definition.Target }, { "availabilityScope", definition.AvailabilityScope }, { "playModeSupport", definition.PlayModeSupport },
|
|
570
|
-
{ "examples", definition.
|
|
1015
|
+
{ "examples", definition.CloneExamples() },
|
|
571
1016
|
{ "defaultTimeoutMs", definition.DefaultTimeoutMs },
|
|
572
1017
|
{ "completionContract", CompletionContractData(definition.CompletionContract) },
|
|
573
|
-
{ "internalDescriptorFingerprint", definition.Fingerprint },
|
|
574
1018
|
{ "available", ToolAvailableNow(definition) }
|
|
575
1019
|
};
|
|
576
1020
|
if (!(bool)data["available"])
|
|
577
1021
|
data["unavailableReason"] = ToolUnavailableReason(definition);
|
|
578
|
-
if (definition.SideEffects
|
|
579
|
-
if (definition.Permissions
|
|
580
|
-
if (definition.RequiresPackages
|
|
1022
|
+
if (definition.SideEffects.Count > 0) data["sideEffects"] = definition.SideEffects.ToArray();
|
|
1023
|
+
if (definition.Permissions.Count > 0) data["permissions"] = definition.Permissions.ToArray();
|
|
1024
|
+
if (definition.RequiresPackages.Count > 0) data["requiresPackages"] = definition.RequiresPackages.ToArray();
|
|
581
1025
|
return data;
|
|
582
1026
|
}
|
|
583
1027
|
|
|
584
1028
|
/// <summary>目录始终显示 Tool;available 只描述当前 mode/API 状态,不参与过滤。</summary>
|
|
585
|
-
private static bool ToolAvailableNow(
|
|
1029
|
+
private static bool ToolAvailableNow(RegisteredToolDescriptor definition)
|
|
586
1030
|
{
|
|
587
1031
|
if (BuildProfileApiRequired(definition) && BuildToolSupport.BuildProfileType() == null)
|
|
588
1032
|
return false;
|
|
@@ -596,7 +1040,7 @@ namespace UnityCli
|
|
|
596
1040
|
}
|
|
597
1041
|
|
|
598
1042
|
/// <summary>按执行门禁优先级返回与 available=false 对应的稳定原因。</summary>
|
|
599
|
-
private static string ToolUnavailableReason(
|
|
1043
|
+
private static string ToolUnavailableReason(RegisteredToolDescriptor definition)
|
|
600
1044
|
{
|
|
601
1045
|
if (definition.Target == "runtime" || definition.PlayModeSupport == "runtime-only")
|
|
602
1046
|
return "E_RUNTIME_NOT_READY: Enter Play Mode before executing this Tool.";
|
|
@@ -610,14 +1054,14 @@ namespace UnityCli
|
|
|
610
1054
|
}
|
|
611
1055
|
|
|
612
1056
|
/// <summary>这些 Tool 在所有版本注册;版本差异仅通过 availability 与执行错误表达。</summary>
|
|
613
|
-
private static bool BuildProfileApiRequired(
|
|
1057
|
+
private static bool BuildProfileApiRequired(RegisteredToolDescriptor definition)
|
|
614
1058
|
{
|
|
615
1059
|
return definition.Name == "build.profile.create"
|
|
616
1060
|
|| definition.Name == "build.profile.modify"
|
|
617
1061
|
|| definition.Name == "build.profile.switch";
|
|
618
1062
|
}
|
|
619
1063
|
|
|
620
|
-
private static bool EditorElementCaptureApiRequired(
|
|
1064
|
+
private static bool EditorElementCaptureApiRequired(RegisteredToolDescriptor definition)
|
|
621
1065
|
{
|
|
622
1066
|
return definition.Name == "screenshot.editor-element";
|
|
623
1067
|
}
|
|
@@ -638,301 +1082,6 @@ namespace UnityCli
|
|
|
638
1082
|
return new Dictionary<string, object> { { "result", result } };
|
|
639
1083
|
}
|
|
640
1084
|
|
|
641
|
-
private sealed class ProjectToolFingerprintCheck
|
|
642
|
-
{
|
|
643
|
-
public bool Blocked;
|
|
644
|
-
public string Expected;
|
|
645
|
-
public string Actual;
|
|
646
|
-
public string Message;
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
private sealed class ProjectToolPinnedFingerprint
|
|
650
|
-
{
|
|
651
|
-
public string Owner;
|
|
652
|
-
public string Value;
|
|
653
|
-
public string Algorithm;
|
|
654
|
-
public string Format;
|
|
655
|
-
public string Sha256;
|
|
656
|
-
public bool HardMismatch;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
private static ProjectToolFingerprintCheck RefreshProjectToolFingerprint(RegisteredTool definition)
|
|
660
|
-
{
|
|
661
|
-
var descriptor = ProjectToolDescriptorFingerprintMaterial(definition);
|
|
662
|
-
definition.DescriptorSha256 = Sha256Hex(descriptor);
|
|
663
|
-
definition.HandlerSha256 = Sha256Hex(ProjectToolHandlerFingerprintMaterial(definition));
|
|
664
|
-
definition.Fingerprint = Sha256Hex(ProjectToolFingerprintAlgorithm + "\nhandler=" + definition.HandlerSha256 + "\ndescriptor=" + definition.DescriptorSha256);
|
|
665
|
-
|
|
666
|
-
ProjectToolPinnedFingerprint pin;
|
|
667
|
-
if (string.Equals(definition.Ownership, "project", StringComparison.Ordinal) && !string.IsNullOrEmpty(definition.PinnedFingerprint))
|
|
668
|
-
{
|
|
669
|
-
pin = ParseProjectToolPinnedFingerprint("descriptor", definition.PinnedFingerprint);
|
|
670
|
-
}
|
|
671
|
-
else if (string.Equals(definition.Ownership, "project", StringComparison.Ordinal))
|
|
672
|
-
{
|
|
673
|
-
var baseline = LoadProjectToolExpectedFingerprint(definition.Name);
|
|
674
|
-
if (string.IsNullOrEmpty(baseline))
|
|
675
|
-
{
|
|
676
|
-
// 首次 project baseline 只能写入携带算法的 v2 pin,无算法 pin 绝不生成。
|
|
677
|
-
baseline = FormatProjectToolFingerprint(definition.Fingerprint);
|
|
678
|
-
SaveProjectToolExpectedFingerprint(definition.Name, baseline);
|
|
679
|
-
}
|
|
680
|
-
pin = ParseProjectToolPinnedFingerprint("project-baseline", baseline);
|
|
681
|
-
}
|
|
682
|
-
else
|
|
683
|
-
{
|
|
684
|
-
// Built-in 与 Extension 的 expected 由当前安装包生成,不写项目 baseline。
|
|
685
|
-
pin = ParseProjectToolPinnedFingerprint("installed-package", FormatProjectToolFingerprint(definition.Fingerprint));
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
definition.PinnedFingerprintOwner = pin.Owner;
|
|
689
|
-
definition.PinnedFingerprintAlgorithm = pin.Algorithm;
|
|
690
|
-
definition.PinnedFingerprintFormat = pin.Format;
|
|
691
|
-
definition.ExpectedFingerprint = pin.Value;
|
|
692
|
-
var matches = !pin.HardMismatch && string.Equals(pin.Sha256, definition.Fingerprint, StringComparison.OrdinalIgnoreCase);
|
|
693
|
-
// 任一 pin 格式、算法或内容不匹配都使用同一阻断路径。
|
|
694
|
-
definition.FingerprintStatus = matches ? "match" : "blocked";
|
|
695
|
-
definition.FingerprintMessage = matches
|
|
696
|
-
? "Custom tool fingerprint matches handler code + descriptor."
|
|
697
|
-
: pin.HardMismatch
|
|
698
|
-
? "Custom tool fingerprint pin uses an unsupported algorithm or format and must be re-pinned."
|
|
699
|
-
: "Custom tool fingerprint content differs from its v2 pin.";
|
|
700
|
-
return new ProjectToolFingerprintCheck
|
|
701
|
-
{
|
|
702
|
-
Blocked = !matches,
|
|
703
|
-
Expected = definition.ExpectedFingerprint,
|
|
704
|
-
Actual = definition.Fingerprint,
|
|
705
|
-
Message = definition.FingerprintMessage
|
|
706
|
-
};
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
/// <summary>解析 project Tool pin;裸 SHA、未知算法和畸形值都不能被当作 v2 expected。</summary>
|
|
710
|
-
private static ProjectToolPinnedFingerprint ParseProjectToolPinnedFingerprint(string owner, string value)
|
|
711
|
-
{
|
|
712
|
-
var pin = new ProjectToolPinnedFingerprint { Owner = owner, Value = value ?? string.Empty, Format = "malformed" };
|
|
713
|
-
var match = Regex.Match(pin.Value, "^([^:]+):([0-9a-fA-F]{64})$");
|
|
714
|
-
if (!match.Success)
|
|
715
|
-
{
|
|
716
|
-
pin.Format = Regex.IsMatch(pin.Value, "^[0-9a-fA-F]{64}$") ? "bare-sha256" : "malformed";
|
|
717
|
-
pin.Algorithm = pin.Format == "bare-sha256" ? "none" : "invalid";
|
|
718
|
-
pin.HardMismatch = true;
|
|
719
|
-
return pin;
|
|
720
|
-
}
|
|
721
|
-
pin.Algorithm = match.Groups[1].Value;
|
|
722
|
-
pin.Sha256 = match.Groups[2].Value.ToLowerInvariant();
|
|
723
|
-
pin.Format = string.Equals(pin.Algorithm, ProjectToolFingerprintAlgorithm, StringComparison.Ordinal) ? "valid-v2" : "algorithm-mismatch";
|
|
724
|
-
pin.HardMismatch = !string.Equals(pin.Algorithm, ProjectToolFingerprintAlgorithm, StringComparison.Ordinal);
|
|
725
|
-
return pin;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
private static string FormatProjectToolFingerprint(string sha256)
|
|
729
|
-
{
|
|
730
|
-
return ProjectToolFingerprintAlgorithm + ":" + (sha256 ?? string.Empty).ToLowerInvariant();
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
private static string LoadProjectToolExpectedFingerprint(string name)
|
|
735
|
-
{
|
|
736
|
-
try
|
|
737
|
-
{
|
|
738
|
-
var path = ProjectToolExpectedFingerprintPath(name);
|
|
739
|
-
return File.Exists(path) ? File.ReadAllText(path).Trim() : null;
|
|
740
|
-
}
|
|
741
|
-
catch (Exception exception)
|
|
742
|
-
{
|
|
743
|
-
WriteLog("custom_tool_fingerprint_baseline_read_failed name=" + name + " error=" + exception.GetType().Name + ": " + exception.Message);
|
|
744
|
-
return null;
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
private static void SaveProjectToolExpectedFingerprint(string name, string fingerprint)
|
|
749
|
-
{
|
|
750
|
-
try
|
|
751
|
-
{
|
|
752
|
-
var path = ProjectToolExpectedFingerprintPath(name);
|
|
753
|
-
Directory.CreateDirectory(Path.GetDirectoryName(path));
|
|
754
|
-
File.WriteAllText(path, fingerprint ?? string.Empty);
|
|
755
|
-
}
|
|
756
|
-
catch (Exception exception)
|
|
757
|
-
{
|
|
758
|
-
WriteLog("custom_tool_fingerprint_baseline_write_failed name=" + name + " error=" + exception.GetType().Name + ": " + exception.Message);
|
|
759
|
-
}
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
private static string ProjectToolExpectedFingerprintPath(string name)
|
|
763
|
-
{
|
|
764
|
-
return Path.Combine(UnityCliTempRoot(), "fingerprints", "tools", SafeProjectToolFingerprintFileName(name) + ".sha256");
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
private static string SafeProjectToolFingerprintFileName(string name)
|
|
768
|
-
{
|
|
769
|
-
return Regex.Replace(name ?? "project-tool", "[^A-Za-z0-9_.-]", "_");
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
private static string ProjectToolDescriptorFingerprintMaterial(RegisteredTool definition)
|
|
773
|
-
{
|
|
774
|
-
return "name=" + (definition.Name ?? string.Empty)
|
|
775
|
-
+ "\nsummary=" + (definition.Summary ?? string.Empty)
|
|
776
|
-
+ "\ndescription=" + (definition.Description ?? string.Empty)
|
|
777
|
-
+ "\ngroup=" + (definition.Group ?? string.Empty)
|
|
778
|
-
+ "\ninputSchema=" + NormalizeFingerprintJson(definition.InputSchema)
|
|
779
|
-
+ "\noutputSchema=" + NormalizeFingerprintJson(definition.OutputSchema)
|
|
780
|
-
+ "\ndestructiveHint=" + Bool(definition.DestructiveHint)
|
|
781
|
-
+ "\nsupportsDryRun=" + Bool(definition.SupportsDryRun)
|
|
782
|
-
+ "\nsideEffects=" + string.Join(",", (definition.SideEffects ?? new string[0]).OrderBy(value => value, StringComparer.Ordinal).ToArray())
|
|
783
|
-
+ "\npermissions=" + string.Join(",", (definition.Permissions ?? new string[0]).OrderBy(value => value, StringComparer.Ordinal).ToArray())
|
|
784
|
-
+ "\nrequiresPackages=" + string.Join(",", (definition.RequiresPackages ?? new string[0]).OrderBy(value => value, StringComparer.Ordinal).ToArray())
|
|
785
|
-
+ "\ntarget=" + (definition.Target ?? string.Empty)
|
|
786
|
-
+ "\navailabilityScope=" + (definition.AvailabilityScope ?? string.Empty)
|
|
787
|
-
+ "\nplayModeSupport=" + (definition.PlayModeSupport ?? string.Empty)
|
|
788
|
-
+ "\ncontractProfile=" + (definition.ContractProfile ?? string.Empty)
|
|
789
|
-
+ "\ndefaultTimeoutMs=" + definition.DefaultTimeoutMs.ToString(CultureInfo.InvariantCulture)
|
|
790
|
-
+ "\nmaxTimeoutMs=" + definition.MaxTimeoutMs.ToString(CultureInfo.InvariantCulture)
|
|
791
|
-
+ "\nrequiredInput=" + string.Join(",", (definition.RequiredInput ?? new string[0]).OrderBy(value => value, StringComparer.Ordinal).ToArray())
|
|
792
|
-
+ "\nexamples=" + NormalizeFingerprintJson(UnityCliSerializer.Serialize(definition.Examples ?? Array.Empty<ToolExample>()))
|
|
793
|
-
+ "\ncompletion=" + CompletionFingerprintMaterial(definition.CompletionContract)
|
|
794
|
-
+ "\ndescriptorSource=" + (definition.DescriptorSource ?? string.Empty);
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
/// <summary>把 sealed completion contract 纳入 descriptor integrity proof。</summary>
|
|
798
|
-
private static string CompletionFingerprintMaterial(UnityCliToolCompletionContract contract)
|
|
799
|
-
{
|
|
800
|
-
if (contract == null) return string.Empty;
|
|
801
|
-
return string.Join("|", new[]
|
|
802
|
-
{
|
|
803
|
-
contract.ExecutionClass, contract.CompletionPredicate, contract.ProofSource,
|
|
804
|
-
contract.ReloadPolicy, contract.TimeoutError, contract.OutcomeUnknownPolicy,
|
|
805
|
-
contract.CompletionDriverId, contract.JournalPolicy, contract.Mutation ? "mutation" : "read-only"
|
|
806
|
-
}.Select(value => value ?? string.Empty).ToArray());
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
private static string NormalizeProjectToolPlayModeSupport(string value, string target)
|
|
810
|
-
{
|
|
811
|
-
if (string.Equals(value, "supported", StringComparison.OrdinalIgnoreCase)) return "supported";
|
|
812
|
-
if (string.Equals(value, "runtime-only", StringComparison.OrdinalIgnoreCase)) return "runtime-only";
|
|
813
|
-
if (string.Equals(value, "session-affecting", StringComparison.OrdinalIgnoreCase)) return "session-affecting";
|
|
814
|
-
if (string.Equals(value, "editor-only", StringComparison.OrdinalIgnoreCase)) return "editor-only";
|
|
815
|
-
if (string.Equals(value, "unknown", StringComparison.OrdinalIgnoreCase)) return "unknown";
|
|
816
|
-
return string.Equals(target, "runtime", StringComparison.OrdinalIgnoreCase) ? "runtime-only" : "editor-only";
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
private static string ProjectToolHandlerFingerprintMaterial(RegisteredTool definition)
|
|
820
|
-
{
|
|
821
|
-
return "assembly=" + (definition.Assembly ?? string.Empty)
|
|
822
|
-
+ "\nmethod=" + (definition.Method ?? string.Empty)
|
|
823
|
-
+ "\nhandler=" + ProjectToolHandlerSource(definition);
|
|
824
|
-
}
|
|
825
|
-
|
|
826
|
-
private static string ProjectToolHandlerSource(RegisteredTool definition)
|
|
827
|
-
{
|
|
828
|
-
try
|
|
829
|
-
{
|
|
830
|
-
var typeName = definition.Method;
|
|
831
|
-
if (string.IsNullOrEmpty(typeName)) return string.Empty;
|
|
832
|
-
var dot = typeName.LastIndexOf('.');
|
|
833
|
-
if (dot <= 0) return string.Empty;
|
|
834
|
-
typeName = typeName.Substring(0, dot);
|
|
835
|
-
var fileName = typeName.Substring(typeName.LastIndexOf('.') + 1) + ".cs";
|
|
836
|
-
foreach (var guid in AssetDatabase.FindAssets(Path.GetFileNameWithoutExtension(fileName) + " t:MonoScript"))
|
|
837
|
-
{
|
|
838
|
-
var path = AssetDatabase.GUIDToAssetPath(guid);
|
|
839
|
-
if (!string.Equals(Path.GetFileName(path), fileName, StringComparison.OrdinalIgnoreCase)) continue;
|
|
840
|
-
var text = File.ReadAllText(path);
|
|
841
|
-
if (text.IndexOf(definition.Method.Substring(dot + 1), StringComparison.Ordinal) >= 0)
|
|
842
|
-
{
|
|
843
|
-
return NormalizeFingerprintText(text);
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
catch (Exception exception)
|
|
848
|
-
{
|
|
849
|
-
WriteLog("custom_tool_fingerprint_source_failed name=" + definition.Name + " error=" + exception.GetType().Name + ": " + exception.Message);
|
|
850
|
-
}
|
|
851
|
-
return string.Empty;
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
private static string NormalizeFingerprintText(string value)
|
|
855
|
-
{
|
|
856
|
-
return (value ?? string.Empty).Replace("\r\n", "\n").Replace("\r", "\n");
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
private static string NormalizeFingerprintJson(string value)
|
|
860
|
-
{
|
|
861
|
-
return Regex.Replace((value ?? string.Empty).Trim(), "\\s+", " ");
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
private static string Sha256Hex(string value)
|
|
865
|
-
{
|
|
866
|
-
using (var sha = SHA256.Create())
|
|
867
|
-
{
|
|
868
|
-
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(value ?? string.Empty));
|
|
869
|
-
var builder = new StringBuilder(bytes.Length * 2);
|
|
870
|
-
foreach (var b in bytes)
|
|
871
|
-
{
|
|
872
|
-
builder.Append(b.ToString("x2", CultureInfo.InvariantCulture));
|
|
873
|
-
}
|
|
874
|
-
return builder.ToString();
|
|
875
|
-
}
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
private static Dictionary<string, object> ProjectToolFingerprintData(RegisteredTool definition)
|
|
879
|
-
{
|
|
880
|
-
return new Dictionary<string, object>
|
|
881
|
-
{
|
|
882
|
-
{ "currentAlgorithm", ProjectToolFingerprintAlgorithm }, { "actualSha256", definition.Fingerprint },
|
|
883
|
-
{ "pinnedOwner", definition.PinnedFingerprintOwner }, { "pinnedAlgorithm", definition.PinnedFingerprintAlgorithm },
|
|
884
|
-
{ "pinnedFormat", definition.PinnedFingerprintFormat }, { "pinnedValue", definition.ExpectedFingerprint },
|
|
885
|
-
{ "status", definition.FingerprintStatus }, { "message", definition.FingerprintMessage },
|
|
886
|
-
{ "rePinValue", FormatProjectToolFingerprint(definition.Fingerprint) },
|
|
887
|
-
{ "rePinSteps", ProjectToolFingerprintRePinSteps(definition) },
|
|
888
|
-
{ "descriptorSha256", definition.DescriptorSha256 }, { "handlerSha256", definition.HandlerSha256 }
|
|
889
|
-
};
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
private static string[] ProjectToolFingerprintRePinSteps(RegisteredTool definition)
|
|
893
|
-
{
|
|
894
|
-
var value = FormatProjectToolFingerprint(definition.Fingerprint);
|
|
895
|
-
return definition.PinnedFingerprintOwner == "descriptor"
|
|
896
|
-
? new[] { "Review the Tool change.", "Set ToolDescriptor.Fingerprint to " + value + ".", "Run the Tool again." }
|
|
897
|
-
: new[] { "Review the Tool change.", "Replace the persisted project Tool fingerprint with " + value + ".", "Run the Tool again." };
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
private static Dictionary<string, object> ProjectToolFingerprintSummary()
|
|
902
|
-
{
|
|
903
|
-
var items = new List<object>();
|
|
904
|
-
var blocked = 0;
|
|
905
|
-
foreach (var pair in ProjectTools)
|
|
906
|
-
{
|
|
907
|
-
var definition = pair.Value;
|
|
908
|
-
RefreshProjectToolFingerprint(definition);
|
|
909
|
-
if (definition.FingerprintStatus == "blocked") blocked++;
|
|
910
|
-
items.Add(new Dictionary<string, object>
|
|
911
|
-
{
|
|
912
|
-
{ "name", definition.Name },
|
|
913
|
-
{ "fingerprint", ProjectToolFingerprintData(definition) }
|
|
914
|
-
});
|
|
915
|
-
}
|
|
916
|
-
items.Sort((left, right) => StringComparer.Ordinal.Compare(
|
|
917
|
-
((Dictionary<string, object>)left)["name"] as string,
|
|
918
|
-
((Dictionary<string, object>)right)["name"] as string));
|
|
919
|
-
return new Dictionary<string, object>
|
|
920
|
-
{
|
|
921
|
-
{ "algorithm", ProjectToolFingerprintAlgorithm },
|
|
922
|
-
{ "toolCount", (long)ProjectTools.Count },
|
|
923
|
-
{ "blockedCount", (long)blocked },
|
|
924
|
-
{ "tools", items }
|
|
925
|
-
};
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
private static Dictionary<string, object> ProjectToolFingerprintDetails(RegisteredTool definition)
|
|
929
|
-
{
|
|
930
|
-
return new Dictionary<string, object>
|
|
931
|
-
{
|
|
932
|
-
{ "tool", definition.Name },
|
|
933
|
-
{ "fingerprint", ProjectToolFingerprintData(definition) }
|
|
934
|
-
};
|
|
935
|
-
}
|
|
936
1085
|
}
|
|
937
1086
|
}
|
|
938
1087
|
#endif
|