@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.
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 +35 -42
  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
@@ -38,7 +38,7 @@ namespace UnityCli
38
38
  return new Dictionary<string, object> { { "type", "object" }, { "additionalProperties", AnyJsonSchema() } };
39
39
  Type anyElement;
40
40
  if (TryElementType(rawType, out anyElement))
41
- return new Dictionary<string, object> { { "type", "array" }, { "items", AnyJsonSchema() } };
41
+ return ItemsRangeSchema(new Dictionary<string, object> { { "type", "array" }, { "items", AnyJsonSchema() } }, member, path);
42
42
  return AnyJsonSchema();
43
43
  }
44
44
 
@@ -46,10 +46,10 @@ namespace UnityCli
46
46
  if (nullableType != null)
47
47
  {
48
48
  if (member != null && member.IsDefined(typeof(UnityCliRequiredAttribute), true))
49
- return BuildSchema(nullableType, path, definitions, null, false);
49
+ return BuildSchema(nullableType, path, definitions, member, false);
50
50
  return new Dictionary<string, object>
51
51
  {
52
- { "oneOf", new object[] { BuildSchema(nullableType, path, definitions, null, false), new Dictionary<string, object> { { "type", "null" } } } }
52
+ { "oneOf", new object[] { BuildSchema(nullableType, path, definitions, member, false), new Dictionary<string, object> { { "type", "null" } } } }
53
53
  };
54
54
  }
55
55
 
@@ -67,8 +67,8 @@ namespace UnityCli
67
67
  }
68
68
  if (rawType == typeof(char)) return TypeSchema("string");
69
69
  if (rawType == typeof(bool)) return TypeSchema("boolean");
70
- if (IsInteger(rawType)) return IntegerSchema(rawType);
71
- if (IsNumber(rawType)) return TypeSchema("number");
70
+ if (IsInteger(rawType)) return NumericRangeSchema(IntegerSchema(rawType), member, path);
71
+ if (IsNumber(rawType)) return NumericRangeSchema(TypeSchema("number"), member, path);
72
72
  if (rawType.IsEnum)
73
73
  {
74
74
  return new Dictionary<string, object>
@@ -91,11 +91,11 @@ namespace UnityCli
91
91
  Type elementType;
92
92
  if (TryElementType(rawType, out elementType))
93
93
  {
94
- return new Dictionary<string, object>
94
+ return ItemsRangeSchema(new Dictionary<string, object>
95
95
  {
96
96
  { "type", "array" },
97
97
  { "items", BuildSchema(elementType, path + "[]", definitions, member, false) }
98
- };
98
+ }, member, path);
99
99
  }
100
100
 
101
101
  if (rawType.IsPointer || rawType.IsByRef || rawType.IsGenericTypeDefinition || rawType.IsInterface || rawType.IsAbstract)
@@ -260,6 +260,35 @@ namespace UnityCli
260
260
  };
261
261
  }
262
262
 
263
+ /// <summary>将声明式集合范围投影为 JSON Schema;非法特性使 descriptor 派生失败。</summary>
264
+ private static Dictionary<string, object> ItemsRangeSchema(Dictionary<string, object> schema, MemberInfo member, string path)
265
+ {
266
+ var range = member == null ? null : member.GetCustomAttributes(typeof(UnityCliItemsRangeAttribute), true).Cast<UnityCliItemsRangeAttribute>().SingleOrDefault();
267
+ if (range == null) return schema;
268
+ if (range.Minimum < 0 || range.Minimum > range.Maximum) throw Unsupported(path, "items range is invalid");
269
+ schema["minItems"] = range.Minimum;
270
+ schema["maxItems"] = range.Maximum;
271
+ return schema;
272
+ }
273
+
274
+ /// <summary>将声明式业务范围收窄到 CLR 可表示范围;非法特性使 descriptor 派生失败。</summary>
275
+ private static Dictionary<string, object> NumericRangeSchema(Dictionary<string, object> schema, MemberInfo member, string path)
276
+ {
277
+ var range = member == null ? null : member.GetCustomAttributes(typeof(UnityCliRangeAttribute), true).Cast<UnityCliRangeAttribute>().SingleOrDefault();
278
+ if (range == null) return schema;
279
+ if (range.Minimum > range.Maximum) throw Unsupported(path, "numeric range minimum exceeds maximum");
280
+ decimal existingMinimum;
281
+ decimal existingMaximum;
282
+ var minimum = schema.TryGetValue("minimum", out var rawMinimum) ? Convert.ToDecimal(rawMinimum) : decimal.MinValue;
283
+ var maximum = schema.TryGetValue("maximum", out var rawMaximum) ? Convert.ToDecimal(rawMaximum) : decimal.MaxValue;
284
+ existingMinimum = Math.Max(minimum, range.Minimum);
285
+ existingMaximum = Math.Min(maximum, range.Maximum);
286
+ if (existingMinimum > existingMaximum) throw Unsupported(path, "numeric range is outside CLR range");
287
+ schema["minimum"] = existingMinimum;
288
+ schema["maximum"] = existingMaximum;
289
+ return schema;
290
+ }
291
+
263
292
  internal static void IntegerRange(Type type, out decimal minimum, out decimal maximum)
