@atelierai/uco 1.0.6 → 1.0.8

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 (280) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/README.md +7 -8
  3. package/bin/server.mjs +1 -1
  4. package/bin/stub-plugin.mjs +3 -3
  5. package/dist/codegen/coerce.js +1 -1
  6. package/dist/codegen/coerce.js.map +1 -1
  7. package/dist/codegen/emit.js +2 -2
  8. package/dist/codegen/emit.js.map +1 -1
  9. package/dist/codegen/refs.js +1 -1
  10. package/dist/codegen/refs.js.map +1 -1
  11. package/dist/codegen/types.js +1 -1
  12. package/dist/codegen/types.js.map +1 -1
  13. package/dist/commands/call.js +1 -1
  14. package/dist/commands/call.js.map +1 -1
  15. package/dist/commands/devops/configure.js +1 -1
  16. package/dist/commands/devops/configure.js.map +1 -1
  17. package/dist/commands/devops/index.js +0 -4
  18. package/dist/commands/devops/index.js.map +1 -1
  19. package/dist/commands/devops/install-plugin.js +1 -1
  20. package/dist/commands/devops/install-plugin.js.map +1 -1
  21. package/dist/commands/devops/open.js +7 -7
  22. package/dist/commands/devops/open.js.map +1 -1
  23. package/dist/commands/devops/status.js +3 -3
  24. package/dist/commands/devops/status.js.map +1 -1
  25. package/dist/commands/exec.js +1 -1
  26. package/dist/commands/exec.js.map +1 -1
  27. package/dist/commands/install.js +2 -2
  28. package/dist/commands/install.js.map +1 -1
  29. package/dist/commands/prompt.js +2 -2
  30. package/dist/commands/prompt.js.map +1 -1
  31. package/dist/commands/resource.js +2 -2
  32. package/dist/commands/resource.js.map +1 -1
  33. package/dist/commands/update.js +1 -26
  34. package/dist/commands/update.js.map +1 -1
  35. package/dist/config/port.js +2 -2
  36. package/dist/config/port.js.map +1 -1
  37. package/dist/config/resolve.d.ts +1 -2
  38. package/dist/config/resolve.js +0 -12
  39. package/dist/config/resolve.js.map +1 -1
  40. package/dist/devops/lib/configure.d.ts +1 -1
  41. package/dist/devops/lib/configure.js +2 -2
  42. package/dist/devops/lib/configure.js.map +1 -1
  43. package/dist/devops/lib/install-plugin.d.ts +1 -1
  44. package/dist/devops/lib/install-plugin.js +1 -1
  45. package/dist/devops/lib/install-plugin.js.map +1 -1
  46. package/dist/devops/lib/install.d.ts +2 -2
  47. package/dist/devops/lib/install.js +5 -5
  48. package/dist/devops/lib/install.js.map +1 -1
  49. package/dist/devops/lib/open.js +11 -32
  50. package/dist/devops/lib/open.js.map +1 -1
  51. package/dist/devops/lib/remove-plugin.d.ts +1 -1
  52. package/dist/devops/lib/remove-plugin.js +3 -3
  53. package/dist/devops/lib/remove-plugin.js.map +1 -1
  54. package/dist/devops/lib/run-tool.d.ts +2 -2
  55. package/dist/devops/lib/run-tool.js +2 -2
  56. package/dist/devops/lib/types.d.ts +17 -54
  57. package/dist/devops/lib/types.js +2 -2
  58. package/dist/devops/lib/types.js.map +1 -1
  59. package/dist/devops/lib/update.d.ts +0 -15
  60. package/dist/devops/lib/update.js +2 -91
  61. package/dist/devops/lib/update.js.map +1 -1
  62. package/dist/devops/utils/agents.d.ts +0 -28
  63. package/dist/devops/utils/agents.js +0 -462
  64. package/dist/devops/utils/agents.js.map +1 -1
  65. package/dist/devops/utils/config.d.ts +9 -18
  66. package/dist/devops/utils/config.js +7 -20
  67. package/dist/devops/utils/config.js.map +1 -1
  68. package/dist/devops/utils/editor-cache.js +1 -1
  69. package/dist/devops/utils/editor-cache.js.map +1 -1
  70. package/dist/devops/utils/launch-error-dismiss.d.ts +2 -2
  71. package/dist/devops/utils/launch-error-dismiss.js +5 -5
  72. package/dist/devops/utils/manifest.d.ts +2 -2
  73. package/dist/devops/utils/manifest.js +3 -3
  74. package/dist/devops/utils/manifest.js.map +1 -1
  75. package/dist/devops/utils/port.d.ts +1 -1
  76. package/dist/devops/utils/port.js +1 -1
  77. package/dist/devops/utils/probe.d.ts +1 -1
  78. package/dist/devops/utils/probe.js +1 -1
  79. package/dist/devops/utils/ui.js +2 -2
  80. package/dist/devops/utils/ui.js.map +1 -1
  81. package/dist/devops/utils/unity-editor.js +4 -4
  82. package/dist/devops/utils/unity-editor.js.map +1 -1
  83. package/dist/devops/utils/unity-hub.js +1 -1
  84. package/dist/devops/utils/unity-hub.js.map +1 -1
  85. package/dist/devops/utils/unity-shutdown.d.ts +1 -1
  86. package/dist/devops/utils/unity-shutdown.js +1 -1
  87. package/dist/devops/utils/update-check.js +3 -3
  88. package/dist/devops/utils/update-check.js.map +1 -1
  89. package/dist/devops/utils/vendor.d.ts +2 -2
  90. package/dist/devops/utils/vendor.js +4 -4
  91. package/dist/devops/utils/vendor.js.map +1 -1
  92. package/dist/generated/tools.js +1 -1
  93. package/dist/generated/tools.js.map +1 -1
  94. package/dist/program.js +3 -3
  95. package/dist/program.js.map +1 -1
  96. package/dist/server/app.d.ts +5 -5
  97. package/dist/server/app.js +3 -3
  98. package/dist/server/app.js.map +1 -1
  99. package/dist/server/index.d.ts +2 -2
  100. package/dist/server/index.js +2 -2
  101. package/dist/server/index.js.map +1 -1
  102. package/dist/server/rest/auth.d.ts +1 -1
  103. package/dist/server/rest/auth.js +3 -3
  104. package/dist/server/rest/auth.js.map +1 -1
  105. package/dist/server/rest/context.d.ts +2 -2
  106. package/dist/server/rest/forward.d.ts +2 -2
  107. package/dist/server/session/store.d.ts +3 -3
  108. package/dist/server/session/store.js +4 -4
  109. package/dist/server/session/store.js.map +1 -1
  110. package/dist/server/stub-client/scenarios.js +8 -8
  111. package/dist/server/stub-client/scenarios.js.map +1 -1
  112. package/dist/server/types.d.ts +11 -11
  113. package/dist/server/types.js +9 -9
  114. package/dist/server/types.js.map +1 -1
  115. package/dist/server/ws/hub.d.ts +8 -8
  116. package/dist/server/ws/hub.js +13 -13
  117. package/dist/server/ws/hub.js.map +1 -1
  118. package/dist/server/ws/rpc.d.ts +3 -3
  119. package/dist/server/ws/rpc.js +4 -4
  120. package/dist/server/ws/rpc.js.map +1 -1
  121. package/dist/transport/index.d.ts +1 -1
  122. package/dist/transport/index.js +3 -3
  123. package/dist/transport/index.js.map +1 -1
  124. package/dist/transport/rest.js +1 -1
  125. package/dist/transport/rest.js.map +1 -1
  126. package/docs/INSTALL.zh-CN.md +2 -3
  127. package/package.json +1 -1
  128. package/skills/uco-setup/SKILL.md +1 -1
  129. package/skills/unity-editor/references/diagnostics.md +24 -24
  130. package/vendor/plugin/com.atelierai.unity.copilot/Editor/DependencyResolver/NuGetConfig.cs +132 -132
  131. package/vendor/plugin/com.atelierai.unity.copilot/Editor/DependencyResolver/NuGetDependencyResolver.cs +237 -237
  132. package/vendor/plugin/com.atelierai.unity.copilot/Editor/DependencyResolver/NuGetDownloader.cs +82 -82
  133. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Resource/MenuItems.cs +247 -247
  134. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Resource/ToolGroups.cs +153 -153
  135. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/SystemTool/Skills.Create.SkillBody.cs +155 -155
  136. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Console.GetLogs.cs +95 -95
  137. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Docs.HttpFetcher.cs +139 -139
  138. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Instance.ListAll.cs +72 -72
  139. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/PathReadHelper.cs +106 -106
  140. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Script.Validate.cs +252 -252
  141. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Tests/TestResultCollector.cs +537 -537
  142. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Tests.cs +400 -400
  143. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Branding/ProductInfo.cs +62 -62
  144. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/CopilotServerManager.cs +1034 -1034
  145. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Skills/Skill_InitialSetup.cs +197 -197
  146. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Startup.Editor.cs +201 -201
  147. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Startup.cs +67 -67
  148. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Components/ExtensionPanel.cs +379 -379
  149. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/MenuItems.cs +72 -72
  150. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/UnityCopilotProjectSettingsProvider.cs +64 -64
  151. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/CopilotListWindowBase.cs +393 -393
  152. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/CopilotWindowBase.cs +113 -113
  153. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.Connection.cs +3 -3
  154. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.CopilotFeatures.cs +195 -195
  155. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.CopilotServer.cs +372 -372
  156. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.CreateGUI.cs +354 -354
  157. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.cs +78 -78
  158. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowInitializer.cs +47 -47
  159. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/SerializationCheckWindow.cs +190 -190
  160. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.Build.cs +91 -91
  161. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.Config.cs +298 -298
  162. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.Static.cs +296 -296
  163. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.cs +69 -69
  164. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/PackageUtils.cs +168 -168
  165. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/PrefsKeyMigration.cs +119 -119
  166. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/SafeDefaultsGuard.cs +180 -180
  167. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/ScriptUtils.cs +229 -229
  168. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/ToolsManifestGenerator.cs +200 -200
  169. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/UnityAuthoringAdapters.cs +1467 -1467
  170. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/UnityAuthoringSafetyReloadGuard.cs +65 -65
  171. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/UnityCopilotUpdateProjectSettings.cs +66 -66
  172. package/vendor/plugin/com.atelierai.unity.copilot/Plugins/ReflectorNet.dll +0 -0
  173. package/vendor/plugin/com.atelierai.unity.copilot/Plugins/Uco.Framework.Common.dll +0 -0
  174. package/vendor/plugin/com.atelierai.unity.copilot/Plugins/Uco.Framework.dll +0 -0
  175. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Converter/Json/Structures/EntityIdConverter.cs +120 -120
  176. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Data/GameObjectMetadata.cs +110 -110
  177. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Data/GameObjectMetadata.pre-Unity.6.5.cs +110 -110
  178. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Unity/Logs/FileLogStorage.cs +547 -547
  179. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.Build.cs +243 -243
  180. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.Config.cs +175 -175
  181. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.cs +295 -295
  182. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPluginBuilder.cs +105 -105
  183. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPluginRuntime.Static.cs +155 -155
  184. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPluginRuntime.cs +59 -59
  185. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Utils/EnvironmentUtils.cs +368 -368
  186. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/AtomicApi/AtomicApiTestFixtures.cs +99 -99
  187. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/AtomicApi/TryModifyAtTests.cs +227 -227
  188. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/AuthoringSafetyPilotTests.cs +1050 -1050
  189. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/DependencyResolver/NuGetLegacyMigrationTests.cs +304 -304
  190. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Environment/EnvironmentUtilsTests.cs +4 -4
  191. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/BlacklistTypeTests.cs +416 -416
  192. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataFieldScriptableObjectPopulationTests.cs +172 -172
  193. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataFieldScriptableObjectPopulationTests.pre-Unity.6.5.cs +169 -169
  194. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPopulationTests.cs +182 -182
  195. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPopulationTests.pre-Unity.6.5.cs +179 -179
  196. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPropertyPopulationTests.cs +175 -175
  197. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPropertyPopulationTests.pre-Unity.6.5.cs +174 -174
  198. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPropertyScriptableObjectPopulationTests.cs +171 -171
  199. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPropertyScriptableObjectPopulationTests.pre-Unity.6.5.cs +170 -170
  200. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsCreateFolderTests.cs +243 -243
  201. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsMaterialTests.cs +101 -101
  202. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsPrefabCreateTests.cs +348 -348
  203. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsPrefabCreateTests.pre-Unity.6.5.cs +348 -348
  204. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsShaderTests.cs +194 -194
  205. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/PathBasedToolTests.cs +573 -573
  206. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/PathBasedToolTests.pre-Unity.6.5.cs +513 -513
  207. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Screenshot/ScreenshotCameraTests.cs +154 -154
  208. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Screenshot/ScreenshotCameraTests.pre-Unity.6.5.cs +154 -154
  209. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Screenshot/ScreenshotGameViewTests.cs +164 -164
  210. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Screenshot/ScreenshotSceneViewTests.cs +155 -155
  211. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/ToolParameterTests.cs +56 -56
  212. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/ToolThreadSafetyTests.cs +651 -651
  213. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/ToolThreadSafetyTests.pre-Unity.6.5.cs +651 -651
  214. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ToolBatchExecutionIntegrationTests.cs +407 -407
  215. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/{McpPluginTests.cs → UcoPluginTests.cs} +105 -105
  216. package/vendor/plugin/com.atelierai.unity.copilot/package.json +1 -1
  217. package/dist/commands/devops/login.d.ts +0 -2
  218. package/dist/commands/devops/login.js +0 -29
  219. package/dist/commands/devops/login.js.map +0 -1
  220. package/dist/commands/devops/setup-mcp.d.ts +0 -2
  221. package/dist/commands/devops/setup-mcp.js +0 -41
  222. package/dist/commands/devops/setup-mcp.js.map +0 -1
  223. package/dist/devops/commands-upstream/bootstrap-local.d.ts +0 -17
  224. package/dist/devops/commands-upstream/bootstrap-local.js +0 -87
  225. package/dist/devops/commands-upstream/bootstrap-local.js.map +0 -1
  226. package/dist/devops/commands-upstream/close.d.ts +0 -57
  227. package/dist/devops/commands-upstream/close.js +0 -188
  228. package/dist/devops/commands-upstream/close.js.map +0 -1
  229. package/dist/devops/commands-upstream/configure.d.ts +0 -2
  230. package/dist/devops/commands-upstream/configure.js +0 -87
  231. package/dist/devops/commands-upstream/configure.js.map +0 -1
  232. package/dist/devops/commands-upstream/create-project.d.ts +0 -2
  233. package/dist/devops/commands-upstream/create-project.js +0 -43
  234. package/dist/devops/commands-upstream/create-project.js.map +0 -1
  235. package/dist/devops/commands-upstream/install-plugin.d.ts +0 -2
  236. package/dist/devops/commands-upstream/install-plugin.js +0 -64
  237. package/dist/devops/commands-upstream/install-plugin.js.map +0 -1
  238. package/dist/devops/commands-upstream/install-unity.d.ts +0 -2
  239. package/dist/devops/commands-upstream/install-unity.js +0 -72
  240. package/dist/devops/commands-upstream/install-unity.js.map +0 -1
  241. package/dist/devops/commands-upstream/login.d.ts +0 -2
  242. package/dist/devops/commands-upstream/login.js +0 -33
  243. package/dist/devops/commands-upstream/login.js.map +0 -1
  244. package/dist/devops/commands-upstream/open.d.ts +0 -32
  245. package/dist/devops/commands-upstream/open.js +0 -282
  246. package/dist/devops/commands-upstream/open.js.map +0 -1
  247. package/dist/devops/commands-upstream/remove-plugin.d.ts +0 -2
  248. package/dist/devops/commands-upstream/remove-plugin.js +0 -41
  249. package/dist/devops/commands-upstream/remove-plugin.js.map +0 -1
  250. package/dist/devops/commands-upstream/run-system-tool.d.ts +0 -1
  251. package/dist/devops/commands-upstream/run-system-tool.js +0 -13
  252. package/dist/devops/commands-upstream/run-system-tool.js.map +0 -1
  253. package/dist/devops/commands-upstream/run-tool-builder.d.ts +0 -25
  254. package/dist/devops/commands-upstream/run-tool-builder.js +0 -125
  255. package/dist/devops/commands-upstream/run-tool-builder.js.map +0 -1
  256. package/dist/devops/commands-upstream/run-tool.d.ts +0 -1
  257. package/dist/devops/commands-upstream/run-tool.js +0 -13
  258. package/dist/devops/commands-upstream/run-tool.js.map +0 -1
  259. package/dist/devops/commands-upstream/setup-mcp.d.ts +0 -2
  260. package/dist/devops/commands-upstream/setup-mcp.js +0 -68
  261. package/dist/devops/commands-upstream/setup-mcp.js.map +0 -1
  262. package/dist/devops/commands-upstream/setup-skills.d.ts +0 -2
  263. package/dist/devops/commands-upstream/setup-skills.js +0 -167
  264. package/dist/devops/commands-upstream/setup-skills.js.map +0 -1
  265. package/dist/devops/commands-upstream/status.d.ts +0 -2
  266. package/dist/devops/commands-upstream/status.js +0 -87
  267. package/dist/devops/commands-upstream/status.js.map +0 -1
  268. package/dist/devops/commands-upstream/update.d.ts +0 -2
  269. package/dist/devops/commands-upstream/update.js +0 -77
  270. package/dist/devops/commands-upstream/update.js.map +0 -1
  271. package/dist/devops/commands-upstream/wait-for-ready.d.ts +0 -2
  272. package/dist/devops/commands-upstream/wait-for-ready.js +0 -93
  273. package/dist/devops/commands-upstream/wait-for-ready.js.map +0 -1
  274. package/dist/devops/lib/setup-mcp.d.ts +0 -35
  275. package/dist/devops/lib/setup-mcp.js +0 -177
  276. package/dist/devops/lib/setup-mcp.js.map +0 -1
  277. package/dist/devops/utils/cloud-login.d.ts +0 -7
  278. package/dist/devops/utils/cloud-login.js +0 -56
  279. package/dist/devops/utils/cloud-login.js.map +0 -1
  280. /package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/{McpPluginTests.cs.meta → UcoPluginTests.cs.meta} +0 -0
