@alpsckr/unitycli 0.4.3 → 0.4.5

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 (425) 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 +145 -39
  49. package/dist/bridge/session-supervisor.js.map +1 -1
  50. package/dist/bridge/start-diagnostics.d.ts +19 -0
  51. package/dist/bridge/start-diagnostics.js +96 -0
  52. package/dist/bridge/start-diagnostics.js.map +1 -0
  53. package/dist/bridge/types.d.ts +6 -5
  54. package/dist/bridge/types.js +16 -2
  55. package/dist/bridge/types.js.map +1 -1
  56. package/dist/catalog-protocol.d.ts +64 -0
  57. package/dist/catalog-protocol.js +460 -0
  58. package/dist/catalog-protocol.js.map +1 -0
  59. package/dist/cli-commands/command.js +84 -61
  60. package/dist/cli-commands/command.js.map +1 -1
  61. package/dist/cli-commands/extensions.js +70 -50
  62. package/dist/cli-commands/extensions.js.map +1 -1
  63. package/dist/cli-commands/instances.js +1 -1
  64. package/dist/cli-commands/instances.js.map +1 -1
  65. package/dist/cli-commands/package.js +10 -2
  66. package/dist/cli-commands/package.js.map +1 -1
  67. package/dist/cli-process-owners.d.ts +6 -0
  68. package/dist/cli-process-owners.js +27 -0
  69. package/dist/cli-process-owners.js.map +1 -0
  70. package/dist/cli.d.ts +1 -1
  71. package/dist/cli.js +20 -5
  72. package/dist/cli.js.map +1 -1
  73. package/dist/contracts/command-contract.js +14 -4
  74. package/dist/contracts/command-contract.js.map +1 -1
  75. package/dist/doctor.d.ts +4 -6
  76. package/dist/doctor.js +451 -398
  77. package/dist/doctor.js.map +1 -1
  78. package/dist/editor-blocker-observer.d.ts +2 -1
  79. package/dist/editor-blocker-observer.js +40 -13
  80. package/dist/editor-blocker-observer.js.map +1 -1
  81. package/dist/extension-availability.d.ts +27 -9
  82. package/dist/extension-availability.js +96 -56
  83. package/dist/extension-availability.js.map +1 -1
  84. package/dist/extensions/descriptor-loader.d.ts +2 -2
  85. package/dist/extensions/descriptor-loader.js +3 -4
  86. package/dist/extensions/descriptor-loader.js.map +1 -1
  87. package/dist/extensions/index.d.ts +1 -1
  88. package/dist/extensions/index.js +1 -1
  89. package/dist/extensions/index.js.map +1 -1
  90. package/dist/extensions/installer.d.ts +57 -5
  91. package/dist/extensions/installer.js +263 -64
  92. package/dist/extensions/installer.js.map +1 -1
  93. package/dist/extensions/registry.js +3 -5
  94. package/dist/extensions/registry.js.map +1 -1
  95. package/dist/extensions/state-contract.d.ts +11 -0
  96. package/dist/extensions/state-contract.js +50 -0
  97. package/dist/extensions/state-contract.js.map +1 -0
  98. package/dist/instances.d.ts +19 -1
  99. package/dist/instances.js +39 -0
  100. package/dist/instances.js.map +1 -1
  101. package/dist/package-manager.d.ts +0 -24
  102. package/dist/package-manager.js +5 -58
  103. package/dist/package-manager.js.map +1 -1
  104. package/dist/project-tool-discovery.d.ts +5 -2
  105. package/dist/project-tool-discovery.js +22 -18
  106. package/dist/project-tool-discovery.js.map +1 -1
  107. package/dist/runtime-code.js +1 -1
  108. package/dist/runtime-code.js.map +1 -1
  109. package/dist/tool-name.d.ts +4 -0
  110. package/dist/tool-name.js +15 -0
  111. package/dist/tool-name.js.map +1 -0
  112. package/dist/tools/.generated-completion-registry.d.ts +2 -0
  113. package/dist/tools/.generated-completion-registry.js +763 -325
  114. package/dist/tools/.generated-completion-registry.js.map +1 -1
  115. package/dist/tools/.generated-registry.d.ts +6 -11
  116. package/dist/tools/.generated-registry.js +6 -11
  117. package/dist/tools/.generated-registry.js.map +1 -1
  118. package/dist/tools/.generated-schemas.js +11 -16
  119. package/dist/tools/.generated-schemas.js.map +1 -1
  120. package/dist/tools/asset/refresh-status.d.ts +3 -0
  121. package/dist/tools/asset/refresh-status.js +12 -0
  122. package/dist/tools/asset/refresh-status.js.map +1 -0
  123. package/dist/tools/asset/refresh.js +7 -3
  124. package/dist/tools/asset/refresh.js.map +1 -1
  125. package/dist/tools/build/player-status.d.ts +3 -0
  126. package/dist/tools/build/player-status.js +12 -0
  127. package/dist/tools/build/player-status.js.map +1 -0
  128. package/dist/tools/build/run.js +1 -1
  129. package/dist/tools/build/run.js.map +1 -1
  130. package/dist/tools/catalog-control.d.ts +22 -0
  131. package/dist/tools/catalog-control.js +335 -0
  132. package/dist/tools/catalog-control.js.map +1 -0
  133. package/dist/tools/catalog.d.ts +7 -9
  134. package/dist/tools/catalog.js +66 -45
  135. package/dist/tools/catalog.js.map +1 -1
  136. package/dist/tools/commands.d.ts +1 -1
  137. package/dist/tools/commands.js +44 -11
  138. package/dist/tools/commands.js.map +1 -1
  139. package/dist/tools/define.js +3 -2
  140. package/dist/tools/define.js.map +1 -1
  141. package/dist/tools/dispatcher.d.ts +53 -6
  142. package/dist/tools/dispatcher.js +277 -108
  143. package/dist/tools/dispatcher.js.map +1 -1
  144. package/dist/tools/editor/compile.js +1 -1
  145. package/dist/tools/editor/compile.js.map +1 -1
  146. package/dist/tools/editor/play-mode-status.d.ts +3 -0
  147. package/dist/tools/editor/play-mode-status.js +12 -0
  148. package/dist/tools/editor/play-mode-status.js.map +1 -0
  149. package/dist/tools/editor/readiness.d.ts +0 -1
  150. package/dist/tools/editor/readiness.js +0 -1
  151. package/dist/tools/editor/readiness.js.map +1 -1
  152. package/dist/tools/editor/refresh.d.ts +7 -3
  153. package/dist/tools/editor/refresh.js +20 -15
  154. package/dist/tools/editor/refresh.js.map +1 -1
  155. package/dist/tools/editor/state.d.ts +2 -2
  156. package/dist/tools/editor/state.js +13 -12
  157. package/dist/tools/editor/state.js.map +1 -1
  158. package/dist/tools/metadata/derived.js +1 -7
  159. package/dist/tools/metadata/derived.js.map +1 -1
  160. package/dist/tools/metadata/status.js +1 -12
  161. package/dist/tools/metadata/status.js.map +1 -1
  162. package/dist/tools/metadata/types.d.ts +0 -2
  163. package/dist/tools/navmesh/live.js +1 -1
  164. package/dist/tools/navmesh/live.js.map +1 -1
  165. package/dist/tools/occlusion/live.js +1 -1
  166. package/dist/tools/occlusion/live.js.map +1 -1
  167. package/dist/tools/package/deploy.js +2 -2
  168. package/dist/tools/package/deploy.js.map +1 -1
  169. package/dist/tools/package/embed.js +1 -1
  170. package/dist/tools/package/embed.js.map +1 -1
  171. package/dist/tools/package/install-status.d.ts +3 -0
  172. package/dist/tools/package/install-status.js +12 -0
  173. package/dist/tools/package/install-status.js.map +1 -0
  174. package/dist/tools/package/install.js +3 -3
  175. package/dist/tools/package/install.js.map +1 -1
  176. package/dist/tools/package/live-resolve.js +45 -12
  177. package/dist/tools/package/live-resolve.js.map +1 -1
  178. package/dist/tools/package/registry-clear.js.map +1 -1
  179. package/dist/tools/package/registry-set.js.map +1 -1
  180. package/dist/tools/package/remove-status.d.ts +3 -0
  181. package/dist/tools/package/remove-status.js +12 -0
  182. package/dist/tools/package/remove-status.js.map +1 -0
  183. package/dist/tools/package/remove.js +3 -3
  184. package/dist/tools/package/remove.js.map +1 -1
  185. package/dist/tools/package/resolve.js +3 -3
  186. package/dist/tools/package/resolve.js.map +1 -1
  187. package/dist/tools/package/restore.js +2 -2
  188. package/dist/tools/package/restore.js.map +1 -1
  189. package/dist/tools/package/scoped-registry-add.js.map +1 -1
  190. package/dist/tools/status/support.d.ts +13 -0
  191. package/dist/tools/status/support.js +28 -0
  192. package/dist/tools/status/support.js.map +1 -0
  193. package/dist/tools/test/cancel.js +2 -2
  194. package/dist/tools/test/cancel.js.map +1 -1
  195. package/dist/tools/test/run-status.d.ts +3 -0
  196. package/dist/tools/test/run-status.js +12 -0
  197. package/dist/tools/test/run-status.js.map +1 -0
  198. package/dist/tools/test/run.js +7 -4
  199. package/dist/tools/test/run.js.map +1 -1
  200. package/dist/tools/tools/group-summaries.js +10 -14
  201. package/dist/tools/tools/group-summaries.js.map +1 -1
  202. package/docs/architecture.md +4 -4
  203. package/docs/availability-differences.md +4 -4
  204. package/docs/command-execution.md +4 -3
  205. package/docs/error-codes.md +13 -5
  206. package/docs/extensions.md +1 -7
  207. package/docs/risk-and-side-effects.md +4 -3
  208. package/docs/safety-boundaries.md +3 -1
  209. package/docs/target-and-instance.md +1 -1
  210. package/docs/tool-discovery.md +17 -10
  211. package/docs/tool-execution.md +18 -7
  212. package/extensions/cinemachine/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  213. package/extensions/hdrp/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  214. package/extensions/probuilder/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  215. package/extensions/urp/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  216. package/extensions/vfx-graph/unitypkg/Editor/UnityCliExtensionAssembly.cs +3 -0
  217. package/package.json +2 -4
  218. package/skills/unitycli/SKILL.md +11 -2
  219. package/skills/unitycli/manifest.json +1 -1
  220. package/skills/unitycli/references/operation-protocol.md +3 -3
  221. package/unitypkg/Editor/AssemblyInfo.cs +5 -0
  222. package/unitypkg/Editor/Bridge/BridgeCommandRegistry.cs +49 -14
  223. package/unitypkg/Editor/Bridge/BridgeHttpServer.cs +9 -35
  224. package/unitypkg/Editor/Bridge/BridgeQueueState.cs +672 -0
  225. package/unitypkg/Editor/Bridge/BridgeWorkQueue.cs +970 -120
  226. package/unitypkg/Editor/BridgeStartupStateMachine.cs +59 -1
  227. package/unitypkg/Editor/Commands/ProjectToolCommands.cs +4 -0
  228. package/unitypkg/Editor/Commands/RuntimeToolCommands.cs +3 -0
  229. package/unitypkg/Editor/Generated/UnityCliToolCompletionRegistry.g.cs +379 -14
  230. package/unitypkg/Editor/Infrastructure/CodeExecSupport.cs +28 -13
  231. package/unitypkg/Editor/Infrastructure/RegistryOwnershipResolver.cs +132 -105
  232. package/unitypkg/Editor/Infrastructure/UnityCliActiveOperation.cs +138 -29
  233. package/unitypkg/Editor/Infrastructure/UnityCliAdmission.cs +1856 -237
  234. package/unitypkg/Editor/Infrastructure/UnityCliDoctorDiagnostics.cs +220 -0
  235. package/unitypkg/Editor/Infrastructure/UnityCliDurableStatusCoordinator.cs +118 -0
  236. package/unitypkg/Editor/Infrastructure/UnityCliEditorStateSnapshot.cs +2 -1
  237. package/unitypkg/Editor/Infrastructure/UnityCliHierarchyPager.cs +203 -0
  238. package/unitypkg/Editor/Infrastructure/UnityCliInstanceCleanup.cs +2 -1
  239. package/unitypkg/Editor/Infrastructure/UnityCliInstanceRegistry.cs +201 -63
  240. package/unitypkg/Editor/Infrastructure/UnityCliJson.cs +2 -3
  241. package/unitypkg/Editor/Infrastructure/UnityCliLiveBuildCoordinator.cs +289 -46
  242. package/unitypkg/Editor/Infrastructure/UnityCliLiveRefreshCoordinator.cs +87 -53
  243. package/unitypkg/Editor/Infrastructure/UnityCliNavMeshBakeCoordinator.cs +4 -4
  244. package/unitypkg/Editor/Infrastructure/UnityCliObjectRef.cs +128 -25
  245. package/unitypkg/Editor/Infrastructure/UnityCliObjectSerializer.cs +435 -327
  246. package/unitypkg/Editor/Infrastructure/UnityCliOcclusionBakeCoordinator.cs +4 -4
  247. package/unitypkg/Editor/Infrastructure/UnityCliOperationCoordinator.cs +351 -53
  248. package/unitypkg/Editor/Infrastructure/UnityCliPackageResolveCoordinator.cs +118 -18
  249. package/unitypkg/Editor/Infrastructure/UnityCliPackageResolveState.cs +20 -1
  250. package/unitypkg/Editor/Infrastructure/UnityCliReadGeneration.cs +97 -0
  251. package/unitypkg/Editor/Infrastructure/UnityCliReflection.cs +644 -133
  252. package/unitypkg/Editor/Infrastructure/UnityCliReloadOperation.cs +128 -41
  253. package/unitypkg/Editor/Infrastructure/UnityCliRuntimeExecutor.cs +14 -84
  254. package/unitypkg/Editor/Infrastructure/UnityCliSerializer.cs +220 -0
  255. package/unitypkg/Editor/Infrastructure/UnityCliSessionMutationLease.cs +69 -29
  256. package/unitypkg/Editor/Infrastructure/UnityCliTestRunCoordinator.cs +440 -54
  257. package/unitypkg/Editor/Infrastructure/UnityCliWaiter.cs +128 -35
  258. package/unitypkg/Editor/ProjectTools/IUnityCliTool.cs +9 -1
  259. package/unitypkg/Editor/ProjectTools/OfficialAttributeToolRegistry.cs +32 -31
  260. package/unitypkg/Editor/ProjectTools/ProjectToolRegistry.cs +607 -458
  261. package/unitypkg/Editor/ProjectTools/ProjectToolSchemaValidator.cs +35 -7
  262. package/unitypkg/Editor/ProjectTools/RegisteredTool.cs +501 -60
  263. package/unitypkg/Editor/ProjectTools/UnityCliProjectToolContext.cs +21 -0
  264. package/unitypkg/Editor/ProjectTools/UnityCliSchemaDeriver.cs +36 -7
  265. package/unitypkg/Editor/ProjectTools/UnityCliToolName.cs +30 -0
  266. package/unitypkg/Editor/Properties/AssemblyInfo.cs +3 -0
  267. package/unitypkg/Editor/Tools/asset/get-data.cs +16 -7
  268. package/unitypkg/Editor/Tools/build/BuildSceneStateTool.cs +23 -3
  269. package/unitypkg/Editor/Tools/build/BuildToolDtos.cs +2 -1
  270. package/unitypkg/Editor/Tools/build/scene/add.cs +6 -3
  271. package/unitypkg/Editor/Tools/build/scene/disable.cs +1 -1
  272. package/unitypkg/Editor/Tools/build/scene/enable.cs +1 -1
  273. package/unitypkg/Editor/Tools/build/scene/list.cs +1 -1
  274. package/unitypkg/Editor/Tools/build/scene/remove.cs +13 -3
  275. package/unitypkg/Editor/Tools/camera/CameraToolSupport.cs +38 -38
  276. package/unitypkg/Editor/Tools/camera/multiview.cs +13 -6
  277. package/unitypkg/Editor/Tools/camera/screenshot.cs +14 -7
  278. package/unitypkg/Editor/Tools/component/get.cs +16 -9
  279. package/unitypkg/Editor/Tools/component/modify.cs +6 -4
  280. package/unitypkg/Editor/Tools/console/clear.cs +7 -6
  281. package/unitypkg/Editor/Tools/console/get.cs +48 -16
  282. package/unitypkg/Editor/Tools/editor/EditorToolSupport.cs +143 -28
  283. package/unitypkg/Editor/Tools/editor/menu-items.cs +1 -1
  284. package/unitypkg/Editor/Tools/editor/play.cs +2 -11
  285. package/unitypkg/Editor/Tools/editor/selection.cs +1 -1
  286. package/unitypkg/Editor/Tools/editor/stop.cs +2 -11
  287. package/unitypkg/Editor/Tools/editor/windows.cs +1 -1
  288. package/unitypkg/Editor/Tools/go/GameObjectToolSupport.cs +54 -4
  289. package/unitypkg/Editor/Tools/go/delete.cs +1 -1
  290. package/unitypkg/Editor/Tools/go/find.cs +167 -70
  291. package/unitypkg/Editor/Tools/graphics/GraphicsToolSupport.cs +25 -0
  292. package/unitypkg/Editor/Tools/graphics/light-probe-group.cs +9 -3
  293. package/unitypkg/Editor/Tools/graphics/pipeline-info.cs +1 -1
  294. package/unitypkg/Editor/Tools/graphics/pipeline-settings.cs +1 -1
  295. package/unitypkg/Editor/Tools/graphics/quality.cs +2 -2
  296. package/unitypkg/Editor/Tools/graphics/reflection-probe.cs +3 -3
  297. package/unitypkg/Editor/Tools/graphics/stats.cs +1 -1
  298. package/unitypkg/Editor/Tools/object/ObjectToolSupport.cs +4 -4
  299. package/unitypkg/Editor/Tools/object/get-data.cs +9 -7
  300. package/unitypkg/Editor/Tools/object/modify.cs +7 -5
  301. package/unitypkg/Editor/Tools/physics/raycast.cs +24 -6
  302. package/unitypkg/Editor/Tools/prefab/stage.cs +1 -1
  303. package/unitypkg/Editor/Tools/profiler/ProfilerFrameDebuggerSupport.cs +94 -21
  304. package/unitypkg/Editor/Tools/profiler/ProfilerMemorySnapshotSupport.cs +397 -10
  305. package/unitypkg/Editor/Tools/profiler/ProfilerToolSupport.cs +18 -4
  306. package/unitypkg/Editor/Tools/profiler/frame-debugger/enable.cs +1 -1
  307. package/unitypkg/Editor/Tools/profiler/frame-debugger/events.cs +88 -87
  308. package/unitypkg/Editor/Tools/profiler/hierarchy.cs +278 -112
  309. package/unitypkg/Editor/Tools/profiler/memory-snapshot/compare.cs +9 -5
  310. package/unitypkg/Editor/Tools/profiler/memory-snapshot/list.cs +37 -35
  311. package/unitypkg/Editor/Tools/profiler/memory-snapshot/take.cs +99 -97
  312. package/unitypkg/Editor/Tools/project/info.cs +1 -1
  313. package/unitypkg/Editor/Tools/project/layers.cs +1 -1
  314. package/unitypkg/Editor/Tools/project/tags.cs +1 -1
  315. package/unitypkg/Editor/Tools/reflection/method/call.cs +9 -7
  316. package/unitypkg/Editor/Tools/reflection/method/find.cs +27 -37
  317. package/unitypkg/Editor/Tools/reflection/type/schema.cs +24 -28
  318. package/unitypkg/Editor/Tools/scene/gameobject/component.cs +8 -5
  319. package/unitypkg/Editor/Tools/scene/gameobject/components.cs +10 -7
  320. package/unitypkg/Editor/Tools/scene/gameobject-support.cs +104 -8
  321. package/unitypkg/Editor/Tools/scene/gameobject.cs +37 -16
  322. package/unitypkg/Editor/Tools/scene/hierarchy.cs +146 -142
  323. package/unitypkg/Editor/Tools/screenshot/EditorElementCaptureToolSupport.cs +77 -25
  324. package/unitypkg/Editor/Tools/screenshot/ScreenshotToolSupport.cs +315 -95
  325. package/unitypkg/Editor/Tools/screenshot/editor-element.cs +7 -5
  326. package/unitypkg/Editor/Tools/screenshot/game-view.cs +35 -21
  327. package/unitypkg/Editor/Tools/screenshot/isolated-object.cs +29 -23
  328. package/unitypkg/Editor/Tools/screenshot/orbit.cs +37 -25
  329. package/unitypkg/Editor/Tools/screenshot/scene-view.cs +25 -18
  330. package/unitypkg/Editor/Tools/screenshot/screen.cs +177 -17
  331. package/unitypkg/Editor/Tools/screenshot/surround.cs +83 -79
  332. package/unitypkg/Editor/Tools/ui/render-preview.cs +16 -11
  333. package/unitypkg/Editor/UnityCliBridge.cs +97 -34
  334. package/unitypkg/Runtime/Infrastructure/UnityCliRuntimeBridge.cs +753 -122
  335. package/unitypkg/Runtime/Properties/AssemblyInfo.cs +5 -0
  336. package/unitypkg/Runtime/UnityCli.Runtime.asmdef +1 -1
  337. package/unitypkg/Shared/AssemblyInfo.cs +4 -0
  338. package/unitypkg/Shared/UnityCliAdmissionPool.cs +265 -75
  339. package/unitypkg/Shared/UnityCliBridgeQueuePolicy.cs +410 -0
  340. package/unitypkg/Shared/UnityCliBridgeWorkItemState.cs +47 -0
  341. package/unitypkg/Shared/UnityCliExceptionProjection.cs +151 -0
  342. package/unitypkg/Shared/UnityCliQueuePublication.cs +30 -0
  343. package/unitypkg/Shared/UnityCliRequestBudget.cs +352 -0
  344. package/unitypkg/Shared/UnityCliRuntimeRequestPublication.cs +107 -0
  345. package/unitypkg/Tests/Editor/UnityCli.Editor.Tests.asmdef +2 -2
  346. package/unitypkg/Tests/Editor/UnityCliAdmissionCoordinatorIntegrationTests.cs +1329 -0
  347. package/unitypkg/Tests/Editor/UnityCliAdmissionPoolTests.cs +311 -114
  348. package/unitypkg/Tests/Editor/UnityCliAdmissionRepairStateTests.cs +94 -0
  349. package/unitypkg/Tests/Editor/UnityCliBridgeAdmissionIntegrationTests.cs +1229 -0
  350. package/unitypkg/Tests/Editor/UnityCliBridgeQueuePolicyTests.cs +412 -0
  351. package/unitypkg/Tests/Editor/UnityCliContinuationIntegrationTests.cs +292 -0
  352. package/unitypkg/Tests/Editor/UnityCliDiagnosticBoundaryTests.cs +120 -0
  353. package/unitypkg/Tests/Editor/UnityCliDurableStatusTests.cs +84 -0
  354. package/unitypkg/Tests/Editor/UnityCliExceptionProjectionTests.cs +72 -0
  355. package/unitypkg/Tests/Editor/UnityCliInstancePublicationRetryTests.cs +180 -97
  356. package/unitypkg/Tests/Editor/UnityCliMemoryReflectionIntegrationTests.cs +434 -0
  357. package/unitypkg/Tests/Editor/UnityCliRequestBudgetTests.cs +615 -0
  358. package/unitypkg/Tests/Editor/UnityCliRuntimeBridgeIntegrationTests.cs +520 -0
  359. package/unitypkg/Tests/Editor/UnityCliToolCatalogTests.cs +562 -0
  360. package/unitypkg/Tests/Runtime/UnityCli.Runtime.Tests.asmdef +23 -0
  361. package/unitypkg/Tests/Runtime/UnityCliRuntimeCatalogValidationTests.cs +29 -0
  362. package/unitypkg/package.json +1 -1
  363. package/contracts/static-capability-catalog.json +0 -48226
  364. package/dist/bridge/domains/diagnose.d.ts +0 -2
  365. package/dist/bridge/domains/diagnose.js +0 -11
  366. package/dist/bridge/domains/diagnose.js.map +0 -1
  367. package/dist/bridge/domains/extension.d.ts +0 -8
  368. package/dist/bridge/domains/extension.js +0 -20
  369. package/dist/bridge/domains/extension.js.map +0 -1
  370. package/dist/capabilities.d.ts +0 -5
  371. package/dist/capabilities.js +0 -84
  372. package/dist/capabilities.js.map +0 -1
  373. package/dist/static-capability-catalog.d.ts +0 -13
  374. package/dist/static-capability-catalog.js +0 -41
  375. package/dist/static-capability-catalog.js.map +0 -1
  376. package/dist/tools/diagnose/capabilities.d.ts +0 -13
  377. package/dist/tools/diagnose/capabilities.js +0 -34
  378. package/dist/tools/diagnose/capabilities.js.map +0 -1
  379. package/dist/tools/package/info.d.ts +0 -19
  380. package/dist/tools/package/info.js +0 -27
  381. package/dist/tools/package/info.js.map +0 -1
  382. package/dist/tools/package/list.d.ts +0 -27
  383. package/dist/tools/package/list.js +0 -21
  384. package/dist/tools/package/list.js.map +0 -1
  385. package/dist/tools/package/registry-get.d.ts +0 -10
  386. package/dist/tools/package/registry-get.js +0 -22
  387. package/dist/tools/package/registry-get.js.map +0 -1
  388. package/dist/tools/package/scoped-registry-list.d.ts +0 -14
  389. package/dist/tools/package/scoped-registry-list.js +0 -22
  390. package/dist/tools/package/scoped-registry-list.js.map +0 -1
  391. package/dist/tools/package/search.d.ts +0 -17
  392. package/dist/tools/package/search.js +0 -39
  393. package/dist/tools/package/search.js.map +0 -1
  394. package/dist/tools/package/status.d.ts +0 -21
  395. package/dist/tools/package/status.js +0 -37
  396. package/dist/tools/package/status.js.map +0 -1
  397. package/dist/tools/tools/describe.d.ts +0 -27
  398. package/dist/tools/tools/describe.js +0 -35
  399. package/dist/tools/tools/describe.js.map +0 -1
  400. package/dist/tools/tools/group-list.d.ts +0 -16
  401. package/dist/tools/tools/group-list.js +0 -38
  402. package/dist/tools/tools/group-list.js.map +0 -1
  403. package/dist/tools/tools/group.d.ts +0 -13
  404. package/dist/tools/tools/group.js +0 -34
  405. package/dist/tools/tools/group.js.map +0 -1
  406. package/dist/tools/tools/pagination.d.ts +0 -17
  407. package/dist/tools/tools/pagination.js +0 -28
  408. package/dist/tools/tools/pagination.js.map +0 -1
  409. package/dist/tools/tools/search.d.ts +0 -15
  410. package/dist/tools/tools/search.js +0 -42
  411. package/dist/tools/tools/search.js.map +0 -1
  412. package/unitypkg/Editor/Tools/diagnose/preflight.cs +0 -269
  413. package/unitypkg/Editor/Tools/editor/console.cs +0 -26
  414. package/unitypkg/Editor/Tools/extension/status.cs +0 -87
  415. package/unitypkg/Editor/Tools/go/inspect.cs +0 -49
  416. package/unitypkg/Editor/Tools/graphics/pipeline-quality.cs +0 -24
  417. package/unitypkg/Editor/Tools/graphics/probe-positions.cs +0 -58
  418. package/unitypkg/Editor/Tools/graphics/settings.cs +0 -104
  419. package/unitypkg/Editor/Tools/graphics/stats-counters.cs +0 -28
  420. package/unitypkg/Editor/Tools/graphics/stats-memory.cs +0 -21
  421. package/unitypkg/Editor/Tools/instances/list.cs +0 -71
  422. package/unitypkg/Editor/Tools/physics/raycast-all.cs +0 -67
  423. package/unitypkg/Editor/Tools/scene/build-settings.cs +0 -69
  424. package/unitypkg/Editor/Tools/scene/data.cs +0 -125
  425. package/unitypkg/Editor/Tools/tests/list.cs +0 -98
