chore(unity): 固定Unity CLI Loop本地包并移除UnitySkills依赖

This commit is contained in:
Liubing\LB
2026-09-28 18:14:59 +08:00
parent 0eb43b2b40
commit c4ae02def9
952 changed files with 70137 additions and 11 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 57ec66af50b5e4a23b7938cc070d1b90
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,277 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Security.Cryptography;
using System.Text;
namespace io.github.hatayama.uLoopMCP
{
/// <summary>
/// Manages the caching of compilation results.
/// Avoids recompiling the same code by hashing requests with SHA256.
/// </summary>
public class CompilationCacheManager
{
private const int MaxCacheEntries = 32;
private readonly Dictionary<string, CachedCompilationResult> _compilationCache = new();
private readonly Queue<string> _cacheOrder = new();
public CompilationResult CheckCache(CompilationRequest request)
{
string cacheKey = GenerateCacheKey(request);
if (_compilationCache.TryGetValue(cacheKey, out CachedCompilationResult cachedResult))
{
return CloneCompilationResult(cachedResult);
}
return null;
}
public void CacheResultIfSuccessful(CompilationResult result, CompilationRequest request)
{
if (result.Success && result.CompiledAssembly != null)
{
string cacheKey = GenerateCacheKey(request);
CachedCompilationResult cachedResult = CreateCachedCompilationResult(result);
if (_compilationCache.ContainsKey(cacheKey))
{
_compilationCache[cacheKey] = cachedResult;
return;
}
// Dynamic assemblies cannot be unloaded from the default AppDomain,
// so the cache keeps only a small hot set instead of retaining every snippet forever.
if (_compilationCache.Count >= MaxCacheEntries)
{
string oldestKey = _cacheOrder.Dequeue();
_compilationCache.Remove(oldestKey);
}
_compilationCache[cacheKey] = cachedResult;
_cacheOrder.Enqueue(cacheKey);
}
}
public void ClearCache()
{
_compilationCache.Clear();
_cacheOrder.Clear();
}
public string GenerateCacheKey(CompilationRequest request)
{
StringBuilder keyBuilder = new StringBuilder();
keyBuilder.Append(request.Code);
keyBuilder.Append("|");
keyBuilder.Append(request.ClassName ?? "");
keyBuilder.Append("|");
keyBuilder.Append(request.Namespace ?? "");
if (request.AdditionalReferences != null && request.AdditionalReferences.Any())
{
keyBuilder.Append("|");
keyBuilder.Append(string.Join(",", request.AdditionalReferences.OrderBy(r => r)));
}
using (SHA256 sha256 = SHA256.Create())
{
byte[] hashBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(keyBuilder.ToString()));
return Convert.ToBase64String(hashBytes);
}
}
private static CachedCompilationResult CreateCachedCompilationResult(CompilationResult result)
{
return new CachedCompilationResult(
result.CompiledAssembly,
CloneCompilationErrors(result.Errors),
CloneStrings(result.Warnings),
result.UpdatedCode,
result.HasSecurityViolations,
CloneSecurityViolations(result.SecurityViolations),
result.FailureReason,
CloneAmbiguousTypeCandidates(result.AmbiguousTypeCandidates),
CloneStrings(result.AutoInjectedNamespaces),
CloneStrings(result.Timings),
CloneStrings(result.AdvisoryLogs),
result.CompilationBackendKind);
}
private static CompilationResult CloneCompilationResult(CachedCompilationResult cachedResult)
{
return new CompilationResult
{
Success = true,
CompiledAssembly = cachedResult.CompiledAssembly,
Errors = CloneCompilationErrors(cachedResult.Errors),
Warnings = CloneStrings(cachedResult.Warnings),
UpdatedCode = cachedResult.UpdatedCode,
HasSecurityViolations = cachedResult.HasSecurityViolations,
SecurityViolations = CloneSecurityViolations(cachedResult.SecurityViolations),
FailureReason = cachedResult.FailureReason,
AmbiguousTypeCandidates = CloneAmbiguousTypeCandidates(cachedResult.AmbiguousTypeCandidates),
AutoInjectedNamespaces = CloneStrings(cachedResult.AutoInjectedNamespaces),
Timings = BuildCachedCompilationTimings(cachedResult.CompilationBackendKind),
AdvisoryLogs = CloneStrings(cachedResult.AdvisoryLogs),
CompilationBackendKind = cachedResult.CompilationBackendKind
};
}
private static List<string> BuildCachedCompilationTimings(
DynamicCompilationBackendKind compilationBackendKind)
{
List<string> timings = new List<string>
{
"[Perf] ReferenceResolution: 0.0ms",
"[Perf] Build: 0.0ms",
"[Perf] AssemblyLoad: 0.0ms"
};
switch (compilationBackendKind)
{
case DynamicCompilationBackendKind.SharedRoslynWorker:
timings.Add("[Perf] Backend: SharedRoslynWorker");
break;
case DynamicCompilationBackendKind.OneShotRoslyn:
timings.Add("[Perf] Backend: OneShotRoslyn");
break;
case DynamicCompilationBackendKind.AssemblyBuilderFallback:
timings.Add("[Perf] Backend: AssemblyBuilderFallback");
break;
}
timings.Add("[Perf] CacheHit: true");
return timings;
}
private static List<CompilationError> CloneCompilationErrors(List<CompilationError> errors)
{
List<CompilationError> clonedErrors = new List<CompilationError>();
if (errors == null)
{
return clonedErrors;
}
foreach (CompilationError error in errors)
{
clonedErrors.Add(new CompilationError
{
Message = error.Message,
Line = error.Line,
Column = error.Column,
ErrorCode = error.ErrorCode
});
}
return clonedErrors;
}
private static List<SecurityViolation> CloneSecurityViolations(List<SecurityViolation> securityViolations)
{
List<SecurityViolation> clonedViolations = new List<SecurityViolation>();
if (securityViolations == null)
{
return clonedViolations;
}
foreach (SecurityViolation violation in securityViolations)
{
clonedViolations.Add(new SecurityViolation
{
Type = violation.Type,
Description = violation.Description,
LineNumber = violation.LineNumber,
CodeSnippet = violation.CodeSnippet,
Message = violation.Message,
ApiName = violation.ApiName,
Location = violation.Location
});
}
return clonedViolations;
}
private static Dictionary<string, List<string>> CloneAmbiguousTypeCandidates(
Dictionary<string, List<string>> ambiguousTypeCandidates)
{
Dictionary<string, List<string>> clonedCandidates = new Dictionary<string, List<string>>();
if (ambiguousTypeCandidates == null)
{
return clonedCandidates;
}
foreach (KeyValuePair<string, List<string>> entry in ambiguousTypeCandidates)
{
clonedCandidates[entry.Key] = CloneStrings(entry.Value);
}
return clonedCandidates;
}
private static List<string> CloneStrings(List<string> values)
{
if (values == null)
{
return new List<string>();
}
return new List<string>(values);
}
private sealed class CachedCompilationResult
{
public Assembly CompiledAssembly { get; }
public List<CompilationError> Errors { get; }
public List<string> Warnings { get; }
public string UpdatedCode { get; }
public bool HasSecurityViolations { get; }
public List<SecurityViolation> SecurityViolations { get; }
public CompilationFailureReason FailureReason { get; }
public Dictionary<string, List<string>> AmbiguousTypeCandidates { get; }
public List<string> AutoInjectedNamespaces { get; }
public List<string> Timings { get; }
public List<string> AdvisoryLogs { get; }
public DynamicCompilationBackendKind CompilationBackendKind { get; }
public CachedCompilationResult(
Assembly compiledAssembly,
List<CompilationError> errors,
List<string> warnings,
string updatedCode,
bool hasSecurityViolations,
List<SecurityViolation> securityViolations,
CompilationFailureReason failureReason,
Dictionary<string, List<string>> ambiguousTypeCandidates,
List<string> autoInjectedNamespaces,
List<string> timings,
List<string> advisoryLogs,
DynamicCompilationBackendKind compilationBackendKind)
{
CompiledAssembly = compiledAssembly;
Errors = errors;
Warnings = warnings;
UpdatedCode = updatedCode;
HasSecurityViolations = hasSecurityViolations;
SecurityViolations = securityViolations;
FailureReason = failureReason;
AmbiguousTypeCandidates = ambiguousTypeCandidates;
AutoInjectedNamespaces = autoInjectedNamespaces;
Timings = timings;
AdvisoryLogs = advisoryLogs;
CompilationBackendKind = compilationBackendKind;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e8f1ec6f980b54adda3f6317615191b5
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,46 @@
using System.Diagnostics;
namespace io.github.hatayama.uLoopMCP
{
public static class DomainReloadStateRegistry
{
private static readonly object SyncRoot = new object();
private static IDomainReloadStateProvider _provider;
public static void RegisterProvider(IDomainReloadStateProvider provider)
{
Debug.Assert(provider != null, "provider must not be null");
lock (SyncRoot)
{
_provider = provider;
}
}
public static bool IsDomainReloadInProgress()
{
IDomainReloadStateProvider provider;
lock (SyncRoot)
{
provider = _provider;
}
if (provider == null)
{
return false;
}
return provider.IsDomainReloadInProgress();
}
public static IDomainReloadStateProvider SwapProviderForTests(IDomainReloadStateProvider provider)
{
lock (SyncRoot)
{
IDomainReloadStateProvider previousProvider = _provider;
_provider = provider;
return previousProvider;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 16f03c71027d5433cb920e1830289b86
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,59 @@
using System.Diagnostics;
namespace io.github.hatayama.uLoopMCP
{
public static class DynamicCompilationServiceRegistry
{
private static readonly object SyncRoot = new object();
private static IDynamicCompilationServiceFactory _factory;
public static bool HasRegisteredFactory
{
get
{
lock (SyncRoot)
{
return _factory != null;
}
}
}
public static void RegisterFactory(IDynamicCompilationServiceFactory factory)
{
Debug.Assert(factory != null, "factory must not be null");
lock (SyncRoot)
{
_factory = factory;
}
}
public static bool TryCreate(DynamicCodeSecurityLevel securityLevel, out IDynamicCompilationService service)
{
IDynamicCompilationServiceFactory factory;
lock (SyncRoot)
{
factory = _factory;
}
if (factory == null)
{
service = null;
return false;
}
service = factory.Create(securityLevel);
return service != null;
}
public static IDynamicCompilationServiceFactory SwapFactoryForTests(IDynamicCompilationServiceFactory factory)
{
lock (SyncRoot)
{
IDynamicCompilationServiceFactory previousFactory = _factory;
_factory = factory;
return previousFactory;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ed1454bd9d3934dea8704fd28061c595
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,40 @@
using System.Diagnostics;
namespace io.github.hatayama.uLoopMCP
{
public static class PreloadAssemblySecurityValidatorRegistry
{
private static readonly object SyncRoot = new object();
private static IPreloadAssemblySecurityValidator _validator;
public static void RegisterValidator(IPreloadAssemblySecurityValidator validator)
{
Debug.Assert(validator != null, "validator must not be null");
lock (SyncRoot)
{
_validator = validator;
}
}
public static bool TryGetValidator(out IPreloadAssemblySecurityValidator validator)
{
lock (SyncRoot)
{
validator = _validator;
}
return validator != null;
}
public static IPreloadAssemblySecurityValidator SwapValidatorForTests(IPreloadAssemblySecurityValidator validator)
{
lock (SyncRoot)
{
IPreloadAssemblySecurityValidator previousValidator = _validator;
_validator = validator;
return previousValidator;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eb0c09b05bdd64ef3b534c2a17507638
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 263a267a9e45a4c288198414a6f98e7d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,212 @@
namespace io.github.hatayama.uLoopMCP
{
/// <summary>
/// Central constants repository for Unity MCP system.
///
/// Design document reference: Packages/src/Editor/ARCHITECTURE.md
///
/// Related classes:
/// - McpConfigService: Uses these constants for configuration management
/// - McpServerConfigFactory: Uses port and environment variable constants
/// - McpEditorWindow: Uses SessionState keys for UI state persistence
/// - McpSessionManager: Uses SessionState keys for connection state management
/// - EditorConfigProvider: Provides client names via GetClientNameForEditor method
/// </summary>
public static class McpConstants
{
private static UnityEditor.PackageManager.PackageInfo _cachedPackageInfo;
/// <summary>
/// Gets the PackageInfo for uLoopMCP package.
/// Results are cached for performance.
/// </summary>
/// <exception cref="System.InvalidOperationException">Thrown when package info cannot be resolved.</exception>
public static UnityEditor.PackageManager.PackageInfo PackageInfo
{
get
{
if (_cachedPackageInfo == null)
{
_cachedPackageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssembly(
typeof(McpConstants).Assembly);
if (_cachedPackageInfo == null)
{
throw new System.InvalidOperationException(
"Failed to resolve PackageInfo for uLoopMCP. " +
"Ensure the package is properly installed via Package Manager.");
}
}
return _cachedPackageInfo;
}
}
/// <summary>
/// Gets the package name (e.g., "io.github.hatayama.uloopmcp").
/// </summary>
public static string PackageName => PackageInfo.name;
/// <summary>
/// Gets the Unity asset path for the package (e.g., "Packages/io.github.hatayama.uloopmcp").
/// Use this for AssetDatabase.LoadAssetAtPath().
/// </summary>
public static string PackageAssetPath => PackageInfo.assetPath;
/// <summary>
/// Gets the resolved file system path for the package.
/// Use this for file system operations.
/// </summary>
public static string PackageResolvedPath => PackageInfo.resolvedPath;
/// <summary>
/// Gets the package name pattern for directory searching (e.g., "io.github.hatayama.uloopmcp@*").
/// Use this for Directory.GetDirectories() in PackageCache.
/// </summary>
public static string PackageNamePattern => $"{PackageName}@*";
/// <summary>
/// Gets the C# namespace for uLoopMCP (e.g., "io.github.hatayama.uLoopMCP").
/// </summary>
public static string PackageNamespace => typeof(McpConstants).Namespace;
public const string PROJECT_NAME = "uLoopMCP";
// JSON configuration keys
public const string JSON_KEY_MCP_SERVERS = "mcpServers";
public const string JSON_KEY_COMMAND = "command";
public const string JSON_KEY_ARGS = "args";
public const string JSON_KEY_ENV = "env";
// Editor settings
public const string SETTINGS_FILE_NAME = "UnityMcpSettings.json";
public const string USER_SETTINGS_FOLDER = "UserSettings";
// Server configuration
public const string NODE_COMMAND = "node";
public const string UNITY_TCP_PORT_ENV_KEY = "UNITY_TCP_PORT";
// Scripting define symbols
public const string SCRIPTING_DEFINE_ULOOPMCP_DEBUG = "ULOOPMCP_DEBUG";
// Environment variable keys for development mode
public const string ENV_KEY_ULOOPMCP_DEBUG = "ULOOPMCP_DEBUG";
public const string ENV_KEY_ULOOPMCP_PRODUCTION = "ULOOPMCP_PRODUCTION";
public const string ENV_KEY_MCP_DEBUG = "MCP_DEBUG";
public const string ENV_KEY_NODE_OPTIONS = "NODE_OPTIONS";
// MCP_CLIENT_NAME removed - now using clientInfo.name from MCP protocol
// Environment variable values
public const string ENV_VALUE_TRUE = "true";
public const string NODE_OPTIONS_ENABLE_SOURCE_MAPS = "--enable-source-maps";
// Client names for different editors
public const string CLIENT_NAME_CURSOR = "Cursor";
public const string CLIENT_NAME_CLAUDE_CODE = "Claude Code";
public const string CLIENT_NAME_VSCODE = "VSCode";
public const string CLIENT_NAME_GEMINI_CLI = "Gemini CLI";
public const string CLIENT_NAME_WINDSURF = "Windsurf";
public const string CLIENT_NAME_CODEX = "Codex";
public const string CLIENT_NAME_MCP_INSPECTOR = "MCP Inspector";
public const string UNKNOWN_CLIENT_NAME = "Unknown Client";
// Command messages
public const string CLIENT_SUCCESS_MESSAGE_TEMPLATE = "Client name registered successfully: {0}";
// Reconnection settings
public const int RECONNECTION_TIMEOUT_SECONDS = 10;
// TypeScript server related constants
public const string TYPESCRIPT_SERVER_DIR = "TypeScriptServer~";
public const string DIST_DIR = "dist";
public const string SERVER_BUNDLE_FILE = "server.bundle.js";
// Package path constants
public const string PACKAGES_DIR = "Packages";
public const string SRC_DIR = "src";
public const string LIBRARY_DIR = "Library";
public const string TEMP_DIR = "Temp";
public const string PACKAGE_CACHE_DIR = "PackageCache";
public const string ULOOPMCP_DIR = "uLoopMCP";
public const string COMPILE_RESULTS_DIR = "compile-results";
public const string JSON_FILE_EXTENSION = ".json";
public const string RUNTIME_ASSEMBLY_FILE_NAME = "uLoopMCP.Runtime.dll";
// .uloop directory
public const string ULOOP_DIR = ".uloop";
public const string ULOOP_SETTINGS_FILE_NAME = "settings.permissions.json";
public const string ULOOP_TOOL_SETTINGS_FILE_NAME = "settings.tools.json";
// Tool name constants
public const string TOOL_NAME_EXECUTE_DYNAMIC_CODE = "execute-dynamic-code";
public const string TOOL_NAME_RUN_TESTS = "run-tests";
// File output directories
public const string OUTPUT_ROOT_DIR = ".uloop/outputs";
public const string TEST_RESULTS_DIR = "TestResults";
public const string SEARCH_RESULTS_DIR = "SearchResults";
public const string HIERARCHY_RESULTS_DIR = "HierarchyResults";
public const string FIND_GAMEOBJECTS_RESULTS_DIR = "FindGameObjectsResults";
public const string SCREENSHOTS_DIR = "Screenshots";
public const string VIBE_LOGS_DIR = "VibeLogs";
// Correlation ID constants
public const int CORRELATION_ID_LENGTH = 8; // Length for correlation ID generation
public const string GUID_FORMAT_NO_HYPHENS = "N"; // GUID format without hyphens
public const string ERROR_MESSAGE_DUPLICATE_ASMDEF = "Duplicate asmdef assembly name detected. Unity may not start compilation until duplicates are removed.";
public const string ERROR_MESSAGE_EXECUTION_IN_PROGRESS = "Another execution is already in progress";
public const string ERROR_MESSAGE_EXECUTION_CANCELLED = "Execution was cancelled or timed out";
public const string ERROR_MESSAGE_NO_COMPILED_ASSEMBLY = "No compiled assembly provided";
public const string ERROR_MESSAGE_NO_EXECUTE_METHOD = "No Execute method found in compiled assembly";
public const string ERROR_MESSAGE_FAILED_TO_CREATE_INSTANCE = "Failed to create instance of target type";
public const string ERROR_MESSAGE_UNSUPPORTED_SIGNATURE = "Execute method signature not supported";
// Test constants
public const int TEST_COMPILE_TIMEOUT_MS = 5000; // 5 seconds for test compilation
// Screenshot coordinate system values
public const string COORDINATE_SYSTEM_GAME_VIEW = "gameView";
public const string COORDINATE_SYSTEM_WINDOW = "window";
// SimulateMouseUi constants
public const float SIMULATE_MOUSE_UI_DEFAULT_DRAG_SPEED = 2000f;
// Compile tool constants
public const int COMPILE_START_TIMEOUT_MS = 5000; // Timeout to detect "compile did not start"
public const int COMPILE_START_POLL_INTERVAL_MS = 100;
public const int COMPILE_DOMAIN_RELOAD_WAIT_TIMEOUT_MS = 10000;
public const int COMPILE_DOMAIN_RELOAD_WAIT_POLL_INTERVAL_MS = 100;
// Security constants
public const int MAX_JSON_SIZE_BYTES = 1024 * 1024; // 1MB limit for JSON files
public const int MAX_SETTINGS_SIZE_BYTES = 1024 * 16; // 16KB limit for settings files
public const string SECURITY_LOG_PREFIX = "[uLoopMCP Security]";
// Security: Allowed namespaces for reflection operations
public static readonly string[] ALLOWED_NAMESPACES = {
"UnityEditor",
"Unity.EditorCoroutines",
"Unity.VisualScripting",
PackageNamespace
};
// Security: Denied types for reflection operations
public static readonly string[] DENIED_SYSTEM_TYPES = {
"System.Diagnostics.Process",
"System.IO.File",
"System.IO.Directory",
"System.Reflection.Assembly",
"System.Activator"
};
/// <summary>
/// Generates a short correlation ID for logging and tracking
/// </summary>
/// <returns>8-character correlation ID</returns>
public static string GenerateCorrelationId()
{
return System.Guid.NewGuid().ToString(GUID_FORMAT_NO_HYPHENS)[..CORRELATION_ID_LENGTH];
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 00252dba49c034c9d9f0e68d333ba8b6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a284a76514b134e60b502c5e531359e9
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,42 @@
using System.Text.RegularExpressions;
namespace io.github.hatayama.uLoopMCP
{
public static class CompilationDiagnosticMessageParser
{
private static readonly Regex TypeNamePattern = new Regex(@"['""]([^'""]+)['""]", RegexOptions.Compiled);
public static string ExtractTypeNameFromMessage(string message)
{
if (message == null)
{
return null;
}
Match match = TypeNamePattern.Match(message);
if (!match.Success)
{
return null;
}
return NormalizeTypeName(match.Groups[1].Value);
}
private static string NormalizeTypeName(string rawName)
{
if (string.IsNullOrWhiteSpace(rawName))
{
return null;
}
string normalized = rawName.Trim();
int genericIndex = normalized.IndexOf('<');
if (genericIndex > 0)
{
normalized = normalized.Substring(0, genericIndex);
}
return normalized;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c2284b0ac895440efaea20245f59aec3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 75c589adcf4f04a2c90deed8ba8578de
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,7 @@
namespace io.github.hatayama.uLoopMCP
{
public interface IDomainReloadStateProvider
{
bool IsDomainReloadInProgress();
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 36b0d8ce58cd14305b7904e3e1f12aaf
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
using System.Threading;
using System.Threading.Tasks;
namespace io.github.hatayama.uLoopMCP
{
public interface IDynamicCompilationService
{
Task<CompilationResult> CompileAsync(CompilationRequest request, CancellationToken ct = default);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4d52a22eb1364461296f82cd4ccaca9e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,7 @@
namespace io.github.hatayama.uLoopMCP
{
public interface IDynamicCompilationServiceFactory
{
IDynamicCompilationService Create(DynamicCodeSecurityLevel securityLevel);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 82de15f662b864f2f8ba18aa82b83a5b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,6 @@
namespace io.github.hatayama.uLoopMCP
{
public interface IOverrideDefaultPreloadAssemblySecurityValidation
{
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 063eaab7859e24ee6862f4196555b7ab
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,7 @@
namespace io.github.hatayama.uLoopMCP
{
public interface IPreloadAssemblySecurityValidator
{
SecurityValidationResult Validate(byte[] assemblyBytes);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: deb095dc198104f59a71c4279fb7094c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: a061ff47efbbd4789bf7cd4c75f83fa4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,357 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Newtonsoft.Json;
using UnityEditor;
using UnityEngine;
namespace io.github.hatayama.uLoopMCP
{
/// <summary>
/// AI-friendly structured logger for Unity MCP
///
/// Design document reference: Packages/src/TypeScriptServer~/ARCHITECTURE.md
///
/// Related classes:
/// - McpLogger: Traditional Unity logger that this class replaces
/// - McpServerController: Uses this logger for domain reload tracking
/// - UnityMcpServer: Uses this logger for connection event tracking
///
/// Key features:
/// - Structured JSON logging with operation, context, correlation_id
/// - AI-friendly format for Claude Code analysis
/// - Automatic file rotation and memory management
/// - Correlation ID tracking for related operations
/// </summary>
public static class VibeLogger
{
private static readonly string LOG_DIRECTORY = Path.Combine(Application.dataPath, "..", McpConstants.OUTPUT_ROOT_DIR, McpConstants.VIBE_LOGS_DIR);
private static readonly string LOG_FILE_PREFIX = "unity_vibe";
private static readonly int MAX_FILE_SIZE_MB = 10;
private static readonly int MAX_MEMORY_LOGS = 1000;
private static readonly int MAX_LOG_FILES = 3;
private static readonly List<VibeLogEntry> memoryLogs = new();
private static readonly object lockObject = new();
private static bool hasCleanedUpOnStartup = false;
[Serializable]
public class VibeLogEntry
{
public string timestamp;
public string level;
public string operation;
public string message;
public object context;
public string correlation_id;
public string source;
public string human_note;
public string ai_todo;
public string stack_trace;
public EnvironmentInfo environment;
}
[Serializable]
public class EnvironmentInfo
{
public string domain_reload_state;
}
/// <summary>
/// Log an info level message with structured context
/// Only logs when ULOOPENV_KEY_ULOOPMCP_DEBUG symbol is defined
/// </summary>
[Conditional(McpConstants.ENV_KEY_ULOOPMCP_DEBUG)]
public static void LogInfo(string operation, string message, object context = null,
string correlationId = null, string humanNote = null, string aiTodo = null, bool includeStackTrace = false)
{
Log("INFO", operation, message, context, correlationId, humanNote, aiTodo, includeStackTrace);
}
/// <summary>
/// Log a warning level message with structured context
/// Only logs when ULOOPENV_KEY_ULOOPMCP_DEBUG symbol is defined
/// </summary>
[Conditional(McpConstants.ENV_KEY_ULOOPMCP_DEBUG)]
public static void LogWarning(string operation, string message, object context = null,
string correlationId = null, string humanNote = null, string aiTodo = null, bool includeStackTrace = true)
{
Log("WARNING", operation, message, context, correlationId, humanNote, aiTodo, includeStackTrace);
}
/// <summary>
/// Log an error level message with structured context
/// Only logs when ULOOPENV_KEY_ULOOPMCP_DEBUG symbol is defined
/// </summary>
[Conditional(McpConstants.ENV_KEY_ULOOPMCP_DEBUG)]
public static void LogError(string operation, string message, object context = null,
string correlationId = null, string humanNote = null, string aiTodo = null, bool includeStackTrace = true)
{
Log("ERROR", operation, message, context, correlationId, humanNote, aiTodo, includeStackTrace);
}
/// <summary>
/// Log a debug level message with structured context
/// Only logs when ULOOPENV_KEY_ULOOPMCP_DEBUG symbol is defined
/// </summary>
[Conditional(McpConstants.ENV_KEY_ULOOPMCP_DEBUG)]
public static void LogDebug(string operation, string message, object context = null,
string correlationId = null, string humanNote = null, string aiTodo = null, bool includeStackTrace = false)
{
Log("DEBUG", operation, message, context, correlationId, humanNote, aiTodo, includeStackTrace);
}
/// <summary>
/// Log an exception with structured context
/// Only logs when ULOOPENV_KEY_ULOOPMCP_DEBUG symbol is defined
/// </summary>
[Conditional(McpConstants.ENV_KEY_ULOOPMCP_DEBUG)]
public static void LogException(string operation, Exception exception, object context = null,
string correlationId = null, string humanNote = null, string aiTodo = null, bool includeStackTrace = true)
{
var exceptionContext = new Dictionary<string, object>();
if (context != null)
{
exceptionContext["original_context"] = context;
}
exceptionContext["exception"] = new
{
type = exception.GetType().Name,
message = exception.Message,
stack_trace = exception.StackTrace,
inner_exception = exception.InnerException?.Message
};
Log("ERROR", operation, $"Exception occurred: {exception.Message}", exceptionContext,
correlationId, humanNote, aiTodo, includeStackTrace);
}
/// <summary>
/// Generate a new correlation ID for tracking related operations
/// </summary>
public static string GenerateCorrelationId()
{
return $"unity_{Guid.NewGuid().ToString("N")[..8]}_{DateTime.Now:HHmmss}";
}
/// <summary>
/// Get logs for AI analysis (formatted for Claude Code)
/// Output directory: {project_root}/.uloop/outputs/VibeLogs/
/// </summary>
public static string GetLogsForAi(string operation = null, string correlationId = null, int maxCount = 100)
{
lock (lockObject)
{
var filteredLogs = new List<VibeLogEntry>(memoryLogs);
if (!string.IsNullOrEmpty(operation))
{
filteredLogs = filteredLogs.FindAll(log => log.operation.Contains(operation));
}
if (!string.IsNullOrEmpty(correlationId))
{
filteredLogs = filteredLogs.FindAll(log => log.correlation_id == correlationId);
}
if (filteredLogs.Count > maxCount)
{
filteredLogs = filteredLogs.GetRange(filteredLogs.Count - maxCount, maxCount);
}
return JsonConvert.SerializeObject(filteredLogs, Formatting.Indented);
}
}
/// <summary>
/// Clear all memory logs
/// </summary>
public static void ClearMemoryLogs()
{
lock (lockObject)
{
memoryLogs.Clear();
}
}
/// <summary>
/// Core logging method
/// </summary>
private static void Log(string level, string operation, string message, object context,
string correlationId, string humanNote, string aiTodo, bool includeStackTrace = true)
{
var logEntry = new VibeLogEntry
{
timestamp = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffzzz"),
level = level,
operation = operation,
message = message,
context = context,
correlation_id = correlationId ?? GenerateCorrelationId(),
source = "Unity",
human_note = humanNote,
ai_todo = aiTodo,
stack_trace = includeStackTrace ? new StackTrace(true).ToString() : null,
environment = GetEnvironmentInfo()
};
// Add to memory logs
lock (lockObject)
{
memoryLogs.Add(logEntry);
// Rotate memory logs if too many
if (memoryLogs.Count > MAX_MEMORY_LOGS)
{
memoryLogs.RemoveAt(0);
}
}
// Save to file
try
{
SaveLogToFile(logEntry);
}
catch (Exception ex)
{
// Fallback to Unity console if file logging fails
UnityEngine.Debug.LogError($"[VibeLogger] Failed to save log to file: {ex.Message}");
UnityEngine.Debug.Log($"[VibeLogger] {level} | {operation} | {message}");
}
}
/// <summary>
/// Save log entry to file with file locking for concurrent access
/// </summary>
private static void SaveLogToFile(VibeLogEntry logEntry)
{
if (!Directory.Exists(LOG_DIRECTORY))
{
Directory.CreateDirectory(LOG_DIRECTORY);
}
// Clean up old log files on first access only
lock (lockObject)
{
if (!hasCleanedUpOnStartup)
{
CleanupOldLogFiles();
hasCleanedUpOnStartup = true;
}
}
string fileName = $"{LOG_FILE_PREFIX}_{DateTime.UtcNow:yyyyMMdd}.json";
string filePath = Path.Combine(LOG_DIRECTORY, fileName);
// Check file size and rotate if necessary
if (File.Exists(filePath))
{
var fileInfo = new FileInfo(filePath);
if (fileInfo.Length > MAX_FILE_SIZE_MB * 1024 * 1024)
{
string rotatedFileName = $"{LOG_FILE_PREFIX}_{DateTime.UtcNow:yyyyMMdd_HHmmss}.json";
string rotatedFilePath = Path.Combine(LOG_DIRECTORY, rotatedFileName);
File.Move(filePath, rotatedFilePath);
// Clean up old files after rotation
CleanupOldLogFiles();
}
}
string jsonLog = JsonConvert.SerializeObject(logEntry) + "\n";
// Use file locking with retry mechanism to handle concurrent access
int maxRetries = 3;
int retryDelayMs = 50;
for (int retry = 0; retry < maxRetries; retry++)
{
try
{
using (var fileStream = new FileStream(filePath, FileMode.Append, FileAccess.Write, FileShare.Read))
using (var writer = new StreamWriter(fileStream))
{
writer.Write(jsonLog);
writer.Flush();
return; // Success - exit retry loop
}
}
catch (IOException ex) when (IsFileSharingViolation(ex) && retry < maxRetries - 1)
{
// Wait and retry for sharing violations
System.Threading.Thread.Sleep(retryDelayMs * (retry + 1));
}
catch (Exception ex)
{
// For other exceptions, throw immediately
throw new InvalidOperationException($"Failed to write VibeLogger entry to file: {ex.Message}", ex);
}
}
// If all retries failed, throw with sharing violation context
throw new InvalidOperationException($"Failed to write VibeLogger entry after {maxRetries} retries due to file sharing violations");
}
/// <summary>
/// Check if exception is a file sharing violation
/// </summary>
private static bool IsFileSharingViolation(IOException ex)
{
// ERROR_SHARING_VIOLATION (0x80070020)
const int ERROR_SHARING_VIOLATION = unchecked((int)0x80070020);
return ex.HResult == ERROR_SHARING_VIOLATION;
}
/// <summary>
/// Clean up old log files, keeping only the most recent MAX_LOG_FILES
/// </summary>
private static void CleanupOldLogFiles()
{
try
{
if (!Directory.Exists(LOG_DIRECTORY))
return;
// Get all vibe log files, sorted by creation time (newest first)
var logFiles = Directory.GetFiles(LOG_DIRECTORY, $"{LOG_FILE_PREFIX}_*.json")
.Select(file => new FileInfo(file))
.OrderByDescending(file => file.CreationTime)
.ToArray();
// Delete files beyond the limit
for (int i = MAX_LOG_FILES; i < logFiles.Length; i++)
{
try
{
logFiles[i].Delete();
}
catch (Exception ex)
{
// Log deletion failure but don't crash
UnityEngine.Debug.LogWarning($"[VibeLogger] Failed to delete old log file {logFiles[i].Name}: {ex.Message}");
}
}
}
catch (Exception ex)
{
UnityEngine.Debug.LogWarning($"[VibeLogger] Failed to cleanup old log files: {ex.Message}");
}
}
/// <summary>
/// Get current environment information
/// </summary>
private static EnvironmentInfo GetEnvironmentInfo()
{
bool isDomainReloadInProgress = EditorApplication.isCompiling || DomainReloadStateRegistry.IsDomainReloadInProgress();
return new EnvironmentInfo
{
domain_reload_state = isDomainReloadInProgress ? "InProgress" : "Idle"
};
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d37177f1ffadc40db966c2ea6be1895d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4bb81f20f00c84854b5f94e40f513d48
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,22 @@
namespace io.github.hatayama.uLoopMCP
{
/// <summary>
/// Represents a compilation error during code compilation
///
/// Related Classes: RoslynCompiler, CompilationResult
/// </summary>
public class CompilationError
{
/// <summary>Line number where the compilation error occurred</summary>
public int Line { get; set; }
/// <summary>Column number where the compilation error occurred</summary>
public int Column { get; set; }
/// <summary>Detailed error message describing the compilation error</summary>
public string Message { get; set; }
/// <summary>Unique error code identifying the specific compilation error</summary>
public string ErrorCode { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c60a3998d37ae4373924387a9603af7b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,47 @@
using System.Collections.Generic;
namespace io.github.hatayama.uLoopMCP
{
/// <summary>
/// Compilation Request
///
/// Related Class: RoslynCompiler
/// </summary>
public class CompilationRequest
{
/// <summary>Source code to compile</summary>
public string Code { get; set; }
/// <summary>Name of the class</summary>
public string ClassName { get; set; }
/// <summary>Namespace of the class</summary>
public string Namespace { get; set; }
/// <summary>
/// Additional references (external DLL file paths)
/// Specify external libraries (such as NuGet package DLLs) that are not loaded in the AppDomain
/// by providing their file paths to be added as references during compilation
/// Example: ["C:/MyLibrary/CustomLibrary.dll", "D:/ThirdParty/SomePackage.dll"]
/// </summary>
public List<string> AdditionalReferences { get; set; } = new();
/// <summary>
/// Legacy assembly loading mode hint.
/// AssemblyBuilder always compiles against the full reference set, so this value is ignored.
/// </summary>
public AssemblyLoadingMode AssemblyMode { get; set; } = AssemblyLoadingMode.AllAssemblies;
}
/// <summary>
/// Defines the mode for loading assemblies during compilation
/// </summary>
public enum AssemblyLoadingMode
{
/// <summary>Reference only selected assemblies</summary>
SelectiveReference,
/// <summary>Add all assemblies</summary>
AllAssemblies
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0ae3bf8a209814a46845da1c0565747b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,83 @@
using System.Collections.Generic;
using System.Reflection;
namespace io.github.hatayama.uLoopMCP
{
/// <summary>
/// Compilation Result
///
/// Related Class: RoslynCompiler
/// </summary>
public class CompilationResult
{
/// <summary>Compilation Success</summary>
public bool Success { get; set; }
/// <summary>Compiled Assembly</summary>
public Assembly CompiledAssembly { get; set; }
/// <summary>Errors</summary>
public List<CompilationError> Errors { get; set; } = new();
/// <summary>Warnings</summary>
public List<string> Warnings { get; set; } = new();
/// <summary>
/// Code Formatted for Compilation
/// (After extracting and moving using statements, applying class/method wrapping)
/// </summary>
public string UpdatedCode { get; set; }
/// <summary>Flag for Security Violations</summary>
public bool HasSecurityViolations { get; set; }
/// <summary>Security Violation Details</summary>
public List<SecurityViolation> SecurityViolations { get; set; } = new();
/// <summary>Failure Reason Category</summary>
public CompilationFailureReason FailureReason { get; set; } = CompilationFailureReason.None;
/// <summary>
/// Types that could not be auto-resolved because multiple namespace candidates were found.
/// Key: type name, Value: list of candidate namespaces.
/// </summary>
public Dictionary<string, List<string>> AmbiguousTypeCandidates { get; set; } = new();
/// <summary>
/// Without this, callers cannot tell which namespaces were auto-resolved
/// and cannot advise users to include them for faster compilation.
/// </summary>
public List<string> AutoInjectedNamespaces { get; set; } = new();
/// <summary>
/// Keeps internal phase timings out of normal logs while allowing tests and
/// benchmarks to compare where time is spent inside the pipeline.
/// </summary>
public List<string> Timings { get; set; } = new();
public List<string> AdvisoryLogs { get; set; } = new();
public DynamicCompilationBackendKind CompilationBackendKind { get; set; } = DynamicCompilationBackendKind.Unknown;
}
/// <summary>
/// Compilation Failure Reason Categories
/// </summary>
public enum CompilationFailureReason
{
/// <summary>No Failure (Success)</summary>
None,
/// <summary>Compilation Error</summary>
CompilationError,
/// <summary>Security Violation</summary>
SecurityViolation,
/// <summary>Dynamic Assembly Addition Failed</summary>
DynamicAssemblyFailed,
/// <summary>Using Statement Addition Failed</summary>
UsingStatementFailed
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ae9eab1ed36274554be091d4c5e84c0b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,10 @@
namespace io.github.hatayama.uLoopMCP
{
public enum DynamicCompilationBackendKind
{
Unknown = 0,
SharedRoslynWorker = 1,
OneShotRoslyn = 2,
AssemblyBuilderFallback = 3
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bfefff852f0cc4b7c95ca26d2ce85217
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,54 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace io.github.hatayama.uLoopMCP
{
/// <summary>
/// Security Validation Result
///
/// Related Class: SecurityValidator
/// </summary>
public class SecurityValidationResult
{
/// <summary>Validation status</summary>
public bool IsValid { get; set; }
/// <summary>List of security violations</summary>
public List<SecurityViolation> Violations { get; set; } = new();
/// <summary>Compilation errors (extended)</summary>
public List<string> CompilationErrors { get; set; } = new();
/// <summary>
/// Retrieve error summary
/// </summary>
public string GetErrorSummary()
{
if (IsValid) return "No security violations detected.";
StringBuilder sb = new();
sb.AppendLine($"Security validation failed with {Violations.Count} violation(s):");
foreach (SecurityViolation violation in Violations)
{
sb.AppendLine($" - [{violation.Type}] {violation.Message}");
if (!string.IsNullOrEmpty(violation.ApiName))
{
sb.AppendLine($" API: {violation.ApiName}");
}
}
if (CompilationErrors?.Any() == true)
{
sb.AppendLine($"Compilation errors: {CompilationErrors.Count}");
foreach (string error in CompilationErrors)
{
sb.AppendLine($" - {error}");
}
}
return sb.ToString();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c25f4cda916ef4d17afc470c799ca0c2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,51 @@
namespace io.github.hatayama.uLoopMCP
{
/// <summary>
/// Security Violation
/// Related Classes: SecurityValidator, SecuritySyntaxWalker
/// </summary>
public class SecurityViolation
{
/// <summary>Violation Type</summary>
public SecurityViolationType Type { get; set; }
/// <summary>Description of the Security Violation</summary>
public string Description { get; set; }
/// <summary>Line Number where the Violation Occurred</summary>
public int LineNumber { get; set; }
/// <summary>Code Snippet Containing the Violation</summary>
public string CodeSnippet { get; set; }
/// <summary>Violation Message</summary>
public string Message { get; set; }
/// <summary>API Name Involved in the Violation</summary>
public string ApiName { get; set; }
/// <summary>Location of the Violation</summary>
public string Location { get; set; }
}
/// <summary>
/// Security Violation Types
/// </summary>
public enum SecurityViolationType
{
/// <summary>Using Declaration of a Forbidden Namespace</summary>
ForbiddenNamespace,
/// <summary>Dangerous API Call (Method, Property, etc.)</summary>
DangerousApiCall,
/// <summary>Inheritance from a Dangerous Class</summary>
DangerousInheritance,
/// <summary>Instantiation of a Dangerous Type</summary>
DangerousTypeCreation,
/// <summary>Unauthorized Reflection Usage</summary>
UnauthorizedReflection
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3a9141487fe1641d881e98b773e5acc2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d47205a1275834cd8985919c4ca82d52
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,111 @@
using System.Collections.Generic;
namespace io.github.hatayama.uLoopMCP
{
/// <summary>
/// Roslyn-independent catalog of dangerous API types and members.
/// Data stays shared because both preload and post-load validators must make identical decisions.
/// </summary>
public static class DangerousApiCatalog
{
private static readonly HashSet<string> DangerousTypes = new(System.StringComparer.Ordinal)
{
"System.IO.FileInfo",
"System.IO.DirectoryInfo",
"System.IO.FileSystemWatcher",
"System.Net.Http.HttpClient",
"System.Net.WebClient",
"System.Net.WebRequest",
"System.Net.Sockets.Socket",
"System.Net.Sockets.TcpClient",
"System.Net.Sockets.TcpListener",
"System.Net.Sockets.UdpClient",
"System.Net.Sockets.NetworkStream",
"System.Net.Dns",
"System.Net.IPAddress",
"System.Net.HttpWebRequest",
"System.Net.Security.SslStream",
"System.Net.WebSockets.ClientWebSocket",
"System.Net.WebSockets.WebSocket",
"System.Net.Mail.SmtpClient",
"System.Net.NetworkInformation.Ping",
"System.Net.Http.HttpClientHandler",
"System.Net.Http.HttpMessageHandler",
"System.Net.Http.SocketsHttpHandler",
"System.Diagnostics.ProcessStartInfo",
"System.Web.HttpContext",
"System.Web.HttpRequest",
"System.Web.HttpResponse",
"UnityEngine.Networking.UnityWebRequest",
"UnityEngine.Networking.NetworkTransport",
"System.Data.SqlClient.SqlConnection",
"System.Data.SqlClient.SqlCommand",
"System.Data.DataSet",
"System.Runtime.Remoting.RemotingConfiguration",
"System.Runtime.Remoting.RemotingServices",
"System.Security.Cryptography.X509Certificates.X509Certificate",
"System.Security.Cryptography.X509Certificates.X509Store"
};
private static readonly Dictionary<string, HashSet<string>> DangerousMembers = new(System.StringComparer.Ordinal)
{
["System.IO.File"] = new() { "Delete", "WriteAllText", "WriteAllBytes", "Replace", "OpenWrite", "AppendAllText", "AppendText", "SetAttributes", "SetCreationTime", "SetCreationTimeUtc", "SetLastAccessTime", "SetLastAccessTimeUtc", "SetLastWriteTime", "SetLastWriteTimeUtc" },
["System.IO.Directory"] = new() { "Delete", "SetCurrentDirectory" },
["System.Diagnostics.Process"] = new() { "Start", "Kill" },
["System.Reflection.Assembly"] = new() { "Load", "LoadFrom", "LoadFile", "LoadWithPartialName", "GetType" },
["System.Type"] = new() { "GetType" },
["System.Activator"] = new() { "CreateComInstanceFrom" },
["UnityEditor.AssetDatabase"] = new() { "DeleteAsset" },
["UnityEditor.FileUtil"] = new() { "DeleteFileOrDirectory" },
["System.Environment"] = new() { "Exit", "FailFast" },
["System.Threading.Thread"] = new() { "Abort", "Suspend", "Resume" },
["io.github.hatayama.uLoopMCP.DynamicCodeSecurityManager"] = new() { "InitializeFromSettings" },
["io.github.hatayama.uLoopMCP.ULoopSettings"] = new() { "SetDynamicCodeSecurityLevel" }
};
public static bool IsDangerousType(string fullTypeName)
{
if (string.IsNullOrEmpty(fullTypeName))
{
return false;
}
return DangerousTypes.Contains(fullTypeName);
}
public static bool IsDangerousApi(string fullTypeName, string memberName)
{
if (string.IsNullOrEmpty(fullTypeName) || string.IsNullOrEmpty(memberName))
{
return false;
}
if (DangerousTypes.Contains(fullTypeName))
{
return true;
}
if (DangerousMembers.TryGetValue(fullTypeName, out HashSet<string> members))
{
return members.Contains(memberName);
}
return false;
}
public static IEnumerable<string> EnumerateDangerousTypes()
{
return DangerousTypes;
}
public static IEnumerable<KeyValuePair<string, IReadOnlyCollection<string>>> EnumerateDangerousMembers()
{
foreach (KeyValuePair<string, HashSet<string>> entry in DangerousMembers)
{
yield return new KeyValuePair<string, IReadOnlyCollection<string>>(
entry.Key,
new List<string>(entry.Value));
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ce4ee0bcf3aba4cfc9a8301ed6418f82
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,21 @@
namespace io.github.hatayama.uLoopMCP
{
/// <summary>
/// Security level definitions for ExecuteDynamicCodeTool
/// Related classes: DynamicCodeSecurityManager, AssemblyReferencePolicy, RoslynCompiler
/// </summary>
public enum DynamicCodeSecurityLevel
{
/// <summary>
/// Dangerous APIs (System.IO, System.Net.Http, Process, reflection, etc.) are blocked.
/// Default level for safe Unity development.
/// </summary>
Restricted = 1,
/// <summary>
/// All APIs available without restrictions.
/// Warning: Security risks present - use only with trusted code.
/// </summary>
FullAccess = 2
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3535bbf0f49344452bf56a69aeb68d7d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,18 @@
{
"name": "uLoopMCP.Editor.Shared",
"rootNamespace": "",
"references": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": true,
"precompiledReferences": [
"Newtonsoft.Json.dll"
],
"autoReferenced": false,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 290394860909340b7835eb7cc215ee75
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: