@alpsckr/unitycli 0.4.3 → 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.
Files changed (420) hide show
  1. package/README.md +5 -5
  2. package/contracts/command-contract.json +109 -217
  3. package/contracts/package-files.json +374 -294
  4. package/contracts/stable-session/direct-call-semantics.json +8 -2
  5. package/contracts/tool-completion-contract.json +148 -672
  6. package/dist/bridge/admission.d.ts +17 -1
  7. package/dist/bridge/admission.js +2 -0
  8. package/dist/bridge/admission.js.map +1 -1
  9. package/dist/bridge/domains/asset.js +3 -1
  10. package/dist/bridge/domains/asset.js.map +1 -1
  11. package/dist/bridge/domains/camera.js +39 -7
  12. package/dist/bridge/domains/camera.js.map +1 -1
  13. package/dist/bridge/domains/console.js +2 -1
  14. package/dist/bridge/domains/console.js.map +1 -1
  15. package/dist/bridge/domains/doctor.d.ts +3 -0
  16. package/dist/bridge/domains/doctor.js +8 -0
  17. package/dist/bridge/domains/doctor.js.map +1 -0
  18. package/dist/bridge/domains/editor.js +0 -4
  19. package/dist/bridge/domains/editor.js.map +1 -1
  20. package/dist/bridge/domains/game-object.js +4 -12
  21. package/dist/bridge/domains/game-object.js.map +1 -1
  22. package/dist/bridge/domains/graphics.js +1 -16
  23. package/dist/bridge/domains/graphics.js.map +1 -1
  24. package/dist/bridge/domains/object.js +3 -0
  25. package/dist/bridge/domains/object.js.map +1 -1
  26. package/dist/bridge/domains/physics.js +1 -2
  27. package/dist/bridge/domains/physics.js.map +1 -1
  28. package/dist/bridge/domains/profiler.js +18 -7
  29. package/dist/bridge/domains/profiler.js.map +1 -1
  30. package/dist/bridge/domains/project-tool.js +4 -0
  31. package/dist/bridge/domains/project-tool.js.map +1 -1
  32. package/dist/bridge/domains/reflection.js +10 -2
  33. package/dist/bridge/domains/reflection.js.map +1 -1
  34. package/dist/bridge/domains/runtime-tool.js +3 -0
  35. package/dist/bridge/domains/runtime-tool.js.map +1 -1
  36. package/dist/bridge/domains/scene.js +3 -8
  37. package/dist/bridge/domains/scene.js.map +1 -1
  38. package/dist/bridge/domains/screenshot.js +30 -6
  39. package/dist/bridge/domains/screenshot.js.map +1 -1
  40. package/dist/bridge/domains/ui.js +3 -1
  41. package/dist/bridge/domains/ui.js.map +1 -1
  42. package/dist/bridge/index.d.ts +5 -5
  43. package/dist/bridge/index.js +6 -9
  44. package/dist/bridge/index.js.map +1 -1
  45. package/dist/bridge/registry.js +3 -5
  46. package/dist/bridge/registry.js.map +1 -1
  47. package/dist/bridge/session-supervisor.d.ts +6 -3
  48. package/dist/bridge/session-supervisor.js +122 -34
  49. package/dist/bridge/session-supervisor.js.map +1 -1
  50. package/dist/bridge/types.d.ts +6 -5
  51. package/dist/bridge/types.js +16 -2
  52. package/dist/bridge/types.js.map +1 -1
  53. package/dist/catalog-protocol.d.ts +64 -0
  54. package/dist/catalog-protocol.js +460 -0
  55. package/dist/catalog-protocol.js.map +1 -0
  56. package/dist/cli-commands/command.js +84 -61
  57. package/dist/cli-commands/command.js.map +1 -1
  58. package/dist/cli-commands/extensions.js +70 -50
  59. package/dist/cli-commands/extensions.js.map +1 -1
  60. package/dist/cli-commands/instances.js +1 -1
  61. package/dist/cli-commands/instances.js.map +1 -1
  62. package/dist/cli-process-owners.d.ts +6 -0
  63. package/dist/cli-process-owners.js +27 -0
  64. package/dist/cli-process-owners.js.map +1 -0
  65. package/dist/cli.d.ts +1 -1
  66. package/dist/cli.js +20 -5
  67. package/dist/cli.js.map +1 -1
  68. package/dist/contracts/command-contract.js +14 -4
  69. package/dist/contracts/command-contract.js.map +1 -1
  70. package/dist/doctor.d.ts +4 -6
  71. package/dist/doctor.js +429 -398
  72. package/dist/doctor.js.map +1 -1
  73. package/dist/editor-blocker-observer.d.ts +2 -1
  74. package/dist/editor-blocker-observer.js +40 -13
  75. package/dist/editor-blocker-observer.js.map +1 -1
  76. package/dist/extension-availability.d.ts +27 -9
  77. package/dist/extension-availability.js +96 -56
  78. package/dist/extension-availability.js.map +1 -1
  79. package/dist/extensions/descriptor-loader.d.ts +2 -2
  80. package/dist/extensions/descriptor-loader.js +3 -4
  81. package/dist/extensions/descriptor-loader.js.map +1 -1
  82. package/dist/extensions/index.d.ts +1 -1
  83. package/dist/extensions/index.js +1 -1
  84. package/dist/extensions/index.js.map +1 -1
  85. package/dist/extensions/installer.d.ts +24 -7
  86. package/dist/extensions/installer.js +142 -66
  87. package/dist/extensions/installer.js.map +1 -1
  88. package/dist/extensions/registry.js +3 -5
  89. package/dist/extensions/registry.js.map +1 -1
  90. package/dist/extensions/state-contract.d.ts +11 -0
  91. package/dist/extensions/state-contract.js +50 -0
  92. package/dist/extensions/state-contract.js.map +1 -0
  93. package/dist/instances.d.ts +13 -1
  94. package/dist/instances.js +26 -0
  95. package/dist/instances.js.map +1 -1
  96. package/dist/package-manager.d.ts +0 -24
  97. package/dist/package-manager.js +5 -58
  98. package/dist/package-manager.js.map +1 -1
  99. package/dist/project-tool-discovery.d.ts +5 -2
  100. package/dist/project-tool-discovery.js +22 -18
  101. package/dist/project-tool-discovery.js.map +1 -1
  102. package/dist/runtime-code.js +1 -1
  103. package/dist/runtime-code.js.map +1 -1
  104. package/dist/tool-name.d.ts +4 -0
  105. package/dist/tool-name.js +15 -0
  106. package/dist/tool-name.js.map +1 -0
  107. package/dist/tools/.generated-completion-registry.d.ts +2 -0
  108. package/dist/tools/.generated-completion-registry.js +763 -325
  109. package/dist/tools/.generated-completion-registry.js.map +1 -1
  110. package/dist/tools/.generated-registry.d.ts +6 -11
  111. package/dist/tools/.generated-registry.js +6 -11
  112. package/dist/tools/.generated-registry.js.map +1 -1
  113. package/dist/tools/.generated-schemas.js +11 -16
  114. package/dist/tools/.generated-schemas.js.map +1 -1
  115. package/dist/tools/asset/refresh-status.d.ts +3 -0
  116. package/dist/tools/asset/refresh-status.js +12 -0
  117. package/dist/tools/asset/refresh-status.js.map +1 -0
  118. package/dist/tools/asset/refresh.js +7 -3
  119. package/dist/tools/asset/refresh.js.map +1 -1
  120. package/dist/tools/build/player-status.d.ts +3 -0
  121. package/dist/tools/build/player-status.js +12 -0
  122. package/dist/tools/build/player-status.js.map +1 -0
  123. package/dist/tools/build/run.js +1 -1
  124. package/dist/tools/build/run.js.map +1 -1
  125. package/dist/tools/catalog-control.d.ts +22 -0
  126. package/dist/tools/catalog-control.js +335 -0
  127. package/dist/tools/catalog-control.js.map +1 -0
  128. package/dist/tools/catalog.d.ts +7 -9
  129. package/dist/tools/catalog.js +66 -45
  130. package/dist/tools/catalog.js.map +1 -1
  131. package/dist/tools/commands.d.ts +1 -1
  132. package/dist/tools/commands.js +44 -11
  133. package/dist/tools/commands.js.map +1 -1
  134. package/dist/tools/define.js +3 -2
  135. package/dist/tools/define.js.map +1 -1
  136. package/dist/tools/dispatcher.d.ts +53 -6
  137. package/dist/tools/dispatcher.js +277 -108
  138. package/dist/tools/dispatcher.js.map +1 -1
  139. package/dist/tools/editor/compile.js +1 -1
  140. package/dist/tools/editor/compile.js.map +1 -1
  141. package/dist/tools/editor/play-mode-status.d.ts +3 -0
  142. package/dist/tools/editor/play-mode-status.js +12 -0
  143. package/dist/tools/editor/play-mode-status.js.map +1 -0
  144. package/dist/tools/editor/readiness.d.ts +0 -1
  145. package/dist/tools/editor/readiness.js +0 -1
  146. package/dist/tools/editor/readiness.js.map +1 -1
  147. package/dist/tools/editor/refresh.d.ts +7 -3
  148. package/dist/tools/editor/refresh.js +20 -15
  149. package/dist/tools/editor/refresh.js.map +1 -1
  150. package/dist/tools/editor/state.d.ts +2 -2
  151. package/dist/tools/editor/state.js +13 -12
  152. package/dist/tools/editor/state.js.map +1 -1
  153. package/dist/tools/metadata/derived.js +1 -7
  154. package/dist/tools/metadata/derived.js.map +1 -1
  155. package/dist/tools/metadata/status.js +1 -12
  156. package/dist/tools/metadata/status.js.map +1 -1
  157. package/dist/tools/metadata/types.d.ts +0 -2
  158. package/dist/tools/navmesh/live.js +1 -1
  159. package/dist/tools/navmesh/live.js.map +1 -1
  160. package/dist/tools/occlusion/live.js +1 -1
  161. package/dist/tools/occlusion/live.js.map +1 -1
  162. package/dist/tools/package/deploy.js +2 -2
  163. package/dist/tools/package/deploy.js.map +1 -1
  164. package/dist/tools/package/embed.js +1 -1
  165. package/dist/tools/package/embed.js.map +1 -1
  166. package/dist/tools/package/install-status.d.ts +3 -0
  167. package/dist/tools/package/install-status.js +12 -0
  168. package/dist/tools/package/install-status.js.map +1 -0
  169. package/dist/tools/package/install.js +3 -3
  170. package/dist/tools/package/install.js.map +1 -1
  171. package/dist/tools/package/live-resolve.js +45 -12
  172. package/dist/tools/package/live-resolve.js.map +1 -1
  173. package/dist/tools/package/registry-clear.js.map +1 -1
  174. package/dist/tools/package/registry-set.js.map +1 -1
  175. package/dist/tools/package/remove-status.d.ts +3 -0
  176. package/dist/tools/package/remove-status.js +12 -0
  177. package/dist/tools/package/remove-status.js.map +1 -0
  178. package/dist/tools/package/remove.js +3 -3
  179. package/dist/tools/package/remove.js.map +1 -1
  180. package/dist/tools/package/resolve.js +3 -3
  181. package/dist/tools/package/resolve.js.map +1 -1
  182. package/dist/tools/package/restore.js +2 -2
  183. package/dist/tools/package/restore.js.map +1 -1
  184. package/dist/tools/package/scoped-registry-add.js.map +1 -1
  185. package/dist/tools/status/support.d.ts +13 -0
  186. package/dist/tools/status/support.js +28 -0
  187. package/dist/tools/status/support.js.map +1 -0
  188. package/dist/tools/test/cancel.js +2 -2
  189. package/dist/tools/test/cancel.js.map +1 -1
  190. package/dist/tools/test/run-status.d.ts +3 -0
  191. package/dist/tools/test/run-status.js +12 -0
  192. package/dist/tools/test/run-status.js.map +1 -0
  193. package/dist/tools/test/run.js +7 -4
  194. package/dist/tools/test/run.js.map +1 -1
  195. package/dist/tools/tools/group-summaries.js +10 -14
  196. package/dist/tools/tools/group-summaries.js.map +1 -1
  197. package/docs/architecture.md +4 -4
  198. package/docs/availability-differences.md +4 -4
  199. package/docs/command-execution.md +4 -3
  200. package/docs/error-codes.md +13 -5
  201. package/docs/extensions.md +1 -7
  202. package/docs/risk-and-side-effects.md +4 -3
  203. package/docs/safety-boundaries.md +3 -1
  204. package/docs/target-and-instance.md +1 -1
  205. package/docs/tool-discovery.md +17 -10
  206. package/docs/tool-execution.md +18 -7
  207. package/extensions/cinemachine/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  208. package/extensions/hdrp/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  209. package/extensions/probuilder/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  210. package/extensions/urp/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  211. package/extensions/vfx-graph/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  212. package/package.json +2 -4
  213. package/skills/unitycli/SKILL.md +11 -2
  214. package/skills/unitycli/manifest.json +1 -1
  215. package/skills/unitycli/references/operation-protocol.md +3 -3
  216. package/unitypkg/Editor/AssemblyInfo.cs +5 -0
  217. package/unitypkg/Editor/Bridge/BridgeCommandRegistry.cs +49 -14
  218. package/unitypkg/Editor/Bridge/BridgeHttpServer.cs +9 -35
  219. package/unitypkg/Editor/Bridge/BridgeQueueState.cs +672 -0
  220. package/unitypkg/Editor/Bridge/BridgeWorkQueue.cs +970 -120
  221. package/unitypkg/Editor/BridgeStartupStateMachine.cs +59 -1
  222. package/unitypkg/Editor/Commands/ProjectToolCommands.cs +4 -0
  223. package/unitypkg/Editor/Commands/RuntimeToolCommands.cs +3 -0
  224. package/unitypkg/Editor/Generated/UnityCliToolCompletionRegistry.g.cs +379 -14
  225. package/unitypkg/Editor/Infrastructure/CodeExecSupport.cs +28 -13
  226. package/unitypkg/Editor/Infrastructure/RegistryOwnershipResolver.cs +132 -105
  227. package/unitypkg/Editor/Infrastructure/UnityCliActiveOperation.cs +138 -29
  228. package/unitypkg/Editor/Infrastructure/UnityCliAdmission.cs +1856 -237
  229. package/unitypkg/Editor/Infrastructure/UnityCliDoctorDiagnostics.cs +220 -0
  230. package/unitypkg/Editor/Infrastructure/UnityCliDurableStatusCoordinator.cs +118 -0
  231. package/unitypkg/Editor/Infrastructure/UnityCliEditorStateSnapshot.cs +2 -1
  232. package/unitypkg/Editor/Infrastructure/UnityCliHierarchyPager.cs +203 -0
  233. package/unitypkg/Editor/Infrastructure/UnityCliInstanceCleanup.cs +2 -1
  234. package/unitypkg/Editor/Infrastructure/UnityCliInstanceRegistry.cs +201 -63
  235. package/unitypkg/Editor/Infrastructure/UnityCliJson.cs +2 -3
  236. package/unitypkg/Editor/Infrastructure/UnityCliLiveBuildCoordinator.cs +289 -46
  237. package/unitypkg/Editor/Infrastructure/UnityCliLiveRefreshCoordinator.cs +87 -53
  238. package/unitypkg/Editor/Infrastructure/UnityCliNavMeshBakeCoordinator.cs +4 -4
  239. package/unitypkg/Editor/Infrastructure/UnityCliObjectRef.cs +128 -25
  240. package/unitypkg/Editor/Infrastructure/UnityCliObjectSerializer.cs +435 -327
  241. package/unitypkg/Editor/Infrastructure/UnityCliOcclusionBakeCoordinator.cs +4 -4
  242. package/unitypkg/Editor/Infrastructure/UnityCliOperationCoordinator.cs +351 -53
  243. package/unitypkg/Editor/Infrastructure/UnityCliPackageResolveCoordinator.cs +118 -18
  244. package/unitypkg/Editor/Infrastructure/UnityCliPackageResolveState.cs +20 -1
  245. package/unitypkg/Editor/Infrastructure/UnityCliReadGeneration.cs +97 -0
  246. package/unitypkg/Editor/Infrastructure/UnityCliReflection.cs +644 -133
  247. package/unitypkg/Editor/Infrastructure/UnityCliReloadOperation.cs +128 -41
  248. package/unitypkg/Editor/Infrastructure/UnityCliRuntimeExecutor.cs +14 -84
  249. package/unitypkg/Editor/Infrastructure/UnityCliSerializer.cs +220 -0
  250. package/unitypkg/Editor/Infrastructure/UnityCliSessionMutationLease.cs +69 -29
  251. package/unitypkg/Editor/Infrastructure/UnityCliTestRunCoordinator.cs +440 -54
  252. package/unitypkg/Editor/Infrastructure/UnityCliWaiter.cs +128 -35
  253. package/unitypkg/Editor/ProjectTools/IUnityCliTool.cs +9 -1
  254. package/unitypkg/Editor/ProjectTools/OfficialAttributeToolRegistry.cs +32 -31
  255. package/unitypkg/Editor/ProjectTools/ProjectToolRegistry.cs +607 -458
  256. package/unitypkg/Editor/ProjectTools/ProjectToolSchemaValidator.cs +35 -7
  257. package/unitypkg/Editor/ProjectTools/RegisteredTool.cs +501 -60
  258. package/unitypkg/Editor/ProjectTools/UnityCliProjectToolContext.cs +21 -0
  259. package/unitypkg/Editor/ProjectTools/UnityCliSchemaDeriver.cs +36 -7
  260. package/unitypkg/Editor/ProjectTools/UnityCliToolName.cs +30 -0
  261. package/unitypkg/Editor/Properties/AssemblyInfo.cs +3 -0
  262. package/unitypkg/Editor/Tools/asset/get-data.cs +16 -7
  263. package/unitypkg/Editor/Tools/build/BuildSceneStateTool.cs +23 -3
  264. package/unitypkg/Editor/Tools/build/BuildToolDtos.cs +2 -1
  265. package/unitypkg/Editor/Tools/build/scene/add.cs +6 -3
  266. package/unitypkg/Editor/Tools/build/scene/disable.cs +1 -1
  267. package/unitypkg/Editor/Tools/build/scene/enable.cs +1 -1
  268. package/unitypkg/Editor/Tools/build/scene/list.cs +1 -1
  269. package/unitypkg/Editor/Tools/build/scene/remove.cs +13 -3
  270. package/unitypkg/Editor/Tools/camera/CameraToolSupport.cs +38 -38
  271. package/unitypkg/Editor/Tools/camera/multiview.cs +13 -6
  272. package/unitypkg/Editor/Tools/camera/screenshot.cs +14 -7
  273. package/unitypkg/Editor/Tools/component/get.cs +16 -9
  274. package/unitypkg/Editor/Tools/component/modify.cs +6 -4
  275. package/unitypkg/Editor/Tools/console/clear.cs +7 -6
  276. package/unitypkg/Editor/Tools/console/get.cs +48 -16
  277. package/unitypkg/Editor/Tools/editor/EditorToolSupport.cs +143 -28
  278. package/unitypkg/Editor/Tools/editor/menu-items.cs +1 -1
  279. package/unitypkg/Editor/Tools/editor/play.cs +2 -11
  280. package/unitypkg/Editor/Tools/editor/selection.cs +1 -1
  281. package/unitypkg/Editor/Tools/editor/stop.cs +2 -11
  282. package/unitypkg/Editor/Tools/editor/windows.cs +1 -1
  283. package/unitypkg/Editor/Tools/go/GameObjectToolSupport.cs +54 -4
  284. package/unitypkg/Editor/Tools/go/delete.cs +1 -1
  285. package/unitypkg/Editor/Tools/go/find.cs +167 -70
  286. package/unitypkg/Editor/Tools/graphics/GraphicsToolSupport.cs +25 -0
  287. package/unitypkg/Editor/Tools/graphics/light-probe-group.cs +9 -3
  288. package/unitypkg/Editor/Tools/graphics/pipeline-info.cs +1 -1
  289. package/unitypkg/Editor/Tools/graphics/pipeline-settings.cs +1 -1
  290. package/unitypkg/Editor/Tools/graphics/quality.cs +2 -2
  291. package/unitypkg/Editor/Tools/graphics/reflection-probe.cs +3 -3
  292. package/unitypkg/Editor/Tools/graphics/stats.cs +1 -1
  293. package/unitypkg/Editor/Tools/object/ObjectToolSupport.cs +4 -4
  294. package/unitypkg/Editor/Tools/object/get-data.cs +9 -7
  295. package/unitypkg/Editor/Tools/object/modify.cs +7 -5
  296. package/unitypkg/Editor/Tools/physics/raycast.cs +24 -6
  297. package/unitypkg/Editor/Tools/prefab/stage.cs +1 -1
  298. package/unitypkg/Editor/Tools/profiler/ProfilerFrameDebuggerSupport.cs +94 -21
  299. package/unitypkg/Editor/Tools/profiler/ProfilerMemorySnapshotSupport.cs +397 -10
  300. package/unitypkg/Editor/Tools/profiler/ProfilerToolSupport.cs +18 -4
  301. package/unitypkg/Editor/Tools/profiler/frame-debugger/enable.cs +1 -1
  302. package/unitypkg/Editor/Tools/profiler/frame-debugger/events.cs +88 -87
  303. package/unitypkg/Editor/Tools/profiler/hierarchy.cs +278 -112
  304. package/unitypkg/Editor/Tools/profiler/memory-snapshot/compare.cs +9 -5
  305. package/unitypkg/Editor/Tools/profiler/memory-snapshot/list.cs +37 -35
  306. package/unitypkg/Editor/Tools/profiler/memory-snapshot/take.cs +99 -97
  307. package/unitypkg/Editor/Tools/project/info.cs +1 -1
  308. package/unitypkg/Editor/Tools/project/layers.cs +1 -1
  309. package/unitypkg/Editor/Tools/project/tags.cs +1 -1
  310. package/unitypkg/Editor/Tools/reflection/method/call.cs +9 -7
  311. package/unitypkg/Editor/Tools/reflection/method/find.cs +27 -37
  312. package/unitypkg/Editor/Tools/reflection/type/schema.cs +24 -28
  313. package/unitypkg/Editor/Tools/scene/gameobject/component.cs +8 -5
  314. package/unitypkg/Editor/Tools/scene/gameobject/components.cs +10 -7
  315. package/unitypkg/Editor/Tools/scene/gameobject-support.cs +104 -8
  316. package/unitypkg/Editor/Tools/scene/gameobject.cs +37 -16
  317. package/unitypkg/Editor/Tools/scene/hierarchy.cs +146 -142
  318. package/unitypkg/Editor/Tools/screenshot/EditorElementCaptureToolSupport.cs +77 -25
  319. package/unitypkg/Editor/Tools/screenshot/ScreenshotToolSupport.cs +315 -95
  320. package/unitypkg/Editor/Tools/screenshot/editor-element.cs +7 -5
  321. package/unitypkg/Editor/Tools/screenshot/game-view.cs +35 -21
  322. package/unitypkg/Editor/Tools/screenshot/isolated-object.cs +29 -23
  323. package/unitypkg/Editor/Tools/screenshot/orbit.cs +37 -25
  324. package/unitypkg/Editor/Tools/screenshot/scene-view.cs +25 -18
  325. package/unitypkg/Editor/Tools/screenshot/screen.cs +177 -17
  326. package/unitypkg/Editor/Tools/screenshot/surround.cs +83 -79
  327. package/unitypkg/Editor/Tools/ui/render-preview.cs +16 -11
  328. package/unitypkg/Editor/UnityCliBridge.cs +97 -34
  329. package/unitypkg/Runtime/Infrastructure/UnityCliRuntimeBridge.cs +753 -122
  330. package/unitypkg/Runtime/Properties/AssemblyInfo.cs +5 -0
  331. package/unitypkg/Runtime/UnityCli.Runtime.asmdef +1 -1
  332. package/unitypkg/Shared/AssemblyInfo.cs +4 -0
  333. package/unitypkg/Shared/UnityCliAdmissionPool.cs +265 -75
  334. package/unitypkg/Shared/UnityCliBridgeQueuePolicy.cs +410 -0
  335. package/unitypkg/Shared/UnityCliBridgeWorkItemState.cs +47 -0
  336. package/unitypkg/Shared/UnityCliExceptionProjection.cs +151 -0
  337. package/unitypkg/Shared/UnityCliQueuePublication.cs +30 -0
  338. package/unitypkg/Shared/UnityCliRequestBudget.cs +352 -0
  339. package/unitypkg/Shared/UnityCliRuntimeRequestPublication.cs +107 -0
  340. package/unitypkg/Tests/Editor/UnityCli.Editor.Tests.asmdef +2 -2
  341. package/unitypkg/Tests/Editor/UnityCliAdmissionCoordinatorIntegrationTests.cs +1329 -0
  342. package/unitypkg/Tests/Editor/UnityCliAdmissionPoolTests.cs +311 -114
  343. package/unitypkg/Tests/Editor/UnityCliAdmissionRepairStateTests.cs +94 -0
  344. package/unitypkg/Tests/Editor/UnityCliBridgeAdmissionIntegrationTests.cs +1229 -0
  345. package/unitypkg/Tests/Editor/UnityCliBridgeQueuePolicyTests.cs +412 -0
  346. package/unitypkg/Tests/Editor/UnityCliContinuationIntegrationTests.cs +292 -0
  347. package/unitypkg/Tests/Editor/UnityCliDiagnosticBoundaryTests.cs +120 -0
  348. package/unitypkg/Tests/Editor/UnityCliDurableStatusTests.cs +84 -0
  349. package/unitypkg/Tests/Editor/UnityCliExceptionProjectionTests.cs +72 -0
  350. package/unitypkg/Tests/Editor/UnityCliInstancePublicationRetryTests.cs +180 -97
  351. package/unitypkg/Tests/Editor/UnityCliMemoryReflectionIntegrationTests.cs +434 -0
  352. package/unitypkg/Tests/Editor/UnityCliRequestBudgetTests.cs +615 -0
  353. package/unitypkg/Tests/Editor/UnityCliRuntimeBridgeIntegrationTests.cs +520 -0
  354. package/unitypkg/Tests/Editor/UnityCliToolCatalogTests.cs +562 -0
  355. package/unitypkg/Tests/Runtime/UnityCli.Runtime.Tests.asmdef +23 -0
  356. package/unitypkg/Tests/Runtime/UnityCliRuntimeCatalogValidationTests.cs +29 -0
  357. package/unitypkg/package.json +1 -1
  358. package/contracts/static-capability-catalog.json +0 -48226
  359. package/dist/bridge/domains/diagnose.d.ts +0 -2
  360. package/dist/bridge/domains/diagnose.js +0 -11
  361. package/dist/bridge/domains/diagnose.js.map +0 -1
  362. package/dist/bridge/domains/extension.d.ts +0 -8
  363. package/dist/bridge/domains/extension.js +0 -20
  364. package/dist/bridge/domains/extension.js.map +0 -1
  365. package/dist/capabilities.d.ts +0 -5
  366. package/dist/capabilities.js +0 -84
  367. package/dist/capabilities.js.map +0 -1
  368. package/dist/static-capability-catalog.d.ts +0 -13
  369. package/dist/static-capability-catalog.js +0 -41
  370. package/dist/static-capability-catalog.js.map +0 -1
  371. package/dist/tools/diagnose/capabilities.d.ts +0 -13
  372. package/dist/tools/diagnose/capabilities.js +0 -34
  373. package/dist/tools/diagnose/capabilities.js.map +0 -1
  374. package/dist/tools/package/info.d.ts +0 -19
  375. package/dist/tools/package/info.js +0 -27
  376. package/dist/tools/package/info.js.map +0 -1
  377. package/dist/tools/package/list.d.ts +0 -27
  378. package/dist/tools/package/list.js +0 -21
  379. package/dist/tools/package/list.js.map +0 -1
  380. package/dist/tools/package/registry-get.d.ts +0 -10
  381. package/dist/tools/package/registry-get.js +0 -22
  382. package/dist/tools/package/registry-get.js.map +0 -1
  383. package/dist/tools/package/scoped-registry-list.d.ts +0 -14
  384. package/dist/tools/package/scoped-registry-list.js +0 -22
  385. package/dist/tools/package/scoped-registry-list.js.map +0 -1
  386. package/dist/tools/package/search.d.ts +0 -17
  387. package/dist/tools/package/search.js +0 -39
  388. package/dist/tools/package/search.js.map +0 -1
  389. package/dist/tools/package/status.d.ts +0 -21
  390. package/dist/tools/package/status.js +0 -37
  391. package/dist/tools/package/status.js.map +0 -1
  392. package/dist/tools/tools/describe.d.ts +0 -27
  393. package/dist/tools/tools/describe.js +0 -35
  394. package/dist/tools/tools/describe.js.map +0 -1
  395. package/dist/tools/tools/group-list.d.ts +0 -16
  396. package/dist/tools/tools/group-list.js +0 -38
  397. package/dist/tools/tools/group-list.js.map +0 -1
  398. package/dist/tools/tools/group.d.ts +0 -13
  399. package/dist/tools/tools/group.js +0 -34
  400. package/dist/tools/tools/group.js.map +0 -1
  401. package/dist/tools/tools/pagination.d.ts +0 -17
  402. package/dist/tools/tools/pagination.js +0 -28
  403. package/dist/tools/tools/pagination.js.map +0 -1
  404. package/dist/tools/tools/search.d.ts +0 -15
  405. package/dist/tools/tools/search.js +0 -42
  406. package/dist/tools/tools/search.js.map +0 -1
  407. package/unitypkg/Editor/Tools/diagnose/preflight.cs +0 -269
  408. package/unitypkg/Editor/Tools/editor/console.cs +0 -26
  409. package/unitypkg/Editor/Tools/extension/status.cs +0 -87
  410. package/unitypkg/Editor/Tools/go/inspect.cs +0 -49
  411. package/unitypkg/Editor/Tools/graphics/pipeline-quality.cs +0 -24
  412. package/unitypkg/Editor/Tools/graphics/probe-positions.cs +0 -58
  413. package/unitypkg/Editor/Tools/graphics/settings.cs +0 -104
  414. package/unitypkg/Editor/Tools/graphics/stats-counters.cs +0 -28
  415. package/unitypkg/Editor/Tools/graphics/stats-memory.cs +0 -21
  416. package/unitypkg/Editor/Tools/instances/list.cs +0 -71
  417. package/unitypkg/Editor/Tools/physics/raycast-all.cs +0 -67
  418. package/unitypkg/Editor/Tools/scene/build-settings.cs +0 -69
  419. package/unitypkg/Editor/Tools/scene/data.cs +0 -125
  420. package/unitypkg/Editor/Tools/tests/list.cs +0 -98
