@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.
- package/CHANGELOG.md +19 -0
- package/dist/codegen/emit.js +1 -1
- package/dist/codegen/fetch.js +1 -1
- package/dist/codegen/fetch.js.map +1 -1
- package/dist/commands/devops/login.js +1 -1
- package/dist/commands/devops/login.js.map +1 -1
- package/dist/commands/devops/setup-mcp.js +1 -1
- package/dist/commands/devops/setup-mcp.js.map +1 -1
- package/dist/commands/gen.js +2 -2
- package/dist/commands/gen.js.map +1 -1
- package/dist/commands/ping.js +2 -2
- package/dist/commands/ping.js.map +1 -1
- package/dist/config/resolve.js +1 -1
- package/dist/config/resolve.js.map +1 -1
- package/dist/generated/tools.js +1 -1
- package/dist/generated/tools.js.map +1 -1
- package/dist/server/app.js +1 -1
- package/dist/server/app.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +2 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/rest/help.js +1 -1
- package/dist/server/rest/help.js.map +1 -1
- package/package.json +1 -1
- package/skills/uco-setup/references/recovery.md +26 -2
- package/vendor/plugin/com.atelierai.unity.copilot/CHANGELOG.md +24 -0
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/CopilotServerManager.cs +31 -41
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.Connection.cs +177 -409
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.CreateGUI.cs +1 -8
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.cs +0 -69
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/UpdatePopupWindow.cs +30 -0
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.Config.cs +6 -1
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.Static.cs +9 -12
- package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.Config.cs +11 -62
- package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.cs +1 -1
- package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Utils/EnvironmentUtils.cs +7 -25
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Environment/EnvironmentUtilsTests.cs +260 -320
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/UI/MainWindowEditor.StatusLogicTests.cs +0 -125
- package/vendor/plugin/com.atelierai.unity.copilot/package.json +1 -1
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Services/DeviceAuthFlow.cs +0 -133
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Services/DeviceAuthFlow.cs.meta +0 -11
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/CopilotServerManagerAutoStartTests.cs +0 -43
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/CopilotServerManagerAutoStartTests.cs.meta +0 -11
|
@@ -1,409 +1,177 @@
|
|
|
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 com.IvanMurzak.ReflectorNet.Utils;
|
|
14
|
-
using com.AtelierAI.Unity.Copilot.Editor.
|
|
15
|
-
using com.AtelierAI.
|
|
16
|
-
using
|
|
17
|
-
using
|
|
18
|
-
using UnityEngine;
|
|
19
|
-
using
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
private
|
|
27
|
-
private
|
|
28
|
-
private
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
_timelinePointUnity
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
_inputFieldHost.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
///
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
/// </summary>
|
|
179
|
-
private static void ConnectToServer()
|
|
180
|
-
{
|
|
181
|
-
UnityCopilotPluginEditor.KeepConnected = true;
|
|
182
|
-
UnityCopilotPluginEditor.Instance.Save();
|
|
183
|
-
UnityBuildAndConnect();
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
private void SetupConnectionModeToggle(VisualElement root)
|
|
187
|
-
{
|
|
188
|
-
var container = root.Q<VisualElement>("segmentConnectionMode");
|
|
189
|
-
if (container == null) return;
|
|
190
|
-
|
|
191
|
-
var control = new SegmentedControl("Custom", "Cloud");
|
|
192
|
-
control.SetTooltips(
|
|
193
|
-
"Connect to your own server. The plugin starts a local server automatically and manages its lifecycle. Use this when you want full control over the server configuration, port, and authorization settings.",
|
|
194
|
-
"Connect to a remote server hosted in the cloud (e.g. ai-game.dev). No local server is started — the plugin connects directly to a built-in cloud endpoint (Cloud URL is predefined and not configurable). Requires authorization via device code flow.");
|
|
195
|
-
container.Add(control);
|
|
196
|
-
|
|
197
|
-
var inputServerUrl = root.Q<TextField>("InputServerURL");
|
|
198
|
-
var mcpServerPoint = root.Q<VisualElement>("TimelinePointMcpServer");
|
|
199
|
-
var cloudAuthSection = root.Q<VisualElement>("cloudAuthSection");
|
|
200
|
-
|
|
201
|
-
void UpdateModeVisibility(ConnectionMode mode)
|
|
202
|
-
{
|
|
203
|
-
var isCustom = mode == ConnectionMode.Custom;
|
|
204
|
-
if (inputServerUrl != null) inputServerUrl.style.display = isCustom ? DisplayStyle.Flex : DisplayStyle.None;
|
|
205
|
-
if (mcpServerPoint != null) mcpServerPoint.style.display = isCustom ? DisplayStyle.Flex : DisplayStyle.None;
|
|
206
|
-
if (cloudAuthSection != null) cloudAuthSection.style.display = isCustom ? DisplayStyle.None : DisplayStyle.Flex;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
var currentMode = UnityCopilotPluginEditor.ConnectionMode;
|
|
210
|
-
control.SetValueWithoutNotify(currentMode == ConnectionMode.Custom ? 0 : 1);
|
|
211
|
-
UpdateModeVisibility(currentMode);
|
|
212
|
-
|
|
213
|
-
control.RegisterCallback<ChangeEvent<int>>(evt =>
|
|
214
|
-
{
|
|
215
|
-
if (evt.newValue == 0)
|
|
216
|
-
{
|
|
217
|
-
UnityCopilotPluginEditor.ConnectionMode = ConnectionMode.Custom;
|
|
218
|
-
UnityCopilotPluginEditor.Instance.Save();
|
|
219
|
-
UpdateModeVisibility(ConnectionMode.Custom);
|
|
220
|
-
UpdateCloudAuthState();
|
|
221
|
-
|
|
222
|
-
// Start local server if configured and reconnect to it
|
|
223
|
-
CopilotServerManager.StartServerIfNeeded();
|
|
224
|
-
ReconnectAfterModeSwitch();
|
|
225
|
-
ScheduleConnectionUIRefresh();
|
|
226
|
-
}
|
|
227
|
-
else
|
|
228
|
-
{
|
|
229
|
-
UnityCopilotPluginEditor.ConnectionMode = ConnectionMode.Cloud;
|
|
230
|
-
|
|
231
|
-
// Cloud requires authorization
|
|
232
|
-
UnityCopilotPluginEditor.AuthOption = AuthOption.required;
|
|
233
|
-
|
|
234
|
-
UnityCopilotPluginEditor.Instance.Save();
|
|
235
|
-
UpdateModeVisibility(ConnectionMode.Cloud);
|
|
236
|
-
UpdateCloudAuthState();
|
|
237
|
-
|
|
238
|
-
// Stop local server — not needed in Cloud mode
|
|
239
|
-
if (CopilotServerManager.IsRunning || CopilotServerManager.IsStarting)
|
|
240
|
-
CopilotServerManager.StopServer();
|
|
241
|
-
|
|
242
|
-
// Reconnect to cloud server (only if authorized)
|
|
243
|
-
if (!string.IsNullOrEmpty(UnityCopilotPluginEditor.CloudToken))
|
|
244
|
-
ReconnectAfterModeSwitch();
|
|
245
|
-
ScheduleConnectionUIRefresh();
|
|
246
|
-
}
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
internal static bool IsAuthFlowRunning(DeviceAuthFlowState state) =>
|
|
251
|
-
state == DeviceAuthFlowState.Initiating
|
|
252
|
-
|| state == DeviceAuthFlowState.WaitingForUser
|
|
253
|
-
|| state == DeviceAuthFlowState.Polling;
|
|
254
|
-
|
|
255
|
-
internal static string GetAuthFlowStatusMessage(DeviceAuthFlowState state, string? userCode, string? errorMessage) => state switch
|
|
256
|
-
{
|
|
257
|
-
DeviceAuthFlowState.Initiating => "Initiating...",
|
|
258
|
-
DeviceAuthFlowState.WaitingForUser => $"Code: {userCode} — Authorize in browser",
|
|
259
|
-
DeviceAuthFlowState.Polling => $"Code: {userCode} — Waiting for authorization...",
|
|
260
|
-
DeviceAuthFlowState.Authorized => "Authorized!",
|
|
261
|
-
DeviceAuthFlowState.Failed => $"Failed: {errorMessage}",
|
|
262
|
-
DeviceAuthFlowState.Expired => "Expired — try again",
|
|
263
|
-
DeviceAuthFlowState.Cancelled => "Cancelled",
|
|
264
|
-
_ => ""
|
|
265
|
-
};
|
|
266
|
-
|
|
267
|
-
private void SetupCloudAuthSection(VisualElement root)
|
|
268
|
-
{
|
|
269
|
-
var inputCloudToken = root.Q<TextField>("inputCloudToken");
|
|
270
|
-
var btnRevoke = root.Q<Button>("btnCloudRevoke");
|
|
271
|
-
var btnAuthorize = root.Q<Button>("btnCloudAuthorize");
|
|
272
|
-
var statusLabel = root.Q<Label>("labelCloudAuthStatus");
|
|
273
|
-
if (inputCloudToken == null || btnAuthorize == null) return;
|
|
274
|
-
|
|
275
|
-
_btnAuthorize = btnAuthorize;
|
|
276
|
-
|
|
277
|
-
inputCloudToken.isPasswordField = true;
|
|
278
|
-
inputCloudToken.RegisterCallback<KeyDownEvent>(evt =>
|
|
279
|
-
{
|
|
280
|
-
if (evt.keyCode == KeyCode.C && (evt.ctrlKey || evt.commandKey))
|
|
281
|
-
{
|
|
282
|
-
GUIUtility.systemCopyBuffer = inputCloudToken.value;
|
|
283
|
-
evt.StopPropagation();
|
|
284
|
-
}
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
const string tokenPlaceholder = "Token — press Authorize";
|
|
288
|
-
void SetTokenValue(string? token)
|
|
289
|
-
{
|
|
290
|
-
var isEmpty = string.IsNullOrEmpty(token);
|
|
291
|
-
inputCloudToken.value = isEmpty ? tokenPlaceholder : token!;
|
|
292
|
-
inputCloudToken.EnableInClassList("token-placeholder", isEmpty);
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
SetTokenValue(UnityCopilotPluginEditor.CloudToken);
|
|
296
|
-
UpdateCloudAuthState();
|
|
297
|
-
|
|
298
|
-
void UpdateRevokeButtonVisibility()
|
|
299
|
-
{
|
|
300
|
-
if (btnRevoke != null)
|
|
301
|
-
btnRevoke.style.display = string.IsNullOrEmpty(UnityCopilotPluginEditor.CloudToken)
|
|
302
|
-
? DisplayStyle.None
|
|
303
|
-
: DisplayStyle.Flex;
|
|
304
|
-
}
|
|
305
|
-
UpdateRevokeButtonVisibility();
|
|
306
|
-
|
|
307
|
-
btnRevoke?.RegisterCallback<ClickEvent>(evt =>
|
|
308
|
-
{
|
|
309
|
-
UnityCopilotPluginEditor.CloudToken = null;
|
|
310
|
-
UnityCopilotPluginEditor.Instance.Save();
|
|
311
|
-
SetTokenValue(null);
|
|
312
|
-
UpdateRevokeButtonVisibility();
|
|
313
|
-
|
|
314
|
-
if (statusLabel != null)
|
|
315
|
-
{
|
|
316
|
-
statusLabel.text = "Token revoked.";
|
|
317
|
-
statusLabel.style.display = DisplayStyle.Flex;
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
UpdateCloudAuthState();
|
|
321
|
-
|
|
322
|
-
// Disconnect if currently in Cloud mode
|
|
323
|
-
if (UnityCopilotPluginEditor.ConnectionMode == ConnectionMode.Cloud
|
|
324
|
-
&& UnityCopilotPluginEditor.Instance.HasMcpPluginInstance)
|
|
325
|
-
_ = UnityCopilotPluginEditor.Instance.Disconnect();
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
_startAuthorizeAction = async () =>
|
|
329
|
-
{
|
|
330
|
-
// If currently running, cancel
|
|
331
|
-
if (_deviceAuthFlow != null && IsAuthFlowRunning(_deviceAuthFlow.State))
|
|
332
|
-
{
|
|
333
|
-
_deviceAuthFlow.Cancel();
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
_deviceAuthFlow?.Cancel();
|
|
338
|
-
_deviceAuthFlow = new DeviceAuthFlow();
|
|
339
|
-
var capturedFlow = _deviceAuthFlow; // Capture to avoid stale field reference in async callbacks
|
|
340
|
-
|
|
341
|
-
capturedFlow.OnStateChanged += state =>
|
|
342
|
-
{
|
|
343
|
-
// Use RunAsync (EditorApplication.update-based) instead of delayCall so that
|
|
344
|
-
// the UI updates even when the Unity Editor window is not focused — delayCall
|
|
345
|
-
// is throttled/paused when Unity loses application focus.
|
|
346
|
-
MainThread.Instance.RunAsync(() =>
|
|
347
|
-
{
|
|
348
|
-
// Ignore stale events from a previous auth flow
|
|
349
|
-
if (_deviceAuthFlow != capturedFlow) return;
|
|
350
|
-
|
|
351
|
-
if (statusLabel != null)
|
|
352
|
-
{
|
|
353
|
-
statusLabel.text = GetAuthFlowStatusMessage(state, capturedFlow.UserCode, capturedFlow.ErrorMessage);
|
|
354
|
-
statusLabel.style.display = string.IsNullOrEmpty(statusLabel.text)
|
|
355
|
-
? DisplayStyle.None
|
|
356
|
-
: DisplayStyle.Flex;
|
|
357
|
-
}
|
|
358
|
-
if (state == DeviceAuthFlowState.Authorized && inputCloudToken != null)
|
|
359
|
-
{
|
|
360
|
-
SetTokenValue(UnityCopilotPluginEditor.CloudToken);
|
|
361
|
-
UpdateRevokeButtonVisibility();
|
|
362
|
-
UpdateCloudAuthState();
|
|
363
|
-
}
|
|
364
|
-
if (state == DeviceAuthFlowState.Authorized)
|
|
365
|
-
{
|
|
366
|
-
// Reconnect to cloud server with the new token (only if still in Cloud mode)
|
|
367
|
-
if (UnityCopilotPluginEditor.ConnectionMode == ConnectionMode.Cloud)
|
|
368
|
-
ReconnectAfterModeSwitch();
|
|
369
|
-
}
|
|
370
|
-
if (btnAuthorize != null)
|
|
371
|
-
{
|
|
372
|
-
btnAuthorize.text = IsAuthFlowRunning(state) ? "Cancel" : "Authorize";
|
|
373
|
-
}
|
|
374
|
-
Repaint();
|
|
375
|
-
});
|
|
376
|
-
};
|
|
377
|
-
|
|
378
|
-
await capturedFlow.StartAsync(UnityCopilotPlugin.UnityConnectionConfig.CloudServerBaseUrl, "Unity Editor");
|
|
379
|
-
};
|
|
380
|
-
|
|
381
|
-
btnAuthorize.RegisterCallback<ClickEvent>(_ => _startAuthorizeAction?.Invoke());
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
private void SetupConnectionAlerts(VisualElement root)
|
|
385
|
-
{
|
|
386
|
-
var container = root.Q<VisualElement>("connectionAlertContainer");
|
|
387
|
-
if (container == null) return;
|
|
388
|
-
|
|
389
|
-
// Auth alert — shown when Cloud mode is active but no token
|
|
390
|
-
_connectionAuthAlert = new AlertPanel(
|
|
391
|
-
"Authorization Required",
|
|
392
|
-
"Cloud mode requires authentication to connect. Press the button below to authorize your device."
|
|
393
|
-
);
|
|
394
|
-
_connectionAuthAlert.SetButton("Authorize", () => _startAuthorizeAction?.Invoke());
|
|
395
|
-
container.Add(_connectionAuthAlert.Root);
|
|
396
|
-
|
|
397
|
-
// Connect alert — shown when authorized but Unity is not connected
|
|
398
|
-
_connectionConnectAlert = new AlertPanel(
|
|
399
|
-
"Connection Required",
|
|
400
|
-
"Cloud authorization is complete but Unity is not connected to the server."
|
|
401
|
-
);
|
|
402
|
-
_connectionConnectAlert.SetButton("Connect", ConnectToServer);
|
|
403
|
-
container.Add(_connectionConnectAlert.Root);
|
|
404
|
-
|
|
405
|
-
// Initial visibility
|
|
406
|
-
UpdateCloudAuthState();
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
}
|
|
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 com.IvanMurzak.ReflectorNet.Utils;
|
|
14
|
+
using com.AtelierAI.Unity.Copilot.Editor.UI.Controls;
|
|
15
|
+
using com.AtelierAI.Uco.Framework;
|
|
16
|
+
using R3;
|
|
17
|
+
using UnityEngine;
|
|
18
|
+
using UnityEngine.UIElements;
|
|
19
|
+
using static com.AtelierAI.Uco.Framework.Common.Consts.MCP.Server;
|
|
20
|
+
|
|
21
|
+
namespace com.AtelierAI.Unity.Copilot.Editor.UI
|
|
22
|
+
{
|
|
23
|
+
public partial class MainWindowEditor
|
|
24
|
+
{
|
|
25
|
+
private TextField? _inputFieldHost;
|
|
26
|
+
private VisualElement? _connectionStatusCircle;
|
|
27
|
+
private Label? _connectionStatusText;
|
|
28
|
+
private readonly SerialDisposable _authRejectedSubscription = new();
|
|
29
|
+
|
|
30
|
+
private void SetupConnectionSection(VisualElement root)
|
|
31
|
+
{
|
|
32
|
+
_inputFieldHost = root.Q<TextField>("InputServerURL");
|
|
33
|
+
var btnConnect = root.Q<Button>("btnConnectOrDisconnect");
|
|
34
|
+
_connectionStatusCircle = root.Q<VisualElement>("connectionStatusCircle");
|
|
35
|
+
_connectionStatusText = root.Q<Label>("connectionStatusText");
|
|
36
|
+
|
|
37
|
+
_btnConnect = btnConnect;
|
|
38
|
+
_timelinePointUnity = root.Q<VisualElement>("TimelinePointUnity");
|
|
39
|
+
if (_timelinePointUnity != null)
|
|
40
|
+
_timelinePointUnity.tooltip = Tooltip_UnityTimelineLabel;
|
|
41
|
+
if (_connectionStatusCircle != null)
|
|
42
|
+
_connectionStatusCircle.tooltip = Tooltip_UnityTimelineLabel;
|
|
43
|
+
if (_connectionStatusText != null)
|
|
44
|
+
_connectionStatusText.tooltip = Tooltip_UnityTimelineLabel;
|
|
45
|
+
|
|
46
|
+
_aiAgentLabelsContainer = root.Q<VisualElement>("aiAgentLabelsContainer");
|
|
47
|
+
_aiAgentStatusCircle = root.Q<VisualElement>("aiAgentStatusCircle");
|
|
48
|
+
|
|
49
|
+
var timelinePointAiAgent = root.Q<VisualElement>("TimelinePointAiAgent");
|
|
50
|
+
if (timelinePointAiAgent != null)
|
|
51
|
+
timelinePointAiAgent.tooltip = Tooltip_AiAgentTimelineLabel;
|
|
52
|
+
|
|
53
|
+
_inputFieldHost.value = UnityCopilotPluginEditor.LocalHost;
|
|
54
|
+
_inputFieldHost.RegisterCallback<FocusOutEvent>(evt =>
|
|
55
|
+
{
|
|
56
|
+
var newValue = _inputFieldHost.value;
|
|
57
|
+
if (UnityCopilotPluginEditor.LocalHost == newValue)
|
|
58
|
+
return;
|
|
59
|
+
|
|
60
|
+
UnityCopilotPluginEditor.LocalHost = newValue;
|
|
61
|
+
SaveChanges($"[{nameof(MainWindowEditor)}] Host Changed: {newValue}");
|
|
62
|
+
Invalidate();
|
|
63
|
+
|
|
64
|
+
UnityCopilotPluginEditor.Instance.DisposeMcpPluginInstance();
|
|
65
|
+
UnityBuildAndConnect();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
SubscribeToConnectionState((state, keepConnected) =>
|
|
69
|
+
{
|
|
70
|
+
UpdateConnectionUI(state, keepConnected);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
UnityCopilotPluginEditor.PluginProperty
|
|
74
|
+
.WhereNotNull()
|
|
75
|
+
.Subscribe(plugin =>
|
|
76
|
+
{
|
|
77
|
+
_authRejectedSubscription.Disposable = plugin.OnAuthorizationRejected
|
|
78
|
+
.ObserveOnCurrentSynchronizationContext()
|
|
79
|
+
.Subscribe(_ => OnAuthorizationRejected());
|
|
80
|
+
})
|
|
81
|
+
.AddTo(_disposables);
|
|
82
|
+
|
|
83
|
+
btnConnect.RegisterCallback<ClickEvent>(evt => HandleConnectButton(btnConnect.text));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private void OnAuthorizationRejected()
|
|
87
|
+
{
|
|
88
|
+
Debug.LogWarning("[Unity Copilot] The server rejected the authorization token. " +
|
|
89
|
+
"The token has been cleared. Update the token (or regenerate it) and reconnect.");
|
|
90
|
+
|
|
91
|
+
UnityCopilotPluginEditor.LocalToken = null;
|
|
92
|
+
UnityCopilotPluginEditor.Instance.Save();
|
|
93
|
+
RefreshConnectionUI();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private void UpdateConnectionUI(ConnectionState state, bool keepConnected)
|
|
97
|
+
{
|
|
98
|
+
if (_inputFieldHost == null || _connectionStatusText == null
|
|
99
|
+
|| _btnConnect == null || _connectionStatusCircle == null)
|
|
100
|
+
return;
|
|
101
|
+
|
|
102
|
+
UpdateHostFieldState(_inputFieldHost, keepConnected, state);
|
|
103
|
+
_connectionStatusText.text = "Unity: " + GetConnectionStatusText(state, keepConnected);
|
|
104
|
+
_btnConnect.text = GetButtonText(state, keepConnected);
|
|
105
|
+
var isConnect = _btnConnect.text == ServerButtonText_Connect;
|
|
106
|
+
_btnConnect.EnableInClassList("btn-primary", isConnect);
|
|
107
|
+
_btnConnect.EnableInClassList("btn-secondary", !isConnect);
|
|
108
|
+
SetStatusIndicator(_connectionStatusCircle, GetConnectionStatusClass(state, keepConnected));
|
|
109
|
+
|
|
110
|
+
if (!(state == ConnectionState.Connected && keepConnected))
|
|
111
|
+
SetAiAgentStatus(false);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/// <summary>
|
|
115
|
+
/// Reads the current connection state and refreshes the Unity connection row UI.
|
|
116
|
+
/// Call this whenever the UI might be stale.
|
|
117
|
+
/// </summary>
|
|
118
|
+
private void RefreshConnectionUI()
|
|
119
|
+
{
|
|
120
|
+
var state = UnityCopilotPluginEditor.ConnectionState.CurrentValue;
|
|
121
|
+
var keepConnected = UnityCopilotPluginEditor.KeepConnected;
|
|
122
|
+
UpdateConnectionUI(state, keepConnected);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// <summary>
|
|
126
|
+
/// Schedules a delayed <see cref="RefreshConnectionUI"/> to catch state changes
|
|
127
|
+
/// that arrive after a reconnect (e.g. async handshake).
|
|
128
|
+
/// </summary>
|
|
129
|
+
private void ScheduleConnectionUIRefresh()
|
|
130
|
+
{
|
|
131
|
+
rootVisualElement?.schedule.Execute(() => RefreshConnectionUI()).ExecuteLater(500);
|
|
132
|
+
rootVisualElement?.schedule.Execute(() => RefreshConnectionUI()).ExecuteLater(2000);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
internal static bool IsHostFieldReadOnly(bool keepConnected, ConnectionState state) =>
|
|
136
|
+
keepConnected || state != ConnectionState.Disconnected;
|
|
137
|
+
|
|
138
|
+
private static void UpdateHostFieldState(TextField field, bool keepConnected, ConnectionState state)
|
|
139
|
+
{
|
|
140
|
+
var isReadOnly = IsHostFieldReadOnly(keepConnected, state);
|
|
141
|
+
field.isReadOnly = isReadOnly;
|
|
142
|
+
var defaultUrl = $"http://localhost:{UnityCopilotPlugin.GeneratePortFromDirectory()}";
|
|
143
|
+
field.tooltip = keepConnected
|
|
144
|
+
? "Editable only when Unity disconnected from the server."
|
|
145
|
+
: $"Usually the server is hosted locally at {defaultUrl}. Feel free to connect to a remote server if needed. The connection is established using WebSocket.";
|
|
146
|
+
|
|
147
|
+
field.EnableInClassList("disabled-text-field", isReadOnly);
|
|
148
|
+
field.EnableInClassList("enabled-text-field", !isReadOnly);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
private void HandleConnectButton(string buttonText)
|
|
152
|
+
{
|
|
153
|
+
if (buttonText.Equals(ServerButtonText_Connect, StringComparison.OrdinalIgnoreCase))
|
|
154
|
+
{
|
|
155
|
+
ConnectToServer();
|
|
156
|
+
}
|
|
157
|
+
else
|
|
158
|
+
{
|
|
159
|
+
UnityCopilotPluginEditor.KeepConnected = false;
|
|
160
|
+
UnityCopilotPluginEditor.Instance.Save();
|
|
161
|
+
if (UnityCopilotPluginEditor.Instance.HasMcpPluginInstance)
|
|
162
|
+
_ = UnityCopilotPluginEditor.Instance.Disconnect();
|
|
163
|
+
}
|
|
164
|
+
ScheduleConnectionUIRefresh();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/// <summary>
|
|
168
|
+
/// Initiates connection to the server. Called by the Connect button.
|
|
169
|
+
/// </summary>
|
|
170
|
+
private static void ConnectToServer()
|
|
171
|
+
{
|
|
172
|
+
UnityCopilotPluginEditor.KeepConnected = true;
|
|
173
|
+
UnityCopilotPluginEditor.Instance.Save();
|
|
174
|
+
UnityBuildAndConnect();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
@@ -142,9 +142,7 @@ namespace com.AtelierAI.Unity.Copilot.Editor.UI
|
|
|
142
142
|
"The AI agent connection indicator shows whether an external AI client (Claude, Cursor, " +
|
|
143
143
|
"Copilot, etc.) is currently connected to the server.\n\n" +
|
|
144
144
|
"It is completely normal for the AI agent to appear disconnected:\n" +
|
|
145
|
-
" •
|
|
146
|
-
"between interactions.\n" +
|
|
147
|
-
" • With a local server the AI agent connects only when it needs to invoke a tool " +
|
|
145
|
+
" • The AI agent connects only when it needs to invoke a tool " +
|
|
148
146
|
"or read a resource, then may drop the session.\n\n" +
|
|
149
147
|
"The AI agent will always reconnect automatically whenever it needs to perform an " +
|
|
150
148
|
"action in Unity — no manual intervention is required.\n\n" +
|
|
@@ -170,8 +168,6 @@ namespace com.AtelierAI.Unity.Copilot.Editor.UI
|
|
|
170
168
|
private VisualElement? _aiAgentLabelsContainer;
|
|
171
169
|
private VisualElement? _aiAgentStatusCircle;
|
|
172
170
|
|
|
173
|
-
private DeviceAuthFlow? _deviceAuthFlow;
|
|
174
|
-
|
|
175
171
|
private long _mcpServerDataVersion;
|
|
176
172
|
private long _aiAgentDataVersion;
|
|
177
173
|
|
|
@@ -181,9 +177,6 @@ namespace com.AtelierAI.Unity.Copilot.Editor.UI
|
|
|
181
177
|
|
|
182
178
|
SetupSettingsSection(root);
|
|
183
179
|
SetupConnectionSection(root);
|
|
184
|
-
SetupConnectionModeToggle(root);
|
|
185
|
-
SetupCloudAuthSection(root);
|
|
186
|
-
SetupConnectionAlerts(root);
|
|
187
180
|
SetupMcpServerSection(root);
|
|
188
181
|
SetupAiAgentSection(root);
|
|
189
182
|
SetupDebugButtons(root);
|