@atelierai/uco 1.0.2 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/codegen/emit.js +1 -1
  3. package/dist/codegen/fetch.js +1 -1
  4. package/dist/codegen/fetch.js.map +1 -1
  5. package/dist/commands/devops/login.js +1 -1
  6. package/dist/commands/devops/login.js.map +1 -1
  7. package/dist/commands/devops/setup-mcp.js +1 -1
  8. package/dist/commands/devops/setup-mcp.js.map +1 -1
  9. package/dist/commands/gen.js +2 -2
  10. package/dist/commands/gen.js.map +1 -1
  11. package/dist/commands/ping.js +2 -2
  12. package/dist/commands/ping.js.map +1 -1
  13. package/dist/config/resolve.js +1 -1
  14. package/dist/config/resolve.js.map +1 -1
  15. package/dist/generated/tools.js +1 -1
  16. package/dist/generated/tools.js.map +1 -1
  17. package/dist/server/app.js +1 -1
  18. package/dist/server/app.js.map +1 -1
  19. package/dist/server/index.d.ts +1 -1
  20. package/dist/server/index.js +2 -2
  21. package/dist/server/index.js.map +1 -1
  22. package/dist/server/rest/help.js +1 -1
  23. package/dist/server/rest/help.js.map +1 -1
  24. package/package.json +1 -1
  25. package/skills/uco-setup/references/recovery.md +26 -2
  26. package/vendor/plugin/com.atelierai.unity.copilot/CHANGELOG.md +24 -0
  27. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/CopilotServerManager.cs +31 -41
  28. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.Connection.cs +177 -409
  29. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.CreateGUI.cs +1 -8
  30. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.cs +0 -69
  31. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/UpdatePopupWindow.cs +30 -0
  32. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.Config.cs +6 -1
  33. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.Static.cs +9 -12
  34. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.Config.cs +11 -62
  35. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.cs +1 -1
  36. package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Utils/EnvironmentUtils.cs +7 -25
  37. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Environment/EnvironmentUtilsTests.cs +260 -320
  38. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/UI/MainWindowEditor.StatusLogicTests.cs +0 -125
  39. package/vendor/plugin/com.atelierai.unity.copilot/package.json +1 -1
  40. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Services/DeviceAuthFlow.cs +0 -133
  41. package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Services/DeviceAuthFlow.cs.meta +0 -11
  42. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/CopilotServerManagerAutoStartTests.cs +0 -43
  43. package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/CopilotServerManagerAutoStartTests.cs.meta +0 -11
@@ -23,11 +23,7 @@ namespace com.AtelierAI.Unity.Copilot.Editor.UI
23
23
  readonly CompositeDisposable _disposables = new();
24
24
 
25
25
  Button? _btnConnect;
26
- Button? _btnAuthorize;
27
- Action? _startAuthorizeAction;
28
26
  VisualElement? _timelinePointUnity;
29
- AlertPanel? _connectionAuthAlert;
30
- AlertPanel? _connectionConnectAlert;
31
27
 
32
28
  protected override string WindowTitle => "Unity Copilot";
33
29
  protected override string[] WindowUxmlPaths => _windowUxmlPaths;
@@ -73,76 +69,11 @@ namespace com.AtelierAI.Unity.Copilot.Editor.UI
73
69
  _authRejectedSubscription.Dispose();
74
70
  }
75
71
 