@@ -5,6 +5,7 @@ using System.Collections.Generic;
5
5
  using System.Globalization;
6
6
  using System.Linq;
7
7
  using System.Reflection;
8
+ using System.Text;
8
9
  using UnityEditor;
9
10
  using UnityEngine;
10
11
 
@@ -15,26 +16,100 @@ namespace UnityCli
15
16
  /// <summary>类型 JSON Schema 生成器:为 live C# / Unity 类型生成带 $defs 的结构化 schema。</summary>
16
17
  internal static class TypeSchemaGenerator
17
18
  {
18
- public static Dictionary<string, object> Generate(string typeName, int maxDepth)
19
+ private const int MaximumSchemaMemberCandidates = 1024;
20
+ private const int MaximumSchemaMemberMetadataUtf8Bytes = 64 * 1024;
21
+ private const int MaximumSchemaCachedTypes = 128;
22
+ private const int MaximumTypeHierarchyDepth = 32;
23
+ private const int MaximumInterfaces = 256;
24
+ private static readonly Dictionary<string, Type> ResolvedTypeCache = new Dictionary<string, Type>(StringComparer.Ordinal);
25
+ private static readonly Dictionary<Type, SchemaMemberCandidate[]> SchemaMemberCache = new Dictionary<Type, SchemaMemberCandidate[]>();
26
+ private static string cacheGeneration;
27
+
28
+ internal sealed class Page
19
29
  {
20
- var type = ResolveType(typeName);
30
+ internal Dictionary<string, object> Data;
31
+ internal bool Truncated;
32
+ internal string NextCursor;
33
+ internal string Generation;
34
+ }
35
+
36
+ /// <summary>根类型先走统一分类;只有 object member 才按 generation-bound raw ordinal 分页。</summary>
37
+ public static Page Generate(string typeName, int maxDepth, int maxMembers, int maxDefinitions, int pageSize, string cursor, UnityCliRequestBudget budget)
38
+ {
39
+ var type = ResolveType(typeName, budget);
21
40
  if (type == null)
22
41
  {
23
- throw new BridgeCommandException("E_TYPE_NOT_FOUND", "Type not found: " + typeName,
24
- "Pass a full type name such as UnityEngine.Transform or UnityEngine.Rigidbody.");
42
+ if (budget.IsExhausted) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
43
+ throw new BridgeCommandException("E_TYPE_NOT_FOUND", "Type not found: " + typeName, "Pass a full type name such as UnityEngine.Transform or UnityEngine.Rigidbody.");
44
+ }
45
+ var context = new SchemaContext(type, maxDepth, maxMembers, maxDefinitions, budget);
46
+ var generation = UnityCliContinuationToken.Fingerprint(UnityCliReadGeneration.Reflection() + "\0" + type.AssemblyQualifiedName + "\0"
47
+ + maxDepth.ToString(CultureInfo.InvariantCulture) + "\0" + maxMembers.ToString(CultureInfo.InvariantCulture) + "\0" + maxDefinitions.ToString(CultureInfo.InvariantCulture));
48
+ if (!IsObjectSchemaRoot(type, context))
49
+ {
50
+ if (!string.IsNullOrEmpty(cursor)) throw StaleSchemaCursor();
51
+ var rootSchema = BuildSchema(type, context, 0, true);
52
+ return new Page
53
+ {
54
+ Truncated = context.Truncated,
55
+ NextCursor = null,
56
+ Generation = generation,
57
+ Data = new Dictionary<string, object>
58
+ {
59
+ { "command", "type.schema" }, { "typeName", FriendlyTypeName(type, context) }, { "schema", rootSchema },
60
+ { "memberCount", 0L }, { "returnedMemberCount", 0L }, { "definitionCount", (long)context.Definitions.Count }
61
+ }
62
+ };
63
+ }
64
+
65
+ var members = GetSchemaMemberCandidates(type, budget);
66
+ generation = UnityCliContinuationToken.Fingerprint(generation + "\0" + SchemaMemberFingerprint(members));
67
+ var index = 0;
68
+ if (!string.IsNullOrEmpty(cursor)) ParseSchemaCursor(cursor, generation, members.Length, out index);
69
+ var rootProperties = new Dictionary<string, object>();
70
+ while (index < members.Length && rootProperties.Count < pageSize)
71
+ {
72
+ if (!context.TryMember()) break;
73
+ var member = members[index];
74
+ if (!budget.TryConsumeReflectionCall() || !budget.TryReserveSerializedUtf8(member.Name)) break;
75
+ rootProperties[member.Name] = SchemaFor(member.ValueType, context, 1);
76
+ index++;
77
+ if (budget.IsExhausted) break;
25
78
  }
26
- var context = new SchemaContext(type, Math.Max(0, maxDepth));
27
- var schema = BuildSchema(type, context, 0, true);
28
- return new Dictionary<string, object>
79
+ if (rootProperties.Count == 0 && index < members.Length) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension ?? "scannedRecords");
80
+ var typeDisplayName = FriendlyTypeName(type, context);
81
+ var schema = new Dictionary<string, object> { { "type", "object" }, { "typeName", typeDisplayName }, { "properties", rootProperties } };
82
+ if (context.Definitions.Count > 0) schema["$defs"] = context.Definitions;
83
+ var next = index < members.Length ? UnityCliContinuationToken.Create("type.schema", generation, "member:" + index.ToString(CultureInfo.InvariantCulture)) : null;
84
+ return new Page
29
85
  {
30
- { "command", "type.schema" },
31
- { "typeName", FriendlyTypeName(type) },
32
- { "schema", schema },
33
- { "definitionCount", (long)context.Definitions.Count },
34
- { "logPath", LogPath() }
86
+ Truncated = next != null || context.Truncated,
87
+ NextCursor = next,
88
+ Generation = generation,
89
+ Data = new Dictionary<string, object>
90
+ {
91
+ { "command", "type.schema" }, { "typeName", typeDisplayName }, { "schema", schema },
92
+ { "memberCount", (long)members.Length }, { "returnedMemberCount", (long)rootProperties.Count },
93
+ { "definitionCount", (long)context.Definitions.Count }
94
+ }
35
95
  };
36
96
  }