264
293
  {
265
294
  if (type == typeof(byte)) { minimum = byte.MinValue; maximum = byte.MaxValue; return; }
@@ -0,0 +1,30 @@
1
+ #if UNITY_EDITOR
2
+ using System;
3
+ using System.Text;
4
+ using System.Text.RegularExpressions;
5
+
6
+ namespace UnityCli
7
+ {
8
+ /// <summary>按公开 contract profile 校验精确 Tool identity 与统一 UTF-8 byte 预算。</summary>
9
+ internal static class UnityCliToolName
10
+ {
11
+ internal const int MaxUtf8Bytes = 128;
12
+ private const string OfficialPrefix = "UnityPipeline_";
13
+ private static readonly Regex NativePattern = new Regex("^[a-z][a-z0-9-]*(?:\\.[a-z0-9][a-z0-9-]*)*\\z", RegexOptions.CultureInvariant);
14
+ private static readonly Regex OfficialSuffixPattern = new Regex("^[a-z][a-z0-9_-]*(?:\\.[a-z0-9][a-z0-9_-]*)*\\z", RegexOptions.CultureInvariant);
15
+
16
+ internal static void Validate(string name, string contractProfile)
17
+ {
18
+ var validProfile = string.Equals(contractProfile, "native", StringComparison.Ordinal)
19
+ || string.Equals(contractProfile, "official-attribute", StringComparison.Ordinal);
20
+ var validSyntax = string.Equals(contractProfile, "official-attribute", StringComparison.Ordinal)
21
+ ? name != null && name.StartsWith(OfficialPrefix, StringComparison.Ordinal)
22
+ && OfficialSuffixPattern.IsMatch(name.Substring(OfficialPrefix.Length))
23
+ : string.Equals(contractProfile, "native", StringComparison.Ordinal)
24
+ && name != null && NativePattern.IsMatch(name);
25
+ if (!validProfile || string.IsNullOrEmpty(name) || Encoding.UTF8.GetByteCount(name) > MaxUtf8Bytes || !validSyntax)
26
+ throw new InvalidOperationException("Tool name must use the registered Tool syntax and fit 128 UTF-8 bytes.");
27
+ }
28
+ }
29
+ }
30
+ #endif
@@ -0,0 +1,3 @@
1
+ using System.Runtime.CompilerServices;
2
+
3
+ [assembly: InternalsVisibleTo("UnityCli.Editor.Tests")]
@@ -12,14 +12,15 @@ namespace UnityCli
12
12
  {
13
13
  public string AssetPath { get; set; }
14
14
  public string Path { get; set; }
15
- public int? MaxDepth { get; set; }
16
- public int? MaxArrayItems { get; set; }
15
+ [UnityCliRange(0L, 16L)] public int? MaxDepth { get; set; }
16
+ [UnityCliRange(1L, 1000L)] public int? MaxArrayItems { get; set; }
17
17
  public bool? IncludeFields { get; set; }
18
18
  public bool? IncludeProperties { get; set; }
19
19
  public bool? DeepSerialization { get; set; }
20
- public System.Collections.Generic.List<string> Paths { get; set; }
20
+ [UnityCliItemsRange(0, 128)] public System.Collections.Generic.List<string> Paths { get; set; }
21
21
  [UnityCliAnyJson] public object ViewQuery { get; set; }
22
22
  public string NamePattern { get; set; }
23
+ public UnityCliReadBudgetInput Budget { get; set; }
23
24
  }
24
25
 
25
26
  public sealed class Result
@@ -33,7 +34,7 @@ namespace UnityCli
33
34
  {
34
35
  Name = "asset.get-data",
35
36
  Summary = "Deep-read an Assets/* asset's members via ObjectSerializer; use paths or viewQuery for token-safe fields.",
36
- Description = "Deep-read an Assets/* asset's members. For token safety, pass exact paths or one viewQuery; they are mutually exclusive and skip full fields/props expansion. Keep maxDepth low before expanding large assets. Arrays/lists/dicts are bounded by maxArrayItems.",
37
+ Description = "Deep-read one loaded Assets/* object without importing. maxDepth is 0..16, maxArrayItems is 1..1000, and paths accepts at most 128 ordered exact reads. Every path and recursive branch shares the request scan/node/getter/reflection/monotonic-time/UTF-8 budget; defaults are 10000/4096/2048/4096/250 ms/1 MiB and maxima are 100000/20000/10000/20000/2000 ms/8 MiB. Truncated nodes return nextPath. Read-only in Edit and Play Mode.",
37
38
  Group = "asset",
38
39
  DestructiveHint = false,
39
40
  Target = "editor",
@@ -48,22 +49,30 @@ namespace UnityCli
48
49
 
49
50
  public System.Threading.Tasks.ValueTask<Result> ExecuteAsync(Request request, UnityCliProjectToolContext ctx, System.Threading.CancellationToken ct = default)
50
51
  {
52
+ var budget = ctx.UseReadBudget(request.Budget);
53
+ budget.CheckpointOrThrow();
51
54
  var path = AssetToolSupport.RequireAssetPath(request.AssetPath ?? request.Path);
55
+ if (!budget.TryConsumeGetter()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
52
56
  if (!AssetToolSupport.AssetExists(path))
53
57
  {
54
58
  throw new UnityCliBridge.BridgeCommandException("E_ASSET_NOT_FOUND", "Asset not found: " + path, "Run tools run asset.search --input '{\"query\":\"<keyword>\"}' to discover the path.");
55
59
  }
60
+ if (!budget.TryConsumeGetter()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
56
61
  var asset = AssetDatabase.LoadMainAssetAtPath(path);
57
62
  if (asset == null)
58
63
  {
59
64
  throw new UnityCliBridge.BridgeCommandException("E_ASSET_LOAD_FAILED", "Asset main object could not be loaded: " + path, "Run asset.info to confirm the asset type, or use a more specific tool for folders/importers.");
60
65
  }
61
66
  var options = GameObjectToolSupport.BuildSerializerOptions(request);
62
- var data = UnityCliBridge.ObjectSerializer.Serialize(asset, options);
67
+ var data = UnityCliBridge.ObjectSerializer.Serialize(asset, options, budget);
68
+ if (!budget.TryConsumeGetter()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
69
+ var guid = AssetDatabase.AssetPathToGUID(path);
70
+ if (!budget.TryConsumeReflectionCall()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
71
+ var typeName = asset.GetType().FullName;
63
72
  return new System.Threading.Tasks.ValueTask<Result>(new Result
64
73
  {
65
- Guid = AssetDatabase.AssetPathToGUID(path),
66
- TypeName = asset.GetType().FullName,
74
+ Guid = guid,
75
+ TypeName = typeName,
67
76
  // ObjectSerializer 输出为动态 JSON 树);解析为嵌套结构供框架序列化(IDictionary 支持)
68
77
  Data = data
69
78
  });
@@ -1,4 +1,5 @@
1
1
  #if UNITY_EDITOR
2
+ using System.Collections.Generic;
2
3
  using System.Linq;
3
4
  using UnityEditor;
4
5
 
@@ -16,22 +17,41 @@ namespace UnityCli
16
17
  var index = scenes.ToList().FindIndex(scene => scene.path == path);
17
18
  if (index < 0)
18
19
  throw new UnityCliBridge.BridgeCommandException("E_BUILD_SCENE_NOT_FOUND", "Build Settings does not contain scene: " + path, "Run build.scene.list and pass a listed path.");
19
- if (!ctx.DryRun)
20
+ var changed = scenes[index].enabled != enabled;
21
+ var plannedEffects = changed
22
+ ? new List<BuildScenePlannedEffect> { new BuildScenePlannedEffect { Type = toolName, Path = path, Enabled = enabled } }
23
+ : new List<BuildScenePlannedEffect>();
24
+ if (!ctx.DryRun && changed)
20
25
  {
21
26
  ctx.CommitEffect();
22
27
  scenes[index] = new EditorBuildSettingsScene(path, enabled);
23
28
  EditorBuildSettings.scenes = scenes;
24
29
  scenes = ReadScenes();
25
- if (!scenes.Any(scene => scene.path == path && scene.enabled == enabled))
30
+ index = scenes.ToList().FindIndex(scene => scene.path == path);
31
+ if (index < 0 || scenes[index].enabled != enabled)
26
32
  throw new UnityCliBridge.BridgeCommandException("E_BUILD_SCENE_STATE_MISMATCH", "Build Settings did not apply the requested enabled state for " + path + ".", "Inspect build.scene.list and retry.");
27
33
  }
28
- return new(new BuildSceneMutationResult { Count = scenes.LongLength, DryRun = ctx.DryRun });
34
+ return new(new BuildSceneMutationResult
35
+ {
36
+ Count = scenes.LongLength,
37
+ Scene = BuildEntry(scenes[index], index),
38
+ Changed = !ctx.DryRun && changed,
39
+ NoOpReason = !changed ? "scene-already-" + (enabled ? "enabled" : "disabled") : null,
40
+ DryRun = ctx.DryRun,
41
+ PlannedEffects = plannedEffects
42
+ });
29
43
  }
30
44
 
31
45
  public static EditorBuildSettingsScene[] ReadScenes()
32
46
  {
33
47
  return EditorBuildSettings.scenes ?? new EditorBuildSettingsScene[0];
34
48
  }
49
+
50
+ // 将 fresh EditorBuildSettings 读回投影为公开条目。
51
+ public static BuildSceneEntry BuildEntry(EditorBuildSettingsScene scene, int index)
52
+ {
53
+ return new BuildSceneEntry { Path = BuildToolSupport.NormalizeAssetPath(scene.path), Enabled = scene.enabled, BuildIndex = index };
54
+ }
35
55
  }
36
56
  }
37
57
  #endif
@@ -8,7 +8,8 @@ namespace UnityCli
8
8
  public sealed class BuildProfileMutationResult { public bool Available { get; set; } public string Action { get; set; } public string ActivePath { get; set; } public bool DryRun { get; set; } }
9
9
  public sealed class BuildSceneEntry { public string Path { get; set; } public bool Enabled { get; set; } public long BuildIndex { get; set; } }
10
10
  public sealed class BuildSceneListResult { public long Count { get; set; } public List<BuildSceneEntry> Scenes { get; set; } }
11
- public sealed class BuildSceneMutationResult { public long Count { get; set; } public bool Changed { get; set; } public string NoOpReason { get; set; } public bool DryRun { get; set; } }
11
+ public sealed class BuildScenePlannedEffect { public string Type { get; set; } public string Path { get; set; } public bool? Enabled { get; set; } }
12
+ public sealed class BuildSceneMutationResult { public long Count { get; set; } public BuildSceneEntry Scene { get; set; } public bool Changed { get; set; } public string NoOpReason { get; set; } public bool DryRun { get; set; } public List<BuildScenePlannedEffect> PlannedEffects { get; set; } }
12
13
  public sealed class BuildSettingsResult { public string ProductName { get; set; } public string BundleVersion { get; set; } public string CompanyId { get; set; } public string ApplicationIdentifier { get; set; } public string ScriptingBackend { get; set; } public string ApiCompatibilityLevel { get; set; } public List<string> Changed { get; set; } public bool DryRun { get; set; } }
13
14
  public sealed class BuildSettingsMutationResult { public List<string> Changed { get; set; } public bool DryRun { get; set; } }
14
15
  }
@@ -7,7 +7,7 @@ namespace UnityCli
7
7
  public class BuildSceneAddTool : IUnityCliTool<BuildSceneAddTool.Request, BuildSceneMutationResult>
8
8
  {
9
9
  public sealed class Request { [UnityCliRequired] public string Path { get; set; } }
10
- public ToolDescriptor Describe() => new() { Name = "build.scene.add", Summary = "Add an enabled scene to persistent Unity Build Settings.", Description = "Adds path to EditorBuildSettings as an enabled scene when absent. dryRun=true previews without writing. Available in Edit and Play mode.", Group = "build",DestructiveHint = true, SupportsDryRun = true, SideEffects = new[] { "project-settings-write" }, Target = "editor", PlayModeSupport = "editor-only", DefaultTimeoutMs = 30000, MaxTimeoutMs = 600000, Examples = new[] { new ToolExample { Description = "Add main scene", Input = new Request { Path = "Assets/Scenes/Main.unity" }, Execution = new ToolExampleExecution { DryRun = true } } } };
10
+ public ToolDescriptor Describe() => new() { Name = "build.scene.add", Summary = "Add an enabled scene to persistent Unity Build Settings.", Description = "Adds one existing Assets/*.unity scene to EditorBuildSettings as enabled. Available in Edit and Play mode. dryRun previews the exact path; success returns the fresh scene entry, count, changed state, and no-op reason.", Group = "build",DestructiveHint = true, SupportsDryRun = true, SideEffects = new[] { "project-settings-write" }, Target = "editor", PlayModeSupport = "supported", DefaultTimeoutMs = 30000, MaxTimeoutMs = 600000, Examples = new[] { new ToolExample { Description = "Add main scene", Input = new Request { Path = "Assets/Scenes/Main.unity" }, Execution = new ToolExampleExecution { DryRun = true } } } };
11
11
  public System.Threading.Tasks.ValueTask<BuildSceneMutationResult> ExecuteAsync(Request request, UnityCliProjectToolContext ctx, System.Threading.CancellationToken ct = default)
12
12
  {
13
13
  var path = BuildToolSupport.NormalizeAssetPath(request.Path);
@@ -32,12 +32,15 @@ namespace UnityCli
32
32
  if (!scenes.Any(scene => scene.path == path && scene.enabled))
33
33
  throw new UnityCliBridge.BridgeCommandException("E_BUILD_SCENE_ADD_MISMATCH", "Build Settings did not add enabled scene: " + path, "Inspect build.scene.list and retry.");
34
34
  }
35
+ var observedIndex = System.Array.FindIndex(scenes, scene => scene.path == path);
35
36
  return new(new BuildSceneMutationResult
36
37
  {
37
38
  Count = scenes.LongLength,
39
+ Scene = observedIndex < 0 ? null : BuildSceneStateTool.BuildEntry(scenes[observedIndex], observedIndex),
38
40
  Changed = !ctx.DryRun && changed,
39
- NoOpReason = !ctx.DryRun && !changed ? "scene-already-enabled" : null,
40
- DryRun = ctx.DryRun
41
+ NoOpReason = !changed ? "scene-already-enabled" : null,
42
+ DryRun = ctx.DryRun,
43
+ PlannedEffects = changed ? new System.Collections.Generic.List<BuildScenePlannedEffect> { new BuildScenePlannedEffect { Type = "build.scene.add", Path = path, Enabled = true } } : new System.Collections.Generic.List<BuildScenePlannedEffect>()
41
44
  });
42
45
  }
43
46
  }
@@ -1,3 +1,3 @@
1
1
  #if UNITY_EDITOR
2
- namespace UnityCli { public class BuildSceneDisableTool : IUnityCliTool<BuildSceneDisableTool.Request, BuildSceneMutationResult> { public sealed class Request { [UnityCliRequired] public string Path { get; set; } } public ToolDescriptor Describe() => new() { Name = "build.scene.disable", Summary = "Disable a scene in persistent Unity Build Settings.", Description = "Marks an existing Build Settings scene as disabled. dryRun=true previews without writing. Available in Edit and Play mode.", Group = "build",DestructiveHint = true, SupportsDryRun = true, SideEffects = new[] { "project-settings-write" }, Target = "editor", PlayModeSupport = "editor-only", DefaultTimeoutMs = 30000, MaxTimeoutMs = 600000, Examples = new[] { new ToolExample { Description = "Disable main scene", Input = new Request { Path = "Assets/Scenes/Main.unity" }, Execution = new ToolExampleExecution { DryRun = true } } } }; public System.Threading.Tasks.ValueTask<BuildSceneMutationResult> ExecuteAsync(Request request, UnityCliProjectToolContext ctx, System.Threading.CancellationToken ct = default) { return BuildSceneStateTool.Execute(request.Path, false, "build.scene.disable", ctx); } } }
2
+ namespace UnityCli { public class BuildSceneDisableTool : IUnityCliTool<BuildSceneDisableTool.Request, BuildSceneMutationResult> { public sealed class Request { [UnityCliRequired] public string Path { get; set; } } public ToolDescriptor Describe() => new() { Name = "build.scene.disable", Summary = "Disable a scene in persistent Unity Build Settings.", Description = "Marks one existing EditorBuildSettings scene as disabled. Available in Edit and Play mode. dryRun previews the exact path; success returns a fresh scene entry, changed state, and no-op reason.", Group = "build",DestructiveHint = true, SupportsDryRun = true, SideEffects = new[] { "project-settings-write" }, Target = "editor", PlayModeSupport = "supported", DefaultTimeoutMs = 30000, MaxTimeoutMs = 600000, Examples = new[] { new ToolExample { Description = "Disable main scene", Input = new Request { Path = "Assets/Scenes/Main.unity" }, Execution = new ToolExampleExecution { DryRun = true } } } }; public System.Threading.Tasks.ValueTask<BuildSceneMutationResult> ExecuteAsync(Request request, UnityCliProjectToolContext ctx, System.Threading.CancellationToken ct = default) { return BuildSceneStateTool.Execute(request.Path, false, "build.scene.disable", ctx); } } }
3
3
  #endif
@@ -1,4 +1,4 @@
1
1
  #if UNITY_EDITOR
2
2
  using System.Linq; using UnityEditor;
3
- namespace UnityCli { public class BuildSceneEnableTool : IUnityCliTool<BuildSceneEnableTool.Request, BuildSceneMutationResult> { public sealed class Request { [UnityCliRequired] public string Path { get; set; } } public ToolDescriptor Describe() => new() { Name = "build.scene.enable", Summary = "Enable a scene in persistent Unity Build Settings.", Description = "Marks an existing Build Settings scene as enabled. dryRun=true previews without writing. Available in Edit and Play mode.", Group = "build",DestructiveHint = true, SupportsDryRun = true, SideEffects = new[] { "project-settings-write" }, Target = "editor", PlayModeSupport = "editor-only", DefaultTimeoutMs = 30000, MaxTimeoutMs = 600000, Examples = new[] { new ToolExample { Description = "Enable main scene", Input = new Request { Path = "Assets/Scenes/Main.unity" }, Execution = new ToolExampleExecution { DryRun = true } } } }; public System.Threading.Tasks.ValueTask<BuildSceneMutationResult> ExecuteAsync(Request request, UnityCliProjectToolContext ctx, System.Threading.CancellationToken ct = default) { return BuildSceneStateTool.Execute(request.Path, true, "build.scene.enable", ctx); } } }
3
+ namespace UnityCli { public class BuildSceneEnableTool : IUnityCliTool<BuildSceneEnableTool.Request, BuildSceneMutationResult> { public sealed class Request { [UnityCliRequired] public string Path { get; set; } } public ToolDescriptor Describe() => new() { Name = "build.scene.enable", Summary = "Enable a scene in persistent Unity Build Settings.", Description = "Marks one existing EditorBuildSettings scene as enabled. Available in Edit and Play mode. dryRun previews the exact path; success returns a fresh scene entry, changed state, and no-op reason.", Group = "build",DestructiveHint = true, SupportsDryRun = true, SideEffects = new[] { "project-settings-write" }, Target = "editor", PlayModeSupport = "supported", DefaultTimeoutMs = 30000, MaxTimeoutMs = 600000, Examples = new[] { new ToolExample { Description = "Enable main scene", Input = new Request { Path = "Assets/Scenes/Main.unity" }, Execution = new ToolExampleExecution { DryRun = true } } } }; public System.Threading.Tasks.ValueTask<BuildSceneMutationResult> ExecuteAsync(Request request, UnityCliProjectToolContext ctx, System.Threading.CancellationToken ct = default) { return BuildSceneStateTool.Execute(request.Path, true, "build.scene.enable", ctx); } } }
4
4
  #endif
@@ -1,4 +1,4 @@
1
1
  #if UNITY_EDITOR
2
2
  using System.Collections.Generic; using UnityEditor;
3
- namespace UnityCli { public class BuildSceneListTool : IUnityCliTool<BuildSceneListTool.Request, BuildSceneListResult> { public sealed class Request { } public ToolDescriptor Describe() => new() { Name = "build.scene.list", Summary = "List scenes registered in Unity Build Settings.", Description = "Returns every Build Settings scene with path, enabled state, and build index. Read-only and available in Edit and Play mode.", Group = "build",Target = "editor", PlayModeSupport = "editor-only", DefaultTimeoutMs = 30000, MaxTimeoutMs = 600000, Examples = new[] { new ToolExample { Description = "List build scenes", Input = new Request() } } }; public System.Threading.Tasks.ValueTask<BuildSceneListResult> ExecuteAsync(Request request, UnityCliProjectToolContext ctx, System.Threading.CancellationToken ct = default) { var scenes = EditorBuildSettings.scenes ?? new EditorBuildSettingsScene[0]; var entries = new List<BuildSceneEntry>(); for (var i = 0; i < scenes.Length; i++) entries.Add(new BuildSceneEntry { Path = BuildToolSupport.NormalizeAssetPath(scenes[i].path), Enabled = scenes[i].enabled, BuildIndex = i }); return new(new BuildSceneListResult { Count = scenes.Length, Scenes = entries }); } } }
3
+ namespace UnityCli { public class BuildSceneListTool : IUnityCliTool<BuildSceneListTool.Request, BuildSceneListResult> { public sealed class Request { } public ToolDescriptor Describe() => new() { Name = "build.scene.list", Summary = "List scenes registered in Unity Build Settings.", Description = "Returns every EditorBuildSettings scene with normalized path, enabled state, and build index. Read-only and available in Edit and Play mode; use the dedicated add/remove/enable/disable Tools for writes.", Group = "build",Target = "editor", PlayModeSupport = "supported", DefaultTimeoutMs = 30000, MaxTimeoutMs = 600000, Examples = new[] { new ToolExample { Description = "List build scenes", Input = new Request() } } }; public System.Threading.Tasks.ValueTask<BuildSceneListResult> ExecuteAsync(Request request, UnityCliProjectToolContext ctx, System.Threading.CancellationToken ct = default) { var scenes = EditorBuildSettings.scenes ?? new EditorBuildSettingsScene[0]; var entries = new List<BuildSceneEntry>(); for (var i = 0; i < scenes.Length; i++) entries.Add(new BuildSceneEntry { Path = BuildToolSupport.NormalizeAssetPath(scenes[i].path), Enabled = scenes[i].enabled, BuildIndex = i }); return new(new BuildSceneListResult { Count = scenes.Length, Scenes = entries }); } } }
4
4
  #endif
@@ -7,14 +7,16 @@ namespace UnityCli
7
7
  public class BuildSceneRemoveTool : IUnityCliTool<BuildSceneRemoveTool.Request, BuildSceneMutationResult>
8
8
  {
9
9
  public sealed class Request { [UnityCliRequired] public string Path { get; set; } }
10
- public ToolDescriptor Describe() => new() { Name = "build.scene.remove", Summary = "Remove a scene from persistent Unity Build Settings.", Description = "Removes path from EditorBuildSettings. dryRun=true previews without writing. Available in Edit and Play mode.", Group = "build",DestructiveHint = true, SupportsDryRun = true, SideEffects = new[] { "project-settings-write" }, Target = "editor", PlayModeSupport = "editor-only", DefaultTimeoutMs = 30000, MaxTimeoutMs = 600000, Examples = new[] { new ToolExample { Description = "Remove main scene", Input = new Request { Path = "Assets/Scenes/Main.unity" }, Execution = new ToolExampleExecution { DryRun = true } } } };
10
+ public ToolDescriptor Describe() => new() { Name = "build.scene.remove", Summary = "Remove a scene from persistent Unity Build Settings.", Description = "Removes one exact scene path from EditorBuildSettings. Available in Edit and Play mode. dryRun previews the path; success returns the fresh remaining count, changed state, and no-op reason.", Group = "build",DestructiveHint = true, SupportsDryRun = true, SideEffects = new[] { "project-settings-write" }, Target = "editor", PlayModeSupport = "supported", DefaultTimeoutMs = 30000, MaxTimeoutMs = 600000, Examples = new[] { new ToolExample { Description = "Remove main scene", Input = new Request { Path = "Assets/Scenes/Main.unity" }, Execution = new ToolExampleExecution { DryRun = true } } } };
11
11
  public System.Threading.Tasks.ValueTask<BuildSceneMutationResult> ExecuteAsync(Request request, UnityCliProjectToolContext ctx, System.Threading.CancellationToken ct = default)
12
12
  {
13
13
  var path = BuildToolSupport.NormalizeAssetPath(request.Path);
14
14
  if (string.IsNullOrEmpty(path))
15
15
  throw new UnityCliBridge.BridgeCommandException("E_BUILD_SCENE_PATH_REQUIRED", "build.scene.remove requires path.", "Pass a scene path such as Assets/Scenes/Main.unity.");
16
16
  var scenes = BuildSceneStateTool.ReadScenes();
17
- if (!ctx.DryRun && scenes.Any(scene => scene.path == path))
17
+ var existingIndex = System.Array.FindIndex(scenes, scene => scene.path == path);
18
+ var changed = existingIndex >= 0;
19
+ if (!ctx.DryRun && changed)
18
20
  {
19
21
  ctx.CommitEffect();
20
22
  EditorBuildSettings.scenes = scenes.Where(scene => scene.path != path).ToArray();
@@ -22,7 +24,15 @@ namespace UnityCli
22
24
  if (scenes.Any(scene => scene.path == path))
23
25
  throw new UnityCliBridge.BridgeCommandException("E_BUILD_SCENE_REMOVE_MISMATCH", "Build Settings did not remove scene: " + path, "Inspect build.scene.list and retry.");
24
26
  }
25
- return new(new BuildSceneMutationResult { Count = scenes.LongLength, DryRun = ctx.DryRun });
27
+ return new(new BuildSceneMutationResult
28
+ {
29
+ Count = scenes.LongLength,
30
+ Scene = ctx.DryRun && changed ? BuildSceneStateTool.BuildEntry(scenes[existingIndex], existingIndex) : null,
31
+ Changed = !ctx.DryRun && changed,
32
+ NoOpReason = !changed ? "scene-not-registered" : null,
33
+ DryRun = ctx.DryRun,
34
+ PlannedEffects = changed ? new System.Collections.Generic.List<BuildScenePlannedEffect> { new BuildScenePlannedEffect { Type = "build.scene.remove", Path = path } } : new System.Collections.Generic.List<BuildScenePlannedEffect>()
35
+ });
26
36
  }
27
37
  }
28
38
  }
@@ -3,7 +3,6 @@ using System;
3
3
  using System.Collections.Generic;
4
4
  using System.Globalization;
5
5
  using System.IO;
6
- using System.Security.Cryptography;
7
6
  using UnityEditor;
8
7
  using UnityEngine;
9
8
 
@@ -57,6 +56,27 @@ namespace UnityCli
57
56
  return null;
58
57
  }
