chore(unity): 固定Unity CLI Loop本地包并移除UnitySkills依赖
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using UnityEditor;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace io.github.hatayama.uLoopMCP
|
||||
{
|
||||
/// <summary>
|
||||
/// An entry for the MCP communication log.
|
||||
/// Log information that includes a request/response pair.
|
||||
/// </summary>
|
||||
public record McpCommunicationLogEntry
|
||||
{
|
||||
public readonly string CommandName;
|
||||
public readonly DateTime Timestamp;
|
||||
public readonly string Request;
|
||||
public readonly string Response;
|
||||
public readonly bool IsError;
|
||||
public bool IsExpanded { get; set; }
|
||||
|
||||
public string HeaderText => $"[{CommandName}: {Timestamp:HH:mm:ss}]";
|
||||
|
||||
public McpCommunicationLogEntry(string commandName, DateTime timestamp, string request, string response, bool isError, bool isExpanded = false)
|
||||
{
|
||||
CommandName = commandName;
|
||||
Timestamp = timestamp;
|
||||
Request = request;
|
||||
Response = response;
|
||||
IsError = isError;
|
||||
IsExpanded = isExpanded;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A class for managing MCP communication logs.
|
||||
/// </summary>
|
||||
public static class McpCommunicationLogger
|
||||
{
|
||||
// Note: Communication logs are now managed via McpSessionManager
|
||||
|
||||
private static List<McpCommunicationLogEntry> _logs;
|
||||
private static Dictionary<string, PendingRequest> _pendingRequests;
|
||||
|
||||
/// <summary>
|
||||
/// Event for when the log is updated (for UI updates).
|
||||
/// </summary>
|
||||
public static event Action OnLogUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Static constructor (automatically executed after a domain reload).
|
||||
/// </summary>
|
||||
static McpCommunicationLogger()
|
||||
{
|
||||
LoadFromSessionState();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Information about pending requests.
|
||||
/// </summary>
|
||||
private record PendingRequest
|
||||
{
|
||||
public readonly string CommandName;
|
||||
public readonly DateTime Timestamp;
|
||||
public readonly string RequestJson;
|
||||
|
||||
public PendingRequest(string commandName, DateTime timestamp, string requestJson)
|
||||
{
|
||||
CommandName = commandName;
|
||||
Timestamp = timestamp;
|
||||
RequestJson = requestJson;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all logs.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<McpCommunicationLogEntry> GetAllLogs()
|
||||
{
|
||||
lock (_logs)
|
||||
{
|
||||
return new List<McpCommunicationLogEntry>(_logs).AsReadOnly();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records a request (awaiting response).
|
||||
/// </summary>
|
||||
public static async Task LogRequest(string jsonRequest)
|
||||
{
|
||||
// Skip if communication logs are disabled
|
||||
if (!McpEditorSettings.GetSettings().enableCommunicationLogs)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
JObject request = JObject.Parse(jsonRequest);
|
||||
string method = request["method"]?.ToString() ?? "unknown";
|
||||
string id = NormalizeId(request["id"]);
|
||||
|
||||
PendingRequest pendingRequest = new(method, DateTime.Now, jsonRequest);
|
||||
|
||||
// Store in memory immediately (on any thread)
|
||||
lock (_pendingRequests)
|
||||
{
|
||||
_pendingRequests[id] = pendingRequest;
|
||||
}
|
||||
|
||||
// Switch to main thread for SessionState operations
|
||||
await MainThreadSwitcher.SwitchToMainThread();
|
||||
|
||||
SaveToSessionState();
|
||||
OnLogUpdated?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Records a response (adds it to the log paired with its request).
|
||||
/// </summary>
|
||||
public static async Task RecordLogResponse(string jsonResponse)
|
||||
{
|
||||
// Skip if communication logs are disabled
|
||||
if (!McpEditorSettings.GetSettings().enableCommunicationLogs)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
JObject response = JObject.Parse(jsonResponse);
|
||||
string id = NormalizeId(response["id"]);
|
||||
|
||||
PendingRequest pendingRequest;
|
||||
bool foundPendingRequest;
|
||||
|
||||
// Process response in memory immediately (on any thread)
|
||||
lock (_pendingRequests)
|
||||
{
|
||||
foundPendingRequest = _pendingRequests.TryGetValue(id, out pendingRequest);
|
||||
if (foundPendingRequest)
|
||||
{
|
||||
_pendingRequests.Remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
if (foundPendingRequest)
|
||||
{
|
||||
bool isError = response["error"] != null;
|
||||
|
||||
// Create new logs in a closed state.
|
||||
McpCommunicationLogEntry logEntry = new(
|
||||
pendingRequest.CommandName,
|
||||
pendingRequest.Timestamp,
|
||||
pendingRequest.RequestJson,
|
||||
jsonResponse,
|
||||
isError,
|
||||
false // Start with the toggle closed.
|
||||
);
|
||||
|
||||
lock (_logs)
|
||||
{
|
||||
// If there are existing logs, close them all (before adding a new log).
|
||||
foreach (McpCommunicationLogEntry existingLog in _logs)
|
||||
{
|
||||
existingLog.IsExpanded = false;
|
||||
}
|
||||
|
||||
_logs.Add(logEntry);
|
||||
|
||||
// Remove old entries if exceeding maximum
|
||||
while (_logs.Count > McpUIConstants.MAX_COMMUNICATION_LOG_ENTRIES)
|
||||
{
|
||||
_logs.RemoveAt(0); // Remove oldest entry
|
||||
}
|
||||
}
|
||||
|
||||
// Switch to main thread for SessionState operations
|
||||
await MainThreadSwitcher.SwitchToMainThread();
|
||||
|
||||
SaveToSessionState();
|
||||
OnLogUpdated?.Invoke();
|
||||
}
|
||||
else
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all logs.
|
||||
/// </summary>
|
||||
public static void ClearLogs()
|
||||
{
|
||||
lock (_logs)
|
||||
{
|
||||
_logs.Clear();
|
||||
}
|
||||
|
||||
lock (_pendingRequests)
|
||||
{
|
||||
_pendingRequests.Clear();
|
||||
}
|
||||
|
||||
// Also completely delete from SessionState and execute UI update on the main thread.
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
ClearLogSessionState();
|
||||
OnLogUpdated?.Invoke();
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores data from SessionState.
|
||||
/// </summary>
|
||||
private static void LoadFromSessionState()
|
||||
{
|
||||
// Restore logs.
|
||||
string logsJson = McpEditorSettings.GetCommunicationLogsJson();
|
||||
_logs = JsonConvert.DeserializeObject<List<McpCommunicationLogEntry>>(logsJson) ?? new List<McpCommunicationLogEntry>();
|
||||
|
||||
// Restore pending requests.
|
||||
string pendingJson = McpEditorSettings.GetPendingRequestsJson();
|
||||
_pendingRequests = JsonConvert.DeserializeObject<Dictionary<string, PendingRequest>>(pendingJson) ?? new Dictionary<string, PendingRequest>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves data to SessionState.
|
||||
/// </summary>
|
||||
public static void SaveToSessionState()
|
||||
{
|
||||
// Create snapshots to avoid collection modification during serialization
|
||||
List<McpCommunicationLogEntry> logsSnapshot;
|
||||
Dictionary<string, PendingRequest> pendingSnapshot;
|
||||
|
||||
lock (_logs)
|
||||
{
|
||||
logsSnapshot = new List<McpCommunicationLogEntry>(_logs);
|
||||
}
|
||||
|
||||
lock (_pendingRequests)
|
||||
{
|
||||
pendingSnapshot = new Dictionary<string, PendingRequest>(_pendingRequests);
|
||||
}
|
||||
|
||||
string logsJson = JsonConvert.SerializeObject(logsSnapshot);
|
||||
string pendingJson = JsonConvert.SerializeObject(pendingSnapshot);
|
||||
|
||||
McpEditorSettings.SetCommunicationLogsJson(logsJson);
|
||||
McpEditorSettings.SetPendingRequestsJson(pendingJson);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears SessionState related to communication logs (for fixing log issues).
|
||||
/// </summary>
|
||||
public static void ClearLogSessionState()
|
||||
{
|
||||
McpEditorSettings.ClearCommunicationLogs();
|
||||
|
||||
lock (_logs)
|
||||
{
|
||||
_logs?.Clear();
|
||||
}
|
||||
|
||||
lock (_pendingRequests)
|
||||
{
|
||||
_pendingRequests?.Clear();
|
||||
}
|
||||
|
||||
// SessionState clear complete (no log output).
|
||||
|
||||
// Notify UI of update.
|
||||
EditorApplication.delayCall += () => OnLogUpdated?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalize ID to consistent string representation for proper matching
|
||||
/// </summary>
|
||||
private static string NormalizeId(JToken idToken)
|
||||
{
|
||||
if (idToken == null)
|
||||
{
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
// Convert both string and numeric IDs to string for consistent matching
|
||||
return idToken.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user