@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
@@ -60,13 +60,13 @@ namespace UnityCli
60
60
  protected override string OperationDisplayName { get { return "Occlusion bake"; } }
61
61
  protected override int CommandTimeoutMs { get { return 600000; } }
62
62
  protected override bool IncludeAcceptedInStatus { get { return true; } }
63
- protected override IEnumerable<(string name, bool destructive)> AdditionalCommands
63
+ protected override IEnumerable<(string name, bool destructive, UnityCliBridgeQueuePolicy.WorkClass queueClass)> AdditionalCommands
64
64
  {
65
65
  get
66
66
  {
67
- yield return (PlanCommand, false);
68
- yield return (SnapshotCommand, false);
69
- yield return (CancelCommand, true);
67
+ yield return (PlanCommand, false, UnityCliBridgeQueuePolicy.WorkClass.Mutation);
68
+ yield return (SnapshotCommand, false, UnityCliBridgeQueuePolicy.WorkClass.OrdinaryRead);
69
+ yield return (CancelCommand, true, UnityCliBridgeQueuePolicy.WorkClass.Control);
70
70
  }
71
71
  }
72
72
 
@@ -50,6 +50,9 @@ namespace UnityCli
50
50
  /// </summary>
51
51
  private static async Task<string> ExecuteCoordinatedRequest(BridgeRequest request, byte[] rawRequestBytes, Func<bool> tryAcceptJournal, CancellationToken cancellationToken)
52
52
  {
53
+ // durable status只读active journal或terminal cache,必须绕开通用direct completion路径。
54
+ if (UnityCliDurableStatusCoordinator.IsCommand(request.Command))
55
+ return await UnityCliDurableStatusCoordinator.Execute(request, cancellationToken);
53
56
  // 私有 compile start/status 必须绕开通用 completion 路径;start 的 accepted 不是公开 terminal。
54
57
  if (UnityCliCompileCoordinator.IsCommand(request.Command))
55
58
  return await UnityCliCompileCoordinator.Execute(request, rawRequestBytes, tryAcceptJournal, cancellationToken);
@@ -97,7 +100,7 @@ namespace UnityCli
97
100
  object durableTerminal;
98
101
  if (UnityCliActiveOperation.TryGetTerminal(DirectMutationTerminalScope, request.RequestId, requestDigest, out durableTerminal))
99
102
  {
100
- CleanupPublishedDirectMutation(request.RequestId, requestDigest, AdmissionTerminalIsOutcomeUnknown(durableTerminal));
103
+ CleanupPublishedDirectMutation(request.RequestId, requestDigest);
101
104
  return UnityCliSerializer.Serialize(durableTerminal);
102
105
  }
103
106
  }
@@ -128,7 +131,7 @@ namespace UnityCli
128
131
  return ExecutionTimeoutEnvelope(request.RequestId, "The request was not accepted before the execution deadline elapsed.");
129
132
  }
130
133
 
131
- try { SaveDirectMutationAcceptedRoot(request.RequestId, requestDigest); }
134
+ try { SaveDirectMutationAcceptedRoot(request, requestDigest, contract.ToolName); }
132
135
  catch (Exception exception)
133
136
  {
134
137
  ScheduleDirectMutationPreEffectCleanup(request.RequestId, requestDigest);
@@ -192,40 +195,73 @@ namespace UnityCli
192
195
  try { UnityCliActiveOperation.BeginExactTerminalCleanup(DirectMutationTerminalScope, requestId, requestDigest); } catch { }
193
196
  }
194
197
 
195
- /// <summary>effect后的direct terminal先做字节admission,再进入active root与共享cache;超限统一替换为紧凑终态。</summary>
198
+ /// <summary>effect后的direct terminal先冻结first terminal,再由exact cleanup marker驱动root、lease与admission收敛。</summary>
196
199
  private static string PersistDirectMutationTerminal(string requestId, string requestDigest, string serializedEnvelope)