59
58
 
59
+ /// <summary>读取型截图解析复用一次 Camera snapshot,并把每个候选计入请求 scan/getter 预算。</summary>
60
+ public static Camera ResolveCamera(string target, UnityCliRequestBudget budget)
61
+ {
62
+ if (budget == null) throw new ArgumentNullException("budget");
63
+ if (string.IsNullOrEmpty(target))
64
+ {
65
+ if (!budget.TryConsumeGetter()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
66
+ var main = Camera.main;
67
+ if (main != null) return main;
68
+ }
69
+ var cameras = Camera.allCameras;
70
+ foreach (var camera in cameras)
71
+ {
72
+ if (!budget.TryConsumeScannedRecord() || !budget.TryConsumeGetter()) throw new UnityCliRequestBudgetExceededException(budget.ExhaustedDimension);
73
+ if (string.IsNullOrEmpty(target) || camera.name == target || camera.GetInstanceID().ToString(CultureInfo.InvariantCulture) == target) return camera;
74
+ var path = UnityCliHierarchyPager.Path(camera.gameObject, budget);
75
+ if (path == target) return camera;
76
+ }
77
+ return null;
78
+ }
79
+
60
80
  /// <summary>构造 Camera 摘要信息 DTO。</summary>
61
81
  public static CameraInfo BuildCameraInfo(Camera camera)
62
82
  {
@@ -143,45 +163,25 @@ namespace UnityCli
143
163
  }
144
164
  }
