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,165 @@
using System.Threading.Tasks;
using System.Threading;
namespace io.github.hatayama.uLoopMCP
{
/// <summary>
/// UseCase responsible for temporal cohesion of Domain Reload recovery processing
/// Processing sequence: 1. Pre-stop processing, 2. Recovery processing, 3. Notification processing
/// Related classes: DomainReloadDetectionService, SessionRecoveryService, ClientNotificationService
/// </summary>
public class DomainReloadRecoveryUseCase
{
/// <summary>
/// Execute processing before Domain Reload starts
/// </summary>
/// <param name="currentServer">Current server instance</param>
/// <returns>Processing result</returns>
public ServiceResult<string> ExecuteBeforeDomainReload(McpBridgeServer currentServer)
{
// 1. Generate tracking ID for related operations
string correlationId = VibeLogger.GenerateCorrelationId();
// 2. Check server state from instance
bool serverRunning = currentServer?.IsRunning ?? false;
int? serverPort = currentServer?.Port;
// 3. Fallback to session state if instance is null but session says server was running
// Handles case where mcpServer instance became null unexpectedly
if (currentServer == null && McpEditorSettings.GetIsServerRunning())
{
int sessionPort = McpEditorSettings.GetCustomPort();
if (NetworkUtility.IsValidPort(sessionPort))
{
serverRunning = true;
serverPort = sessionPort;
VibeLogger.LogWarning(
"domain_reload_session_fallback",
"Server instance is null but session state indicates running. Using session state for recovery.",
new { session_port = sessionPort },
correlationId
);
}
else
{
VibeLogger.LogWarning(
"domain_reload_session_fallback_invalid_port",
"Session indicates running but port is invalid. Ignoring session state fallback.",
new { session_port = sessionPort },
correlationId
);
}
}
// 4. Detect and record Domain Reload start
DomainReloadDetectionService.StartDomainReload(correlationId, serverRunning, serverPort);
// 4. If server is running, execute stop processing
if (currentServer?.IsRunning == true)
{
int portToSave = currentServer.Port;
try
{
// 4.1. Notify client of server stop
ClientNotificationService.LogServerStoppingBeforeDomainReload(correlationId, portToSave);
// 4.2. Stop server
currentServer.StopServerBeforeDomainReload();
// 4.3. Notify client of stop completion
ClientNotificationService.LogServerStoppedAfterDomainReload(correlationId);
return ServiceResult<string>.SuccessResult(correlationId);
}
catch (System.Exception ex)
{
// 4.4. Error notification
ClientNotificationService.LogServerShutdownError(correlationId, ex, portToSave);
DomainReloadDetectionService.RollbackDomainReloadStart(correlationId);
// Server stop failure is a critical error (causes port conflicts)
throw new System.InvalidOperationException(
$"Failed to properly shutdown MCP server before assembly reload. This may cause port conflicts on restart.", ex);
}
}
return ServiceResult<string>.SuccessResult(correlationId);
}
/// <summary>
/// Execute recovery processing after Domain Reload completion
/// </summary>
/// <returns>Processing result</returns>
public async Task<ServiceResult<string>> ExecuteAfterDomainReloadAsync(CancellationToken cancellationToken = default)
{
// 1. Generate tracking ID for related operations
string correlationId = VibeLogger.GenerateCorrelationId();
// 2. Record Domain Reload completion
DomainReloadDetectionService.CompleteDomainReload(correlationId);
// 3. Start timeout for reconnection UI display if needed
if (DomainReloadDetectionService.ShouldShowReconnectingUI())
{
SessionRecoveryService.StartReconnectionUITimeoutAsync().Forget();
}
// 4. Restore server state
ValidationResult restoreResult = SessionRecoveryService.RestoreServerStateIfNeeded();
if (!restoreResult.IsValid)
{
return ServiceResult<string>.FailureResult($"Server restoration failed: {restoreResult.ErrorMessage}");
}
// 5. Process pending compile requests (currently disabled)
ProcessPendingCompileRequests(correlationId);
// 6. Send tool change notification if server is running
if (McpServerController.IsServerRunning)
{
try
{
await ClientNotificationService.SendToolNotificationAfterCompilationAsync();
}
catch (System.Exception ex)
{
VibeLogger.LogWarning("tool_notification_failed", $"Failed to send tool notification: {ex.Message}", correlationId: correlationId);
}
}
return ServiceResult<string>.SuccessResult(correlationId);
}
/// <summary>
/// Process pending compile requests
/// Note: Currently disabled by feature flag (to avoid main thread errors)
/// </summary>
/// <param name="correlationId">Correlation ID for tracking related operations</param>
private void ProcessPendingCompileRequests(string correlationId)
{
// Feature flag control - currently disabled, can be enabled via editor settings in the future
// TODO: Add McpEditorSettings.GetEnablePendingCompileProcessing() when needed
bool enablePendingCompileProcessing = false;
if (enablePendingCompileProcessing)
{
// Planned to be enabled after main thread issue resolution
// CompileSessionState.StartForcedRecompile();
VibeLogger.LogInfo(
"pending_compile_processing",
"Processing pending compile requests",
correlationId: correlationId
);
}
else
{
VibeLogger.LogInfo(
"pending_compile_processing_disabled",
"Pending compile request processing is disabled via feature flag",
correlationId: correlationId
);
}
}
}
}