@@ -23,14 +23,80 @@ namespace UnityCli
23
23
  private const string Scope = "build";
24
24
  private static bool reloadRecoveryPending;
25
25
  internal static bool IsCommand(string command) { return command == Command; }
26
+
27
+ /// <summary>production固定真实prepare/effect;测试仅按单次Execute显式注入,不写全局hook。</summary>
28
+ internal sealed class BuildExecutionDependencies
29
+ {
30
+ internal Func<string, string, List<string>, Prepared> PrepareTarget;
31
+ internal Func<Prepared, BuildEffectResult> RunEffect;
32
+ internal readonly BuildTestBoundary Boundary;
33
+
34
+ internal BuildExecutionDependencies(bool testMode)
35
+ {
36
+ Boundary = new BuildTestBoundary(testMode);
37
+ }
38
+
39
+ internal static BuildExecutionDependencies Production()
40
+ {
41
+ var dependencies = new BuildExecutionDependencies(false)
42
+ {
43
+ PrepareTarget = (target, outputPath, options) => Prepare(new TargetArgs { Target = target, OutputPath = outputPath, Options = options }),
44
+ RunEffect = RunBuildEffect
45
+ };
46
+ return dependencies;
47
+ }
48
+
49
+ internal void Hit(string name)
50
+ {
51
+ Boundary.Hit(name);
52
+ }
53
+
54
+ internal sealed class BuildTestBoundary
55
+ {
56
+ private readonly object sync = new object();
57
+ private readonly Dictionary<string, Action> expected = new Dictionary<string, Action>(StringComparer.Ordinal);
58
+ private readonly bool enabled;
59
+
60
+ internal BuildTestBoundary(bool enabled) { this.enabled = enabled; }
61
+ internal void ExpectOnce(string name, Action action)
62
+ {
63
+ if (!enabled) throw new InvalidOperationException("Production build dependencies do not accept test boundaries.");
64
+ if (string.IsNullOrWhiteSpace(name) || action == null) throw new ArgumentException("Build test boundary is invalid.");
65
+ lock (sync)
66
+ {
67
+ if (expected.ContainsKey(name)) throw new InvalidOperationException("Build test boundary is already registered: " + name);
68
+ expected.Add(name, action);
69
+ }
70
+ }
71
+ internal void Hit(string name)
72
+ {
73
+ Action action;
74
+ lock (sync)
75
+ {
76
+ if (!expected.TryGetValue(name, out action)) return;
77
+ expected.Remove(name);
78
+ }
79
+ action();
80
+ }
81
+ internal void AssertDrained()
82
+ {
83
+ lock (sync)
84
+ {
85
+ if (expected.Count != 0) throw new InvalidOperationException("Build test boundaries were not consumed: " + string.Join(",", expected.Keys));
86
+ }
87
+ }
88
+ }
89
+ }
90
+
26
91
  internal static void Register(BridgeCommandRegistry registry)