145
165
 
146
- /// <summary>写入截图后重新读取字节、校验格式并返回稳定 artifact proof。</summary>
147
- public static CameraArtifactProof WriteScreenshotBytes(string outputPath, byte[] bytes)
148
- {
149
- if (bytes == null || bytes.Length == 0)
150
- throw new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_ENCODE_FAILED", "Screenshot encoder returned no bytes.", "Check the Camera render state and retry.");
151
- try { File.WriteAllBytes(outputPath, bytes); }
152
- catch (Exception exception) { throw new UnityCliBridge.BridgeCommandException("E_IO_ERROR", "Failed to write screenshot output: " + exception.Message); }
153
- return VerifyScreenshotArtifact(outputPath, Path.GetExtension(outputPath).TrimStart('.'));
154
- }
155
-
156
- /// <summary>artifact proof 必须来自写后 fresh readback,不能只依赖 Exists 或原始编码 bytes。</summary>
157
- public static CameraArtifactProof VerifyScreenshotArtifact(string outputPath, string format)
158
- {
159
- if (!File.Exists(outputPath)) throw new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_WRITE_PROOF_MISSING", "Screenshot output was not written: " + outputPath, "Check destination path and permissions, then retry.");
160
- var bytes = File.ReadAllBytes(outputPath);
161
- var normalized = NormalizeFormat(format, outputPath);
162
- var valid = normalized == "png"
163
- ? bytes.Length >= 8 && bytes[0] == 0x89 && bytes[1] == 0x50 && bytes[2] == 0x4e && bytes[3] == 0x47 && bytes[4] == 0x0d && bytes[5] == 0x0a && bytes[6] == 0x1a && bytes[7] == 0x0a
164
- : bytes.Length >= 3 && bytes[0] == 0xff && bytes[1] == 0xd8 && bytes[2] == 0xff;
165
- if (!valid) throw new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_WRITE_PROOF_MISSING", "Screenshot output is empty or does not match its requested format: " + outputPath, "Use a .png or .jpg output path and retry.");
166
- using (var sha = SHA256.Create()) return new CameraArtifactProof { SizeBytes = bytes.LongLength, Sha256 = string.Concat(Array.ConvertAll(sha.ComputeHash(bytes), value => value.ToString("x2"))) };
167
- }
168
-
169
- /// <summary>渲染指定 Camera 到 RenderTexture 并编码为图片字节,返回捕获结果。捕获后恢复相机状态。</summary>
170
- public static CameraCapture CaptureCamera(Camera camera, int width, int height, bool transparent, int quality, string outputPath, string format, bool overwrite, bool inlineBase64)
166
+ /// <summary>按冻结 aggregate plan 渲染、编码与证明单张 Camera artifact,并在 finally 恢复纹理状态。</summary>
167
+ public static CameraCapture CaptureCamera(Camera camera, int width, int height, bool transparent, int quality, string outputPath, string format, bool overwrite, bool inlineBase64, ScreenshotCaptureBudget budget)
171
168
  {
172
169
  if (File.Exists(outputPath) && !overwrite)
173
170
  {
174
171
  throw new UnityCliBridge.BridgeCommandException("E_OUTPUT_EXISTS", "Screenshot output already exists: " + outputPath, "Pass overwrite=true or choose a different output path.");
175
172
  }
173
+ budget.BeginCapture(width, height);
176
174
  CreateScreenshotDirectory(outputPath);
177
175
  var previousTarget = camera.targetTexture;
178
176
  var previousActive = RenderTexture.active;
179
177
  var previousClearFlags = camera.clearFlags;
180
178
  var previousBackground = camera.backgroundColor;
181
- var renderTexture = new RenderTexture(width, height, 24, RenderTextureFormat.ARGB32);
182
- var texture = new Texture2D(width, height, transparent ? TextureFormat.ARGB32 : TextureFormat.RGB24, false);
179
+ RenderTexture renderTexture = null;
180
+ Texture2D texture = null;
183
181
  try
184
182
  {
183
+ renderTexture = new RenderTexture(width, height, 24, RenderTextureFormat.ARGB32);
184
+ texture = new Texture2D(width, height, transparent ? TextureFormat.ARGB32 : TextureFormat.RGB24, false);
185
185
  camera.targetTexture = renderTexture;
186
186
  if (transparent)
187
187
  {
@@ -193,20 +193,22 @@ namespace UnityCli
193
193
  texture.ReadPixels(new Rect(0, 0, width, height), 0, 0);
194
194
  texture.Apply();
195
195
  var bytes = format == "jpg" ? texture.EncodeToJPG(quality) : texture.EncodeToPNG();
196
- var artifact = WriteScreenshotBytes(outputPath, bytes);
196
+ var artifact = ScreenshotToolSupport.WriteScreenshotBytes(outputPath, bytes, budget);
197
197
  var capture = new CameraCapture
198
198
  {
199
199
  ScreenshotPath = outputPath,
200
200
  ArtifactPath = outputPath,
201
201
  Source = "camera",
202
202
  SizeBytes = artifact.SizeBytes,
203
- Sha256 = artifact.Sha256
203
+ Sha256 = artifact.Sha256,
204
+ CapturePlan = budget.Plan
204
205
  };
205
- // inlineBase64 仅在图片字节小于内联上限时附带,避免大 base64 撑爆 JSON 响应
206
+ // inlineBase64 同时受固定图片上限与请求级 UTF-8 预算约束。
206
207
  const int InlineImageMaxBytes = 512 * 1024;
207
- if (inlineBase64 && bytes != null && bytes.Length <= InlineImageMaxBytes)
208
+ string encoded;
209
+ if (inlineBase64 && bytes != null && bytes.Length <= InlineImageMaxBytes && budget.TryCreateInlineBase64(bytes, out encoded))
208
210
  {
209
- capture.ImageBase64 = Convert.ToBase64String(bytes);
211
+ capture.ImageBase64 = encoded;
210
212
  capture.ImageWidth = width;
211
213
  capture.ImageHeight = height;
212
214
  capture.ImageMimeType = format == "jpg" ? "image/jpeg" : "image/png";
@@ -219,9 +221,8 @@ namespace UnityCli
219
221
  camera.targetTexture = previousTarget;
220
222
  camera.clearFlags = previousClearFlags;
221
223
  camera.backgroundColor = previousBackground;
222
- UnityEngine.Object.DestroyImmediate(texture);
223
- renderTexture.Release();
224
- UnityEngine.Object.DestroyImmediate(renderTexture);
224
+ if (texture != null) UnityEngine.Object.DestroyImmediate(texture);
225
+ if (renderTexture != null) { renderTexture.Release(); UnityEngine.Object.DestroyImmediate(renderTexture); }
225
226
  }
226
227
  }
227
228
 
@@ -275,10 +276,9 @@ namespace UnityCli
275
276
  public string ImageMimeType { get; set; }
276
277
  public long SizeBytes { get; set; }
277
278
  public string Sha256 { get; set; }
279
+ public ScreenshotCapturePlan CapturePlan { get; set; }
278
280
  }
279
281
 
280
- public sealed class CameraArtifactProof { public long SizeBytes { get; set; } public string Sha256 { get; set; } }
281
-
282
282
  /// <summary>计划副作用项,camera 域 mutation 工具 Result 共享。</summary>
283
283
  public sealed class CameraPlannedEffect
284
284
  {
@@ -16,9 +16,11 @@ namespace UnityCli
16
16
  {
17
17
  public string Targets { get; set; }
18
18
  public string Output { get; set; }
19
- public int? Width { get; set; }
20
- public int? Height { get; set; }
19
+ [UnityCliRange(1L, 8192L)] public int? Width { get; set; }
20
+ [UnityCliRange(1L, 8192L)] public int? Height { get; set; }
21
21
  public string Format { get; set; }
22
+ public ScreenshotBudgetInput Limits { get; set; }
23
+ public UnityCliReadBudgetInput Budget { get; set; }
22
24
  }
23
25
 
24
26
  public sealed class Result
@@ -26,13 +28,14 @@ namespace UnityCli
26
28
  public List<CameraCapture> Captures { get; set; }
27
29
  public long Count { get; set; }
28
30
  public List<CameraPlannedEffect> PlannedEffects { get; set; }
31
+ public ScreenshotCapturePlan CapturePlan { get; set; }
29
32
  }
30
33
 
31
34
  public ToolDescriptor Describe() => new()
32
35
  {
33
36
  Name = "camera.multiview",
34
37
  Summary = "Capture one PNG/JPG artifact per named Camera into an output directory for multiview review.",
35
- Description = "Capture one PNG/JPG artifact per named Camera into an output directory for side-by-side scene review. Returns artifact paths instead of inline image bytes; keep target count and resolution small for token and disk efficiency.",
38
+ Description = "Capture 1..16 named Cameras to PNG/JPG artifacts. Each dimension is 1..8192 and each image is at most 16777216 pixels. Before the first effect, the aggregate plan must fit default ceilings of 33554432 total pixels, 128 MiB temporary memory and 64 MiB artifacts; public maxima are 67108864, 256 MiB and 256 MiB. All textures are released after each capture. Available in Edit and Play Mode with session render effects.",
36
39
  Group = "camera",
37
40
  DestructiveHint = true,
38
41
  SideEffects = new[] { "artifact-write", "render-texture" },
@@ -52,11 +55,14 @@ namespace UnityCli
52
55
  {
53
56
  throw new UnityCliBridge.BridgeCommandException("E_TARGET_NOT_FOUND", "camera.multiview requires targets.", "Run tools run camera.list, then pass comma-separated camera names, hierarchy paths, or instance ids.");
54
57
  }
58
+ var requestBudget = ctx.UseReadBudget(request.Budget);
55
59
  var names = request.Targets.Split(new[] { ',' }, System.StringSplitOptions.RemoveEmptyEntries);
60
+ if (names.Length < 1 || names.Length > 16)
61
+ throw new UnityCliBridge.BridgeCommandException("E_SCREENSHOT_LIMIT_INVALID", "camera.multiview targets must contain 1..16 cameras.", "Reduce the target list before retrying.");
56
62
  var cameras = new List<Camera>();
57
63
  foreach (var name in names)
58
64
  {
59
- var camera = CameraToolSupport.ResolveCamera(name.Trim());
65
+ var camera = CameraToolSupport.ResolveCamera(name.Trim(), requestBudget);
60
66
  if (camera == null)
61
67
  {
62
68
  throw new UnityCliBridge.BridgeCommandException("E_CAMERA_NOT_FOUND", "Camera not found: " + name.Trim(), "Run tools run camera.list, then retry with camera names, hierarchy paths, or instance ids from the result.");
@@ -67,6 +73,7 @@ namespace UnityCli
67
73
  var width = CameraToolSupport.ClampScreenshotDimension(request.Width ?? 1280, "width");
68
74
  var height = CameraToolSupport.ClampScreenshotDimension(request.Height ?? 720, "height");
69
75
  var format = CameraToolSupport.NormalizeFormat(request.Format, Path.Combine(outputDir, "capture.png"));
76
+ var captureBudget = ScreenshotToolSupport.PrepareCaptureBudget(width, height, cameras.Count, false, request.Limits, requestBudget);
70
77
  var captures = new List<CameraCapture>();
71
78
  var effects = new List<CameraPlannedEffect>();
72
79
  var plans = new List<KeyValuePair<int, string>>();
@@ -83,13 +90,13 @@ namespace UnityCli
83
90
  {
84
91
  var camera = EditorUtility.InstanceIDToObject(plan.Key) as Camera;
85
92
  if (camera == null) throw new UnityCliBridge.BridgeCommandException("E_CAMERA_NOT_FOUND", "Camera disappeared before capture.", "Retry after the Editor is idle.");
86
- var capture = CameraToolSupport.CaptureCamera(camera, width, height, false, 95, plan.Value, format, true, false);
93
+ var capture = CameraToolSupport.CaptureCamera(camera, width, height, false, 95, plan.Value, format, true, false, captureBudget);
87
94
  if (!File.Exists(plan.Value) || new FileInfo(plan.Value).Length == 0)
88
95
  throw new UnityCliBridge.BridgeCommandException("E_MULTIVIEW_ARTIFACT_MISSING", "Camera capture artifact was not observed: " + plan.Value, "Check the output directory and retry.");
89
96
  captures.Add(capture);
90
97
  effects.Add(new CameraPlannedEffect { Type = "camera.multiview", Property = "capture", Path = plan.Value });
91
98
  }
92
- return new System.Threading.Tasks.ValueTask<Result>(new Result { Captures = captures, Count = captures.Count, PlannedEffects = effects });
99
+ return new System.Threading.Tasks.ValueTask<Result>(new Result { Captures = captures, Count = captures.Count, PlannedEffects = effects, CapturePlan = captureBudget.Plan });
93
100
  }
94
101
  }
95
102
  }
@@ -1,4 +1,5 @@
1
1
  #if UNITY_EDITOR
2
+ using System.IO;
2
3
  using UnityEngine;
3
4
 
4
5
  namespace UnityCli
@@ -13,20 +14,22 @@ namespace UnityCli
13
14
  public string Target { get; set; }
14
15
  public string Path { get; set; }
15
16
  public string Output { get; set; }
16
- public int? Width { get; set; }
17
- public int? Height { get; set; }
17
+ [UnityCliRange(1L, 8192L)] public int? Width { get; set; }
18
+ [UnityCliRange(1L, 8192L)] public int? Height { get; set; }
18
19
  public string Format { get; set; }
19
20
  public bool Transparent { get; set; }
20
- public int? Quality { get; set; }
21
+ [UnityCliRange(1L, 100L)] public int? Quality { get; set; }
21
22
  public bool Overwrite { get; set; }
22
23
  public bool InlineBase64 { get; set; }
24
+ public ScreenshotBudgetInput Limits { get; set; }
25
+ public UnityCliReadBudgetInput Budget { get; set; }
23
26
  }