200
+ {
201
+ return PublishDirectMutationTerminal(ProductionQueueState, requestId, requestDigest, serializedEnvelope);
202
+ }
203
+
204
+ internal static string PublishDirectMutationTerminalForTests(BridgeQueueState state, string requestId, string requestDigest, string serializedEnvelope)
205
+ {
206
+ if (state == null || state.IsolatedExecution == null) throw new ArgumentException("Direct mutation test state must be isolated.", "state");
207
+ return PublishDirectMutationTerminal(state, requestId, requestDigest, serializedEnvelope);
208
+ }
209
+
210
+ private static string PublishDirectMutationTerminal(BridgeQueueState state, string requestId, string requestDigest, string serializedEnvelope)
197
211
  {
198
212
  try
199
213
  {
200
214
  bool isOwned;
201
- if (!UnityCliSessionMutationLease.TryIsOwnedBy(requestId, requestDigest, out isOwned) || !isOwned)
215
+ if (!state.TryIsLeaseOwned(requestId, requestDigest, out isOwned) || !isOwned)
202
216
  serializedEnvelope = ExecutionTimeoutEnvelope(requestId, "The direct mutation lost its exact lease after effect; outcome is unknown. Inspect the affected business state.");
203
217
  if (Encoding.UTF8.GetByteCount(serializedEnvelope ?? string.Empty) > MaxDirectMutationTerminalBytes)
204
218
  serializedEnvelope = ExecutionTimeoutEnvelope(requestId, "The direct mutation completed, but its terminal exceeded the durable byte budget; inspect the affected business state.");
205
219
  object envelope = UnityCliSerializer.Parse(serializedEnvelope) as Dictionary<string, object>;
206
220
  if (envelope == null)
207
221
  envelope = ProtocolEnvelopeFactory.Error(requestId, "E_EXECUTION_TIMEOUT", "The direct mutation terminal could not be decoded after effect; outcome is unknown.");
208
- var root = LoadDirectMutationRoot();
222
+ var root = LoadDirectMutationRoot(state);
209
223
  RequireDirectMutationRoot(root, requestId, requestDigest);
210
224
  root["phase"] = "terminal_pending";
211
225
  root["terminalEnvelope"] = envelope;
212
- UnityCliActiveOperation.Save(DirectMutationTerminalScope, root);
213
- var first = UnityCliActiveOperation.StoreTerminalFirst(DirectMutationTerminalScope, requestId, requestDigest, envelope);
214
- UnityCliActiveOperation.Clear(DirectMutationTerminalScope);
226
+ state.SaveOperation(DirectMutationTerminalScope, root);
227
+ var first = state.StoreTerminalFirst(DirectMutationTerminalScope, requestId, requestDigest, null, root["toolName"] as string, envelope);
228
+ CleanupPublishedDirectMutation(state, requestId, requestDigest);
215
229
  return UnityCliSerializer.Serialize(first);
216
230
  }
217
231
  catch (BridgeCommandException exception) { return SerializeBridgeExceptionEnvelope(requestId, exception); }
218
232
  catch (Exception exception) { return SerializeBridgeExceptionEnvelope(requestId, new BridgeCommandException("E_EXECUTION_TIMEOUT", "Direct mutation terminal publication is incomplete; the exact owner is retained: " + exception.GetType().Name + ".")); }
219
233
  }
220
234
 
221
- private static void SaveDirectMutationAcceptedRoot(string requestId, string requestDigest)
235
+ private static void SaveDirectMutationAcceptedRoot(BridgeRequest request, string requestDigest, string toolName)
222
236
  {
223
237
  UnityCliActiveOperation.Save(DirectMutationTerminalScope, new Dictionary<string, object>
224
238
  {
225
- { "requestId", requestId }, { "requestDigest", requestDigest }, { "editorSessionId", EditorSessionId() }, { "phase", "accepted_before_effect" }
239
+ { "requestId", request.RequestId }, { "requestDigest", requestDigest }, { "toolName", toolName },
240
+ { "editorSessionId", EditorSessionId() }, { "absoluteDeadlineUtc", request.Execution.AbsoluteDeadlineUtc },
241
+ { "phase", "accepted_before_effect" }
226
242
  });
227
243
  }
228
244
 
245
+ /// <summary>editor.play-mode.status只读取editor.play/editor.stop的direct owner或其已发布终态。</summary>
246
+ private static object ReadEditorPlayModeStatus(string requestId)
247
+ {
248
+ string requestDigest;
249
+ string statusDomain;
250
+ object terminal;
251
+ if (UnityCliActiveOperation.TryGetTerminalByRequestId(DirectMutationTerminalScope, requestId, out requestDigest, out statusDomain, out terminal))
252
+ {
253
+ if (statusDomain != "editor.play" && statusDomain != "editor.stop")
254
+ throw new BridgeCommandException("E_REQUEST_NOT_FOUND", "The requested Play Mode operation was not found in this status domain.");
255
+ return UnityCliDurableStatusCoordinator.Terminal(requestId, terminal);
256
+ }
257
+ var root = LoadDirectMutationRoot();
258
+ var toolName = root == null ? null : root.ContainsKey("toolName") ? root["toolName"] as string : null;
259
+ if (root == null || !string.Equals(root["requestId"] as string, requestId, StringComparison.Ordinal)
260
+ || (toolName != "editor.play" && toolName != "editor.stop"))
261
+ throw new BridgeCommandException("E_REQUEST_NOT_FOUND", "The requested Play Mode operation was not found in this Editor session.");
262
+ return UnityCliDurableStatusCoordinator.Running(requestId, root["phase"] as string);
263
+ }
264
+
229
265
  private static void MarkDirectMutationEffectStarted(string requestId, string requestDigest)
230
266
  {
231
267
  var root = LoadDirectMutationRoot();
@@ -233,20 +269,29 @@ namespace UnityCli
233
269
  if (!string.Equals(root["phase"] as string, "accepted_before_effect", StringComparison.Ordinal)
234
270
  && !string.Equals(root["phase"] as string, "effect_started", StringComparison.Ordinal))
235
271
  throw new BridgeCommandException("E_EXECUTION_TIMEOUT", "Direct mutation active state cannot cross its effect barrier.");
272
+ var toolName = root["toolName"] as string;
273
+ if ((string.Equals(toolName, "editor.play", StringComparison.Ordinal) || string.Equals(toolName, "editor.stop", StringComparison.Ordinal))
274
+ && !root.ContainsKey("playModeWasPlaying"))
275
+ root["playModeWasPlaying"] = EditorApplication.isPlaying;
236
276
  root["phase"] = "effect_started";
237
277
  UnityCliActiveOperation.Save(DirectMutationTerminalScope, root);
238
278
  }
239
279
 
240
280
  private static Dictionary<string, object> LoadDirectMutationRoot()