View File

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

View File

@@ -0,0 +1,155 @@
using System.Threading.Tasks;
using System.Threading;
namespace io.github.hatayama.uLoopMCP
{
/// <summary>
/// UseCase responsible for temporal cohesion of server initialization processing
/// Processing sequence: 1. Configuration validation, 2. Port allocation, 3. Server startup, 4. State update
/// Related classes: McpServerConfigurationService, PortAllocationService, McpServerStartupService, SecurityValidationService
/// </summary>
public class McpServerInitializationUseCase : AbstractUseCase<ServerInitializationSchema, ServerInitializationResponse>
{
private readonly McpServerConfigurationService _configService;
private readonly SecurityValidationService _securityService;
private readonly PortAllocationService _portService;
private readonly McpServerStartupService _startupService;
private readonly InitializationNotificationService _notificationService;
public McpServerInitializationUseCase()
{
_configService = new McpServerConfigurationService();
_securityService = new SecurityValidationService();
_portService = new PortAllocationService();
_startupService = new McpServerStartupService();
_notificationService = new InitializationNotificationService();
}
public McpServerInitializationUseCase(
McpServerConfigurationService configService,
SecurityValidationService securityService,
PortAllocationService portService,
McpServerStartupService startupService,
InitializationNotificationService notificationService)
{
_configService = configService ?? throw new System.ArgumentNullException(nameof(configService));
_securityService = securityService ?? throw new System.ArgumentNullException(nameof(securityService));
_portService = portService ?? throw new System.ArgumentNullException(nameof(portService));
_startupService = startupService ?? throw new System.ArgumentNullException(nameof(startupService));
_notificationService = notificationService ?? throw new System.ArgumentNullException(nameof(notificationService));
}
/// <summary>
/// Execute server initialization processing
/// </summary>
/// <param name="parameters">Initialization parameters</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Initialization result</returns>
public override Task<ServerInitializationResponse> ExecuteAsync(ServerInitializationSchema parameters, CancellationToken cancellationToken)
{
ServerInitializationResponse response = new ServerInitializationResponse();
try
{
// 1. Configuration validation - McpServerConfigurationService
var portResult = _configService.ResolvePort(parameters.Port);
if (!portResult.Success)
{
response.Success = false;
response.Message = portResult.ErrorMessage;
return Task.FromResult(response);
}
int actualPort = portResult.Data;
var validationResult = _configService.ValidateConfiguration(actualPort);
if (!validationResult.Success)
{
_notificationService.ShowInvalidPortDialog(actualPort);
response.Success = false;
response.Message = validationResult.ErrorMessage;
return Task.FromResult(response);
}
// 2. Security validation - SecurityValidationService
var editorStateValidation = _securityService.ValidateEditorState();
if (!editorStateValidation.IsValid)
{
response.Success = false;
response.Message = editorStateValidation.ErrorMessage;
return Task.FromResult(response);
}
var portSecurityValidation = _securityService.ValidatePortSecurity(actualPort);
if (!portSecurityValidation.IsValid)
{
response.Success = false;
response.Message = portSecurityValidation.ErrorMessage;
return Task.FromResult(response);
}
// 3. Port allocation - PortAllocationService
var availablePortResult = _portService.FindAvailablePort(actualPort);
if (!availablePortResult.Success)
{
response.Success = false;
response.Message = availablePortResult.ErrorMessage;
return Task.FromResult(response);
}
int availablePort = availablePortResult.Data;
// Handle port conflict
if (availablePort != actualPort)
{
var conflictResult = _portService.HandlePortConflict(actualPort, availablePort);
if (!conflictResult.Success || !conflictResult.Data)
{
response.Success = false;
response.Message = "Port conflict resolution cancelled by user";
return Task.FromResult(response);
}
}
// 4. Server startup - McpServerStartupService
var serverResult = _startupService.StartServer(
availablePort,
!parameters.PreserveStartupLockUntilExplicitRelease);
if (!serverResult.Success)
{
response.Success = false;
response.Message = serverResult.ErrorMessage;
return Task.FromResult(response);
}
McpBridgeServer serverInstance = serverResult.Data;
// 5. Session state update
string projectRootPath = UnityMcpPathResolver.GetProjectRoot();
var sessionUpdateResult =
_startupService.UpdateSessionState(true, availablePort, projectRootPath);
if (!sessionUpdateResult.Success)
{
response.Success = false;
response.Message = sessionUpdateResult.ErrorMessage;
return Task.FromResult(response);
}
// Success response
response.Success = true;
response.ServerPort = availablePort;
response.IsRunning = true;
response.ServerInstance = serverInstance;
response.Message = "Server initialization completed successfully";
return Task.FromResult(response);
}
catch (System.Exception ex)
{
// Log the full exception for debugging
UnityEngine.Debug.LogError($"Server initialization failed: {ex}");
response.Success = false;
response.Message = "Server initialization failed. Please check the logs for details.";
return Task.FromResult(response);
}
}
}
}