76
- internal static (bool needsAuth, bool hasToken, bool isCloud) ComputeCloudAuthState(ConnectionMode mode, string? token)
77
- {
78
- var isCloud = mode == ConnectionMode.Cloud;
79
- var hasToken = !string.IsNullOrEmpty(token);
80
- var needsAuth = isCloud && !hasToken;
81
- return (needsAuth, hasToken, isCloud);
82
- }
83
-
84
- private void UpdateCloudAuthState()
85
- {
86
- var (needsAuth, hasToken, isCloud) = ComputeCloudAuthState(UnityCopilotPluginEditor.ConnectionMode, UnityCopilotPluginEditor.CloudToken);
87
-
88
- if (_timelinePointUnity != null)
89
- {
90
- _timelinePointUnity.SetEnabled(!needsAuth);
91
- _timelinePointUnity.tooltip = needsAuth
92
- ? "Cloud token is required. Press the Authorize button to authenticate."
93
- : "";
94
- }
95
- if (_btnConnect != null)
96
- {
97
- if (needsAuth)
98
- {
99
- _btnConnect.text = ServerButtonText_Connect;
100
- _btnConnect.EnableInClassList("btn-primary", false);
101
- _btnConnect.EnableInClassList("btn-secondary", true);
102
- }
103
- else if (isCloud && hasToken
104
- && _btnConnect.text == ServerButtonText_Connect)
105
- {
106
- _btnConnect.EnableInClassList("btn-primary", true);
107
- _btnConnect.EnableInClassList("btn-secondary", false);
108
- }
109
- }
110
- if (_btnAuthorize != null)
111
- {
112
- _btnAuthorize.EnableInClassList("btn-primary", !hasToken);
113
- }
114
- _connectionAuthAlert?.SetVisible(needsAuth);
115
-
116
- // Show connect alert when authorized in Cloud mode but not connected and not trying
117
- if (_connectionConnectAlert != null)
118
- {
119
- var connectionState = UnityCopilotPluginEditor.ConnectionState.CurrentValue;
120
- var keepConnected = UnityCopilotPluginEditor.KeepConnected;
121
- var isDisconnected = connectionState == ConnectionState.Disconnected;
122
- var needsConnect = isCloud && hasToken && isDisconnected && !keepConnected;
123
- _connectionConnectAlert.SetVisible(needsConnect);
124
- }
125
- }
126
-
127
72
  private static void UnityBuildAndConnect()
128
73
  {
129
74
  UnityCopilotPluginEditor.Instance.BuildMcpPluginIfNeeded();
130
75
  UnityCopilotPluginEditor.Instance.AddUnityLogCollectorIfNeeded(() => new BufferedFileLogStorage());
131
76
  UnityCopilotPluginEditor.ConnectIfNeeded();
132
77
  }
133
-
134
- /// <summary>
135
- /// Disconnects, disposes the current MCP plugin, rebuilds it (picking up the new Host/Token
136
- /// from the changed ConnectionMode), and reconnects if KeepConnected is enabled.
137
- /// Called when switching between Local and Cloud modes.
138
- /// </summary>
139
- private static void ReconnectAfterModeSwitch()
140
- {
141
- if (UnityCopilotPluginEditor.Instance.HasMcpPluginInstance)
142
- {
143
- UnityCopilotPluginEditor.Instance.DisposeMcpPluginInstance();
144
- }
145
- UnityBuildAndConnect();
146
- }
147
78
  }
148
79
  }
@@ -11,6 +11,7 @@
11
11
  #nullable enable
12
12
 
13
13
  using System;
14
+ using System.IO;
14
15
  using com.AtelierAI.Unity.Copilot.Editor.DependencyResolver;
15
16
  using com.AtelierAI.Unity.Copilot.Editor.Utils;
16
17
  using R3;
@@ -170,6 +171,20 @@ namespace com.AtelierAI.Unity.Copilot.Editor.UI
170
171
  if (installButton != null)
171
172
  installButton.text = "Installing...";
172
173
 
174
+ // Embedded packages (installed by `uco install` as a physical copy into
175
+ // Packages/) cannot be updated through the registry — Unity refuses
176
+ // Client.Add on an embedded id. Detect this and direct the user to
177
+ // the CLI instead of failing with an opaque UPM error.
178
+ if (IsEmbeddedInstall())
179
+ {
180
+ if (installButton != null)
181
+ installButton.text = "Use: uco install";
182
+ Debug.Log("[Unity Copilot] This project uses an embedded install (uco install). " +
183
+ "Run `uco install <project-path>` from a terminal to upgrade — the in-editor " +
184
+ "updater cannot replace embedded packages.");
185
+ return;
186
+ }
187
+
173
188
  // Force a clean post-install recompile so an unrelated user-asmdef
174
189
  // error can't leave the OLD plugin AppDomain loaded with the new
175
190
  // package files on disk.
@@ -179,6 +194,21 @@ namespace com.AtelierAI.Unity.Copilot.Editor.UI
179
194
  EditorApplication.update += OnPackageInstallProgress;
180
195
  }
181
196
 
197
+ /// <summary>
198
+ /// True when this package is physically embedded in the project's
199
+ /// Packages folder (installed via `uco install`), as opposed to
200
+ /// resolved from a registry or git URL. Embedded packages cannot be
201
+ /// updated through Unity's package manager.
202
+ /// </summary>
203
+ private static bool IsEmbeddedInstall()
204
+ {
205
+ return Directory.Exists(
206
+ Path.Combine(ProjectRootPath, "Packages", "com.atelierai.unity.copilot"));
207
+ }
208
+
209
+ private static string ProjectRootPath =>
210
+ Path.GetDirectoryName(Application.dataPath);
211
+
182
212
  private void OnPackageInstallProgress()