@@ -1,651 +1,651 @@
1
- /*
2
- ┌──────────────────────────────────────────────────────────────────┐
3
- │ Author: Ivan Murzak (https://github.com/IvanMurzak) │
4
- │ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │
5
- │ Copyright (c) 2025 Ivan Murzak │
6
- │ Licensed under the Apache License, Version 2.0. │
7
- │ See the LICENSE file in the project root for more information. │
8
- └──────────────────────────────────────────────────────────────────┘
9
- */
10
-
11
- #nullable enable
12
- #if UNITY_6000_5_OR_NEWER
13
- using System.Collections;
14
- using System.IO;
15
- using com.AtelierAI.Unity.Copilot.Editor.API;
16
- using NUnit.Framework;
17
- using UnityEditor;
18
- using UnityEngine;
19
- using UnityEngine.TestTools;
20
-
21
- namespace com.AtelierAI.Unity.Copilot.Editor.Tests
22
- {
23
- /// <summary>
24
- /// Thread-safety coverage for MCP tools — regression for
25
- /// <see href="https://github.com/IvanMurzak/uco-plugin/issues/633">#633</see>.
26
- ///
27
- /// For each tool we want to guarantee the tool's body handles invocation from any
28
- /// thread (the MCP server dispatches tool calls from a SignalR background thread,
29
- /// so tools that touch Unity APIs must marshal back to the main thread via
30
- /// <c>MainThread.Instance.Run</c>). Forgetting to do so caused the regression in
31
- /// <see href="https://github.com/IvanMurzak/uco-plugin/issues/632">#632</see> /
32
- /// <see href="https://github.com/IvanMurzak/uco-plugin/pull/637">#637</see>
33
- /// (<c>console-clear-logs</c>).
34
- ///
35
- /// <b>Excluded tools</b>: <c>package-add</c>/<c>package-remove</c> (trigger domain
36
- /// reload), <c>tests-run</c> (recursive hang), system tools (<c>ping</c>,
37
- /// <c>unity-skill-*</c>). See inline comments for rationale.
38
- ///
39
- /// Two helpers from <see cref="BaseTest"/> are used:
40
- /// <list type="bullet">
41
- /// <item><see cref="BaseTest.RunToolBothThreads"/> — happy path; asserts the tool
42
- /// succeeds on both threads. Used when crafting a valid input is practical.</item>
43
- /// <item><see cref="BaseTest.RunToolExpectNoThreadViolation"/> — lenient mode; tolerates
44
- /// business-level errors and only fails the test if Unity surfaces a
45
- /// <c>"can only be called from the main thread"</c> exception. Used for tools
46
- /// that need elaborate setup (<c>SerializedMember</c>, prefab stages, etc.) or
47
- /// whose happy path would mutate the project in risky ways
48
- /// (<c>package-add</c>/<c>package-remove</c>, <c>tests-run</c>, etc.).</item>
49
- /// </list>
50
- /// </summary>
51
- public class ToolThreadSafetyTests : BaseTest
52
- {
53
- const string TmpFolderName = "BgThreadTests_TMP";
54
- const string TmpFolder = "Assets/" + TmpFolderName;
55
-
56
- [TearDown]
57
- public void BgTearDown()
58
- {
59
- if (AssetDatabase.IsValidFolder(TmpFolder))
60
- AssetDatabase.DeleteAsset(TmpFolder);
61
- }
62
-
63
- // ================================================================
64
- // NO-ARG / READ-ONLY tools (happy path, empty JSON)
65
- // ================================================================
66
-
67
- [UnityTest] public IEnumerator ToolList_BothThreads()
68
- => RunToolBothThreads(Tool_Tool.ToolListId, "{}");
69
-
70
- [UnityTest] public IEnumerator GameObjectComponentListAll_BothThreads()
71
- => RunToolBothThreads(Tool_GameObject.ComponentListToolId, @"{""pageSize"":3}");
72
-
73
- [UnityTest] public IEnumerator SceneListOpened_BothThreads()
74
- => RunToolBothThreads(Tool_Scene.SceneListOpenedToolId, "{}");
75
-
76
- [UnityTest] public IEnumerator SceneGetData_BothThreads()
77
- => RunToolBothThreads(Tool_Scene.SceneGetDataToolId, "{}");
78
-
79
- [UnityTest] public IEnumerator EditorApplicationGetState_BothThreads()
80
- => RunToolBothThreads(Tool_Editor.EditorApplicationGetStateToolId, "{}");
81
-
82
- [UnityTest] public IEnumerator EditorSelectionGet_BothThreads()
83
- => RunToolBothThreads(Tool_Editor_Selection.EditorSelectionGetToolId, "{}");
84
-
85
- [UnityTest] public IEnumerator ConsoleGetLogs_BothThreads()
86
- => RunToolBothThreads(Tool_Console.ConsoleGetLogsToolId, "{}");
87
-
88
- [UnityTest]
89
- public IEnumerator ConsoleClearLogs_BothThreads()
90
- {
91
- // Regression for #632/#637: ClearLogs calls Debug.ClearDeveloperConsole() which
92
- // MUST be invoked on the main thread.
93
- yield return RunToolBothThreads(Tool_Console.ConsoleClearLogsToolId, "{}");
94
- }
95
-
96
- [UnityTest] public IEnumerator AssetsFind_BothThreads()
97
- => RunToolBothThreads(Tool_Assets.AssetsFindToolId, @"{""filter"":""t:Scene"",""maxResults"":3}");
98
-
99
- [UnityTest] public IEnumerator AssetsFindBuiltIn_BothThreads()
100
- => RunToolBothThreads(Tool_Assets.AssetsFindBuiltInToolId, @"{""maxResults"":3}");
101
-
102
- [UnityTest] public IEnumerator AssetsShaderListAll_BothThreads()
103
- => RunToolBothThreads(Tool_Assets_Shader.AssetsShaderListAllToolId, "{}");
104
-
105
- [UnityTest] public IEnumerator AssetsRefresh_BothThreads()
106
- => RunToolBothThreads(Tool_Assets.AssetsRefreshToolId, "{}");
107
-
108
- [UnityTest] public IEnumerator PackageList_BothThreads()
109
- => RunToolBothThreads(Tool_Package.PackageListToolId, "{}");
110
-
111
- [UnityTest] public IEnumerator TypeGetJsonSchema_BothThreads()
112
- => RunToolBothThreads(Tool_Type.TypeGetJsonSchemaToolId, @"{""typeName"":""UnityEngine.Vector3""}");
113
-
114
- // ================================================================
115
- // GAMEOBJECT tools — happy path, per-call setup
116
- // ================================================================
117
-
118
- [UnityTest]
119
- public IEnumerator GameObjectCreate_BothThreads()
120
- {
121
- yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectCreateToolId, @"{""name"":""bg_main""}");
122
- Assert.IsNotNull(GameObject.Find("bg_main"));
123
- yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectCreateToolId, @"{""name"":""bg_bg""}");
124
- Assert.IsNotNull(GameObject.Find("bg_bg"));
125
- }
126
-
127
- [UnityTest]
128
- public IEnumerator GameObjectFind_BothThreads()
129
- {
130
- var go = new GameObject("bg_find");
131
- var id = go.GetEntityId();
132
- yield return RunToolBothThreads(Tool_GameObject.GameObjectFindToolId,
133
- $@"{{""gameObjectRef"":{{""instanceID"":{id}}}}}");
134
- }
135
-
136
- [UnityTest]
137
- public IEnumerator GameObjectDestroy_BothThreads()
138
- {
139
- var a = new GameObject("bg_destroy_a").GetEntityId();
140
- yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectDestroyToolId,
141
- $@"{{""gameObjectRef"":{{""instanceID"":{a}}}}}");
142
-
143
- var b = new GameObject("bg_destroy_b").GetEntityId();
144
- yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectDestroyToolId,
145
- $@"{{""gameObjectRef"":{{""instanceID"":{b}}}}}");
146
- }
147
-
148
- [UnityTest]
149
- public IEnumerator GameObjectDuplicate_BothThreads()
150
- {
151
- var a = new GameObject("bg_dup_a").GetEntityId();
152
- yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectDuplicateToolId,
153
- $@"{{""gameObjectRefs"":[{{""instanceID"":{a}}}]}}");
154
-
155
- var b = new GameObject("bg_dup_b").GetEntityId();
156
- yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectDuplicateToolId,
157
- $@"{{""gameObjectRefs"":[{{""instanceID"":{b}}}]}}");
158
- }
159
-
160
- [UnityTest]
161
- public IEnumerator GameObjectSetParent_BothThreads()
162
- {
163
- var parent = new GameObject("bg_parent").GetEntityId();
164
- var child1 = new GameObject("bg_child_a").GetEntityId();
165
- yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectSetParentToolId,
166
- $@"{{""gameObjectRefs"":[{{""instanceID"":{child1}}}],""parentGameObjectRef"":{{""instanceID"":{parent}}}}}");
167
-
168
- var child2 = new GameObject("bg_child_b").GetEntityId();
169
- yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectSetParentToolId,
170
- $@"{{""gameObjectRefs"":[{{""instanceID"":{child2}}}],""parentGameObjectRef"":{{""instanceID"":{parent}}}}}");
171
- }
172
-
173
- [UnityTest]
174
- public IEnumerator GameObjectComponentAdd_BothThreads()
175
- {
176
- var a = new GameObject("bg_addc_a").GetEntityId();
177
- yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectComponentAddToolId,
178
- $@"{{""gameObjectRef"":{{""instanceID"":{a}}},""componentNames"":[""UnityEngine.BoxCollider""]}}");
179
-
180
- var b = new GameObject("bg_addc_b").GetEntityId();
181
- yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectComponentAddToolId,
182
- $@"{{""gameObjectRef"":{{""instanceID"":{b}}},""componentNames"":[""UnityEngine.SphereCollider""]}}");
183
- }
184
-
185
- [UnityTest]
186
- public IEnumerator GameObjectComponentGet_BothThreads()
187
- {
188
- var go = new GameObject("bg_getc");
189
- var id = go.GetEntityId();
190
- // Transform lives at component index 0 on every GameObject.
191
- yield return RunToolBothThreads(Tool_GameObject.GameObjectComponentGetToolId,
192
- $@"{{""gameObjectRef"":{{""instanceID"":{id}}},""componentRef"":{{""index"":0}}}}");
193
- }
194
-
195
- [UnityTest]
196
- public IEnumerator GameObjectComponentDestroy_BothThreads()
197
- {
198
- var a = new GameObject("bg_destc_a");
199
- a.AddComponent<BoxCollider>();
200
- var idA = a.GetEntityId();
201
- yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectComponentDestroyToolId,
202
- $@"{{""gameObjectRef"":{{""instanceID"":{idA}}},""destroyComponentRefs"":[{{""typeName"":""UnityEngine.BoxCollider""}}]}}");
203
-
204
- var b = new GameObject("bg_destc_b");
205
- b.AddComponent<SphereCollider>();
206
- var idB = b.GetEntityId();
207
- yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectComponentDestroyToolId,
208
- $@"{{""gameObjectRef"":{{""instanceID"":{idB}}},""destroyComponentRefs"":[{{""typeName"":""UnityEngine.SphereCollider""}}]}}");
209
- }
210
-
211
- [UnityTest]
212
- public IEnumerator GameObjectModify_ThreadSafetyOnly()
213
- {
214
- // gameobject-modify requires a SerializedMember tree — we cover thread safety
215
- // only (business errors from an empty diff are tolerated).
216
- var id = new GameObject("bg_modify").GetEntityId();
217
- yield return RunToolExpectNoThreadViolation(Tool_GameObject.GameObjectModifyToolId,
218
- $@"{{""gameObjectRefs"":[{{""instanceID"":{id}}}],""gameObjectDiffs"":[{{""name"":""root""}}]}}");
219
- }
220
-
221
- [UnityTest]
222
- public IEnumerator GameObjectComponentModify_ThreadSafetyOnly()
223
- {
224
- var go = new GameObject("bg_cmodify");
225
- go.AddComponent<BoxCollider>();
226
- var id = go.GetEntityId();
227
- yield return RunToolExpectNoThreadViolation(Tool_GameObject.GameObjectComponentModifyToolId,
228
- $@"{{""gameObjectRef"":{{""instanceID"":{id}}},""componentRef"":{{""typeName"":""UnityEngine.BoxCollider""}},""componentDiff"":{{""name"":""root""}}}}");
229
- }
230
-
231
- // ================================================================
232
- // OBJECT tools
233
- // ================================================================
234
-
235
- [UnityTest]
236
- public IEnumerator ObjectGetData_BothThreads()
237
- {
238
- var id = new GameObject("bg_obj").GetEntityId();
239
- yield return RunToolBothThreads(Tool_Object.ObjectGetDataToolId,
240
- $@"{{""objectRef"":{{""instanceID"":{id}}}}}");
241
- }
242
-
243
- [UnityTest]
244
- public IEnumerator ObjectModify_ThreadSafetyOnly()
245
- {
246
- var id = new GameObject("bg_objmod").GetEntityId();
247
- yield return RunToolExpectNoThreadViolation(Tool_Object.ObjectModifyToolId,
248
- $@"{{""objectRef"":{{""instanceID"":{id}}},""objectDiff"":{{""name"":""root""}}}}");
249
- }
250
-
251
- // ================================================================
252
- // EDITOR tools
253
- // ================================================================
254
-
255
- [UnityTest]
256
- public IEnumerator EditorSelectionSet_BothThreads()
257
- {
258
- var id = new GameObject("bg_select").GetEntityId();
259
- yield return RunToolBothThreads(Tool_Editor_Selection.EditorSelectionSetToolId,
260
- $@"{{""select"":[{{""instanceID"":{id}}}]}}");
261
- }
262
-
263
- [UnityTest]
264
- public IEnumerator EditorApplicationSetState_BothThreads()
265
- {
266
- // No-op toggle: current state is not-playing, not-paused.
267
- yield return RunToolBothThreads(Tool_Editor.EditorApplicationSetStateToolId,
268
- @"{""isPlaying"":false,""isPaused"":false}");
269
- }
270
-
271
- // ================================================================
272
- // ASSETS tools
273
- // ================================================================
274
-
275
- [UnityTest]
276
- public IEnumerator AssetsCreateFolder_BothThreads()
277
- {
278
- EnsureTmpFolder();
279
- yield return RunToolMainThreadCoop(Tool_Assets.AssetsCreateFolderToolId,
280
- $@"{{""inputs"":[{{""parentFolderPath"":""{TmpFolder}"",""newFolderName"":""a""}}]}}");
281
- yield return RunToolFromBackgroundThread(Tool_Assets.AssetsCreateFolderToolId,
282
- $@"{{""inputs"":[{{""parentFolderPath"":""{TmpFolder}"",""newFolderName"":""b""}}]}}");
283
- }
284
-
285
- [UnityTest]
286
- public IEnumerator AssetsGetData_BothThreads()
287
- {
288
- var assetPath = CreateTmpTextAsset("get_data.txt", "hello");
289
- yield return RunToolBothThreads(Tool_Assets.AssetsGetDataToolId,
290
- $@"{{""assetRef"":{{""assetPath"":""{assetPath}""}}}}");
291
- }
292
-
293
- [UnityTest]
294
- public IEnumerator AssetsModify_ThreadSafetyOnly()
295
- {
296
- var assetPath = CreateTmpTextAsset("modify.txt", "hello");
297
- yield return RunToolExpectNoThreadViolation(Tool_Assets.AssetsModifyToolId,
298
- $@"{{""assetRef"":{{""assetPath"":""{assetPath}""}},""content"":{{""name"":""root""}}}}");
299
- }
300
-
301
- [UnityTest]
302
- public IEnumerator AssetsMaterialCreate_BothThreads()
303
- {
304
- EnsureTmpFolder();
305
- yield return RunToolMainThreadCoop(Tool_Assets.AssetsMaterialCreateToolId,
306
- $@"{{""assetPath"":""{TmpFolder}/mat_main.mat"",""shaderName"":""Standard""}}");
307
- yield return RunToolFromBackgroundThread(Tool_Assets.AssetsMaterialCreateToolId,
308
- $@"{{""assetPath"":""{TmpFolder}/mat_bg.mat"",""shaderName"":""Standard""}}");
309
- }
310
-
311
- [UnityTest]
312
- public IEnumerator AssetsShaderGetData_BothThreads()
313
- {
314
- // Find any shader asset in the project to query.
315
- var shaderPath = FindFirstShaderPath();
316
- if (shaderPath == null)
317
- {
318
- Assert.Ignore("No shader asset available in project.");
319
- yield break;
320
- }
321
- yield return RunToolBothThreads(Tool_Assets_Shader.AssetsShaderGetDataToolId,
322
- $@"{{""assetRef"":{{""assetPath"":""{shaderPath}""}}}}");
323
- }
324
-
325
- [UnityTest]
326
- public IEnumerator AssetsCopy_BothThreads()
327
- {
328
- var src = CreateTmpTextAsset("copy_src.txt", "source");
329
- yield return RunToolMainThreadCoop(Tool_Assets.AssetsCopyToolId,
330
- $@"{{""sourcePaths"":[""{src}""],""destinationPaths"":[""{TmpFolder}/copy_main.txt""]}}");
331
- yield return RunToolFromBackgroundThread(Tool_Assets.AssetsCopyToolId,
332
- $@"{{""sourcePaths"":[""{src}""],""destinationPaths"":[""{TmpFolder}/copy_bg.txt""]}}");
333
- }
334
-
335
- [UnityTest]
336
- public IEnumerator AssetsMove_BothThreads()
337
- {
338
- var src1 = CreateTmpTextAsset("move_a.txt", "a");
339
- yield return RunToolMainThreadCoop(Tool_Assets.AssetsMoveToolId,
340
- $@"{{""sourcePaths"":[""{src1}""],""destinationPaths"":[""{TmpFolder}/move_a_done.txt""]}}");
341
-
342
- var src2 = CreateTmpTextAsset("move_b.txt", "b");
343
- yield return RunToolFromBackgroundThread(Tool_Assets.AssetsMoveToolId,
344
- $@"{{""sourcePaths"":[""{src2}""],""destinationPaths"":[""{TmpFolder}/move_b_done.txt""]}}");
345
- }
346
-
347
- [UnityTest]
348
- public IEnumerator AssetsDelete_BothThreads()
349
- {
350
- var src1 = CreateTmpTextAsset("del_a.txt", "a");
351
- yield return RunToolMainThreadCoop(Tool_Assets.AssetsDeleteToolId, $@"{{""paths"":[""{src1}""]}}");
352
-
353
- var src2 = CreateTmpTextAsset("del_b.txt", "b");
354
- yield return RunToolFromBackgroundThread(Tool_Assets.AssetsDeleteToolId,
355
- $@"{{""paths"":[""{src2}""]}}");
356
- }
357
-
358
- // ---------- Prefabs ----------
359
-
360
- [UnityTest]
361
- public IEnumerator AssetsPrefabCreate_BothThreads()
362
- {
363
- EnsureTmpFolder();
364
- var idA = new GameObject("bg_prefab_a").GetEntityId();
365
- yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabCreateToolId,
366
- $@"{{""prefabAssetPath"":""{TmpFolder}/prefab_a.prefab"",""gameObjectRef"":{{""instanceID"":{idA}}}}}");
367
-
368
- var idB = new GameObject("bg_prefab_b").GetEntityId();
369
- yield return RunToolFromBackgroundThread(Tool_Assets_Prefab.AssetsPrefabCreateToolId,
370
- $@"{{""prefabAssetPath"":""{TmpFolder}/prefab_b.prefab"",""gameObjectRef"":{{""instanceID"":{idB}}}}}");
371
- }
372
-
373
- [UnityTest]
374
- public IEnumerator AssetsPrefabInstantiate_BothThreads()
375
- {
376
- EnsureTmpFolder();
377
- var seedId = new GameObject("bg_seed_prefab").GetEntityId();
378
- var prefabPath = $"{TmpFolder}/seed.prefab";
379
- yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabCreateToolId,
380
- $@"{{""prefabAssetPath"":""{prefabPath}"",""gameObjectRef"":{{""instanceID"":{seedId}}}}}");
381
-
382
- yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabInstantiateToolId,
383
- $@"{{""prefabAssetPath"":""{prefabPath}"",""gameObjectPath"":""bg_inst_main""}}");
384
- yield return RunToolFromBackgroundThread(Tool_Assets_Prefab.AssetsPrefabInstantiateToolId,
385
- $@"{{""prefabAssetPath"":""{prefabPath}"",""gameObjectPath"":""bg_inst_bg""}}");
386
- }
387
-
388
- [UnityTest]
389
- public IEnumerator AssetsPrefabOpenSaveClose_BothThreads()
390
- {
391
- EnsureTmpFolder();
392
-
393
- // Create a prefab asset from a scene GameObject.
394
- var seedA = new GameObject("bg_prefab_osc_a").GetEntityId();
395
- var prefabPathA = $"{TmpFolder}/osc_a.prefab";
396
- yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabCreateToolId,
397
- $@"{{""prefabAssetPath"":""{prefabPathA}"",""gameObjectRef"":{{""instanceID"":{seedA}}}}}");
398
-
399
- // Instantiate it so we get a prefab instance to open.
400
- yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabInstantiateToolId,
401
- $@"{{""prefabAssetPath"":""{prefabPathA}"",""gameObjectPath"":""bg_osc_inst_a""}}");
402
- var instanceA = GameObject.Find("bg_osc_inst_a");
403
- Assert.IsNotNull(instanceA, "Prefab instance not found after instantiate.");
404
-
405
- // Open → Save → Close (main thread).
406
- yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabOpenToolId,
407
- $@"{{""gameObjectRef"":{{""instanceID"":{instanceA!.GetEntityId()}}}}}");
408
- yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabSaveToolId, "{}");
409
- yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabCloseToolId, @"{""save"":false}");
410
-
411
- // Same sequence from a background thread.
412
- var seedB = new GameObject("bg_prefab_osc_b").GetEntityId();
413
- var prefabPathB = $"{TmpFolder}/osc_b.prefab";
414
- yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabCreateToolId,
415
- $@"{{""prefabAssetPath"":""{prefabPathB}"",""gameObjectRef"":{{""instanceID"":{seedB}}}}}");
416
- yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabInstantiateToolId,
417
- $@"{{""prefabAssetPath"":""{prefabPathB}"",""gameObjectPath"":""bg_osc_inst_b""}}");
418
- var instanceB = GameObject.Find("bg_osc_inst_b");
419
- Assert.IsNotNull(instanceB, "Prefab instance not found after instantiate.");
420
-
421
- yield return RunToolFromBackgroundThread(Tool_Assets_Prefab.AssetsPrefabOpenToolId,
422
- $@"{{""gameObjectRef"":{{""instanceID"":{instanceB!.GetEntityId()}}}}}");
423
- yield return RunToolFromBackgroundThread(Tool_Assets_Prefab.AssetsPrefabSaveToolId, "{}");
424
- yield return RunToolFromBackgroundThread(Tool_Assets_Prefab.AssetsPrefabCloseToolId, @"{""save"":false}");
425
- }
426
-
427
- // ================================================================
428
- // SCENE tools
429
- // ================================================================
430
-
431
- [UnityTest]
432
- public IEnumerator SceneCreate_BothThreads()
433
- {
434
- EnsureTmpFolder();
435
- var startingActive = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
436
- yield return RunToolMainThreadCoop(Tool_Scene.SceneCreateToolId,
437
- $@"{{""path"":""{TmpFolder}/scene_main.unity""}}");
438
- yield return RunToolFromBackgroundThread(Tool_Scene.SceneCreateToolId,
439
- $@"{{""path"":""{TmpFolder}/scene_bg.unity""}}");
440
-
441
- // Restore a baseline active scene if the test clobbered it.
442
- if (startingActive.IsValid() && UnityEngine.SceneManagement.SceneManager.GetActiveScene() != startingActive)
443
- UnityEngine.SceneManagement.SceneManager.SetActiveScene(startingActive);
444
- }
445
-
446
- [UnityTest]
447
- public IEnumerator SceneOpenSaveSetActive_BothThreads()
448
- {
449
- EnsureTmpFolder();
450
- var scenePath = $"{TmpFolder}/reopen.unity";
451
- yield return RunToolMainThreadCoop(Tool_Scene.SceneCreateToolId, $@"{{""path"":""{scenePath}""}}");
452
-
453
- // open (main + bg)
454
- yield return RunToolMainThreadCoop(Tool_Scene.SceneOpenToolId,
455
- $@"{{""sceneRef"":{{""assetPath"":""{scenePath}""}}}}");
456
- yield return RunToolFromBackgroundThread(Tool_Scene.SceneOpenToolId,
457
- $@"{{""sceneRef"":{{""assetPath"":""{scenePath}""}}}}");
458
-
459
- // set-active (both)
460
- yield return RunToolBothThreads(Tool_Scene.SceneSetActiveToolId,
461
- $@"{{""sceneRef"":{{""assetPath"":""{scenePath}""}}}}");
462
-
463
- // save (both)
464
- yield return RunToolBothThreads(Tool_Scene.SceneSaveToolId, "{}");
465
- }
466
-
467
- [UnityTest]
468
- public IEnumerator SceneUnload_BothThreads()
469
- {
470
- EnsureTmpFolder();
471
-
472
- // Create a saved scene so we can open additional scenes additively.
473
- var basePath = $"{TmpFolder}/unload_base.unity";
474
- yield return RunToolMainThreadCoop(Tool_Scene.SceneCreateToolId,
475
- $@"{{""path"":""{basePath}""}}");
476
-
477
- // Create two extra scenes additively (allowed now because base is saved).
478
- var mainPath = $"{TmpFolder}/unload_main.unity";
479
- var bgPath = $"{TmpFolder}/unload_bg.unity";
480
- yield return RunToolMainThreadCoop(Tool_Scene.SceneCreateToolId,
481
- $@"{{""path"":""{mainPath}"",""newSceneMode"":""Additive""}}");
482
- yield return RunToolMainThreadCoop(Tool_Scene.SceneCreateToolId,
483
- $@"{{""path"":""{bgPath}"",""newSceneMode"":""Additive""}}");
484
-
485
- // Unload each additive scene from both threads (tool takes scene name, not path).
486
- yield return RunToolMainThreadCoop(Tool_Scene.SceneUnloadToolId,
487
- @"{""name"":""unload_main""}");
488
- yield return RunToolFromBackgroundThread(Tool_Scene.SceneUnloadToolId,
489
- @"{""name"":""unload_bg""}");
490
- }
491
-
492
- // ================================================================
493
- // SCRIPT tools
494
- // ================================================================
495
-
496
- [UnityTest]
497
- public IEnumerator ScriptUpdateOrCreate_ReadDelete_BothThreads()
498
- {
499
- EnsureTmpFolder();
500
- var pathA = $"{TmpFolder}/S_a.cs";
501
- var pathB = $"{TmpFolder}/S_b.cs";
502
- const string SrcA = "public class BgSa { public static void M() {} }";
503
- const string SrcB = "public class BgSb { public static void M() {} }";
504
-
505
- // update-or-create
506
- yield return RunToolMainThreadCoop(Tool_Script.ScriptUpdateOrCreateToolId,
507
- $@"{{""filePath"":""{pathA}"",""content"":{JsonString(SrcA)}}}");
508
- yield return RunToolFromBackgroundThread(Tool_Script.ScriptUpdateOrCreateToolId,
509
- $@"{{""filePath"":""{pathB}"",""content"":{JsonString(SrcB)}}}");
510
-
511
- // read (main + bg)
512
- yield return RunToolBothThreads(Tool_Script.ScriptReadToolId,
513
- $@"{{""filePath"":""{pathA}""}}",
514
- $@"{{""filePath"":""{pathB}""}}");
515
-
516
- // delete
517
- yield return RunToolMainThreadCoop(Tool_Script.ScriptDeleteToolId, $@"{{""files"":[""{pathA}""]}}");
518
- yield return RunToolFromBackgroundThread(Tool_Script.ScriptDeleteToolId,
519
- $@"{{""files"":[""{pathB}""]}}");
520
- }
521
-
522
- [UnityTest]
523
- public IEnumerator ScriptExecute_BothThreads()
524
- {
525
- const string Code = "public class Script { public static int Main() { return 42; } }";
526
- yield return RunToolBothThreads(Tool_Script.ScriptExecuteToolId,
527
- $@"{{""csharpCode"":{JsonString(Code)},""className"":""Script"",""methodName"":""Main""}}");
528
- }
529
-
530
- // ================================================================
531
- // TOOL CONTROL
532
- // ================================================================
533
-
534
- [UnityTest]
535
- public IEnumerator ToolSetEnabledState_BothThreads()
536
- {
537
- // Enable unity-tool-list twice (idempotent, already enabled) from both threads.
538
- yield return RunToolBothThreads(Tool_Tool.ToolSetEnabledStateId,
539
- @"{""tools"":[{""name"":""unity-tool-list"",""enabled"":true}]}");
540
- }
541
-
542
- // ================================================================
543
- // REFLECTION (complex MethodRef schema → thread-safety only)
544
- // ================================================================
545
-
546
- [UnityTest]
547
- public IEnumerator ReflectionMethodFind_ThreadSafetyOnly()
548
- {
549
- yield return RunToolExpectNoThreadViolation(Tool_Reflection.ReflectionMethodFindToolId,
550
- @"{""filter"":{""Namespace"":""UnityEngine"",""TypeName"":""Transform"",""MethodName"":""LookAt""}}");
551
- }
552
-
553
- [UnityTest]
554
- public IEnumerator ReflectionMethodCall_BothThreads()
555
- {
556
- // Call a static method on a test-scoped GameObject's Transform via instance method.
557
- var go = new GameObject("bg_reflect");
558
- var id = go.GetEntityId();
559
- yield return RunToolBothThreads(Tool_Reflection.ReflectionMethodCallToolId,
560
- $@"{{""filter"":{{""Namespace"":""UnityEngine"",""TypeName"":""GameObject"",""MethodName"":""Find"",""InputParameters"":[{{""TypeName"":""System.String"",""Name"":""name""}}]}},""inputParameters"":[{{""typeName"":""System.String"",""name"":""name"",""value"":""bg_reflect""}}]}}");
561
- }
562
-
563
- // ================================================================
564
- // PACKAGE MANAGER — package-add / package-remove intentionally NOT
565
- // exercised here: both return status "Processing" and kick off a real
566
- // Unity PackageManager resolution on a background task, which in turn
567
- // forces a domain reload. Under the test runner that hangs the whole
568
- // batch-mode run. Their thread-safety is already covered by the fact
569
- // that their entry path is a synchronous parameter check.
570
- // ================================================================
571
-
572
- [UnityTest]
573
- public IEnumerator PackageSearch_ThreadSafetyOnly()
574
- {
575
- // Registry round-trip is slow and flaky under test; thread-safety only.
576
- yield return RunToolExpectNoThreadViolation(Tool_Package.PackageSearchToolId,
577
- @"{""query"":""com.unity.textmeshpro"",""maxResults"":1,""offlineMode"":true}");
578
- }
579
-
580
- // ================================================================
581
- // SCREENSHOTS — not guaranteed to have a camera/view in batchmode.
582
- // ================================================================
583
-
584
- [UnityTest]
585
- public IEnumerator ScreenshotCamera_BothThreads()
586
- {
587
- new GameObject("TestCamera").AddComponent<Camera>();
588
- yield return RunToolBothThreads(Tool_Screenshot.ScreenshotCameraToolId, @"{""width"":64,""height"":64}");
589
- }
590
-
591
- [UnityTest]
592
- public IEnumerator ScreenshotGameView_ThreadSafetyOnly()
593
- => RunToolExpectNoThreadViolation(Tool_Screenshot.ScreenshotGameViewToolId, "{}");
594
-
595
- [UnityTest]
596
- public IEnumerator ScreenshotSceneView_ThreadSafetyOnly()
597
- => RunToolExpectNoThreadViolation(Tool_Screenshot.ScreenshotSceneViewToolId, @"{""width"":64,""height"":64}");
598
-
599
- // ================================================================
600
- // TESTS — recursive; skip deliberately.
601
- // ================================================================
602
- // tests-run intentionally has no test here: executing it from within the test
603
- // runner causes recursion and a hang. Its thread safety is implicit because the
604
- // test runner itself schedules the invocation onto the main thread.
605
-
606
- // ================================================================
607
- // Helpers
608
- // ================================================================
609
-
610
- static void EnsureTmpFolder()
611
- {
612
- if (!AssetDatabase.IsValidFolder(TmpFolder))
613
- AssetDatabase.CreateFolder("Assets", TmpFolderName);
614
- }
615
-
616
- static string CreateTmpTextAsset(string fileName, string contents)
617
- {
618
- EnsureTmpFolder();
619
- var path = $"{TmpFolder}/{fileName}";
620
- File.WriteAllText(path, contents);
621
- AssetDatabase.ImportAsset(path);
622
- return path;
623
- }
624
-
625
- static string? FindFirstShaderPath()
626
- {
627
- // Prefer the built-in Standard shader.
628
- var standard = Shader.Find("Standard");
629
- if (standard != null)
630
- {
631
- var p = AssetDatabase.GetAssetPath(standard);
632
- if (!string.IsNullOrEmpty(p))
633
- return p;
634
- }
635
-
636
- var guids = AssetDatabase.FindAssets("t:Shader");
637
- foreach (var guid in guids)
638
- {
639
- var p = AssetDatabase.GUIDToAssetPath(guid);
640
- if (!string.IsNullOrEmpty(p))
641
- return p;
642
- }
643
- return null;
644
- }
645
-
646
- /// <summary>Returns a JSON string literal (quoted, backslash-escaped).</summary>
647
- static string JsonString(string raw)
648
- => System.Text.Json.JsonSerializer.Serialize(raw);
649
- }
650
- }
651
- #endif
1
+ /*
2
+ ┌──────────────────────────────────────────────────────────────────┐
3
+ │ Author: Ivan Murzak (https://github.com/IvanMurzak) │
4
+ │ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │
5
+ │ Copyright (c) 2025 Ivan Murzak │
6
+ │ Licensed under the Apache License, Version 2.0. │
7
+ │ See the LICENSE file in the project root for more information. │
8
+ └──────────────────────────────────────────────────────────────────┘
9
+ */
10
+
11
+ #nullable enable
12
+ #if UNITY_6000_5_OR_NEWER
13
+ using System.Collections;
14
+ using System.IO;
15
+ using com.AtelierAI.Unity.Copilot.Editor.API;
16
+ using NUnit.Framework;
17
+ using UnityEditor;
18
+ using UnityEngine;
19
+ using UnityEngine.TestTools;
20
+
21
+ namespace com.AtelierAI.Unity.Copilot.Editor.Tests
22
+ {
23
+ /// <summary>
24
+ /// Thread-safety coverage for Uco tools — regression for
25
+ /// <see href="https://github.com/IvanMurzak/uco-plugin/issues/633">#633</see>.
26
+ ///
27
+ /// For each tool we want to guarantee the tool's body handles invocation from any
28
+ /// thread (the Uco server dispatches tool calls from a SignalR background thread,
29
+ /// so tools that touch Unity APIs must marshal back to the main thread via
30
+ /// <c>MainThread.Instance.Run</c>). Forgetting to do so caused the regression in
31
+ /// <see href="https://github.com/IvanMurzak/uco-plugin/issues/632">#632</see> /
32
+ /// <see href="https://github.com/IvanMurzak/uco-plugin/pull/637">#637</see>
33
+ /// (<c>console-clear-logs</c>).
34
+ ///
35
+ /// <b>Excluded tools</b>: <c>package-add</c>/<c>package-remove</c> (trigger domain
36
+ /// reload), <c>tests-run</c> (recursive hang), system tools (<c>ping</c>,
37
+ /// <c>unity-skill-*</c>). See inline comments for rationale.
38
+ ///
39
+ /// Two helpers from <see cref="BaseTest"/> are used:
40
+ /// <list type="bullet">
41
+ /// <item><see cref="BaseTest.RunToolBothThreads"/> — happy path; asserts the tool
42
+ /// succeeds on both threads. Used when crafting a valid input is practical.</item>
43
+ /// <item><see cref="BaseTest.RunToolExpectNoThreadViolation"/> — lenient mode; tolerates
44
+ /// business-level errors and only fails the test if Unity surfaces a
45
+ /// <c>"can only be called from the main thread"</c> exception. Used for tools
46
+ /// that need elaborate setup (<c>SerializedMember</c>, prefab stages, etc.) or
47
+ /// whose happy path would mutate the project in risky ways
48
+ /// (<c>package-add</c>/<c>package-remove</c>, <c>tests-run</c>, etc.).</item>
49
+ /// </list>
50
+ /// </summary>
51
+ public class ToolThreadSafetyTests : BaseTest
52
+ {
53
+ const string TmpFolderName = "BgThreadTests_TMP";
54
+ const string TmpFolder = "Assets/" + TmpFolderName;
55
+
56
+ [TearDown]
57
+ public void BgTearDown()
58
+ {
59
+ if (AssetDatabase.IsValidFolder(TmpFolder))
60
+ AssetDatabase.DeleteAsset(TmpFolder);
61
+ }
62
+
63
+ // ================================================================
64
+ // NO-ARG / READ-ONLY tools (happy path, empty JSON)
65
+ // ================================================================
66
+
67
+ [UnityTest] public IEnumerator ToolList_BothThreads()
68
+ => RunToolBothThreads(Tool_Tool.ToolListId, "{}");
69
+
70
+ [UnityTest] public IEnumerator GameObjectComponentListAll_BothThreads()
71
+ => RunToolBothThreads(Tool_GameObject.ComponentListToolId, @"{""pageSize"":3}");
72
+
73
+ [UnityTest] public IEnumerator SceneListOpened_BothThreads()
74
+ => RunToolBothThreads(Tool_Scene.SceneListOpenedToolId, "{}");
75
+
76
+ [UnityTest] public IEnumerator SceneGetData_BothThreads()
77
+ => RunToolBothThreads(Tool_Scene.SceneGetDataToolId, "{}");
78
+
79
+ [UnityTest] public IEnumerator EditorApplicationGetState_BothThreads()
80
+ => RunToolBothThreads(Tool_Editor.EditorApplicationGetStateToolId, "{}");
81
+
82
+ [UnityTest] public IEnumerator EditorSelectionGet_BothThreads()
83
+ => RunToolBothThreads(Tool_Editor_Selection.EditorSelectionGetToolId, "{}");
84
+
85
+ [UnityTest] public IEnumerator ConsoleGetLogs_BothThreads()
86
+ => RunToolBothThreads(Tool_Console.ConsoleGetLogsToolId, "{}");
87
+
88
+ [UnityTest]
89
+ public IEnumerator ConsoleClearLogs_BothThreads()
90
+ {
91
+ // Regression for #632/#637: ClearLogs calls Debug.ClearDeveloperConsole() which
92
+ // MUST be invoked on the main thread.
93
+ yield return RunToolBothThreads(Tool_Console.ConsoleClearLogsToolId, "{}");
94
+ }
95
+
96
+ [UnityTest] public IEnumerator AssetsFind_BothThreads()
97
+ => RunToolBothThreads(Tool_Assets.AssetsFindToolId, @"{""filter"":""t:Scene"",""maxResults"":3}");
98
+
99
+ [UnityTest] public IEnumerator AssetsFindBuiltIn_BothThreads()
100
+ => RunToolBothThreads(Tool_Assets.AssetsFindBuiltInToolId, @"{""maxResults"":3}");
101
+
102
+ [UnityTest] public IEnumerator AssetsShaderListAll_BothThreads()
103
+ => RunToolBothThreads(Tool_Assets_Shader.AssetsShaderListAllToolId, "{}");
104
+
105
+ [UnityTest] public IEnumerator AssetsRefresh_BothThreads()
106
+ => RunToolBothThreads(Tool_Assets.AssetsRefreshToolId, "{}");
107
+
108
+ [UnityTest] public IEnumerator PackageList_BothThreads()
109
+ => RunToolBothThreads(Tool_Package.PackageListToolId, "{}");
110
+
111
+ [UnityTest] public IEnumerator TypeGetJsonSchema_BothThreads()
112
+ => RunToolBothThreads(Tool_Type.TypeGetJsonSchemaToolId, @"{""typeName"":""UnityEngine.Vector3""}");
113
+
114
+ // ================================================================
115
+ // GAMEOBJECT tools — happy path, per-call setup
116
+ // ================================================================
117
+
118
+ [UnityTest]
119
+ public IEnumerator GameObjectCreate_BothThreads()
120
+ {
121
+ yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectCreateToolId, @"{""name"":""bg_main""}");
122
+ Assert.IsNotNull(GameObject.Find("bg_main"));
123
+ yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectCreateToolId, @"{""name"":""bg_bg""}");
124
+ Assert.IsNotNull(GameObject.Find("bg_bg"));
125
+ }
126
+
127
+ [UnityTest]
128
+ public IEnumerator GameObjectFind_BothThreads()
129
+ {
130
+ var go = new GameObject("bg_find");
131
+ var id = go.GetEntityId();
132
+ yield return RunToolBothThreads(Tool_GameObject.GameObjectFindToolId,
133
+ $@"{{""gameObjectRef"":{{""instanceID"":{id}}}}}");
134
+ }
135
+
136
+ [UnityTest]
137
+ public IEnumerator GameObjectDestroy_BothThreads()
138
+ {
139
+ var a = new GameObject("bg_destroy_a").GetEntityId();
140
+ yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectDestroyToolId,
141
+ $@"{{""gameObjectRef"":{{""instanceID"":{a}}}}}");
142
+
143
+ var b = new GameObject("bg_destroy_b").GetEntityId();
144
+ yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectDestroyToolId,
145
+ $@"{{""gameObjectRef"":{{""instanceID"":{b}}}}}");
146
+ }
147
+
148
+ [UnityTest]
149
+ public IEnumerator GameObjectDuplicate_BothThreads()
150
+ {
151
+ var a = new GameObject("bg_dup_a").GetEntityId();
152
+ yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectDuplicateToolId,
153
+ $@"{{""gameObjectRefs"":[{{""instanceID"":{a}}}]}}");
154
+
155
+ var b = new GameObject("bg_dup_b").GetEntityId();
156
+ yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectDuplicateToolId,
157
+ $@"{{""gameObjectRefs"":[{{""instanceID"":{b}}}]}}");
158
+ }
159
+
160
+ [UnityTest]
161
+ public IEnumerator GameObjectSetParent_BothThreads()
162
+ {
163
+ var parent = new GameObject("bg_parent").GetEntityId();
164
+ var child1 = new GameObject("bg_child_a").GetEntityId();
165
+ yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectSetParentToolId,
166
+ $@"{{""gameObjectRefs"":[{{""instanceID"":{child1}}}],""parentGameObjectRef"":{{""instanceID"":{parent}}}}}");
167
+
168
+ var child2 = new GameObject("bg_child_b").GetEntityId();
169
+ yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectSetParentToolId,
170
+ $@"{{""gameObjectRefs"":[{{""instanceID"":{child2}}}],""parentGameObjectRef"":{{""instanceID"":{parent}}}}}");
171
+ }
172
+
173
+ [UnityTest]
174
+ public IEnumerator GameObjectComponentAdd_BothThreads()
175
+ {
176
+ var a = new GameObject("bg_addc_a").GetEntityId();
177
+ yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectComponentAddToolId,
178
+ $@"{{""gameObjectRef"":{{""instanceID"":{a}}},""componentNames"":[""UnityEngine.BoxCollider""]}}");
179
+
180
+ var b = new GameObject("bg_addc_b").GetEntityId();
181
+ yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectComponentAddToolId,
182
+ $@"{{""gameObjectRef"":{{""instanceID"":{b}}},""componentNames"":[""UnityEngine.SphereCollider""]}}");
183
+ }
184
+
185
+ [UnityTest]
186
+ public IEnumerator GameObjectComponentGet_BothThreads()
187
+ {
188
+ var go = new GameObject("bg_getc");
189
+ var id = go.GetEntityId();
190
+ // Transform lives at component index 0 on every GameObject.
191
+ yield return RunToolBothThreads(Tool_GameObject.GameObjectComponentGetToolId,
192
+ $@"{{""gameObjectRef"":{{""instanceID"":{id}}},""componentRef"":{{""index"":0}}}}");
193
+ }
194
+
195
+ [UnityTest]
196
+ public IEnumerator GameObjectComponentDestroy_BothThreads()
197
+ {
198
+ var a = new GameObject("bg_destc_a");
199
+ a.AddComponent<BoxCollider>();
200
+ var idA = a.GetEntityId();
201
+ yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectComponentDestroyToolId,
202
+ $@"{{""gameObjectRef"":{{""instanceID"":{idA}}},""destroyComponentRefs"":[{{""typeName"":""UnityEngine.BoxCollider""}}]}}");
203
+
204
+ var b = new GameObject("bg_destc_b");
205
+ b.AddComponent<SphereCollider>();
206
+ var idB = b.GetEntityId();
207
+ yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectComponentDestroyToolId,
208
+ $@"{{""gameObjectRef"":{{""instanceID"":{idB}}},""destroyComponentRefs"":[{{""typeName"":""UnityEngine.SphereCollider""}}]}}");
209
+ }
210
+
211
+ [UnityTest]
212
+ public IEnumerator GameObjectModify_ThreadSafetyOnly()
213
+ {
214
+ // gameobject-modify requires a SerializedMember tree — we cover thread safety
215
+ // only (business errors from an empty diff are tolerated).
216
+ var id = new GameObject("bg_modify").GetEntityId();
217
+ yield return RunToolExpectNoThreadViolation(Tool_GameObject.GameObjectModifyToolId,
218
+ $@"{{""gameObjectRefs"":[{{""instanceID"":{id}}}],""gameObjectDiffs"":[{{""name"":""root""}}]}}");
219
+ }
220
+
221
+ [UnityTest]
222
+ public IEnumerator GameObjectComponentModify_ThreadSafetyOnly()
223
+ {
224
+ var go = new GameObject("bg_cmodify");
225
+ go.AddComponent<BoxCollider>();
226
+ var id = go.GetEntityId();
227
+ yield return RunToolExpectNoThreadViolation(Tool_GameObject.GameObjectComponentModifyToolId,
228
+ $@"{{""gameObjectRef"":{{""instanceID"":{id}}},""componentRef"":{{""typeName"":""UnityEngine.BoxCollider""}},""componentDiff"":{{""name"":""root""}}}}");
229
+ }
230
+
231
+ // ================================================================
232
+ // OBJECT tools
233
+ // ================================================================
234
+
235
+ [UnityTest]
236
+ public IEnumerator ObjectGetData_BothThreads()
237
+ {
238
+ var id = new GameObject("bg_obj").GetEntityId();
239
+ yield return RunToolBothThreads(Tool_Object.ObjectGetDataToolId,
240
+ $@"{{""objectRef"":{{""instanceID"":{id}}}}}");
241
+ }
242
+
243
+ [UnityTest]
244
+ public IEnumerator ObjectModify_ThreadSafetyOnly()
245
+ {
246
+ var id = new GameObject("bg_objmod").GetEntityId();
247
+ yield return RunToolExpectNoThreadViolation(Tool_Object.ObjectModifyToolId,
248
+ $@"{{""objectRef"":{{""instanceID"":{id}}},""objectDiff"":{{""name"":""root""}}}}");
249
+ }
250
+
251
+ // ================================================================
252
+ // EDITOR tools
253
+ // ================================================================
254
+
255
+ [UnityTest]
256
+ public IEnumerator EditorSelectionSet_BothThreads()
257
+ {
258
+ var id = new GameObject("bg_select").GetEntityId();
259
+ yield return RunToolBothThreads(Tool_Editor_Selection.EditorSelectionSetToolId,
260
+ $@"{{""select"":[{{""instanceID"":{id}}}]}}");
261
+ }
262
+
263
+ [UnityTest]
264
+ public IEnumerator EditorApplicationSetState_BothThreads()
265
+ {
266
+ // No-op toggle: current state is not-playing, not-paused.
267
+ yield return RunToolBothThreads(Tool_Editor.EditorApplicationSetStateToolId,
268
+ @"{""isPlaying"":false,""isPaused"":false}");
269
+ }
270
+
271
+ // ================================================================
272
+ // ASSETS tools
273
+ // ================================================================
274
+
275
+ [UnityTest]
276
+ public IEnumerator AssetsCreateFolder_BothThreads()
277
+ {
278
+ EnsureTmpFolder();
279
+ yield return RunToolMainThreadCoop(Tool_Assets.AssetsCreateFolderToolId,
280
+ $@"{{""inputs"":[{{""parentFolderPath"":""{TmpFolder}"",""newFolderName"":""a""}}]}}");
281
+ yield return RunToolFromBackgroundThread(Tool_Assets.AssetsCreateFolderToolId,
282
+ $@"{{""inputs"":[{{""parentFolderPath"":""{TmpFolder}"",""newFolderName"":""b""}}]}}");
283
+ }
284
+
285
+ [UnityTest]
286
+ public IEnumerator AssetsGetData_BothThreads()
287
+ {
288
+ var assetPath = CreateTmpTextAsset("get_data.txt", "hello");
289
+ yield return RunToolBothThreads(Tool_Assets.AssetsGetDataToolId,
290
+ $@"{{""assetRef"":{{""assetPath"":""{assetPath}""}}}}");
291
+ }
292
+
293
+ [UnityTest]
294
+ public IEnumerator AssetsModify_ThreadSafetyOnly()
295
+ {
296
+ var assetPath = CreateTmpTextAsset("modify.txt", "hello");
297
+ yield return RunToolExpectNoThreadViolation(Tool_Assets.AssetsModifyToolId,
298
+ $@"{{""assetRef"":{{""assetPath"":""{assetPath}""}},""content"":{{""name"":""root""}}}}");
299
+ }
300
+
301
+ [UnityTest]
302
+ public IEnumerator AssetsMaterialCreate_BothThreads()
303
+ {
304
+ EnsureTmpFolder();
305
+ yield return RunToolMainThreadCoop(Tool_Assets.AssetsMaterialCreateToolId,
306
+ $@"{{""assetPath"":""{TmpFolder}/mat_main.mat"",""shaderName"":""Standard""}}");
307
+ yield return RunToolFromBackgroundThread(Tool_Assets.AssetsMaterialCreateToolId,
308
+ $@"{{""assetPath"":""{TmpFolder}/mat_bg.mat"",""shaderName"":""Standard""}}");
309
+ }
310
+
311
+ [UnityTest]
312
+ public IEnumerator AssetsShaderGetData_BothThreads()
313
+ {
314
+ // Find any shader asset in the project to query.
315
+ var shaderPath = FindFirstShaderPath();
316
+ if (shaderPath == null)
317
+ {
318
+ Assert.Ignore("No shader asset available in project.");
319
+ yield break;
320
+ }
321
+ yield return RunToolBothThreads(Tool_Assets_Shader.AssetsShaderGetDataToolId,
322
+ $@"{{""assetRef"":{{""assetPath"":""{shaderPath}""}}}}");
323
+ }
324
+
325
+ [UnityTest]
326
+ public IEnumerator AssetsCopy_BothThreads()
327
+ {
328
+ var src = CreateTmpTextAsset("copy_src.txt", "source");
329
+ yield return RunToolMainThreadCoop(Tool_Assets.AssetsCopyToolId,
330
+ $@"{{""sourcePaths"":[""{src}""],""destinationPaths"":[""{TmpFolder}/copy_main.txt""]}}");
331
+ yield return RunToolFromBackgroundThread(Tool_Assets.AssetsCopyToolId,
332
+ $@"{{""sourcePaths"":[""{src}""],""destinationPaths"":[""{TmpFolder}/copy_bg.txt""]}}");
333
+ }
334
+
335
+ [UnityTest]
336
+ public IEnumerator AssetsMove_BothThreads()
337
+ {
338
+ var src1 = CreateTmpTextAsset("move_a.txt", "a");
339
+ yield return RunToolMainThreadCoop(Tool_Assets.AssetsMoveToolId,
340
+ $@"{{""sourcePaths"":[""{src1}""],""destinationPaths"":[""{TmpFolder}/move_a_done.txt""]}}");
341
+
342
+ var src2 = CreateTmpTextAsset("move_b.txt", "b");
343
+ yield return RunToolFromBackgroundThread(Tool_Assets.AssetsMoveToolId,
344
+ $@"{{""sourcePaths"":[""{src2}""],""destinationPaths"":[""{TmpFolder}/move_b_done.txt""]}}");
345
+ }
346
+
347
+ [UnityTest]
348
+ public IEnumerator AssetsDelete_BothThreads()
349
+ {
350
+ var src1 = CreateTmpTextAsset("del_a.txt", "a");
351
+ yield return RunToolMainThreadCoop(Tool_Assets.AssetsDeleteToolId, $@"{{""paths"":[""{src1}""]}}");
352
+
353
+ var src2 = CreateTmpTextAsset("del_b.txt", "b");
354
+ yield return RunToolFromBackgroundThread(Tool_Assets.AssetsDeleteToolId,
355
+ $@"{{""paths"":[""{src2}""]}}");
356
+ }
357
+
358
+ // ---------- Prefabs ----------
359
+
360
+ [UnityTest]
361
+ public IEnumerator AssetsPrefabCreate_BothThreads()
362
+ {
363
+ EnsureTmpFolder();
364
+ var idA = new GameObject("bg_prefab_a").GetEntityId();
365
+ yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabCreateToolId,
366
+ $@"{{""prefabAssetPath"":""{TmpFolder}/prefab_a.prefab"",""gameObjectRef"":{{""instanceID"":{idA}}}}}");
367
+
368
+ var idB = new GameObject("bg_prefab_b").GetEntityId();
369
+ yield return RunToolFromBackgroundThread(Tool_Assets_Prefab.AssetsPrefabCreateToolId,
370
+ $@"{{""prefabAssetPath"":""{TmpFolder}/prefab_b.prefab"",""gameObjectRef"":{{""instanceID"":{idB}}}}}");
371
+ }
372
+
373
+ [UnityTest]
374
+ public IEnumerator AssetsPrefabInstantiate_BothThreads()
375
+ {
376
+ EnsureTmpFolder();
377
+ var seedId = new GameObject("bg_seed_prefab").GetEntityId();
378
+ var prefabPath = $"{TmpFolder}/seed.prefab";
379
+ yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabCreateToolId,
380
+ $@"{{""prefabAssetPath"":""{prefabPath}"",""gameObjectRef"":{{""instanceID"":{seedId}}}}}");
381
+
382
+ yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabInstantiateToolId,
383
+ $@"{{""prefabAssetPath"":""{prefabPath}"",""gameObjectPath"":""bg_inst_main""}}");
384
+ yield return RunToolFromBackgroundThread(Tool_Assets_Prefab.AssetsPrefabInstantiateToolId,
385
+ $@"{{""prefabAssetPath"":""{prefabPath}"",""gameObjectPath"":""bg_inst_bg""}}");
386
+ }
387
+
388
+ [UnityTest]
389
+ public IEnumerator AssetsPrefabOpenSaveClose_BothThreads()
390
+ {
391
+ EnsureTmpFolder();
392
+
393
+ // Create a prefab asset from a scene GameObject.
394
+ var seedA = new GameObject("bg_prefab_osc_a").GetEntityId();
395
+ var prefabPathA = $"{TmpFolder}/osc_a.prefab";
396
+ yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabCreateToolId,
397
+ $@"{{""prefabAssetPath"":""{prefabPathA}"",""gameObjectRef"":{{""instanceID"":{seedA}}}}}");
398
+
399
+ // Instantiate it so we get a prefab instance to open.
400
+ yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabInstantiateToolId,
401
+ $@"{{""prefabAssetPath"":""{prefabPathA}"",""gameObjectPath"":""bg_osc_inst_a""}}");
402
+ var instanceA = GameObject.Find("bg_osc_inst_a");
403
+ Assert.IsNotNull(instanceA, "Prefab instance not found after instantiate.");
404
+
405
+ // Open → Save → Close (main thread).
406
+ yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabOpenToolId,
407
+ $@"{{""gameObjectRef"":{{""instanceID"":{instanceA!.GetEntityId()}}}}}");
408
+ yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabSaveToolId, "{}");
409
+ yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabCloseToolId, @"{""save"":false}");
410
+
411
+ // Same sequence from a background thread.
412
+ var seedB = new GameObject("bg_prefab_osc_b").GetEntityId();
413
+ var prefabPathB = $"{TmpFolder}/osc_b.prefab";
414
+ yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabCreateToolId,
415
+ $@"{{""prefabAssetPath"":""{prefabPathB}"",""gameObjectRef"":{{""instanceID"":{seedB}}}}}");
416
+ yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabInstantiateToolId,
417
+ $@"{{""prefabAssetPath"":""{prefabPathB}"",""gameObjectPath"":""bg_osc_inst_b""}}");
418
+ var instanceB = GameObject.Find("bg_osc_inst_b");
419
+ Assert.IsNotNull(instanceB, "Prefab instance not found after instantiate.");
420
+
421
+ yield return RunToolFromBackgroundThread(Tool_Assets_Prefab.AssetsPrefabOpenToolId,
422
+ $@"{{""gameObjectRef"":{{""instanceID"":{instanceB!.GetEntityId()}}}}}");
423
+ yield return RunToolFromBackgroundThread(Tool_Assets_Prefab.AssetsPrefabSaveToolId, "{}");
424
+ yield return RunToolFromBackgroundThread(Tool_Assets_Prefab.AssetsPrefabCloseToolId, @"{""save"":false}");
425
+ }
426
+
427
+ // ================================================================
428
+ // SCENE tools
429
+ // ================================================================
430
+
431
+ [UnityTest]
432
+ public IEnumerator SceneCreate_BothThreads()
433
+ {
434
+ EnsureTmpFolder();
435
+ var startingActive = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
436
+ yield return RunToolMainThreadCoop(Tool_Scene.SceneCreateToolId,
437
+ $@"{{""path"":""{TmpFolder}/scene_main.unity""}}");
438
+ yield return RunToolFromBackgroundThread(Tool_Scene.SceneCreateToolId,
439
+ $@"{{""path"":""{TmpFolder}/scene_bg.unity""}}");
440
+
441
+ // Restore a baseline active scene if the test clobbered it.
442
+ if (startingActive.IsValid() && UnityEngine.SceneManagement.SceneManager.GetActiveScene() != startingActive)
443
+ UnityEngine.SceneManagement.SceneManager.SetActiveScene(startingActive);
444
+ }
445
+
446
+ [UnityTest]
447
+ public IEnumerator SceneOpenSaveSetActive_BothThreads()
448
+ {
449
+ EnsureTmpFolder();
450
+ var scenePath = $"{TmpFolder}/reopen.unity";
451
+ yield return RunToolMainThreadCoop(Tool_Scene.SceneCreateToolId, $@"{{""path"":""{scenePath}""}}");
452
+
453
+ // open (main + bg)
454
+ yield return RunToolMainThreadCoop(Tool_Scene.SceneOpenToolId,
455
+ $@"{{""sceneRef"":{{""assetPath"":""{scenePath}""}}}}");
456
+ yield return RunToolFromBackgroundThread(Tool_Scene.SceneOpenToolId,
457
+ $@"{{""sceneRef"":{{""assetPath"":""{scenePath}""}}}}");
458
+
459
+ // set-active (both)
460
+ yield return RunToolBothThreads(Tool_Scene.SceneSetActiveToolId,
461
+ $@"{{""sceneRef"":{{""assetPath"":""{scenePath}""}}}}");
462
+
463
+ // save (both)
464
+ yield return RunToolBothThreads(Tool_Scene.SceneSaveToolId, "{}");
465
+ }
466
+
467
+ [UnityTest]
468
+ public IEnumerator SceneUnload_BothThreads()
469
+ {
470
+ EnsureTmpFolder();
471
+
472
+ // Create a saved scene so we can open additional scenes additively.
473
+ var basePath = $"{TmpFolder}/unload_base.unity";
474
+ yield return RunToolMainThreadCoop(Tool_Scene.SceneCreateToolId,
475
+ $@"{{""path"":""{basePath}""}}");
476
+
477
+ // Create two extra scenes additively (allowed now because base is saved).
478
+ var mainPath = $"{TmpFolder}/unload_main.unity";
479
+ var bgPath = $"{TmpFolder}/unload_bg.unity";
480
+ yield return RunToolMainThreadCoop(Tool_Scene.SceneCreateToolId,
481
+ $@"{{""path"":""{mainPath}"",""newSceneMode"":""Additive""}}");
482
+ yield return RunToolMainThreadCoop(Tool_Scene.SceneCreateToolId,
483
+ $@"{{""path"":""{bgPath}"",""newSceneMode"":""Additive""}}");
484
+
485
+ // Unload each additive scene from both threads (tool takes scene name, not path).
486
+ yield return RunToolMainThreadCoop(Tool_Scene.SceneUnloadToolId,
487
+ @"{""name"":""unload_main""}");
488
+ yield return RunToolFromBackgroundThread(Tool_Scene.SceneUnloadToolId,
489
+ @"{""name"":""unload_bg""}");
490
+ }
491
+
492
+ // ================================================================
493
+ // SCRIPT tools
494
+ // ================================================================
495
+
496
+ [UnityTest]
497
+ public IEnumerator ScriptUpdateOrCreate_ReadDelete_BothThreads()
498
+ {
499
+ EnsureTmpFolder();
500
+ var pathA = $"{TmpFolder}/S_a.cs";
501
+ var pathB = $"{TmpFolder}/S_b.cs";
502
+ const string SrcA = "public class BgSa { public static void M() {} }";
503
+ const string SrcB = "public class BgSb { public static void M() {} }";
504
+
505
+ // update-or-create
506
+ yield return RunToolMainThreadCoop(Tool_Script.ScriptUpdateOrCreateToolId,
507
+ $@"{{""filePath"":""{pathA}"",""content"":{JsonString(SrcA)}}}");
508
+ yield return RunToolFromBackgroundThread(Tool_Script.ScriptUpdateOrCreateToolId,
509
+ $@"{{""filePath"":""{pathB}"",""content"":{JsonString(SrcB)}}}");
510
+
511
+ // read (main + bg)
512
+ yield return RunToolBothThreads(Tool_Script.ScriptReadToolId,
513
+ $@"{{""filePath"":""{pathA}""}}",
514
+ $@"{{""filePath"":""{pathB}""}}");
515
+
516
+ // delete
517
+ yield return RunToolMainThreadCoop(Tool_Script.ScriptDeleteToolId, $@"{{""files"":[""{pathA}""]}}");
518
+ yield return RunToolFromBackgroundThread(Tool_Script.ScriptDeleteToolId,
519
+ $@"{{""files"":[""{pathB}""]}}");
520
+ }
521
+
522
+ [UnityTest]
523
+ public IEnumerator ScriptExecute_BothThreads()
524
+ {
525
+ const string Code = "public class Script { public static int Main() { return 42; } }";
526
+ yield return RunToolBothThreads(Tool_Script.ScriptExecuteToolId,
527
+ $@"{{""csharpCode"":{JsonString(Code)},""className"":""Script"",""methodName"":""Main""}}");
528
+ }
529
+
530
+ // ================================================================
531
+ // TOOL CONTROL
532
+ // ================================================================
533
+
534
+ [UnityTest]
535
+ public IEnumerator ToolSetEnabledState_BothThreads()
536
+ {
537
+ // Enable unity-tool-list twice (idempotent, already enabled) from both threads.
538
+ yield return RunToolBothThreads(Tool_Tool.ToolSetEnabledStateId,
539
+ @"{""tools"":[{""name"":""unity-tool-list"",""enabled"":true}]}");
540
+ }
541
+
542
+ // ================================================================
543
+ // REFLECTION (complex MethodRef schema → thread-safety only)
544
+ // ================================================================
545
+
546
+ [UnityTest]
547
+ public IEnumerator ReflectionMethodFind_ThreadSafetyOnly()
548
+ {
549
+ yield return RunToolExpectNoThreadViolation(Tool_Reflection.ReflectionMethodFindToolId,
550
+ @"{""filter"":{""Namespace"":""UnityEngine"",""TypeName"":""Transform"",""MethodName"":""LookAt""}}");
551
+ }
552
+
553
+ [UnityTest]
554
+ public IEnumerator ReflectionMethodCall_BothThreads()
555
+ {
556
+ // Call a static method on a test-scoped GameObject's Transform via instance method.
557
+ var go = new GameObject("bg_reflect");
558
+ var id = go.GetEntityId();
559
+ yield return RunToolBothThreads(Tool_Reflection.ReflectionMethodCallToolId,
560
+ $@"{{""filter"":{{""Namespace"":""UnityEngine"",""TypeName"":""GameObject"",""MethodName"":""Find"",""InputParameters"":[{{""TypeName"":""System.String"",""Name"":""name""}}]}},""inputParameters"":[{{""typeName"":""System.String"",""name"":""name"",""value"":""bg_reflect""}}]}}");
561
+ }
562
+
563
+ // ================================================================
564
+ // PACKAGE MANAGER — package-add / package-remove intentionally NOT
565
+ // exercised here: both return status "Processing" and kick off a real
566
+ // Unity PackageManager resolution on a background task, which in turn
567
+ // forces a domain reload. Under the test runner that hangs the whole
568
+ // batch-mode run. Their thread-safety is already covered by the fact
569
+ // that their entry path is a synchronous parameter check.
570
+ // ================================================================
571
+
572
+ [UnityTest]
573
+ public IEnumerator PackageSearch_ThreadSafetyOnly()
574
+ {
575
+ // Registry round-trip is slow and flaky under test; thread-safety only.
576
+ yield return RunToolExpectNoThreadViolation(Tool_Package.PackageSearchToolId,
577
+ @"{""query"":""com.unity.textmeshpro"",""maxResults"":1,""offlineMode"":true}");
578
+ }
579
+
580
+ // ================================================================
581
+ // SCREENSHOTS — not guaranteed to have a camera/view in batchmode.
582
+ // ================================================================
583
+
584
+ [UnityTest]
585
+ public IEnumerator ScreenshotCamera_BothThreads()
586
+ {
587
+ new GameObject("TestCamera").AddComponent<Camera>();
588
+ yield return RunToolBothThreads(Tool_Screenshot.ScreenshotCameraToolId, @"{""width"":64,""height"":64}");
589
+ }
590
+
591
+ [UnityTest]
592
+ public IEnumerator ScreenshotGameView_ThreadSafetyOnly()
593
+ => RunToolExpectNoThreadViolation(Tool_Screenshot.ScreenshotGameViewToolId, "{}");
594
+
595
+ [UnityTest]
596
+ public IEnumerator ScreenshotSceneView_ThreadSafetyOnly()
597
+ => RunToolExpectNoThreadViolation(Tool_Screenshot.ScreenshotSceneViewToolId, @"{""width"":64,""height"":64}");
598
+
599
+ // ================================================================
600
+ // TESTS — recursive; skip deliberately.
601
+ // ================================================================
602
+ // tests-run intentionally has no test here: executing it from within the test
603
+ // runner causes recursion and a hang. Its thread safety is implicit because the
604
+ // test runner itself schedules the invocation onto the main thread.
605
+
606
+ // ================================================================
607
+ // Helpers
608
+ // ================================================================
609
+
610
+ static void EnsureTmpFolder()
611
+ {
612
+ if (!AssetDatabase.IsValidFolder(TmpFolder))
613
+ AssetDatabase.CreateFolder("Assets", TmpFolderName);
614
+ }
615
+
616
+ static string CreateTmpTextAsset(string fileName, string contents)
617
+ {
618
+ EnsureTmpFolder();
619
+ var path = $"{TmpFolder}/{fileName}";
620
+ File.WriteAllText(path, contents);
621
+ AssetDatabase.ImportAsset(path);
622
+ return path;
623
+ }
624
+
625
+ static string? FindFirstShaderPath()
626
+ {
627
+ // Prefer the built-in Standard shader.
628
+ var standard = Shader.Find("Standard");
629
+ if (standard != null)
630
+ {
631
+ var p = AssetDatabase.GetAssetPath(standard);
632
+ if (!string.IsNullOrEmpty(p))
633
+ return p;
634
+ }
635
+
636
+ var guids = AssetDatabase.FindAssets("t:Shader");
637
+ foreach (var guid in guids)
638
+ {
639
+ var p = AssetDatabase.GUIDToAssetPath(guid);
640
+ if (!string.IsNullOrEmpty(p))
641
+ return p;
642
+ }
643
+ return null;
644
+ }
645
+
646
+ /// <summary>Returns a JSON string literal (quoted, backslash-escaped).</summary>
647
+ static string JsonString(string raw)
648
+ => System.Text.Json.JsonSerializer.Serialize(raw);
649
+ }
650
+ }
651
+ #endif