View File

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

View File

@@ -0,0 +1,99 @@
using System.Threading.Tasks;
using System.Threading;
namespace io.github.hatayama.uLoopMCP
{
/// <summary>
/// UseCase responsible for temporal cohesion of server shutdown processing
/// Processing sequence: 1. Server stop, 2. Session state clear, 3. Resource disposal
/// Related classes: McpServerStartupService, McpSessionManager
/// </summary>
public class McpServerShutdownUseCase : AbstractUseCase<ServerShutdownSchema, ServerShutdownResponse>
{
private readonly McpServerStartupService _startupService;
public McpServerShutdownUseCase(McpServerStartupService startupService)
{
_startupService = startupService ?? throw new System.ArgumentNullException(nameof(startupService));
}
/// <summary>
/// Execute server shutdown processing
/// </summary>
/// <param name="parameters">Shutdown parameters</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Shutdown result</returns>
public override Task<ServerShutdownResponse> ExecuteAsync(ServerShutdownSchema parameters, CancellationToken cancellationToken)
{
ServerShutdownResponse response = new ServerShutdownResponse();
try
{
cancellationToken.ThrowIfCancellationRequested();
// 1. Get current server instance
McpBridgeServer currentServer = McpServerController.CurrentServer;
if (currentServer == null)
{
response.Success = true;
response.Message = "Server was not running";
return Task.FromResult(response);
}
cancellationToken.ThrowIfCancellationRequested();
// 2. Server stop processing - McpServerStartupService
var stopResult = _startupService.StopServer(currentServer);
if (!stopResult.Success)
{
response.Success = false;
response.Message = stopResult.ErrorMessage;
return Task.FromResult(response);
}
cancellationToken.ThrowIfCancellationRequested();
// 3. Session state clear
var sessionUpdateResult = _startupService.UpdateSessionState(false);
if (!sessionUpdateResult.Success)
{
response.Success = false;
response.Message = sessionUpdateResult.ErrorMessage;
return Task.FromResult(response);
}
cancellationToken.ThrowIfCancellationRequested();
// 4. Session clear with SessionManager
try
{
McpEditorSettings.ClearServerSession();
}
catch (System.Exception sessionEx)
{
response.Success = false;
response.Message = $"Failed to clear session: {sessionEx.Message}";
return Task.FromResult(response);
}
// Success response
response.Success = true;
response.Message = "Server shutdown completed successfully";
}
catch (System.OperationCanceledException)
{
// Propagate cancellation exceptions
throw;
}
catch (System.Exception ex)
{
// Log the full exception for debugging
UnityEngine.Debug.LogError($"Server shutdown failed: {ex}");
response.Success = false;
response.Message = "Server shutdown failed. Please check the logs for details.";
}
return Task.FromResult(response);
}
}
}

View File

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