37
97
 
98
+ private static void ParseSchemaCursor(string cursor, string generation, int memberCount, out int index)
99
+ {
100
+ string position;
101
+ index = -1;
102
+ if (!UnityCliContinuationToken.TryParse(cursor, "type.schema", generation, out position)
103
+ || !position.StartsWith("member:", StringComparison.Ordinal)
104
+ || !int.TryParse(position.Substring(7), NumberStyles.Integer, CultureInfo.InvariantCulture, out index)
105
+ || index < 0 || index > memberCount) throw StaleSchemaCursor();
106
+ }
107
+
108
+ private static BridgeCommandException StaleSchemaCursor()
109
+ {
110
+ return new BridgeCommandException("E_CONTINUATION_STALE", "The type.schema continuation does not match the current assembly generation and limits.", "Start a new type.schema read without cursor.");
111
+ }
112
+
38
113
  /// <summary>递归生成 schema;复杂嵌套类型通过 $defs/$ref 去重,避免循环类型无限展开。</summary>
39
114
  private static Dictionary<string, object> BuildSchema(Type type, SchemaContext context, int depth, bool isRoot)
40
115
  {
@@ -46,55 +121,71 @@ namespace UnityCli
46
121
  /// <summary>按类型类别构造 JSON Schema 字段。</summary>
47
122
  private static Dictionary<string, object> SchemaProperties(Type type, SchemaContext context, int depth)
48
123
  {
124
+ if (!context.Budget.TryConsumeNode()) { context.Truncated = true; return TruncatedSchema(type, context); }
49
125
  if (type == null) return new Dictionary<string, object> { { "type", "null" } };
50
- if (type.IsByRef || type.IsPointer) return new Dictionary<string, object> { { "type", "string" }, { "typeName", FriendlyTypeName(type) } };
126
+ if (type.IsByRef || type.IsPointer) return new Dictionary<string, object> { { "type", "string" }, { "typeName", FriendlyTypeName(type, context) } };
51
127
  if (type.IsGenericParameter) return new Dictionary<string, object> { { "type", "object" }, { "typeName", type.Name }, { "genericParameter", true } };
52
128
  if (IsNullable(type))
53
129
  {
54
130
  return new Dictionary<string, object>
55
131
  {
56
132
  { "anyOf", new List<object> { SchemaFor(Nullable.GetUnderlyingType(type), context, depth + 1), new Dictionary<string, object> { { "type", "null" } } } },
57
- { "typeName", FriendlyTypeName(type) }
133
+ { "typeName", FriendlyTypeName(type, context) }
58
134
  };
59
135
  }
60
- if (IsPrimitiveLike(type)) return new Dictionary<string, object> { { "type", JsonTypeFor(type) }, { "typeName", FriendlyTypeName(type) } };
61
- if (type.IsEnum) return new Dictionary<string, object> { { "type", "string" }, { "typeName", FriendlyTypeName(type) }, { "enum", Enum.GetNames(type).Cast<object>().ToList() } };
136
+ if (IsPrimitiveLike(type)) return new Dictionary<string, object> { { "type", JsonTypeFor(type) }, { "typeName", FriendlyTypeName(type, context) } };
137
+ if (type.IsEnum)
138
+ {
139
+ if (!context.Budget.TryConsumeReflectionCall()) { context.Truncated = true; return TruncatedSchema(type, context); }
140
+ var values = new List<object>();
141
+ foreach (var value in UnityCliReflectionSnapshot.EnumNames(type, context.Budget, MaximumSchemaMemberCandidates))
142
+ {
143
+ if (!context.TryMember() || !context.Budget.TryReserveSerializedUtf8(value)) { context.Truncated = true; break; }
144
+ values.Add(value);
145
+ }
146
+ var enumSchema = new Dictionary<string, object>
147
+ {
148
+ { "type", "string" }, { "typeName", FriendlyTypeName(type, context) }, { "enum", values },
149
+ { "truncated", context.Truncated }, { "continuationSupported", false }
150
+ };
151
+ if (context.Truncated) enumSchema["truncationReason"] = "non-resumable-enum-truncation";
152
+ return enumSchema;
153
+ }
62
154
 
63
- if (TryGetDictionaryTypes(type, out var keyType, out var dictionaryValueType))
155
+ if (TryGetDictionaryTypes(type, context, out var keyType, out var dictionaryValueType))
64
156
  {
65
- var dictionarySchema = new Dictionary<string, object> { { "type", "object" }, { "typeName", FriendlyTypeName(type) } };
66
- WriteGenericMetadata(dictionarySchema, type);
67
- dictionarySchema["propertyNames"] = DictionaryKeySchema(keyType);
68
- dictionarySchema["additionalProperties"] = depth >= context.MaxDepth ? TruncatedSchema(dictionaryValueType) : SchemaFor(dictionaryValueType, context, depth + 1);
157
+ var dictionarySchema = new Dictionary<string, object> { { "type", "object" }, { "typeName", FriendlyTypeName(type, context) } };
158
+ WriteGenericMetadata(dictionarySchema, type, context);
159
+ dictionarySchema["propertyNames"] = DictionaryKeySchema(keyType, context);
160
+ dictionarySchema["additionalProperties"] = depth >= context.MaxDepth ? TruncatedSchema(dictionaryValueType, context) : SchemaFor(dictionaryValueType, context, depth + 1);
69
161
  return dictionarySchema;
70
162
  }
71
163
 
72
- var elementType = GetEnumerableElementType(type);
164
+ var elementType = GetEnumerableElementType(type, context);
73
165
  if (elementType != null)
74
166
  {
75
- var arraySchema = new Dictionary<string, object> { { "type", "array" }, { "typeName", FriendlyTypeName(type) } };
167
+ var arraySchema = new Dictionary<string, object> { { "type", "array" }, { "typeName", FriendlyTypeName(type, context) } };
76
168
  if (type.IsArray) arraySchema["arrayRank"] = (long)type.GetArrayRank();
77
- WriteGenericMetadata(arraySchema, type);
78
- arraySchema["items"] = depth >= context.MaxDepth ? TruncatedSchema(elementType) : SchemaFor(elementType, context, depth + 1);
169
+ WriteGenericMetadata(arraySchema, type, context);
170
+ arraySchema["items"] = depth >= context.MaxDepth ? TruncatedSchema(elementType, context) : SchemaFor(elementType, context, depth + 1);
79
171
  return arraySchema;
80
172
  }
81
173
 
82
- var objectSchema = new Dictionary<string, object> { { "type", "object" }, { "typeName", FriendlyTypeName(type) } };
83
- WriteGenericMetadata(objectSchema, type);
174
+ var objectSchema = new Dictionary<string, object> { { "type", "object" }, { "typeName", FriendlyTypeName(type, context) } };
175
+ WriteGenericMetadata(objectSchema, type, context);
84
176
  if (depth >= context.MaxDepth)
85
177
  {
86
178
  objectSchema["truncated"] = true;
87
179
  return objectSchema;
88
180
  }
89
181
  var properties = new Dictionary<string, object>();
90
- foreach (var field in type.GetFields(BindingFlags.Instance | BindingFlags.Public).OrderBy(f => f.Name, StringComparer.Ordinal))
91
- {
92
- properties[field.Name] = SchemaFor(field.FieldType, context, depth + 1);
93
- }
94
- foreach (var property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public).OrderBy(p => p.Name, StringComparer.Ordinal))
182
+ foreach (var member in GetSchemaMemberCandidates(type, context.Budget))
95
183
  {
96
- if (property.CanRead && property.GetIndexParameters().Length == 0) properties[property.Name] = SchemaFor(property.PropertyType, context, depth + 1);
184
+ if (!context.TryMember() || !context.Budget.TryConsumeReflectionCall()) { context.Truncated = true; break; }
185
+ properties[member.Name] = SchemaFor(member.ValueType, context, depth + 1);
186
+ if (context.Budget.IsExhausted) break;
97
187
  }
188
+ if (context.Truncated) objectSchema["truncated"] = true;
98
189
  objectSchema["properties"] = properties;
99
190
  return objectSchema;
100
191
  }
