@atelierai/uco 1.0.6 → 1.0.7
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 +21 -0
- package/README.md +7 -8
- package/bin/server.mjs +1 -1
- package/bin/stub-plugin.mjs +3 -3
- package/dist/codegen/coerce.js +1 -1
- package/dist/codegen/coerce.js.map +1 -1
- package/dist/codegen/emit.js +2 -2
- package/dist/codegen/emit.js.map +1 -1
- package/dist/codegen/refs.js +1 -1
- package/dist/codegen/refs.js.map +1 -1
- package/dist/codegen/types.js +1 -1
- package/dist/codegen/types.js.map +1 -1
- package/dist/commands/call.js +1 -1
- package/dist/commands/call.js.map +1 -1
- package/dist/commands/devops/configure.js +1 -1
- package/dist/commands/devops/configure.js.map +1 -1
- package/dist/commands/devops/index.js +0 -4
- package/dist/commands/devops/index.js.map +1 -1
- package/dist/commands/devops/install-plugin.js +1 -1
- package/dist/commands/devops/install-plugin.js.map +1 -1
- package/dist/commands/devops/open.js +7 -7
- package/dist/commands/devops/open.js.map +1 -1
- package/dist/commands/devops/status.js +3 -3
- package/dist/commands/devops/status.js.map +1 -1
- package/dist/commands/exec.js +1 -1
- package/dist/commands/exec.js.map +1 -1
- package/dist/commands/install.js +2 -2
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/prompt.js +2 -2
- package/dist/commands/prompt.js.map +1 -1
- package/dist/commands/resource.js +2 -2
- package/dist/commands/resource.js.map +1 -1
- package/dist/commands/update.js +1 -26
- package/dist/commands/update.js.map +1 -1
- package/dist/config/port.js +2 -2
- package/dist/config/port.js.map +1 -1
- package/dist/config/resolve.d.ts +1 -2
- package/dist/config/resolve.js +0 -12
- package/dist/config/resolve.js.map +1 -1
- package/dist/devops/lib/configure.d.ts +1 -1
- package/dist/devops/lib/configure.js +2 -2
- package/dist/devops/lib/configure.js.map +1 -1
- package/dist/devops/lib/install-plugin.d.ts +1 -1
- package/dist/devops/lib/install-plugin.js +1 -1
- package/dist/devops/lib/install-plugin.js.map +1 -1
- package/dist/devops/lib/install.d.ts +2 -2
- package/dist/devops/lib/install.js +5 -5
- package/dist/devops/lib/install.js.map +1 -1
- package/dist/devops/lib/open.js +11 -32
- package/dist/devops/lib/open.js.map +1 -1
- package/dist/devops/lib/remove-plugin.d.ts +1 -1
- package/dist/devops/lib/remove-plugin.js +3 -3
- package/dist/devops/lib/remove-plugin.js.map +1 -1
- package/dist/devops/lib/run-tool.d.ts +2 -2
- package/dist/devops/lib/run-tool.js +2 -2
- package/dist/devops/lib/types.d.ts +17 -54
- package/dist/devops/lib/types.js +2 -2
- package/dist/devops/lib/types.js.map +1 -1
- package/dist/devops/lib/update.d.ts +0 -15
- package/dist/devops/lib/update.js +2 -91
- package/dist/devops/lib/update.js.map +1 -1
- package/dist/devops/utils/agents.d.ts +0 -28
- package/dist/devops/utils/agents.js +0 -462
- package/dist/devops/utils/agents.js.map +1 -1
- package/dist/devops/utils/config.d.ts +9 -18
- package/dist/devops/utils/config.js +7 -20
- package/dist/devops/utils/config.js.map +1 -1
- package/dist/devops/utils/editor-cache.js +1 -1
- package/dist/devops/utils/editor-cache.js.map +1 -1
- package/dist/devops/utils/launch-error-dismiss.d.ts +2 -2
- package/dist/devops/utils/launch-error-dismiss.js +5 -5
- package/dist/devops/utils/manifest.d.ts +2 -2
- package/dist/devops/utils/manifest.js +3 -3
- package/dist/devops/utils/manifest.js.map +1 -1
- package/dist/devops/utils/port.d.ts +1 -1
- package/dist/devops/utils/port.js +1 -1
- package/dist/devops/utils/probe.d.ts +1 -1
- package/dist/devops/utils/probe.js +1 -1
- package/dist/devops/utils/ui.js +2 -2
- package/dist/devops/utils/ui.js.map +1 -1
- package/dist/devops/utils/unity-editor.js +4 -4
- package/dist/devops/utils/unity-editor.js.map +1 -1
- package/dist/devops/utils/unity-hub.js +1 -1
- package/dist/devops/utils/unity-hub.js.map +1 -1
- package/dist/devops/utils/unity-shutdown.d.ts +1 -1
- package/dist/devops/utils/unity-shutdown.js +1 -1
- package/dist/devops/utils/update-check.js +3 -3
- package/dist/devops/utils/update-check.js.map +1 -1
- package/dist/devops/utils/vendor.d.ts +2 -2
- package/dist/devops/utils/vendor.js +4 -4
- package/dist/devops/utils/vendor.js.map +1 -1
- package/dist/generated/tools.js +1 -1
- package/dist/generated/tools.js.map +1 -1
- package/dist/program.js +3 -3
- package/dist/program.js.map +1 -1
- package/dist/server/app.d.ts +5 -5
- package/dist/server/app.js +3 -3
- package/dist/server/app.js.map +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +2 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/rest/auth.d.ts +1 -1
- package/dist/server/rest/auth.js +3 -3
- package/dist/server/rest/auth.js.map +1 -1
- package/dist/server/rest/context.d.ts +2 -2
- package/dist/server/rest/forward.d.ts +2 -2
- package/dist/server/session/store.d.ts +3 -3
- package/dist/server/session/store.js +4 -4
- package/dist/server/session/store.js.map +1 -1
- package/dist/server/stub-client/scenarios.js +8 -8
- package/dist/server/stub-client/scenarios.js.map +1 -1
- package/dist/server/types.d.ts +11 -11
- package/dist/server/types.js +9 -9
- package/dist/server/types.js.map +1 -1
- package/dist/server/ws/hub.d.ts +8 -8
- package/dist/server/ws/hub.js +13 -13
- package/dist/server/ws/hub.js.map +1 -1
- package/dist/server/ws/rpc.d.ts +3 -3
- package/dist/server/ws/rpc.js +4 -4
- package/dist/server/ws/rpc.js.map +1 -1
- package/dist/transport/index.d.ts +1 -1
- package/dist/transport/index.js +3 -3
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/rest.js +1 -1
- package/dist/transport/rest.js.map +1 -1
- package/docs/INSTALL.zh-CN.md +2 -3
- package/package.json +1 -1
- package/skills/uco-setup/SKILL.md +1 -1
- package/skills/unity-editor/references/diagnostics.md +24 -24
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Components/ExtensionPanel.cs +379 -379
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/MenuItems.cs +72 -72
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/UnityCopilotProjectSettingsProvider.cs +64 -64
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.CopilotServer.cs +372 -372
- package/vendor/plugin/com.atelierai.unity.copilot/Plugins/ReflectorNet.dll +0 -0
- package/vendor/plugin/com.atelierai.unity.copilot/Plugins/Uco.Framework.Common.dll +0 -0
- package/vendor/plugin/com.atelierai.unity.copilot/Plugins/Uco.Framework.dll +0 -0
- package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.Config.cs +175 -175
- package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.cs +1 -1
- package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Utils/EnvironmentUtils.cs +368 -368
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Environment/EnvironmentUtilsTests.cs +3 -3
- package/vendor/plugin/com.atelierai.unity.copilot/package.json +1 -1
- package/dist/commands/devops/login.d.ts +0 -2
- package/dist/commands/devops/login.js +0 -29
- package/dist/commands/devops/login.js.map +0 -1
- package/dist/commands/devops/setup-mcp.d.ts +0 -2
- package/dist/commands/devops/setup-mcp.js +0 -41
- package/dist/commands/devops/setup-mcp.js.map +0 -1
- package/dist/devops/commands-upstream/bootstrap-local.d.ts +0 -17
- package/dist/devops/commands-upstream/bootstrap-local.js +0 -87
- package/dist/devops/commands-upstream/bootstrap-local.js.map +0 -1
- package/dist/devops/commands-upstream/close.d.ts +0 -57
- package/dist/devops/commands-upstream/close.js +0 -188
- package/dist/devops/commands-upstream/close.js.map +0 -1
- package/dist/devops/commands-upstream/configure.d.ts +0 -2
- package/dist/devops/commands-upstream/configure.js +0 -87
- package/dist/devops/commands-upstream/configure.js.map +0 -1
- package/dist/devops/commands-upstream/create-project.d.ts +0 -2
- package/dist/devops/commands-upstream/create-project.js +0 -43
- package/dist/devops/commands-upstream/create-project.js.map +0 -1
- package/dist/devops/commands-upstream/install-plugin.d.ts +0 -2
- package/dist/devops/commands-upstream/install-plugin.js +0 -64
- package/dist/devops/commands-upstream/install-plugin.js.map +0 -1
- package/dist/devops/commands-upstream/install-unity.d.ts +0 -2
- package/dist/devops/commands-upstream/install-unity.js +0 -72
- package/dist/devops/commands-upstream/install-unity.js.map +0 -1
- package/dist/devops/commands-upstream/login.d.ts +0 -2
- package/dist/devops/commands-upstream/login.js +0 -33
- package/dist/devops/commands-upstream/login.js.map +0 -1
- package/dist/devops/commands-upstream/open.d.ts +0 -32
- package/dist/devops/commands-upstream/open.js +0 -282
- package/dist/devops/commands-upstream/open.js.map +0 -1
- package/dist/devops/commands-upstream/remove-plugin.d.ts +0 -2
- package/dist/devops/commands-upstream/remove-plugin.js +0 -41
- package/dist/devops/commands-upstream/remove-plugin.js.map +0 -1
- package/dist/devops/commands-upstream/run-system-tool.d.ts +0 -1
- package/dist/devops/commands-upstream/run-system-tool.js +0 -13
- package/dist/devops/commands-upstream/run-system-tool.js.map +0 -1
- package/dist/devops/commands-upstream/run-tool-builder.d.ts +0 -25
- package/dist/devops/commands-upstream/run-tool-builder.js +0 -125
- package/dist/devops/commands-upstream/run-tool-builder.js.map +0 -1
- package/dist/devops/commands-upstream/run-tool.d.ts +0 -1
- package/dist/devops/commands-upstream/run-tool.js +0 -13
- package/dist/devops/commands-upstream/run-tool.js.map +0 -1
- package/dist/devops/commands-upstream/setup-mcp.d.ts +0 -2
- package/dist/devops/commands-upstream/setup-mcp.js +0 -68
- package/dist/devops/commands-upstream/setup-mcp.js.map +0 -1
- package/dist/devops/commands-upstream/setup-skills.d.ts +0 -2
- package/dist/devops/commands-upstream/setup-skills.js +0 -167
- package/dist/devops/commands-upstream/setup-skills.js.map +0 -1
- package/dist/devops/commands-upstream/status.d.ts +0 -2
- package/dist/devops/commands-upstream/status.js +0 -87
- package/dist/devops/commands-upstream/status.js.map +0 -1
- package/dist/devops/commands-upstream/update.d.ts +0 -2
- package/dist/devops/commands-upstream/update.js +0 -77
- package/dist/devops/commands-upstream/update.js.map +0 -1
- package/dist/devops/commands-upstream/wait-for-ready.d.ts +0 -2
- package/dist/devops/commands-upstream/wait-for-ready.js +0 -93
- package/dist/devops/commands-upstream/wait-for-ready.js.map +0 -1
- package/dist/devops/lib/setup-mcp.d.ts +0 -35
- package/dist/devops/lib/setup-mcp.js +0 -177
- package/dist/devops/lib/setup-mcp.js.map +0 -1
- package/dist/devops/utils/cloud-login.d.ts +0 -7
- package/dist/devops/utils/cloud-login.js +0 -56
- package/dist/devops/utils/cloud-login.js.map +0 -1
|
@@ -1,368 +1,368 @@
|
|
|
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.Collections.Generic;
|
|
14
|
-
using com.AtelierAI.Uco.Framework.Common.Utils;
|
|
15
|
-
using com.AtelierAI.Unity.Copilot.Utils;
|
|
16
|
-
using Microsoft.Extensions.Logging;
|
|
17
|
-
using static com.AtelierAI.Uco.Framework.Common.Consts.MCP.Server;
|
|
18
|
-
|
|
19
|
-
namespace com.AtelierAI.Unity.Copilot.Runtime.Utils
|
|
20
|
-
{
|
|
21
|
-
/// <summary>
|
|
22
|
-
/// Loads MCP plugin configuration from layered sources and (optionally) applies
|
|
23
|
-
/// runtime overrides from environment variables and process command-line arguments.
|
|
24
|
-
///
|
|
25
|
-
/// Priority (highest wins):
|
|
26
|
-
/// 1. Process command-line flags (e.g. <c>--url</c>, <c>--token</c>, <c>--auth</c>,
|
|
27
|
-
/// or any <c>--
|
|
28
|
-
/// 2. Process environment variables (<c>
|
|
29
|
-
/// 3. On-disk config (<c>UserSettings/AI-Game-Developer-Config.json</c>)
|
|
30
|
-
/// 4. Built-in defaults
|
|
31
|
-
///
|
|
32
|
-
/// Overrides applied via env vars / flags are NEVER persisted back to disk —
|
|
33
|
-
/// they are runtime-only. The <see cref="OverrideRecord"/> returned by
|
|
34
|
-
/// <see cref="ApplyEnvironmentOverrides"/> captures both the disk-baseline values
|
|
35
|
-
/// and the override values so that the editor's Save() flow can restore the
|
|
36
|
-
/// baseline before serializing. See <c>UnityCopilotPluginEditor.Save</c>.
|
|
37
|
-
/// </summary>
|
|
38
|
-
public static class EnvironmentUtils
|
|
39
|
-
{
|
|
40
|
-
static readonly ILogger _logger = UnityLoggerFactory.LoggerFactory.CreateLogger(nameof(EnvironmentUtils));
|
|
41
|
-
|
|
42
|
-
// Environment variable names for MCP connection overrides.
|
|
43
|
-
public const string EnvHost = "
|
|
44
|
-
public const string EnvKeepConnected = "
|
|
45
|
-
public const string EnvAuthOption = "
|
|
46
|
-
public const string EnvToken = "
|
|
47
|
-
public const string EnvTools = "
|
|
48
|
-
public const string EnvStartServer = "
|
|
49
|
-
public const string EnvTransport = "
|
|
50
|
-
public const string EnvConnectionMode = "
|
|
51
|
-
|
|
52
|
-
// Short flag aliases recognised by the in-plugin command-line parser.
|
|
53
|
-
// The CLI tool already translates these to the equivalent env vars before
|
|
54
|
-
// launching Unity, but the plugin still parses them directly so
|
|
55
|
-
// out-of-band invocations (manual editor launch, IDE config, etc.) work.
|
|
56
|
-
public const string FlagUrl = "url";
|
|
57
|
-
public const string FlagToken = "token";
|
|
58
|
-
public const string FlagAuth = "auth";
|
|
59
|
-
|
|
60
|
-
// Field-name keys used by OverrideRecord to track which fields were overridden
|
|
61
|
-
// by env/flags so the Save flow can restore disk-baseline values before writing.
|
|
62
|
-
public const string FieldHost = nameof(UnityCopilotPlugin.UnityConnectionConfig.LocalHost);
|
|
63
|
-
public const string FieldKeepConnected = nameof(UnityCopilotPlugin.UnityConnectionConfig.KeepConnected);
|
|
64
|
-
public const string FieldAuthOption = nameof(UnityCopilotPlugin.UnityConnectionConfig.AuthOption);
|
|
65
|
-
public const string FieldLocalToken = nameof(UnityCopilotPlugin.UnityConnectionConfig.LocalToken);
|
|
66
|
-
public const string FieldTools = nameof(UnityCopilotPlugin.UnityConnectionConfig.EnabledToolsOverride);
|
|
67
|
-
public const string FieldStartServer = nameof(UnityCopilotPlugin.UnityConnectionConfig.KeepServerRunning);
|
|
68
|
-
public const string FieldTransport = nameof(UnityCopilotPlugin.UnityConnectionConfig.TransportMethod);
|
|
69
|
-
public const string FieldConnectionMode = nameof(UnityCopilotPlugin.UnityConnectionConfig.ConnectionMode);
|
|
70
|
-
|
|
71
|
-
/// <summary>
|
|
72
|
-
/// Captures, per overridden field, the disk-baseline value (what was read from JSON)
|
|
73
|
-
/// and the runtime override value applied on top. Allows <c>Save()</c> to round-trip
|
|
74
|
-
/// the baseline to disk while keeping the runtime override on the in-memory config.
|
|
75
|
-
/// Empty when no env/flag overrides were detected.
|
|
76
|
-
/// </summary>
|
|
77
|
-
public sealed class OverrideRecord
|
|
78
|
-
{
|
|
79
|
-
readonly Dictionary<string, object?> _baselineValues = new();
|
|
80
|
-
readonly Dictionary<string, object?> _overrideValues = new();
|
|
81
|
-
|
|
82
|
-
public IReadOnlyDictionary<string, object?> BaselineValues => _baselineValues;
|
|
83
|
-
public IReadOnlyDictionary<string, object?> OverrideValues => _overrideValues;
|
|
84
|
-
|
|
85
|
-
public bool HasAny => _baselineValues.Count > 0;
|
|
86
|
-
public bool Contains(string fieldName) => _baselineValues.ContainsKey(fieldName);
|
|
87
|
-
|
|
88
|
-
internal void Track(string fieldName, object? baselineValue, object? overrideValue)
|
|
89
|
-
{
|
|
90
|
-
_baselineValues[fieldName] = baselineValue;
|
|
91
|
-
_overrideValues[fieldName] = overrideValue;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/// <summary>
|
|
96
|
-
/// Checks if the current environment is a CI environment.
|
|
97
|
-
/// </summary>
|
|
98
|
-
public static bool IsCi()
|
|
99
|
-
{
|
|
100
|
-
var commandLineArgs = ArgsUtils.ParseCommandLineArguments();
|
|
101
|
-
|
|
102
|
-
var ci = commandLineArgs.GetValueOrDefault("CI") ?? Environment.GetEnvironmentVariable("CI");
|
|
103
|
-
var gha = commandLineArgs.GetValueOrDefault("GITHUB_ACTIONS") ?? Environment.GetEnvironmentVariable("GITHUB_ACTIONS");
|
|
104
|
-
var az = commandLineArgs.GetValueOrDefault("TF_BUILD") ?? Environment.GetEnvironmentVariable("TF_BUILD"); // Azure Pipelines
|
|
105
|
-
|
|
106
|
-
return string.Equals(ci?.Trim()?.Trim('"'), "true", StringComparison.OrdinalIgnoreCase)
|
|
107
|
-
|| string.Equals(gha?.Trim()?.Trim('"'), "true", StringComparison.OrdinalIgnoreCase)
|
|
108
|
-
|| string.Equals(az?.Trim()?.Trim('"'), "true", StringComparison.OrdinalIgnoreCase);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/// <summary>
|
|
112
|
-
/// Checks if the Editor was launched with -batchmode (COCli-13).
|
|
113
|
-
/// Batchmode launches without a configured server (offline builds,
|
|
114
|
-
/// command-line automation) must not run the connection retry loop —
|
|
115
|
-
/// its warnings would pollute BuildReport output.
|
|
116
|
-
/// </summary>
|
|
117
|
-
public static bool IsBatchMode()
|
|
118
|
-
=> IsBatchMode(UnityEngine.Application.isBatchMode, ArgsUtils.ParseCommandLineArguments());
|
|
119
|
-
|
|
120
|
-
/// <summary>Test-friendly overload taking the parsed command-line args.</summary>
|
|
121
|
-
public static bool IsBatchMode(
|
|
122
|
-
bool applicationIsBatchMode,
|
|
123
|
-
IReadOnlyDictionary<string, string> commandLineArgs)
|
|
124
|
-
=> applicationIsBatchMode || commandLineArgs.ContainsKey("batchmode");
|
|
125
|
-
|
|
126
|
-
/// <summary>
|
|
127
|
-
/// Applies environment-variable and command-line-argument overrides to the given config.
|
|
128
|
-
/// Args (highest priority) override env vars, env vars override the disk-baseline values
|
|
129
|
-
/// already present on <paramref name="config"/>. Returns an <see cref="OverrideRecord"/>
|
|
130
|
-
/// that captures the disk-baseline value for each field that was overridden.
|
|
131
|
-
/// </summary>
|
|
132
|
-
public static OverrideRecord ApplyEnvironmentOverrides(UnityCopilotPlugin.UnityConnectionConfig config)
|
|
133
|
-
{
|
|
134
|
-
var args = ArgsUtils.ParseCommandLineArguments();
|
|
135
|
-
return ApplyEnvironmentOverrides(config, args, Environment.GetEnvironmentVariable);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/// <summary>
|
|
139
|
-
/// Test-friendly overload. <paramref name="argReader"/> is the parsed command-line
|
|
140
|
-
/// arg dictionary (keys without leading dashes); <paramref name="envReader"/> resolves
|
|
141
|
-
/// environment variables. Either may be empty / always-null to disable that source.
|
|
142
|
-
/// </summary>
|
|
143
|
-
public static OverrideRecord ApplyEnvironmentOverrides(
|
|
144
|
-
UnityCopilotPlugin.UnityConnectionConfig config,
|
|
145
|
-
IReadOnlyDictionary<string, string> argReader,
|
|
146
|
-
Func<string, string?> envReader)
|
|
147
|
-
{
|
|
148
|
-
if (config == null) throw new ArgumentNullException(nameof(config));
|
|
149
|
-
if (argReader == null) throw new ArgumentNullException(nameof(argReader));
|
|
150
|
-
if (envReader == null) throw new ArgumentNullException(nameof(envReader));
|
|
151
|
-
|
|
152
|
-
var record = new OverrideRecord();
|
|
153
|
-
|
|
154
|
-
// Resolve a value with priority: short-flag-alias (highest) >
|
|
155
|
-
// Returns null if no source provided a non-empty value.
|
|
156
|
-
string? Resolve(string envKey, string? flagAlias)
|
|
157
|
-
{
|
|
158
|
-
if (!string.IsNullOrEmpty(flagAlias)
|
|
159
|
-
&& argReader.TryGetValue(flagAlias, out var flagValue)
|
|
160
|
-
&& !string.IsNullOrWhiteSpace(flagValue))
|
|
161
|
-
{
|
|
162
|
-
return flagValue;
|
|
163
|
-
}
|
|
164
|
-
if (argReader.TryGetValue(envKey, out var envFlagValue)
|
|
165
|
-
&& !string.IsNullOrWhiteSpace(envFlagValue))
|
|
166
|
-
{
|
|
167
|
-
return envFlagValue;
|
|
168
|
-
}
|
|
169
|
-
var envValue = envReader(envKey);
|
|
170
|
-
if (!string.IsNullOrWhiteSpace(envValue))
|
|
171
|
-
return envValue;
|
|
172
|
-
return null;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
string Sanitize(string raw) => raw.Trim().Trim('"');
|
|
176
|
-
|
|
177
|
-
//
|
|
178
|
-
string? sanitizedHost = null;
|
|
179
|
-
var rawHost = Resolve(EnvHost, FlagUrl);
|
|
180
|
-
if (rawHost != null)
|
|
181
|
-
{
|
|
182
|
-
var host = Sanitize(rawHost).TrimEnd('/');
|
|
183
|
-
if (host.Length > 0)
|
|
184
|
-
{
|
|
185
|
-
sanitizedHost = host;
|
|
186
|
-
if (!string.Equals(host, config.LocalHost, StringComparison.Ordinal))
|
|
187
|
-
{
|
|
188
|
-
record.Track(FieldHost, config.LocalHost, host);
|
|
189
|
-
config.LocalHost = host;
|
|
190
|
-
_logger.LogInformation("[MCP] Override: {Key}={Value}", FieldHost, host);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// Loopback URLs without an explicit mode infer Custom (worktree / local-dev).
|
|
196
|
-
// Remote URLs without an explicit mode leave the disk-baseline ConnectionMode untouched.
|
|
197
|
-
var rawMode = Resolve(EnvConnectionMode, flagAlias: null);
|
|
198
|
-
ConnectionMode? targetMode = null;
|
|
199
|
-
if (rawMode != null && Enum.TryParse<ConnectionMode>(Sanitize(rawMode), ignoreCase: true, out var explicitMode))
|
|
200
|
-
{
|
|
201
|
-
targetMode = explicitMode;
|
|
202
|
-
}
|
|
203
|
-
else if (sanitizedHost != null && IsLoopbackUrl(sanitizedHost))
|
|
204
|
-
{
|
|
205
|
-
targetMode = ConnectionMode.Custom;
|
|
206
|
-
}
|
|
207
|
-
if (targetMode.HasValue && targetMode.Value != config.ConnectionMode)
|
|
208
|
-
{
|
|
209
|
-
record.Track(FieldConnectionMode, config.ConnectionMode, targetMode.Value);
|
|
210
|
-
config.ConnectionMode = targetMode.Value;
|
|
211
|
-
_logger.LogInformation("[MCP] Override: {Key}={Value}", FieldConnectionMode, targetMode.Value);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
var rawKeep = Resolve(EnvKeepConnected, flagAlias: null);
|
|
215
|
-
if (rawKeep != null && bool.TryParse(Sanitize(rawKeep), out var keep) && keep != config.KeepConnected)
|
|
216
|
-
{
|
|
217
|
-
record.Track(FieldKeepConnected, config.KeepConnected, keep);
|
|
218
|
-
config.KeepConnected = keep;
|
|
219
|
-
_logger.LogInformation("[MCP] Override: {Key}={Value}", EnvKeepConnected, keep);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
var rawAuth = Resolve(EnvAuthOption, FlagAuth);
|
|
223
|
-
if (rawAuth != null
|
|
224
|
-
&& Enum.TryParse<AuthOption>(Sanitize(rawAuth), ignoreCase: true, out var ao)
|
|
225
|
-
&& ao != config.AuthOption)
|
|
226
|
-
{
|
|
227
|
-
record.Track(FieldAuthOption, config.AuthOption, ao);
|
|
228
|
-
config.AuthOption = ao;
|
|
229
|
-
_logger.LogInformation("[MCP] Override: {Key}={Value}", EnvAuthOption, ao);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// We track the specific
|
|
233
|
-
// backing field rather than the abstract Token property because only the backing
|
|
234
|
-
// fields are serialised — restoring the baseline must target the same field that
|
|
235
|
-
// was clobbered.
|
|
236
|
-
var rawToken = Resolve(EnvToken, FlagToken);
|
|
237
|
-
if (rawToken != null)
|
|
238
|
-
{
|
|
239
|
-
var token = Sanitize(rawToken);
|
|
240
|
-
if (!string.Equals(token, config.LocalToken, StringComparison.Ordinal))
|
|
241
|
-
{
|
|
242
|
-
record.Track(FieldLocalToken, config.LocalToken, token);
|
|
243
|
-
config.LocalToken = token;
|
|
244
|
-
_logger.LogInformation("[MCP] Override: {Key}=*** (LocalToken)", EnvToken);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
var rawTransport = Resolve(EnvTransport, flagAlias: null);
|
|
249
|
-
if (rawTransport != null
|
|
250
|
-
&& Enum.TryParse<TransportMethod>(Sanitize(rawTransport), ignoreCase: true, out var tm)
|
|
251
|
-
&& tm != config.TransportMethod)
|
|
252
|
-
{
|
|
253
|
-
record.Track(FieldTransport, config.TransportMethod, tm);
|
|
254
|
-
config.TransportMethod = tm;
|
|
255
|
-
_logger.LogInformation("[MCP] Override: {Key}={Value}", EnvTransport, tm);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
var rawStart = Resolve(EnvStartServer, flagAlias: null);
|
|
259
|
-
if (rawStart != null && bool.TryParse(Sanitize(rawStart), out var ss) && ss != config.KeepServerRunning)
|
|
260
|
-
{
|
|
261
|
-
record.Track(FieldStartServer, config.KeepServerRunning, ss);
|
|
262
|
-
config.KeepServerRunning = ss;
|
|
263
|
-
_logger.LogInformation("[MCP] Override: {Key}={Value}", EnvStartServer, ss);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
// EnabledToolsOverride is [JsonIgnore] so it is never persisted regardless of the
|
|
267
|
-
// baseline-restoration logic; we still track it for completeness.
|
|
268
|
-
var rawTools = Resolve(EnvTools, flagAlias: null);
|
|
269
|
-
if (rawTools != null)
|
|
270
|
-
{
|
|
271
|
-
var ids = rawTools.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
|
272
|
-
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
273
|
-
var trimmed = new List<string>(ids.Length);
|
|
274
|
-
foreach (var id in ids)
|
|
275
|
-
{
|
|
276
|
-
var value = Sanitize(id);
|
|
277
|
-
if (!string.IsNullOrWhiteSpace(value) && seen.Add(value))
|
|
278
|
-
trimmed.Add(value);
|
|
279
|
-
}
|
|
280
|
-
record.Track(FieldTools, config.EnabledToolsOverride, trimmed);
|
|
281
|
-
config.EnabledToolsOverride = trimmed;
|
|
282
|
-
_logger.LogInformation("[MCP] Override: {Key}={Value}", EnvTools, rawTools.Trim());
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
return record;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
/// <summary>
|
|
289
|
-
/// Applies the BaselineValues from <paramref name="record"/> to <paramref name="config"/>
|
|
290
|
-
/// (i.e. reverses runtime overrides so the in-memory config reflects what is on disk).
|
|
291
|
-
/// Used by the editor's Save() flow to ensure runtime-only overrides are not persisted.
|
|
292
|
-
/// </summary>
|
|
293
|
-
public static void ApplyBaseline(UnityCopilotPlugin.UnityConnectionConfig config, OverrideRecord record)
|
|
294
|
-
{
|
|
295
|
-
if (config == null) throw new ArgumentNullException(nameof(config));
|
|
296
|
-
if (record == null) throw new ArgumentNullException(nameof(record));
|
|
297
|
-
ApplyValues(config, record.BaselineValues);
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
/// <summary>
|
|
301
|
-
/// Re-applies the OverrideValues from <paramref name="record"/> to <paramref name="config"/>
|
|
302
|
-
/// (i.e. restores runtime overrides after a temporary baseline-restore).
|
|
303
|
-
/// </summary>
|
|
304
|
-
public static void ApplyOverrides(UnityCopilotPlugin.UnityConnectionConfig config, OverrideRecord record)
|
|
305
|
-
{
|
|
306
|
-
if (config == null) throw new ArgumentNullException(nameof(config));
|
|
307
|
-
if (record == null) throw new ArgumentNullException(nameof(record));
|
|
308
|
-
ApplyValues(config, record.OverrideValues);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
static void ApplyValues(UnityCopilotPlugin.UnityConnectionConfig config, IReadOnlyDictionary<string, object?> values)
|
|
312
|
-
{
|
|
313
|
-
foreach (var kvp in values)
|
|
314
|
-
{
|
|
315
|
-
switch (kvp.Key)
|
|
316
|
-
{
|
|
317
|
-
case FieldHost:
|
|
318
|
-
config.LocalHost = (string?)kvp.Value ?? UnityCopilotPlugin.UnityConnectionConfig.DefaultHost;
|
|
319
|
-
break;
|
|
320
|
-
case FieldKeepConnected:
|
|
321
|
-
if (kvp.Value is bool kc) config.KeepConnected = kc;
|
|
322
|
-
break;
|
|
323
|
-
case FieldAuthOption:
|
|
324
|
-
if (kvp.Value is AuthOption ao) config.AuthOption = ao;
|
|
325
|
-
break;
|
|
326
|
-
case FieldLocalToken:
|
|
327
|
-
config.LocalToken = (string?)kvp.Value;
|
|
328
|
-
break;
|
|
329
|
-
case FieldTools:
|
|
330
|
-
config.EnabledToolsOverride = (List<string>?)kvp.Value;
|
|
331
|
-
break;
|
|
332
|
-
case FieldStartServer:
|
|
333
|
-
if (kvp.Value is bool ss) config.KeepServerRunning = ss;
|
|
334
|
-
break;
|
|
335
|
-
case FieldTransport:
|
|
336
|
-
if (kvp.Value is TransportMethod tm) config.TransportMethod = tm;
|
|
337
|
-
break;
|
|
338
|
-
case FieldConnectionMode:
|
|
339
|
-
if (kvp.Value is ConnectionMode cm) config.ConnectionMode = cm;
|
|
340
|
-
break;
|
|
341
|
-
default:
|
|
342
|
-
// Fail loudly if a new Field* constant is added to Track but forgotten here —
|
|
343
|
-
// a silent miss would let runtime overrides leak to disk on Save.
|
|
344
|
-
throw new InvalidOperationException($"Unhandled override field: {kvp.Key}");
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
/// <summary>
|
|
350
|
-
/// Returns true if the given URL targets a loopback host
|
|
351
|
-
/// (<c>localhost</c>, <c>127.0.0.0/8</c>, or IPv6 <c>::1</c>).
|
|
352
|
-
/// Used to infer <see cref="ConnectionMode.Custom"/> when a worktree-style local URL is supplied.
|
|
353
|
-
/// </summary>
|
|
354
|
-
public static bool IsLoopbackUrl(string url)
|
|
355
|
-
{
|
|
356
|
-
if (string.IsNullOrWhiteSpace(url))
|
|
357
|
-
return false;
|
|
358
|
-
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
|
|
359
|
-
return false;
|
|
360
|
-
var host = uri.Host;
|
|
361
|
-
if (string.Equals(host, "localhost", StringComparison.OrdinalIgnoreCase))
|
|
362
|
-
return true;
|
|
363
|
-
if (System.Net.IPAddress.TryParse(host, out var ip))
|
|
364
|
-
return System.Net.IPAddress.IsLoopback(ip);
|
|
365
|
-
return false;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
}
|
|
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.Collections.Generic;
|
|
14
|
+
using com.AtelierAI.Uco.Framework.Common.Utils;
|
|
15
|
+
using com.AtelierAI.Unity.Copilot.Utils;
|
|
16
|
+
using Microsoft.Extensions.Logging;
|
|
17
|
+
using static com.AtelierAI.Uco.Framework.Common.Consts.MCP.Server;
|
|
18
|
+
|
|
19
|
+
namespace com.AtelierAI.Unity.Copilot.Runtime.Utils
|
|
20
|
+
{
|
|
21
|
+
/// <summary>
|
|
22
|
+
/// Loads MCP plugin configuration from layered sources and (optionally) applies
|
|
23
|
+
/// runtime overrides from environment variables and process command-line arguments.
|
|
24
|
+
///
|
|
25
|
+
/// Priority (highest wins):
|
|
26
|
+
/// 1. Process command-line flags (e.g. <c>--url</c>, <c>--token</c>, <c>--auth</c>,
|
|
27
|
+
/// or any <c>--UNITY_COPILOT_*</c> variant)
|
|
28
|
+
/// 2. Process environment variables (<c>UNITY_COPILOT_*</c>)
|
|
29
|
+
/// 3. On-disk config (<c>UserSettings/AI-Game-Developer-Config.json</c>)
|
|
30
|
+
/// 4. Built-in defaults
|
|
31
|
+
///
|
|
32
|
+
/// Overrides applied via env vars / flags are NEVER persisted back to disk —
|
|
33
|
+
/// they are runtime-only. The <see cref="OverrideRecord"/> returned by
|
|
34
|
+
/// <see cref="ApplyEnvironmentOverrides"/> captures both the disk-baseline values
|
|
35
|
+
/// and the override values so that the editor's Save() flow can restore the
|
|
36
|
+
/// baseline before serializing. See <c>UnityCopilotPluginEditor.Save</c>.
|
|
37
|
+
/// </summary>
|
|
38
|
+
public static class EnvironmentUtils
|
|
39
|
+
{
|
|
40
|
+
static readonly ILogger _logger = UnityLoggerFactory.LoggerFactory.CreateLogger(nameof(EnvironmentUtils));
|
|
41
|
+
|
|
42
|
+
// Environment variable names for MCP connection overrides.
|
|
43
|
+
public const string EnvHost = "UNITY_COPILOT_HOST";
|
|
44
|
+
public const string EnvKeepConnected = "UNITY_COPILOT_KEEP_CONNECTED";
|
|
45
|
+
public const string EnvAuthOption = "UNITY_COPILOT_AUTH_OPTION";
|
|
46
|
+
public const string EnvToken = "UNITY_COPILOT_TOKEN";
|
|
47
|
+
public const string EnvTools = "UNITY_COPILOT_TOOLS";
|
|
48
|
+
public const string EnvStartServer = "UNITY_COPILOT_START_SERVER";
|
|
49
|
+
public const string EnvTransport = "UNITY_COPILOT_TRANSPORT";
|
|
50
|
+
public const string EnvConnectionMode = "UNITY_COPILOT_CONNECTION_MODE";
|
|
51
|
+
|
|
52
|
+
// Short flag aliases recognised by the in-plugin command-line parser.
|
|
53
|
+
// The CLI tool already translates these to the equivalent env vars before
|
|
54
|
+
// launching Unity, but the plugin still parses them directly so
|
|
55
|
+
// out-of-band invocations (manual editor launch, IDE config, etc.) work.
|
|
56
|
+
public const string FlagUrl = "url";
|
|
57
|
+
public const string FlagToken = "token";
|
|
58
|
+
public const string FlagAuth = "auth";
|
|
59
|
+
|
|
60
|
+
// Field-name keys used by OverrideRecord to track which fields were overridden
|
|
61
|
+
// by env/flags so the Save flow can restore disk-baseline values before writing.
|
|
62
|
+
public const string FieldHost = nameof(UnityCopilotPlugin.UnityConnectionConfig.LocalHost);
|
|
63
|
+
public const string FieldKeepConnected = nameof(UnityCopilotPlugin.UnityConnectionConfig.KeepConnected);
|
|
64
|
+
public const string FieldAuthOption = nameof(UnityCopilotPlugin.UnityConnectionConfig.AuthOption);
|
|
65
|
+
public const string FieldLocalToken = nameof(UnityCopilotPlugin.UnityConnectionConfig.LocalToken);
|
|
66
|
+
public const string FieldTools = nameof(UnityCopilotPlugin.UnityConnectionConfig.EnabledToolsOverride);
|
|
67
|
+
public const string FieldStartServer = nameof(UnityCopilotPlugin.UnityConnectionConfig.KeepServerRunning);
|
|
68
|
+
public const string FieldTransport = nameof(UnityCopilotPlugin.UnityConnectionConfig.TransportMethod);
|
|
69
|
+
public const string FieldConnectionMode = nameof(UnityCopilotPlugin.UnityConnectionConfig.ConnectionMode);
|
|
70
|
+
|
|
71
|
+
/// <summary>
|
|
72
|
+
/// Captures, per overridden field, the disk-baseline value (what was read from JSON)
|
|
73
|
+
/// and the runtime override value applied on top. Allows <c>Save()</c> to round-trip
|
|
74
|
+
/// the baseline to disk while keeping the runtime override on the in-memory config.
|
|
75
|
+
/// Empty when no env/flag overrides were detected.
|
|
76
|
+
/// </summary>
|
|
77
|
+
public sealed class OverrideRecord
|
|
78
|
+
{
|
|
79
|
+
readonly Dictionary<string, object?> _baselineValues = new();
|
|
80
|
+
readonly Dictionary<string, object?> _overrideValues = new();
|
|
81
|
+
|
|
82
|
+
public IReadOnlyDictionary<string, object?> BaselineValues => _baselineValues;
|
|
83
|
+
public IReadOnlyDictionary<string, object?> OverrideValues => _overrideValues;
|
|
84
|
+
|
|
85
|
+
public bool HasAny => _baselineValues.Count > 0;
|
|
86
|
+
public bool Contains(string fieldName) => _baselineValues.ContainsKey(fieldName);
|
|
87
|
+
|
|
88
|
+
internal void Track(string fieldName, object? baselineValue, object? overrideValue)
|
|
89
|
+
{
|
|
90
|
+
_baselineValues[fieldName] = baselineValue;
|
|
91
|
+
_overrideValues[fieldName] = overrideValue;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// <summary>
|
|
96
|
+
/// Checks if the current environment is a CI environment.
|
|
97
|
+
/// </summary>
|
|
98
|
+
public static bool IsCi()
|
|
99
|
+
{
|
|
100
|
+
var commandLineArgs = ArgsUtils.ParseCommandLineArguments();
|
|
101
|
+
|
|
102
|
+
var ci = commandLineArgs.GetValueOrDefault("CI") ?? Environment.GetEnvironmentVariable("CI");
|
|
103
|
+
var gha = commandLineArgs.GetValueOrDefault("GITHUB_ACTIONS") ?? Environment.GetEnvironmentVariable("GITHUB_ACTIONS");
|
|
104
|
+
var az = commandLineArgs.GetValueOrDefault("TF_BUILD") ?? Environment.GetEnvironmentVariable("TF_BUILD"); // Azure Pipelines
|
|
105
|
+
|
|
106
|
+
return string.Equals(ci?.Trim()?.Trim('"'), "true", StringComparison.OrdinalIgnoreCase)
|
|
107
|
+
|| string.Equals(gha?.Trim()?.Trim('"'), "true", StringComparison.OrdinalIgnoreCase)
|
|
108
|
+
|| string.Equals(az?.Trim()?.Trim('"'), "true", StringComparison.OrdinalIgnoreCase);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// <summary>
|
|
112
|
+
/// Checks if the Editor was launched with -batchmode (COCli-13).
|
|
113
|
+
/// Batchmode launches without a configured server (offline builds,
|
|
114
|
+
/// command-line automation) must not run the connection retry loop —
|
|
115
|
+
/// its warnings would pollute BuildReport output.
|
|
116
|
+
/// </summary>
|
|
117
|
+
public static bool IsBatchMode()
|
|
118
|
+
=> IsBatchMode(UnityEngine.Application.isBatchMode, ArgsUtils.ParseCommandLineArguments());
|
|
119
|
+
|
|
120
|
+
/// <summary>Test-friendly overload taking the parsed command-line args.</summary>
|
|
121
|
+
public static bool IsBatchMode(
|
|
122
|
+
bool applicationIsBatchMode,
|
|
123
|
+
IReadOnlyDictionary<string, string> commandLineArgs)
|
|
124
|
+
=> applicationIsBatchMode || commandLineArgs.ContainsKey("batchmode");
|
|
125
|
+
|
|
126
|
+
/// <summary>
|
|
127
|
+
/// Applies environment-variable and command-line-argument overrides to the given config.
|
|
128
|
+
/// Args (highest priority) override env vars, env vars override the disk-baseline values
|
|
129
|
+
/// already present on <paramref name="config"/>. Returns an <see cref="OverrideRecord"/>
|
|
130
|
+
/// that captures the disk-baseline value for each field that was overridden.
|
|
131
|
+
/// </summary>
|
|
132
|
+
public static OverrideRecord ApplyEnvironmentOverrides(UnityCopilotPlugin.UnityConnectionConfig config)
|
|
133
|
+
{
|
|
134
|
+
var args = ArgsUtils.ParseCommandLineArguments();
|
|
135
|
+
return ApplyEnvironmentOverrides(config, args, Environment.GetEnvironmentVariable);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/// <summary>
|
|
139
|
+
/// Test-friendly overload. <paramref name="argReader"/> is the parsed command-line
|
|
140
|
+
/// arg dictionary (keys without leading dashes); <paramref name="envReader"/> resolves
|
|
141
|
+
/// environment variables. Either may be empty / always-null to disable that source.
|
|
142
|
+
/// </summary>
|
|
143
|
+
public static OverrideRecord ApplyEnvironmentOverrides(
|
|
144
|
+
UnityCopilotPlugin.UnityConnectionConfig config,
|
|
145
|
+
IReadOnlyDictionary<string, string> argReader,
|
|
146
|
+
Func<string, string?> envReader)
|
|
147
|
+
{
|
|
148
|
+
if (config == null) throw new ArgumentNullException(nameof(config));
|
|
149
|
+
if (argReader == null) throw new ArgumentNullException(nameof(argReader));
|
|
150
|
+
if (envReader == null) throw new ArgumentNullException(nameof(envReader));
|
|
151
|
+
|
|
152
|
+
var record = new OverrideRecord();
|
|
153
|
+
|
|
154
|
+
// Resolve a value with priority: short-flag-alias (highest) > UNITY_COPILOT_* env-style flag > env var.
|
|
155
|
+
// Returns null if no source provided a non-empty value.
|
|
156
|
+
string? Resolve(string envKey, string? flagAlias)
|
|
157
|
+
{
|
|
158
|
+
if (!string.IsNullOrEmpty(flagAlias)
|
|
159
|
+
&& argReader.TryGetValue(flagAlias, out var flagValue)
|
|
160
|
+
&& !string.IsNullOrWhiteSpace(flagValue))
|
|
161
|
+
{
|
|
162
|
+
return flagValue;
|
|
163
|
+
}
|
|
164
|
+
if (argReader.TryGetValue(envKey, out var envFlagValue)
|
|
165
|
+
&& !string.IsNullOrWhiteSpace(envFlagValue))
|
|
166
|
+
{
|
|
167
|
+
return envFlagValue;
|
|
168
|
+
}
|
|
169
|
+
var envValue = envReader(envKey);
|
|
170
|
+
if (!string.IsNullOrWhiteSpace(envValue))
|
|
171
|
+
return envValue;
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
string Sanitize(string raw) => raw.Trim().Trim('"');
|
|
176
|
+
|
|
177
|
+
// UNITY_COPILOT_HOST / --url override the local server host.
|
|
178
|
+
string? sanitizedHost = null;
|
|
179
|
+
var rawHost = Resolve(EnvHost, FlagUrl);
|
|
180
|
+
if (rawHost != null)
|
|
181
|
+
{
|
|
182
|
+
var host = Sanitize(rawHost).TrimEnd('/');
|
|
183
|
+
if (host.Length > 0)
|
|
184
|
+
{
|
|
185
|
+
sanitizedHost = host;
|
|
186
|
+
if (!string.Equals(host, config.LocalHost, StringComparison.Ordinal))
|
|
187
|
+
{
|
|
188
|
+
record.Track(FieldHost, config.LocalHost, host);
|
|
189
|
+
config.LocalHost = host;
|
|
190
|
+
_logger.LogInformation("[MCP] Override: {Key}={Value}", FieldHost, host);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Loopback URLs without an explicit mode infer Custom (worktree / local-dev).
|
|
196
|
+
// Remote URLs without an explicit mode leave the disk-baseline ConnectionMode untouched.
|
|
197
|
+
var rawMode = Resolve(EnvConnectionMode, flagAlias: null);
|
|
198
|
+
ConnectionMode? targetMode = null;
|
|
199
|
+
if (rawMode != null && Enum.TryParse<ConnectionMode>(Sanitize(rawMode), ignoreCase: true, out var explicitMode))
|
|
200
|
+
{
|
|
201
|
+
targetMode = explicitMode;
|
|
202
|
+
}
|
|
203
|
+
else if (sanitizedHost != null && IsLoopbackUrl(sanitizedHost))
|
|
204
|
+
{
|
|
205
|
+
targetMode = ConnectionMode.Custom;
|
|
206
|
+
}
|
|
207
|
+
if (targetMode.HasValue && targetMode.Value != config.ConnectionMode)
|
|
208
|
+
{
|
|
209
|
+
record.Track(FieldConnectionMode, config.ConnectionMode, targetMode.Value);
|
|
210
|
+
config.ConnectionMode = targetMode.Value;
|
|
211
|
+
_logger.LogInformation("[MCP] Override: {Key}={Value}", FieldConnectionMode, targetMode.Value);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
var rawKeep = Resolve(EnvKeepConnected, flagAlias: null);
|
|
215
|
+
if (rawKeep != null && bool.TryParse(Sanitize(rawKeep), out var keep) && keep != config.KeepConnected)
|
|
216
|
+
{
|
|
217
|
+
record.Track(FieldKeepConnected, config.KeepConnected, keep);
|
|
218
|
+
config.KeepConnected = keep;
|
|
219
|
+
_logger.LogInformation("[MCP] Override: {Key}={Value}", EnvKeepConnected, keep);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
var rawAuth = Resolve(EnvAuthOption, FlagAuth);
|
|
223
|
+
if (rawAuth != null
|
|
224
|
+
&& Enum.TryParse<AuthOption>(Sanitize(rawAuth), ignoreCase: true, out var ao)
|
|
225
|
+
&& ao != config.AuthOption)
|
|
226
|
+
{
|
|
227
|
+
record.Track(FieldAuthOption, config.AuthOption, ao);
|
|
228
|
+
config.AuthOption = ao;
|
|
229
|
+
_logger.LogInformation("[MCP] Override: {Key}={Value}", EnvAuthOption, ao);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// We track the specific
|
|
233
|
+
// backing field rather than the abstract Token property because only the backing
|
|
234
|
+
// fields are serialised — restoring the baseline must target the same field that
|
|
235
|
+
// was clobbered.
|
|
236
|
+
var rawToken = Resolve(EnvToken, FlagToken);
|
|
237
|
+
if (rawToken != null)
|
|
238
|
+
{
|
|
239
|
+
var token = Sanitize(rawToken);
|
|
240
|
+
if (!string.Equals(token, config.LocalToken, StringComparison.Ordinal))
|
|
241
|
+
{
|
|
242
|
+
record.Track(FieldLocalToken, config.LocalToken, token);
|
|
243
|
+
config.LocalToken = token;
|
|
244
|
+
_logger.LogInformation("[MCP] Override: {Key}=*** (LocalToken)", EnvToken);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
var rawTransport = Resolve(EnvTransport, flagAlias: null);
|
|
249
|
+
if (rawTransport != null
|
|
250
|
+
&& Enum.TryParse<TransportMethod>(Sanitize(rawTransport), ignoreCase: true, out var tm)
|
|
251
|
+
&& tm != config.TransportMethod)
|
|
252
|
+
{
|
|
253
|
+
record.Track(FieldTransport, config.TransportMethod, tm);
|
|
254
|
+
config.TransportMethod = tm;
|
|
255
|
+
_logger.LogInformation("[MCP] Override: {Key}={Value}", EnvTransport, tm);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
var rawStart = Resolve(EnvStartServer, flagAlias: null);
|
|
259
|
+
if (rawStart != null && bool.TryParse(Sanitize(rawStart), out var ss) && ss != config.KeepServerRunning)
|
|
260
|
+
{
|
|
261
|
+
record.Track(FieldStartServer, config.KeepServerRunning, ss);
|
|
262
|
+
config.KeepServerRunning = ss;
|
|
263
|
+
_logger.LogInformation("[MCP] Override: {Key}={Value}", EnvStartServer, ss);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// EnabledToolsOverride is [JsonIgnore] so it is never persisted regardless of the
|
|
267
|
+
// baseline-restoration logic; we still track it for completeness.
|
|
268
|
+
var rawTools = Resolve(EnvTools, flagAlias: null);
|
|
269
|
+
if (rawTools != null)
|
|
270
|
+
{
|
|
271
|
+
var ids = rawTools.Split(',', StringSplitOptions.RemoveEmptyEntries);
|
|
272
|
+
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
|
273
|
+
var trimmed = new List<string>(ids.Length);
|
|
274
|
+
foreach (var id in ids)
|
|
275
|
+
{
|
|
276
|
+
var value = Sanitize(id);
|
|
277
|
+
if (!string.IsNullOrWhiteSpace(value) && seen.Add(value))
|
|
278
|
+
trimmed.Add(value);
|
|
279
|
+
}
|
|
280
|
+
record.Track(FieldTools, config.EnabledToolsOverride, trimmed);
|
|
281
|
+
config.EnabledToolsOverride = trimmed;
|
|
282
|
+
_logger.LogInformation("[MCP] Override: {Key}={Value}", EnvTools, rawTools.Trim());
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return record;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/// <summary>
|
|
289
|
+
/// Applies the BaselineValues from <paramref name="record"/> to <paramref name="config"/>
|
|
290
|
+
/// (i.e. reverses runtime overrides so the in-memory config reflects what is on disk).
|
|
291
|
+
/// Used by the editor's Save() flow to ensure runtime-only overrides are not persisted.
|
|
292
|
+
/// </summary>
|
|
293
|
+
public static void ApplyBaseline(UnityCopilotPlugin.UnityConnectionConfig config, OverrideRecord record)
|
|
294
|
+
{
|
|
295
|
+
if (config == null) throw new ArgumentNullException(nameof(config));
|
|
296
|
+
if (record == null) throw new ArgumentNullException(nameof(record));
|
|
297
|
+
ApplyValues(config, record.BaselineValues);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/// <summary>
|
|
301
|
+
/// Re-applies the OverrideValues from <paramref name="record"/> to <paramref name="config"/>
|
|
302
|
+
/// (i.e. restores runtime overrides after a temporary baseline-restore).
|
|
303
|
+
/// </summary>
|
|
304
|
+
public static void ApplyOverrides(UnityCopilotPlugin.UnityConnectionConfig config, OverrideRecord record)
|
|
305
|
+
{
|
|
306
|
+
if (config == null) throw new ArgumentNullException(nameof(config));
|
|
307
|
+
if (record == null) throw new ArgumentNullException(nameof(record));
|
|
308
|
+
ApplyValues(config, record.OverrideValues);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
static void ApplyValues(UnityCopilotPlugin.UnityConnectionConfig config, IReadOnlyDictionary<string, object?> values)
|
|
312
|
+
{
|
|
313
|
+
foreach (var kvp in values)
|
|
314
|
+
{
|
|
315
|
+
switch (kvp.Key)
|
|
316
|
+
{
|
|
317
|
+
case FieldHost:
|
|
318
|
+
config.LocalHost = (string?)kvp.Value ?? UnityCopilotPlugin.UnityConnectionConfig.DefaultHost;
|
|
319
|
+
break;
|
|
320
|
+
case FieldKeepConnected:
|
|
321
|
+
if (kvp.Value is bool kc) config.KeepConnected = kc;
|
|
322
|
+
break;
|
|
323
|
+
case FieldAuthOption:
|
|
324
|
+
if (kvp.Value is AuthOption ao) config.AuthOption = ao;
|
|
325
|
+
break;
|
|
326
|
+
case FieldLocalToken:
|
|
327
|
+
config.LocalToken = (string?)kvp.Value;
|
|
328
|
+
break;
|
|
329
|
+
case FieldTools:
|
|
330
|
+
config.EnabledToolsOverride = (List<string>?)kvp.Value;
|
|
331
|
+
break;
|
|
332
|
+
case FieldStartServer:
|
|
333
|
+
if (kvp.Value is bool ss) config.KeepServerRunning = ss;
|
|
334
|
+
break;
|
|
335
|
+
case FieldTransport:
|
|
336
|
+
if (kvp.Value is TransportMethod tm) config.TransportMethod = tm;
|
|
337
|
+
break;
|
|
338
|
+
case FieldConnectionMode:
|
|
339
|
+
if (kvp.Value is ConnectionMode cm) config.ConnectionMode = cm;
|
|
340
|
+
break;
|
|
341
|
+
default:
|
|
342
|
+
// Fail loudly if a new Field* constant is added to Track but forgotten here —
|
|
343
|
+
// a silent miss would let runtime overrides leak to disk on Save.
|
|
344
|
+
throw new InvalidOperationException($"Unhandled override field: {kvp.Key}");
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/// <summary>
|
|
350
|
+
/// Returns true if the given URL targets a loopback host
|
|
351
|
+
/// (<c>localhost</c>, <c>127.0.0.0/8</c>, or IPv6 <c>::1</c>).
|
|
352
|
+
/// Used to infer <see cref="ConnectionMode.Custom"/> when a worktree-style local URL is supplied.
|
|
353
|
+
/// </summary>
|
|
354
|
+
public static bool IsLoopbackUrl(string url)
|
|
355
|
+
{
|
|
356
|
+
if (string.IsNullOrWhiteSpace(url))
|
|
357
|
+
return false;
|
|
358
|
+
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
|
|
359
|
+
return false;
|
|
360
|
+
var host = uri.Host;
|
|
361
|
+
if (string.Equals(host, "localhost", StringComparison.OrdinalIgnoreCase))
|
|
362
|
+
return true;
|
|
363
|
+
if (System.Net.IPAddress.TryParse(host, out var ip))
|
|
364
|
+
return System.Net.IPAddress.IsLoopback(ip);
|
|
365
|
+
return false;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|