281
+ {
282
+ return LoadDirectMutationRoot(ProductionQueueState);
283
+ }
284
+
285
+ private static Dictionary<string, object> LoadDirectMutationRoot(BridgeQueueState state)
241
286
  {
242
287
  try
243
288
  {
244
- var root = UnityCliActiveOperation.Load(DirectMutationTerminalScope);
289
+ var root = state.LoadOperation(DirectMutationTerminalScope);
245
290
  if (root == null) return null;
246
- object requestId; object requestDigest; object editorSessionId; object phase;
291
+ object requestId; object requestDigest; object toolName; object editorSessionId; object phase;
247
292
  if (!root.TryGetValue("requestId", out requestId) || !root.TryGetValue("requestDigest", out requestDigest)
248
- || !root.TryGetValue("editorSessionId", out editorSessionId) || !root.TryGetValue("phase", out phase)
249
- || !(requestId is string) || !(requestDigest is string) || !(editorSessionId is string) || !(phase is string))
293
+ || !root.TryGetValue("toolName", out toolName) || !root.TryGetValue("editorSessionId", out editorSessionId) || !root.TryGetValue("phase", out phase)
294
+ || !(requestId is string) || !(requestDigest is string) || !(toolName is string) || !(editorSessionId is string) || !(phase is string))
250
295
  throw new InvalidDataException("Direct mutation active state is incomplete.");
251
296
  return root;
252
297
  }
@@ -262,6 +307,51 @@ namespace UnityCli
262
307
  throw new BridgeCommandException("E_EXECUTION_TIMEOUT", "Direct mutation active identity cannot be proven; outcome is unknown.");
263
308
  }
264
309
 
310
+ internal enum DirectMutationReloadOutcome { NotApplicable, WaitingExactOwner, SettledEffectBefore, IdentityBlocked }
311
+
312
+ /// <summary>旧domain不再执行effect;CleanupPending先释放旧lease,再冻结terminal并执行exact cleanup。</summary>
313
+ internal static DirectMutationReloadOutcome ReconcileAcceptedDirectMutationAfterReload(BridgeQueueState state, string requestId, string requestDigest)
314
+ {
315
+ return ReconcileAcceptedDirectMutationAfterReload(state, requestId, requestDigest,
316
+ DirectMutationCleanupPendingForIdentity(requestId, requestDigest));
317
+ }
318
+
319
+ internal static DirectMutationReloadOutcome ReconcileAcceptedDirectMutationAfterReload(BridgeQueueState state, string requestId, string requestDigest, bool releaseExactOwnerBeforeSettlement)
320
+ {
321
+ if (state == null) throw new ArgumentNullException("state");
322
+ var root = LoadDirectMutationRoot(state);
323
+ if (root == null) return DirectMutationReloadOutcome.NotApplicable;
324
+ RequireDirectMutationRoot(root, requestId, requestDigest);
325
+ if (!string.Equals(root["phase"] as string, "accepted_before_effect", StringComparison.Ordinal))
326
+ return DirectMutationReloadOutcome.NotApplicable;
327
+ string leaseRequestId;
328
+ string leaseRequestDigest;
329
+ if (!state.TryGetCurrentLeaseOwner(out leaseRequestId, out leaseRequestDigest))
330
+ return DirectMutationReloadOutcome.IdentityBlocked;
331
+ if (string.Equals(leaseRequestId, requestId, StringComparison.Ordinal)
332
+ && string.Equals(leaseRequestDigest, requestDigest, StringComparison.Ordinal))
333
+ {
334
+ if (!releaseExactOwnerBeforeSettlement) return DirectMutationReloadOutcome.WaitingExactOwner;
335
+ if (object.ReferenceEquals(state, ProductionQueueState)) ReleaseDirectMutationLease(requestId, requestDigest);
336
+ else state.ReleaseLease(requestId, requestDigest);
337
+ if (!state.TryGetCurrentLeaseOwner(out leaseRequestId, out leaseRequestDigest))
338
+ return DirectMutationReloadOutcome.IdentityBlocked;
339
+ }
340
+ if (!string.IsNullOrEmpty(leaseRequestId) || !string.IsNullOrEmpty(leaseRequestDigest))
341
+ return DirectMutationReloadOutcome.IdentityBlocked;
342
+ var owner = CaptureAdmissionOwner(state, requestId, requestDigest);
343
+ if (owner.OwnerState != UnityCliAdmissionPool.AdmissionOwnerState.WorkItem
344
+ && owner.OwnerState != UnityCliAdmissionPool.AdmissionOwnerState.Recovery)
345
+ return DirectMutationReloadOutcome.IdentityBlocked;
346
+ var terminal = ProtocolEnvelopeFactory.Error(requestId, "E_EXECUTION_TIMEOUT", "Direct mutation was accepted before domain reload but never crossed its effect barrier; no effect was executed.", null, new[] { "Submit a new request only after inspecting the exact terminal." });
347
+ root["phase"] = "terminal_pending";
348
+ root["terminalEnvelope"] = terminal;
349
+ state.SaveOperation(DirectMutationTerminalScope, root);
350
+ state.StoreTerminalFirst(DirectMutationTerminalScope, requestId, requestDigest, null, root["toolName"] as string, terminal);
351
+ UnityCliActiveOperation.BeginExactTerminalCleanup(DirectMutationTerminalScope, requestId, requestDigest, state);
352
+ return DirectMutationReloadOutcome.SettledEffectBefore;
353
+ }
354
+
265
355
  private static string TryResumeDirectMutation(string requestId, string requestDigest)