27
92
  {
28
- registry.Register<BridgeRequest>(Command, "internal", true, 600000, (r, c) => { throw new InvalidOperationException("Build is coordinator-owned."); });
93
+ registry.Register<BridgeRequest>(Command, "internal", true, 600000, (r, c) => { throw new InvalidOperationException("Build is coordinator-owned."); }, UnityCliBridgeQueuePolicy.WorkClass.Mutation);
29
94
  }
30
95
 
31
96
  /// <summary>reload后先持久化outcome-unknown标记,再释放已终止旧domain的exact lease。</summary>
32
- internal static void Initialize() { RecoverTerminalCleanup(); reloadRecoveryPending = true; MarkReloadOutcomeUnknownAndRelease(); }
33
- internal static void Tick() { RecoverTerminalCleanup(); if (reloadRecoveryPending) MarkReloadOutcomeUnknownAndRelease(); }
97
+ internal static void Initialize() { RecoverTerminalCleanup(ProductionQueueState); reloadRecoveryPending = true; MarkReloadOutcomeUnknownAndRelease(); }
98
+ internal static void Tick() { Tick(ProductionQueueState); }
99
+ internal static void Tick(BridgeQueueState state) { RecoverTerminalCleanup(state); if (object.ReferenceEquals(state, ProductionQueueState) && reloadRecoveryPending) MarkReloadOutcomeUnknownAndRelease(); }
34
100
 