24
27
 
25
28
  public ToolDescriptor Describe() => new()
26
29
  {
27
30
  Name = "camera.screenshot",
28
31
  Summary = "Render a PNG/JPG artifact from one specific Camera; use screenshot.* for view-based captures.",
29
- Description = "Render a screenshot from a specific Camera into a PNG/JPG artifact file. Defaults return artifact paths only; inlineBase64 must be explicit and is omitted when the image exceeds the bridge inline limit. Restores camera render state after capture.",
32
+ Description = "Render one Camera to PNG/JPG. Before the first effect, dimensions must be 1..8192 with at most 16777216 pixels, and the aggregate plan must fit default ceilings of 33554432 total pixels, 128 MiB temporary memory and 64 MiB artifacts (public maxima 67108864, 256 MiB and 256 MiB). limits may only tighten or raise within those maxima; zero, negative and excessive values fail before capture. The texture and Camera state are released/restored in finally. Available in Edit and Play Mode with session render effects.",
30
33
  Group = "camera",
31
34
  DestructiveHint = true,
32
35
  SideEffects = new[] { "artifact-write", "render-texture" },
@@ -47,7 +50,8 @@ namespace UnityCli
47
50
  {
48
51
  throw new UnityCliBridge.BridgeCommandException("E_TARGET_NOT_FOUND", "Camera screenshot requires a target Camera.", "Pass a Camera name, hierarchy path or instance id. Run tools run camera.list before retrying.");
49
52
  }
50
- var camera = CameraToolSupport.ResolveCamera(target);
53
+ var requestBudget = ctx.UseReadBudget(request.Budget);
54
+ var camera = CameraToolSupport.ResolveCamera(target, requestBudget);
51
55
  if (camera == null)
52
56
  {
53
57
  throw new UnityCliBridge.BridgeCommandException("E_TARGET_NOT_FOUND", "Camera not found: " + target, "Run tools run camera.list, then retry with a camera name, hierarchy path, or instance id from the result.");
@@ -56,9 +60,12 @@ namespace UnityCli
56
60
  var format = CameraToolSupport.NormalizeFormat(request.Format, outputPath);
57
61
  var width = CameraToolSupport.ClampScreenshotDimension(request.Width ?? (camera.pixelWidth > 0 ? camera.pixelWidth : 1280), "width");
58
62
  var height = CameraToolSupport.ClampScreenshotDimension(request.Height ?? (camera.pixelHeight > 0 ? camera.pixelHeight : 720), "height");
59
- var quality = Mathf.Clamp(request.Quality ?? 95, 1, 100);
63
+ var quality = request.Quality ?? 95;
64
+ if (File.Exists(outputPath) && !request.Overwrite)
65
+ throw new UnityCliBridge.BridgeCommandException("E_OUTPUT_EXISTS", "Screenshot output already exists: " + outputPath, "Pass overwrite=true or choose a different output path.");
66
+ var captureBudget = ScreenshotToolSupport.PrepareCaptureBudget(width, height, 1, false, request.Limits, requestBudget);
60
67
  ctx.CommitEffect();
61
- return new System.Threading.Tasks.ValueTask<CameraCapture>(CameraToolSupport.CaptureCamera(camera, width, height, request.Transparent, quality, outputPath, format, request.Overwrite, request.InlineBase64));
68
+ return new System.Threading.Tasks.ValueTask<CameraCapture>(CameraToolSupport.CaptureCamera(camera, width, height, request.Transparent, quality, outputPath, format, request.Overwrite, request.InlineBase64, captureBudget));
62
69
  }
63
70
  }
64
71
  }