@@ -102,14 +193,21 @@ namespace UnityCli
102
193
  /// <summary>复杂类型使用 $ref,基础/集合类型直接内联。</summary>
103
194
  private static object SchemaFor(Type type, SchemaContext context, int depth)
104
195
  {
105
- if (type == null || IsPrimitiveLike(type) || type.IsEnum || type.IsByRef || type.IsPointer || type.IsGenericParameter || IsNullable(type) || TryGetDictionaryTypes(type, out _, out _) || GetEnumerableElementType(type) != null)
196
+ if (type == null || IsPrimitiveLike(type) || type.IsEnum || type.IsByRef || type.IsPointer || type.IsGenericParameter || IsNullable(type)
197
+ || TryGetDictionaryTypes(type, context, out _, out _) || GetEnumerableElementType(type, context) != null)
106
198
  {
107
199
  return BuildSchema(type, context, depth, false);
108
200
  }
109
201
  if (type == context.RootType) return new Dictionary<string, object> { { "$ref", "#" } };
110
- var key = DefinitionKey(type);
202
+ var key = DefinitionKey(type, context);
111
203
  if (!context.Definitions.ContainsKey(key) && !context.BuildingDefinitions.Contains(key))
112
204
  {
205
+ if (!context.Budget.TryReserveSerializedUtf8(key)) { context.Truncated = true; return TruncatedSchema(type, context); }
206
+ if (context.Definitions.Count >= context.MaxDefinitions || !context.Budget.TryConsumeReflectionCall())
207
+ {
208
+ context.Truncated = true;
209
+ return TruncatedSchema(type, context);
210
+ }
113
211
  context.BuildingDefinitions.Add(key);
114
212
  context.Definitions[key] = BuildSchema(type, context, depth, false);
115
213
  context.BuildingDefinitions.Remove(key);
@@ -117,59 +215,101 @@ namespace UnityCli
117
215
  return new Dictionary<string, object> { { "$ref", "#/$defs/" + key } };
118
216
  }
119
217
 
120
- private static Dictionary<string, object> DictionaryKeySchema(Type keyType)
218
+ private static Dictionary<string, object> DictionaryKeySchema(Type keyType, SchemaContext context)
121
219
  {
122
220
  var schema = new Dictionary<string, object> { { "type", "string" } };
123
- if (keyType != null && keyType.IsEnum) schema["enum"] = Enum.GetNames(keyType).Cast<object>().ToList();
124
- else if (keyType != null) schema["typeName"] = FriendlyTypeName(keyType);
221
+ if (keyType != null && keyType.IsEnum)
222
+ {
223
+ if (!context.Budget.TryConsumeReflectionCall()) { context.Truncated = true; schema["truncated"] = true; return schema; }
224
+ var values = new List<object>();
225
+ foreach (var value in UnityCliReflectionSnapshot.EnumNames(keyType, context.Budget, MaximumSchemaMemberCandidates))
226
+ {
227
+ if (!context.TryMember() || !context.Budget.TryReserveSerializedUtf8(value)) { context.Truncated = true; schema["truncated"] = true; break; }
228
+ values.Add(value);
229
+ }
230
+ schema["enum"] = values;
231
+ }
232
+ else if (keyType != null) schema["typeName"] = FriendlyTypeName(keyType, context);
125
233
  return schema;
126
234
  }
127
235
 
128
- private static Dictionary<string, object> TruncatedSchema(Type type)
236
+ private static Dictionary<string, object> TruncatedSchema(Type type, SchemaContext context)
129
237
  {
130
- return new Dictionary<string, object> { { "type", "object" }, { "typeName", FriendlyTypeName(type) }, { "truncated", true } };
238
+ return new Dictionary<string, object> { { "type", "object" }, { "typeName", FriendlyTypeName(type, context) }, { "truncated", true } };
131
239
  }
132
240
 
133
- private static void WriteGenericMetadata(Dictionary<string, object> schema, Type type)
241
+ private static void WriteGenericMetadata(Dictionary<string, object> schema, Type type, SchemaContext context)
134
242
  {
135
243
  if (!type.IsGenericType) return;
136
- schema["genericTypeDefinition"] = FriendlyTypeName(type.GetGenericTypeDefinition());
137
- schema["genericArguments"] = type.GetGenericArguments().Select(FriendlyTypeName).Cast<object>().ToList();
244
+ if (!context.Budget.TryConsumeReflectionCall()) { context.Truncated = true; schema["truncated"] = true; return; }
245
+ schema["genericTypeDefinition"] = FriendlyTypeName(type.GetGenericTypeDefinition(), context);
246
+ var values = new List<object>();
247
+ foreach (var argument in UnityCliReflectionSnapshot.GenericArguments(type, context.Budget, MaximumTypeHierarchyDepth))
248
+ {
249
+ if (!context.TryMember()) { context.Truncated = true; schema["truncated"] = true; break; }
250
+ values.Add(FriendlyTypeName(argument, context));
251
+ if (context.Budget.IsExhausted) { context.Truncated = true; schema["truncated"] = true; break; }
252
+ }
253
+ schema["genericArguments"] = values;
138
254
  }
139
255
 
140
- private static bool TryGetDictionaryTypes(Type type, out Type keyType, out Type valueType)
256
+ private static bool TryGetDictionaryTypes(Type type, SchemaContext context, out Type keyType, out Type valueType)
141
257
  {
142
258
  keyType = null;
143
259
  valueType = null;
144
260
  if (type == null || type == typeof(string)) return false;
145
- foreach (var candidate in type.GetInterfaces().Concat(new[] { type }))
261
+ if (!context.Budget.TryConsumeReflectionCall()) { context.Truncated = true; return false; }
262
+ if (TryGetDictionaryCandidate(type, context, out keyType, out valueType)) return true;
263
+ var examined = 0;
264
+ foreach (var candidate in UnityCliReflectionSnapshot.Interfaces(type, context.Budget, MaximumInterfaces))
146
265
  {
147
- if (!candidate.IsGenericType) continue;
148
- var definition = candidate.GetGenericTypeDefinition();
149
- if (definition == typeof(IDictionary<,>) || definition == typeof(IReadOnlyDictionary<,>))
150
- {
151
- var args = candidate.GetGenericArguments();
152
- keyType = args[0];
153
- valueType = args[1];
154
- return true;
155
- }
266
+ examined++;
267
+ if (!context.Budget.TryConsumeScannedRecord() || !context.Budget.TryConsumeReflectionCall()) { context.Truncated = true; return false; }
268
+ if (TryGetDictionaryCandidate(candidate, context, out keyType, out valueType)) return true;
156
269
  }
157
270
  return false;
158
271
  }
159
272
 
160
- private static Type GetEnumerableElementType(Type type)
273
+ private static bool TryGetDictionaryCandidate(Type candidate, SchemaContext context, out Type keyType, out Type valueType)
274
+ {
275
+ keyType = null;
276
+ valueType = null;
277
+ if (!candidate.IsGenericType) return false;
278
+ var definition = candidate.GetGenericTypeDefinition();
279
+ if (definition != typeof(IDictionary<,>) && definition != typeof(IReadOnlyDictionary<,>)) return false;
280
+ var arguments = UnityCliReflectionSnapshot.GenericArguments(candidate, context.Budget, 2);
281
+ keyType = arguments[0];
282
+ valueType = arguments[1];
283
+ return true;
284
+ }
285
+
286
+ private static Type GetEnumerableElementType(Type type, SchemaContext context)
161
287
  {
162
288
  if (type == null || type == typeof(string)) return null;
289
+ if (!context.Budget.TryConsumeReflectionCall()) { context.Truncated = true; return null; }
163
290
  if (type.IsArray) return type.GetElementType();
164
- foreach (var candidate in type.GetInterfaces().Concat(new[] { type }))
291
+ var direct = GetEnumerableCandidate(type, context);
292
+ if (direct != null) return direct;
293
+ var examined = 0;
294
+ foreach (var candidate in UnityCliReflectionSnapshot.Interfaces(type, context.Budget, MaximumInterfaces))
165
295
  {
166
- if (!candidate.IsGenericType) continue;
167
- var definition = candidate.GetGenericTypeDefinition();
168
- if (definition == typeof(IEnumerable<>) || definition == typeof(IList<>) || definition == typeof(ICollection<>)) return candidate.GetGenericArguments()[0];
296
+ examined++;
297
+ if (!context.Budget.TryConsumeScannedRecord() || !context.Budget.TryConsumeReflectionCall()) { context.Truncated = true; return null; }
298
+ var element = GetEnumerableCandidate(candidate, context);
299
+ if (element != null) return element;
169
300
  }
170
301
  return typeof(IEnumerable).IsAssignableFrom(type) ? typeof(object) : null;
171
302
  }
172
303
 
304
+ private static Type GetEnumerableCandidate(Type candidate, SchemaContext context)
305
+ {
306
+ if (!candidate.IsGenericType) return null;
307
+ var definition = candidate.GetGenericTypeDefinition();
308
+ return definition == typeof(IEnumerable<>) || definition == typeof(IList<>) || definition == typeof(ICollection<>)
309
+ ? UnityCliReflectionSnapshot.GenericArguments(candidate, context.Budget, 1)[0]
310
+ : null;
311
+ }
312
+
173
313
  private static string JsonTypeFor(Type type)
174
314
  {
175
315
  if (type == typeof(bool)) return "boolean";
@@ -182,56 +322,265 @@ namespace UnityCli
182
322
  private static bool IsPrimitiveLike(Type type) => type.IsPrimitive || type == typeof(string) || type == typeof(decimal) || type == typeof(DateTime);
183
323
  private static bool IsNullable(Type type) => type != null && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
184
324
 
185
- private static string FriendlyTypeName(Type type)
325
+ private static bool IsObjectSchemaRoot(Type type, SchemaContext context)
326
+ {
327
+ if (!context.Budget.TryConsumeReflectionCall()) throw new UnityCliRequestBudgetExceededException(context.Budget.ExhaustedDimension);
328
+ if (type == null || type.IsByRef || type.IsPointer || type.IsGenericParameter || IsNullable(type) || IsPrimitiveLike(type) || type.IsEnum) return false;
329
+ if (TryGetDictionaryTypes(type, context, out _, out _)) return false;
330
+ if (context.Budget.IsExhausted) throw new UnityCliRequestBudgetExceededException(context.Budget.ExhaustedDimension);
331
+ if (GetEnumerableElementType(type, context) != null) return false;
332
+ if (context.Budget.IsExhausted) throw new UnityCliRequestBudgetExceededException(context.Budget.ExhaustedDimension);
333
+ return true;
334
+ }
335
+
336
+ /// <summary>候选 metadata 按 reflection generation 缓存,构建与命中都对每个已检查成员计费。</summary>
337
+ private static SchemaMemberCandidate[] GetSchemaMemberCandidates(Type type, UnityCliRequestBudget budget)
338
+ {
339
+ var generation = UnityCliReadGeneration.Reflection();
340
+ if (!string.Equals(cacheGeneration, generation, StringComparison.Ordinal))
341
+ {
342
+ ResolvedTypeCache.Clear();
343
+ SchemaMemberCache.Clear();
344
+ cacheGeneration = generation;
345
+ }
346
+ SchemaMemberCandidate[] cached;
347
+ if (SchemaMemberCache.TryGetValue(type, out cached))
348
+ {
349
+ if (!budget.TryConsumeScannedRecords(cached.Length) || !budget.TryConsumeReflectionCalls(cached.Length))
350
+ throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
351
+ return cached;
352
+ }
353
+
354
+ var candidates = new Dictionary<string, SchemaMemberCandidate>(StringComparer.Ordinal);
355
+ var examined = 0;
356
+ var metadataBytes = 0;
357
+ var depth = 0;
358
+ for (var current = type; current != null && current != typeof(object); current = current.BaseType)
359
+ {
360
+ if (depth++ >= MaximumTypeHierarchyDepth) throw new UnityCliRequestBudgetExceededException("reflectionCalls");
361
+ if (!budget.TryConsumeReflectionCall()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
362
+ foreach (var field in UnityCliReflectionSnapshot.DeclaredFields(current, budget, MaximumSchemaMemberCandidates))
363
+ {
364
+ ConsumeSchemaMemberMetadata(field.Name, field.FieldType, budget, ref examined, ref metadataBytes);
365
+ if (!field.IsPublic || field.IsStatic || candidates.ContainsKey(field.Name)) continue;
366
+ candidates[field.Name] = new SchemaMemberCandidate(field.Name, field.FieldType, depth, false);
367
+ }
368
+ foreach (var property in UnityCliReflectionSnapshot.DeclaredProperties(current, budget, MaximumSchemaMemberCandidates))
369
+ {
370
+ ConsumeSchemaMemberMetadata(property.Name, property.PropertyType, budget, ref examined, ref metadataBytes);
371
+ var getter = property.GetGetMethod(false);
372
+ if (getter == null || getter.IsStatic || property.GetIndexParameters().Length != 0) continue;
373
+ SchemaMemberCandidate existing;
374
+ if (candidates.TryGetValue(property.Name, out existing) && existing.Depth != depth) continue;
375
+ candidates[property.Name] = new SchemaMemberCandidate(property.Name, property.PropertyType, depth, true);
376
+ }
377
+ }
378
+ var result = candidates.Values.OrderBy(candidate => candidate.Name, StringComparer.Ordinal).ToArray();
379
+ if (SchemaMemberCache.Count >= MaximumSchemaCachedTypes) SchemaMemberCache.Clear();
380
+ SchemaMemberCache[type] = result;
381
+ return result;
382
+ }
383
+
384
+ private static void ConsumeSchemaMemberMetadata(string name, Type valueType, UnityCliRequestBudget budget, ref int examined, ref int metadataBytes)
385
+ {
386
+ if (examined++ >= MaximumSchemaMemberCandidates) throw new UnityCliRequestBudgetExceededException("scannedRecords");
387
+ if (!budget.TryConsumeScannedRecord() || !budget.TryConsumeReflectionCall()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
388
+ metadataBytes += Encoding.UTF8.GetByteCount(name ?? string.Empty);
389
+ metadataBytes += Encoding.UTF8.GetByteCount(valueType == null ? string.Empty : valueType.AssemblyQualifiedName ?? valueType.Name);
390
+ if (metadataBytes > MaximumSchemaMemberMetadataUtf8Bytes) throw new UnityCliRequestBudgetExceededException("serializedUtf8Bytes");
391
+ }
392
+
393
+ private static string SchemaMemberFingerprint(IEnumerable<SchemaMemberCandidate> members)
394
+ {
395
+ var builder = new StringBuilder();
396
+ foreach (var member in members) builder.Append(member.Name).Append('\0').Append(member.ValueType == null ? string.Empty : member.ValueType.AssemblyQualifiedName).Append('\0');
397
+ return UnityCliContinuationToken.Fingerprint(builder.ToString());
398
+ }
399
+
400
+ private sealed class SchemaMemberCandidate
401
+ {
402
+ internal readonly string Name;
403
+ internal readonly Type ValueType;
404
+ internal readonly int Depth;
405
+ internal readonly bool IsProperty;
406
+
407
+ internal SchemaMemberCandidate(string name, Type valueType, int depth, bool isProperty)
408
+ {
409
+ Name = name;
410
+ ValueType = valueType;
411
+ Depth = depth;
412
+ IsProperty = isProperty;
413
+ }
414
+ }
415
+
416
+ private static string FriendlyTypeName(Type type, SchemaContext context, int depth = 0)
186
417
  {
187
418
  if (type == null) return null;
188
- if (type.IsArray) return FriendlyTypeName(type.GetElementType()) + "[]";
189
- if (!type.IsGenericType) return type.FullName ?? type.Name;
190
- var baseName = type.GetGenericTypeDefinition().FullName ?? type.GetGenericTypeDefinition().Name;
191
- var tick = baseName.IndexOf('`');
192
- if (tick >= 0) baseName = baseName.Substring(0, tick);
193
- return baseName + "<" + string.Join(",", type.GetGenericArguments().Select(FriendlyTypeName).ToArray()) + ">";
419
+ if (depth >= MaximumTypeHierarchyDepth) throw new UnityCliRequestBudgetExceededException("reflectionCalls");
420
+ if (!context.Budget.TryConsumeReflectionCall()) throw new UnityCliRequestBudgetExceededException(context.Budget.ExhaustedDimension);
421
+ string value;
422
+ if (type.IsArray) value = FriendlyTypeName(type.GetElementType(), context, depth + 1) + "[]";
423
+ else if (!type.IsGenericType) value = type.FullName ?? type.Name;
424
+ else
425
+ {
426
+ var definition = type.GetGenericTypeDefinition();
427
+ var baseName = definition.FullName ?? definition.Name;
428
+ var tick = baseName.IndexOf('`');
429
+ if (tick >= 0) baseName = baseName.Substring(0, tick);
430
+ var arguments = UnityCliReflectionSnapshot.GenericArguments(type, context.Budget, MaximumTypeHierarchyDepth);
431
+ var names = new string[arguments.Length];
432
+ for (var index = 0; index < arguments.Length; index++) names[index] = FriendlyTypeName(arguments[index], context, depth + 1);
433
+ value = baseName + "<" + string.Join(",", names) + ">";
434
+ }
435
+ if (!context.Budget.TryReserveSerializedUtf8(value)) throw new UnityCliRequestBudgetExceededException(context.Budget.ExhaustedDimension);
436
+ return value;
437
+ }
438
+
439
+ private static string DefinitionKey(Type type, SchemaContext context)
440
+ {
441
+ var value = FriendlyTypeName(type, context) ?? "Unknown";
442
+ var characters = value.ToCharArray();
443
+ for (var index = 0; index < characters.Length; index++) if (!char.IsLetterOrDigit(characters[index])) characters[index] = '_';
444
+ return new string(characters);
194
445
  }
195
446
 
196
- private static string DefinitionKey(Type type) => new string((FriendlyTypeName(type) ?? "Unknown").Select(c => char.IsLetterOrDigit(c) ? c : '_').ToArray());
197
- internal static Type ResolveType(string fullName) => FindType(fullName);
447
+ internal static Type ResolveType(string fullName)
448
+ {
449
+ return FindType(fullName);
450
+ }
451
+
452
+ /// <summary>assembly generation 改变时整体失效;命中缓存不再重复扫描 AppDomain。</summary>
453
+ internal static Type ResolveType(string fullName, UnityCliRequestBudget budget)
454
+ {
455
+ if (string.IsNullOrEmpty(fullName)) return null;
456
+ var generation = UnityCliReadGeneration.Reflection();
457
+ if (!string.Equals(cacheGeneration, generation, StringComparison.Ordinal))
458
+ {
459
+ ResolvedTypeCache.Clear();
460
+ SchemaMemberCache.Clear();
461
+ cacheGeneration = generation;
462
+ }
463
+ Type cached;
464
+ if (ResolvedTypeCache.TryGetValue(fullName, out cached)) return cached;
465
+ if (!budget.TryConsumeReflectionCall()) return null;
466
+ try { cached = Type.GetType(fullName, false); } catch { cached = null; }
467
+ if (cached == null)
468
+ {
469
+ foreach (var assembly in UnityCliAssemblySnapshot.Get(budget))
470
+ {
471
+ if (!budget.TryConsumeReflectionCall()) return null;
472
+ try { cached = assembly.GetType(fullName, false); } catch { cached = null; }
473
+ if (cached != null) break;
474
+ }
475
+ }
476
+ if (cached != null) ResolvedTypeCache[fullName] = cached;
477
+ return cached;
478
+ }
198
479
 
199
480
  private sealed class SchemaContext
200
481
  {
201
482
  public readonly Type RootType;
202
483
  public readonly int MaxDepth;
484
+ public readonly int MaxMembers;
485
+ public readonly int MaxDefinitions;
486
+ public readonly UnityCliRequestBudget Budget;
203
487
  public readonly Dictionary<string, object> Definitions = new Dictionary<string, object>();
204
488
  public readonly HashSet<string> BuildingDefinitions = new HashSet<string>();
205
- public SchemaContext(Type rootType, int maxDepth) { RootType = rootType; MaxDepth = maxDepth; }
489
+ public int MemberCount;
490
+ public bool Truncated;
491
+
492
+ public SchemaContext(Type rootType, int maxDepth, int maxMembers, int maxDefinitions, UnityCliRequestBudget budget)
493
+ {
494
+ RootType = rootType; MaxDepth = maxDepth; MaxMembers = maxMembers; MaxDefinitions = maxDefinitions; Budget = budget;
495
+ }
496
+
497
+ public bool TryMember()
498
+ {
499
+ if (MemberCount >= MaxMembers) { Truncated = true; return false; }
500
+ MemberCount++;
501
+ return true;
502
+ }
206
503
  }
207
504
  }
208
505
 
209
506
  /// <summary>反射方法查找与调用:不限制 public/private/static/instance/namespace/assembly/Editor/Runtime 范围。</summary>
210
507
  internal static class ReflectionInvoker
211
508
  {
212
- public static List<ReflectionMethodDescriptor> FindMethods(string typeName, string methodName, BindingFlags bindings)
509
+ private const int MaximumExaminedCallMethods = 4096;
510
+ private const int MaximumMatchingCallMethods = 128;
511
+ private const int MaximumCallParameters = 256;
512
+ private const int MaximumCallSignatureUtf8Bytes = 64 * 1024;
513
+ private const int MaximumCallTypeHierarchyDepth = 32;
514
+
515
+ internal sealed class MethodPage
516
+ {
517
+ internal List<ReflectionMethodDescriptor> Methods;
518
+ internal int Total;
519
+ internal string NextCursor;
520
+ internal string Generation;
521
+ internal bool Truncated;
522
+ }
523
+
524
+ /// <summary>MethodInfo array 是唯一 runtime reflection snapshot;过滤和 descriptor 从 raw ordinal 流式推进。</summary>
525
+ public static MethodPage FindMethods(string typeName, string methodName, BindingFlags bindings, int pageSize, string cursor, UnityCliRequestBudget budget)
213
526
  {
214
- var type = TypeSchemaGenerator.ResolveType(typeName);
215
- if (type == null) throw new BridgeCommandException("E_TYPE_NOT_FOUND", "Type not found: " + typeName, "Pass a full type name such as UnityEngine.Transform.");
216
- return type.GetMethods(bindings)
217
- .Where(m => string.IsNullOrEmpty(methodName) || string.Equals(m.Name, methodName, StringComparison.Ordinal))
218
- .Select(method => new ReflectionMethodDescriptor
527
+ var type = TypeSchemaGenerator.ResolveType(typeName, budget);
528
+ if (type == null)
529
+ {
530
+ if (budget.IsExhausted) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
531
+ throw new BridgeCommandException("E_TYPE_NOT_FOUND", "Type not found: " + typeName, "Pass a full type name such as UnityEngine.Transform.");
532
+ }
533
+ if (!budget.TryConsumeReflectionCall()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
534
+ var methods = UnityCliReflectionSnapshot.Methods(type, bindings, budget, MaximumExaminedCallMethods);
535
+ var generation = UnityCliContinuationToken.Fingerprint(UnityCliReadGeneration.Reflection() + "\0" + type.AssemblyQualifiedName + "\0"
536
+ + ((int)bindings).ToString(CultureInfo.InvariantCulture) + "\0" + methodName + "\0" + methods.Length.ToString(CultureInfo.InvariantCulture));
537
+ var index = 0;
538
+ if (!string.IsNullOrEmpty(cursor))
539
+ {
540
+ string position;
541
+ if (!UnityCliContinuationToken.TryParse(cursor, "reflection.method.find", generation, out position)
542
+ || !int.TryParse(position, NumberStyles.Integer, CultureInfo.InvariantCulture, out index) || index < 0 || index > methods.Length)
543
+ throw new BridgeCommandException("E_CONTINUATION_STALE", "The reflection method continuation does not match the current assembly generation and selector.", "Start a new reflection.method.find read without cursor.");
544
+ }
545
+ var pageStartIndex = index;
546
+ var result = new List<ReflectionMethodDescriptor>();
547
+ while (index < methods.Length && result.Count < pageSize)
548
+ {
549
+ if (!budget.TryConsumeScannedRecord() || !budget.TryConsumeReflectionCall()) break;
550
+ var method = methods[index];
551
+ var name = method.Name;
552
+ if (!string.IsNullOrEmpty(methodName) && !string.Equals(name, methodName, StringComparison.Ordinal)) { index++; continue; }
553
+ if (!budget.TryConsumeNode() || !budget.TryConsumeReflectionCall()) break;
554
+ var parameters = method.GetParameters();
555
+ var returnType = method.ReturnType == null ? null : method.ReturnType.FullName;
556
+ var declaringType = method.DeclaringType == null ? null : method.DeclaringType.FullName;
557
+ if (!budget.TryReserveSerializedUtf8(name) || !budget.TryReserveSerializedUtf8(returnType) || !budget.TryReserveSerializedUtf8(declaringType)) break;
558
+ var parameterResults = new List<ReflectionParameterDescriptor>();
559
+ var parametersComplete = true;
560
+ foreach (var parameter in parameters)
219
561
  {
220
- Name = method.Name,
221
- IsStatic = method.IsStatic,
222
- IsPublic = method.IsPublic,
223
- IsPrivate = method.IsPrivate,
224
- ReturnType = method.ReturnType.FullName,
225
- DeclaringType = method.DeclaringType.FullName,
226
- Parameters = method.GetParameters().Select(parameter => new ReflectionParameterDescriptor
227
- {
228
- Name = parameter.Name,
229
- ParameterType = parameter.ParameterType.FullName,
230
- HasDefaultValue = parameter.HasDefaultValue
231
- }).ToList()
232
- }).ToList();
562
+ if (!budget.TryConsumeNode() || !budget.TryConsumeReflectionCall()) { parametersComplete = false; break; }
563
+ var parameterName = parameter.Name;
564
+ var parameterType = parameter.ParameterType == null ? null : parameter.ParameterType.FullName;
565
+ if (!budget.TryReserveSerializedUtf8(parameterName) || !budget.TryReserveSerializedUtf8(parameterType)) { parametersComplete = false; break; }
566
+ parameterResults.Add(new ReflectionParameterDescriptor { Name = parameterName, ParameterType = parameterType, HasDefaultValue = parameter.HasDefaultValue });
567
+ }
568
+ result.Add(new ReflectionMethodDescriptor
569
+ {
570
+ Name = name, IsStatic = method.IsStatic, IsPublic = method.IsPublic, IsPrivate = method.IsPrivate,
571
+ ReturnType = returnType, DeclaringType = declaringType, ParameterCount = parameters.Length,
572
+ ParametersTruncated = !parametersComplete, Parameters = parameterResults
573
+ });
574
+ index++;
575
+ if (!parametersComplete) break;
576
+ }
577
+ if (index == pageStartIndex && index < methods.Length)
578
+ throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension ?? "scannedRecords");
579
+ var next = index < methods.Length ? UnityCliContinuationToken.Create("reflection.method.find", generation, index.ToString(CultureInfo.InvariantCulture)) : null;
580
+ return new MethodPage { Methods = result, Total = methods.Length, NextCursor = next, Generation = generation, Truncated = next != null || budget.IsExhausted };
233
581
  }
234
582
 
583
+
235
584
  internal sealed class PreparedMethodCall
236
585
  {
237
586
  internal Type Type;
@@ -241,28 +590,50 @@ namespace UnityCli
241
590
  internal object Instance;
242
591
  }
243
592
 
244
- /// <summary>effect 前完整解析 type、overload、参数与 instance;不会调用目标方法。</summary>
245
- public static PreparedMethodCall PrepareCall(string typeName, string methodName, object argsValue, List<string> parameterTypes, Dictionary<string, object> instanceRef, BindingFlags bindings)
593
+ /// <summary>effect 前在同一 request budget 下解析 type、overload、参数与 instance;不会调用目标方法。</summary>
594
+ public static PreparedMethodCall PrepareCall(string typeName, string methodName, object argsValue, List<string> parameterTypes, Dictionary<string, object> instanceRef, BindingFlags bindings, UnityCliRequestBudget budget)
246
595
  {
247
- var type = TypeSchemaGenerator.ResolveType(typeName);
248
- if (type == null) throw new BridgeCommandException("E_TYPE_NOT_FOUND", "Type not found: " + typeName, "Pass a full type name.");
249
- var method = ResolveCallMethod(type, methodName, argsValue, parameterTypes, bindings);
250
- RequireSynchronousResult(method);
251
- var parameters = method.GetParameters();
252
- var arguments = ResolveCallArguments(argsValue, parameters);
596
+ if (budget == null) throw new ArgumentNullException("budget");
597
+ var type = TypeSchemaGenerator.ResolveType(typeName, budget);
598
+ if (type == null)
599
+ {
600
+ if (budget.IsExhausted) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
601
+ throw new BridgeCommandException("E_TYPE_NOT_FOUND", "Type not found: " + typeName, "Pass a full type name.");
602
+ }
603
+ var candidate = ResolveCallMethod(type, methodName, argsValue, parameterTypes, bindings, budget);
604
+ RequireSynchronousResult(candidate.Method, budget);
605
+ var arguments = ResolveCallArguments(argsValue, candidate.Parameters, budget);
253
606
  object instance = null;
254
- if (!method.IsStatic)
607
+ if (!candidate.Method.IsStatic)
255
608
  {
256
- instance = ResolveCallInstance(instanceRef, type);
609
+ instance = ResolveCallInstance(instanceRef, type, budget);
257
610
  if (instance == null) throw new BridgeCommandException("E_METHOD_INSTANCE_REQUIRED", "Instance method " + methodName + " requires an instance reference.", "Pass instanceRef with instanceId/assetPath/path.");
258
611
  }
259
- return new PreparedMethodCall { Type = type, Method = method, Parameters = parameters, Arguments = arguments, Instance = instance };
612
+ PrebudgetInvocationResult(type, candidate.Method, candidate.Parameters, budget);
613
+ return new PreparedMethodCall { Type = type, Method = candidate.Method, Parameters = candidate.Parameters, Arguments = arguments, Instance = instance };
614
+ }
615
+
616
+ /// <summary>effect 前为固定审计外壳和参数回读保留最低容量,避免确定可知的 post-effect 预算失败。</summary>
617
+ private static void PrebudgetInvocationResult(Type type, MethodInfo method, ParameterInfo[] parameters, UnityCliRequestBudget budget)
618
+ {
619
+ budget.CheckpointOrThrow();
620
+ var minimumNodes = checked(1L + parameters.Length);
621
+ if (budget.RemainingNodes < minimumNodes) throw new UnityCliRequestBudgetExceededException("nodes");
622
+ if (budget.RemainingGetters < parameters.Length) throw new UnityCliRequestBudgetExceededException("getters");
623
+ if (budget.RemainingReflectionCalls < parameters.Length) throw new UnityCliRequestBudgetExceededException("reflectionCalls");
624
+ var fixedBytes = 512L + checked((long)parameters.Length * 256L);
625
+ if (!budget.TryReserveSerializedUtf8(type.FullName)
626
+ || !budget.TryReserveSerializedUtf8(method.Name)
627
+ || !budget.TryReserveSerializedUtf8(method.ReturnType.FullName)
628
+ || !budget.TryReserveSerializedUtf8Bytes(fixedBytes))
629
+ throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension ?? "serializedUtf8Bytes");
260
630
  }
261
631
 
262
632
  /// <summary>跨 effect barrier 后只执行一次已准备的方法并返回审计值树。</summary>
263
- public static Dictionary<string, object> InvokePrepared(PreparedMethodCall call)
633
+ public static Dictionary<string, object> InvokePrepared(PreparedMethodCall call, UnityCliRequestBudget budget)
264
634
  {
265
635
  if (call == null) throw new ArgumentNullException("call");
636
+ if (budget == null) throw new ArgumentNullException("budget");
266
637
  var data = new Dictionary<string, object>
267
638
  {
268
639
  { "typeName", call.Type.FullName }, { "methodName", call.Method.Name },
@@ -273,8 +644,8 @@ namespace UnityCli
273
644
  var result = call.Method.Invoke(call.Instance, call.Arguments);
274
645
  if (result is System.Threading.Tasks.Task || result is IEnumerator || result is AsyncOperation || result is CustomYieldInstruction)
275
646
  throw new BridgeCommandException("E_METHOD_COMPLETION_UNPROVABLE", "The method returned an asynchronous/yield operation that has no immediate completion proof.", "Use a dedicated Tool with an explicit asynchronous completion predicate.");
276
- data["result"] = BuildCallResult(result);
277
- data["parameters"] = call.Parameters.Select((parameter, index) => (object)new Dictionary<string, object> { { "name", parameter.Name }, { "value", BuildCallResult(call.Arguments[index]) } }).ToList();
647
+ data["result"] = BuildCallResult(result, budget);
648
+ data["parameters"] = call.Parameters.Select((parameter, index) => (object)new Dictionary<string, object> { { "name", parameter.Name }, { "value", BuildCallResult(call.Arguments[index], budget) } }).ToList();
278
649
  }
279
650
  catch (TargetInvocationException tie)
280
651
  {
@@ -286,6 +657,8 @@ namespace UnityCli
286
657
  new[] { "Inspect the method contract and arguments before submitting a new request." });
287
658
  }
288
659
  catch (BridgeCommandException) { throw; }
660
+ catch (UnityCliRequestBudgetExceededException) { throw; }
661
+ catch (OperationCanceledException) { throw; }
289
662
  catch (Exception exception)
290
663
  {
291
664
  throw new BridgeCommandException(
@@ -298,8 +671,9 @@ namespace UnityCli
298
671
  }
299
672
 
300
673
  /// <summary>completionProbe 只允许同步方法;void 由内部 observer 的 fresh after proof 覆盖。</summary>
301
- private static void RequireSynchronousResult(MethodInfo method)
674
+ private static void RequireSynchronousResult(MethodInfo method, UnityCliRequestBudget budget)
302
675
  {
676
+ if (!budget.TryConsumeReflectionCall()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
303
677
  var returnType = method.ReturnType;
304
678
  var fullName = returnType.FullName ?? string.Empty;
305
679
  if (typeof(System.Threading.Tasks.Task).IsAssignableFrom(returnType)
@@ -315,39 +689,49 @@ namespace UnityCli
315
689
  }
316
690
 
317
691
  // 参数可为位置数组或按参数名索引的对象;值转换复用 ObjectPatch 的 typed conversion。
318
- /// <summary>按显式参数类型或唯一参数数量选择 overload,歧义时要求调用方精确指定签名。</summary>
319
- private static MethodInfo ResolveCallMethod(Type type, string methodName, object argsValue, List<string> parameterTypeNames, BindingFlags bindings)
692
+ /// <summary>按 raw declared-method ordinal 流式筛选;候选、参数和签名都有硬上限。</summary>
693
+ private static CallCandidate ResolveCallMethod(Type type, string methodName, object argsValue, List<string> parameterTypeNames, BindingFlags bindings, UnityCliRequestBudget budget)
320
694
  {
321
- var candidates = type.GetMethods(bindings)
322
- .Where(method => string.Equals(method.Name, methodName, StringComparison.Ordinal) && !method.ContainsGenericParameters)
323
- .ToList();
324
- if (candidates.Count == 0)
325
- {
326
- throw new BridgeCommandException("E_METHOD_NOT_FOUND", "Method not found: " + type.FullName + "." + methodName,
327
- "Run reflection.method.find to discover available methods.");
328
- }
329
-
330
- if (parameterTypeNames != null && parameterTypeNames.Count > 0)
695
+ var selectedParameterTypes = new List<Type>();
696
+ if (parameterTypeNames != null)
331
697
  {
332
- var parameterTypes = new List<Type>();
333
698
  foreach (var parameterTypeName in parameterTypeNames)
334
699
  {
335
- var parameterType = TypeSchemaGenerator.ResolveType(parameterTypeName);
700
+ var parameterType = TypeSchemaGenerator.ResolveType(parameterTypeName, budget);
336
701
  if (parameterType == null)
337
702
  {
338
- throw new BridgeCommandException("E_TYPE_NOT_FOUND", "Parameter type not found: " + parameterTypeName,
339
- "Use the full parameterType returned by reflection.method.find.");
703
+ if (budget.IsExhausted) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
704
+ throw new BridgeCommandException("E_TYPE_NOT_FOUND", "Parameter type not found: " + parameterTypeName, "Use the full parameterType returned by reflection.method.find.");
340
705
  }
341
- parameterTypes.Add(parameterType);
706
+ selectedParameterTypes.Add(parameterType);
342
707
  }
343
- candidates = candidates.Where(method => method.GetParameters().Select(parameter => parameter.ParameterType).SequenceEqual(parameterTypes)).ToList();
344
708
  }
345
- else
709
+ var argumentCount = argsValue is List<object> positional ? positional.Count : argsValue is Dictionary<string, object> named ? named.Count : 0;
710
+ var candidates = new List<CallCandidate>();
711
+ var examinedMethods = 0;
712
+ var depth = 0;
713
+ for (var current = type; current != null; current = current.BaseType)
346
714
  {
347
- var argumentCount = argsValue is List<object> positional ? positional.Count
348
- : argsValue is Dictionary<string, object> named ? named.Count
349
- : 0;
350
- candidates = candidates.Where(method => method.GetParameters().Length == argumentCount).ToList();
715
+ if (depth++ >= MaximumCallTypeHierarchyDepth) throw new UnityCliRequestBudgetExceededException("reflectionCalls");
716
+ if (!budget.TryConsumeReflectionCall()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
717
+ foreach (var method in UnityCliReflectionSnapshot.DeclaredMethods(current, budget, MaximumExaminedCallMethods))
718
+ {
719
+ if (examinedMethods++ >= MaximumExaminedCallMethods) throw new UnityCliRequestBudgetExceededException("scannedRecords");
720
+ if (!budget.TryConsumeScannedRecord() || !budget.TryConsumeReflectionCall()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
721
+ if (!MethodMatchesBindings(method, bindings, current == type) || !string.Equals(method.Name, methodName, StringComparison.Ordinal) || method.ContainsGenericParameters) continue;
722
+ if (!budget.TryConsumeReflectionCall()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
723
+ var parameters = method.GetParameters();
724
+ if (parameters.Length > MaximumCallParameters) throw new UnityCliRequestBudgetExceededException("reflectionCalls");
725
+ var matches = selectedParameterTypes.Count > 0 ? parameters.Length == selectedParameterTypes.Count : parameters.Length == argumentCount;
726
+ for (var index = 0; index < parameters.Length; index++)
727
+ {
728
+ if (!budget.TryConsumeScannedRecord() || !budget.TryConsumeReflectionCall()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
729
+ if (selectedParameterTypes.Count > 0 && parameters[index].ParameterType != selectedParameterTypes[index]) matches = false;
730
+ }
731
+ if (!matches) continue;
732
+ if (candidates.Count >= MaximumMatchingCallMethods) throw new UnityCliRequestBudgetExceededException("scannedRecords");
733
+ candidates.Add(new CallCandidate(method, parameters));
734
+ }
351
735
  }
352
736
 
353
737
  if (candidates.Count == 1) return candidates[0];
@@ -358,13 +742,30 @@ namespace UnityCli
358
742
  new[] { "Run reflection.method.find and pass the returned parameterType values as parameterTypes." });
359
743
  }
360
744
 
361
- var signatures = candidates.Select(method => method.Name + "(" + string.Join(",", method.GetParameters().Select(parameter => parameter.ParameterType.FullName).ToArray()) + ")").ToList();
745
+ var signatures = new List<string>();
746
+ var signatureBytes = 0;
747
+ foreach (var candidate in candidates)
748
+ {
749
+ var parameterNames = new string[candidate.Parameters.Length];
750
+ for (var index = 0; index < candidate.Parameters.Length; index++) parameterNames[index] = candidate.Parameters[index].ParameterType.FullName;
751
+ var signature = candidate.Method.Name + "(" + string.Join(",", parameterNames) + ")";
752
+ signatureBytes += Encoding.UTF8.GetByteCount(signature);
753
+ if (signatureBytes > MaximumCallSignatureUtf8Bytes || !budget.TryReserveSerializedUtf8(signature)) throw new UnityCliRequestBudgetExceededException("serializedUtf8Bytes");
754
+ signatures.Add(signature);
755
+ }
362
756
  throw new BridgeCommandException("E_METHOD_AMBIGUOUS", "Multiple overloads match: " + type.FullName + "." + methodName,
363
757
  new Dictionary<string, object> { { "typeName", type.FullName }, { "methodName", methodName }, { "candidateSignatures", signatures } },
364
758
  new[] { "Run reflection.method.find and pass one returned signature's parameterType values as parameterTypes." });
365
759
  }
366
760
 
367
- private static object[] ResolveCallArguments(object argsValue, ParameterInfo[] parameters)
761
+ private static bool MethodMatchesBindings(MethodInfo method, BindingFlags bindings, bool declaredOnTarget)
762
+ {
763
+ if (method.IsStatic && (bindings & BindingFlags.Static) == 0 || !method.IsStatic && (bindings & BindingFlags.Instance) == 0) return false;
764
+ if (method.IsPublic && (bindings & BindingFlags.Public) == 0 || !method.IsPublic && (bindings & BindingFlags.NonPublic) == 0) return false;
765
+ return declaredOnTarget || !method.IsPrivate;
766
+ }
767
+
768
+ private static object[] ResolveCallArguments(object argsValue, ParameterInfo[] parameters, UnityCliRequestBudget budget)
368
769
  {
369
770
  if (parameters.Length == 0) return new object[0];
370
771
  var parsed = argsValue as List<object> ?? new List<object>();
@@ -372,6 +773,7 @@ namespace UnityCli
372
773
  var result = new object[parameters.Length];
373
774
  for (var i = 0; i < parameters.Length; i++)
374
775
  {
776
+ if (!budget.TryConsumeScannedRecord() || !budget.TryConsumeReflectionCall()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
375
777
  var parameter = parameters[i];
376
778
  object value = null;
377
779
  if (objForm != null && objForm.ContainsKey(parameter.Name)) value = objForm[parameter.Name];
@@ -383,7 +785,7 @@ namespace UnityCli
383
785
  return result;
384
786
  }
385
787
 
386
- private static object ResolveCallInstance(Dictionary<string, object> instanceRef, Type expectedType)
788
+ private static object ResolveCallInstance(Dictionary<string, object> instanceRef, Type expectedType, UnityCliRequestBudget budget)
387
789
  {
388
790
  if (instanceRef == null) return null;
389
791
  if (instanceRef.TryGetValue("instanceId", out var iid))
@@ -391,30 +793,65 @@ namespace UnityCli
391
793
  var id = Convert.ToInt32(iid, CultureInfo.InvariantCulture);
392
794
  if (id != 0)
393
795
  {
796
+ if (!budget.TryConsumeGetter()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
394
797
  var obj = EditorUtility.InstanceIDToObject(id);
395
798
  if (obj != null && expectedType.IsInstanceOfType(obj)) return obj;
396
799
  }
397
800
  }
398
801
  if (instanceRef.TryGetValue("assetPath", out var ap) && ap is string path)
399
802
  {
803
+ if (!budget.TryConsumeGetter()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
400
804
  var obj = AssetDatabase.LoadMainAssetAtPath(path);
401
805
  if (obj != null && expectedType.IsInstanceOfType(obj)) return obj;
402
806
  }
403
807
  if (instanceRef.TryGetValue("path", out var p) && p is string goPath && typeof(Component).IsAssignableFrom(expectedType))
404
808
  {
405
- var go = ObjectRefResolver.FindGameObjectByPath(goPath);
406
- if (go != null) return go.GetComponent(expectedType);
809
+ var reference = new ReflectionInstanceReference { Path = goPath };
810
+ var go = ObjectRefResolver.ResolveGameObject(reference, budget);
811
+ if (go != null)
812
+ {
813
+ if (!budget.TryConsumeGetter()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
814
+ return go.GetComponent(expectedType);
815
+ }
407
816
  }
408
817
  return null;
409
818
  }
410
819
 
411
- private static object BuildCallResult(object result)
820
+ private sealed class CallCandidate
821
+ {
822
+ internal readonly MethodInfo Method;
823
+ internal readonly ParameterInfo[] Parameters;
824
+
825
+ internal CallCandidate(MethodInfo method, ParameterInfo[] parameters)
826
+ {
827
+ Method = method;
828
+ Parameters = parameters;
829
+ }
830
+ }
831
+
832
+ private sealed class ReflectionInstanceReference : IUnityCliObjectReferenceInput
833
+ {
834
+ public int? InstanceId { get { return null; } }
835
+ public string AssetPath { get { return null; } }
836
+ public string Guid { get { return null; } }
837
+ public string Path { get; set; }
838
+ public string Name { get { return null; } }
839
+ public string Target { get { return null; } }
840
+ public int? ComponentInstanceId { get { return null; } }
841
+ public int? ComponentIndex { get { return null; } }
842
+ public string Component { get { return null; } }
843
+ public string ComponentType { get { return null; } }
844
+ }
845
+
846
+ private static object BuildCallResult(object result, UnityCliRequestBudget budget)
412
847
  {
413
848
  if (result == null) return null;
414
849
  try
415
850
  {
416
- return ObjectSerializer.Serialize(result, new ObjectSerializer.Options { MaxDepth = 2, IncludeFields = false, IncludeProperties = true, DeepSerialization = false });
851
+ return ObjectSerializer.Serialize(result, new ObjectSerializer.Options { MaxDepth = 2, IncludeFields = false, IncludeProperties = true, DeepSerialization = false }, budget);
417
852
  }
853
+ catch (UnityCliRequestBudgetExceededException) { throw; }
854
+ catch (OperationCanceledException) { throw; }
418
855
  catch (Exception exception)
419
856
  {
420
857
  // 反射返回值不可投影时显式暴露原因,不能 ToString() 制造不稳定业务数据。
@@ -431,6 +868,8 @@ namespace UnityCli
431
868
  public bool IsPrivate { get; set; }
432
869
  public string ReturnType { get; set; }
433
870
  public string DeclaringType { get; set; }
871
+ public long ParameterCount { get; set; }
872
+ public bool ParametersTruncated { get; set; }
434
873
  public List<ReflectionParameterDescriptor> Parameters { get; set; }
435
874
  }
436
875
 
@@ -473,5 +912,77 @@ namespace UnityCli
473
912
  return null;
474
913
  }
475
914
  }
915
+
916
+ /// <summary>先冻结带硬上限的反射集合,再允许调用方逐项计费或分页。</summary>
917
+ internal static class UnityCliReflectionSnapshot
918
+ {
919
+ internal static FieldInfo[] DeclaredFields(Type type, UnityCliRequestBudget budget, int maximum)
920
+ {
921
+ if (type == null) throw new ArgumentNullException("type");
922
+ budget.CheckpointOrThrow();
923
+ var items = type.GetTypeInfo().DeclaredFields.ToArray();
924
+ EnsureBounded(items.Length, maximum);
925
+ return items;
926
+ }
927
+
928
+ internal static PropertyInfo[] DeclaredProperties(Type type, UnityCliRequestBudget budget, int maximum)
929
+ {
930
+ if (type == null) throw new ArgumentNullException("type");
931
+ budget.CheckpointOrThrow();
932
+ var items = type.GetTypeInfo().DeclaredProperties.ToArray();
933
+ EnsureBounded(items.Length, maximum);
934
+ return items;
935
+ }
936
+
937
+ internal static MethodInfo[] DeclaredMethods(Type type, UnityCliRequestBudget budget, int maximum)
938
+ {
939
+ if (type == null) throw new ArgumentNullException("type");
940
+ budget.CheckpointOrThrow();
941
+ var items = type.GetTypeInfo().DeclaredMethods.ToArray();
942
+ EnsureBounded(items.Length, maximum);
943
+ return items;
944
+ }
945
+
946
+ internal static MethodInfo[] Methods(Type type, BindingFlags bindings, UnityCliRequestBudget budget, int maximum)
947
+ {
948
+ if (type == null) throw new ArgumentNullException("type");
949
+ budget.CheckpointOrThrow();
950
+ var items = type.GetMethods(bindings);
951
+ EnsureBounded(items.Length, maximum);
952
+ return items;
953
+ }
954
+
955
+ internal static Type[] Interfaces(Type type, UnityCliRequestBudget budget, int maximum)
956
+ {
957
+ if (type == null) throw new ArgumentNullException("type");
958
+ budget.CheckpointOrThrow();
959
+ var items = type.GetInterfaces();
960
+ EnsureBounded(items.Length, maximum);
961
+ return items;
962
+ }
963
+
964
+ internal static Type[] GenericArguments(Type type, UnityCliRequestBudget budget, int maximum)
965
+ {
966
+ if (type == null) throw new ArgumentNullException("type");
967
+ budget.CheckpointOrThrow();
968
+ var items = type.GetGenericArguments();
969
+ EnsureBounded(items.Length, maximum);
970
+ return items;
971
+ }
972
+
973
+ internal static string[] EnumNames(Type type, UnityCliRequestBudget budget, int maximum)
974
+ {
975
+ if (type == null) throw new ArgumentNullException("type");
976
+ budget.CheckpointOrThrow();
977
+ var items = Enum.GetNames(type);
978
+ EnsureBounded(items.Length, maximum);
979
+ return items;
980
+ }
981
+
982
+ private static void EnsureBounded(int count, int maximum)
983
+ {
984
+ if (maximum < 0 || count > maximum) throw new UnityCliRequestBudgetExceededException("nodes");
985
+ }
986
+ }
476
987
  }
477
988
  #endif