@atelierai/uco 1.0.7 → 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 (87) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/package.json +1 -1
  3. package/vendor/plugin/com.atelierai.unity.copilot/Editor/DependencyResolver/NuGetConfig.cs +132 -132
  4. package/vendor/plugin/com.atelierai.unity.copilot/Editor/DependencyResolver/NuGetDependencyResolver.cs +237 -237
  5. package/vendor/plugin/com.atelierai.unity.copilot/Editor/DependencyResolver/NuGetDownloader.cs +82 -82
  6. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Resource/MenuItems.cs +247 -247
  7. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Resource/ToolGroups.cs +153 -153
  8. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/SystemTool/Skills.Create.SkillBody.cs +155 -155
  9. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Console.GetLogs.cs +95 -95
  10. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Docs.HttpFetcher.cs +139 -139
  11. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Instance.ListAll.cs +72 -72
  12. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/PathReadHelper.cs +106 -106
  13. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Script.Validate.cs +252 -252
  14. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Tests/TestResultCollector.cs +537 -537
  15. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Tests.cs +400 -400
  16. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Branding/ProductInfo.cs +62 -62
  17. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/CopilotServerManager.cs +1034 -1034
  18. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Skills/Skill_InitialSetup.cs +197 -197
  19. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Startup.Editor.cs +201 -201
  20. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Startup.cs +67 -67
  21. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/CopilotListWindowBase.cs +393 -393
  22. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/CopilotWindowBase.cs +113 -113
  23. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.Connection.cs +3 -3
  24. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.CopilotFeatures.cs +195 -195
  25. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.CopilotServer.cs +20 -20
  26. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.CreateGUI.cs +354 -354
  27. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.cs +78 -78
  28. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowInitializer.cs +47 -47
  29. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/SerializationCheckWindow.cs +190 -190
  30. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.Build.cs +91 -91
  31. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.Config.cs +298 -298
  32. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.Static.cs +296 -296
  33. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.cs +69 -69
  34. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/PackageUtils.cs +168 -168
  35. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/PrefsKeyMigration.cs +119 -119
  36. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/SafeDefaultsGuard.cs +180 -180
  37. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/ScriptUtils.cs +229 -229
  38. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/ToolsManifestGenerator.cs +200 -200
  39. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/UnityAuthoringAdapters.cs +1467 -1467
  40. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/UnityAuthoringSafetyReloadGuard.cs +65 -65
  41. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/UnityCopilotUpdateProjectSettings.cs +66 -66
  42. package/vendor/plugin/com.atelierai.unity.copilot/Plugins/ReflectorNet.dll +0 -0
  43. package/vendor/plugin/com.atelierai.unity.copilot/Plugins/Uco.Framework.Common.dll +0 -0
  44. package/vendor/plugin/com.atelierai.unity.copilot/Plugins/Uco.Framework.dll +0 -0
  45. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Converter/Json/Structures/EntityIdConverter.cs +120 -120
  46. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Data/GameObjectMetadata.cs +110 -110
  47. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Data/GameObjectMetadata.pre-Unity.6.5.cs +110 -110
  48. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Unity/Logs/FileLogStorage.cs +547 -547
  49. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.Build.cs +243 -243
  50. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.Config.cs +3 -3
  51. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.cs +295 -295
  52. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPluginBuilder.cs +105 -105
  53. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPluginRuntime.Static.cs +155 -155
  54. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPluginRuntime.cs +59 -59
  55. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Utils/EnvironmentUtils.cs +11 -11
  56. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/AtomicApi/AtomicApiTestFixtures.cs +99 -99
  57. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/AtomicApi/TryModifyAtTests.cs +227 -227
  58. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/AuthoringSafetyPilotTests.cs +1050 -1050
  59. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/DependencyResolver/NuGetLegacyMigrationTests.cs +304 -304
  60. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Environment/EnvironmentUtilsTests.cs +1 -1
  61. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/BlacklistTypeTests.cs +416 -416
  62. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataFieldScriptableObjectPopulationTests.cs +172 -172
  63. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataFieldScriptableObjectPopulationTests.pre-Unity.6.5.cs +169 -169
  64. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPopulationTests.cs +182 -182
  65. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPopulationTests.pre-Unity.6.5.cs +179 -179
  66. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPropertyPopulationTests.cs +175 -175
  67. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPropertyPopulationTests.pre-Unity.6.5.cs +174 -174
  68. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPropertyScriptableObjectPopulationTests.cs +171 -171
  69. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPropertyScriptableObjectPopulationTests.pre-Unity.6.5.cs +170 -170
  70. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsCreateFolderTests.cs +243 -243
  71. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsMaterialTests.cs +101 -101
  72. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsPrefabCreateTests.cs +348 -348
  73. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsPrefabCreateTests.pre-Unity.6.5.cs +348 -348
  74. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsShaderTests.cs +194 -194
  75. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/PathBasedToolTests.cs +573 -573
  76. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/PathBasedToolTests.pre-Unity.6.5.cs +513 -513
  77. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Screenshot/ScreenshotCameraTests.cs +154 -154
  78. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Screenshot/ScreenshotCameraTests.pre-Unity.6.5.cs +154 -154
  79. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Screenshot/ScreenshotGameViewTests.cs +164 -164
  80. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Screenshot/ScreenshotSceneViewTests.cs +155 -155
  81. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/ToolParameterTests.cs +56 -56
  82. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/ToolThreadSafetyTests.cs +651 -651
  83. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/ToolThreadSafetyTests.pre-Unity.6.5.cs +651 -651
  84. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ToolBatchExecutionIntegrationTests.cs +407 -407
  85. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/{McpPluginTests.cs → UcoPluginTests.cs} +105 -105
  86. package/vendor/plugin/com.atelierai.unity.copilot/package.json +1 -1
  87. /package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/{McpPluginTests.cs.meta → UcoPluginTests.cs.meta} +0 -0