266
356
  {
267
357
  Dictionary<string, object> root;
@@ -284,35 +374,74 @@ namespace UnityCli
284
374
  bool recovered;
285
375
  try { if (!UnityCliSessionMutationLease.TryRecoverExact(requestId, requestDigest, out recovered)) return UnityCliSerializer.Serialize(ProtocolEnvelopeFactory.Error(requestId, "E_UNITY_BUSY", "Another mutation owns the selected Unity instance.", null, null, true, 250)); }
286
376
  catch (BridgeCommandException exception) { return SerializeBridgeExceptionEnvelope(requestId, exception); }
377
+ var toolName = root["toolName"] as string;
378
+ if (string.Equals(phase, "effect_started", StringComparison.Ordinal)
379
+ && (string.Equals(toolName, "editor.play", StringComparison.Ordinal) || string.Equals(toolName, "editor.stop", StringComparison.Ordinal)))
380
+ {
381
+ if (!TryFinalizeRecoveredPlayModeRoot(
382
+ ProductionQueueState, requestId, requestDigest,
383
+ EditorApplication.isPlaying, EditorApplication.isPaused, IsPlayModeChanging(),
384
+ EditorToolSupport.BuildEditorState, DateTimeOffset.UtcNow))
385
+ return UnityCliSerializer.Serialize(ProtocolEnvelopeFactory.Error(
386
+ requestId, "E_UNITY_BUSY", "The Play Mode transition is still active for this exact request.",
387
+ null, new[] { "Run tools run editor.play-mode.status with the same requestId." }, true, 100));
388
+ object recoveredTerminal;
389
+ if (UnityCliActiveOperation.TryGetTerminal(DirectMutationTerminalScope, requestId, requestDigest, out recoveredTerminal))
390
+ return UnityCliSerializer.Serialize(recoveredTerminal);
391
+ return ExecutionTimeoutEnvelope(requestId, "The recovered Play Mode terminal could not be read after publication.");
392
+ }
287
393
  object pending;
288
394
  var envelope = string.Equals(phase, "terminal_pending", StringComparison.Ordinal) && root.TryGetValue("terminalEnvelope", out pending) && pending != null
289
395
  ? pending
290
396
  : (object)ProtocolEnvelopeFactory.Error(requestId, "E_EXECUTION_TIMEOUT", "Direct mutation effect was interrupted before a terminal was frozen; outcome is unknown.");
291
397
  var result = PersistDirectMutationTerminal(requestId, requestDigest, UnityCliSerializer.Serialize(envelope));
292
- CleanupPublishedDirectMutation(requestId, requestDigest, AdmissionTerminalIsOutcomeUnknown(envelope));
398
+ CleanupPublishedDirectMutation(requestId, requestDigest);
293
399
  return result;
294
400
  }
295
401
 
296
- private static void CleanupPublishedDirectMutation(string requestId, string requestDigest, bool completeAdmission)
402
+ /// <summary>主循环重试已发布direct terminal的exact cleanup;失败保留固定marker等待下一帧。</summary>
403
+ private static void RetryDirectMutationTerminalCleanup()
404
+ {
405
+ RetryDirectMutationTerminalCleanup(ProductionQueueState);
406
+ }
407
+
408
+ internal static void RetryDirectMutationTerminalCleanupForTests(BridgeQueueState state)
409
+ {
410
+ if (state == null || state.IsolatedExecution == null) throw new ArgumentException("Direct mutation test state must be isolated.", "state");
411
+ RetryDirectMutationTerminalCleanup(state);
412
+ }
413
+
414
+ private static void RetryDirectMutationTerminalCleanup(BridgeQueueState state)
415
+ {
416
+ try { UnityCliActiveOperation.RetryExactTerminalCleanup(DirectMutationTerminalScope, state); }
417
+ catch (Exception exception) { state.Sinks.WriteLog("direct_mutation_cleanup_retry_failed " + exception.GetType().Name); }
418
+ }
419
+
420
+ /// <summary>terminal类型不决定slot释放;exact marker必须在root清理前durable存在。</summary>
421
+ private static void CleanupPublishedDirectMutation(string requestId, string requestDigest)
422
+ {
423
+ CleanupPublishedDirectMutation(ProductionQueueState, requestId, requestDigest);
424
+ }
425
+
426
+ private static void CleanupPublishedDirectMutation(BridgeQueueState state, string requestId, string requestDigest)
297
427
  {
298
428
  try
299
429
  {
300
430
  object terminal;
301
- if (!UnityCliActiveOperation.TryGetTerminal(DirectMutationTerminalScope, requestId, requestDigest, out terminal)) return;
302
- UnityCliActiveOperation.BeginExactTerminalCleanup(DirectMutationTerminalScope, requestId, requestDigest, completeAdmission);
431
+ if (!state.TryGetTerminal(DirectMutationTerminalScope, requestId, requestDigest, out terminal)) return;
432
+ AdmissionOwnerIdentity owner;
433
+ var completeAdmission = TryCaptureExactAdmissionOwner(state, requestId, requestDigest, out owner);
434
+ UnityCliActiveOperation.BeginExactTerminalCleanup(DirectMutationTerminalScope, requestId, requestDigest, state, completeAdmission);
303
435
  }
304
436
  catch { }
305
437
  }
306
438
 