35
101
  private static void MarkReloadOutcomeUnknownAndRelease()
36
102
  {
@@ -65,6 +131,24 @@ namespace UnityCli
65
131
 
66
132
  internal static async Task<string> Execute(BridgeRequest request, byte[] rawRequestBytes, Func<bool> tryAccept, CancellationToken cancellationToken)
67
133
  {
134
+ try
135
+ {
136
+ return await Execute(ProductionQueueState, request, rawRequestBytes, tryAccept, cancellationToken, BuildExecutionDependencies.Production());
137
+ }
138
+ catch (Exception exception)
139
+ {
140
+ WriteLog("build_entry_failed requestId=" + request.RequestId + " exception=" + exception);
141
+ return SerializeBridgeExceptionEnvelope(request.RequestId, new BridgeCommandException(
142
+ "E_EXECUTION_FAILED",
143
+ "Build request preparation failed before a trusted coordinator terminal was available.",
144
+ "Inspect UnityCliBridge.log and Build Settings before retrying build.run."));
145
+ }
146
+ }
147
+
148
+ internal static async Task<string> Execute(BridgeQueueState queueState, BridgeRequest request, byte[] rawRequestBytes, Func<bool> tryAccept, CancellationToken cancellationToken, BuildExecutionDependencies dependencies)
149
+ {
150
+ if (queueState == null) throw new ArgumentNullException("queueState");
151
+ if (dependencies == null || dependencies.PrepareTarget == null || dependencies.RunEffect == null) throw new ArgumentNullException("dependencies");
68
152
  var args = request.DeserializeArgs<Args>();
69
153
  if (request.Execution != null && request.Execution.DryRun == true)
70
154
  {
@@ -73,13 +157,13 @@ namespace UnityCli
73
157
  }
74
158
  var digest = UnityCliRequestDigest.Compute(rawRequestBytes, request.InstanceId);
75
159
  object recoveredTerminal;
76
- if (UnityCliActiveOperation.TryGetTerminal(Scope, request.RequestId, digest, out recoveredTerminal))
160
+ if (queueState.TryGetTerminal(Scope, request.RequestId, digest, out recoveredTerminal))
77
161
  {
78
- try { UnityCliActiveOperation.BeginExactTerminalCleanup(Scope, request.RequestId, digest); } catch { }
162
+ try { UnityCliActiveOperation.BeginExactTerminalCleanup(Scope, request.RequestId, digest, queueState); } catch { }
79
163
  return Terminal(request.RequestId, recoveredTerminal);
80
164
  }
81
165
  Dictionary<string, object> active;
82
- try { active = UnityCliActiveOperation.Load(Scope); }
166
+ try { active = queueState.LoadOperation(Scope); }
83
167
  catch (Exception) { return SerializeBridgeExceptionEnvelope(request.RequestId, new BridgeCommandException("E_EXECUTION_TIMEOUT", "Build active state cannot be read; outcome is unknown.")); }
84
168
  if (active != null)
85
169
  {
@@ -90,55 +174,72 @@ namespace UnityCli
90
174
  if (!active.TryGetValue("reloadOutcomeUnknown", out reloadUnknown) || !(reloadUnknown is bool) || !(bool)reloadUnknown)
91
175
  return Busy(request.RequestId);
92
176
  var unknown = ProtocolEnvelopeFactory.Error(request.RequestId, "E_EXECUTION_TIMEOUT", "A previous build did not reach a terminal result before reload or timeout.", null, new[] { "Inspect build output and editor.state before retrying." });
93
- return FinishTerminal(request.RequestId, digest, unknown);
177
+ return FinishTerminal(queueState, request.RequestId, digest, unknown, dependencies);
94
178
  }
95
179
  bool newlyAcquiredLease;
96
- if (!UnityCliSessionMutationLease.TryAcquire(request.RequestId, digest, out newlyAcquiredLease) || !newlyAcquiredLease) return Busy(request.RequestId);
180
+ if (!queueState.TryAcquireLease(request.RequestId, digest, out newlyAcquiredLease) || !newlyAcquiredLease) return Busy(request.RequestId);
181
+ AdmissionOwnerIdentity queueAdmissionOwner;
182
+ try { queueAdmissionOwner = CaptureAdmissionOwner(queueState, request.RequestId, digest); }
183
+ catch (Exception exception)
184
+ {
185
+ return FinishBuildPreRootFault(queueState, request.RequestId, digest, new BridgeCommandException("E_EXECUTION_TIMEOUT", "Build queue admission identity could not be frozen: " + exception.GetType().Name + "."));
186
+ }
187
+ var activeRootCreated = false;
97
188
  try
98
189
  {
99
190
  if (tryAccept == null || !tryAccept()) throw new OperationCanceledException();
100
191
  Validate(args);
101
- var prepared = args.Targets.Select(Prepare).ToList();
192
+ var prepared = args.Targets.Select(item => dependencies.PrepareTarget(item.Target, item.OutputPath, item.Options)).ToList();
102
193
  active = NewActive(request, digest, args);
103
- UnityCliActiveOperation.Save(Scope, active);
194
+ queueState.SaveOperation(Scope, active);
195
+ activeRootCreated = true;
104
196
  cancellationToken.ThrowIfCancellationRequested();
105
197
  active["phase"] = "running";
106
- UnityCliActiveOperation.Save(Scope, active);
198
+ queueState.SaveOperation(Scope, active);
107
199
  var reports = new List<object>();
108
200
  foreach (var item in prepared)
109
201
  {
110
202
  bool ownedBefore;
111
- if (!UnityCliSessionMutationLease.TryIsOwnedBy(request.RequestId, digest, out ownedBefore) || !ownedBefore)
112
- return FinishTerminal(request.RequestId, digest, UnknownLeaseTerminal(request.RequestId));
113
- reports.Add(Build(item));
203
+ if (!queueState.TryIsLeaseOwned(request.RequestId, digest, out ownedBefore) || !ownedBefore)
204
+ return FinishTerminal(queueState, request.RequestId, digest, UnknownLeaseTerminal(request.RequestId), dependencies);
205
+ reports.Add(Build(item, dependencies));
114
206
  bool ownedAfter;
115
- if (!UnityCliSessionMutationLease.TryIsOwnedBy(request.RequestId, digest, out ownedAfter) || !ownedAfter)
116
- return FinishTerminal(request.RequestId, digest, UnknownLeaseTerminal(request.RequestId));
207
+ if (!queueState.TryIsLeaseOwned(request.RequestId, digest, out ownedAfter) || !ownedAfter)
208
+ return FinishTerminal(queueState, request.RequestId, digest, UnknownLeaseTerminal(request.RequestId), dependencies);
117
209
  }
118
210
  var data = new Dictionary<string, object> { { "targets", reports } };
119
211
  var failed = reports.Cast<Dictionary<string, object>>().Any(x => !string.Equals(x["result"] as string, "Succeeded", StringComparison.Ordinal));
120
212
  object envelope = failed ? (object)ProtocolEnvelopeFactory.Error(request.RequestId, "E_BUILD_FAILED", "One or more BuildPlayer reports were not successful.", data, new[] { "Inspect the returned BuildReport summaries and Unity Console." }) : ProtocolEnvelopeFactory.Success(request.RequestId, data);
121
- return FinishTerminal(request.RequestId, digest, envelope);
213
+ return FinishTerminal(queueState, request.RequestId, digest, envelope, dependencies);
122
214
  }
123
215
  catch (OperationCanceledException)
124
216
  {
125
- return FinishTerminal(request.RequestId, digest, ProtocolEnvelopeFactory.Error(request.RequestId, "E_EXECUTION_TIMEOUT", "Build did not complete before the request deadline.", null, new[] { "Inspect editor.state and retry." }));
217
+ var error = new BridgeCommandException("E_EXECUTION_TIMEOUT", "Build did not begin before the request deadline.");
218
+ if (!activeRootCreated && ActiveRootDefinitelyAbsent(queueState, request.RequestId, digest))
219
+ return FinishBuildPreRootFault(queueState, request.RequestId, digest, error);
220
+ return FinishBuildTerminalAfterRootFault(queueState, request.RequestId, digest, ProtocolEnvelopeFactory.Error(request.RequestId, "E_EXECUTION_TIMEOUT", "Build did not complete before the request deadline.", null, new[] { "Inspect editor.state and retry." }), queueAdmissionOwner, dependencies);
126
221
  }
127
222
  catch (BridgeCommandException ex)
128
223
  {
129
- return SerializeBridgeExceptionEnvelope(request.RequestId, ex);
224
+ if (!activeRootCreated && ActiveRootDefinitelyAbsent(queueState, request.RequestId, digest))
225
+ return FinishBuildPreRootFault(queueState, request.RequestId, digest, ex);
226
+ return FinishBuildTerminalAfterRootFault(queueState, request.RequestId, digest, BridgeExceptionTerminal(request.RequestId, ex), queueAdmissionOwner, dependencies);
130
227
  }
131
228
  catch (Exception ex)
132
229
  {
133
- return FinishTerminal(request.RequestId, digest, SerializeException(request.RequestId, ex));
230
+ WriteLog("build_unhandled_exception requestId=" + request.RequestId + " activeRootCreated=" + activeRootCreated + " exception=" + ex);
231
+ var error = new BridgeCommandException("E_EXECUTION_FAILED", "Build preparation failed before BuildPlayer could start.", "Inspect UnityCliBridge.log and Build Settings before retrying build.run.");
232
+ if (!activeRootCreated && ActiveRootDefinitelyAbsent(queueState, request.RequestId, digest))
233
+ return FinishBuildPreRootFault(queueState, request.RequestId, digest, error);
234
+ return FinishBuildTerminalAfterRootFault(queueState, request.RequestId, digest, SerializeException(request.RequestId, ex), queueAdmissionOwner, dependencies);
134
235
  }
135
236
  finally
136
237
  {
137
238
  try
138
239
  {
139
- UnityCliActiveOperation.RetryExactTerminalCleanup(Scope);
140
- if (!UnityCliActiveOperation.Exists(Scope) && !UnityCliActiveOperation.Exists(Scope + "-terminal-cleanup"))
141
- UnityCliSessionMutationLease.Release(request.RequestId, digest);
240
+ UnityCliActiveOperation.RetryExactTerminalCleanup(Scope, queueState);
241
+ if (!queueState.OperationExists(Scope) && !queueState.OperationExists(Scope + "-terminal-cleanup"))
242
+ queueState.ReleaseLease(request.RequestId, digest);
142
243
  }
143
244
  catch { }
144
245
  await Task.FromResult(0);
@@ -148,11 +249,83 @@ namespace UnityCli
148
249
  private static Dictionary<string, object> NewActive(BridgeRequest request, string digest, Args args) { return new Dictionary<string, object> { { "requestId", request.RequestId }, { "requestDigest", digest }, { "editorSessionId", EditorSessionId() }, { "phase", "accepted" }, { "startedAt", DateTimeOffset.UtcNow.ToString("O", CultureInfo.InvariantCulture) }, { "targets", args.Targets.Select(x => (object)new Dictionary<string, object> { { "target", x.Target }, { "outputPath", x.OutputPath } }).ToList() } }; }
149
250
  private static string Value(IDictionary<string, object> value, string key) { object item; return value != null && value.TryGetValue(key, out item) ? item as string : null; }
150
251
  private static string Busy(string id) { return UnityCliSerializer.Serialize(ProtocolEnvelopeFactory.Error(id, "E_UNITY_BUSY", "Another mutation owns the selected Unity instance.", null, new[] { "Wait for the current mutation to reach its terminal result, then retry." }, true, 250)); }
151
- private static string FinishTerminal(string id, string digest, object envelope)
252
+ private static bool ActiveRootDefinitelyAbsent(BridgeQueueState state, string id, string digest)
152
253
  {
254
+ try
255
+ {
256
+ var root = state.LoadOperation(Scope);
257
+ if (root != null) return false;
258
+ return !state.OperationExists(Scope);
259
+ }
260
+ catch { return false; }
261
+ }
262
+
263
+ private static string FinishBuildPreRootFault(BridgeQueueState state, string id, string digest, BridgeCommandException error)
264
+ {
265
+ try { state.ReleaseLease(id, digest); } catch { }
153
266
  bool isOwned;
154
- if (!UnityCliSessionMutationLease.TryIsOwnedBy(id, digest, out isOwned) || !isOwned) envelope = UnknownLeaseTerminal(id);
155
- var active = UnityCliActiveOperation.Load(Scope);
267
+ if (state.TryIsLeaseOwned(id, digest, out isOwned) && !isOwned)
268
+ return SerializeBridgeExceptionEnvelope(id, error);
269
+ try { UnityCliActiveOperation.BeginExactTerminalCleanup(Scope, id, digest, state); } catch { }
270
+ return SerializeBridgeExceptionEnvelope(id, new BridgeCommandException("E_EXECUTION_TIMEOUT", "Build was rejected before its durable root, but exact lease cleanup remains pending."));
271
+ }
272
+
273
+ private static string FinishBuildTerminalAfterRootFault(BridgeQueueState queueState, string id, string digest, object envelope, AdmissionOwnerIdentity queueAdmissionOwner, BuildExecutionDependencies dependencies)
274
+ {
275
+ try { return FinishTerminal(queueState, id, digest, envelope, dependencies); }
276
+ catch (Exception exception)
277
+ {
278
+ var unknown = ProtocolEnvelopeFactory.Error(id, "E_EXECUTION_TIMEOUT", "Build active state or terminal publication failed after effect; outcome is unknown.", null, new[] { "Inspect build artifacts and editor.state before submitting another mutation." });
279
+ try
280
+ {
281
+ var owner = queueAdmissionOwner ?? CaptureAdmissionOwner(queueState, id, digest);
282
+ owner.TopRequestDigest = digest;
283
+ PersistAdmissionOwnerTransfer(queueState, Scope + "-admission-owner", new Dictionary<string, object>
284
+ {
285
+ { "requestId", id }, { "requestDigest", digest }, { "editorSessionId", EditorSessionId() }
286
+ }, owner, UnityCliAdmissionPool.AdmissionOwnerState.Recovery);
287
+ }
288
+ catch (Exception markerException)
289
+ {
290
+ return SerializeBridgeExceptionEnvelope(id, new BridgeCommandException("E_EXECUTION_TIMEOUT", "Build recovery marker remains pending after a post-root failure: " + markerException.GetType().Name + "."));
291
+ }
292
+ try
293
+ {
294
+ try
295
+ {
296
+ var recoveryRoot = queueState.LoadOperation(Scope);
297
+ if (recoveryRoot != null && string.Equals(Value(recoveryRoot, "requestId"), id, StringComparison.Ordinal)
298
+ && string.Equals(Value(recoveryRoot, "requestDigest"), digest, StringComparison.Ordinal))
299
+ {
300
+ recoveryRoot["phase"] = "terminal_pending";
301
+ recoveryRoot["terminalEnvelope"] = unknown;
302
+ queueState.SaveOperation(Scope, recoveryRoot);
303
+ }
304
+ }
305
+ catch { }
306
+ dependencies.Hit("before-build-terminal-store");
307
+ var first = queueState.StoreTerminalFirst(Scope, id, digest, unknown);
308
+ try { UnityCliActiveOperation.BeginExactTerminalCleanup(Scope, id, digest, queueState); } catch { }
309
+ return Terminal(id, first);
310
+ }
311
+ catch
312
+ {
313
+ return SerializeBridgeExceptionEnvelope(id, new BridgeCommandException("E_EXECUTION_TIMEOUT", "Build conservative terminal remains pending after a post-root failure: " + exception.GetType().Name + "."));
314
+ }
315
+ }
316
+ }
317
+
318
+ private static object BridgeExceptionTerminal(string id, BridgeCommandException exception)
319
+ {
320
+ return UnityCliSerializer.Parse(SerializeBridgeExceptionEnvelope(id, exception));
321
+ }
322
+
323
+ private static string FinishTerminal(BridgeQueueState queueState, string id, string digest, object envelope, BuildExecutionDependencies dependencies)
324
+ {
325
+ bool isOwned;
326
+ if (!queueState.TryIsLeaseOwned(id, digest, out isOwned) || !isOwned) envelope = UnknownLeaseTerminal(id);
327
+ dependencies.Hit("before-build-terminal-root-read");
328
+ var active = queueState.LoadOperation(Scope);
156
329
  if (active != null)
157
330
  {
158
331
  if (!string.Equals(Value(active, "requestId"), id, StringComparison.Ordinal)
@@ -160,24 +333,25 @@ namespace UnityCli
160
333
  throw new BridgeCommandException("E_EXECUTION_TIMEOUT", "Build terminal active identity cannot be proven.");
161
334
  active["phase"] = "terminal_pending";
162
335
  active["terminalEnvelope"] = envelope;
163
- UnityCliActiveOperation.Save(Scope, active);
336
+ queueState.SaveOperation(Scope, active);
164
337
  }
165
- var first = UnityCliActiveOperation.StoreTerminalFirst(Scope, id, digest, envelope);
166
- UnityCliActiveOperation.BeginExactTerminalCleanup(Scope, id, digest);
338
+ dependencies.Hit("before-build-terminal-store");
339
+ var first = queueState.StoreTerminalFirst(Scope, id, digest, envelope);
340
+ UnityCliActiveOperation.BeginExactTerminalCleanup(Scope, id, digest, queueState);
167
341
  return Terminal(id, first);
168
342
  }
169
- private static void RecoverTerminalCleanup()
343
+ private static void RecoverTerminalCleanup(BridgeQueueState state)
170
344
  {
171
345
  try
172
346
  {
173
- if (UnityCliActiveOperation.RetryExactTerminalCleanup(Scope)) return;
174
- var active = UnityCliActiveOperation.Load(Scope);
175
- if (active == null) return;
347
+ if (UnityCliActiveOperation.RetryExactTerminalCleanup(Scope, state)) return;
348
+ var active = state.LoadOperation(Scope);
349
+ if (active == null) { RecoverFromBuildAdmissionMarker(state); return; }
176
350
  var requestId = Value(active, "requestId");
177
351
  var requestDigest = Value(active, "requestDigest");
178
352
  object terminal = null;
179
353
  var hasTerminal = IsSafeId(requestId) && IsSha256(requestDigest)
180
- && UnityCliActiveOperation.TryGetTerminal(Scope, requestId, requestDigest, out terminal);
354
+ && state.TryGetTerminal(Scope, requestId, requestDigest, out terminal);
181
355
  if (!hasTerminal)
182
356
  {
183
357
  object pending;
@@ -187,43 +361,112 @@ namespace UnityCli
187
361
  pending = UnknownLeaseTerminal(requestId);
188
362
  active["phase"] = "terminal_pending";
189
363
  active["terminalEnvelope"] = pending;
190
- UnityCliActiveOperation.Save(Scope, active);
364
+ state.SaveOperation(Scope, active);
191
365
  }
192
- terminal = UnityCliActiveOperation.StoreTerminalFirst(Scope, requestId, requestDigest, pending);
366
+ terminal = state.StoreTerminalFirst(Scope, requestId, requestDigest, pending);
367
+ }
368
+ if (terminal != null) UnityCliActiveOperation.BeginExactTerminalCleanup(Scope, requestId, requestDigest, state);
369
+ }
370
+ catch { RecoverFromBuildAdmissionMarker(state); }
371
+ }
372
+
373
+ /// <summary>root缺失时按完整Recovery marker与exact lease决定保守终态或无root清理。</summary>
374
+ private static void RecoverFromBuildAdmissionMarker(BridgeQueueState state)
375
+ {
376
+ try
377
+ {
378
+ var markerScope = Scope + "-admission-owner";
379
+ var marker = state.LoadOperation(markerScope);
380
+ if (marker == null || (UnityCliAdmissionPool.AdmissionOwnerState)AdmissionLong(marker, "admissionIntendedOwnerState") != UnityCliAdmissionPool.AdmissionOwnerState.Recovery) return;
381
+ var requestId = Value(marker, "requestId");
382
+ var topRequestDigest = Value(marker, "requestDigest");
383
+ var ownerRequestDigest = Value(marker, "admissionRequestDigest");
384
+ if (!IsSafeId(requestId) || !IsSha256(topRequestDigest) || !IsSha256(ownerRequestDigest)) return;
385
+ var owner = ReconcileAdmissionTransferForLiveMarker(state, markerScope, marker, requestId, ownerRequestDigest);
386
+ if (owner == null || !string.Equals(owner.TopRequestDigest, topRequestDigest, StringComparison.Ordinal)) return;
387
+ bool leaseOwned;
388
+ if (!state.TryIsLeaseOwned(owner.RequestId, owner.RequestDigest, out leaseOwned)) return;
389
+ object terminal;
390
+ if (!state.TryGetTerminal(Scope, requestId, topRequestDigest, out terminal))
391
+ {
392
+ var recoveryTerminal = leaseOwned
393
+ ? ProtocolEnvelopeFactory.Error(requestId, "E_EXECUTION_TIMEOUT", "Build active root disappeared while its exact lease was retained; outcome is unknown.", null, new[] { "Inspect build artifacts before submitting another mutation." })
394
+ : UnknownLeaseTerminal(requestId);
395
+ terminal = state.StoreTerminalFirst(Scope, requestId, topRequestDigest, recoveryTerminal);
193
396
  }
194
- if (terminal != null) UnityCliActiveOperation.BeginExactTerminalCleanup(Scope, requestId, requestDigest);
397
+ object confirmed;
398
+ if (terminal == null || !state.TryGetTerminal(Scope, requestId, topRequestDigest, out confirmed)
399
+ || !string.Equals(UnityCliSerializer.Serialize(terminal), UnityCliSerializer.Serialize(confirmed), StringComparison.Ordinal)) return;
400
+ UnityCliActiveOperation.BeginExactTerminalCleanup(Scope, requestId, topRequestDigest, state);
195
401
  }
196
402
  catch { }
197
403
  }
404
+
198
405
  private static object UnknownLeaseTerminal(string id) { return ProtocolEnvelopeFactory.Error(id, "E_EXECUTION_TIMEOUT", "Build lost its exact mutation lease after BuildPlayer; outcome is unknown.", null, new[] { "Inspect build artifacts before submitting another mutation." }); }
199
406
  private static string Terminal(string id, object envelope) { return UnityCliSerializer.Serialize(ProtocolEnvelopeFactory.Success(id, new Dictionary<string, object> { { "terminal", true }, { "rootEnvelope", envelope } })); }
200
407
  private static object SerializeException(string id, Exception ex) { return ProtocolEnvelopeFactory.Error(id, "E_BRIDGE_COMMAND_FAILED", ex.Message, null, new[] { "Check Unity Console and UnityCliBridge.log." }); }
408
+ internal sealed class BuildEffectResult
409
+ {
410
+ internal string Result;
411
+ internal long TotalSize;
412
+ internal long TotalErrors;
413
+ internal long TotalWarnings;
414
+ internal Dictionary<string, object> Artifact;
415
+ internal List<object> Messages;
416
+ }
417
+
201
418
  private static Prepared Prepare(TargetArgs input)
202
419
  {
203
420
  if (input == null || string.IsNullOrWhiteSpace(input.Target) || string.IsNullOrWhiteSpace(input.OutputPath)) throw new BridgeCommandException("E_SCHEMA_INVALID", "Each build target requires target and outputPath.");
204
421
  BuildTarget target; if (!Enum.TryParse(input.Target, false, out target)) throw new BridgeCommandException("E_SCHEMA_INVALID", "Unsupported Unity BuildTarget: " + input.Target);
205
- var scenes = EditorBuildSettings.scenes.Where(x => x.enabled && File.Exists(x.path)).Select(x => x.path).ToArray(); if (scenes.Length == 0) throw new BridgeCommandException("E_BUILD_SCENES_EMPTY", "No enabled existing build scenes are available.");
422
+ var configuredScenes = EditorBuildSettings.scenes ?? Array.Empty<EditorBuildSettingsScene>();
423
+ var scenes = configuredScenes.Where(x => x != null && x.enabled && File.Exists(x.path)).Select(x => x.path).ToArray();
424
+ if (scenes.Length == 0) throw new BridgeCommandException("E_BUILD_SCENES_EMPTY", "No enabled existing build scenes are available.");
206
425
  var publicPath = input.OutputPath.Replace('\\', '/'); if (Path.IsPathRooted(input.OutputPath) || publicPath.Split('/').Any(x => x == "..")) throw new BridgeCommandException("E_SCHEMA_INVALID", "Build outputPath must be project-relative.");
207
426
  var projectRoot = Path.GetFullPath(ProjectRoot()); var outputPath = Path.GetFullPath(Path.Combine(projectRoot, input.OutputPath)); if (!outputPath.StartsWith(projectRoot + Path.DirectorySeparatorChar, StringComparison.Ordinal) && !string.Equals(outputPath, projectRoot, StringComparison.Ordinal)) throw new BridgeCommandException("E_SCHEMA_INVALID", "Build outputPath escapes the project.");
208
427
  return new Prepared { Target = target, TargetName = input.Target, OutputPath = outputPath, PublicOutputPath = publicPath, Scenes = scenes, Options = ParseOptions(input.Options) };
209
428
  }
210
429
  private static BuildOptions ParseOptions(List<string> values) { var result = BuildOptions.None; foreach (var value in values ?? new List<string>()) { BuildOptions option; if (string.IsNullOrWhiteSpace(value) || !Enum.TryParse(value, false, out option)) throw new BridgeCommandException("E_SCHEMA_INVALID", "Unsupported BuildOptions value: " + value); result |= option; } return result; }
211
- private static Dictionary<string, object> Build(Prepared p)
430
+ private static Dictionary<string, object> Build(Prepared p, BuildExecutionDependencies dependencies)
431
+ {
432
+ var started = DateTimeOffset.UtcNow;
433
+ var parent = Path.GetDirectoryName(p.OutputPath);
434
+ if (!string.IsNullOrEmpty(parent)) Directory.CreateDirectory(parent);
435
+ var effect = dependencies.RunEffect(p);
436
+ return BuildResultData(p, effect, started, DateTimeOffset.UtcNow);
437
+ }
438
+
439
+ private static Dictionary<string, object> BuildResultData(Prepared prepared, BuildEffectResult effect, DateTimeOffset started, DateTimeOffset finished)
440
+ {
441
+ if (effect == null) throw new InvalidOperationException("Build effect returned no result.");
442
+ if (effect.Result == BuildResult.Succeeded.ToString() && !SuccessfulArtifactProof(effect.Artifact))
443
+ throw new BridgeCommandException("E_BUILD_ARTIFACT_MISSING", "BuildReport succeeded but its fresh output artifact has no non-empty SHA-256 proof.");
444
+ return new Dictionary<string, object>
445
+ {
446
+ { "target", prepared.TargetName }, { "result", effect.Result }, { "outputPath", prepared.PublicOutputPath },
447
+ { "totalSize", effect.TotalSize }, { "totalErrors", effect.TotalErrors }, { "totalWarnings", effect.TotalWarnings },
448
+ { "startedAt", started.ToString("O", CultureInfo.InvariantCulture) }, { "finishedAt", finished.ToString("O", CultureInfo.InvariantCulture) },
449
+ { "durationMs", (long)(finished - started).TotalMilliseconds }, { "messages", effect.Messages ?? new List<object>() }, { "artifact", effect.Artifact }
450
+ };
451
+ }
452
+
453
+ private static BuildEffectResult RunBuildEffect(Prepared p)
212
454
  {
213
- var started = DateTimeOffset.UtcNow; var parent = Path.GetDirectoryName(p.OutputPath); if (!string.IsNullOrEmpty(parent)) Directory.CreateDirectory(parent);
214
- var report = BuildPipeline.BuildPlayer(p.Scenes, p.OutputPath, p.Target, p.Options); var finished = DateTimeOffset.UtcNow; var artifact = ReadArtifact(p.OutputPath, p.PublicOutputPath);
215
- if (report.summary.result == BuildResult.Succeeded && !SuccessfulArtifactProof(artifact)) throw new BridgeCommandException("E_BUILD_ARTIFACT_MISSING", "BuildReport succeeded but its fresh output artifact has no non-empty SHA-256 proof.");
216
- var messages = report.steps.SelectMany(step => step.messages ?? new BuildStepMessage[0]).Where(message => !string.IsNullOrWhiteSpace(message.content)).Select(message => (object)new Dictionary<string, object> { { "type", message.type.ToString() }, { "message", message.content } }).Take(100).ToList();
217
- return new Dictionary<string, object> { { "target", p.TargetName }, { "result", report.summary.result.ToString() }, { "outputPath", p.PublicOutputPath }, { "totalSize", report.summary.totalSize }, { "totalErrors", (long)report.summary.totalErrors }, { "totalWarnings", (long)report.summary.totalWarnings }, { "startedAt", started.ToString("O", CultureInfo.InvariantCulture) }, { "finishedAt", finished.ToString("O", CultureInfo.InvariantCulture) }, { "durationMs", (long)(finished - started).TotalMilliseconds }, { "messages", messages }, { "artifact", artifact } };
455
+ var report = BuildPipeline.BuildPlayer(p.Scenes, p.OutputPath, p.Target, p.Options);
456
+ if (report == null) throw new BridgeCommandException("E_BUILD_FAILED", "BuildPlayer returned no BuildReport.");
457
+ var artifact = ReadArtifact(p.OutputPath, p.PublicOutputPath);
458
+ var steps = report.steps ?? Array.Empty<BuildStep>();
459
+ var messages = steps.SelectMany(step => step.messages ?? Array.Empty<BuildStepMessage>()).Where(message => !string.IsNullOrWhiteSpace(message.content)).Select(message => (object)new Dictionary<string, object> { { "type", message.type.ToString() }, { "message", message.content } }).Take(100).ToList();
460
+ return new BuildEffectResult { Result = report.summary.result.ToString(), TotalSize = checked((long)report.summary.totalSize), TotalErrors = report.summary.totalErrors, TotalWarnings = report.summary.totalWarnings, Artifact = artifact, Messages = messages };
218
461
  }
219
462
  private static Dictionary<string, object> ReadArtifact(string path, string publicPath) { var roots = new List<string>(); if (File.Exists(path)) roots.Add(path); if (Directory.Exists(path)) roots.Add(path); var sidecar = Path.Combine(Path.GetDirectoryName(path) ?? string.Empty, Path.GetFileNameWithoutExtension(path) + "_Data"); if (Directory.Exists(sidecar)) roots.Add(sidecar); var files = roots.SelectMany(root => File.Exists(root) ? new[] { root } : Directory.GetFiles(root, "*", SearchOption.AllDirectories)).Distinct(StringComparer.Ordinal).OrderBy(x => x, StringComparer.Ordinal).ToArray(); var size = files.Sum(x => new FileInfo(x).Length); var treeRoot = Path.GetDirectoryName(path) ?? path; return new Dictionary<string, object> { { "outputPath", publicPath }, { "exists", files.Length > 0 }, { "hash", File.Exists(path) ? HashFile(path) : null }, { "treeHash", files.Length > 0 ? HashTree(files, treeRoot) : null }, { "fileCount", (long)files.Length }, { "size", size } }; }
220
- private static bool SuccessfulArtifactProof(Dictionary<string, object> artifact) { object exists; object count; object size; object hash; object treeHash; artifact.TryGetValue("exists", out exists); artifact.TryGetValue("fileCount", out count); artifact.TryGetValue("size", out size); artifact.TryGetValue("hash", out hash); artifact.TryGetValue("treeHash", out treeHash); return exists is bool && (bool)exists && count is long && (long)count > 0 && size is long && (long)size > 0 && (IsSha256(hash as string) || IsSha256(treeHash as string)); }
463
+ private static bool SuccessfulArtifactProof(Dictionary<string, object> artifact) { object exists; object count; object size; object hash; object treeHash; return artifact != null && artifact.TryGetValue("exists", out exists) && artifact.TryGetValue("fileCount", out count) && artifact.TryGetValue("size", out size) && artifact.TryGetValue("hash", out hash) && artifact.TryGetValue("treeHash", out treeHash) && exists is bool && (bool)exists && count is long && (long)count > 0 && size is long && (long)size > 0 && (IsSha256(hash as string) || IsSha256(treeHash as string)); }
221
464
  private static string HashFile(string path) { using (var sha = SHA256.Create()) using (var stream = File.OpenRead(path)) return BitConverter.ToString(sha.ComputeHash(stream)).Replace("-", "").ToLowerInvariant(); }
222
465
  private static string HashTree(IEnumerable<string> files, string root) { using (var sha = SHA256.Create()) return BitConverter.ToString(sha.ComputeHash(Encoding.UTF8.GetBytes(string.Join("\n", files.Select(x => Path.GetRelativePath(root, x).Replace('\\', '/') + "\0" + HashFile(x)))))).Replace("-", "").ToLowerInvariant(); }
223
466
  private static void Validate(Args args) { if (args == null || string.IsNullOrWhiteSpace(args.ExpectedEditorSessionId) || args.Targets == null || args.Targets.Count == 0) throw new BridgeCommandException("E_SCHEMA_INVALID", "Build targets and expectedEditorSessionId are required."); if (args.Targets.Any(x => x == null || string.IsNullOrWhiteSpace(x.Target) || string.IsNullOrWhiteSpace(x.OutputPath))) throw new BridgeCommandException("E_SCHEMA_INVALID", "Each build target requires target and outputPath."); if (args.Targets.Select(x => x.Target).Distinct(StringComparer.Ordinal).Count() != args.Targets.Count) throw new BridgeCommandException("E_SCHEMA_INVALID", "Build targets must not contain duplicates."); if (args.Targets.Select(x => x.OutputPath.Replace('\\', '/')).Distinct(StringComparer.OrdinalIgnoreCase).Count() != args.Targets.Count) throw new BridgeCommandException("E_SCHEMA_INVALID", "Build output paths must not conflict."); if (!string.Equals(args.ExpectedEditorSessionId, EditorSessionId(), StringComparison.Ordinal)) throw new BridgeCommandException("E_EDITOR_SESSION_REPLACED", "The selected Editor session changed before build execution."); if (EditorApplication.isPlaying || IsPlayModeChanging()) throw new BridgeCommandException("E_TOOL_PLAYMODE_UNAVAILABLE", "build.run is editor-only and requires stable Edit Mode.", new Dictionary<string, object> { { "toolName", "build.run" }, { "playModeSupport", "editor-only" } }, new[] { "Exit Play Mode before running build.run." }); if (EditorUtility.scriptCompilationFailed) throw new BridgeCommandException("E_UNITY_COMPILE_FAILED", "Unity has unresolved script compilation errors and cannot start BuildPlayer.", "Run editor.compile and fix all diagnostics before retrying build.run."); }
224
467
  private sealed class Args { public List<TargetArgs> Targets { get; set; } public string ExpectedEditorSessionId { get; set; } }
225
468
  private sealed class TargetArgs { public string Target { get; set; } public string OutputPath { get; set; } public List<string> Options { get; set; } }
226
- private sealed class Prepared { public BuildTarget Target; public string TargetName; public string OutputPath; public string PublicOutputPath; public string[] Scenes; public BuildOptions Options; }
469
+ internal sealed class Prepared { internal BuildTarget Target; internal string TargetName; internal string OutputPath; internal string PublicOutputPath; internal string[] Scenes; internal BuildOptions Options; }
227
470
  }
228
471
  }
229
472
  }