183
213
  {
184
214
  if (addRequest == null)
@@ -138,7 +138,12 @@ namespace com.AtelierAI.Unity.Copilot
138
138
  {
139
139
  config = string.IsNullOrWhiteSpace(json)
140
140
  ? null
141
- : JsonSerializer.Deserialize<UnityConnectionConfig>(json!, new JsonSerializerOptions
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
142
147
  {
143
148
  PropertyNameCaseInsensitive = true,
144
149
  Converters = { new JsonStringEnumConverter() }
@@ -101,6 +101,15 @@ namespace com.AtelierAI.Unity.Copilot
101
101
  NotifyChanged(Instance.unityConnectionConfig);
102
102
  }
103
103
  }
104
+ public static string? LocalToken
105
+ {
106
+ get => Instance.unityConnectionConfig.LocalToken;
107
+ set
108
+ {
109
+ Instance.unityConnectionConfig.LocalToken = value;
110
+ NotifyChanged(Instance.unityConnectionConfig);
111
+ }
112
+ }
104
113
  public static string LocalHost
105
114
  {
106
115
  get => Instance.unityConnectionConfig.LocalHost;
@@ -229,18 +238,6 @@ namespace com.AtelierAI.Unity.Copilot
229
238
  NotifyChanged(Instance.unityConnectionConfig);
230
239
  }
231
240
  }
232
- public static string CloudServerUrl => UnityCopilotPlugin.UnityConnectionConfig.CloudServerUrl;
233
- public static string? CloudToken
234
- {
235
- get => Instance.unityConnectionConfig.CloudToken;
236
- set
237
- {
238
- Instance.unityConnectionConfig.CloudToken = value;
239
- NotifyChanged(Instance.unityConnectionConfig);
240
- }
241
- }
242
-
243
-
244
241
 
245
242
  // 'new' is intentional: static dispatch on the subtype, instance logic lives in the base.
246
243
  public static new ReadOnlyReactiveProperty<WsState> ConnectionState
@@ -35,87 +35,36 @@ namespace com.AtelierAI.Unity.Copilot
35
35
  public static List<CopilotFeature> DefaultResources => new();
36
36
 
37
37
  /// <summary>
38
- /// Backing field for the local server URL. Serialized as "host" in JSON.
39
- /// Use <see cref="Host"/> for the active connection URL (routes through Cloud mode).
38
+ /// The local server URL. Serialized as "host" in JSON.
40
39
  /// </summary>
41
40
  [JsonPropertyName("host")]
42
41
  public string LocalHost { get; set; } = DefaultHost;
43
42
 
44
43
  /// <summary>
45
- /// Backing field for the local auth token. Serialized as "token" in JSON.
46
- /// Use <see cref="Token"/> for the active token (routes through Cloud mode).
44
+ /// The local auth token. Serialized as "token" in JSON.
47
45
  /// </summary>
48
46
  [JsonPropertyName("token")]
49
47
  public string? LocalToken { get; set; }
50
48
 
51
- public const string DefaultCloudServerBaseUrl = "https://ai-game.dev";
52
-
53
- public static string CloudServerBaseUrl
54
- {
55
- get
56
- {
57
- var args = ArgsUtils.ParseCommandLineArguments();
58
- var envValue = args.GetValueOrDefault(EnvironmentUtils.EnvCloudUrl)
59
- ?? Environment.GetEnvironmentVariable(EnvironmentUtils.EnvCloudUrl);
60
-
61
- if (string.IsNullOrWhiteSpace(envValue))
62
- return DefaultCloudServerBaseUrl;
63
-
64
- var normalized = envValue.Trim().Trim('"').TrimEnd('/');
65
-
66
- if (!Uri.TryCreate(normalized, UriKind.Absolute, out var uri) ||
67
- (uri.Scheme != Uri.UriSchemeHttp && uri.Scheme != Uri.UriSchemeHttps))
68
- {
69
- return DefaultCloudServerBaseUrl;
70
- }
71
-
72
- // Strip trailing "/mcp" so CloudServerUrl doesn't produce "/mcp/mcp"
73
- if (normalized.EndsWith("/mcp", StringComparison.OrdinalIgnoreCase))
74
- normalized = normalized[..^4];
75
-
76
- return normalized;
77
- }
78
- }
79
-
80
- public static string CloudServerUrl => CloudServerBaseUrl + "/mcp";
81
-
82
- /// <summary>
83
- /// Returns the active connection host based on <see cref="ConnectionMode"/>.
84
- /// In Cloud mode, returns <see cref="CloudServerUrl"/>.
85
- /// In Local mode, returns <see cref="LocalHost"/>.
86
- /// </summary>
87
49
  [JsonIgnore]
88
50
  public override string Host
89
51
  {
90
- get => ConnectionMode == ConnectionMode.Cloud ? CloudServerUrl : LocalHost;
52
+ get => LocalHost;
91
53
  set => LocalHost = value;
92
54
  }
93
55
 
94
- /// <summary>
95
- /// Gets/sets the active auth token based on <see cref="ConnectionMode"/>.
96
- /// In Cloud mode, routes to <see cref="CloudToken"/>.
97
- /// In Local mode, routes to <see cref="LocalToken"/>.
98
- /// Setter mirrors the getter so env-var / CLI overrides (which write via
99
- /// the generic Token property) land on the right field regardless of mode.
100
- /// </summary>
101
56
  [JsonIgnore]
102
57
  public override string? Token
103
58
  {
104
- get => ConnectionMode == ConnectionMode.Cloud ? CloudToken : LocalToken;
105
- set
106
- {
107
- if (ConnectionMode == ConnectionMode.Cloud)
108
- CloudToken = value;
109
- else
110
- LocalToken = value;
111
- }
59
+ get => LocalToken;
60
+ set => LocalToken = value;
112
61
  }
113
62
 
114
63
  public LogLevel LogLevel { get; set; } = LogLevel.Warning;
115
64
  public bool KeepServerRunning { get; set; } = false;
116
65
  public TransportMethod TransportMethod { get; set; } = TransportMethod.streamableHttp;
117
66
  public AuthOption AuthOption { get; set; } = AuthOption.none;
118
- public ConnectionMode ConnectionMode { get; set; } = ConnectionMode.Cloud;
67
+ public ConnectionMode ConnectionMode { get; set; } = ConnectionMode.Custom;
119
68
 
120
69
  /// <summary>
121
70
  /// Optional path to the Node.js MCP server entry script (cocli's bin/server.mjs).
@@ -165,7 +114,6 @@ namespace com.AtelierAI.Unity.Copilot
165
114
  /// <c>auth=none</c> (not recommended).
166
115
  /// </summary>
167
116
  public bool ForceTokenWhenLanBind { get; set; } = true;
168
- public string? CloudToken { get; set; }
169
117
  public List<CopilotFeature> Tools { get; set; } = new();
170
118
  public List<CopilotFeature> Prompts { get; set; } = new();
171
119
  public List<CopilotFeature> Resources { get; set; } = new();
@@ -191,9 +139,8 @@ namespace com.AtelierAI.Unity.Copilot
191
139
  KeepServerRunning = !isCi;
192
140
  TransportMethod = TransportMethod.streamableHttp;
193
141
  AuthOption = AuthOption.none;
194
- ConnectionMode = ConnectionMode.Cloud;
142
+ ConnectionMode = ConnectionMode.Custom;
195
143
  NodeServerPath = null;
196
- CloudToken = null;
197
144
  LogLevel = LogLevel.Warning;
198
145
  TimeoutMs = Consts.Hub.DefaultTimeoutMs;
199
146
  Tools = DefaultTools;
@@ -218,9 +165,11 @@ namespace com.AtelierAI.Unity.Copilot
218
165
  }