307
- private static bool AdmissionTerminalIsOutcomeUnknown(object envelope)
439
+ internal static string RecoverPublishedDirectMutationForTests(BridgeQueueState state, string requestId, string requestDigest)
308
440
  {
309
- var value = envelope as IDictionary<string, object>;
310
- object ok;
311
- if (value == null || !value.TryGetValue("ok", out ok) || !(ok is bool) || (bool)ok) return false;
312
- object rawError;
313
- var error = value.TryGetValue("error", out rawError) ? rawError as IDictionary<string, object> : null;
314
- object code;
315
- return error != null && error.TryGetValue("code", out code) && string.Equals(code as string, "E_EXECUTION_TIMEOUT", StringComparison.Ordinal);
441
+ object terminal;
442
+ if (state == null || state.IsolatedExecution == null || !state.TryGetTerminal(DirectMutationTerminalScope, requestId, requestDigest, out terminal)) return null;
443
+ CleanupPublishedDirectMutation(state, requestId, requestDigest);
444
+ return UnityCliSerializer.Serialize(terminal);
316
445
  }
317
446
 
318
447
  private static void FinalizeDirectMutationExecution(string requestId, string requestDigest)
@@ -341,12 +470,13 @@ namespace UnityCli
341
470
  ReleaseDirectMutationLease(requestId, requestDigest);
342
471
  }
343
472
 
344
- /// <summary>主循环恢复direct terminal发布或精确lease清理,I/O不确定时保持root与owner。</summary>
473
+ /// <summary>主循环只对可直接观察的PlayMode root冻结原类型终态;其余direct effect仍保持unknown。</summary>
345
474
  private static bool TryFinalizeRecoveredPlayModeRoot(string requestId, string requestDigest)
346
475
  {
476
+ Dictionary<string, object> root;
347
477
  try
348
478
  {
349
- var root = LoadDirectMutationRoot();
479
+ root = LoadDirectMutationRoot();
350
480
  if (root != null && SessionState.GetBool(DirectMutationCleanupPendingSessionKey, false)
351
481
  && DirectMutationBindingMatches(requestId, requestDigest)
352
482
  && string.Equals(root["requestId"] as string, requestId, StringComparison.Ordinal)
@@ -355,19 +485,110 @@ namespace UnityCli
355
485
  UnityCliActiveOperation.Clear(DirectMutationTerminalScope);
356
486
  }
357
487
  catch { return false; }
358
- var resumed = TryResumeDirectMutation(requestId, requestDigest);
359
- if (resumed != null)
488
+
489
+ var finalized = TryFinalizeRecoveredPlayModeRoot(
490
+ ProductionQueueState, requestId, requestDigest,
491
+ EditorApplication.isPlaying, EditorApplication.isPaused, IsPlayModeChanging(),
492
+ EditorToolSupport.BuildEditorState, DateTimeOffset.UtcNow);
493
+ if (!finalized)
494
+ {
495
+ try { root = LoadDirectMutationRoot(); }
496
+ catch { root = null; }
497
+ var toolName = root == null ? null : root["toolName"] as string;
498
+ if (toolName != "editor.play" && toolName != "editor.stop")
499
+ finalized = TryResumeDirectMutation(requestId, requestDigest) != null;
500
+ }
501
+ if (finalized)
360
502
  {
361
503
  try { if (!UnityCliActiveOperation.Exists(DirectMutationTerminalScope)) ReleaseDirectMutationLease(requestId, requestDigest); }
362
504
  catch { }
363
505
  }
364
506
  if (!SessionState.GetBool(DirectMutationCleanupPendingSessionKey, false)
365
- || !DirectMutationBindingMatches(requestId, requestDigest)) return resumed != null;
507
+ || !DirectMutationBindingMatches(requestId, requestDigest)) return finalized;
366
508
  ReleaseDirectMutationLease(requestId, requestDigest);
367
509
  bool isOwned;
368
510
  return UnityCliSessionMutationLease.TryIsOwnedBy(requestId, requestDigest, out isOwned) && !isOwned;
369
511
  }
370
512
 