@@ -1,298 +1,298 @@
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
- using System;
13
- using System.IO;
14
- using System.Linq;
15
- using System.Text.Json;
16
- using System.Text.Json.Serialization;
17
- using com.AtelierAI.Unity.Copilot.Runtime.Utils;
18
- using Microsoft.Extensions.Logging;
19
- using UnityEngine;
20
-
21
- namespace com.AtelierAI.Unity.Copilot
22
- {
23
- public partial class UnityCopilotPluginEditor
24
- {
25
- public static string ResourcesFileName => "uco-config";
26
-
27
- /// <summary>
28
- /// Pre-rename filename (uco 0.3.0 / plugin 0.76.0 renamed
29
- /// AI-Game-Developer-Config.json to uco-config.json). When only the
30
- /// legacy file exists it is read and then migrated: saved under the
31
- /// new name and the legacy file removed.
32
- /// </summary>
33
- public static string LegacyResourcesFileName => "AI-Game-Developer-Config";
34
-
35
- /// <summary>True when only the legacy-named config exists right now.</summary>
36
- static bool LegacyConfigNeedsMigration
37
- {
38
- get
39
- {
40
- var newPath = Path.GetFullPath(Path.Combine(ProjectRootPath, $"UserSettings/{ResourcesFileName}.json"));
41
- var legacyPath = Path.GetFullPath(Path.Combine(ProjectRootPath, $"UserSettings/{LegacyResourcesFileName}.json"));
42
- return !File.Exists(newPath) && File.Exists(legacyPath);
43
- }
44
- }
45
-
46
- /// <summary>Project-relative path of the config actually present on disk.</summary>
47
- public static string EffectiveAssetsFilePath
48
- {
49
- get
50
- {
51
- var newPath = $"UserSettings/{ResourcesFileName}.json";
52
- var legacyPath = $"UserSettings/{LegacyResourcesFileName}.json";
53
- if (File.Exists(Path.GetFullPath(Path.Combine(ProjectRootPath, newPath))))
54
- return newPath;
55
- return File.Exists(Path.GetFullPath(Path.Combine(ProjectRootPath, legacyPath)))
56
- ? legacyPath
57
- : newPath;
58
- }
59
- }
60
-
61
- /// <summary>
62
- /// One-shot rename migration: after a config was loaded from the
63
- /// legacy filename, persist it under the new name and delete the old
64
- /// file so the project converges on UserSettings/uco-config.json.
65
- /// </summary>
66
- static void MigrateLegacyConfigFileIfNeeded()
67
- {
68
- try
69
- {
70
- if (!LegacyConfigNeedsMigration)
71
- return;
72
- var legacyPath = Path.GetFullPath(Path.Combine(ProjectRootPath, $"UserSettings/{LegacyResourcesFileName}.json"));
73
- var newPath = Path.GetFullPath(Path.Combine(ProjectRootPath, $"UserSettings/{ResourcesFileName}.json"));
74
- File.Copy(legacyPath, newPath, overwrite: false);
75
- File.Delete(legacyPath);
76
- _logger.LogWarning("{method}: migrated {legacy} to {new} (uco rename); the old file was removed.",
77
- nameof(MigrateLegacyConfigFileIfNeeded), LegacyResourcesFileName, ResourcesFileName);
78
- }
79
- catch (Exception e)
80
- {
81
- _logger.LogWarning(e, "{method}: legacy config migration failed; falling back to the legacy name for this session.",
82
- nameof(MigrateLegacyConfigFileIfNeeded));
83
- }
84
- }
85
-
86
- /// <summary>
87
- /// Project-relative path used by Unity's AssetDatabase API.
88
- /// Do NOT use this with System.IO File/Directory operations — use <see cref="AssetsFileAbsolutePath"/> instead.
89
- /// </summary>
90
- public static string AssetsFilePath => $"UserSettings/{ResourcesFileName}.json";
91
-
92
- /// <summary>
93
- /// Gets the Unity project root folder path (the folder that contains the Assets directory).
94
- /// Uses <see cref="Application.dataPath"/> to ensure correctness regardless of the process working directory.
95
- /// </summary>
96
- public static string ProjectRootPath => Path.GetDirectoryName(Application.dataPath)!;
97
-
98
- /// <summary>
99
- /// Absolute path to the config file for use with System.IO File/Directory operations.
100
- /// Built from <see cref="ProjectRootPath"/> and <see cref="AssetsFilePath"/> to keep paths consistent.
101
- /// </summary>
102
- public static string AssetsFileAbsolutePath => Path.GetFullPath(Path.Combine(ProjectRootPath, AssetsFilePath));
103
-
104
-
105
- /// <summary>
106
- /// Resets the config file to its default state.
107
- /// </summary>
108
- public static void ResetConfig()
109
- {
110
- Instance.unityConnectionConfig.SetDefault();
111
- var plugin = CurrentPlugin;
112
- if (plugin != null)
113
- Instance.ApplyConfigToMcpPlugin(plugin);
114
- Instance.Save(captureCurrentToolStates: false);
115
- }
116
-
117
- #if UNITY_EDITOR
118
- public static UnityEngine.TextAsset AssetFile => UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.TextAsset>(EffectiveAssetsFilePath);
119
- #endif
120
-
121
- UnityConnectionConfig GetOrCreateConfig() => GetOrCreateConfig(out _);
122
- UnityConnectionConfig GetOrCreateConfig(out bool wasCreated)
123
- {
124
- wasCreated = false;
125
- try
126
- {
127
- // Both Edit mode and Play mode read from the same UserSettings JSON file
128
- // Use the absolute path so File.Exists/ReadAllText resolve correctly regardless of CWD.
129
- // The effective path prefers the renamed uco-config.json and falls back to
130
- // the legacy AI-Game-Developer-Config.json until the one-shot migration runs.
131
- var effectiveAbsolute = Path.GetFullPath(Path.Combine(ProjectRootPath, EffectiveAssetsFilePath));
132
- var json = File.Exists(effectiveAbsolute) ? File.ReadAllText(effectiveAbsolute) : null;
133
- if (!string.IsNullOrWhiteSpace(json))
134
- MigrateLegacyConfigFileIfNeeded();
135
-
136
- UnityConnectionConfig? config = null;
137
- try
138
- {
139
- config = string.IsNullOrWhiteSpace(json)
140
- ? null
141
- : JsonSerializer.Deserialize<UnityConnectionConfig>(
142
- // Legacy 1.0.2-and-earlier configs may carry the removed Cloud
143
- // connection mode; JsonStringEnumConverter would throw on it.
144
- json!.Replace("\"connectionMode\": \"Cloud\"", "\"connectionMode\": \"Custom\"")
145
- .Replace("\"ConnectionMode\": \"Cloud\"", "\"ConnectionMode\": \"Custom\""),
146
- new JsonSerializerOptions
147
- {
148
- PropertyNameCaseInsensitive = true,
149
- Converters = { new JsonStringEnumConverter() }
150
- });
151
- }
152
- catch (Exception e)
153
- {
154
- _logger.LogCritical(e, "{method}: <color=red><b>{file}</b> file is corrupted at <i>{path}</i></color>",
155
- nameof(GetOrCreateConfig), ResourcesFileName, AssetsFilePath);
156
- }
157
- // Track whether the config originated from a deserialized disk file. The
158
- // safe-defaults migration below only applies to configs that pre-existed on
159
- // disk — a freshly created config already starts with the safe defaults.
160
- var loadedFromDisk = config != null;
161
- if (config == null)
162
- {
163
- _logger.LogWarning("{method}: <color=orange><b>Creating {file}</b> file at <i>{path}</i></color>",
164
- nameof(GetOrCreateConfig), ResourcesFileName, AssetsFilePath);
165
- config = new UnityConnectionConfig();
166
- wasCreated = true;
167
- }
168
- if (string.IsNullOrEmpty(config.LocalToken))
169
- {
170
- config.LocalToken = GenerateToken();
171
- wasCreated = true;
172
- }
173
-
174
- // ── Safe-defaults (fail-closed) one-shot migration ─────────────────────────
175
- //
176
- // Newly created configs default AllowLanBind/AllowInsecureRemoteHttp to false so
177
- // a fresh project never starts the server on 0.0.0.0 without explicit consent.
178
- // However, projects that EXISTED before this change may already have LocalHost set
179
- // to a non-loopback URL (e.g. 0.0.0.0 or a LAN IP) on purpose. Silently dropping
180
- // their server start would be a regression. Detect that case here and grant the
181
- // opt-in once, with a one-time LogWarning so the user knows it happened and can
182
- // turn it back off if they did not intend it.
183
- //
184
- // Triggers ONLY when:
185
- // • The config was actually loaded from disk (loadedFromDisk == true)
186
- // • LocalHost parses as a URL with a non-loopback host
187
- // • AllowLanBind is currently false (default)
188
- // The migration sets AllowLanBind = true and flags `wasCreated` so the Save() at
189
- // the end of construction persists the new flag immediately. AllowInsecureRemoteHttp
190
- // is NOT auto-granted — it gates the configurators (which are advisory) and only
191
- // affects emitted client configs, not server start, so a one-time warning at start
192
- // is sufficient there.
193
- if (loadedFromDisk && !config.AllowLanBind && !string.IsNullOrEmpty(config.LocalHost))
194
- {
195
- if (com.AtelierAI.Unity.Copilot.Editor.Utils.SafeDefaultsGuard.IsNonLoopbackUrl(config.LocalHost))
196
- {
197
- _logger.LogWarning(
198
- "{method}: <color=orange>Detected pre-existing LAN/all-interfaces LocalHost ({host}) without AllowLanBind. " +
199
- "Granting AllowLanBind=true once to preserve current behaviour. " +
200
- "Open the Game Developer window and turn it off if you did not intend to expose the server to the network.</color>",
201
- nameof(GetOrCreateConfig), config.LocalHost);
202
- config.AllowLanBind = true;
203
- wasCreated = true; // force Save() so the migration persists.
204
- }
205
- }
206
-
207
- return config;
208
- }
209
- catch (Exception e)
210
- {
211
- _logger.LogCritical(e, "{method}: <color=red><b>{file}</b> file can't be loaded from <i>{path}</i></color>",
212
- nameof(GetOrCreateConfig), ResourcesFileName, AssetsFilePath);
213
- throw;
214
- }
215
- }
216
-
217
- public void Save(bool captureCurrentToolStates = true)
218
- {
219
- #if UNITY_EDITOR
220
- Validate();
221
- try
222
- {
223
- var directory = Path.GetDirectoryName(AssetsFileAbsolutePath);
224
- if (!Directory.Exists(directory))
225
- Directory.CreateDirectory(directory!);
226
-
227
- unityConnectionConfig ??= new UnityConnectionConfig();
228
-
229
- if (captureCurrentToolStates)
230
- {
231
- var enabledToolNames = Tools?.GetAllTools()
232
- ?.Select(t => new UnityConnectionConfig.CopilotFeature(t.Name, Tools.IsToolEnabled(t.Name)))
233
- ?.ToList();
234
-
235
- var enabledPromptNames = Prompts?.GetAllPrompts()
236
- ?.Select(p => new UnityConnectionConfig.CopilotFeature(p.Name, Prompts.IsPromptEnabled(p.Name)))
237
- ?.ToList();
238
-
239
- var enabledResourceNames = Resources?.GetAllResources()
240
- ?.Select(r => new UnityConnectionConfig.CopilotFeature(r.Name, Resources.IsResourceEnabled(r.Name)))
241
- ?.ToList();
242
-
243
- unityConnectionConfig.Tools = enabledToolNames != null && enabledToolNames.Count > 0
244
- ? enabledToolNames
245
- : UnityConnectionConfig.DefaultTools;
246
-
247
- unityConnectionConfig.Prompts = enabledPromptNames != null && enabledPromptNames.Count > 0
248
- ? enabledPromptNames
249
- : UnityConnectionConfig.DefaultPrompts;
250
-
251
- unityConnectionConfig.Resources = enabledResourceNames != null && enabledResourceNames.Count > 0
252
- ? enabledResourceNames
253
- : UnityConnectionConfig.DefaultResources;
254
- }
255
-
256
- // Runtime-only overrides (env vars / CLI flags) MUST NOT be persisted to disk.
257
- // Temporarily restore the disk-baseline values for any overridden field, serialize,
258
- // then re-apply the overrides so the in-memory config keeps its runtime values.
259
- // The "in-memory keeps overrides" property is critical: callers (UI, configurators,
260
- // SignalR client) hold references to unityConnectionConfig and continue reading
261
- // from it after Save returns. Restoring overrides ensures behaviour is unchanged.
262
- var hasRuntimeOverrides = RuntimeOverrides != null && RuntimeOverrides.HasAny;
263
- if (hasRuntimeOverrides)
264
- EnvironmentUtils.ApplyBaseline(unityConnectionConfig, RuntimeOverrides!);
265
- try
266
- {
267
- var json = JsonSerializer.Serialize(unityConnectionConfig, new JsonSerializerOptions
268
- {
269
- WriteIndented = true,
270
- PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
271
- Converters = { new JsonStringEnumConverter() }
272
- });
273
- File.WriteAllText(AssetsFileAbsolutePath, json);
274
- }
275
- finally
276
- {
277
- if (hasRuntimeOverrides)
278
- EnvironmentUtils.ApplyOverrides(unityConnectionConfig, RuntimeOverrides!);
279
- }
280
-
281
- var assetFile = AssetFile;
282
- if (assetFile != null)
283
- UnityEditor.EditorUtility.SetDirty(assetFile);
284
- else
285
- UnityEditor.AssetDatabase.ImportAsset(AssetsFilePath);
286
- }
287
- catch (Exception e)
288
- {
289
- _logger.LogCritical(e, "{method}: <color=red><b>{file}</b> file can't be saved at <i>{path}</i></color>",
290
- nameof(Save), ResourcesFileName, AssetsFilePath);
291
- }
292
- #else
293
- // do nothing in runtime builds
294
- return;
295
- #endif
296
- }
297
- }
298
- }
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
+ using System;
13
+ using System.IO;
14
+ using System.Linq;
15
+ using System.Text.Json;
16
+ using System.Text.Json.Serialization;
17
+ using com.AtelierAI.Unity.Copilot.Runtime.Utils;
18
+ using Microsoft.Extensions.Logging;
19
+ using UnityEngine;
20
+
21
+ namespace com.AtelierAI.Unity.Copilot
22
+ {
23
+ public partial class UnityCopilotPluginEditor
24
+ {
25
+ public static string ResourcesFileName => "uco-config";
26
+
27
+ /// <summary>
28
+ /// Pre-rename filename (uco 0.3.0 / plugin 0.76.0 renamed
29
+ /// AI-Game-Developer-Config.json to uco-config.json). When only the
30
+ /// legacy file exists it is read and then migrated: saved under the
31
+ /// new name and the legacy file removed.
32
+ /// </summary>
33
+ public static string LegacyResourcesFileName => "AI-Game-Developer-Config";
34
+
35
+ /// <summary>True when only the legacy-named config exists right now.</summary>
36
+ static bool LegacyConfigNeedsMigration
37
+ {
38
+ get
39
+ {
40
+ var newPath = Path.GetFullPath(Path.Combine(ProjectRootPath, $"UserSettings/{ResourcesFileName}.json"));
41
+ var legacyPath = Path.GetFullPath(Path.Combine(ProjectRootPath, $"UserSettings/{LegacyResourcesFileName}.json"));
42
+ return !File.Exists(newPath) && File.Exists(legacyPath);
43
+ }
44
+ }
45
+
46
+ /// <summary>Project-relative path of the config actually present on disk.</summary>
47
+ public static string EffectiveAssetsFilePath
48
+ {
49
+ get
50
+ {
51
+ var newPath = $"UserSettings/{ResourcesFileName}.json";
52
+ var legacyPath = $"UserSettings/{LegacyResourcesFileName}.json";
53
+ if (File.Exists(Path.GetFullPath(Path.Combine(ProjectRootPath, newPath))))
54
+ return newPath;
55
+ return File.Exists(Path.GetFullPath(Path.Combine(ProjectRootPath, legacyPath)))
56
+ ? legacyPath
57
+ : newPath;
58
+ }
59
+ }
60
+
61
+ /// <summary>
62
+ /// One-shot rename migration: after a config was loaded from the
63
+ /// legacy filename, persist it under the new name and delete the old
64
+ /// file so the project converges on UserSettings/uco-config.json.
65
+ /// </summary>
66
+ static void MigrateLegacyConfigFileIfNeeded()
67
+ {
68
+ try
69
+ {
70
+ if (!LegacyConfigNeedsMigration)
71
+ return;
72
+ var legacyPath = Path.GetFullPath(Path.Combine(ProjectRootPath, $"UserSettings/{LegacyResourcesFileName}.json"));
73
+ var newPath = Path.GetFullPath(Path.Combine(ProjectRootPath, $"UserSettings/{ResourcesFileName}.json"));
74
+ File.Copy(legacyPath, newPath, overwrite: false);
75
+ File.Delete(legacyPath);
76
+ _logger.LogWarning("{method}: migrated {legacy} to {new} (uco rename); the old file was removed.",
77
+ nameof(MigrateLegacyConfigFileIfNeeded), LegacyResourcesFileName, ResourcesFileName);
78
+ }
79
+ catch (Exception e)
80
+ {
81
+ _logger.LogWarning(e, "{method}: legacy config migration failed; falling back to the legacy name for this session.",
82
+ nameof(MigrateLegacyConfigFileIfNeeded));
83
+ }
84
+ }
85
+
86
+ /// <summary>
87
+ /// Project-relative path used by Unity's AssetDatabase API.
88
+ /// Do NOT use this with System.IO File/Directory operations — use <see cref="AssetsFileAbsolutePath"/> instead.
89
+ /// </summary>
90
+ public static string AssetsFilePath => $"UserSettings/{ResourcesFileName}.json";
91
+
92
+ /// <summary>
93
+ /// Gets the Unity project root folder path (the folder that contains the Assets directory).
94
+ /// Uses <see cref="Application.dataPath"/> to ensure correctness regardless of the process working directory.
95
+ /// </summary>
96
+ public static string ProjectRootPath => Path.GetDirectoryName(Application.dataPath)!;
97
+
98
+ /// <summary>
99
+ /// Absolute path to the config file for use with System.IO File/Directory operations.
100
+ /// Built from <see cref="ProjectRootPath"/> and <see cref="AssetsFilePath"/> to keep paths consistent.
101
+ /// </summary>
102
+ public static string AssetsFileAbsolutePath => Path.GetFullPath(Path.Combine(ProjectRootPath, AssetsFilePath));
103
+
104
+
105
+ /// <summary>
106
+ /// Resets the config file to its default state.
107
+ /// </summary>
108
+ public static void ResetConfig()
109
+ {
110
+ Instance.unityConnectionConfig.SetDefault();
111
+ var plugin = CurrentPlugin;
112
+ if (plugin != null)
113
+ Instance.ApplyConfigToUcoPlugin(plugin);
114
+ Instance.Save(captureCurrentToolStates: false);
115
+ }
116
+
117
+ #if UNITY_EDITOR
118
+ public static UnityEngine.TextAsset AssetFile => UnityEditor.AssetDatabase.LoadAssetAtPath<UnityEngine.TextAsset>(EffectiveAssetsFilePath);
119
+ #endif
120
+
121
+ UnityConnectionConfig GetOrCreateConfig() => GetOrCreateConfig(out _);
122
+ UnityConnectionConfig GetOrCreateConfig(out bool wasCreated)
123
+ {
124
+ wasCreated = false;
125
+ try
126
+ {
127
+ // Both Edit mode and Play mode read from the same UserSettings JSON file
128
+ // Use the absolute path so File.Exists/ReadAllText resolve correctly regardless of CWD.
129
+ // The effective path prefers the renamed uco-config.json and falls back to
130
+ // the legacy AI-Game-Developer-Config.json until the one-shot migration runs.
131
+ var effectiveAbsolute = Path.GetFullPath(Path.Combine(ProjectRootPath, EffectiveAssetsFilePath));
132
+ var json = File.Exists(effectiveAbsolute) ? File.ReadAllText(effectiveAbsolute) : null;
133
+ if (!string.IsNullOrWhiteSpace(json))
134
+ MigrateLegacyConfigFileIfNeeded();
135
+
136
+ UnityConnectionConfig? config = null;
137
+ try
138
+ {
139
+ config = string.IsNullOrWhiteSpace(json)
140
+ ? null
141
+ : JsonSerializer.Deserialize<UnityConnectionConfig>(
142
+ // Legacy 1.0.2-and-earlier configs may carry the removed Cloud
143
+ // connection mode; JsonStringEnumConverter would throw on it.
144
+ json!.Replace("\"connectionMode\": \"Cloud\"", "\"connectionMode\": \"Custom\"")
145
+ .Replace("\"ConnectionMode\": \"Cloud\"", "\"ConnectionMode\": \"Custom\""),
146
+ new JsonSerializerOptions
147
+ {
148
+ PropertyNameCaseInsensitive = true,
149
+ Converters = { new JsonStringEnumConverter() }
150
+ });
151
+ }
152
+ catch (Exception e)
153
+ {
154
+ _logger.LogCritical(e, "{method}: <color=red><b>{file}</b> file is corrupted at <i>{path}</i></color>",
155
+ nameof(GetOrCreateConfig), ResourcesFileName, AssetsFilePath);
156
+ }
157
+ // Track whether the config originated from a deserialized disk file. The
158
+ // safe-defaults migration below only applies to configs that pre-existed on
159
+ // disk — a freshly created config already starts with the safe defaults.
160
+ var loadedFromDisk = config != null;
161
+ if (config == null)
162
+ {
163
+ _logger.LogWarning("{method}: <color=orange><b>Creating {file}</b> file at <i>{path}</i></color>",
164
+ nameof(GetOrCreateConfig), ResourcesFileName, AssetsFilePath);
165
+ config = new UnityConnectionConfig();
166
+ wasCreated = true;
167
+ }
168
+ if (string.IsNullOrEmpty(config.LocalToken))
169
+ {
170
+ config.LocalToken = GenerateToken();
171
+ wasCreated = true;
172
+ }
173
+
174
+ // ── Safe-defaults (fail-closed) one-shot migration ─────────────────────────
175
+ //
176
+ // Newly created configs default AllowLanBind/AllowInsecureRemoteHttp to false so
177
+ // a fresh project never starts the server on 0.0.0.0 without explicit consent.
178
+ // However, projects that EXISTED before this change may already have LocalHost set
179
+ // to a non-loopback URL (e.g. 0.0.0.0 or a LAN IP) on purpose. Silently dropping
180
+ // their server start would be a regression. Detect that case here and grant the
181
+ // opt-in once, with a one-time LogWarning so the user knows it happened and can
182
+ // turn it back off if they did not intend it.
183
+ //
184
+ // Triggers ONLY when:
185
+ // • The config was actually loaded from disk (loadedFromDisk == true)
186
+ // • LocalHost parses as a URL with a non-loopback host
187
+ // • AllowLanBind is currently false (default)
188
+ // The migration sets AllowLanBind = true and flags `wasCreated` so the Save() at
189
+ // the end of construction persists the new flag immediately. AllowInsecureRemoteHttp
190
+ // is NOT auto-granted — it gates the configurators (which are advisory) and only
191
+ // affects emitted client configs, not server start, so a one-time warning at start
192
+ // is sufficient there.
193
+ if (loadedFromDisk && !config.AllowLanBind && !string.IsNullOrEmpty(config.LocalHost))
194
+ {
195
+ if (com.AtelierAI.Unity.Copilot.Editor.Utils.SafeDefaultsGuard.IsNonLoopbackUrl(config.LocalHost))
196
+ {
197
+ _logger.LogWarning(
198
+ "{method}: <color=orange>Detected pre-existing LAN/all-interfaces LocalHost ({host}) without AllowLanBind. " +
199
+ "Granting AllowLanBind=true once to preserve current behaviour. " +
200
+ "Open the Game Developer window and turn it off if you did not intend to expose the server to the network.</color>",
201
+ nameof(GetOrCreateConfig), config.LocalHost);
202
+ config.AllowLanBind = true;
203
+ wasCreated = true; // force Save() so the migration persists.
204
+ }
205
+ }
206
+
207
+ return config;
208
+ }
209
+ catch (Exception e)
210
+ {
211
+ _logger.LogCritical(e, "{method}: <color=red><b>{file}</b> file can't be loaded from <i>{path}</i></color>",
212
+ nameof(GetOrCreateConfig), ResourcesFileName, AssetsFilePath);
213
+ throw;
214
+ }
215
+ }
216
+
217
+ public void Save(bool captureCurrentToolStates = true)
218
+ {
219
+ #if UNITY_EDITOR
220
+ Validate();
221
+ try
222
+ {
223
+ var directory = Path.GetDirectoryName(AssetsFileAbsolutePath);
224
+ if (!Directory.Exists(directory))
225
+ Directory.CreateDirectory(directory!);
226
+
227
+ unityConnectionConfig ??= new UnityConnectionConfig();
228
+
229
+ if (captureCurrentToolStates)
230
+ {
231
+ var enabledToolNames = Tools?.GetAllTools()
232
+ ?.Select(t => new UnityConnectionConfig.CopilotFeature(t.Name, Tools.IsToolEnabled(t.Name)))
233
+ ?.ToList();
234
+
235
+ var enabledPromptNames = Prompts?.GetAllPrompts()
236
+ ?.Select(p => new UnityConnectionConfig.CopilotFeature(p.Name, Prompts.IsPromptEnabled(p.Name)))
237
+ ?.ToList();
238
+
239
+ var enabledResourceNames = Resources?.GetAllResources()
240
+ ?.Select(r => new UnityConnectionConfig.CopilotFeature(r.Name, Resources.IsResourceEnabled(r.Name)))
241
+ ?.ToList();
242
+
243
+ unityConnectionConfig.Tools = enabledToolNames != null && enabledToolNames.Count > 0
244
+ ? enabledToolNames
245
+ : UnityConnectionConfig.DefaultTools;
246
+
247
+ unityConnectionConfig.Prompts = enabledPromptNames != null && enabledPromptNames.Count > 0
248
+ ? enabledPromptNames
249
+ : UnityConnectionConfig.DefaultPrompts;
250
+
251
+ unityConnectionConfig.Resources = enabledResourceNames != null && enabledResourceNames.Count > 0
252
+ ? enabledResourceNames
253
+ : UnityConnectionConfig.DefaultResources;
254
+ }
255
+
256
+ // Runtime-only overrides (env vars / CLI flags) MUST NOT be persisted to disk.
257
+ // Temporarily restore the disk-baseline values for any overridden field, serialize,
258
+ // then re-apply the overrides so the in-memory config keeps its runtime values.
259
+ // The "in-memory keeps overrides" property is critical: callers (UI, configurators,
260
+ // SignalR client) hold references to unityConnectionConfig and continue reading
261
+ // from it after Save returns. Restoring overrides ensures behaviour is unchanged.
262
+ var hasRuntimeOverrides = RuntimeOverrides != null && RuntimeOverrides.HasAny;
263
+ if (hasRuntimeOverrides)
264
+ EnvironmentUtils.ApplyBaseline(unityConnectionConfig, RuntimeOverrides!);
265
+ try
266
+ {
267
+ var json = JsonSerializer.Serialize(unityConnectionConfig, new JsonSerializerOptions
268
+ {
269
+ WriteIndented = true,
270
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
271
+ Converters = { new JsonStringEnumConverter() }
272
+ });
273
+ File.WriteAllText(AssetsFileAbsolutePath, json);
274
+ }
275
+ finally
276
+ {
277
+ if (hasRuntimeOverrides)
278
+ EnvironmentUtils.ApplyOverrides(unityConnectionConfig, RuntimeOverrides!);
279
+ }
280
+
281
+ var assetFile = AssetFile;
282
+ if (assetFile != null)
283
+ UnityEditor.EditorUtility.SetDirty(assetFile);
284
+ else
285
+ UnityEditor.AssetDatabase.ImportAsset(AssetsFilePath);
286
+ }
287
+ catch (Exception e)
288
+ {
289
+ _logger.LogCritical(e, "{method}: <color=red><b>{file}</b> file can't be saved at <i>{path}</i></color>",
290
+ nameof(Save), ResourcesFileName, AssetsFilePath);
291
+ }
292
+ #else
293
+ // do nothing in runtime builds
294
+ return;
295
+ #endif
296
+ }
297
+ }
298
+ }