219
166
  }
220
167
 
168
+ /// <summary>Local/custom server connection. The legacy Cloud mode (remote
169
+ /// ai-game.dev endpoint) was removed in 1.0.3; configs that still carry
170
+ /// "Cloud" are mapped to Custom on load.</summary>
221
171
  public enum ConnectionMode
222
172
  {
223
- Custom,
224
- Cloud
173
+ Custom
225
174
  }
226
175
  }
@@ -26,7 +26,7 @@ namespace com.AtelierAI.Unity.Copilot
26
26
 
27
27
  public partial class UnityCopilotPlugin : IDisposable
28
28
  {
29
- public const string Version = "1.0.2";
29
+ public const string Version = "1.0.4";
30
30
 
31
31
  private static int _singletonCount = 0;
32
32
  public static bool HasAnyInstance => _singletonCount > 0;
@@ -47,7 +47,6 @@ namespace com.AtelierAI.Unity.Copilot.Runtime.Utils
47
47
  public const string EnvTools = "UNITY_MCP_TOOLS";
48
48
  public const string EnvStartServer = "UNITY_MCP_START_SERVER";
49
49
  public const string EnvTransport = "UNITY_MCP_TRANSPORT";
50
- public const string EnvCloudUrl = "UNITY_MCP_CLOUD_URL";
51
50
  public const string EnvConnectionMode = "UNITY_MCP_CONNECTION_MODE";
52
51
 
53
52
  // Short flag aliases recognised by the in-plugin command-line parser.
@@ -64,7 +63,6 @@ namespace com.AtelierAI.Unity.Copilot.Runtime.Utils
64
63
  public const string FieldKeepConnected = nameof(UnityCopilotPlugin.UnityConnectionConfig.KeepConnected);
65
64
  public const string FieldAuthOption = nameof(UnityCopilotPlugin.UnityConnectionConfig.AuthOption);
66
65
  public const string FieldLocalToken = nameof(UnityCopilotPlugin.UnityConnectionConfig.LocalToken);
67
- public const string FieldCloudToken = nameof(UnityCopilotPlugin.UnityConnectionConfig.CloudToken);
68
66
  public const string FieldTools = nameof(UnityCopilotPlugin.UnityConnectionConfig.EnabledToolsOverride);
69
67
  public const string FieldStartServer = nameof(UnityCopilotPlugin.UnityConnectionConfig.KeepServerRunning);
70
68
  public const string FieldTransport = nameof(UnityCopilotPlugin.UnityConnectionConfig.TransportMethod);
@@ -176,9 +174,9 @@ namespace com.AtelierAI.Unity.Copilot.Runtime.Utils
176
174
 
177
175
  string Sanitize(string raw) => raw.Trim().Trim('"');
178
176
 
179
- // UNITY_MCP_HOST is the legacy alias for UNITY_MCP_CLOUD_URL.
177
+ // UNITY_MCP_HOST / --url override the local server host.
180
178
  string? sanitizedHost = null;
181
- var rawHost = Resolve(EnvCloudUrl, FlagUrl) ?? Resolve(EnvHost, flagAlias: null);
179
+ var rawHost = Resolve(EnvHost, FlagUrl);
182
180
  if (rawHost != null)
183
181
  {
184
182
  var host = Sanitize(rawHost).TrimEnd('/');
@@ -231,8 +229,7 @@ namespace com.AtelierAI.Unity.Copilot.Runtime.Utils
231
229
  _logger.LogInformation("[MCP] Override: {Key}={Value}", EnvAuthOption, ao);
232
230
  }
233
231
 
234
- // Resolved AFTER ConnectionMode so we route to the correct underlying field
235
- // (LocalToken in Custom mode, CloudToken in Cloud mode). We track the specific
232
+ // We track the specific
236
233
  // backing field rather than the abstract Token property because only the backing
237
234
  // fields are serialised — restoring the baseline must target the same field that
238
235
  // was clobbered.
@@ -240,23 +237,11 @@ namespace com.AtelierAI.Unity.Copilot.Runtime.Utils
240
237
  if (rawToken != null)
241
238
  {
242
239
  var token = Sanitize(rawToken);
243
- if (config.ConnectionMode == ConnectionMode.Cloud)
240
+ if (!string.Equals(token, config.LocalToken, StringComparison.Ordinal))
244
241
  {
245
- if (!string.Equals(token, config.CloudToken, StringComparison.Ordinal))
246
- {
247
- record.Track(FieldCloudToken, config.CloudToken, token);
248
- config.CloudToken = token;
249
- _logger.LogInformation("[MCP] Override: {Key}=*** (CloudToken)", EnvToken);
250
- }
251
- }
252
- else
253
- {
254
- if (!string.Equals(token, config.LocalToken, StringComparison.Ordinal))
255
- {
256
- record.Track(FieldLocalToken, config.LocalToken, token);
257
- config.LocalToken = token;
258
- _logger.LogInformation("[MCP] Override: {Key}=*** (LocalToken)", EnvToken);
259
- }
242
+ record.Track(FieldLocalToken, config.LocalToken, token);
243
+ config.LocalToken = token;
244
+ _logger.LogInformation("[MCP] Override: {Key}=*** (LocalToken)", EnvToken);
260
245
  }
261
246
  }
262
247
 
@@ -341,9 +326,6 @@ namespace com.AtelierAI.Unity.Copilot.Runtime.Utils
341
326
  case FieldLocalToken:
342
327
  config.LocalToken = (string?)kvp.Value;
343
328
  break;
344
- case FieldCloudToken:
345
- config.CloudToken = (string?)kvp.Value;
346
- break;
347
329
  case FieldTools:
348
330
  config.EnabledToolsOverride = (List<string>?)kvp.Value;
349
331
  break;