513
+ internal static bool TryFinalizeRecoveredPlayModeRootForTests(
514
+ BridgeQueueState state, string requestId, string requestDigest,
515
+ bool isPlaying, bool isPaused, bool isChanging, DateTimeOffset utcNow)
516
+ {
517
+ return TryFinalizeRecoveredPlayModeRoot(state, requestId, requestDigest, isPlaying, isPaused, isChanging,
518
+ () => new EditorStateInfo
519
+ {
520
+ PlayMode = new PlayModeInfo
521
+ {
522
+ IsPlaying = isPlaying,
523
+ IsPaused = isPaused,
524
+ IsPlayingOrWillChangePlaymode = isPlaying || isChanging
525
+ },
526
+ Busy = new BusyInfo { PlayModeChanging = isChanging }
527
+ }, utcNow);
528
+ }
529
+
530
+ /// <summary>固定root中的before/deadline与当前主线程状态共同构成跨reload完成证明。</summary>
531
+ private static bool TryFinalizeRecoveredPlayModeRoot(
532
+ BridgeQueueState state, string requestId, string requestDigest,
533
+ bool isPlaying, bool isPaused, bool isChanging,
534
+ Func<EditorStateInfo> readState, DateTimeOffset utcNow)
535
+ {
536
+ Dictionary<string, object> root;
537
+ try
538
+ {
539
+ root = LoadDirectMutationRoot(state);
540
+ RequireDirectMutationRoot(root, requestId, requestDigest);
541
+ }
542
+ catch { return false; }
543
+ if (!string.Equals(root["phase"] as string, "effect_started", StringComparison.Ordinal)) return false;
544
+ var toolName = root["toolName"] as string;
545
+ var enterPlayMode = string.Equals(toolName, "editor.play", StringComparison.Ordinal);
546
+ if (!enterPlayMode && !string.Equals(toolName, "editor.stop", StringComparison.Ordinal)) return false;
547
+
548
+ object beforeValue;
549
+ object deadlineValue;
550
+ DateTimeOffset deadline;
551
+ if (!root.TryGetValue("playModeWasPlaying", out beforeValue) || !(beforeValue is bool)
552
+ || !root.TryGetValue("absoluteDeadlineUtc", out deadlineValue) || !(deadlineValue is string)
553
+ || !DateTimeOffset.TryParse((string)deadlineValue, CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind, out deadline))
554
+ {
555
+ PublishDirectMutationTerminal(state, requestId, requestDigest,
556
+ ExecutionTimeoutEnvelope(requestId, "Play Mode recovery state is incomplete; outcome is unknown."));
557
+ object invalidTerminal;
558
+ return state.TryGetTerminal(DirectMutationTerminalScope, requestId, requestDigest, out invalidTerminal);
559
+ }
560
+
561
+ object terminal;
562
+ if (!isChanging && isPlaying == enterPlayMode)
563
+ {
564
+ var before = (bool)beforeValue;
565
+ var result = new PlayModeResult
566
+ {
567
+ Changed = enterPlayMode ? !before : before,
568
+ IsPlaying = isPlaying,
569
+ IsPaused = isPaused
570
+ };
571
+ terminal = enterPlayMode
572
+ ? (object)new EditorPlayTool.Response { Result = result, State = readState(), PlannedEffects = new List<EditorPlannedEffect> { new EditorPlannedEffect { Type = "editor.play" } } }
573
+ : new EditorStopTool.Response { Result = result, State = readState(), PlannedEffects = new List<EditorPlannedEffect> { new EditorPlannedEffect { Type = "editor.stop" } } };
574
+ PublishDirectMutationTerminal(state, requestId, requestDigest,
575
+ UnityCliSerializer.Serialize(ProtocolEnvelopeFactory.Success(requestId, terminal)));
576
+ }
577
+ else
578
+ {
579
+ if (utcNow < deadline) return false;
580
+ var code = enterPlayMode ? "E_PLAY_MODE_TERMINAL_NOT_OBSERVED" : "E_EDIT_MODE_TERMINAL_NOT_OBSERVED";
581
+ var message = enterPlayMode
582
+ ? "Play Mode did not reach a stable playing state before the immutable deadline."
583
+ : "The Editor did not reach stable Edit Mode before the immutable deadline.";
584
+ terminal = ProtocolEnvelopeFactory.Error(requestId, code, message, null,
585
+ new[] { "Run tools run editor.play-mode.status with the same requestId before deciding whether to submit a new request." });
586
+ PublishDirectMutationTerminal(state, requestId, requestDigest, UnityCliSerializer.Serialize(terminal));
587
+ }
588
+ object published;
589
+ return state.TryGetTerminal(DirectMutationTerminalScope, requestId, requestDigest, out published);
590
+ }
591
+
371
592
  /// <summary>绑定本domain正在执行的direct mutation;SessionState只跨本次Editor启动的domain reload存活。</summary>
372
593
  private static void BindDirectMutationLease(string requestId, string requestDigest)
373
594
  {
@@ -407,6 +628,12 @@ namespace UnityCli
407
628
  && string.Equals(SessionState.GetString(DirectMutationRequestDigestSessionKey, string.Empty), requestDigest, StringComparison.Ordinal);
408
629
  }
409
630
 
631
+ private static bool DirectMutationCleanupPendingForIdentity(string requestId, string requestDigest)
632
+ {
633
+ return DirectMutationBindingMatches(requestId, requestDigest)
634
+ && SessionState.GetBool(DirectMutationCleanupPendingSessionKey, false);
635
+ }
636
+
410
637
  /// <summary>普通 Tool 的 effect 提交只保留 runner 协调语义,不写请求级持久状态。</summary>
411
638
  private static object PrepareToolEffect(BridgeRequest request, UnityCliToolCompletionContract contract, object typedInput)
412
639
  {
@@ -611,45 +838,113 @@ namespace UnityCli
611
838
  return request == null ? null : request.Command;
612
839
  }
613
840
 
614
- /// <summary>queue 只需保守判断资源所有权;解析失败按 mutation 串行,最终错误仍由 coordinator 结构化返回。</summary>
615
- private static bool CoordinatedRequestIsMutation(BridgeRequest request)
841
+ /// <summary>HTTP worker只读取immutable request与已冻结registry;未知或依赖场景现状的分类显式按Mutation关闭。</summary>
842
+ internal static UnityCliBridgeQueuePolicy.WorkClass QueueRequestWorkClass(BridgeRequest request)
616
843
  {
617
- try
618
- {
619
- if (request != null && string.Equals(request.Command, "internal.editor.compile.start", StringComparison.Ordinal))
620
- return request.Execution == null || request.Execution.DryRun != true;
621
- var contract = ResolveRequestCompletionContract(request);
622
- return RequestRequiresMutation(request, contract);
623
- }
624
- catch
844
+ if (request == null) return UnityCliBridgeQueuePolicy.WorkClass.Mutation;
845
+ var toolName = RequestToolName(request);
846
+ if (!string.IsNullOrEmpty(toolName) && toolName.StartsWith("scene.", StringComparison.Ordinal))
847
+ return UnityCliBridgeQueuePolicy.WorkClass.Mutation;
848
+ UnityCliBridgeQueuePolicy.WorkClass registered;
849
+ if (CommandRegistry.TryGetQueueClass(request.Command, out registered)) return registered;
850
+ var contract = ResolveQueueCompletionContract(request);
851
+ if (contract == null)
852
+ return IsReadOnlyBridgeCommand(request.Command)
853
+ ? UnityCliBridgeQueuePolicy.WorkClass.OrdinaryRead
854
+ : UnityCliBridgeQueuePolicy.WorkClass.Mutation;
855
+ if (request.Execution != null && request.Execution.DryRun == true) return UnityCliBridgeQueuePolicy.WorkClass.OrdinaryRead;
856
+ if (!contract.Mutation) return UnityCliBridgeQueuePolicy.WorkClass.OrdinaryRead;
857
+ bool requiresSceneState;
858
+ var inputMutation = RequestMutationFromImmutableInput(contract.ToolName, RequestBusinessInput(request), out requiresSceneState);
859
+ return requiresSceneState || inputMutation
860
+ ? UnityCliBridgeQueuePolicy.WorkClass.Mutation
861
+ : UnityCliBridgeQueuePolicy.WorkClass.OrdinaryRead;
862
+ }
863
+
864
+ /// <summary>worker只读取内置冻结contract;项目自定义Tool未知时由调用方保守归Mutation。</summary>
865
+ private static UnityCliToolCompletionContract ResolveQueueCompletionContract(BridgeRequest request)
866
+ {
867
+ var toolName = RequestToolName(request);
868
+ if (IsInstanceCleanupCommand(toolName)) return InstanceCleanupCompletionContract();
869
+ UnityCliToolCompletionContract contract;
870
+ if (UnityCliToolCompletionRegistry.TryGet(toolName, out contract)) return contract;
871
+ return string.Equals(toolName, "code.exec", StringComparison.Ordinal) ? UnityCliToolCompletionRegistry.CodeExec : null;
872
+ }
873
+
874
+ private sealed class DurableMutationBarrierProof
875
+ {
876
+ internal UnityCliBridgeQueuePolicy.DurableMutationBarrier State;
877
+ internal string RequestId;
878
+ internal string RequestDigest;
879
+ }
880
+
881
+ /// <summary>durable lease三态同时冻结当前owner identity,供锁外control root proof精确绑定。</summary>
882
+ private static DurableMutationBarrierProof CurrentDurableMutationBarrier(BridgeQueueState state)
883
+ {
884
+ string requestId;
885
+ string requestDigest;
886
+ if (state == null || !state.TryGetCurrentLeaseOwner(out requestId, out requestDigest))
887
+ return new DurableMutationBarrierProof { State = UnityCliBridgeQueuePolicy.DurableMutationBarrier.Unreadable };
888
+ if (string.IsNullOrEmpty(requestId) && string.IsNullOrEmpty(requestDigest))
889
+ return new DurableMutationBarrierProof { State = UnityCliBridgeQueuePolicy.DurableMutationBarrier.Clear };
890
+ if (!IsSafeId(requestId) || !IsSha256(requestDigest))
891
+ return new DurableMutationBarrierProof { State = UnityCliBridgeQueuePolicy.DurableMutationBarrier.Unreadable };
892
+ return new DurableMutationBarrierProof
625
893
  {
626
- return true;
627
- }
894
+ State = UnityCliBridgeQueuePolicy.DurableMutationBarrier.Held,
895
+ RequestId = requestId,
896
+ RequestDigest = requestDigest
897
+ };
628
898
  }
629
899
 
630
- /// <summary>descriptor 表示潜在风险;本次 input 没有 setter/action 时按 read 执行。</summary>
900
+ /// <summary>主线程在pure input裁决后,仅对真正依赖场景现状的幂等分支读取Unity对象。</summary>
631
901
  private static bool RequestRequiresMutation(BridgeRequest request, UnityCliToolCompletionContract contract)
632
902
  {
633
903
  if (request == null || contract == null || !contract.Mutation || (request.Execution != null && request.Execution.DryRun == true)) return false;
634
- var name = contract.ToolName;
635
904
  var input = RequestBusinessInput(request);
636
- if (name == "graphics.reflection-probe") return HasAnyMeaningfulInput(input, "mode", "resolution", "bounces", "hdr", "boxSize", "boxOffset") || CreateProbeNeedsMutation(input, true);
637
- if (name == "graphics.light-probe-group") return HasAnyMeaningfulInput(input, "positions") || CreateProbeNeedsMutation(input, false);
905
+ bool requiresSceneState;
906
+ var inputMutation = RequestMutationFromImmutableInput(contract.ToolName, input, out requiresSceneState);
907
+ if (!requiresSceneState) return inputMutation;
908
+ if (contract.ToolName == "physics.rigidbody") return RigidbodyNeedsMutation(input);
909
+ if (contract.ToolName == "graphics.reflection-probe") return CreateProbeNeedsMutation(input, true);
910
+ if (contract.ToolName == "graphics.light-probe-group") return CreateProbeNeedsMutation(input, false);
911
+ return true;
912
+ }
913
+
914
+ /// <summary>只按immutable JSON字段判断;需要场景现状的幂等分支通过requiresSceneState显式上报。</summary>
915
+ private static bool RequestMutationFromImmutableInput(string name, IDictionary<string, object> input, out bool requiresSceneState)
916
+ {
917
+ requiresSceneState = false;
918
+ if (name == "graphics.reflection-probe")
919
+ {
920
+ if (HasAnyMeaningfulInput(input, "mode", "resolution", "bounces", "hdr", "boxSize", "boxOffset")) return true;
921
+ requiresSceneState = InputBoolean(input, "create");
922
+ return false;
923
+ }
924
+ if (name == "graphics.light-probe-group")
925
+ {
926
+ if (HasAnyMeaningfulInput(input, "positions")) return true;
927
+ requiresSceneState = InputBoolean(input, "create");
928
+ return false;
929
+ }
638
930
  if (name == "graphics.skybox-set-properties") return HasAllMeaningfulInput(input, "property", "value");
639
931
  if (name == "build.settings.set") return HasAnyInput(input, "apiCompatibilityLevel", "applicationIdentifier", "bundleVersion", "companyId", "productName", "scriptingBackend");
640
932
  if (name == "graphics.bake-settings") return HasAnyInput(input, "bakedGI", "bounces", "directSamples", "environmentSamples", "filtering", "indirectSamples", "lightingMode", "realtimeGI");
641
- if (name == "graphics.probe-positions") return HasAnyInput(input, "positions");
642
933
  if (name == "graphics.quality") return HasAnyInput(input, "anisotropicFiltering", "antiAliasing", "lodBias", "masterTextureLimit", "maximumLODLevel", "qualityLevel", "realtimeReflectionProbes", "shadowDistance", "shadowResolution", "shadows", "softParticles", "vSyncCount");
643
- if (name == "graphics.settings") return HasAnyInput(input, "antiAliasing", "lodBias", "masterTextureLimit", "maximumLODLevel", "property", "renderPipeline", "shadowDistance", "vSyncCount", "value");
644
934
  if (name == "graphics.pipeline-settings") return HasAnyInput(input, "renderPipeline");
645
- if (name == "graphics.pipeline-quality") return HasAnyInput(input, "qualityLevel");
646
935
  if (name == "graphics.skybox-set-ambient") return HasAnyMeaningfulInput(input, "ambientEquatorColor", "ambientGroundColor", "ambientSkyColor", "color", "intensity", "mode");
647
936
  if (name == "graphics.skybox-set-fog") return HasAnyMeaningfulInput(input, "color", "density", "enabled", "endDistance", "mode", "startDistance");
648
937
  if (name == "graphics.skybox-set-reflection") return HasAnyMeaningfulInput(input, "bounces", "intensity", "mode", "resolution");
649
938
  if (name == "graphics.scene-debug") return HasAnyInput(input, "drawMode", "shadingMode");
650
939
  if (name == "physics.collision-matrix") return HasAllInput(input, "layerA", "layerB", "ignore");
651
940
  if (name == "physics.settings") return HasAnyInput(input, "autoSimulation", "bounceThreshold", "defaultContactOffset", "defaultSolverIterations", "defaultSolverVelocityIterations", "gravity", "gravity2d", "property", "queriesHitBackfaces", "queriesHitTriggers", "queriesStartInColliders", "value");
652
- if (name == "physics.rigidbody") return !InputBoolean(input, "validate") && (HasAnyMeaningfulInput(input, "angularDrag", "drag", "isKinematic", "mass", "useGravity") || RigidbodyNeedsMutation(input));
941
+ if (name == "physics.rigidbody")
942
+ {
943
+ if (InputBoolean(input, "validate")) return false;
944
+ if (HasAnyMeaningfulInput(input, "angularDrag", "drag", "isKinematic", "mass", "useGravity")) return true;
945
+ requiresSceneState = true;
946
+ return false;
947
+ }
653
948
  if (name == "physics.simulation") return InputBoolean(input, "simulate");
654
949
  if (name == "texture.import-settings") return HasAnyInput(input, "alphaIsTransparency", "compression", "filterMode", "maxTextureSize", "mipmap", "readable", "sRGB", "textureType", "type", "wrapMode");
655
950
  if (name == "profiler.deep") return HasAnyInput(input, "enabled");
@@ -740,8 +1035,11 @@ namespace UnityCli
740
1035
  /// <summary>Context/catalog/health 类请求没有 business mutation,不得创建 request journal。</summary>
741
1036
  private static bool IsReadOnlyBridgeCommand(string command)
742
1037
  {
743
- if (IsEditorStateSnapshotCommand(command)) return true;
744
- return string.Equals(command, "project-tool.list", StringComparison.Ordinal)
1038
+ if (IsEditorStateSnapshotCommand(command) || IsDoctorDiagnosticCommand(command)) return true;
1039
+ return IsProjectToolCatalogReadCommand(command)
1040
+ || string.Equals(command, "runtime-tool.groups", StringComparison.Ordinal)
1041
+ || string.Equals(command, "runtime-tool.summary", StringComparison.Ordinal)
1042
+ || string.Equals(command, "runtime-tool.describe", StringComparison.Ordinal)
745
1043
  || string.Equals(command, "runtime-tool.list", StringComparison.Ordinal);
746
1044
  }
747
1045